usb audio设备驱动



本文引用自ls.cq《usb audio设备驱动》

http://bbs.driverdevelop.com/read.php?tid-118579-page-1.html

已基本完成wavedev结构的usb音频驱动,现将开发心得及经验总结一下与大家分享 

/*****************************************************************************************/ 
在做usb audio设备驱动开发前我还不知道有usb audio device class,以为这是个HID类型的驱动,开发起来应该容易实现,后来才发现原来自己进入了一个未知领域。幸亏之前有开发过mass storage固件程序,又了解过OV511的usb camera驱动,所以尽管多花了点时间,中间也出现了波折但还是完成了开发。完成开发后回头一看发现自己对USB协议、音频处理有了进一步的了解,这也算是收获一吧;因为之前没做过,在开发过程中我有40%的时间是用在思考如何进行、10%的时间用于将思路文档化、50%的时间编码,发现这样效率还不错,人也没那么累,也算是收获二吧。 

一、在总结开发经验的时候,先温习一些基础知识点做铺垫。 
1、usb协议 
(1)USB设备通过描述符来描述其功能,标准的USB设备有5种USB描述符:设备描述符,配置描述符,字符串描述符,接口描述符,端点描述符。 
(2)USB设备可以看作提供了多个串口的设备,依据USB的规范,我们将每个串口称作端点(Endpoint),要和这个端点通信,我们就要打开到这个端点的连接,这个连接就是管道(Pipe)。 
(3)由于一个设备可能要适应多种情况,端点的设置会有多套,以备使用。端点设置称为接口(Interface)。USB设备展现给我们能够找到的东西就是这些Interface,我们选择要用的Interface,就可以找到Endpoint,再打开Endpoint,就可以传输数据了。所以,在驱动程序开始的时候,需要记录下这些Interface。 
(4)打开端点之后,就可以像串口一样进行数据传输了。USB有4种不同类型的传输方式:控制传输(Control Transfer),批量传输(Bulk Transfer),中断传输(Interrupt Transfer)和实时传输(IsochTransfer)。象audio和viedo这种对实时性要求高的就要用实时传输。 

2、usb设备驱动 
(1)HID、usb audio class device、usb video class device、mass storage这四种设备可以开发通用的设备驱动,只需要修改VID和PID即可。 
(2)对于一个USB设备的流驱动,除开实现流驱动接口外,还需要实现USBInstallDriver、USBDeviceAttach、USBUnInstallDriver这三个函数以及设备拔出的回调函数。 

3、音频处理 
(1)PCM编码方式:脉冲编码调制PCM(Pulse Code Modulation),其实就是将声音数字化进行传输。也是最常见的方式 。具体原理可以从网上查询。 
(2)PCM文件:模拟音频信号经模数转换(A/D变换)直接形成的二进制序列,该文件没有附加的文件头和文件结束标志。 
(3)傅立叶变换:数字信号处理领域一种很重要的算法。傅立叶原理表明:任何连续测量的时序或信号,都可以表示为不同频率的正弦波信号的无限叠加。而根据该原理创立的傅立叶变换算法利用直接测量到的原始信号,以累加方式来计算该信号中不同正弦波信号的频率、振幅和相位。和傅立叶变换算法对应的是反傅立叶变换算法。该反变换从本质上说也是一种累加处理,这样就可以将单独改变的正弦波信号转换成一个信号。因此,可以说,傅立叶变换将原来难以处理的时域信号转换成了易于分析的频域信号(信号的频谱),可以利用一些工具对这些频域信号进行处理、加工。最后还可以利用傅立叶反变换将这些频域信号转换成时域信号。 
在数学可以认为:任何波形都可以用多项式来表示,就是说可以进行数学运算。 
(4)人耳能听到的声波范围是200hz-20Khz,声音信号中的噪音一般是在低频段(比如500hz以下),通过傅立叶变换后将此部分消除再逆转换回去,就可以达到去噪的目的 
(5)WINDOWS下对音频的处理,大致可分为两部分,即音频的输入、输出,和ACM压缩处理。 

4、音频驱动 
(1)音频驱动通过一个wave api manager的模块与上层应用打交道。具体结构图可参考MSDN 
(2)音频驱动处理的就是PCM数据流。因为设备的I/O采样率已经是预先设置好一个值了,因此对于上层应用要求的不同采样率要进行转换,此时用线性插值算法来保证转换后声音不失真。 
(3)AC97音频驱动结构功能分析。 

