Introducción a la API Video for Linux


Contenidos

Introducción
Configuración de kernel Linux
Descripción de la API
Programa de ejemplo
Conclusiones
Referencias


Introducción

Video for Linux (V4L) es una interfaz de programación de aplicaciones (API) orientada a la captura de imágenes desde múltiples dispositivos, tales como tarjetas sintonizadoras de TV y webcams. Su principal objetivo es proveer de una interfaz común para una gran variedad de hardware, de manera tal que el código generado para una aplicación tenga un alto grado de portabilidad.

Actualmente se encuentra en desarrollo una versión más avanzada de V4L conocida como Video for Linux 2 (V4L2), la cual no será abordad en esta oportunidad. Si se requiere más información de V4L2 vea los links que aparecen en la referencia.

A partir del estudio de la API se pretende generar una pequeña aplicación a modo de integrar los conceptos y ejemplificar el uso de las funciones involucradas. Para llevar a cabo lo anterior se utilizará una cámara web creative USB sobre un sistema Linux Debian, con el kernel 2.4.22


Configuración de kernel Linux

Para poder programar aplicaciones, utilizando la API V4L, es imprescindible que el kernel del sistema que se va ha utilizar lo soporte.
 Casi la mayoría de las distribuciones de Linux traen, por defecto, soporte para V4L como módulo. Para confirmar esto, ejecute como superusuario en línea de comando, lo siguiente:
 # modprobe videodev

También es necesario que se encuentre el driver que maneja el dispositivo de video. En el caso específico de la cámara que se utilizará, su módulo se carga de la siguiente manera:
# modprobe ov511

Si por algún motivo no existiese alguno de los módulos anteriormente mencionados será necesario recompilar el kernel, lo cual no será abordado en este documento, ya que se supone el usuario conoce esos pasos .

El dispositivo de video que se utiliza tiene un acceso en el sistema de archivos llamado /dev/video0 , a través del cual se cual se realiza la comunicación. Si no existe tal archivo, se puede crear con las siguientes instrucciones en línea de comando:
mknod /dev/video0 c 81 0
chmod a+r /dev/video0
ln -s /dev/video0 /dev/video

Descripción de la API

En este documento se va a describir sólo el conjunto de llamadas de la API V4L utilizadas para desarrollar el proyecto, las restantes pueden ser consultadas en la API.

Para poder tener acceso al control de la cámara, V4L utiliza la función ioctl(), la cual recibe como argumento algunas de las instrucciones que se le quiere dar al dispositivo. La estructura de empleo de ioctl() es la siguiente:

ioctl(<descriptor_dispositivo>, <instrucción>, <direccion_estructura>)

VIDIOCGCAP se utiliza para obtener información acerca de las capacidades del dispositivo de video. Esta información es retornada a una estructura del tipo video_capability

ioctl(camara, VIDIOCGCAP, &capability)

VIDIOCGWIN y VIDIOCSWIN se encargan de manejar el área de captura. Ambas se diferencian en la letra G y S, donde G se refiere a “get” y S a “set”. Esta información se guarda en una estructura tipo video_window. Dentro de ésta se puede encontrar el tamaño de la imagen a capturar (ancho y alto), el cromakey, manipular el efecto clipping, entre otros.

VIDIOCGPICT y VIDIOCSPICT, son las responsables de las propiedades de la imagen. Su información se guarda en la estructura video_picture. Aquí puede manipularse el brillo, contraste, profundidad de bits, paletas de colores, entre otros.

Para realizar la captura de imágenes existen dos métodos. El más sencillo es utilizando la función read(), la cual, para ciertas aplicaciones, presenta una apreciable lentitud; en cambio, existe otro método mucho más rápido, el cual utiliza un acceso directo a memoria, a través de la función mmap().

Una vez mapeado el frame con VIDIOCGMBUF hay que generar una llamada a VIDIOCMCAPTURE y después VIDIOCSYNC para terminar el proceso de captura.

El siguiente diagrama permite comprender el proceso de captura de una imagen, o una secuencia de imágenes, con V4L.

Programa de ejemplo

Este es un programa básico, que permite obtener información, configurar los parámetros de captura y capturar un frame desde una cámara web. Para hacer más sencilla la explicación del programa, a continuación iremos describiendo este por partes. 1) Para que el programa reconozca todas las macros, estructuras y funciones, debemos incluir las siguientes librerias:

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <linux/videodev.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>

2) A continuación dentro de la función main definimos las variables y las estructuras que usaremos:

