From 5135ff2dc2e41ebc4cd39b4bccb21b32c6251db3 Mon Sep 17 00:00:00 2001 From: Sebastian Sumpf Date: Tue, 18 May 2021 12:52:09 +0200 Subject: [PATCH] usb_webcam: An app using libuvc for USB webcams issue #4158 --- repos/libports/src/app/usb_webcam/README | 26 ++ repos/libports/src/app/usb_webcam/main.cc | 274 ++++++++++++++++++++ repos/libports/src/app/usb_webcam/target.mk | 3 + 3 files changed, 303 insertions(+) create mode 100644 repos/libports/src/app/usb_webcam/README create mode 100644 repos/libports/src/app/usb_webcam/main.cc create mode 100644 repos/libports/src/app/usb_webcam/target.mk diff --git a/repos/libports/src/app/usb_webcam/README b/repos/libports/src/app/usb_webcam/README new file mode 100644 index 0000000000..a0d406cdc1 --- /dev/null +++ b/repos/libports/src/app/usb_webcam/README @@ -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 +------------- + +! +! +! 2018-01-01 00:01 +! +! +! +! + +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. + + diff --git a/repos/libports/src/app/usb_webcam/main.cc b/repos/libports/src/app/usb_webcam/main.cc new file mode 100644 index 0000000000..03f1198e9f --- /dev/null +++ b/repos/libports/src/app/usb_webcam/main.cc @@ -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 +#include +#include +#include +#include + +/* libuv */ +#include + +/* libuvc stuff */ +#include "libuvc/libuvc.h" +#include +#include + + +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 _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(); + + using Command = Gui::Session::Command; + using namespace Gui; + + _gui.enqueue(_view, Gui::Rect(Gui::Point(0, 0), _mode.area)); + _gui.enqueue(_view, Gui::Session::View_handle()); + _gui.enqueue(_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(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 { }; + Signal_handler
_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); +} diff --git a/repos/libports/src/app/usb_webcam/target.mk b/repos/libports/src/app/usb_webcam/target.mk new file mode 100644 index 0000000000..261aa0f768 --- /dev/null +++ b/repos/libports/src/app/usb_webcam/target.mk @@ -0,0 +1,3 @@ +TARGET := usb_webcam +SRC_CC := main.cc +LIBS := libuvc libyuv libc