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)
              /*mmapvideo_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(可执行)
FlagsMAP_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->framestatframe的值来进行双缓冲,即采集一帧时处理另一帧,同时加一个外循环达到连续多帧的采集,每帧的地址都是通过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分配内存空间,程序开始时分配即可。