针对davinci dm6446平台,网络上也有很多网友写了v4l2的驱动,但只是解析montavista <b>linux-2.6.10</b> v4l2的原理、结构和函数,深度不够。本文决定把montavista 的<b>linux-2.6.18</b> v4l2好好分析一下,顺便讲解在产品中的应用,满足一些客户提出要求,毕竟v4l2是linux一个很重要的视频驱动,适合很多嵌入式芯片平台。本文首先讲解dm6446 davinci视频处理技术的硬件工作原理,然后讲解dm6446 v4l2采集驱动和输出驱动,然后对ti dvsdk2.0里边提供的v4l2的例子进行详细讲解,怎样和驱动配合起来。
第一节 davinci视频处理硬件 有关dm6446 davinci视频处理技术,有两个文档:vpfe sprue38ec.pdf和vpbe sprue37c.pdf,必须要看看的。下图是davinci视频处理技术的框图,vpss(视频处理子系统)包含vpfe和vpbe,vpfe负责前端视频采集和处理,而vpbe负责后端视频输出,通过osd和vecn直接输出到dacs(数字转模拟输出口,一共4个通道dac,通过外围视频编码芯片转换成复合视频cvbs输出到普通电视机)或者直接输出到lcd(dm6446支持rgb24位信号输出到数字lcd屏,4.3寸,7寸屏等)。

