详解Wavedev2模式的音频驱动


WinCE 音频驱动

一、WinCE目前有3种音频模式:MDD/PDD、WaveDev2和UAM。

这些模式的存在是有历史原因的,下面具体说明一下。首先,这3个模式是使用同一套WaveAPI的驱动接口,在系统中以驱动形式存在,都会导出WAV_Open、WAV_Close等接口。通过IoControl带入IOCTL_WAV_MESSAGE,与waveapi的子系统配合使用。对下都是控制硬件,它们不同之处在于内部的设计。

(1)MDD/PDD:

CE的旧驱动模型,MDD/PDD把驱动分成2块,sort-of hardware independent的MDD层和really hardware dependent的PDD层。MDD部分是通用的、公开出来的,生成wavemdd.lib的库;PDD必须是OEM自己编写的。这2部分静态的链接起来,才形成一个完整的驱动。

Waveapi驱动已经把硬件依赖关系给分割开来。MDD和PDD把驱动分成硬件相关的和硬件无关的。为了把驱动分割成硬件无关的,MDD层要设想好硬件的工作流程和功能。下面是MDD和PDD需要设定的内容:

A,只有一个设备

B,设备只有1个流(1个输入流和1个输出流)。Waveapi包含了软件混音器(software mixer),在应用层把多个流合并成一个流。

C,输入输出DMA共享一个中断

由此,此模式把PDD层大大简化了,使得音频设备比较容易的被移植和更改。但如果你的设备是比较特殊的,或者需要添加特别的功能,那就需要自己修改MDD的代码。这可能需要做的比较复杂。

(2)WaveDev2:

2000年开始smartphone开发时候,增添了对音频的许多需求,而MDD/PDD模式不能不做改动。在CE 3.0时候,还没有waveapi的软件混音提供,不可以同时播放多个音乐。那就需要一个新的设计,WaveDev2诞生了。

WaveDev2的所有文件都在一个目录下。移植WaveDev2驱动,只需要把样例的所有文件复制过来,再进行修改就可以了。主要修改hwctxt.h和hwctxt.cpp。CE6发布时候带有WaveDev2的样例(public/common/oak/drivers/wavedev/wavedev2/ensoniq),

WaveDev2驱动有以下新特性:

A,MIDI合成器。手机上需要播放MIDI音乐。

B,输入输出流的采样率转换。驱动可以把多个不同采样率的输出流,合并成一个流。也可以把一个输入流,分成多个应用程序需要的采样率输出流(这个应用比较特殊,很少见)。

C,Gain class接口。每个输出流都有自己的class(音量),当应用创建一个新的流时候,它的class是0。系统可以通过不同的waveOutMessage来控制每个class的音量等级。如电话进来时候,后台播放的音乐要静音。

D,Forcespeaker,主要是电话进来时候,让speaker播放铃声。

E,支持S/PDIF接口,让WMAPro流通过S/PDIF进行压缩。

如果是开发一个电话设备,最好使用WaveDev2的模式,系统需要依赖WaveDev2的扩展功能。

(3)UAM:

CE 4.2开始,需要为新增的DirectSound提供驱动。新的设计会利用WaveDev2的一部分改变,增加了使用硬件来混合2个音频流。UAM和WaveDev2看起来差不多,实际内部设计上还是有许多差别的。

真正使用硬件做混音的需求,实在太少。所以在CE5上,DirectSound才被加入。这个设计并不是很有价值,因此许多OEM还是使用以前的音频驱动设计。

二、阅读CPU手册,查看音频接口:

通过阅读AM35X ARM Microprocessor芯片手册,了解到Multi-Channel Buffered Serial Port,共5个,其中McBSP2( Audio data with audio buffer and SIDETONE feature)用来外接音频从设备,来实现音频功能。

音频从设备选用TI的TLV320AIC23B芯片,接下来,就是见证奇迹的时刻……(如何在WINCE中音频驱动架构中添加TLV320AIC23B的驱动)

三、WinCE的Wavedev2模式实现:

Wavedev2模式是单层的驱动模式,平台相关的都在hwctxt.h和hwctxt.cpp中,此外还加入了midi支持、software mixer支持、S/PDIF接口、gain class接口、forcespeaker接口等等。


(1)音频系统的软件实现

音频设备驱动程序通过对硬件的控制实现音频数据流传输,同时向上层提供标准的音频接口。其中的一个流接口函数WAV_IOControl()提供应用层控制音频设备的接口,通过输入和输出消息实现对硬件的控制,主要包括两个函数HandleWaveMessage()和HandleMixerMessage (),其中HandleWaveMessage()负责音频数据的传输即播放数字化声音文件和录音操作等;而HandleMixerMessage ()负责对输出音频进行混音处理,如音量调节、高低音控制等。

