qemu-usb: adapt to webcam capture on/off changes

Issue #4287
This commit is contained in:
Christian Helmuth 2021-10-08 14:37:06 +02:00 committed by Norman Feske
parent 6f1d3862cd
commit d078f7db76
6 changed files with 52 additions and 80 deletions

View File

@ -33,28 +33,21 @@ when the timer triggers.
Usage webcam model attached to the xHCI model Usage webcam model attached to the xHCI model
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If the xHCI model is enabled and used, then the webcam device model can be If the xHCI model is enabled and used, the webcam device model can be
enabled by specifying a XML node in the config ROM: enabled by specifying a XML node in the config ROM.
<config ...> <config ...>
... ...
<webcam width="640" height="480" fps="15" vertical_flip="false" screen_size="false" report="false"/> <webcam width="640" height="480" fps="15" vertical_flip="false" screen_size="false"/>
... ...
</config> </config>
The webcam attributes are optional. The values shown above, are the default ones Webcam attributes are optional, default values are depicted above. The model
when a attribute is not specified. When the webcam node is specified, the model supports YUV2 and BGR3 image formats reported to the guest via bFormatIndex.
will open and will use a Genode capture session to obtain the It requests a Genode capture session to obtain the frames in the rate as
frames in the rate as specified by the frame per secondes (fps) attribute. The specified by the 'fps' (frame per seconds) attribute. The 'vertical_flip'
'vertical_flip' attribute specifies, whether the frames are shown flipped attribute specifies, whether the frames are shown flipped vertically for
for guests. For Windows guests the value has to be false, for Linux guests guests. For Windows guests the value has to be false, for Linux guests true.
true. The format supported by the model is YUV2 or BGR3. If the 'screen_size' is set If the 'screen_size' attribute is set to true, the webcam model configures the
to true, the webcam model will try to use the screen size as provided by the initial screen size to the size provided by the capture session. If the screen
capture session. If the screen size is invalid (e.g. 0x0), the attribute size is invalid (e.g. 0x0), 'width' and 'height' will be used as fallback.
values of 'width' and 'height' will be used instead. If the 'report' attribute
is set, a report will be generated whenever the guests starts/ends capturing and
if the capturing format choosen by the guest changes,
<capture enabled="..." format="BGR3"/>
or
<capture enabled="..." format="YUV2"/>

View File

@ -435,18 +435,6 @@ static void usb_webcam_handle_reset(USBDevice *dev)
usb_webcam_init_state(state); usb_webcam_init_state(state);
} }
static void usb_webcam_capture_state_changed(bool const on)
{
char const * format = "unknown";
if (vs_commit_state.bFormatIndex == DEVICE_VS_FORMAT_BGR)
format = "BGR3";
else if (vs_commit_state.bFormatIndex == DEVICE_VS_FORMAT_YUV)
format = "YUY2";
capture_state_changed(on, format);
}
static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * const p) static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * const p)
{ {
unsigned packet_size = vs_commit_state.dwMaxPayLoadTransferSize; unsigned packet_size = vs_commit_state.dwMaxPayLoadTransferSize;
@ -458,7 +446,7 @@ static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * co
if (packet_size <= sizeof(header)) { if (packet_size <= sizeof(header)) {
p->status = USB_RET_STALL; p->status = USB_RET_STALL;
if (state->capture) if (state->capture)
usb_webcam_capture_state_changed(false); capture_state_changed(false);
usb_webcam_init_state(state); usb_webcam_init_state(state);
return; return;
} }
@ -466,7 +454,7 @@ static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * co
if (state->bytes_frame >= max_frame_size(active_format())) { if (state->bytes_frame >= max_frame_size(active_format())) {
p->status = USB_RET_STALL; p->status = USB_RET_STALL;
if (state->capture) if (state->capture)
usb_webcam_capture_state_changed(false); capture_state_changed(false);
usb_webcam_init_state(state); usb_webcam_init_state(state);
return; return;
} }
@ -478,7 +466,7 @@ static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * co
/* check for capture state change */ /* check for capture state change */
if (!state->capture) { if (!state->capture) {
state->capture = true; state->capture = true;
usb_webcam_capture_state_changed(state->capture); capture_state_changed(state->capture);
} }
if (!state->timer_active) if (!state->timer_active)
@ -543,7 +531,7 @@ static void webcam_timeout(void *opague)
if (state->delay_packet || (state->watchdog && state->watchdog >= fps)) { if (state->delay_packet || (state->watchdog && state->watchdog >= fps)) {
state->capture = false; state->capture = false;
state->delay_packet = false; state->delay_packet = false;
usb_webcam_capture_state_changed(state->capture); capture_state_changed(state->capture);
} else { } else {
state->watchdog ++; state->watchdog ++;
webcam_start_timer(state); webcam_start_timer(state);
@ -625,7 +613,7 @@ static void usb_webcam_handle_control(USBDevice * const dev,
if (state->capture) { if (state->capture) {
state->capture = false; state->capture = false;
state->delay_packet = false; state->delay_packet = false;
usb_webcam_capture_state_changed(state->capture); capture_state_changed(state->capture);
} }
} }
stall = false; stall = false;
@ -689,7 +677,7 @@ static void usb_webcam_handle_control(USBDevice * const dev,
if (notify) { if (notify) {
USBWebcamState *state = USB_WEBCAM(dev); USBWebcamState *state = USB_WEBCAM(dev);
usb_webcam_capture_state_changed(state->capture); capture_state_changed(state->capture);
} }
} }
@ -719,7 +707,7 @@ static void usb_webcam_handle_data(USBDevice *dev, USBPacket *p)
if (!p->ep || p->ep->nr != DEVICE_EP_ID) { if (!p->ep || p->ep->nr != DEVICE_EP_ID) {
p->status = USB_RET_STALL; p->status = USB_RET_STALL;
if (state->capture) if (state->capture)
usb_webcam_capture_state_changed(false); capture_state_changed(false);
usb_webcam_init_state(state); usb_webcam_init_state(state);
return; return;
} }
@ -727,7 +715,7 @@ static void usb_webcam_handle_data(USBDevice *dev, USBPacket *p)
default: default:
p->status = USB_RET_STALL; p->status = USB_RET_STALL;
if (state->capture) if (state->capture)
usb_webcam_capture_state_changed(false); capture_state_changed(false);
usb_webcam_init_state(state); usb_webcam_init_state(state);
return; return;
} }

