VideoForLinux: Modelo de Programación


Copyright

Este artículo es Copyright 1998 de Juan Antonio Martínez Castaño y se distribuye bajo las siguientes condiciones:

Indice


Entradilla

En el número anterior de Linux Actual describimos cómo instalar y configurar desde linux el soporte para tarjetas de televisión, radio, etc, así como diverso software que utiliza estas capacidades. En el presente artículo profundizaremos en el API de VideoForLinux, describiendo su funcionamiento, explicando la metodología de programación y analizando ejemplos concretos

Introducción

Antes de empezar, vamos a definir una serie de términos y conceptos:

La figura 1 ilustra las diversas dependencias:

Modelo de dependencias en el API de v4l
Figura 1: Modelo de dependencias en el API de VideoForLinux

Aunque el sintonizador depende de cada canal de vídeo, la implementación en el núcleo puede realizarse -y de hecho se realiza- mediante un módulo independiente. Así, por ejemplo en el caso del driver bttv, existe un módulo tuner.o que se instala cuando uno de los canales existentes en la tarjeta corresponde a una señal de televisión sintonizable. ( por el contrario, este módulo no se instala si la tarjeta no dispone de sintonizador, como pueda ser en el caso de una tarjeta de videoconferencia )

Pasaremos a continuación a describir en detalle las estructuras y funciones definidas en el API de VideoForLinux


Descripciónd del API de VideoForLinux

Hagamos caso al dicho "Use the Source, Luke", y procedamos a analizar el fichero de definición del API de VideoForLinux: el fichero "videodev.h". El listado 1 ilustra la versión de este fichero tal y como aparece en la versión del núcleo linux-2.2.1-ac7

#ifndef __LINUX_VIDEODEV_H
#define __LINUX_VIDEODEV_H

#include 
#include 

#ifdef __KERNEL__

#if LINUX_VERSION_CODE >= 0x020100
#include 
#endif

struct video_device
{
	char name[32];
	int type;
	int hardware;

	int (*open)(struct video_device *, int mode);
	void (*close)(struct video_device *);
	long (*read)(struct video_device *, char *, unsigned long, int noblock);
	/* Do we need a write method ? */
	long (*write)(struct video_device *, const char *, unsigned long, int noblock);
#if LINUX_VERSION_CODE >= 0x020100
	unsigned int (*poll)(struct video_device *, struct file *, poll_table *);
#endif
	int (*ioctl)(struct video_device *, unsigned int , void *);
	int (*mmap)(struct video_device *, const char *, unsigned long);
	int (*initialize)(struct video_device *);	
	void *priv;		/* Used to be 'private' but that upsets C++ */
	int busy;
	int minor;
};

extern int videodev_init(void);
#define VIDEO_MAJOR	81
extern int video_register_device(struct video_device *, int type);

#define VFL_TYPE_GRABBER	0
#define VFL_TYPE_VBI		1
#define VFL_TYPE_RADIO		2
#define VFL_TYPE_VTX		3

extern void video_unregister_device(struct video_device *);
#endif


#define VID_TYPE_CAPTURE	1	/* Can capture */
#define VID_TYPE_TUNER		2	/* Can tune */
#define VID_TYPE_TELETEXT	4	/* Does teletext */
#define VID_TYPE_OVERLAY	8	/* Overlay onto frame buffer */
#define VID_TYPE_CHROMAKEY	16	/* Overlay by chromakey */
#define VID_TYPE_CLIPPING	32	/* Can clip */
#define VID_TYPE_FRAMERAM	64	/* Uses the frame buffer memory */
#define VID_TYPE_SCALES		128	/* Scalable */
#define VID_TYPE_MONOCHROME	256	/* Monochrome only */
#define VID_TYPE_SUBCAPTURE	512	/* Can capture subareas of the image */

struct video_capability
{
	char name[32];
	int type;
	int channels;	/* Num channels */
	int audios;	/* Num audio devices */
	int maxwidth;	/* Supported width */
	int maxheight;	/* And height */
	int minwidth;	/* Supported width */
	int minheight;	/* And height */
};


struct video_channel
{
	int channel;
	char name[32];
	int tuners;
	__u32  flags;
#define VIDEO_VC_TUNER		1	/* Channel has a tuner */
#define VIDEO_VC_AUDIO		2	/* Channel has audio */
	__u16  type;
#define VIDEO_TYPE_TV		1
#define VIDEO_TYPE_CAMERA	2	
	__u16 norm;			/* Norm set by channel */
};