二、设备分析 
通过将此设备接到PC上,用工具软件bushound和usbview分析出此设备的信息,它有四个接口(端点配置)如下: 
1、接口0:音频控制,使用端点0,用于调节音量等操作,class为1(Audio),subclass为1(Audio Control) 
音量调节过程:缺省是接口0,发送SET CUR指令及音量大小值。因为改变音量后会响一下,发送SET INTERFACE从接口0切换到接口2,然后往接口2发送音频数据(PCM格式的WAVE文件),最后发送SET INTERFACE从接口2切换回接口1。 
2、接口1、2:音频流输出(SPEAK),使用端点1,class为1(Audio),subclass为2(Audio Streaming),前者bAlternateSetting和bNumEndpoints均为0(zero bandwidth),后者均为1 
3、接口3、4:音频输入(MIC),使用端点2,class为1(Audio),subclass为2(Audio Streaming),前者bAlternateSetting和bNumEndpoints均为0(zero bandwidth),后者均为1 
4、接口5:HID,使用端点3。未使用,class为3(HID),subclass为0 
5、Data Format Type 
输入是单声道、8Khz采样率、16位的PCM,端点最大传输值是16字节;输出是双声道、48Khz采样率、16位的PCM,端点最大传输值是192字节 
6、接口1、2和3、4有Alternative接口,是为了启动/停止MIC或SPK而准备,usb audio device class规范要求实现zero bandwidth的接口。因此要实现此设备的录音和播放只需要操作这几个接口即可。 

三、实现步骤及思路 
第一步:参照usb print实现驱动,在驱动中提供自定义的IoControl控制码供应用程序调用来实现操作设备的目的,因为它跳过了waveapi所以不具有通用性,这主要是为了测试设备使用,同时加深对USB设备开发的理解并积累经验。 
第二步:在第一步的基础上参照wavedev驱动,这样做出的驱动应用程序只需要调用waveapi系列函数就能实现操作设备的目的,具有通用性。 
第三步:实现平台多音频设备的切换。 

四、开发中的重点问题 
第一步 
(1)采样到的PCM文件在PC上播放有加快现象 
原因:播放加快是因为数据丢失。因为驱动的读控制码这个地方只是简单地投递一个实时传输请求,应用程序循环调用自然就有缓冲区溢出的问题。改成驱动中用线程结合事件和信号量,最多可投递5个实时传输请求进入内核的异步等待队列,引入双端循环队列来管理缓冲区就基本上不会有数据丢失的问题。需要注意的是dwLen数组赋值不能超过端点的最大传输,这点在MSDN也是有说明的。 
(2)采样到的PCM文件在PC上播放有噪音现象 
原因一:此设备一次传送960字节(即10ms),因为输入端点的包大小是100,所以我的Frame数组是100*10,但是数据到来是并不是由Frame[0]开始填,而是每个Frame都填96,这样在生成PCM文件的时候就会每隔96字节多出4个0,将Frame数组改成96*10后,噪音要小多了。 
原因二:此音频设备是USB的5V供电,开发板的5V电源还接了其它使用,受硬件原因的影响有噪音存在。换到产品设备上试验基本上无噪音。 
(3)多拔插几次就会出异常 
在设备拔出的回调函数中,对于之前分配的内存、事件、信号量及打开的管道没有做回收或关闭操作。 

第二步 
对于wavedev结构的驱动的修改,我们只需要关注与硬件相关的几个部分,那就是hwctxt、wavemain部分,其它通用部分中只需要修改devctxt中的设备名即可。 
(1)几个重要函数的说明 
SetRate:设置进行线性插值算法的初始值 
Render2:线性插值算法,分别对8位、16位、单声道、立体声进行采样率转换。 
TransferBuffers:对于输入将数据从DMA缓冲经采样率转换后搬移到应用缓冲,对于输出将应用缓冲经采样率转换后搬移到DMA缓冲 
(2)WAV_INIT参数的修改 
对于缺省的音频设备在系统启动的时候就会加载。根据WAV_INIT参数可以得知设备管理器是通过RegisterDevice来加载的,但对于USB音频设备因为是通过USBDeviceAttach动态加载的,它跳过了设备管理器,加载驱动用的ActivateDevice会传递不同的参数,因此需要修改WAV_INIT参数(或者改成用RegisterDevice,不过MSDN推荐用前者) 
(3)DMA操作的修改 
不管是AC97还是IIS音频驱动,其过程都是通过有限状态机来进行状态切换。数据传输有一个重要机制就是使用双缓冲,作用是一个由DMA负责传送,一个由CPU做数据搬移。第一次启动的时候两个缓冲区都填满,但只有一个交给DMA,在IST中进行接下来的数据转换和搬移,这样就可以保证传送的连续性,避免数据搬移的时候DMA空置停机。因为DMA传输是不占用CPU的,所以在CPU进行数据转换和搬移的时候DMA也在进行,这样codec一直有数据播放就不会有播放停顿现象。 
在Usb音频驱动中是没有DMA而是实时传输,如果想完全照搬DMA的工作原理,通过创建一个传输线程来模拟DMA,不管传输缓冲区开多大,在传送回调函数激活IST进行数据转换和搬移时,codec已经播放完数据,IST完成数据转换和搬移并通知传送线程投递新请求这整个过程都会占用CPU其中是有延时的,因此会造成播放停顿现象。因此只能完全抛开DMA的工作机制,采用第一步中实现机制,这样就不会有问题了。对于起停DMA只需要保留其接口,内部实现改为切换USB的接口就能实现起/停音频设备。 

