qemu-usb: reduce cpu load by webcam model

if no new frame is available for capturing

Fixes #4078 #4196
This commit is contained in:
Alexander Boettcher 2021-06-04 15:22:49 +02:00 committed by Christian Helmuth
parent ff452619e3
commit 4aa99fd1a9
3 changed files with 73 additions and 33 deletions

View File

@ -42,6 +42,7 @@ typedef struct USBWebcamState {
uint8_t frame_toggle_bit; uint8_t frame_toggle_bit;
bool delay_packet; bool delay_packet;
bool capture; bool capture;
bool timer_active;
uint8_t watchdog; uint8_t watchdog;
uint8_t *frame_pixel; uint8_t *frame_pixel;
} USBWebcamState; } USBWebcamState;
@ -202,8 +203,8 @@ static struct {
uint8_t bpp; uint8_t bpp;
uint16_t width; uint16_t width;
uint16_t height; uint16_t height;
uint32_t interval; /* dwFrameInterval */ uint32_t interval; /* dwFrameInterval in 100ns units */
void (*capture)(void *); bool (*capture)(void *);
} formats [2]; } formats [2];
static unsigned active_format() static unsigned active_format()
@ -400,7 +401,8 @@ static Property webcam_properties[] = {
static void webcam_start_timer(USBWebcamState * const state) static void webcam_start_timer(USBWebcamState * const state)
{ {
int64_t const now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); state->timer_active = true;
uint64_t const now_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
timer_mod(state->timer, now_ns + 100ull * formats[active_format()].interval); timer_mod(state->timer, now_ns + 100ull * formats[active_format()].interval);
} }
@ -420,6 +422,7 @@ static void usb_webcam_init_state(USBWebcamState *state)
state->delay_packet = false; state->delay_packet = false;
state->capture = false; state->capture = false;
state->watchdog = 0; state->watchdog = 0;
state->timer_active = false;
} }
static void usb_webcam_handle_reset(USBDevice *dev) static void usb_webcam_handle_reset(USBDevice *dev)
@ -448,7 +451,6 @@ static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * co
{ {
unsigned packet_size = vs_commit_state.dwMaxPayLoadTransferSize; unsigned packet_size = vs_commit_state.dwMaxPayLoadTransferSize;
struct payload_header header = { .length = 0, .bfh = 0 }; struct payload_header header = { .length = 0, .bfh = 0 };
bool start_timer = !state->bytes_frame;
if (p->iov.size < packet_size) if (p->iov.size < packet_size)
packet_size = p->iov.size; packet_size = p->iov.size;
@ -470,19 +472,16 @@ static void usb_webcam_setup_packet(USBWebcamState * const state, USBPacket * co
} }
/* reset capture watchdog */ /* reset capture watchdog */
if (state->watchdog) { if (state->watchdog)
state->watchdog = 0; state->watchdog = 0;
start_timer = true;
}
/* check for capture state change */ /* check for capture state change */
if (!state->capture) { if (!state->capture) {
state->capture = true; state->capture = true;
start_timer = true;
usb_webcam_capture_state_changed(state->capture); usb_webcam_capture_state_changed(state->capture);
} }
if (start_timer) if (!state->timer_active)
webcam_start_timer(state); webcam_start_timer(state);
/* payload header */ /* payload header */
@ -533,6 +532,8 @@ static void webcam_timeout(void *opague)
USBDevice *dev = (USBDevice *)opague; USBDevice *dev = (USBDevice *)opague;
USBWebcamState *state = USB_WEBCAM(opague); USBWebcamState *state = USB_WEBCAM(opague);
state->timer_active = false;
if (!state->capture) if (!state->capture)
return; return;
@ -550,14 +551,18 @@ static void webcam_timeout(void *opague)
return; return;
} }
/* start timer before requesting new frame to account the cpu time */
webcam_start_timer(state);
/* request next frame pixel buffer */
if (!formats[active_format()].capture(state->frame_pixel))
return;
USBPacket *p = state->delayed_packet; USBPacket *p = state->delayed_packet;
state->delayed_packet = 0; state->delayed_packet = 0;
state->delay_packet = false; state->delay_packet = false;
/* request next frame pixel buffer */
formats[active_format()].capture(state->frame_pixel);
/* continue usb transmission with new frame */ /* continue usb transmission with new frame */
usb_webcam_setup_packet(state, p); usb_webcam_setup_packet(state, p);
if (p->status == USB_RET_SUCCESS) if (p->status == USB_RET_SUCCESS)

