usb_webcam: An app using libuvc for USB webcams

issue #4158
This commit is contained in:
Sebastian Sumpf 2021-05-18 12:52:09 +02:00 committed by Christian Helmuth
parent 4a56171a77
commit 5135ff2dc2
3 changed files with 303 additions and 0 deletions

View File

@ -0,0 +1,26 @@
A USB webcam app using libuvc
Behaviour
---------
The app requests an USB session for accessing the webcam and a GUI session in
order to render the camera into.
Configuration
-------------
! <config enabled="yes" width="320" height="240" format="yuv" fps="30">
! <vfs>
! <dir name="dev"> <log/> <inline name="rtc">2018-01-01 00:01</inline> </dir>
! <dir name="pipe"> <pipe/> </dir>
! </vfs>
! <libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" pipe="/pipe"/>
! </conifg>
The frame format can be configured via the config attributes "width", "height",
"format", "fps". Currently the "yuv" and "mjpeg" formats are supported. The
"enabled" attribute allows for dynamic power on/off of the camera at runtime. In
case there is no suitable frame format present, the app will dump the camera
supported formats during startup.

View File

@ -0,0 +1,274 @@
/*
* \brief USB webcam app using libuvc
* \author Josef Soentgen
* \author Sebastian Sumpf
* \date 2021-01-25
*
* The test component is based on the original 'example.c'.
*/
/*
* Copyright (C) 2021 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* Genode includes */
#include <base/attached_rom_dataspace.h>
#include <base/log.h>
#include <gui_session/connection.h>
#include <libc/component.h>
#include <os/pixel_rgb888.h>
/* libuv */
#include <libyuv/convert_argb.h>
/* libuvc stuff */
#include "libuvc/libuvc.h"
#include <stdio.h>
#include <unistd.h>
using namespace Genode;
class Viewer
{
private:
Viewer(const Viewer &) = delete;
const Viewer& operator=(const Viewer&) = delete;
using PT = Genode::Pixel_rgb888;
Genode::Env &_env;
Gui::Connection _gui { _env, "webcam" };
Gui::Session::View_handle _view { _gui.create_view() };
Framebuffer::Mode const _mode;
Constructible<Genode::Attached_dataspace> _fb_ds { };
uint8_t *_framebuffer { nullptr };
public:
Viewer(Genode::Env &env, Framebuffer::Mode mode)
:
_env { env },
_mode { mode }
{
_gui.buffer(mode, false);
_fb_ds.construct(_env.rm(), _gui.framebuffer()->dataspace());
_framebuffer = _fb_ds->local_addr<uint8_t>();
using Command = Gui::Session::Command;
using namespace Gui;
_gui.enqueue<Command::Geometry>(_view, Gui::Rect(Gui::Point(0, 0), _mode.area));
_gui.enqueue<Command::To_front>(_view, Gui::Session::View_handle());
_gui.enqueue<Command::Title>(_view, "webcam");
_gui.execute();
}
uint8_t *framebuffer() { return _framebuffer; }
void refresh() {
_gui.framebuffer()->refresh(0, 0, _mode.area.w(), _mode.area.h());
}
Framebuffer::Mode const &mode() { return _mode; }
};
static void cb(uvc_frame_t *frame, void *ptr)
{
Viewer *viewer = ptr ? reinterpret_cast<Viewer*>(ptr) : nullptr;
if (!viewer) return;
int err = 0;
int width = viewer->mode().area.w();
int height = viewer->mode().area.h();
switch (frame->frame_format) {
case UVC_COLOR_FORMAT_MJPEG:
err = libyuv::MJPGToARGB((uint8_t const *)frame->data,
frame->data_bytes,
viewer->framebuffer(),
width * 4,
width, height,
width, height);
if (err) {
error("MJPGToARGB returned:", err);
return;
}
break;
case UVC_COLOR_FORMAT_YUYV:
/* skip incomplete frames */
if (frame->data_bytes < width * height * 2ul)
break;
err = libyuv::YUY2ToARGB((uint8_t const *)frame->data,
width * 2,
viewer->framebuffer(),
width * 4,
width, height);
if (err) {
error("YUY2ToARGB returned:", err);
return;
}
break;
default:
return;
};
viewer->refresh();
}
class Webcam
{
private:
Webcam(const Webcam &) = delete;
const Webcam& operator=(const Webcam&) = delete;
Env &_env;
uvc_context_t *_context { nullptr };
uvc_device_t *_device { nullptr };
uvc_device_handle_t *_handle { nullptr };
Viewer _viewer;
void _cleanup()
{
Libc::with_libc([&] () {
if (_handle) uvc_stop_streaming(_handle);
if (_device) uvc_unref_device(_device);
if (_context) uvc_exit(_context);
});
}
public:
Webcam(Env &env, Framebuffer::Mode mode, uvc_frame_format format, unsigned fps)
:
_env(env), _viewer(env, mode)
{
int result = Libc::with_libc([&] () {
uvc_error_t res = uvc_init(&_context, NULL);
if (res < 0) {
uvc_perror(res, "uvc_init failed");
return -1;
}
res = uvc_find_device(_context, &_device, 0, 0, NULL);
if (res < 0) {
uvc_perror(res, "uvc_find_device failed");
return -2;
}
res = uvc_open(_device, &_handle);
if (res < 0) {
uvc_perror(res, "uvc_open failed");
return -3;
}
uvc_stream_ctrl_t control;
res = uvc_get_stream_ctrl_format_size(_handle, &control, format,
mode.area.w(), mode.area.h(),
fps);
if (res < 0) {
error("Unsupported mode: ", mode, " format: ", (unsigned)format, " fps: ", fps);
log("Supported modes: ");
uvc_print_diag(_handle, stderr);
return -4;
}
res = uvc_start_streaming(_handle, &control, cb, &_viewer, 0);
if (res < 0) {
uvc_perror(res, "Start streaming failed");
return -5;
}
/* e.g., turn on auto exposure */
uvc_set_ae_mode(_handle, 1);
return 0;
});
if (result < 0) {
_cleanup();
throw -1;
}
}
~Webcam()
{
_cleanup();
}
};
class Main
{
private:
Env &_env;
Attached_rom_dataspace _config_rom { _env, "config" };
Constructible<Webcam> _webcam { };
Signal_handler<Main> _config_sigh { _env.ep(), *this, &Main::_config_update };
void _config_update()
{
_config_rom.update();
if (_config_rom.valid() == false) return;
Xml_node config = _config_rom.xml();
bool enabled = config.attribute_value("enabled", false);
unsigned width = config.attribute_value("width", 640u);
unsigned height = config.attribute_value("height", 480u);
unsigned fps = config.attribute_value("fps", 15u);
String<8> format { config.attribute_value("format", String<8>("yuv")) };
uvc_frame_format frame_format;
if (format == "yuv")
frame_format = UVC_FRAME_FORMAT_YUYV;
else if (format == "mjpeg")
frame_format = UVC_FRAME_FORMAT_MJPEG;
else {
warning("Unkown format '", format, "' trying 'yuv'");
frame_format = UVC_FRAME_FORMAT_YUYV;
}
log("config: enabled: ", enabled, " ", width, "x", height, " format: ", (unsigned)frame_format);
if (_webcam.constructed())
_webcam.destruct();
if (enabled) {
try {
_webcam.construct(_env, Framebuffer::Mode {
.area = { width, height } }, frame_format, fps);
} catch (...) { }
}
}
public:
Main(Env &env)
:
_env(env)
{
_config_rom.sigh(_config_sigh);
_config_update();
}
};
void Libc::Component::construct(Libc::Env &env)
{
static Main main(env);
}

View File

@ -0,0 +1,3 @@
TARGET := usb_webcam
SRC_CC := main.cc
LIBS := libuvc libyuv libc