第三步 
可以用waveInMessage、waveOutMessage来实现音频设备的切换,另外如果确定设备已连接可以在waveInOpen、waveOutOpen中直接指定设备id 

五、引用文章 
1、PCM文件格式简介 
http://blog.csdn.net/c0ffee1982/archive/2007/11/19/1892319.aspx 
2、傅立叶变换的物理意义 
http://blog.csdn.net/wjw2586121/archive/2009/05/02/4142352.aspx 
3、WINDOWS下对音频的处理过程 
http://blog.csdn.net/vavale/articles/306481.aspx 
4、wince音频驱动 
http://blog.sina.com.cn/s/blog_537bca2a0100ct4h.html~type=v5_one&label=rela_prevarticle 
5、AC97+WM9715音频驱动开发 
http://i.cn.yahoo.com/ROCKFAN_1986/blog/p_35/ 
6、音频驱动中采样率转换的线性插值算法说明 
http://topic.csdn.net/u/20090629/14/99352117-12b9-41ce-ba2b-b3d937bacb8e.html 
7、WINCE声音驱动模型概述 
http://blog.csdn.net/cnhighway/archive/2009/03/18/4001945.aspx 
8、解决双音频设备切换问题 
http://blog.csdn.net/embest_mhq/archive/2009/04/01/4041860.aspx 
9、如何得到系统音频设备列表 
http://topic.csdn.net/u/20081006/17/609326BB-A166-4733-A3FE-E68AC9A56811.html 
10、WinCE USB Audio/Video Driver 
http://blog.naver.com/PostView.nhn?blogId=mkson69&logNo=30031777873&widgetTypeCall=true 
11、How To Stream Isochronous Data Over Universal Serial Bus 
http://support.microsoft.com/kb/317434/en-us 



我的平台是三星2450+wince5。现有一个usb audio设备,这是一个USB混合设备,插到电脑上通过usbview可以分析出它有Audio Class 和HID Class两种接口。此设备有四个Interface,分别是Audio Control、Audio Stream(SPEAK)、Audio Stream(MICPHONE)、HID Interface;四个EndPoint,分别是Default EP0、Audio Stream Interface EP1 
Output Isoc、Audio Stream Interface EP2 Input Isoc、HID EP3 Input Interrupt(未使用)。 

我的想法可以按两种方法实现,第一种就是参照usb print实现驱动,在驱动中提供自定义的IoControl控制码供应用程序调用来实现操作设备的目的,因为它跳过了waveapi所以不具有通用性,这主要是为了测试设备使用。第二种方法是看到有人提过的,上层调用参考音频驱动的MDD/PDD,下层实现参照usb print,这样做出的驱动应用程序只需要调用waveapi系列函数就能实现操作设备的目的,具有通用性。 

我现在先按第一种方法写了一个驱动测试MIC,在应用程序中通过获取Raw PCM数据后,根据usbview获得的Audio Class等参数(采样率48000、16位、单声道)构造成一个PCM格式的wav文件,在PC上播放发现声音有失真现象,而且播放有明显加快现象;为排除数据流可能造成的问题我用此设备接PC,用PC的录音机程序录音,从USB捕捉到的Raw PCM数据构造的WAV文件播放速度正常,但是有噪音,不知道录音机程序做了什么去噪处理。因为我对音频文件的处理不是很懂,到这一步简直一筹莫展了。 

我的问题: 
(1)此Audio设备因为自带codec,所以能输出:采样率48000、16位、单声道的Raw PCM数据流,但是为什么在2450平台和PC上播放现象不同呢? 
(2)如果不同,那PC上做了什么去噪处理? 2450也是做和PC一样的操作获取数据流,为什么播放会失真,速度会加快? 
(3)按第二种方法实现驱动,上层调用肯定要导出WAV_XXX系列,下层至少要导出USBInstallDriver、USBUnInstallDriver、USBDeviceAttach以及相应流驱动接口,是放在一个dll中导出好一些,还是分成两个dll,上层用fastcall的方式调用下层好一些? 