struct video_tuner
{
	int tuner;
	char name[32];
	ulong rangelow, rangehigh;	/* Tuner range */
	__u32 flags;
#define VIDEO_TUNER_PAL		1
#define VIDEO_TUNER_NTSC	2
#define VIDEO_TUNER_SECAM	4
#define VIDEO_TUNER_LOW		8	/* Uses KHz not MHz */
#define VIDEO_TUNER_NORM	16	/* Tuner can set norm */
#define VIDEO_TUNER_STEREO_ON	128	/* Tuner is seeing stereo */
	__u16 mode;			/* PAL/NTSC/SECAM/OTHER */
#define VIDEO_MODE_PAL		0
#define VIDEO_MODE_NTSC		1
#define VIDEO_MODE_SECAM	2
#define VIDEO_MODE_AUTO		3
	__u16 signal;			/* Signal strength 16bit scale */
};

struct video_picture
{
	__u16	brightness;
	__u16	hue;
	__u16	colour;
	__u16	contrast;
	__u16	whiteness;	/* Black and white only */
	__u16	depth;		/* Capture depth */
	__u16   palette;	/* Palette in use */
#define VIDEO_PALETTE_GREY	1	/* Linear greyscale */
#define VIDEO_PALETTE_HI240	2	/* High 240 cube (BT848) */
#define VIDEO_PALETTE_RGB565	3	/* 565 16 bit RGB */
#define VIDEO_PALETTE_RGB24	4	/* 24bit RGB */
#define VIDEO_PALETTE_RGB32	5	/* 32bit RGB */	
#define VIDEO_PALETTE_RGB555	6	/* 555 15bit RGB */
#define VIDEO_PALETTE_YUV422	7	/* YUV422 capture */
#define VIDEO_PALETTE_YUYV	8
#define VIDEO_PALETTE_UYVY	9	/* The great thing about standards is ... */
#define VIDEO_PALETTE_YUV420	10
#define VIDEO_PALETTE_YUV411	11	/* YUV411 capture */
#define VIDEO_PALETTE_RAW	12	/* RAW capture (BT848) */
#define VIDEO_PALETTE_YUV422P	13	/* YUV 4:2:2 Planar */
#define VIDEO_PALETTE_YUV411P	14	/* YUV 4:1:1 Planar */
#define VIDEO_PALETTE_YUV420P	15	/* YUV 4:2:0 Planar */
#define VIDEO_PALETTE_YUV410P	16	/* YUV 4:1:0 Planar */
#define VIDEO_PALETTE_PLANAR	13	/* start of planar entries */
#define VIDEO_PALETTE_COMPONENT 7	/* start of component entries */
};

struct video_audio
{
	int	audio;		/* Audio channel */
	__u16	volume;		/* If settable */
	__u16	bass, treble;
	__u32	flags;
#define VIDEO_AUDIO_MUTE	1
#define VIDEO_AUDIO_MUTABLE	2
#define VIDEO_AUDIO_VOLUME	4
#define VIDEO_AUDIO_BASS	8
#define VIDEO_AUDIO_TREBLE	16	
	char    name[16];
#define VIDEO_SOUND_MONO	1
#define VIDEO_SOUND_STEREO	2
#define VIDEO_SOUND_LANG1	4
#define VIDEO_SOUND_LANG2	8
        __u16   mode;
        __u16	balance;	/* Stereo balance */
        __u16	step;		/* Step actual volume uses */
};

struct video_clip
{
	__s32	x,y;
	__s32	width, height;
	struct	video_clip *next;	/* For user use/driver use only */
};

struct video_window
{
	__u32	x,y;			/* Position of window */
	__u32	width,height;		/* Its size */
	__u32	chromakey;
	__u32	flags;
	struct	video_clip *clips;	/* Set only */
	int	clipcount;
#define VIDEO_WINDOW_INTERLACE	1
#define VIDEO_CLIP_BITMAP	-1
/* bitmap is 1024x625, a '1' bit represents a clipped pixel */
#define VIDEO_CLIPMAP_SIZE	(128 * 625)
};