在音频数据采样过程中,假如按音频采样频率为44.1KHz、16位每个声道、左右2个声道来计算,码流为1.411Mbps,所以采用DMA控制器来传输十分必要。(有关音频DMA的具体说明,见文:WINCE 平台的DMA)

(2)混音的处理

如果要WINDOWS CE的声音驱动模型支持混音,则要考虑如下问题:

A,声音设备是否支持硬件混音

B,声音设备需要工作在同一种采样频率下

C,声音设备要能够同时支持录音和放音操作

而声音的驱动要负责完成声音采集的混音和声音放音的混音。其基本原理如下:

A,将声音设备设定在一个频率下,比如:44.1KHZ,16BIT

B,驱动允许打开多个音频流,每个音频流可以允许不同的采样率,比如: (A:8KHZ,8BIT B: 44.1KHZ,16BIT)

C,放音的混音在最后的数据准备阶段(即放入到DMA前),用合成算法将所有的流进行数学运算,得出声音设备采样频率下(44.1KHZ,16BIT)的数据。数据通过DMA发送到CODEC中。

D,录音的混音操作,都是从声音设备采样频率下(44.1KHZ,16BIT)下得到采样的基本数据,然后通过数学运算分配到不同的频率下的音频流上。

E, 要注意的就是合成的时候要注意数据不溢出;分开的时候要注意数据速率的匹配和数据宽度的匹配。

(3)Wavedev2的流程

先不去看具体跟硬件操作相关的东西,而是从流程入手,把整个流程搞清楚了,调试起来就非常的容易了。我们着重看hwctxt.cpp,hwctxt.H,devctxt.cpp,devctxt.H,strmctxt.cpp,strmctxt.H这几个源文件。其中hwctxt是类HardwareContext代码文件,devctxt是DeviceContext代码文件,strmctxt是StreamContext代码文件。这几个类的其他一些功能,还在其他一些文件中实现,如output.Cpp,midistrm.Cpp等。

现在我们来看下StreamContext的类图,StreamContext是管理音频流的对象,包括播放、暂停、停止、设置音量、获取播放位置等。从下面的StreamContext的类图中,我们可以看到它派生了WaveStreamContext和MidiStream。然后WaveStreamContext又派生了Input和Output类型的Stream。不用说也可以知道InputStreamContext是针对于像麦克这种输入设备流的。

StreamContext类图

 

其中OutputStreamContext派生了六个类,M代表单音道,S代表的是立体音,8/16是8/16比特采样了。 SPDIF(SONY/PHILIPS DIGITAL INTERFACE)是一种最新的音频传输格式,它通过光纤进行数字音频信号传输以取代传统的模拟信号传输方式,因此可以取得更高质量的音质效果。

StreamContext是一个管理音频数据流的对象,像智能手机中可能存在用media player播放音乐,同时又开着FM,突然又来电。从上篇文章中我们知道,要想调用wave驱动的播放功能,每个应用都有一份StreamContext对象,上面提到的状况,就会有三个StreamContext对象被创建。在硬件只要一个的条件下,那么这三个StreamContext是如果协同工作的呢?而DeviceContext正是管理StreamContext对象的。

如下是DeviceContext类图:

DeviceContext类图

 

DeviceContext派生出InputDeviceContext和OutputDeviceContext,他们分别管理InputStreamContext和OutputStreamContext。在DeviceContext内部维护了一个双向链表来管理StreamContext。

HardwareContext是具体操作硬件相关的类,其内部包含InputDeviceContext和OutputDeviceContext对象,下面这种图,就是三个类的关系图,一看就知道他们的对应关系了。

DeivceContext和StreamContext关系图

 

对于HardwareContext是具体操作硬件的东西,不具有代码性,只要仔细看看代码就行了。现在我们主要分析下DeviceContext和StreamContext的关系。

DeviceContext的作用是管理StreamContext,可以分为几套函数,见Devctxt.h, Devctxt.cpp

音量增益管理:下面这个函数主要是设置设备的整个音量增益,设置了设备音量增益后,对流音量的增益起了限制做用的。音量函数如下:

DWORD GetGain();

DWORD SetGain(DWORD dwGain);

DWORD GetDefaultStreamGain();

DWORD SetDefaultStreamGain(DWORD dwGain);

DWORD GetSecondaryGainLimit(DWORD GainClass);

