Captura de video de cámaras USB en dispositivos Linux

Antecedentes


Hace algún tiempo, tuve la tentación de "mejorar" un tanque del conocido conjunto de "batalla de tanques", agregando la capacidad de jugar como si fuera un conductor de tanques. La idea apareció después de leer varios artículos sobre Habré (por ejemplo, aquí: geektimes.ru/post/257528), en ellos descubrí cómo se puede hacer esto con un pequeño enrutador WiFi y una cámara USB. La solución parecía cautivadoramente simple: el enrutador cuenta con un firmware especial, la cámara está conectada a él, el tanque está controlado por el control remoto nativo y el video se ve en el navegador. Después de ensamblar rápidamente el prototipo, descubrí que el video fue capturado con una calidad desagradable. Era 320x440x30 o 640x480x30. Cuando se activó el modo 1280x720, en el mejor de los casos hubo un video desgarrado con artefactos, en el peor de los casos, no lo fue en absoluto. El modo 1920x1080 no funcionó en principio. Esto me molestó mucho, ya que en una PC la cámara soportaba modos de hasta 1920x1080x30 y tenía compresión MJPG de hardware. Mi intuición sugirió que la implementación está lejos de ser perfecta.

Metas


  1. Video en resolución FullHD (1920X1080) o HD (1280x720) y velocidad de fotogramas normal (para que pueda reproducir).
  2. Tenía planeado dar el juguete a los niños, por lo que necesitaba un inicio automático y soporte para conectar / desconectar la cámara.

En general, quería algo como esto:



Limitaciones


No iba a buscar una solución que funcione siempre y en todas partes. Las siguientes restricciones me quedaron perfectamente:

  1. Buena señal wifi.
  2. Un número limitado de conexiones, se le dio prioridad al caso cuando solo hay un cliente.
  3. La cámara admite el modo MJPG.