int main()
{
	struct video_capability cap;
	struct video_window captureWindow;
	struct video_window ask_captureWindow;
	struct video_picture imageProperties;
	int deviceHandle;
	int width;
	int height;
	int depth;
	int palette;
	char* deviceName = "/dev/video";

3) Luego abriremos el dispositivo /dev/video que es nuestro dispositivo desde el cual realizaremos la captura de imagen. Desde ahora el dispositivo podrá ser accedido mediante el descriptor de archivo deviceHandle, mediante llamadas a la función ioctl().

	deviceHandle = open (deviceName, O_RDWR);
	if (deviceHandle == -1)
	{
		printf("no se puede abrir el dispositivo");
	}

4) Ahora ocuparemos la función ioctl() con el flag VIDIOCGCAP, este le indica al dispositivo que entregue información sobre sus capacidades, llenado una estructura del tipo video_capability (cap en este caso). Por lo tanto la infamación después puede ser leída accediendo a los campos de esta estructura

if (ioctl (deviceHandle, VIDIOCGCAP, &cap) != -1)
{       // peticion exitosa
	printf("\nnombre = %s",cap.name);
	printf("\nnumero de canales = %d",cap.channels);
	printf("\nnumero de dispositivos de audio = %d",cap.audios);
	printf("\nmaximo ancho = %d",cap.maxwidth);
	printf("\nmaximo alto = %d",cap.maxheight);
	printf("\nminimo ancho = %d",cap.minwidth);
	printf("\nminimo alto = %d\n",cap.minheight);
}
else
{       // peticion fallida
	printf("\nPeticion fallida");
}

4) A continuación debemos averiguar si nuestro dispositivo permite capturar imágenes a memoria. Para esto se revisa si el bit VID_TYPE_CAPTURE del campo cap.type es verdadero o falso.

if ((cap.type & VID_TYPE_CAPTURE) != 0)
{
	printf("\nEsta targeta puede capturar video a memoria\n");
}
else
{
	printf("\nEsta targeta NO puede capturar video a memoria\n");
}

5) Luego debemos chequear si el dispositivo tiene la capacidad de escalar las imagines capturadas. Esto se hace revisando el bit VID_TYPE_SCALES del campo type de la estructura video_capability cap. Luego si se cumple lo anterior se configura la ventana de captura, esto se hace llenando los campos de la estructura del tipo video_window llamada captureWindow y luego llamando la funcion ioctl() con el flag VIDEOCSWIN, que indica que se esta configurando la ventana de captura.

if ((cap.type & VID_TYPE_SCALES) != 0)
{       // soporta escalamiento de imagenes

        captureWindow.x = 0;
        captureWindow.y = 0;
        captureWindow.width = cap.minwidth;
        captureWindow.height = cap.minheight;
        captureWindow.chromakey = 0;
        captureWindow.flags = 0;
        captureWindow.clips = 0;
        captureWindow.clipcount = 0;
        if (ioctl (deviceHandle, VIDIOCSWIN, &captureWindow) == -1)
        {       // could not set window values for capture
        	printf("\nNo se pudieron setear los valores para la ventana de captura");
	}
	else
	{
		printf("\nSe seteo exitosamente la ventana de captura\n");
	}
}

6) A continuación debemos revisar que la ventana de captura haya quedado bien configurada. Para lograr esto se usa ioctl() con el flag VIDEOCGWIN y la información será enviada en la estructura ask_capturewindow que es del tipo video_window.

//se pregunta por la ventana de captura seteada en la targeta

	if (ioctl (deviceHandle, VIDIOCGWIN, &ask_captureWindow) == -1)
	{       // could not obtain specifics of capture window
		printf("\nNo se pudo obtener informacion de la ventana de captura");
	}
	width = ask_captureWindow.width;
	height = ask_captureWindow.height;
	printf("\nVentana de captura:  ancho = %d",width);
	printf("\nVentana de captura:  alto = %d\n",height);

7) Luego debemos obtener información sobre las propiedades de la imagen. Para esto se llama la función ioctl() con el flag VIDIOCGPICT y la información es almacenada en la estructura video_picture imageProperties.

if (ioctl (deviceHandle, VIDIOCGPICT, &imageProperties) != -1)
{
	printf("\nSe obtuvo exitosamente informacion sobre las propiedades de la imagen\n");
        // los siguientes valores son para una escala de grises de 8 bits
        imageProperties.depth = 8;
        imageProperties.palette = VIDEO_PALETTE_GREY;
        if (ioctl (deviceHandle, VIDIOCSPICT, &imageProperties) == -1)
        {
        	printf("\nNo se pudieron setear las propiedades de la imagen");
	}
}

8) Ahora revisaremos que las opciones de la imagen quedaron configuradas. Esto se hace llamando la funcion ioctl() con el flag VIDIOCGPICT, que indica que se esta solicitando información sobre las propiedades de la imagen.