DWORD SetSecondaryGainLimit(DWORD GainClass, DWORD Limit);

先来讲下设备音量增益(Device Gain)和流音量增益(Stream Gain)的关系。我们从微软Media Player中,很容易就看到了设备音量和流音量的关系。设备音量时通过音量键来控制系统的音量,从而改变整个输出设备的音量的,但是在Media Player中,还是有一个单独的音量控制按钮,它能调节Media Player的音量(不要问我在哪里,自己找),但是调试它是受限制于系统音量,是如何限制,请看下面讲解。

我们现在看下设置系统音量和设置流音量的整个流程,来了解整个音量控制的过程。用户设置时,会调用waveOutSetVolume:

MMRESULT waveOutSetVolume(

HWAVEOUT hwo,

DWORD dwVolume

);

当HWAVEOUT传入为空时,设置的就是设备音量,当HWAVEOUT是通过调用waveOutOpen返回的句柄是,设置的就是流音量。

好,我们进入到驱动中区看看,waveOutSetVolume会调用到来看wavemain.Cpp中HandleWaveMessage的WODM_SETVOLUME分支,我在代码中去掉了不重要的部分,可以看得更清晰些。

case WODM_SETVOLUME:

{

StreamContext *pStreamContext;

pStreamContext = (StreamContext *) dwUser;

LONG dwGain = dwParam1;

if (pStreamContext)

{

dwRet = pStreamContext->SetGain(dwGain);

}

else

{

DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);

dwRet = pDeviceContext->SetGain(dwGain);

}

}

dwUser 指向的是StreamContext对象(在前文中已经讲过),如果pStreamContext为空,那么就调用DeviceContext的SetGain函数,否则调用StreamContext的SetGain函数。调用StreamContext的Gain只对当前的StreamContext的音量起作用,不影响其他的Stream音量。但是对DeviceContext设置音量增益是对DeviceContext管理的所有StreamContext起了控制作用,但是具体是如何影响的,还是根据代码来分析:

在Devctxt.h中的SetGain函数代码如下:

DWORD SetGain(DWORD dwGain)

{

m_dwGain = dwGain;

RecalcAllGains();

return MMSYSERR_NOERROR;

}

用m_dwGain保存设备音量,然后调用RecalcAllGains来重新计算所有StreamContext的音量增益。

在Devctxt.cpp中的RecalcAllGains的实现如下:

void DeviceContext::RecalcAllGains()

{

PLIST_ENTRY pListEntry;

StreamContext *pStreamContext;

for (pListEntry = m_StreamList.Flink;

pListEntry != &m_StreamList;

pListEntry = pListEntry->Flink)

{

pStreamContext = CONTAINING_RECORD(pListEntry,StreamContext,m_Link);

pStreamContext->GainChange();

}

return;

}

它便利所有的StreamContext,并调用pStreamContext->GainChange()来改变StreamContext对象的音量。接着看StreamContext类中的GainChange的实现:

void GainChange()

{

m_fxpGain = MapGain(m_dwGain);

}

DWORD StreamContext::MapGain(DWORD Gain)

{

DWORD TotalGain = Gain & 0xFFFF;

DWORD SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass) & 0xFFFF;

if (m_SecondaryGainClass < SECONDARYDEVICEGAINCLASSMAX)

{

// Apply device gain

DWORD DeviceGain = m_pDeviceContext->GetGain() & 0xFFFF;

TotalGain *= DeviceGain;

TotalGain += 0xFFFF; // Round up

TotalGain >>= 16; // Shift to lowest 16 bits

}

// Apply secondary gain

TotalGain *= SecondaryGain;

TotalGain += 0xFFFF; // Round up

TotalGain >>= 16; // Shift to lowest 16 bits

// Special case 0 as totally muted

if (TotalGain==0)

{

return 0;

}

// Convert to index into table

DWORD Index = 63 - (TotalGain>>10);

return GainMap[Index];

}

音量在系统中用一个DWORD值来表示,其高低两个字节分别来表示左右声道,一般情况下左声道和右声道的音量大小是一样的,所以只取其低两个字节,DWORD TotalGain = Gain & 0xFFFF;

TotalGain是DeviceGain和m_dwGain的乘积,然后再左移16位得到的。其实就是TotalGain=DeviceGain*m_dwGain/最高音量,如果把DeviceGain/最高音量,用百分比来算的话,就很更容易理解了,那么最后的公式就变成TotalGain=DeviceGain*系统音量百分比。那么这里就解释了系统音量是如何限制流音量的疑问。