View File

@ -18,8 +18,8 @@ struct webcam_config {
}; };
extern void webcam_backend_config(struct webcam_config *); extern void webcam_backend_config(struct webcam_config *);
extern void capture_bgr_frame(void * pixel); extern bool capture_bgr_frame(void * pixel);
extern void 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, char const * format);
#endif /* _WEBCAM_BACKEND_H_ */ #endif /* _WEBCAM_BACKEND_H_ */

View File

@ -34,6 +34,7 @@ struct Capture_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 };
Attached_dataspace _ds { _env.rm(), _capture.dataspace() }; Attached_dataspace _ds { _env.rm(), _capture.dataspace() };
Constructible<Reporter> _reporter { }; Constructible<Reporter> _reporter { };
@ -54,9 +55,18 @@ struct Capture_webcam
return area; return area;
} }
void update_yuv(void *frame)
bool update_yuv(void *frame)
{ {
_capture.capture_at(Capture::Point(0, 0)); if (!_area.valid())
return false;
bool changed = _force_update;
_capture.capture_at(Capture::Point(0, 0)).for_each_rect([&](auto) {
changed = true; });
if (!changed)
return false;
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;
@ -64,30 +74,55 @@ struct Capture_webcam
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());
if (_force_update)
_force_update = false;
return true;
} }
void update_bgr(void *frame) bool update_bgr(void *frame)
{ {
_capture.capture_at(Capture::Point(0, 0)); if (!_area.valid())
return false;
uint8_t * const bgr = reinterpret_cast<uint8_t *>(frame); bool changed = false;
Pixel_rgb888 * data = reinterpret_cast<Pixel_rgb888 *>(_ds.local_addr<void>());
for (int y = 0; y < _area.h(); y++) { uint8_t * const bgr = reinterpret_cast<uint8_t *>(frame);
unsigned const row = _vflip ? y : _area.h() - 1 - y; Pixel_rgb888 const * const data = _ds.local_addr<Pixel_rgb888>();
unsigned const row_byte = (row * _area.w() * 3);
for (int x = 0; x < _area.w(); x++) {
bgr[row_byte + x * 3 + 0] = data->b();
bgr[row_byte + x * 3 + 1] = data->g();
bgr[row_byte + x * 3 + 2] = data->r();
data++; auto const &update_fn = ([&](auto &rect) {
changed = true;
for (int y = rect.y1(); y <= rect.y2(); y++) {
unsigned const row = _vflip ? y : _area.h() - 1 - y;
unsigned const row_byte = (row * _area.w() * 3);
for (int x = rect.x1(); x < rect.x2(); x++) {
auto &pixel = data[y * _area.w() + x];
bgr[row_byte + x * 3 + 0] = pixel.b();
bgr[row_byte + x * 3 + 1] = pixel.g();
bgr[row_byte + x * 3 + 2] = pixel.r();
}
} }
} });
if (_force_update) {
/* update whole frame */
_force_update = false;
Rect const whole(Point(0,0), _area);
_capture.capture_at(Capture::Point(0, 0));
update_fn(whole);
} else
_capture.capture_at(Capture::Point(0, 0)).for_each_rect(update_fn);
return changed;
} }
void capture_state_changed(bool on, char const * format) void capture_state_changed(bool on, char const * format)
{ {
/* next time update whole frame due to format changes or on/off */
_force_update = true;
if (!_reporter.constructed()) if (!_reporter.constructed())
return; return;
@ -123,14 +158,14 @@ extern "C" void capture_state_changed(bool on, char const * format)
capture->capture_state_changed(on, format); capture->capture_state_changed(on, format);
} }
extern "C" void capture_bgr_frame(void * pixel) extern "C" bool capture_bgr_frame(void * pixel)
{ {
capture->update_bgr(pixel); return capture->update_bgr(pixel);
} }
extern "C" void capture_yuv_frame(void * pixel) extern "C" bool capture_yuv_frame(void * pixel)
{ {
capture->update_yuv(pixel); return capture->update_yuv(pixel);
} }