View File

@ -20,6 +20,6 @@ struct webcam_config {
extern void webcam_backend_config(struct webcam_config *); extern void webcam_backend_config(struct webcam_config *);
extern bool capture_bgr_frame(void * pixel); extern bool capture_bgr_frame(void * pixel);
extern bool capture_yuv_frame(void * pixel); extern bool capture_yuv_frame(void * pixel);
extern void capture_state_changed(bool on, char const * format); extern void capture_state_changed(bool on);
#endif /* _WEBCAM_BACKEND_H_ */ #endif /* _WEBCAM_BACKEND_H_ */

View File

@ -30,28 +30,27 @@ using namespace Genode;
struct Capture_webcam struct Capture_webcam
{ {
Env &_env; Env &_env;
Capture::Connection _capture { _env, "webcam" };
Gui::Area const _area; Gui::Area const _area;
bool const _vflip; bool const _vflip;
uint8_t const _fps; uint8_t const _fps;
bool _force_update { false }; bool _force_update { false };
Attached_dataspace _ds { _env.rm(), _capture.dataspace() };
Constructible<Reporter> _reporter { };
Constructible<Capture::Connection> _capture;
Constructible<Attached_dataspace> _ds;
Gui::Area setup_area(Gui::Area const area_in, bool const auto_area) Gui::Area _setup_area(Gui::Area const area_in, bool const auto_area)
{ {
Gui::Area area = area_in; Gui::Area area = area_in;
if (auto_area) { if (auto_area) {
area = _capture.screen_size(); Capture::Connection probe { _env, "webcam" };
area = probe.screen_size();
if (!area.valid()) if (!area.valid())
area = area_in; area = area_in;
} }
/* request setup of dataspace by server */
_capture.buffer(area);
return area; return area;
} }
@ -62,7 +61,7 @@ struct Capture_webcam
return false; return false;
bool changed = _force_update; bool changed = _force_update;
_capture.capture_at(Capture::Point(0, 0)).for_each_rect([&](auto) { _capture->capture_at(Capture::Point(0, 0)).for_each_rect([&](auto) {
changed = true; }); changed = true; });
if (!changed) if (!changed)
@ -71,7 +70,7 @@ struct Capture_webcam
int const src_stride_argb = _area.w() * 4; int const src_stride_argb = _area.w() * 4;
int const dst_stride_yuy2 = _area.w() * 2; int const dst_stride_yuy2 = _area.w() * 2;
libyuv::ARGBToYUY2(_ds.local_addr<uint8_t>(), src_stride_argb, libyuv::ARGBToYUY2(_ds->local_addr<uint8_t>(), src_stride_argb,
reinterpret_cast<uint8_t*>(frame), dst_stride_yuy2, reinterpret_cast<uint8_t*>(frame), dst_stride_yuy2,
_area.w(), _area.h()); _area.w(), _area.h());
@ -89,7 +88,7 @@ struct Capture_webcam
bool changed = false; bool changed = false;
uint8_t * const bgr = reinterpret_cast<uint8_t *>(frame); uint8_t * const bgr = reinterpret_cast<uint8_t *>(frame);
Pixel_rgb888 const * const data = _ds.local_addr<Pixel_rgb888>(); Pixel_rgb888 const * const data = _ds->local_addr<Pixel_rgb888>();
auto const &update_fn = ([&](auto &rect) { auto const &update_fn = ([&](auto &rect) {
changed = true; changed = true;
@ -110,52 +109,47 @@ struct Capture_webcam
/* update whole frame */ /* update whole frame */
_force_update = false; _force_update = false;
Rect const whole(Point(0,0), _area); Rect const whole(Point(0,0), _area);
_capture.capture_at(Capture::Point(0, 0)); _capture->capture_at(Capture::Point(0, 0));
update_fn(whole); update_fn(whole);
} else } else
_capture.capture_at(Capture::Point(0, 0)).for_each_rect(update_fn); _capture->capture_at(Capture::Point(0, 0)).for_each_rect(update_fn);
return changed; return changed;
} }
void capture_state_changed(bool on, char const * format) void capture_state_changed(bool on)
{ {
/* next time update whole frame due to format changes or on/off */ /* next time update whole frame due to format changes or on/off */
_force_update = true; _force_update = true;
if (!_reporter.constructed()) /* construct/destruct capture connection and dataspace */
return; if (on) {
_capture.construct(_env, "webcam");
Reporter::Xml_generator xml(*_reporter, [&] () { _capture->buffer(_area);
xml.attribute("enabled", on); _ds.construct(_env.rm(), _capture->dataspace());
xml.attribute("format", format); } else {
}); _ds.destruct();
_capture.destruct();
}
} }
Capture_webcam (Env &env, Gui::Area area, bool auto_area, bool flip, Capture_webcam(Env &env, Gui::Area area, bool auto_area, bool flip, uint8_t fps)
uint8_t fps, bool report)
: :
_env(env), _env(env),
_area(setup_area(area, auto_area)), _area(_setup_area(area, auto_area)),
_vflip(flip), _vflip(flip),
_fps(fps) _fps(fps)
{ {
if (report) {
_reporter.construct(_env, "capture");
_reporter->enabled(true);
}
log("USB webcam ", _area, " fps=", _fps, " vertical_flip=", log("USB webcam ", _area, " fps=", _fps, " vertical_flip=",
_vflip ? "yes" : "no", _vflip ? "yes" : "no");
" report=", _reporter.constructed() ? "enabled" : "disabled");
} }
}; };
static Genode::Constructible<Capture_webcam> capture; static Genode::Constructible<Capture_webcam> capture;
extern "C" void capture_state_changed(bool on, char const * format) extern "C" void capture_state_changed(bool on)
{ {
capture->capture_state_changed(on, format); capture->capture_state_changed(on);
} }
extern "C" bool capture_bgr_frame(void * pixel) extern "C" bool capture_bgr_frame(void * pixel)
@ -187,8 +181,7 @@ extern "C" void _type_init_host_webcam_register_types(Env &env,
webcam.attribute_value("height", 480u)), webcam.attribute_value("height", 480u)),
webcam.attribute_value("screen_size", false), webcam.attribute_value("screen_size", false),
webcam.attribute_value("vertical_flip", false), webcam.attribute_value("vertical_flip", false),
webcam.attribute_value("fps", 15u), webcam.attribute_value("fps", 15u));
webcam.attribute_value("report", false));
/* register webcam model, which will call webcam_backend_config() */ /* register webcam model, which will call webcam_backend_config() */
_type_init_usb_webcam_register_types(); _type_init_usb_webcam_register_types();