我们设置好音量增益后,最终会再哪里体现呢:首先看一下Output.cpp文件,WaveStreamContext::Render之后的数据就是直接发送到外部声音芯片的数据,他根据参数以及标志位选择OutputStreamContextXXX::Render2,XXX表示双声道S单声道M,bit位是8位还是16位。以双声道OutputStreamContextS16::Render2为例,BSP里面的代码如下:

PBYTE OutputStreamContextS16::Render2(PBYTE pBuffer, PBYTE pBufferEnd, PBYTE pBufferLast)

{

LONG CurrT = m_CurrT;

LONG DeltaT = m_DeltaT;

LONG CurrSamp0 = m_CurrSamp[0];

LONG PrevSamp0 = m_PrevSamp[0];

PBYTE pCurrData = m_lpCurrData;

PBYTE pCurrDataEnd = m_lpCurrDataEnd;

LONG fxpGain = m_fxpGain;

LONG OutSamp0;

__try

{

while (pBuffer < pBufferEnd)

{

while (CurrT >= 0x100)

{

if (pCurrData>=pCurrDataEnd)

{

goto Exit;

}

CurrT -= 0x100;

PrevSamp0 = CurrSamp0;

PPCM_SAMPLE pSampleSrc = (PPCM_SAMPLE)pCurrData;

CurrSamp0 = (LONG)pSampleSrc->s16.sample_left;

CurrSamp0 += (LONG)pSampleSrc->s16.sample_right;

CurrSamp0 = CurrSamp0>>1;

pCurrData+=4;

}

OutSamp0 = PrevSamp0 + (((CurrSamp0 - PrevSamp0) * CurrT) >> 8);

// 设置增益

OutSamp0 = (OutSamp0 * fxpGain) >> VOLSHIFT;

CurrT += DeltaT;

if (pBuffer < pBufferLast)

{

OutSamp0 += *(HWSAMPLE *)pBuffer;

}

*(HWSAMPLE *)pBuffer = (HWSAMPLE)OutSamp0;

pBuffer += sizeof(HWSAMPLE);

}

}//end the __try block

__except (EXCEPTION_EXECUTE_HANDLER)

{

RETAILMSG(1, (TEXT("InputStreamContext::Render2!/r/n")));

m_lpCurrData = m_lpCurrDataEnd = NULL;

return NULL;

}

Exit:

m_dwByteCount += (pCurrData - m_lpCurrData);

m_lpCurrData = pCurrData;

m_CurrT = CurrT;

m_PrevSamp[0] = PrevSamp0;

m_CurrSamp[0] = CurrSamp0;

return pBuffer;

}

从上面看到是与采样数据相乘,然后在左移16位。跟上面提到的系统音量影响流音量是一样的。

上面讲了,DeviceContext的音量增益管理,现在来看下它的流管理。

StreamContext流管理:主要来管理StreamContext的创建、删除、渲染、传输等功能。主要函数:

StreamContext *CreateStream(LPWAVEOPENDESC lpWOD);

DWORD OpenStream(LPWAVEOPENDESC lpWOD, DWORD dwFlags, StreamContext **ppStreamContext);

HRESULT Open(DeviceContext *pDeviceContext, LPWAVEOPENDESC lpWOD, DWORD dwFlags);

void NewStream(StreamContext *pStreamContext);

void DeleteStream(StreamContext *pStreamContext);

void StreamReadyToRender(StreamContext *pStreamContext);

PBYTE TransferBuffer(PBYTE pBuffer, PBYTE pBufferEnd, DWORD *pNumStreams, BOOL bMuteFlag);

在DeviceContext中有个m_StreamList的双向链表(LIST_ENTRY), m_StreamList用来指向链表的头。在StreamConext中也存在一个m_Link(LIST_ENTRY)。StreamContext是调用DeviceContext的OpenStream来创建的,然后把StreamContext对象加入到DeviceContext的m_StreamList中。

上层调用waveoutOpen,在wavedev2中会调用WODM_OPEN这个分支。在WODM_OPEN中的代码如下:

case WODM_OPEN:

{

StreamContext *pStreamContext;

pStreamContext = (StreamContext *) dwUser;

dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)pStreamContext);

break;

}

OpenStream的其流程图如下:

StreamContext 初始化流程

 

CreateStream是根据WAVEFORMATEX这个结构体,来判断具体要创建StreamContext的哪个派生类,下面是CreateStream的流程图,不可不提,还是流程图清晰。

OutputDeviceContext:: CreateStream流程图

 