HW y SW


  1. Videocámara Logitech B910 HD ( http://www.logitech.com/en-us/product/b910-hd-webcam ).
  2. Router TP-LINK TL-MR3020. Este bebé tiene el siguiente hardware: CPU MIPS 24K 400MHz, RAM 32 MiB, Flash 4 MiB, Ethernet 100 Mbit, USB 2.0 ( http://wiki.openwrt.org/en/toh/tp-link/tl-mr3020 ).
  3. . OR-WRT (http://roboforum.ru/wiki/OR-WRT), OpenWRT (http://openwrt.org/, 12.07 15.05).
  4. . , .
  5. “ ”.


En general, esta es una configuración realmente débil, especialmente si recuerda que un cuadro en el formato YUV420 de tamaño 1920X1080 toma 4 MiB (2 bytes por píxel). Me alentó que la cámara sea compatible con la compresión MJPG por hardware. Los experimentos han demostrado que un cuadro FullHD comprimido es típicamente <500 KiB. Entonces decidí continuar la investigación. Resultó que para capturar video y transmitirlo a través de HTTP, se usa mjpg-streamer (http://sourceforge.net/projects/mjpg-streamer/). El análisis de su código mostró que usa 1 transmisión para capturar video + una transmisión separada para cada cliente. Esta no es la mejor solución para un sistema de un solo núcleo, ya que requiere sincronización de subprocesos y memoria para la pila de cada subproceso. También copió fotogramas capturados. En general, mjpg-streamer se convirtió en sospechoso # 1.

Hallazgo interesante


Al estudiar mjpg-streamer, descubrí que la captura de video en Linux se realiza utilizando la biblioteca v4l2 y se utiliza una cola de búfer para capturarla. Mientras depuraba la inicialización de estos búferes en mjpg-streamer, descubrí que incluso para el modo MJPG su tamaño es muy grande e inesperadamente coincide con el tamaño del marco sin comprimir. Entonces comencé a sospechar que tendría que ingresar el código del controlador UVC, que es responsable de soportar las cámaras.

Análisis del código del conductor y primer éxito


Al estudiar el código, llegué a la conclusión de que la cámara pregunta el tamaño del búfer y mi cámara devolvió el tamaño del cuadro sin comprimir. Esta es probablemente la solución más segura desde el punto de vista de los desarrolladores de cámaras. Pero tampoco es óptimo. Decidí que para mi caso, puede ajustar el tamaño de búfer requerido utilizando la relación de compresión mínima experimental. Elegí k = 5. Con este valor, tenía un margen de aproximadamente el 20%.

Una pequeña digresión.
, JPG. . , .

El código del controlador UVC estaba listo para agregar varios tipos de soluciones "especiales", y encontré fácilmente un lugar para ajustar el tamaño del búfer (función uvc_fixup_video_ctrl ()). Además, el controlador admite un conjunto de peculiaridades que le permiten admitir cámaras con diversas desviaciones del estándar UVC. En general, los desarrolladores del controlador han hecho lo mejor posible para admitir las cámaras del zoológico.

Al agregar la corrección del tamaño del búfer, obtuve una operación estable en modo 1280x720 e incluso en modo 1920x1080. ¡Hurra! ¡La mitad del problema está resuelto!

Buscando nuevas aventuras


Un poco feliz con el primer éxito, recordé que mjpg-streamer está lejos de ser perfecto. Seguramente puedes hacer algo simple, no tan universal como mjpg-streamer, pero más adecuado para mis condiciones. Entonces decidí hacer uvc2http.

En mjpg-streamer, no me gustaba usar múltiples transmisiones y copiar buffers. Esto determinó la arquitectura de la solución: 1 flujo y sin copia. Usando IO sin bloqueo, esto se hace de manera bastante simple: captura el marco y envíalo al cliente sin copiarlo. Hay un pequeño problema: mientras enviamos datos desde el búfer, no podemos devolver el búfer a la cola. Y mientras el búfer no está en la cola, el controlador no puede poner un nuevo marco en él. Pero si el tamaño de la cola es> 1, entonces esto es posible. El número de buffers determina el número máximo de conexiones que se pueden garantizar para servir. Es decir, si quiero admitir con seguridad 1 cliente, entonces 3 buffers son suficientes (el controlador escribe en un búfer, los datos se envían desde el segundo, el tercero está en stock para evitar competir con el controlador por el búfer al intentar obtener un nuevo marco).

Uvc2http


Uvc2http consta de dos componentes: UvcGrabber y HttpStreamer. El primero es responsable de recibir buffers (tramas) de la cola y devolverlos a la cola. El segundo es responsable de servir a los clientes a través de HTTP. Hay un poco más de código que vincula estos componentes. Los detalles se pueden encontrar en la fuente.

Problema inesperado


Todo fue maravilloso: la aplicación funcionó y en la resolución de 1280x720 produjo más de 20 cuadros / segundo. Hice cambios cosméticos al código. Después de otro lote de cambios, medí la velocidad de fotogramas. El resultado fue deprimente: menos de 15 cuadros. Me apresuré a buscar lo que condujo a la degradación. Probablemente pasé 2 horas durante las cuales la frecuencia disminuyó con cada medición a un valor de 7 cuadros / seg. Diferentes pensamientos entraron en mi cabeza sobre la degradación debido al largo trabajo del enrutador, debido a su sobrecalentamiento. Era algo incomprensible. En algún momento, apagué la transmisión y vi que solo una captura (sin transmisión) daba los mismos 7 fotogramas. Incluso comencé a sospechar problemas con la cámara. En general, algunas tonterías. Era de noche y la cámara, por la ventana, mostró algo gris. Para cambiar la imagen sombría, encendí la cámara dentro de la habitación. Y he aquí!La velocidad de cuadros aumentó a 15 y entendí todo. La cámara ajustó automáticamente el tiempo de exposición y en algún momento este tiempo se hizo más largo que la duración del cuadro a una frecuencia dada. Durante estas dos horas, sucedió lo siguiente: al principio estaba oscureciendo gradualmente (era de noche), y luego encendí la cámara dentro de la habitación iluminada. Apuntando la cámara a la lámpara, obtuve más de 20 cuadros / segundo. ¡Hurra!


  1. . 1-1.5 .
  2. . , , qv4l2, . : - . .
  3. . USB , ( ) ( ). USB ( ).
  4. El enrutador tiene muy poca memoria y espacio en disco. Por esta razón, rechacé OR-WRT y compilé mi imagen OpenWRT, eliminando todo lo superfluo.

resultados


A continuación se muestra una placa con los resultados de comparar mjpg-streamer y uvc2http. En resumen, hay una ganancia significativa en el consumo de memoria y una pequeña ganancia en la velocidad de fotogramas y la utilización de la CPU.
1280x7201920x1080
VSZ, KB, 1 clienteVSZ, KB, 2 clientesCPU,%, 1 clienteCPU,%, 2 clientesFPS, f / s, 1 clienteFPS, f / s, 2 clientesVSZ, KB, 1 clienteVSZ, KB, 2 clientesCPU,%, 1 clienteCPU,%, 2 clientesFPS, f / s, 1 clienteFPS, f / s, 2 clientes
Mjpg-streamer1686019040264317,6quince254562581228cincuenta13,810
uvc2http3960396026432219,675767576284315,512,2

Y, por supuesto, el video que hice con los niños:



Foto del tanque resultante (resultó algo así como un carro gitano):



Utilizando


Las fuentes están aquí . Para usar en PC Linux, solo necesita compilar (siempre que no quiera parchear el controlador UVC). La utilidad se construye utilizando CMake de la manera estándar. Si necesita usarlo en OpenWRT, debe seguir pasos adicionales:

  1. Copie el contenido del directorio OpenWrt-15.05 en la raíz del repositorio OpenWRT. Estos archivos son solo para OpenWRT 15.05. Describen un nuevo paquete para OpenWRT y un parche para el controlador UVC.
  2. , quirk UVC_QUIRK_COMPRESSION_RATE uvc_driver.c. UVC. , wiki.openwrt.org/doc/devel/patches. uvc_ids. :

    /* Logitech B910 HD Webcam */
    	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
    				| USB_DEVICE_ID_MATCH_INT_INFO,
    	  .idVendor		= 0x046d,
    	  .idProduct		= 0x0823,
    	  .bInterfaceClass	= USB_CLASS_VIDEO,
    	  .bInterfaceSubClass	= 1,
    	  .bInterfaceProtocol	= 0,
    	  .driver_info		= UVC_QUIRK_RESTORE_CTRLS_ON_INIT
    				| UVC_QUIRK_COMPRESSION_RATE }, // Enable buffer correction for compressed modes
    

  3. OpenWRT (http://wiki.openwrt.org/doc/howto/build). uvc2http Multimedia.
  4. uvc2http ( ) . , .
  5. Instale el paquete en el dispositivo / actualice el sistema

Que sigue


La solución consta de dos partes: un parche de controlador y otro algoritmo de transmisión. El parche del controlador podría incluirse en la nueva versión del kernel de Linux, pero esta es una decisión controvertida, ya que se basa en el supuesto de una relación de compresión mínima. La utilidad, en mi opinión, es adecuada para su uso en sistemas débiles (juguetes, sistemas de videovigilancia en el hogar), y se puede mejorar ligeramente al agregar la capacidad de especificar la configuración de la cámara a través de los parámetros.

El algoritmo de transmisión se puede mejorar ya que hay un margen en la carga de la CPU y el ancho del canal (recibí fácilmente más de 50 MBit del enrutador que conecta diez clientes). También puede agregar soporte de sonido.

Source: https://habr.com/ru/post/es386273/


All Articles