//revisando seteo de propiedades de la imagen


if (ioctl (deviceHandle, VIDIOCGPICT, &imageProperties) == -1)
{       // error al recibir propiedades de la imagen
	printf("\nError al obtener informacion sobre las propiedades de la imagen\n");
}
	depth = imageProperties.depth;
	palette = imageProperties.palette;
	printf("\nPropiedades de la imagen: depth= %d",depth);
	printf("\nPropiedades de la imagen: palette= %d\n",palette);

9) A continuación se pide información al dispositivo sobre la memoria, esta información después es muy importante ya que nos permite realizar el mapeo a memoria. La petición se hace ocupando el flag VIDIOCGBUF y se almacena en una estructura del tipo video_mbuf.

//obteniendo información para capturar un frame usando mapeo a memoria
	struct video_mbuf memoryBuffer;
	if (ioctl (deviceHandle, VIDIOCGMBUF, &memoryBuffer) == -1)
	{       // failed to retrieve information about capture memory space
		printf("\nNo se pudo obtener informacion de mapeo a memoria");
	}
	// obtain memory mapped area

10) A continuación se define un buffer que será del tipo (char *) y que nos permitirá realizar el mapeo a memoria. La función may se usa para configurar el mapeo a memoria, esta función retorna un puntero que hace referencia al área de memoria mapeada.

char* memoryMap;
memoryMap = (char*)mmap (0, memoryBuffer.size, PROT_READ | PROT_WRITE, MAP_SHARED, deviceHandle, 0);
if ((int)memoryMap == -1)
{       // failed to retrieve pointer to memory mapped area
	printf("\nNo se pudo recuperar puntero a memoria mapeada");
}

11) Ahora de define mmaps como una estructura del tipo (video_mmap *), de define como puntero por que el dispositivo de captura puede trabajar con varios frames al mismo tiempo. Como en este ejemplo solo se capturara un frame solo se llenaran los parámetros de la estructura mmaps[0], donde 0 es el numero del frame.

struct video_mmap* mmaps;
mmaps = (struct video_mmap*)(malloc (memoryBuffer.frames * sizeof (struct video_mmap)));

// fill out the fields

printf("\nEl dispositivo de captura soporta %d frames\n", memoryBuffer.frames);

mmaps[0].frame = i;
mmaps[0].width = width;
mmaps[0].height = height;
mmaps[0].format = palette;
printf("\nTamaño del frame = %d\n",memoryBuffer.size);

12) A continuación, se le indica al dispositivo que capture un frame, esto se hace usando la funcion ioctl() con el flag VIDIOCMCAPTURE y pasándole la dirección de la estructura video_mmap a usar en el frame.

//capturando
if (ioctl (deviceHandle, VIDIOCMCAPTURE, &mmaps[0]) == -1)
{       // capture request failed
	printf("\nError al capturar un frame\n");
}

13) Ahora falta indicarle al dispositivo que realicé el mapeo a memoria del frame, es en esta parte cuando se pasa la información de la imagen hacia la memoria, esto se consigue usando la función ioctl() con el flag VIDIOCSYNC y pasándole como parámetro la dirección de memoria donde se realizara el mapeo a memoria. Esta ultima será igual a la dirección del buffer memoryMap mas el offset correspondiente al frame, esto se debe a que en el buffer se pueden almacenar varios frames. La funcion ioctl() retorna cuando se ha terminado de copiar toda la información en el área de memoria.

i = -1;
while(i<0)
{
	i= ioctl(deviceHandle,VIDIOCSYNC,memoryMap+ memoryBuffer.offsets[0] );
	if(i < 0 && errno == EINTR) continue;
	if (i < 0)
	{
		perror ("VIDIOCSYNC");
		exit(-1);
	}
}

14) Finalmente nuestro programa retornara un puntero que indica la dirección de memoria donde esta almacenado el frame, que es una foto en escala de grises de 8 bits.

close (deviceHandle);
return(memoryMap+ memoryBuffer.offsets[0]);
}

Conclusiones

En este informe se explico como configurar el kernel y como usar, mediante lenguaje c, la API llamada Video For Linux. Esta API nos permite capturar imágenes desde una cámara web o una tarjeta capturadora y guardarlas en memoria. Esto nos abre un mundo de aplicaciones, que se pueden desarrollar en el área de procesamiento digital de imágenes.



Referencias

1) video4linux HQ
2) v4l Linux Video Capture standart
3) V4L2
4) BTTV page

Fecha de entrega: 29 de octubre del 2003
Autores: Mauricio Venegas Morales & Aquiles Yáñez C.
Profesor: Agustín González



Volver