全部博文(187)
分类: LINUX
2009-10-08 22:12:51
Video4linux 编程
2.1 Video4linux 简介
Video4Linux是为市场现在常见的电视捕获卡和并口及USB口的摄像头提供
统一的编程接口。同时也提供无线电通信和文字电视广播解码和垂直消隐的数据
接口。本文主要针对USB摄像头设备文件/dev/video0,进行视频图像采集方面的
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 4 -
程序设计。
2.2 Video4linux 编程指南
1.视频编程的流程
(1)打开视频设备:
(2)读取设备信息
(3)更改设备当前设置(可以不做)
(4)进行视频采集,两种方法:
a.内存映射
b.直接从设备读取
(5)对采集的视频进行处理
(6)关闭视频设备。
定义的数据结构及使用函数
struct _v4l_struct
{
int fd;
struct video_capability capability;
struct video_buffer buffer;
struct video_window window;
struct video_channel channel[8];
struct video_picture picture;
struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
};
typedef struct _v4l_struct v4l_device;
extern int v4l_open(char *, v4l_device *);
extern int v4l_close(v4l_device *);
extern int v4l_get_capability(v4l_device *);
extern int v4l_set_norm(v4l_device *, int);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_init(v4l_device *, int, int);
extern int v4l_grab_frame(v4l_device *, int);
extern int v4l_grab_sync(v4l_device *);
extern int v4l_mmap_init(v4l_device *);
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 5 -
extern int v4l_get_mbuf(v4l_device *);
extern int v4l_get_picture(v4l_device *);
extern int v4l_grab_picture(v4l_device *, unsigned int);
extern int v4l_set_buffer(v4l_device *);
extern int v4l_get_buffer(v4l_device *);
extern int v4l_switch_channel(v4l_device *, int);
3.Video4linux支持的数据结构及其用途
(1)video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨
率、信号源信息等)
name[32] 设备名称
maxwidth
maxheight
minwidth
minheight
Channels 信号源个数
type 是否能capture , 彩色还是黑白, 是否能裁剪等等。值如
VID_TYPE_CAPTURE等
(2)video_picture 设备采集的图象的各种属性
Brightness 0~65535
hue
colour
contrast
whiteness
depth 8 16 24 32
palette VIDEO_PALETTE_RGB24 | VIDEO_PALETTE_RGB565|
VIDEO_PALETTE_JPEG| VIDEO_PALETTE_RGB32
(3)video_channel 关于各个信号源的属性
Channel 信号源的编号
name
tuners
Type VIDEO_TYPE_TV | IDEO_TYPE_CAMERA
Norm 制式 PAL|NSTC|SECAM|AUTO
(4)video_window 包含关于capture area的信息
x x windows 中的坐标.
y y windows 中的坐标.
width The width of the image capture.
height The height of the image capture.
chromakey A host order RGB32 value for the chroma key.
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 6 -
flags Additional capture flags.
clips A list of clipping rectangles. (Set only)
clipcount The number of clipping rectangles. (Set only)
(5)video_mbuf 利用mmap进行映射的帧的信息
size 每帧大小
Frames 最多支持的帧数
Offsets 每帧相对基址的偏移
(6)video_mmap 用于mmap
4.关键步骤介绍
【注】接多个摄像头。方法如下:买一个usb hub接到开发板的usb host上。cat
/proc/devices可以知道video capture device的major是81,再ls –l /dev看到video0
的次设备号是0。两个摄像头当然要两个设备号,所以mknod /dev/video1 c 81 1,
如果接4个,就mknod /dev/video2 c 81 2,mknod /dev/video3 c 81 3。依次类推。
(1)打开视频:
int v4l_open(char *dev, v4l_device *vd)
{
if (!dev)
dev = ”/dev/video0”;
if ((vd ->fd = open(dev, O_RDWR)) < 0) {
perror("v4l_open:");
return -1;
}
if (v4l_get_capability(vd))
return -1;
if (v4l_get_picture(vd))
retu rn -1;
return 0;
}
(2)读video_capability 中信息
int v4l_get_capability(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGCAP, &(vd->capability)) < 0) {
perror("v4l_get_capability:");
return -1;
}
return 0;
}
成功后可读取vd->capability各分量
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 7 -
(3)读video_picture中信息
int v4l_get_picture(v4l_device *vd)
{
if (ioctl(vd ->fd, VIDIOCGPICT, &(vd->picture)) < 0) {
perror("v4l_get_picture:");
return -1;
}
return 0;
}
成功后可读取图像的属性
(4)改变video_picture中分量的值 (可以不做的)
先为分量赋新值,再调用VIDIOCSPICT
vd->picture.colour = 65535;
if(ioctl(vd->fd, VIDIOCSPICT, &(vd->picture)) < 0)
{
perror("VIDIOCSPICT");
return -1;
}
(5)初始化channel (可以不做的)
必须先做得到vd->capability中的信息
int v4l_get_channels(v4l_device *vd)
{
int i;
for (i = 0; i < vd ->capability.channels; i++) {
vd ->channel.channel = i;
if (ioctl(vd ->fd, VIDIOCGCHAN, &(vd->channel)) < 0) {
perror("v4l_get_channel:");
return -1;
}
}
return 0;
}
(6)关闭设备
int v4l_close(v4l_device *vd)
{
close(vd ->fd);
return 0;
}
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 8 -
重点:截取图象的两种方法
一、用mmap(内存映射)方式截取视频
mmap( )系统调用使得进程之间通过映射同一个普通文件实现共享内存。普
通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访
问,不必再调用read(),write()等操作。两个不同进程A、B共享内存的意思是,
同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进
程B对共享内存中数据的更新,反之亦然。
采用共享内存通信的一个显而易见的好处是效率高,因为进程可以直接读写
内存,而不需要任何数据的拷贝
(1)设置picture的属性
(2) 初始化video_mbuf,以得到所映射的buffer的信息
ioctl(vd->fd, VIDIOCGMBUF, &(vd->mbuf))
(3)可以修改video_mmap和帧状态的当前设置
(4)将mmap与video_mbuf绑定
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
len:映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始
算起
Prot:指定共享内存的访问权限 PROT_READ(可 读), PROT_WRITE (可写),
PROT_EXEC (可执行)
Flags:MAP_SHARED MAP_PRIVATE中必选一个,MAP_ FIXED不推荐使用
Addr:共内存享的起始地址,一般设0,表示由系统分配
Mmap( ) 返回值是系统实际分配的起始地址
int v4l_mmap_init(v4l_device *vd)
{
if (v4l_get_mbuf(vd) < 0)
return -1;
if ((vd ->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE,
MAP_SHARED, vd->fd, 0)) < 0) {
perror("v4 l_mmap_init:mmap");
return -1;
}
return 0;
}
(5)Mmap方式下真正做视频截取的 VIDIOCMCAPTURE
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 9 -
ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) ;
若调用成功,开始一帧的截取,是非阻塞的,
是否截取完毕留给VIDIOCSYNC来判断
(6)调用VIDIOCSYNC等待一帧截取结束
if(ioctl(vd->fd, VIDIOCSYNC, &frame) < 0)
{
perror("v4l_sync:VIDIOCSYNC");
return -1;
}
若成功,表明一帧截取已完成。可以开始做下一次 VIDIOCMCAPTURE
frame是当前截取的帧的序号。
********关于双缓冲************
video_bmuf bmuf.frames = 2;一帧被处理时可以采集另一帧
int frame; //当前采集的是哪一帧
int framestat[2]; //帧的状态 没开始采集|等待采集结束
帧的地址由vd->map + vd->mbuf.offsets[vd->frame]得到。
采集工作结束后调用munmap取消绑定
munmap(vd->map, vd->mbuf.size)
在实际应用时还可以采用缓冲队列等方式。
二、视频截取的第二种方法:直接读设备
关于缓冲大小,图象等的属性须由使用者事先设置
调用read();
int read (要访问的文件描述符;指向要读写的信息的指针;应该读写的字符数);
返回值为实际读写的字符数
int len ;
unsigned char
*vd->map=
(unsigned char *) malloc(vd◊capability.maxwidth*vd◊capability.maxheight );
len = read(vd◊fd,vd◊ vd->map,
vd◊capability.maxwidth*vd◊capability.maxheight*3 );
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 10 -
2.3 编程实例(mouse_capture)
不管是ov511还是zc301的摄像头,它们采集的方式都是相同的,只不过采集
到的数据有所差异,ov511的就是rgb的位流,而zc301是jpeg编码的位流。
mouse_capture是根据servfox改编的一个专门从zc301摄像头获取一张jpeg图片,
用来测试摄像头是否加载成功的小程序。这样就可以不用cat /dev/video0>1.jpg来
测试摄像头是否正常。cat命令一运行,就源源不断地采集jpeg流。但是采到的图
片只能显示第一个jpeg头和jpeg尾之间的数据。mouse_capture仅仅获得一张完整
的jpeg。 可以从
()
处下载参考。
现将主要函数的功能介绍如下:
static int GetVideoPict (struct vdIn *vd);//获取图片属性信息。
static int SetVideoPict (struct vdIn *vd);//设置图片属性。
static int isSpcaChip (const char *BridgeName);//测试芯片类型
static int GetStreamId (const char *BridgeName); //测试输出数据的格式
static int GetDepth (int format);//获取颜色深度。
void exit_fatal(char *messages);//错误显示。
int init_videoIn(struct vdIn *vd,char *device,int width,int height,int format,int
grabmethod);//初始化设备。
int convertframe(unsigned char *dst,unsigned char *src, int width,int height, int
formatIn, int size);//把共享缓冲区中的数据放到一个变量中,通知系统已获得一
帧。
int v4lGrab (struct vdIn *vd,char *filename );//从摄像头采集图片。
int close_v4l (struct vdIn *vd);//关闭摄像头。
int get_jpegsize (unsigned char *buf, int insize);//获取jpeg图片大小。
三 实例程序
3.1 LCD 实时显示从ov511 上采集的图像
参考HHARM9-EDU/applications/usbcam2lcd。从摄像头获取bmp位流直接显示
在framebuffer中。此程序图像的采集采用read的方式,注意由于lcd液晶屏显示的
是16bits的RGB图片,所以,ov511输出的图片格式也应该是16bits的RGB图片数
据,宏VIDEO_PALETTE_RGB565定义的就是16bits的RGB数据图片。而linux自
带的ov511驱动中图像采集是32位的,这样采集到的图片显示在lcd上是雪花点。
因此需要修改驱动。 在kernet/driver/usb/目录下有ov511芯片的驱动ov511.c,驱
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 11 -
动里的ov51x_set_default_params函数是设置芯片默认的输出图片的格式,该函数
中的
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
if (force_palette)
ov511->frame.format = force_palette;
else
ov511->frame.format = VIDEO_PALETTE_RGB24;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
部分语句是主要设置ov511默认输出图片格式的,其中maxwidth和maxheight
设置了图片的最大的宽度和高度。Ifelse语句设置了图片的格式,作如下的修改:
for (i = 0; i < OV511_NUMFRAMES; i++)
{
ov511->frame.width = ov511->maxwidth;
ov511->frame.height = ov511->maxheight;
ov511->frame.bytes_read = 0;
ov511->frame.format = VIDEO_PALETTE_RGB565;
ov511->frame.depth = ov511_get_depth(ov511->frame.format);
}
如果需要,也可以改变图片的默认输出大小。
3.2 LCD 实时显示从zc301 上采集的图像
编程思想:从摄像头采集到的图片存放在本地文件夹,通过minigui加载jpeg
来实现显示。
具体过程:
1.从网上下载jpegsrc-6b的jpeg库,交叉编译。
(1)./configure –enable-static –enable-shared –prefix=.libs
(2)修改Makefile,将编译器改成交叉编译器。
例如:我改成/opt/host/armv4l/bin/armv4l-unknown-linux-gcc
(3)make 后即在.libs目录中生成for arm的
libjpeg.a, libjpeg.la,libjpeg.so,libjpeg.so.62,libjpeg.so.62.0.0。将这些文件拷贝到系
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 12 -
统库文件目录,我的是/usr/lib中。
2.因为看从zc301采集的图片的二进制位流,jpeg头是ff d8 ff db。而在minigui库
文件libminigui的源文件src/mybmp/jpeg.c中,load_jpg和check_jpg的时候测试的头
位EXIF和JFIF两种格式的jpeg图片。这两种对应的二进制分别是ff d8 ff e1和ff d8
ff e0。所以我们minigui通过判断认为这是错误的jpeg格式而不加载,故无法显示。
实际上通过测试,在源码中去掉这两个判断就能正确加载。
3.交叉编译minigui
( 1 ) 编译库: ./configure --host=arm-unknown-linux --enable-jpgsupport=yes
--enable-pngsupport=no --enable-gifsupport=no --disable-lite
--prefix=/HHARM9-EDU/applications/minigui-free/nfsroot
--enable-smdk2410ial=yes
make
make install
(2)编译实例程序时,要加上jpeg库的支持,即在Makefile中加上-ljpeg。此时
将在nfsroot生成的库文件和可执行文件移到ramdisk.image.gz相应的目录下。(具
体参考华恒的2410开发手册)。
3.Minigui程序的编写
编程小技巧,我采取的方法是一刻不停地从摄像头采集到图片存储在
/tmp/1.jpg中,在minigui中通过loadbitmap函数来加载图片。而图片加载后不会自
动更新,不能自动根据1.jpg的改变自动变化。因此,我在程序中设定一个timer。
每隔100ms刷新屏幕,基本上实现实时更新了。而出现另外一个问题,刷新时会
以背景色来填充桌面,导致屏幕闪烁严重。故想到采用MSG_ERASEBKGND的
方式,用前一张图片做为刷新屏幕时的填充背景图片。这样就保证了lcd上图像
的连续性啦。
Minigui程序如下:其中一些自定义的函数跟mouse_capture中的一样,只是
变采集单幅到采集多幅。具体您可以自己改一下:)。也可以向我索取源码。
#include
#include
#include
#include
#include
#include "spcav4l.h"
#define IDTIMER 100
static BITMAP bmp;
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 13 -
static int LoadBmpWinProc(HWND hWnd, int message, WPARAM wParam,
LPARAM lParam)
{
HDC hdc;
RECT rc={0,0,240,320};
switch (message) {
case MSG_CREATE:
SetTimer(hWnd,IDTIMER,100);
return 0;
case MSG_ERASEBKGND:
{
RECT rcTemp;
if( LoadBitmap(HDC_SCREEN,&bmp,"/tmp/1.jpg"))
{
printf("load wrong!\n");
return -1;
}
GetClientRect(hWnd, &rcTemp);
hdc = BeginPaint (hWnd);
FillBoxWithBitmap (hdc, rcTemp.left, rcTemp.top, rcTemp.right-rcTemp.left,
rcTemp.bottom-rcTemp.top, &bmp);
EndPaint(hWnd, hdc);
return 0;
}
case MSG_TIMER:
InvalidateRect(hWnd,&rc,TRUE);
return 0;
case MSG_CLOSE:
UnloadBitmap (&bmp);
DestroyMainWindow (hWnd);
PostQuitMessage (hWnd);
return 0;
}
return DefaultMainWinProc(hWnd, message, wParam, lParam);
}
int MiniGUIMain (int argc, const char* argv[])
{
MSG Msg;
HWND hMainWnd;
MAINWINCREATE CreateInfo;
char videodevice[] = "/dev/video0";
char jpegfile[] = "/tmp/1.jpg";
int grabmethod = 0;
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 14 -
int format = VIDEO_PALETTE_JPEG;
int width = 240;
int height = 320;
int i;
#ifdef _LITE_VERSION
SetDesktopRect(0, 0, 1024, 768);
#endif
CreateInfo.dwStyle = WS_VISIBLE | WS_BORDER | WS_CAPTION;
CreateInfo.dwExStyle = WS_EX_NONE;
CreateInfo.spCaption = "Load and display a bitmap";
CreateInfo.hMenu = 0;
CreateInfo.hCursor = GetSystemCursor(0);
CreateInfo.hIcon = 0;
CreateInfo.MainWindowProc = LoadBmpWinProc;
CreateInfo.lx = 0;
CreateInfo.ty = 0;
CreateInfo.rx = 240;
CreateInfo.by = 320;
CreateInfo.iBkColor = PIXEL_lightwhite;
CreateInfo.dwAddData = 0;
CreateInfo.hHosting = HWND_DESKTOP;
hMainWnd = CreateMainWindow (&CreateInfo);
if (hMainWnd == HWND_INVALID)
return -1;
ShowWindow (hMainWnd, SW_SHOWNORMAL);
memset(&videoIn, 0, sizeof (struct vdIn));
if(init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) == 0)
{
printf("init is ok!\n");
}
else printf("init is wrong!\n");
while (GetMessage(&Msg, hMainWnd)) {
TranslateMessage(&Msg);
v4lGrab(&videoIn, jpegfile);
DispatchMessage(&Msg);
}
close_v4l (&videoIn);
MainWindowThreadCleanup (hMainWnd);
return 0;
}
#ifndef _LITE_VERSION
#include
#endif
基于Video4Linux 的USB 摄像头图像采集实现 戴小鼠 daily3
- 15 -
先写到这里吧,呵呵,希望能对您有所帮助。如果您在阅读的过程中发现问
题,欢迎和我交流。
2006-7-7 晚
参考文献
1.HHARM2410摄像头调试记录 华恒科技