View File

@ -5,7 +5,6 @@
<file_system label="shared"/> <file_system label="shared"/>
<nic/> <nic/>
<capture/> <capture/>
<report label="capture"/>
<gui/> <gui/>
<rom label="capslock"/> <rom label="capslock"/>
<report label="shape"/> <report label="shape"/>
@ -67,7 +66,7 @@
<fs writeable="yes"/> <fs writeable="yes"/>
</vfs> </vfs>
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc"/> <libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc"/>
<webcam vertical_flip="true" fps="30" report="true" screen_size="false" width="640" height="480"/> <webcam vertical_flip="true" fps="30" screen_size="false" width="640" height="480"/>
</config> </config>
<route> <route>
<service name="Audio_out"> <parent/> </service> <service name="Audio_out"> <parent/> </service>
@ -81,7 +80,6 @@
<service name="Report" label="shape"> <parent label="shape"/> </service> <service name="Report" label="shape"> <parent label="shape"/> </service>
<service name="ROM" label="clipboard"> <parent label="clipboard"/> </service> <service name="ROM" label="clipboard"> <parent label="clipboard"/> </service>
<service name="Report" label="clipboard"> <parent label="clipboard"/> </service> <service name="Report" label="clipboard"> <parent label="clipboard"/> </service>
<service name="Report" label="capture"> <parent label="capture"/> </service>
<service name="Gui"> <parent label=""/> </service> <service name="Gui"> <parent label=""/> </service>
<any-service> <parent/> </any-service> <any-service> <parent/> </any-service>
</route> </route>

View File

@ -280,7 +280,7 @@ for { set i 1} { $i <= $use_vms } { incr i} {
} }
append_if [expr $use_webcam] config_of_app { append_if [expr $use_webcam] config_of_app {
<webcam vertical_flip="} $webcam_vflip {" fps="15" report="false" screen_size="false" width="640" height="480" />} <webcam vertical_flip="} $webcam_vflip {" fps="15" screen_size="false" width="640" height="480"/>}
append config_of_app { append config_of_app {
<libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" pipe="/pipe"/> <libc stdout="/dev/log" stderr="/dev/log" rtc="/dev/rtc" pipe="/pipe"/>