Al trabajar con la plataforma RK3588 ejecutando múltiples flujos de video simultáneos (por ejemplo, 5 cámaras UVC 4K con codificación H.265), puede surgir un error crítico donde una de las señales de video se congela. Tras un análisis exhaustivo de los registros del kernel, se identifica que el problema reside en la entrega desordenada de las tramas desde el controlador hacia la capa de abstracción de hardware (HAL).
En el log del sistema se observa que, aunque el proceso uvc_video_next_buffers inicia el procesamiento de la trama 1890 antes que la 1891, la inserción en la cola final (buffer queue) ocurre de forma inversa: la trama 1891 finaliza y se entrega antes que la 1890.
// Ejemplo de traza de error en el kernel
[DEBUG] uvc_video_next_buffers: index: 2, seq: 1890
[DEBUG] uvcvideo: Frame complete (EOF) para trama 1891
[DEBUG] videobuf2_common: vb2_buffer_done: add tail en cola: index: 3, seq: 1891
[DEBUG] videobuf2_common: vb2_core_dqbuf: retornando trama 1891
[DEBUG] videobuf2_common: vb2_buffer_done: add tail en cola: index: 2, seq: 1890
[DEBUG] videobuf2_common: vb2_core_dqbuf: retornando trama 1890
Este comportamiento se propaga a la Camera HAL, que recibe las tramas fuera de secuencia. Debido a la naturaleza de la compresión H.265, las bibliotecas de decodificación no pueden procesar tramas desordenadas correctamente, lo que provoca excepciones y el consecuente bloqueo de la vista previa.
Análisis de las colas de trabajo (Workqueues)
El origen de esta inconsistencia se encuentra en el mecanismo de Workqueues del kernel de Linux, específicamente en la evolución hacia CMWQ (Concurrency Managed Workqueue). En el controlador UVC estándar (uvc_driver.c), la cola de trabajo se crea de la siguiente manera:
static struct uvc_streaming *init_uvc_stream(struct uvc_device *uvc_dev,
struct usb_interface *usb_intf)
{
struct uvc_streaming *v_stream;
v_stream = kzalloc(sizeof(*v_stream), GFP_KERNEL);
if (!v_stream)
return NULL;
mutex_init(&v_stream->mutex);
v_stream->dev = uvc_dev;
v_stream->intf = usb_get_intf(usb_intf);
v_stream->intfnum = usb_intf->cur_altsetting->desc.bInterfaceNumber;
/* Configuración original: permite ejecución concurrente multihilo */
v_stream->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI, 0);
if (!v_stream->async_wq) {
destroy_uvc_stream(v_stream);
return NULL;
}
return v_stream;
}
El uso de los flags WQ_UNBOUND y un max_active de 0 (que por defecto es 256) permite que las tareas se ejecuetn en cualquier núcleo de la CPU disponible. En un procesador multinúcleo como el RK3588, esto significa que la trama 1890 y la 1891 pueden procesarse en núcleos diferentes simultáneamente. Si el núcleo que maneja la trama 1891 tiene menos carga o es un núcleo de alto rendimiento, finalizará la tarea antes que el núcleo que procesa la trama 1890, rompiendo la secuencia temporal necesaria para el video.
Implementación de una ejecución secuencila
Para garantizar que las tramas se procesen en el orden estricto en que fueron capturadas, es necesario forzar a la cola de trabajo a comportarse de manera serializada. Esto se logra ajustando los parámetros de alloc_workqueue para que actúe como una single-thread workqueue ordenada.
La modificación recomendada en el controlador del kernel es la siguiente:
/* Modificación en drivers/media/usb/uvc/uvc_driver.c */
// Se reemplaza la asignación original por una que garantice el orden
// stream->async_wq = alloc_workqueue("uvcvideo", WQ_UNBOUND | WQ_HIGHPRI, 0);
v_stream->async_wq = alloc_workqueue("uvcvideo",
WQ_UNBOUND |
WQ_HIGHPRI |
__WQ_ORDERED |
__WQ_ORDERED_EXPLICIT |
WQ_MEM_RECLAIM,
1);
Al establecer max_active = 1 y utilizar el flag __WQ_ORDERED, el programador del kernel asegura que solo un ítem de trabajo se ejecute a la vez, manteniendo la integridad de la secuencia de tramas incluso en entornos de alto rendimiento. Tras aplicar este cambio, las pruebas de estrés con 5 cámaras simultáneas muestran un flujo constante de tramas H.265 sin pérdida de orden ni congelamientos en la decodificación.