http://blog.chinaunix.net/u1/41134/showart_405902.html
在进一步完善我的驱动之前,想先总结一下我的开发过程,以使以后少走弯路。这次开发USB电视卡的Linux驱动,我分成了以下几个步骤:
1、 了解卡上IC的寄存器设置;
2、 使USB电视卡可以和Linux系统正常通讯;
因为刚开始时,我对Video4Linux不是很熟悉,再加上要播放电视首先需要配置USB电视卡上的芯片的寄存器,所以我没有一开始就去编写支持V4L的驱动,而只是先写了一个简单的USB驱动和一个应用程序。
这个应用程序的功能就是发送不同的ioctl命令,而驱动程序则是接收这些命令,并根据不同的命令,或者初始化AU8522和AU0828,或者读写AU8522和AU0828的寄存器。另外,该驱动也只是简单地实现了read方法,对于write方法并未多加处理。
3、 在Linux下,让USB电视卡采集CVBS视频数据,然后在Windows下进行分析;
当我在第二步中写的应用程序和驱动可以正确配置USB电视卡上芯片的寄存器后,就开始编写支持V4L的USB视频驱动,同时编写了另一个基于V4L的应用程序,用来发送V4L的IOCTL命令,以及采集视频数据,并将这些数据放到Windows下进行分析(Windows下有同事编好的YUV查看工具,可以很方便的确定采集到数据是否正确。当然,我相信Linux下也一定有类似的工具程序,但是一来不能确定这些工具是否一定可以用,二来还要去学习怎么使用这些工具,所以……,反正我在Fedaor 7下装了Windows XP的虚拟机,切换起来很方便)。
之所以要增加这一步骤,原因有二:一是因为媒体播放器(mplayer)播放TV时用什么样的选项命令还需要摸索摸索;二是若直接使用mplayer,我不能确定它在发送IOCTL命令后能得到怎样的返回值,就是说这也是调试驱动的一个步骤。
4、 在Linux下,用媒体播放器(mplayer)播放CVBS输入的TV
当我可以用Windows下的YUV查看工具正确播放在Linux下采集到的视频数据后,就开始了这一步。
通过网络、man mplayer和几次尝试,确定了mplayer播放TV的命令选项:mplayer tv:// -tv driver=v4l:input=0:width=720:height=480:norm=NTSC:device=/dev/video0:outfmt=uyvy
然后稍稍修改了一下驱动,主要是将驱动程序中的VIDEO_PALETTE_YUV422改成了VIDEO_PALETTE_UYVY,我的USB电视卡就可以正常播放CVBS信号了。
5、 最后配置tuner,用媒体播放器(mplayer)播放tuner送来的TV
USB电视卡驱动程序简述
我的USB电视卡驱动的实现由USB和VIDEO两部分组成:USB部分可以参考《Linux USB驱动框架分析》;VIDEO部分是按照标准的USB VIDEO设备的驱动框架编写的(以ov511的驱动为基础),具体框架的分析可以参考《摄像头驱动实现源码分析》,其实就我看过的几个基于V4L(或者V4L2)的USB视频驱动,不管是摄像头的还是电视卡的,它们的框架都没有本质的区别,所不同的只是设备的配置和对数据的处理。
另外从数据流向方面分析,我的USB电视卡驱动(包括许多其它基于V4L的USB驱动)也可以分为同样的两部分:USB部分和VIDEO部分。其中USB部分负责视频数据的输入(来自USB设备),VIDEO部分负责视频数据的输出(去往上层应用程序,即播放器)。
流程图如图1所示:
图1
![](http://blogimg.chinaunix.net/blog/upfile/071023144741.jpg)
(以下部分只限于数据处理方面,有关于USB设备或者VIDEO设备的注册,信号量的初始化等等不加讨论)
一、USB部分:数据的输入
(以下函数的介绍根据调用的顺序排列)
.probe函数:
主要是配置USB电视卡,初始化一些数据结构,比如frame buffer的状态,以及初始化USB电视卡设备的属性,如亮度,对比度等等。以前不明白为什么很多USB视频设备的驱动大多都在.probe函数中初始化设备,而在LDD3中作者却说设备的初始化是在.open函数中完成的。后来逐渐想明白,一般视频设备的初始化配置只需一次就够了,而且往往要配置的数据比较多,同时还一般都会与硬件交互,而.open方法是可以多次调用的,如果将设备的初始化配置放在.open函数中,那么就会出现这样的情况:假如设备当前已被某个上层应用程序(播放器)打开,这时有另外一个应用程序想要打开这个设备,那么不但要花费较多时间,而且还会中断当前播放器的播放画面,因为设备又要初始化一次了!
.open函数:
主要是分配buffer的内存。buffer需要三个,一个用来接收USB设备传来的数据,在程序中我定义为sbuf;另一个用来保存USB设备传来的原始画面的数据,也就是经过处理的sbuf中的数据(如果数据处理不复杂,这个buffer不是必须的,因为可以将USB传来的数据送到frame buffer中),在程序中我定义为rawfbuf;最后一个用来发送处理过的视频数据,即frame buffer,在程序中我定义为fbuf。(当然,若中间处理视频数据时需要额外的buffer,也可以在这里分配。)
至于这三个buffer的size:
sbuf取决于USB的ISO传输时,使用几个urb,每个urb由几个packets组成以及每个packet传输的最大数据量(也就是ISO端点的MaxSize),事实上sbuf的内存在地址上并不是连续的,因为每个urb都有相对独立的buffer;
rawfbuf取决于USB控制器送来的视频格式和每一场画面的高度和宽度,若USB控制器可传送多种格式的视频,取其最大值。另外为防止数据溢出,往往为这个buffer额外分配一些内存,我的程序中是多分配了一个包大小(3072 Bytes)的内存;+
fbuf取决于要送给播放器的视频数据的最大值,比如播放器最大的播放画面是720*576,而视频数据格式是BGR24的,那么所需分配的大小便是720*576*4=191K,如果需要在每一场的视频数据中插入数据的接收时间,那么也要为这时间数据的存储分配额外字节的内存。我想,如果系统的性能很好,内存也很大,那么多分配一些内存也无伤大局。
其中需注意的是frame buffer的内存分配,因为它的内存是用来存储器映射的,与应用程序共享,而且要分配的内存也比较大,所以不能简单地用kmalloc()或者vmalloc()函数,而是专门写了一个称为rvmalloc()的函数。其实很多USB视频设备驱动的rvmalloc()函数都大同小异,有的甚至完全一样!
.open函数中还有一个很重要的部分就是ISO packets的初始化和第一次submit,也就是isoc的初始化。在这个初始化函数中,首先设置USB的interface,然后为urb的各个参数赋值,这里只对以下几个参数做一下说明:
urb->transfer_buffer:用来保存urb返回时data的buffer;
urb->complete:指定urb的回调函数,也就是urb返回时的数据处理函数;
urb->number_of_packets:指定每一个urb由n个packets组成,packets的数量n不是随意的,它有上限和下限。其中有上限是因为urb的buffer是由kmalloc()这个函数分配的,而kmalloc()函数最多只能分配128K大小的内存,所以对USB2.0来说,若ISO端点的最大包大小是3072 Bytes,那么必须使n <= 128K/3072 = 42;至于下限,则是由于USB2.0的数据传输速度和画面传输的数据量这两方面的原因,就我的USB电视卡来说,在传输PAL制的bt656数据时,每秒传输的数据量大小为720*576*2*25 = 20.736M(其实NTSC也是一样的大小,因为NTSC虽然每场只有480行,但是每妙却有30帧),而USB2.0的ISO端点每秒最大能传输3072*8*1000 = 24.576M,所以若n值太小了,那么传输效率就不会很高,也就失去了使用urb传输的意义;
urb->transfer_buffer_length:每个urb的buffer大小,取决于ISO端点的最大包大小和每个urb中的packets数量,即两者相乘;
urb->iso_frame_desc[i].offset和urb->iso_frame_desc[i].length:每个urb中的每个packet的offset和长度。
AU0828_isoc_irq:
这是urb的回调函数,也就是urb返回后会调用这个函数对urb中的数据进行处理,这其实就是个中断例程。其处理内容主要如下:
a、 若没有上层应用程序采集数据的请求,则简单丢掉urb中的数据,然后resubmit该urb包,若有上层应用程序采集数据的请求,则开始处理;
b、 判断每个urb包是不是有效数据包;
c、 根据每个urb包的第一个字节判断是不是一场的开始,以及是奇场还是偶场;
d、 若有新的一场开始,则说明上一场数据已经采集完毕,wake_up等待数据的进程。
e、 对urb包中的数据做处理后,存入rawfbuf中。
f、 resubmit urb
二、VIDEO部分:数据的输出
.mmap:
存储器映射函数,实现将设备内存映射到用户进程的地址空间的功能,通俗点说就是将frame buffer中的数据对用户进程可见,也就是让播放器可以直接读取内核空间frame buffer中的数据,以提高效率。
.IOCTL:
V4L定义了很多IOCTL命令,但并不是每一个IOCTL命令都需要实现。在我的驱动中,主要实现了以下几个IOCTL命令:VIDIOCGPICT、VIDIOCSPICT、VIDIOCSWIN、VIDIOCGWIN、VIDIOCGMBUF、VIDIOCMCAPTURE和VIDIOCSYNC。
其中与数据处理相关的最重要的是VIDIOCGMBUF、VIDIOCMCAPTURE和VIDIOCSYNC,它们分别是获取共享内存,命令开始采集和同步采集(当然其他几个也不能胡乱实现,因为若实现地不正确,播放器就不能获得必要的信息,就无法播放画面)。
VIDIOCGMBUF即获取共享内存,播放器通过这个IOCTL获取frame buffer的一些信息,包括有几重buffer,每重buffer的size和offsets等
VIDIOCMCAPTURE即命令开始采集,播放器通过这个IOCTL传递给驱动一个frame buffer号,通知驱动开始采集视频数据,并要求驱动将数据存到给定序号的frame buffer中。这个命令不用等待采集完毕就会返回。
VIDIOCSYNC即同步采集,说白了就是播放器通过这个IOCTL来查询,一场的视频数据是否已采集完毕。若已采集完毕,就会返回成功,若还未完毕,可以选择阻塞等待(由AU0828_isoc_irq函数唤醒),也可以立即返回,要求播放器重试。
三、关于I2C
大多数电视卡或者摄像头,卡上的芯片一般都是用I2C的方式通信的,所以很多USB视频设备的驱动中都包含了I2C模块,在加载驱动module时也就必须将I2C module加载进去。
曾与同事讨论过是否需要在视频设备驱动中使用I2C模块来管理设备的I2C通信,他认为需要,加入I2C模块可以便于管理设备。但我的驱动中却没有用到I2C模块,因为我觉得没有必要!我个人以为,只有连到系统总线上的I2C设备才需要用到Linux中的I2C模块,比如说用SMbus总线管理的设备!
而我的USB电视卡上的I2C,只是卡上芯片之间的I2C通信,而并不与系统上这块卡以外的其它任何设备存在I2C通信,也就是说Linux并不知道也不需要管理这块USB电视卡上的I2C操作,因为无论卡上的芯片进行什么样的I2C通信,对Linux来说也都只是读写USB控制器的寄存器而已,卡上的I2C操作对它是不可见的。
总之,我看不出来加入I2C模块后有什么特别的好处,若有达人知晓,还望告知,不甚感激!
我的USB电视卡driver开发成功
|