USB电视卡驱动

http://blog.chinaunix.net/u1/41134/showart_405902.html

在进一步完善我的驱动之前,想先总结一下我的开发过程,以使以后少走弯路。这次开发USB电视卡的Linux驱动,我分成了以下几个步骤:

1、 了解卡上IC的寄存器设置;
2、 使USB电视卡可以和Linux系统正常通讯;
因为刚开始时,我对Video4Linux不是很熟悉,再加上要播放电视首先需要配置USB电视卡上的芯片的寄存器,所以我没有一开始就去编写支持V4L的驱动,而只是先写了一个简单的USB驱动和一个应用程序。
这个应用程序的功能就是发送不同的ioctl命令,而驱动程序则是接收这些命令,并根据不同的命令,或者初始化AU8522AU0828,或者读写AU8522AU0828的寄存器。另外,该驱动也只是简单地实现了read方法,对于write方法并未多加处理。
3、 Linux下,让USB电视卡采集CVBS视频数据,然后在Windows下进行分析;
当我在第二步中写的应用程序和驱动可以正确配置USB电视卡上芯片的寄存器后,就开始编写支持V4LUSB视频驱动,同时编写了另一个基于V4L的应用程序,用来发送V4LIOCTL命令,以及采集视频数据,并将这些数据放到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电视卡驱动的实现由USBVIDEO两部分组成:USB部分可以参考《Linux USB驱动框架分析》;VIDEO部分是按照标准的USB VIDEO设备的驱动框架编写的(ov511的驱动为基础),具体框架的分析可以参考《摄像头驱动实现源码分析》,其实就我看过的几个基于V4L(或者V4L2)USB视频驱动,不管是摄像头的还是电视卡的,它们的框架都没有本质的区别,所不同的只是设备的配置和对数据的处理。
另外从数据流向方面分析,我的USB电视卡驱动(包括许多其它基于V4LUSB驱动)也可以分为同样的两部分:USB部分和VIDEO部分。其中USB部分负责视频数据的输入(来自USB设备)VIDEO部分负责视频数据的输出(去往上层应用程序,即播放器)
流程图如图1所示:
1

(以下部分只限于数据处理方面,有关于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,也可以在这里分配。)
至于这三个buffersize
sbuf取决于USBISO传输时,使用几个urb,每个urb由几个packets组成以及每个packet传输的最大数据量(也就是ISO端点的MaxSize),事实上sbuf的内存在地址上并不是连续的,因为每个urb都有相对独立的buffer
rawfbuf取决于USB控制器送来的视频格式和每一场画面的高度和宽度,若USB控制器可传送多种格式的视频,取其最大值。另外为防止数据溢出,往往为这个buffer额外分配一些内存,我的程序中是多分配了一个包大小(3072 Bytes)的内存;+
fbuf取决于要送给播放器的视频数据的最大值,比如播放器最大的播放画面是720*576,而视频数据格式是BGR24的,那么所需分配的大小便是720*576*4191K,如果需要在每一场的视频数据中插入数据的接收时间,那么也要为这时间数据的存储分配额外字节的内存。我想,如果系统的性能很好,内存也很大,那么多分配一些内存也无伤大局。
其中需注意的是frame buffer的内存分配,因为它的内存是用来存储器映射的,与应用程序共享,而且要分配的内存也比较大,所以不能简单地用kmalloc()或者vmalloc()函数,而是专门写了一个称为rvmalloc()的函数。其实很多USB视频设备驱动的rvmalloc()函数都大同小异,有的甚至完全一样!
.open函数中还有一个很重要的部分就是ISO packets的初始化和第一次submit,也就是isoc的初始化。在这个初始化函数中,首先设置USBinterface,然后为urb的各个参数赋值,这里只对以下几个参数做一下说明:
urb->transfer_buffer:用来保存urb返回时databuffer
urb->complete:指定urb的回调函数,也就是urb返回时的数据处理函数;
urb->number_of_packets:指定每一个urbnpackets组成,packets的数量n不是随意的,它有上限和下限。其中有上限是因为urbbuffer是由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.0ISO端点每秒最大能传输3072*8*1000 = 24.576M,所以若n值太小了,那么传输效率就不会很高,也就失去了使用urb传输的意义;
urb->transfer_buffer_length:每个urbbuffer大小,取决于ISO端点的最大包大小和每个urb中的packets数量,即两者相乘;
urb->iso_frame_desc[i].offseturb->iso_frame_desc[i].length:每个urb中的每个packetoffset和长度。
 