上面讲了上层通过WODM_OPEN创建一个StreamContext的过程,那么音频流被打开之后,接下来就是给StreamContext传入音频数据开始播放音乐。Wavedev2提供了WODM_WRITE来向音频设置写入数据。我们先看下WODM_WRITE分支的代码:

case WODM_WRITE:

{

StreamContext *pStreamContext;

pStreamContext = (StreamContext *) dwUser;

dwRet = pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);

break;

}

这里调用了StreamContext中的QueueBuffer,QueueBuffer的作用就是把WAVEHDR中的数据加入到StreamContext的队列中,等待播放。下面是QueueBuffer的流程图:


QueueBuffer流程图

在QueueBuffer中调用DeviceContext中的StreamReadyToReander通知可以开始渲染了,流程图中的箭头方向是StreamReadyToReander调用流程,最终调用SetEvent(hOutputIntEvent),来通知线程数据已经准备好,得到通知后,就开始播放了。该线程在HardwareContext中的OutputInterruptThread函数中

OutputInterruptThread流程如下:


四、程序实现:

创建平台相关的WAVEDEV2目录,wavemain.cpp中开始初始化:

1,WAV_Init

===>HardwareContext::CreateHWContext(hwctxt.cpp)

===>HardwareContext::Init

    ===>通过GetDriverRegValue获得I2C总线接口,打开MCBSP

    ===>创建TLV320AIC23B芯片类m_CAIC23 = new CAIC23(); (创建aic23.cpp和aic23.h,并创建class CAIC23)

    ===>CAIC23::InitHardware

        ===>通过I2C总线,来控制TLV320AIC23B,先reset

        ===>然后configure(选择DAC、频率、使能Slave、设置input bit length、Activate interface等初始化工作,特别注意mic是否被设置成静音)

        ===>SetSampleRate设置采样率(对照TLV320AIC23B手册)

    ===>Register MCBSP callbacks注册回调函数

2,SetMicBoost设置mic输入的boost(声音增益)

3,InputEnable打开/关闭mic/line的输入

4,Volume的设置

5,WAV_IOControl控制函数,使用上层API——wave***系列时,通过该函数来控制。

 

注意事项:

1,InputEnable确保mic打开,line关闭,ADC(输入)打开

2,调节mic boost、mic muted、INSEL(选择mic)

 

附录一:相关文件

Devctxt.cpp 器件关联——包含了音频流的创造,删除,打开,关闭,格式等功能

Hwctxt.cpp 硬件关联——包含了基本的硬件功能在各个状态的全局配置

Input.cpp 负责输入音频流

Output.cpp 负责输出音频流

Midinote.cpp 负责输出MIDI

Midistrm.cpp 负责MIDI的开关以及控制 

Mixerdrv.cpp 系统软件混音

XXX.cpp XXX芯片的所有配置,以及I2C读写等

Strmctxt.cpp 负责所有音频流的增益,buffer请求等功能以及对Devctxt的控制

Wavemain.cpp 包含了所有的流接口函数(init、IOcontrol等)

若要移植音频驱动到另一个器件,一般更改Hwctxt.cpp、XXX.cpp和相关头文件。

 

附录二:注册表

1,在注册表中还要建立驱动程序的入口点,这样设备管理器才能识别和管理这个驱动。

[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/WaveDev]

"Prefix"="WAV"

"Dll"="XXX_wavedev2.dll"

"PortDriver"="MCP1:"

"I2CBaudrateIndex"=dword:1

"I2CBus"=dword:3

"I2CAddress"=dword:1A

……

2,声音设置都在注册表:HKEY_CURRENT_USER\ControlPanel\Volume下,里面的几个键值都是控制声音的。如下:

Volume:系统的主音量,范围是0x0 ~ 0xFFFFFFFF。

Screen:屏幕敲击声。当数值为0(或65536)无声,1为柔和,65538为洪亮。

Key:键盘敲击声,数值的意义和Screen相同。

Mute:控制其它静音的选项。置0x04位为1时允许事件声音,0x02允许应用程序声音,0x01允许警告声。需要注意的是,如果不允许应用程序声音,则警告声位也将被忽略。

通过waveOutSetVolume()这个API,我们可以很容易的更改系统设备的音量,但这个时候,如果你去查看注册表的Volume的键值是没有变化的,因为它只修改了设备的音量,变化还没有这么快到达注册表。但你可以到“控制面板”中的“音量与声音”打开一下,注册表的值也随之改变。(反之,通过对注册表的单独操作对具体音量是起不到作用的)

系统音量的调节,是通过Creg(对注册表操作)类,调用SetDW改变注册表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值