-
Notifications
You must be signed in to change notification settings - Fork 7.6k
Description
Is your feature request related to a problem? Please describe.
Configuring and setting up video streams between devices is tedious (error-prone) and always the same (easy to automate).
Describe the solution you'd like
Provide video_connect(video_stream, video_in, video_out, fmt, num_bufs)
to check if both devices are ready, configure a device pair, allocate buffers, enqueue them, specify the format, setup the poll signals
Provide video_forward(video_stream)
that starts each device and run an endless loop with k_poll()
that enqueues/dequeues
Describe alternatives you've considered
Letting the application route buffers between video devices like in Linux.
Make the video_stream
above a global variable instead of argument
Additional context
Example implementation from #88839
zephyr/samples/drivers/video/sw_pipeline/src/main.c
Lines 34 to 206 in ff6694a
camera_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); | |
if (camera_dev == NULL || !device_is_ready(camera_dev)) { | |
LOG_ERR("%s is not ready or failed to initialize", camera_dev->name); | |
return -ENODEV; | |
} | |
pipe_dev = DEVICE_DT_GET(DT_NODELABEL(video_sw_pipeline)); | |
if (pipe_dev == NULL || !device_is_ready(pipe_dev)) { | |
LOG_ERR("%s is not ready or failed to initialize", pipe_dev->name); | |
return -ENODEV; | |
} | |
uvc_dev = DEVICE_DT_GET(DT_NODELABEL(uvc)); | |
if (uvc_dev == NULL || !device_is_ready(uvc_dev)) { | |
LOG_ERR("%s is not ready or failed to initialize", uvc_dev->name); | |
return -ENODEV; | |
} | |
video_sw_pipeline_set_source(pipe_dev, camera_dev); | |
uvc_set_video_dev(uvc_dev, pipe_dev); | |
sample_usbd = sample_usbd_init_device(NULL); | |
if (sample_usbd == NULL) { | |
return -ENODEV; | |
} | |
ret = usbd_enable(sample_usbd); | |
if (ret != 0) { | |
return ret; | |
} | |
LOG_INF("Waiting the host to select the video format"); | |
/* Get the video format once it is selected by the host */ | |
while (true) { | |
ret = video_get_format(uvc_dev, VIDEO_EP_IN, &fmt); | |
if (ret == 0) { | |
break; | |
} | |
if (ret != -EAGAIN) { | |
LOG_ERR("Failed to get the video format"); | |
return ret; | |
} | |
k_sleep(K_MSEC(10)); | |
} | |
LOG_INF("The host selected format %ux%u '%s'", | |
fmt.width, fmt.height, VIDEO_FOURCC_TO_STR(fmt.pixelformat)); | |
LOG_INF("Preparing %u buffers of %u bytes", | |
CONFIG_VIDEO_BUFFER_POOL_NUM_MAX, fmt.pitch * fmt.height); | |
/* Half of the buffers between the camera device and the pipeline device */ | |
for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX / 2; i++) { | |
vbuf = video_buffer_alloc(fmt.pitch * fmt.height, K_NO_WAIT); | |
if (vbuf == NULL) { | |
LOG_ERR("Failed to allocate the video buffer"); | |
return -ENOMEM; | |
} | |
ret = video_enqueue(camera_dev, VIDEO_EP_OUT, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer"); | |
return ret; | |
} | |
} | |
/* Half of the buffers between the pipeline device and the sink device */ | |
for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX / 2; i++) { | |
vbuf = video_buffer_alloc(fmt.pitch * fmt.height, K_NO_WAIT); | |
if (vbuf == NULL) { | |
LOG_ERR("Failed to allocate the video buffer"); | |
return -ENOMEM; | |
} | |
ret = video_enqueue(pipe_dev, VIDEO_EP_OUT, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer"); | |
return ret; | |
} | |
} | |
LOG_DBG("Preparing signaling for %s input/output", camera_dev->name); | |
k_poll_signal_init(&sig); | |
k_poll_event_init(&evt[0], K_POLL_TYPE_SIGNAL, K_POLL_MODE_NOTIFY_ONLY, &sig); | |
ret = video_set_signal(camera_dev, VIDEO_EP_OUT, &sig); | |
if (ret != 0) { | |
LOG_WRN("Failed to setup the signal on %s output endpoint", camera_dev->name); | |
timeout = K_MSEC(1); | |
} | |
ret = video_set_signal(pipe_dev, VIDEO_EP_OUT, &sig); | |
if (ret != 0) { | |
LOG_WRN("Failed to setup the signal on %s output endpoint", pipe_dev->name); | |
timeout = K_MSEC(1); | |
} | |
ret = video_set_signal(uvc_dev, VIDEO_EP_IN, &sig); | |
if (ret != 0) { | |
LOG_WRN("Failed to setup the signal on %s input endpoint", uvc_dev->name); | |
timeout = K_MSEC(1); | |
} | |
LOG_INF("Starting the video transfer"); | |
ret = video_stream_start(camera_dev); | |
if (ret != 0) { | |
LOG_ERR("Failed to start %s", camera_dev->name); | |
return ret; | |
} | |
ret = video_stream_start(pipe_dev); | |
if (ret != 0) { | |
LOG_ERR("Failed to start %s", pipe_dev->name); | |
return ret; | |
} | |
while (true) { | |
ret = k_poll(evt, ARRAY_SIZE(evt), timeout); | |
if (ret != 0 && ret != -EAGAIN) { | |
LOG_ERR("Poll exited with status %d", ret); | |
return ret; | |
} | |
if (video_dequeue(camera_dev, VIDEO_EP_OUT, &vbuf, K_NO_WAIT) == 0) { | |
LOG_DBG("Dequeued %p from %s, enqueueing to %s", | |
(void *)vbuf, camera_dev->name, uvc_dev->name); | |
ret = video_enqueue(pipe_dev, VIDEO_EP_IN, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer to %s", pipe_dev->name); | |
return ret; | |
} | |
} | |
if (video_dequeue(pipe_dev, VIDEO_EP_IN, &vbuf, K_NO_WAIT) == 0) { | |
LOG_DBG("Dequeued %p from %s, enqueueing to %s", | |
(void *)vbuf, pipe_dev->name, uvc_dev->name); | |
ret = video_enqueue(camera_dev, VIDEO_EP_OUT, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer to %s", uvc_dev->name); | |
return ret; | |
} | |
} | |
if (video_dequeue(pipe_dev, VIDEO_EP_OUT, &vbuf, K_NO_WAIT) == 0) { | |
LOG_DBG("Dequeued %p from %s, enqueueing to %s", | |
(void *)vbuf, pipe_dev->name, uvc_dev->name); | |
ret = video_enqueue(uvc_dev, VIDEO_EP_IN, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer to %s", uvc_dev->name); | |
return ret; | |
} | |
} | |
if (video_dequeue(uvc_dev, VIDEO_EP_IN, &vbuf, K_NO_WAIT) == 0) { | |
LOG_DBG("Dequeued %p from %s, enqueueing to %s", | |
(void *)vbuf, uvc_dev->name, pipe_dev->name); | |
ret = video_enqueue(pipe_dev, VIDEO_EP_OUT, vbuf); | |
if (ret != 0) { | |
LOG_ERR("Failed to enqueue video buffer to %s", pipe_dev->name); | |
return ret; | |
} | |
} | |
k_poll_signal_reset(&sig); | |
} |
In Linux, applications such as Gstreamer provide this feature.