struct video_capture
{
	__u32 	x,y;			/* Offsets into image */
	__u32	width, height;		/* Area to capture */
	__u16	decimation;		/* Decimation divder */
	__u16	flags;			/* Flags for capture */
#define VIDEO_CAPTURE_ODD		0	/* Temporal */
#define VIDEO_CAPTURE_EVEN		1
};

struct video_buffer
{
	void	*base;
	int	height,width;
	int	depth;
	int	bytesperline;
};

struct video_mmap
{
	unsigned	int frame;		/* Frame (0 - n) for double buffer */
	int		height,width;
	unsigned	int format;		/* should be VIDEO_PALETTE_* */
};

struct video_key
{
	__u8	key[8];
	__u32	flags;
};


#define VIDEO_MAX_FRAME		32

struct video_mbuf
{
	int	size;		/* Total memory to map */
	int	frames;		/* Frames */
	int	offsets[VIDEO_MAX_FRAME];
};
	

#define 	VIDEO_NO_UNIT	(-1)

	
struct video_unit
{
	int 	video;		/* Video minor */
	int	vbi;		/* VBI minor */
	int	radio;		/* Radio minor */
	int	audio;		/* Audio minor */
	int	teletext;	/* Teletext minor */
};

#define VIDIOCGCAP		_IOR('v',1,struct video_capability)	/* Get capabilities */
#define VIDIOCGCHAN		_IOWR('v',2,struct video_channel)	/* Get channel info (sources) */
#define VIDIOCSCHAN		_IOW('v',3,struct video_channel)	/* Set channel 	*/
#define VIDIOCGTUNER		_IOWR('v',4,struct video_tuner)		/* Get tuner abilities */
#define VIDIOCSTUNER		_IOW('v',5,struct video_tuner)		/* Tune the tuner for the current channel */
#define VIDIOCGPICT		_IOR('v',6,struct video_picture)	/* Get picture properties */
#define VIDIOCSPICT		_IOW('v',7,struct video_picture)	/* Set picture properties */
#define VIDIOCCAPTURE		_IOW('v',8,int)				/* Start, end capture */
#define VIDIOCGWIN		_IOR('v',9, struct video_window)	/* Set the video overlay window */
#define VIDIOCSWIN		_IOW('v',10, struct video_window)	/* Set the video overlay window */
						/* passes clip list for hardware smarts , chromakey etc */
#define VIDIOCGFBUF		_IOR('v',11, struct video_buffer)	/* Get frame buffer */
#define VIDIOCSFBUF		_IOW('v',12, struct video_buffer)	/* Set frame buffer - root only */
#define VIDIOCKEY		_IOR('v',13, struct video_key)		/* Video key event - to dev 255 is to all */
						/*  cuts capture on all DMA windows with this key (0xFFFFFFFF == all) */
#define VIDIOCGFREQ		_IOR('v',14, unsigned long)		/* Set tuner */
#define VIDIOCSFREQ		_IOW('v',15, unsigned long)		/* Set tuner */
#define VIDIOCGAUDIO		_IOR('v',16, struct video_audio)	/* Get audio info */
#define VIDIOCSAUDIO		_IOW('v',17, struct video_audio)	/* Audio source, mute etc */
#define VIDIOCSYNC		_IOW('v',18, int)			/* Sync with mmap grabbing */
#define VIDIOCMCAPTURE		_IOW('v',19, struct video_mmap)		/* Grab frames */
#define VIDIOCGMBUF		_IOR('v', 20, struct video_mbuf)	/* Memory map buffer info */
#define VIDIOCGUNIT		_IOR('v', 21, struct video_unit)	/* Get attached units */
#define VIDIOCGCAPTURE		_IOR('v',22, struct video_capture)	/* Get frame buffer */
#define VIDIOCSCAPTURE		_IOW('v',23, struct video_capture)	/* Set frame buffer - root only */

#define BASE_VIDIOCPRIVATE	192		/* 192-255 are private */


