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.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]); }
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.
Fecha de entrega: 29 de octubre del 2003
Autores: Mauricio Venegas Morales & Aquiles Yáñez C.
Profesor: Agustín González