请各位走过路过的好心人给点建议或意见,我也会及时分享我的经验的。 

问题原因: 
(1)在2450上获得的PCM数据之所以播放加快是因为数据丢失。因为驱动的读控制码这个地方只是简单地投递一个实时传输请求,应用程序循环调用自然就有缓冲区溢出的问题。改成驱动中用线程结合事件和信号量,最多可投递5个实时传输请求,加大缓冲区就基本上不会有数据丢失的问题。 
(2)噪音现象:此音频设备输入是48000hz、16bit、mono,一次传送960字节(即10ms),因为输入端点的包大小是100,所以我的Frame数组是100*10,但是数据到来是并不是由Frame[0]开始填,而是每个Frame都填96,这样在生成PCM文件的时候就会每隔96字节多出4个0,将Frame数组改成96*10后,噪音要小多了。不过因为此音频设备是USB的5V供电,受硬件原因的影响,还是有些噪音存在。 
(3)因此,对于象这样的音频设备,首先还是要从源头解决问题,如HOST供电、咪头的外围电路、电容参数等方面下手,如果仅仅从软件方面想要处理好数据,效果是不会太好的。 
(4)对于usb audio class的设备,其实最基本的还是HOST端设备驱动的读写,因为固件端已经根据协议写好,输出的是格式流(PCM),接收固定格式的输入就能进行处理。 
(5)如果要做成通用的音频设备,还是必须参照微软MDD/PDD或WAVEDEV格式的来写,至于如何实现多音频设备的切换还没有搞清楚。 

我也是这么想的。只所以先绕过waveapi是因为为了做个测试,看usb音频设备能否正常工作,再者是个特殊的应用,只需要实现简单的收发就可以,这一步已经完成,现在准备做成通用的,通过分析BSP自带的wavedev结构的音频驱动已经对waveapi的原理有了一定程序的了解,可以开始着手进行了。 因为BSP的驱动只有传输这一部分不同,它涉及到DMA、codec等硬件操作,象其它缓冲区管理、数据处理都是通用的,我只需要把与硬件相关的操作替换掉,应该可以实现通用驱动。 

下面就是我的启动日志,可以大致了解工件原理了。 
注:删除了重复的uMsg 
一、启动初始化 
WAV_Init 
+++InitCodec 
---InitCodec 
v_pIOPregs->GPGDAT= 0x0 
+++InitCodec 
---InitCodec 
WAV_Open 
HardwareContext::IOControl - dwCode=321000 
WAV_Open 
WAV_Close 
WAV_Open 
/* The waveform API manager sends this message by calling the audio driver's */ 
/* WAV_IOControl entry point through the DeviceIoControl function. */ 
/* mmddk.h */ 
HandleWaveMessage - uMsg=50 WIDM_GETNUMDEVS 
HandleWaveMessage - uMsg=3 WODM_GETNUMDEVS 
HandleWaveMessage - uMsg=22 WODM_GETEXTDEVCAPS 
HandleWaveMessage - uMsg=5 WODM_OPEN 
HandleWaveMessage - uMsg=17 WODM_SETVOLUME 
HandleWaveMessage - uMsg=7 WODM_PREPARE 
HandleWaveMessage - uMsg=9 WODM_WRITE 
HandleWaveMessage - uMsg=8 WODM_UNPREPARE 
HandleWaveMessage - uMsg=6 WODM_CLOSE 
二、录音(运行WINCE5自带的waverec例程) 
HandleWaveMessage - uMsg=52 WIDM_OPEN 
HandleWaveMessage - uMsg=54 WIDM_PREPARE 
HandleWaveMessage - uMsg=56 WIDM_ADDBUFFER 
/* Starting capture... */ 
HandleWaveMessage - uMsg=57 WIDM_START 
HandleWaveMessage - uMsg=55 WIDM_UNPREPARE 
HandleWaveMessage - uMsg=53 WIDM_CLOSE 
三、播放(使用TCPMP播放WAV文件) 
HandleWaveMessage - uMsg=5 WODM_OPEN 
HandleWaveMessage - uMsg=17 WODM_SETVOLUME 
HandleWaveMessage - uMsg=7 WODM_PREPARE 
HandleWaveMessage - uMsg=9 WODM_WRITE 
HandleWaveMessage - uMsg=8 WODM_UNPREPARE 
HandleWaveMessage - uMsg=6 WODM_CLOSE
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值