AU0828_isoc_irq
这是urb的回调函数,也就是urb返回后会调用这个函数对urb中的数据进行处理,这其实就是个中断例程。其处理内容主要如下:
a、  若没有上层应用程序采集数据的请求,则简单丢掉urb中的数据,然后resubmiturb包,若有上层应用程序采集数据的请求,则开始处理;
b、  判断每个urb包是不是有效数据包;
c、  根据每个urb包的第一个字节判断是不是一场的开始,以及是奇场还是偶场;
d、  若有新的一场开始,则说明上一场数据已经采集完毕,wake_up等待数据的进程。
e、  urb包中的数据做处理后,存入rawfbuf中。
f、   resubmit urb
 
二、VIDEO部分:数据的输出
.mmap
存储器映射函数,实现将设备内存映射到用户进程的地址空间的功能,通俗点说就是将frame buffer中的数据对用户进程可见,也就是让播放器可以直接读取内核空间frame buffer中的数据,以提高效率。
 
.IOCTL
V4L定义了很多IOCTL命令,但并不是每一个IOCTL命令都需要实现。在我的驱动中,主要实现了以下几个IOCTL命令:VIDIOCGPICTVIDIOCSPICTVIDIOCSWINVIDIOCGWINVIDIOCGMBUFVIDIOCMCAPTUREVIDIOCSYNC
其中与数据处理相关的最重要的是VIDIOCGMBUFVIDIOCMCAPTUREVIDIOCSYNC,它们分别是获取共享内存,命令开始采集和同步采集(当然其他几个也不能胡乱实现,因为若实现地不正确,播放器就不能获得必要的信息,就无法播放画面)
VIDIOCGMBUF即获取共享内存,播放器通过这个IOCTL获取frame buffer的一些信息,包括有几重buffer,每重buffersizeoffsets
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开发成功
最近一段时间一直在给公司的USB电视卡做Linux下的驱动程序,虽然还没有全部完成,还有一些后续工作要做,但是也可以告一段落了,因为已经可以在我的Fedora 7系统下正常播放电视信号了!
 
图1
 
图2
 
由于这块USB电视卡本来就是我设计的,甚至还在这块卡的基础上设计了陆续设计了许多其它类型的电视卡(比如ExpressCard接口的电视卡,其他类型tunerUSB电视卡等等),所以基本可以不用在硬件上花费太多心思。
 
对于硬件电路,简单点说就是XC5000(Xceive公司的tuner芯片)将电视RF信号直接转换为CVBSSIF,然后我们公司的解调芯片AU8522CVBS转换为bt656格式的视频数据,并将SIF转换为IIS格式的音频数据,最后USB控制器AU0828再将IIS数据和由bt656转换而成的YUYV数据送到PC机。(更加详细的就不能多说了,应该属于商业机密!)
 
在驱动上,我编写的是基于Video4Linux的驱动程序,主要参考了源码树中自带的ov511的源码,当然之前也分析了其它的许多源码,包括em28xxusbvision等等。
 
在写驱动时也曾遇到了许多问题,走了些弯路,主要如下:
 
1、在刚开始时我是在VMware虚拟机的Fedora 7下写驱动的,发现每次只能采集到一个USB微帧(microframe),也就是3072个字节的数据,本以为是判断每帧视频数据的数据头时有错误,后来才发现VMware 6.0虽然也号称支持USB2.0,但是对于ISO端口的支持却并不好,于是将驱动放到了真实Fedora 7下编译和加载,解决了这个问题。
 
2、因为我想查看USB控制器AU0828送来的数据,主要是数据头格式,所以在程序中加了很多printk语句,但是这严重影响了系统性能,所以经常会发生丢失数据的情况。
 
3bt656格式的数据是分奇场和偶场的,AU0828虽然去掉了bt656数据中的一些数据头(比如SAVEAV等等),送给PC机的只是芯片自定义的视频头和YUV422数据,但是这些数据也是分奇场和偶场的,所以在将YUV422数据送给播放器时,我需要事先在驱动中将奇偶场的YUV422数据交叉放进frame buffer中。在这个问题上我曾被卡了很长一段时间!
 
4、在AU0828datasheet上说,它将输入的bt656数据转换成了YUV422类型的视频数据,但是mplayer却不支持YUV422,曾想过在驱动程序将这些视频数据转换为BGR16或者BGR24格式的数据,但是后来发现mplayer支持UYVY,我只要将驱动中的VIDEO_PALETTE_YUV422改为VIDEO_PALETTE_UYVY就可以了。
 
接下来还主要还有三件事TODO
1、  在驱动中增加处理声音的部分;
2、  在驱动中加入一些出错检测,以使驱动更加稳定。
3、 在驱动中加入兼容V4L2的部分。

原文地址:https://www.cnblogs.com/leaven/p/1643759.html