#define VID_HARDWARE_BT848	1
#define VID_HARDWARE_QCAM_BW	2
#define VID_HARDWARE_PMS	3
#define VID_HARDWARE_QCAM_C	4
#define VID_HARDWARE_PSEUDO	5
#define VID_HARDWARE_SAA5249	6
#define VID_HARDWARE_AZTECH	7
#define VID_HARDWARE_SF16MI	8
#define VID_HARDWARE_RTRACK	9
#define VID_HARDWARE_ZOLTRIX	10
#define VID_HARDWARE_SAA7146    11
#define VID_HARDWARE_VIDEUM	12	/* Reserved for Winnov videum */
#define VID_HARDWARE_RTRACK2	13
#define VID_HARDWARE_PERMEDIA2	14	/* Reserved for Permedia2 */
#define VID_HARDWARE_RIVA128	15	/* Reserved for RIVA 128 */
#define VID_HARDWARE_PLANB	16	/* PowerMac motherboard video-in */
#define VID_HARDWARE_BROADWAY	17	/* Broadway project */
#define VID_HARDWARE_GEMTEK	18
#define VID_HARDWARE_TYPHOON	19
#define VID_HARDWARE_VINO	20	/* Reserved for SGI Indy Vino */

/*
 *	Initialiser list
 */
 
struct video_init
{
	char *name;
	int (*init)(struct video_init *);
};

#endif
Listado 1: fichero /usr/include/linux/videodev.h

Lo primero que debemos hacer para manejar un dispositivo de vídeo es -aparte de abrirlo- es saber qué funcionalidades posee y cómo las podemos manejar. El parámetro VIDIOCGCAP de una llamada ioctl() al dispositivo nos rellena una estructura del tipo video_capability que nos indica:

El campo type de la estructura es un bitfield que nos indicará si el dispositivo puede capturar imágenes, si tiene sintonizador, si posee capacidades de teletexto, si es en blanco y negro o color, si puede capturar sub-ventanas, etc

La estructura video_capability nos proporciona información sobre las características de cada dispositivo de vídeo

Una vez vistas las capacidades del dispositivo es preciso analizar cada canal de que dispone hasta encontrar el deseado. para ello necesitamos invocar una llamada a ioctl() con el parámetro VIDIOCGCHAN pasando una estructura del tipo video_channel en la que hemos indicado el numero del canal sobre el que vamos a operar.
VIDIOCGCHAN nos proporciona información sobre:

Una vez conocidas las capacidades, de cada canal, una llamada a ioctl con el parámetro VIDIOCSCHAN, nos permitirá seleccionar el canal deseado sobre en el dispositivo escogido. Hay que hacer notar que cada canal puede tener sus diferencias ( por ejemplo, tener o no sintonizador ) por lo que el usuario deberá guardar los datos de cada canal de manera independiente. Lo usual ( como veremos en el ejemplo práctico ) es guardar una estructura de manejadores asociados a cada canal, resueltos mediante punteros a las posibles funciones asociadas; aunque en algunos casos, como por ejemplo el sintonizar una emisora en una entrada de vídeo compuesto, puedan ser punteros a funciones nulas

Finalmente, si el canal deseado dispone de sintonizador, otro ioctl(), VIDIOCGTUNER nos indica las características del sintonizador:

La función VIDIOCSTUNER sirve para activar el sintonizador deseado sobre el canal activo en cada momento, y para programar el modo de funcionamiento. Los datos se almacenan en una estructura de tipo video_tuner. Nótese que es precisa una llamada previa a VIDIOCSCHAN antes de poder utilizar un sintonizador

Es precisa la selección previa de un canal para poder ajustar el sintonizador asociado a dicho canal

Bueno, ya sabemos como adivinar qué es lo que podemos hacer... vamos ahora a manejar los dispositivos.
Normalmente, las operaciones habituales sobre un dispositivo son:

Para ello, necesitamos saber datos acerca de como manejar dicha información. VideoForLinux nos proporciona dos nuevos ioctls: VIDIOCGPICT y VIDIOCSPICT que nos dicen, y nos permiten ajustar: El programador debe llegar a un compromiso entre lo que su tarjeta gráfica y su tarjeta de vídeo son capaces de hacer, para ajustar el formato que más convenga. En el caso peor, esto implica que en ocasiones no se podrá hacer un mmap() directamente sobre la pantalla, sino que habrá que hacerlo sobre memoria principal, y de ahí mediante un proceso auxiliar, volcar a pantalla los datos.
Una llamada a VIDIOCSPICT, con unos parámetros especificados por el usuario en una estructura del tipo video_picture, no siempre tiene éxito. Es preciso que el programador vuelva a invocar a VIDIOCGPICT para ver cual ha sido el resultado final del ajuste

La estructura video_picture nos permite ajustar diversos parámetros de la imagen, tales como brillo, contraste, tinte, saturación, etc

