BCNG 项 目 组
技 术 文 档
OV511视频采集设计文档
版 本:1.0
作 者:hgang
日 期:2008-07-12
目 录
一. 概述...
二. 编译内核与驱动加载...
1. 静态加载...
2. 动态加载...
三. 设计方案...
3. 定义的数据结构...
4. 视频编程流程及函数实现...
四. 调试过程...
五. 存在的问题和拟采取的解决方案...
一. 概述
本项目是在基于S3C2440的嵌入式开发板上实现视频图像采集,压缩,传输等功能,本文主要介绍视频采集部分。
视频采集使用的是带有OV511芯片的V2000摄像头,linux系统自带了OV511的驱动程序,所以无需另外安装驱动,只需在编译内核时加载进去
二. 编译内核与驱动加载
1. 静态加载
(1) 在/home/xiyong/bcng2440/linux-bcng2440-xiyong目录下make menuconfig。
(即板子的内核目录,如果要在PC上使用摄像头则在/usr/src/linux-2.4下操作)
(2) 首先(*)(“y”键)选择Multimedia device->下的Video for linux。加载video4linux模块,为视频采集设备提供编程接口;
(3) 然后在usb support->目录下(*)选择support for usb和usb camera ov511
support。这使得在内核中加入了对采用OV511接口芯片的USB数字摄像头的驱动支持。
(4) 保存配置退出。
(5) make dep;
(6) 修改/home/xiyong/bcng2440/linux-bcng2440-xiyong/drivers/media/video目录下
videodev.c程序(修改方法在后面调试过程部分有叙述)。
(7) make zImage,然后cp ./arch/arm/boot/zImage /tftpboot/;
2. 动态加载
(1) 在arm linux的kernel目录下make menuconfig。
(2) 首先<*>选择Multimedia device->下的Video for linux。
(3) 然后在usb support->目录下<*>选择support for usb和<M>选择usb camera
ov511 support。
(4) 保存退出。
(5) Make dep;
(6) 修改videodev.c程序。
(7) make zImage;make modules然后就在/driver/usb下生成ov511.o,同
时生成的zImage自动放在/tftpboot下。
(8) 然后用新内核启动板子后insmod ov511.o就可以成功加载。
我们使用的是静态加载;内核编译完以后板子的启动命令为t 30008000 zImage; go 30008000;
需要注意的是:
(1) 编译内核是要选择正确的路径,编译PC机内核路径是/usr/src/linux-2.4.18-14,编译板子是/home/xiyong/bcng2440/linux-bcng2440-xiyong。
(2) 注意“*”(y键)选和“M”选的不同。
三. 设计方案
3. 定义的数据结构
typedef struct v4l_struct
{
int fd;//设备名
struct video_capability capability;//设备信息
struct video_picture picture;//摄像头缓冲区内图像信息
struct video_window window;//窗口信息
struct video_mmap mmap;//用于内存映射
struct video_mbuf mbuf;//利用mmap进行映射的帧的信息
unsigned char *map;//用于存储映射得到的图片的首地址
int framestat[2];//定义帧状态
}v4l_device;
(1) video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨率、信号源信息等)
name[32] 设备名称
maxwidth
maxheight
minwidth
minheight
Channels 信号源个数
type 是否能capture , 彩色还是黑白, 是否能裁剪等等。值如VID_TYPE_CAPTURE等
可以调用
ioctl(vd->fd,VIDIOCGCAP,&(vd->capability)
来读取这些信息。
(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|VIDEO_PALETTE_YUV420等。
可以调用
ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))读取各信息,
ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))重新设置各信息分量。
(3) video_mbuf 利用mmap进行映射的帧的信息size 每帧大小
Frames 最多支持的帧数
Offsets 每帧相对基址的偏移
可以调用
ioctl(vd->fd, VIDIOCGMBUF, &vd->mbuf)
读取各信息分量。
这部分出现的问题:
(1) 在程序开始之时声明一个v4l_device结构之后一定要为其分配内存空间
v4l_device *vd;
vd=(v4l_device *)malloc(sizeof(v4l_device));
否则会导致问题,在读取vd->mbuf信息时vd->mbuf.offsets为一个非常大的负值,然后在读取映射图片的地址时(vd->map + vd->mbuf.offsets[frame])就会导致读取数据失败。
4. 视频编程流程及函数实现
(1)打开视频设备和输出文件:
char *devicename="/dev/video0";
if((vd->fd = open(devicename,O_RDWR))<0)
{
perror("v4l_open:");
return -1;
}//打开设备。
(2) 读取设备及图片信息
int v4l_get_capability(v4l_device *vd)
{
if(ioctl(vd->fd,VIDIOCGCAP,&(vd->capability))<0)
{
perror("v4l_get_capability:");
return -1;
}
if(ioctl(vd->fd, VIDIOCGWIN, &vd->window) != 0)
{
perror("ioctl (VIDIOCGWIN)");
return -1;
}
return 0;
}
int v4l_get_picture(v4l_device *vd)
{
if(ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))<0)
{
perror("v4l_get_picture:");
return -1;
}
(3)更改当前设置
int v4l_grab_init(v4l_device *vd)
{
vd->mmap.width=320;
vd->mmap.height=240;
vd->mmap.format=VIDEO_PALETTE_YUV420;
printf("vd->mmap.format%d/n",vd->mmap.format);
vd->mmap.frame=0;
vd->framestat[0]=0;
vd->framestat[1]=0;
vd->picture.brightness=19968;
vd->picture.hue=32768;
vd->picture.colour=65535;
vd->picture.contrast=22016;
vd->picture.whiteness=26880;
vd->picture.palette=vd->mmap.format;
ioctl(vd->fd,VIDIOCSPICT,&(vd->picture));
//ioctl(vd->fd,VIDIOCGPICT,&(vd->picture));
return 0;
}
这一步非常重要,如不注意会直接导致采集失败,应注意两点,一是一定要对vd->picture.palette进行设置,且与vd->mmap.format一致,这两处的功能是设置采集到的图
像格式,可根据需要格式进行设置。二是调用ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))设置之后切不可再次调用ioctl(vd->fd,VIDIOCGPICT,&(vd->picture)),否则会再次恢
复默认值。另外picture其他分量会影响图像的效果,可调整其值以达到最佳效果。
(4)进行视频采集(使用内存映射方法)
int v4l_mmap_init(v4l_device *vd)
{
if(ioctl(vd->fd, VIDIOCGMBUF, &vd->mbuf) == 0)
{
printf("video_mbuf:/nsize:%d/nframes:%d/noffsets:%d/n",vd->mbuf.size,
vd->mbuf.frames,vd->mbuf.offsets);
if((vd->map=mmap(0,vd->mbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0))<0)
/*将mmap与video_mbuf绑定。*/
{
perror("v4l_mmap_init:mmap");
return -1;
}
}
内存映射;关键函数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中必选一个,一般为前者;
Addr:共享内存的起始地址,一般设为0,表示由系统分配
int v4l_grab_start(v4l_device *vd,int frame)
{
vd->mmap.frame = frame;
if((ioctl(vd->fd,VIDIOCMCAPTURE,&(vd->mmap)))<0)
{
perror("v4l_grab_start:");
return -1;
}
vd->framestat[frame] = 1;
printf("grab_start successful/n");
return 0;
}
开始一帧的映射;
int v4l_grab_sync(v4l_device *vd,int frame)
{
if(ioctl(vd->fd,VIDIOCSYNC,&(vd->mmap.frame))<0)
{
perror("v4l_grab_sync:");
return -1;
}
vd->framestat[frame] = 0;
// printf("grab_sync successful/n");
return 0;
}
等待一帧映射结束;
unsigned char *v4l_getaddress(v4l_device *vd,int frame)
{
return (vd->map + vd->mbuf.offsets[frame]);
}
获取帧的地址;
(5)对采集的视频进行处理
if((outfile=open("/tmp/temp.YUV",O_WRONLY | O_CREAT))<0)
{
perror("open outfile error");
return -1;
}
size=(vd->mmap.width * vd->mmap.height * 3)/2;
if(write(outfile,buffer,size)<0)//将内存中的图片信息写入文件中。
{
printf("write outfile error");
return -1;
}
需要注意的是:
1. 打开输出文件的目录因该是板子上的目录,而不是PC机上的,因为程序运行是在板子上运行,所以它寻找路径是从板子上的根目录开始,而与pc机上的目录无关。
2. 对于YUV420格式,size的大小一定要正确,因为此格式图像各分量是分块排列,size错误会导致图像数据整个错误。
(6)关闭视频设备。
close(outfile);
munmap(vd->map,vd->mbuf.size);//取消映射,对应mmap。
close(vd->fd);
此处取消映射一定要在写文件之后,否则会导致数据丢失而无法写入。
单帧采集时只需设置frame=0;同时无需定义帧状态(vd->framestat[frame]),vd->map直接得到帧数据起始地址;而连续帧采集时,由读取的vd->mbuf.frames=2可以知道内存映
射时最多支持两帧,定义int vd->framestat[2]表示帧状态,用frame=0 or1表示当前为那一帧,通过改变vd->framestat和frame的值来进行双缓冲,即采集一帧时处理另一帧,同
时加一个外循环达到连续多帧的采集,每帧的地址都是通过vd->map + vd->mbuf.offsets[frame]得到,值得注意的是,由于偏移地址只有vd->mbuf.offsets[0] 和 vd-
>mbuf.offsets[1], 在采集完下一帧之前必须将当前帧处理完毕,否则再次采集到的新数据会将当前数据覆盖。
四. 调试过程
1. 打开设备出错,
提示信息:
not such device or directory.
原因:没有建立文件节点,可先用cat/proc/devices查看到video caprure device的主设备号是81,再ls –l /dev看到video0次设备号为0.然后mknod /dev/video 0 c 81 0;建立
设备节点。
Open v4l::No such devices
Mknod后用cat查看还是没有81设备号;
原因:编译内核时/home/xiyong/bcng2440/linux-bcng2440-xiyong/drivers/media/video目录下
videodev.c程序没有改动,
以前的代码是通过MODULE的方式运行的,
#ifdef MODULE
int init_module(void)
{
return videodev_init();
}
现在修改为
module_init(videodev_init);
module_exit(videodev_exit);
但是发现还是没有运行,又发现需要在函数前增加_init的标记才能运行
static int __init
videodev_init(void)
现在能够运行了,在启动信息中可以发现如下的信息:
Video for Linux One (2.2.16). Major device: 81
Video for Linux Two (V0.20). Major device: 81
使用cat /proc/devices可以看到设备名字:81 v4l1/2
2. 读取设备信息出错,
提示信息:
VIDIOCMCAPTURE: invalid format (Unknown)
原因:没有对vd->mmap各参数进行设置,只需对其width,height,format等设置相应值就行了。
3. 打开输出文件出错,
提示信息:
open outfile error: No such file or directory
原因如前面所述把电脑上的目录和板子的目录搞混了,修改文件目录为板子目录就行了。
4. 写入文件失败,没有数据可写,
原因:mummap()放到了write之前,交换位置即可。
5. 程序能正常运行了,但读取的数据错误,读取的各设备分量信息中发现不合理值
vd->mbuf.offsets:-1073742308,
原因:没有给v4l_device *vd分配内存空间,程序开始时分配即可。
6. 采集到的数据不能显示;
原因:关键是没有设置vd->picture.palette,将其设置为与vd->mmap.format相同值即可。如果设置的是YUV420格式,则数据可以用YUVviewer播放,如果数据格式设为RGB24则还需
加上BMP头后数据才能显示为图像。
其中RGB24图像格式为:每个像素点包含R,G,B三个分量,且三分量交替排列,即(R,G,B);(R,G,B);(R,G,B);……这样交替出现,如果不加BMP头图像大小
就为width*height*3.
YUV格式又分为很多种,各种格式的Y,U,V分量的比例以及排列顺序不一样,比如UYUV就是[u,y,v,y][u,y,v,y]交替排列,比例为Y:U:V=4:2:2,图像大小就为
width*height*2.
而yuv420则不一样,首先是比例,每行都只有U分量或者V分量,Y:U:V=4:2:0或4:0:2;
其次排列形式也不一样,YUV420三分量不是交替排列,而是各自分块排列,所有y分量之后才是u分量,最后是v分量。
如一幅4*4的图片其像素排列为:
y y y y
y y y y
y y y y
y y y y
u u
u u
v v
v v
图片大小为width*height*1.5.
7. 可以成功采集到连续的视频了,但是存在的对连续视频的帧采集速度太慢,加入时
间函数测了一下,每秒只能采集一到两帧,且主要是将数据写入文件部分耗时太多,
原因:写入文件的目录为“/temp.YUV”,这个目录还是在PC机上,写入时还是要通过网线传到电脑上,将目录改为“/tmp/temp.YUV”就可以大为缩短时间,“/tmp”目录是在板子
上的本地目录,这可以在PC上打开/home/nfs/tmp和在板子上打开/tmp查看内容发现内容不同来验证。
xiyong8260的补充:
2008-5-29
1、编译内核,使用make menuconfig加上ov511(在usb选项中)的和vedio for linux驱动,
编译的时候错误,报告videodev.h文件不能找到version.h文件,后来修改/usr/local/arm/…. videodev.h文件,删除包含version.h,就好了。
2、启动内核,会打印下面的信息:
hub.c: new USB device usb-ohci-1, assigned address 2
ov511.c: USB OV511+ video device found
ov511.c: model: Unknown
ov511.c: Camera type (108) not recognized
ov511.c: Please notify mark@alpha.dyndns.org of the name,
ov511.c: manufacturer, model, and this number of your camera.
ov511.c: Also include the output of the detection process.
ov511.c: Sensor is an OV7620
Looking up port of RPC 100005/1 on 192.168.10.135
ov511.c: Device registered on minor 0
说明检测到了OV511的设备,但是model不认识,查看源代码,camlist中不包含108的设备号;
3、编写了一个测试程序,打开/dev/video0设备
建立设备号 /dev/videio0 c 81 0,测试结果不能打开设备,怀疑是没有这个设备,
查看源代码,发现是driver/meida/video/videodev.c文件处理的video0设备,
进一步发现,没有运行videodev_init程序,
以前的代码是通过MODULE的方式运行的,
#ifdef MODULE
int init_module(void)
{
return videodev_init();
}
现在修改为
module_init(videodev_init);
module_exit(videodev_exit);
但是发现还是没有运行,又发现需要在函数前增加_init的标记才能运行
static int __init
videodev_init(void)
现在能够运行了,在启动信息中可以发现如下的信息:
Video for Linux One (2.2.16). Major device: 81
Video for Linux Two (V0.20). Major device: 81
使用cat /proc/devices可以看到设备名字:81 v4l1/2
[root@192 /]# cat /proc/devices
Character devices:
1 mem
2 pty/m%d
3 pty/s%d
4 vc/0
5 ptmx
7 vcs
10 misc
13 input
29 fb
81 v4l1/2
89 i2c
90 mtd
128 ptm
136 pts/%d
162 raw
180 usb
204 ttyS%d
205 cua%d
254 s3c2440-ts
Block devices:
1 ramdisk
7 loop
31 mtdblock
同时在dev目录下自动建立了下面的节点:
/dev/v4l/video
2008-5-31
1、今天尝试着编译了webcam-server-0.50,
使用2.95.3编译器,显示不能找到jpeg库,重新编译了jpeg库,还是有些问题,干脆直接使用3.3.2的编译器;
./configure –prefix=/usr/local/arm/3.3.2/ -srcdir=/home/xiyong/vedio/webcam_server-0.50
然后修改Makefile src/Makefile
cc=/usr/local/arm/3.3.2/bin
报错:没有找到fd,
修改:webcam_server.c的那几个struct定义到main开头即可
直接make,会在src/目录下生成webcam_server文件,拷贝到/home/nfs目录下
建立节点:/dev/video0 c 81 0
运行 ./webcam_server目录
在计算机的IE浏览器中输入 http://192.168.10.199:8888/可以看到采集到的图像
点击刷新,图片会不断刷新。
五. 存在的问题和拟采取的解决方案
改变输出文件目录以后采集速度可以达到每秒4,5帧,但是一般的视频播放都要求每秒15帧以上,所以相对说来采集速度还是太慢,暂时还没有好的解决方案。