图-1 davinci vpss框图
从图-1可以看出,vpfe系统可以接ccd或者cmos sensor,同时也可以接视频解码芯片,目前montavista linux-2.6.18驱动给出的ti evm驱动支持mt9t001 cmos芯片和tvp5146视频解码芯片,vpfe采用raw模式控制mt9t001 cmos芯片,数码相机产品基本是这种应用方式,而vpfe采用bt601或bt656的方式控制tvp5146视频解码芯片,很多做安防、机器视觉等的方案都是这种模式,因为这种方式最普通,视频前端买个普通的ccd摄像机,接条视频线和电源,就可以用通过类似tvp5146的芯片采集到图像了,本人也着重介绍这种情况。而图-1里边的resizer(图像缩放1/4x~4x)、preview(预览器)、h3a(硬件自动白平衡、自动对焦、自动曝光)、histogram(直方图)是对采集到的视频进行处理,一般常用到的是resizer,不需要占用arm和dsp的资源,对采集到的yuv422数据进行处理,然后才提交给h264等算法进行压缩,这一点可以在dvsdk_2_00_00_22\dvsdk_demos_2_00_00_07\dm6446里边的例子体现到。 vpbe系统可以对处理后的视频(video)数据或图像(image)进行处理和输出,一般用户可以通过osd功能叠加自己的logo、字符、时间、坐标、框图等信息,然后通过venc模块输出到dac或者lcd接口。 vpfe和vpbe所有的数据交换都是在ddr上处理,vpfe采集的视频数据,比如yuv422格式(u0y0v0y1)都有指定的ddr地址,而vpbe也有另外指定的ddr地址。
<b>第二节 </b><b>v4l2</b><b>采集驱动</b>
对应上面的硬件处理过程,软件工程师最关心的是如何配置vpfe和vpbe的寄存器,如何实现ddr的视频数据视频缓冲处理,在linux内核里如何实现dma处理。montavista 的linux-2.6.18 v4l2驱动源码已经帮客户实现vpfe和vpbe的处理,他们的源码目录是linux-2.6.18_pro500\drivers\media\video\和linux-2.6.18_pro500\drivers\media\video\davinci目录。对于linux驱动工程师,首先先按以下三个图配置montavista linux-2.6.18_pro500的内核,让linux-2.6.18_pro500支持v4l2。
图-2 配置multimedia devices
按图-2选择video for linux,然后进入“video capture adapters”,按图-3配置davinci视频采集选项,
图-3 配置采集选项
同在一个配置界面,选择和进入“encoders and decoders”,配置vpbe实现视频输出处理。
<b>第三节 </b><b>v4l2</b><b>例子源码分析</b>
在dvsdk_2_00_00_22\psp_02_00_00_140\examples\dm644x\v4l2里,有v4l2应用的例子,里边有v4l2_mmap_loopback.c和v4l2_userptr_loopback.c,我们主要分析v4l2_mmap_loopback.c。很多网友介绍linux v4l2视频原理都是从本节开始的,以tvp5146采集芯片为例。
<b>1</b><b>、</b><b>makefile</b><b>修改</b>
下面的makefile的内容也适合于其他linux应用程序, # makefile for v4l2 application crosscompile = arm_v5t_le- cc=$(crosscompile)gcc ld=$(crosscompile)ld objcopy=$(crosscompile)objcopy objdump=$(crosscompile)objdump include = /home/davinci/dm6446/ty-dm6446-1000/linux-2.6.18_pro500/include (本人把产品级的linux-2.6.18_pro500放到上面的目录,ty-dm6446-1000是本人公司深圳桐烨科技的一个dm6446产品) all: tvp5146_v4l2_mmap tvp5146_v4l2_mmap: v4l2_mmap_loopback.c $(crosscompile)gcc -wall -o2 v4l2_mmap_loopback.c -i $(include) -o tvp5146_v4l2_mmap $(crosscompile)strip tvp5146_v4l2_mmap cp -f tvp5146_v4l2_mmap/home/davinci/nfs/tirootfs/opt/app/ (自动copy到nfs进行调试) %.o:%.c $(cc) $(cflags) -c $^ clean: rm -f *.o *~ core tvp5146_v4l2_mmap
<b>2</b><b>、下面通过分析</b><b>v4l2_mmap_loopback.c</b><b>的源码,从应用层的角度讨论</b><b>v4l2</b><b>的原理:</b>
#include <stdio.h> #include <fcntl.h> #include <string.h> #include <getopt.h> #include <stdlib.h> 。。。。。。。。。。。。。。。。。。。。。。。。。。 #include <fcntl.h> #include <time.h> /*以上指向你安装的linux主机/usr/include*/ #include <linux/fb.h>/*指向montavista linux-2.6.18\include\linux*/ #include <asm/types.h>/*指向linux-2.6.18\include\asm-arm*/ /* kernel header file, prefix path comes from makefile */ #include <media/davinci/davinci_vpfe.h>/*指向linux-2.6.18 \include\media\davinci*/ #include <video/davincifb_ioctl.h>/*指向linux-2.6.18 \include\video*/ #include <linux/videodev.h> #include <linux/videodev2.h> #include <media/davinci/davinci_display.h> #include <media/davinci/ccdc_davinci.h> /* local defines*/
#define capture_device "/dev/video0" 文件系统中采集驱动用到的设备节点 #define width_ntsc 720
#define height_ntsc 480 视频ntsc制式 #define width_pal 720
#define height_pal 576 视频pal支持 #define min_buffers 2 采集时存放yuv视频数据的缓冲数,做到乒乓buffer, #define uyvy_black 0x10801080无图像的yuv值处理 /* device parameters */ #define vid0_device "/dev/video2"
文件系统中display输出设备节点(对照内核驱动davinci_display.c) #define vid1_device "/dev/video3"文件系统中display输出设备节点 #define osd0_device "/dev/fb/0"文件系统中osd0设备节点 #define osd1_device "/dev/fb/2"文件系统中osd1设备节点 /* function error codes */ #define success 0
#define failure -1
/* bits per pixel for video window */ #define yuv_422_bpp 16 #define bitmap_bpp_8 8
#define display_interface "composite"显示输出定义复合视频输出 #define display_mode_pal "pal"显示输出定义pal制输出 #define display_mode_ntsc "ntsc"显示输出定义ntsc制输出 #define round_32(width) ((((width) + 31) / 32) * 32 ) 字节对齐 /* standards and output information */ #define attrib_mode "mode"
#define attrib_output "output"
#define loop_count 500 本例子采集多少帧就停止运行(pal制每秒25帧) 介绍完v4l2_mmap_loopback.c的前面部分的定义,我们可以开始熟悉v4l2,这里借用网友的描述,顺便加入本人的分析。 video4linux2(简称v4l2),是linux中关于视频设备的内核驱动。在linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video0下。 video4linux2一般操作流程(视频设备): 1. 打开设备文件。 int fd=open(”/dev/video0″,o_rdwr); 2. 取得设备的capability,看看设备具有什么功能,比如是否具有视频输入等。vidioc_querycap,struct v4l2_capability 3. 选择视频输入,一个视频设备可以有多个视频输入。vidioc_s_input,struct v4l2_input 4. 设置视频的制式和帧格式,制式包括pal,ntsc,帧的格式个包括宽度和高度等。 vidioc_s_std,vidioc_s_fmt,struct v4l2_std_id,struct v4l2_format 5. 向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers 6. 将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。 7. 将申请到的帧缓冲全部入队列,以便存放采集到的数据.vidioc_qbuf,struct v4l2_buffer 8. 开始视频的采集。vidioc_streamon 9. 出队列以取得已采集数据的帧缓冲,取得原始采集数据。vidioc_dqbuf 10. 将缓冲重新入队列尾,这样可以循环采集。vidioc_qbuf 11. 停止视频的采集。vidioc_streamoff 12. 关闭视频设备。close(fd); 常用的结构体(参见linux-2.6.18_pro500/include/linux/include/linux/videodev2.h): struct v4l2_requestbuffers reqbufs;//向驱动申请帧缓冲的请求,里面包含申请的个数 struct v4l2_capability cap;//这个设备的功能,比如是否是视频输入设备 struct v4l2_input input; //视频输入 struct v4l2_standard std;//视频的制式,比如pal,ntsc struct v4l2_format fmt;//帧的格式,比如宽度,高度等 struct v4l2_buffer buf;//代表驱动中的一帧 v4l2_std_id stdid;//视频制式,例如:v4l2_std_pal struct v4l2_queryctrl query;//查询的控制 struct v4l2_control control;//具体控制的值
从main()函数调用vpbe_ue_1(),在vpbe_ue_1()里,可以看到采集流程和显示输出流程。
<b>3</b><b>、</b><b>v4l2</b><b>采集过程</b>
initialize_capture()里初始化采集配置、分配采集内存缓冲、启动开始采集。顺序调用init_capture_device()+set_data_format(),init_capture_buffers(),start_streaming()。 <b>打开视频设备</b> 在v4l2中,视频设备被看做一个文件。使用open函数打开这个设备: /用非阻塞模式打开采集设备,见init_capture_device()函数,fdcapture在本例子中定义为全局变量, if ((fdcapture = open(capture_device, o_rdwr | o_nonblock, 0)) <= -1) { printf("initdevice:open::\n"); return -1; } 如果用阻塞模式打开采集设备,上述代码变为: if ((fdcapture = open(capture_device, o_rdwr, 0)) <= -1) { printf("initdevice:open::\n"); return -1; } 关于阻塞模式和非阻塞模式,应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(dqbuff)里的东西返回给应用程序。 <b>设定属性及采集方式</b> 打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在linux编程中,一般使用ioctl函数来对设备的i/o通道进行管理: extern int ioctl (int __fd, unsigned long int __request, …) __throw; __fd:设备的id,例如刚才用open函数打开视频通道后返回的fdcapture; __request:具体的命令标志符。 在进行v4l2开发中,一般会用到以下的命令标志符: 1. vidioc_reqbufs:分配内存 2. vidioc_querybuf:把vidioc_reqbufs中分配的数据缓存转换成物理地址 3. vidioc_querycap:查询驱动功能 4. vidioc_enum_fmt:获取当前驱动支持的视频格式 5. vidioc_s_fmt:设置当前驱动的频捕获格式 6. vidioc_g_fmt:读取当前驱动的频捕获格式 7. vidioc_try_fmt:验证当前驱动的显示格式 8. vidioc_cropcap:查询驱动的修剪能力 9. vidioc_s_crop:设置视频信号的边框 10. vidioc_g_crop:读取视频信号的边框 11. vidioc_qbuf:把数据从缓存中读取出来 12. vidioc_dqbuf:把数据放回缓存队列 13. vidioc_streamon:开始视频显示函数 14. vidioc_streamoff:结束视频显示函数 15. vidioc_querystd:检查当前视频设备支持的标准,例如pal或ntsc。 这些io调用,有些是必须的,有些是可选择的。他们可以从在内核中davinci_vpfe.c 里static int vpfe_doioctl(struct inode *inode, struct file *file,unsigned int cmd, void *arg)函数找到对应关系。 <b>检查当前视频设备支持的标准和设置视频捕获格式</b> 在set_data_format()函数里,检测完视频设备支持的标准后,还需要设定视频捕获格式:pal制还是ntsc制,采集像素格式uyvy,奇偶场交错方式interlaced。 <b>分配内存</b> 接下来可以为视频捕获分配内存: 在init_capture_buffers()里,使用vidioc_reqbufs,我们获取了req.count个缓存,下一步通过调用vidioc_querybuf命令来获取这些缓存的地址,然后使用mmap函数转换成应用程序中的绝对地址,最后把这段缓存放入缓存队列: // 读取缓存 if (ioctl(fdcapture, vidioc_querybuf, &buf) == -1) { return -1; } buffers[numbufs].length = buf.length; // 转换成相对地址 buffers[nindex].length = buf.length; buffers[nindex].start = mmap(null, buf.length, prot_read | prot_write, map_shared, fdcapture, buf.m.offset); <b>启动开始采集</b> // 放入缓存队列 if (ioctl(fdcapture, vidioc_qbuf, &buf) == -1) { return -1; }} /* all done , get set go */ type = v4l2_buf_type_video_capture; if (-1 == ioctl(fdcapture, vidioc_streamon, &type)) printf("start_streaming:ioctl:vidioc_streamon:\n"); <b> </b> <b>关于视频采集方式</b> 操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。 一共有三种视频采集方式:使用read、write方式;内存映射方式和用户指针模式。 read、write方式:在用户空间和内核空间不断拷贝数据,占用了大量用户内存空间,效率不高。 内存映射方式:把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。 用户指针模式:内存片段由应用程序自己分配。这点需要在v4l2_requestbuffers里将memory字段设置成v4l2_memory_userptr。 处理采集数据 v4l2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用fifo的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的 视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,vidioc_dqbuf和vidioc_qbuf: struct v4l2_buffer buf; memset(&buf,0,sizeof(buf)); buf.type=v4l2_buf_type_video_capture; buf.memory=v4l2_memory_mmap; buf.index=0; //读取缓存 if (ioctl(fdcapture, vidioc_dqbuf, &buf) == -1) { return -1; } //…………视频处理算法 //重新放入缓存队列 if (ioctl(fdcapture, vidioc_qbuf, &buf) == -1) { return -1; } 关闭视频设备 使用close函数关闭一个视频设备 close(fdcapture)
<b>4</b><b>、</b><b>v4l2</b><b>显示输出</b>
配置视频显示输出函数init_vid1_device(),初始化和采集差不多,这里就不用多解析,这个显示输出的例子通过dac口,把采集的图像通过loopback方式,直接输出到普通电视机或dvd等视频in的端口里,当然你的板子要有把dm6446 dac信号通过视频放大器才能接到电视机上。从start_loop()函数里,下面的代码 buf.type = v4l2_buf_type_video_capture; buf.memory = v4l2_memory_mmap; /* determine ready buffer */ if (-1 == ioctl(fdcapture, vidioc_dqbuf, &buf)) { if (eagain == errno) continue; printf("startcameracaputre:ioctl:vidioc_dqbuf\n"); return -1; } /******************* v4l2 display ********************/ displaybuffer = get_display_buffer(fd_vid1); if (null == displaybuffer) { printf("error in getting the display buffer:vid1\n"); return ret; } src=\'#\'" /span> dest = displaybuffer; /* display image onto requested video window */ for(i=0 ; i < dispheight; i++) { memcpy(dest, src, disppitch); src += disppitch; dest += disppitch; } 可以看出loopback方式的操作memcpy(dest, src, disppitch),直接把采集的数据(720x576x2)字节放到视频输出缓冲dest,disppitch=1440,,就是一行uyvy的自己是1440。
<b>第四节 </b><b>dvsdk2.0</b><b>有关</b><b>v4l2</b><b>的例子分析</b>
有上面的介绍,我们可以深入学习dm6446 dvsdk2.0有关v4l2的例子。dvsdk_2_00_00_22\dvsdk_demos_2_00_00_07\dm6446里有encode,decode,encodedecode的例子,这些例子全部是应用程序,v4l2的例子函数为capture.c和display.c,他们不像第三节介绍的v4l2_mmap_loopback.c直接跟内核davinci_vpfe.c接口函数打交道,而是通过dmai,即dvsdk_2_00_00_22\dmai_1_20_00_06\packages\ti\sdo\dmai\linux目录下的源代码,跟内核davinci_vpfe.c、vpbe_encoder.c打交道,montavista把内核驱动和visa调用封装在一起,dvsdk_2_00_00_22\dvsdk_demos_2_00_00_07\dm6446里的例子就是产品级的例子,带有h264、mpeg4、g711这些算法的应用。dvsdk_2_00_00_22\dmai_1_20_00_06\packages\ti\sdo\_dmai\linux里的c文件就是v4l2和内核对接的源文件,好好学习这些例子,对大家做davinci嵌入式产品非常有好处,本人也是从这些例子里学到很多linux的东西。
第五节后记 新的产品即将出来,根据一些客户的要求,我们重新对dm365/dm368进行第2轮pcb设计,我想很快就可以和大家探讨高清的方案,深圳市桐烨科技有限公司专门提供对应硬件平台和产品方案支持,我们专注arm+dsp的产品方案和项目设计,ivs(智能视频监控)设计,810mhz的dm6446核心板将更加满足算法的要求。同时我们根据客户的项目需求,以深圳的速度(产品一条龙服务)帮客户设计产品。