Nos falta todavía un dato más: es preciso decirle al dispositivo cuál es la región sobre la que vamos a trabajar: Algunas tarjetas permiten capturar, no solo pantallas completas, sino regiones de pantalla. Es más: algunas, incluso permiten especificar rectángulos de imagen sobre los que no se debe realizar captura ( clipping ), permitiendo ahorro de CPU y de DMA. Por último podemos seleccionar capturar todos los frames, o bien escoger frames pares o impares. De nuevo nos encontramos con una pareja de ioctl()'s, VIDEOCGWIN y VIDIOCSWIN que nos manejan la estructura video_window

Ya estamos terminando: una vez deducidos y ajustados los valores,sólo nos queda invocar las "palabras mágicas":

Para capturar imágenes podemos selecionar dos modos de trabajo: mediante la función read(), o bien mapeando el vídeo en una región de memoria de nuestro sistema


Modelo de programación

Podemos resumir aquí los pasos que ha de seguir el programador para manejar con éxito su tarjeta de vídeo:

La figura 2 ilustra este proceso:

Modelo de programacion
Figura 2: Modelo de programación con el API VideoForLinux


Ejemplos

En el CD-Rom incluído en la revista, se puede encontrar la última versión existente en el momento de escribir este artículo, de los más conocidos paquetes de software de vídeo para Linux. Vamos a detenernos en el paquete xawtv-2.37, del cual hicimos una descripción en el número anterior de Linux Actual

Para evitar problemas en el estudio del código, repasaremos la versión fbtv, que no utiliza las X-Windows, sino el acceso directo al frame-buffer, una de las facilidades que ofrece el nuevo núcleo 2.2 de nuestro sistema operativo. Tenemos pues:

Por motivos de espacio, no es posible detallar aquí los ejemplos de manejo y programación de programas para sintonizar radio, y visualizar teletexto, asi como el API relacionado con ellos. Se remite al lector al estudio de la documentación y de los programas que se adjuntan en el CD-Rom que acompaña a esta revista

Una extensión de X-Windows, el Direct Graphic Access (DGA) nos permitirá "mapear" la imagen directamente en la pantalla del ordenador

Un último detalle: a la hora de mapear el buffer de vídeo sobre la pantalla, es preciso conocer las direcciones de memoria y los modos de vídeo disponibles. Cuando se trabaja desde X-Windows, las extensiones DGA ( Direct Graphics Access ) nos proporcionan la información deseada. Del mismo modo, cuando se trabaja con el FrameBuffer, una serie de ioctl()'s nos indicarán la información deseada.
El próximo número de Linux Actual, incluirá un artículo sobre la programación y el manejo de dispositvos frame buffer, presentes en el nuevo núcleo Linux 2.2, con lo que daremos por terminada la serie sobre programación de dispositivos multimedia con Linux


Conclusión

En el número anterior de Linux Actual, describimos la instalación, configuración y manejo de los diversos componentes de Video For Linux. En este número hemos hecho una descripción del API y del modelo de programación dando cuenta de las razones de la potencia y versatilidad de este componente de nuestro querido Sistema Operativo.

Baste decir que la evolución de VideoForLinux no está ni mucho menos detenida: Parece ser que por fin en breve plazo va a haber soporte para tarjetas multimedia de la casa ATI, que hasta ahora se había resistido a proporcionar detalles de funcionamiento.
Por otro lado tenemos el nuevo estandard de gestión multimedia: VideoForLinux 2, que contiene nuevas e interesantes opciones:

Video For Linux 2 será adoptado a partir de las nuevas versiones de desarrollo 2.3 del núcleo Linux. Por ahora está todavía en fase de definición de sus características definitivas, aunque ya hay software disponible que es compatible con el nuevo estándard

El nuevo núcleo de desarrollo Linux 2.3 incluirá el API de programación Video For Linux II

Hemos utilizado, por simplicidad, para la descrición del API de programación, las capacidades de frame-buffering del nuevo núcleo 2.2. En otro artículo de este número se describe dicho nuevo núcleo, sus mejoras y ampliaciones respecto a la versión anterior, y se hace una pequeña introducción al Linux Frame Buffer. En el próximo número de Linux Actual se hará una descripción exhaustiva de dicho dispositivo, detallando su funcionamiento, configuración y su modelo de programación. En la documentación del núcleo Linux, el lector encontrará suficientes referencias como para poder configurar e instalar dicho frame buffer


Referencias