Audio Driver 架构
在WINCE中 Audio Driver 架构支持两种驱动模式
即独立型的unified audio model (UAM)驱动 和 分层式的MDD and PDD mode驱动,(不论是UAM或者MDD/PDD都是流接口驱动)。
其架构还支持的audio compression manager (ACM)驱动,例如codecs, converters, and filters等器件
1) UAM
UAM支持标准波形驱动接口(standard wave driver interfaces),过去的波形驱动和采样驱动是由MDD和PDD模型组成。MDD模型执行了驱动的独立硬件部分以及输出到驱动接口的中间设备。PDD模型提供了驱动依赖硬件的执行部分
The following illustration shows the UAM stack.:
Using MDD and PDD, the previous model had the following limitations:
· No support for multiple streams
· No multiple devices on one driver
· No reliable support for looping
· Poor support for streaming
OEM商可以围绕这些限制来移植自己的MDD或者写入他们自己的完整驱动来输出到合适的接口到中间设备
UAM实现了对WAV和Microsoft DirectSound®音频API的高效支持。它还使得编写一个能有效支持WAV和DirectSound的驱动程序成为可能。
在我们的WM8753 音频Driver中即使用了UAM这种驱动模式,它也是一种流接口驱动,故只需编写驱动中WAVE和MIXER这两部分,然后使用流接口函数调用即可。
2).音频MDD和PDD
编写音频驱动我们可以选择UAM架构,或者直接执行流接口(stream interface),我们使用由微软提供的MDD库-Wavemdd.lib。这个库通过DDSI来执行流接口功能。如果使用了Wavemdd.lib,则必须一个PDD库来执行音频DDSI的功能。这个库被称为Wavepdd,lib, 这两个库编译连接后就形成了我们的音频驱动,通常被为Wavedev,dll。
在系统程序文件中可能缺少器件的功能导致音频器件的很多功能无法被使用,为了解决这个问题DeviceIOControl 就变得很重要了。DeviceIOControl是流接口的一部分,其允许任意功能能够通过WAV_IOControl功能来正确得操作硬件。例如:当命令器件准备录音时,中间设备会使用WAV_IOControl发送WIDM_PREPARE消息给音频驱动。
由于音频驱动完全依赖DeviceIOControl功能消息,所以执行剩余得流接口就相对简单了,
特别像WAV_Read, WAV_Seek, and WAV_Write功能仅仅是返回不变的值
下图显示了使用 MDD 库的音频驱动程序的交互
以下列表显示了音频驱动程序的 DDSI 函数
PDD_AudioDeinitialize
This function turns off and disconnects the audio device.
PDD_AudioGetInterruptType
This function determines the cause of the audio interrupt and returns the current device status.
PDD_AudioInitialize
This function initializes the audio device for operation.
PDD_AudioMessage
This function sends messages from user applications to the audio driver's platform-dependent driver (PDD) layer.
PDD_AudioPowerHandler
This function is responsible for managing the audio hardware during POWER_UP and POWER_DOWN notifications.
PDD_WaveProc
This function sends messages to the audio driver's PDD layer.
PDD和MDD都依靠调用DDSI函数来实现相互通信,所以若采用分层式来编写驱动,只需找到微软提供的MDD,然后根据其DDSI来编写PDD层即可。
对于流驱动,×××_Open/×××_Close/×××_IoControl等就是ddi;如果不是流驱动,它的ddi不具有上述形式;
以我们的driver为例,在程序中,我们可以在C:/WINCE500/PLATFORM/C340/Src/Drivers/audio/IIS/wm8753/wavemain.cpp中找到以下对应点
Programming element |
Description |
WAV_IOControl |
This function is the device I/O control routine for the WAV I/O device. |
WAV_Init |
This function initializes the WAV I/O device. |
WAV_Deinit |
This function deinitializes the WAV I/O device. |
WAV_Open |
This function opens the WAV I/O device. |
WAV_Close |
This function closes the WAV I/O device. |
WAV_Read |
This function is the read routine for the WAV I/O device driver. |
WAV_Write |
This function is the write routine for the WAV I/O device. |
WAV_Seek |
This function is the seek routine for the WAV I/O device. |
WAV_PowerUp |
This function notifies the WAV I/O device that the system is leaving the suspend state. |
WAV_PowerDown |
This function turns off the WAV I/O device |
在注册表中还要建立驱动程序的入口点,这样设备管理器才能识别和管理这个驱动
[HKEY_LOCAL_MACHINE/Drivers/BuiltIn/Audio]
"Prefix"="WAV"
"Dll"="s3c2440a_iis_wm8753.dll"
"Index"=dword:1
"Order"=dword:0
此外,注册表还能储存额外的信息,这些信息可以在驱动运行之后被使用到,DLL项是设备管理器在加载驱动时需要的DLL名称;Prefix代表了设备前缀;Order是驱动程序被加载的顺序
|
|
WIDM_GETDEVCAPS |
This message is used to request a waveform input driver to return the capabilities of a specified device. |
WIDM_GETNUMDEVS |
This message is used to request a waveform input driver to return the number of devices that it supports. |
WIDM_GETPOS |
This message is used to request a stream input driver to return the current input position within a waveform. The input position is relative to the first recorded sample of the waveform. |
WIDM_OPEN |
This message is used to request a waveform input driver to open a stream of a specified device. |
WIDM_PREPARE |
This message is used to request a waveform input driver to prepare a system-exclusive data buffer for input. |
WIDM_RESET |
This message is used to request a waveform input driver to stop recording and return all buffers in the input queue to the caller. |
WIDM_START |
This message is used to request a waveform input driver to begin recording. |
WIDM_STOP |
This message is used to request a waveform input driver to stop recording. |
WIDM_UNPREPARE |
This message is used to request a waveform input driver to undo the buffer preparation that was performed in response to a WIDM_PREPARE message. |
下表中列出了各个输出driver消息
Programming element |
Description |
WODM_BREAKLOOP |
This message is used to request a waveform output driver to break an output loop that was created with a WODM_WRITE message. |
WODM_CLOSE |
This message is used to request a waveform output driver to close a specified stream that was previously opened with a WODM_OPEN message. |
WODM_GETDEVCAPS |
This message is used to request a waveform output driver to return the capabilities of a specified device. |
WODM_GETNUMDEVS |
This message is used to request a waveform output driver to return the number of device instances that it supports. |
WODM_GETPITCH |
This message is used to request a waveform output driver to return the specified device's current pitch multiplier value. |
WODM_GETPLAYBACKRATE |
This message is to request a waveform output driver to return the current playback rate multiplier value for the specified device. |
WODM_GETPOS |
This message is used to return the current position within a stream. The position is relative to the beginning of the waveform. |
WODM_GETVOLUME |
This message is used to request a waveform output driver to return the current volume level setting for the specified device or stream. |
WODM_OPEN |
This message is used to request a waveform output driver to open a stream on the specified device. |
WODM_PAUSE |
This message is used to request a waveform output driver to pause playback of a waveform. |
WODM_PREPARE |
This message is used to request a waveform output driver to prepare a system-exclusive data buffer for output. |
WODM_RESET |
This message is used to request a waveform output driver to stop sending output data and return all output buffers to the list. |
WODM_RESTART |
This message is used to request a waveform output driver to continue playback of a waveform after playback has been paused with WODM_PAUSE. |
|
WODM_SETPITCH |
This message is used to request a waveform output driver to set the specified device's pitch multiplier value. |
WODM_SETPLAYBACKRATE |
This message is used to request a waveform output driver to set the playback rate multiplier value for the specified device. |
WODM_SETVOLUME |
This message is used to request a waveform output driver to set the playback rate multiplier value for the specified device. |
WODM_UNPREPARE |
This message is used to request a waveform output driver to remove the buffer preparation performed in response to WODM_PREPARE. |
WODM_WRITE |
This message is used to request a waveform output driver to write a waveform data block to the specified device. |
以我们的WM8753音频Driver 为例,整个驱动里包含了以下重要功能的子驱动:
Devctxt.cpp |
器件关联——包含了音频流的创造,删除,打开,关闭,格式等功能 |
Hwctxt.cpp |
硬件关联——包含了基本的硬件功能在各个状态的全局配置 |
I2citf.cpp |
I2C传输配置 |
I2S.cpp |
I2S传输配置 |
Input.cpp |
负责输入音频流 |
Output.cpp |
负责输出音频流 |
Midinote.cpp |
负责输出MIDI |
Midistrm.cpp |
负责MIDI的开关以及控制 |
Mixerdrv.cpp |
系统软件混音 |
RTcodecComm.cpp |
Wm8753的所有功能配置,以及初始化设置 |
Strmctxt.cpp |
负责所有音频流的增益,buffer请求等功能以及对Devctxt的控制 |
Wavemain.cpp |
包含了所有的流接口函数 |
按照UAM的定义, wm8753的driver主要由mixer和wave两部分组成;
由于mixer只实现一些基本的混音功能,其主要由Mixerdrv.cpp承担,还包括Midistrm.cpp,Input.cpp,Output.cpp的部分功能。而其他基本都是wave功能
Wavemain.cpp基于整个驱动的最上层,其中,流接口函数做到了以下控制:
Wav_init ——> hwctxt RTcodecComm
I2citf.,
I2S
Wav_deinit devctxt
Wav_Powerup hwctxt
Wav_powerdown mixerdrv
strmctxt
Wav_IOControl——> Wav_open/close——> 所有
若要移植一个音频驱动到另一个器件,一般只需更改Hwctxt.cpp,I2citf.cpp,I2S.cpp,RTcodecComm.cpp 即可
四.以下是WM8753的WAV_IOControl调用说明:
extern "C" BOOL WAV_IOControl(DWORD dwOpenData,
DWORD dwCode,
PBYTE pBufIn,
DWORD dwLenIn,
PBYTE pBufOut,
DWORD dwLenOut,
PDWORD pdwActualOut)
{
_try
{
switch (dwCode)
{
case IOCTL_MIX_MESSAGE:
return HandleMixerMessage((PMMDRV_MESSAGE_PARAMS)pBufIn, (DWORD *)pBufOut);//调用混音消息
case IOCTL_WAV_MESSAGE:
return HandleWaveMessage((PMMDRV_MESSAGE_PARAMS)pBufIn, (DWORD *)pBufOut);//调用音频消息
//以下为电源管理功能
case IOCTL_POWER_CAPABILITIES:
case IOCTL_POWER_SET:
case IOCTL_POWER_GET:
return g_pHWContext->IOControl
(dwOpenData, dwCode, pBufIn, dwLenIn, pBufOut, dwLenOut, pdwActualOut);
}
}
BOOL HandleWaveMessage(PMMDRV_MESSAGE_PARAMS pParams, DWORD *pdwResult) //管理音频消息
{
// set the error code to be no error first
SetLastError(MMSYSERR_NOERROR);
UINT uMsg = pParams->uMsg;
UINT uDeviceId = pParams->uDeviceId;
DWORD dwParam1 = pParams->dwParam1;
DWORD dwParam2 = pParams->dwParam2;
DWORD dwUser = pParams->dwUser;
StreamContext *pStreamContext = (StreamContext *)dwUser;
DWORD dwRet;
g_pHWContext->Lock();
// catch exceptions inside device lock, otherwise device will remain locked!
_try
{
switch (uMsg)
{
case WODM_GETNUMDEVS: //This message is used to request a waveform output driver to return the capabilities of a specified device.
{
dwRet = g_pHWContext->GetNumOutputDevices();
break;
}
case WIDM_GETNUMDEVS:// This message is used to request a waveform input driver to return the number of devices that it supports.
{
dwRet = g_pHWContext->GetNumInputDevices();
break;
}
case WODM_GETDEVCAPS:// This message is used to request a waveform output driver to return the capabilities of a specified device.
{
DeviceContext *pDeviceContext;
UINT NumDevs = g_pHWContext->GetNumOutputDevices();
if (pStreamContext)
{
pDeviceContext=pStreamContext->GetDeviceContext();
}
else
{
pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
}
dwRet = pDeviceContext->GetDevCaps((PVOID)dwParam1,dwParam2);
break;
}
case WIDM_GETDEVCAPS:// This message is used to request a waveform input driver to return the capabilities of a specified device.
{
DeviceContext *pDeviceContext;
http://www.bbfar.com/article/2e/590.html
UINT NumDevs = g_pHWContext->GetNumInputDevices();
if (pStreamContext)
{
pDeviceContext=pStreamContext->GetDeviceContext();
}
else
{
pDeviceContext = g_pHWContext->GetInputDeviceContext(uDeviceId);
}
dwRet = pDeviceContext->GetDevCaps((PVOID)dwParam1,dwParam2);
break;
}
case WODM_GETEXTDEVCAPS:
{
DeviceContext *pDeviceContext;
UINT NumDevs = g_pHWContext->GetNumOutputDevices();
if (pStreamContext)
{
pDeviceContext=pStreamContext->GetDeviceContext();
}
else
{
pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
}
dwRet = pDeviceContext->GetExtDevCaps((PVOID)dwParam1,dwParam2);
break;
}
case WODM_OPEN:// This message is used to request a waveform output driver to open a stream on the specified device.
{
// DEBUGMSG(1, (TEXT("WODM_OPEN/r/n"));
DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)dwUser);
break;
}
case WIDM_OPEN:// This message is used to request a waveform input driver to open a stream of a specified device.
{
// DEBUGMSG(1, (TEXT("WIDM_OPEN/r/n"));
DeviceContext *pDeviceContext = g_pHWContext->GetInputDeviceContext(uDeviceId);
dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)dwUser);
break;
}
case WODM_CLOSE:
case WIDM_CLOSE:
{
// DEBUGMSG(1, (TEXT("WIDM_CLOSE/WODM_CLOSE/r/n"));
dwRet = pStreamContext->Close();
// Release stream context here, rather than inside StreamContext::Close, so that if someone
// (like CMidiStream) has subclassed Close there's no chance that the object will get released
// out from under them.
if (dwRet==MMSYSERR_NOERROR)
{
pStreamContext->Release();
}
break;
}
case WODM_RESTART:
case WIDM_START:
{
dwRet = pStreamContext->Run();
break;
}
case WODM_PAUSE:
case WIDM_STOP:
{
dwRet = pStreamContext->Stop();
break;
}
case WODM_GETPOS:
case WIDM_GETPOS:
{
dwRet = pStreamContext->GetPos((PMMTIME)dwParam1);
break;
}
case WODM_RESET:
case WIDM_RESET:
{
dwRet = pStreamContext->Reset();
break;
}
case WODM_WRITE:
case WIDM_ADDBUFFER:
{
// DEBUGMSG(1, (TEXT("WODM_WRITE/WIDM_ADDBUFFER, Buffer=0x%x/r/n"),dwParam1);
dwRet = pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);
break;
}
case WODM_GETVOLUME:// This message is used to request a waveform output driver to return the current volume level setting for the specified device or stream.
{
PULONG pdwGain = (PULONG)dwParam1;
if (pStreamContext)
{
*pdwGain = pStreamContext->GetGain();
}
else
{
#ifdef USE_HW_GAIN_WODM_SETGETVOLUME
// Handle device gain in hardware
*pdwGain = g_pHWContext->GetOutputGain();
#else
// Handle device gain in software
DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
*pdwGain = pDeviceContext->GetGain();
#endif
}
dwRet = MMSYSERR_NOERROR;
break;
}
case WODM_SETVOLUME:// This message is used to request a waveform output driver to set the playback rate multiplier value for the specified device.
{
LONG dwGain = dwParam1;
if (pStreamContext)
{
dwRet = pStreamContext->SetGain(dwGain);
}
else
{
#ifdef USE_HW_GAIN_WODM_SETGETVOLUME
// Handle device gain in hardware
dwRet = g_pHWContext->SetOutputGain(dwGain);
#else
// Handle device gain in software
DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
dwRet = pDeviceContext->SetGain(dwGain);
#endif
}
break;
}
case WODM_BREAKLOOP:// This message is used to request a waveform output driver to break an output loop that was created with a WODM_WRITE message.
{
dwRet = pStreamContext->BreakLoop();
break;
}
case WODM_SETPLAYBACKRATE:// This message is used to request a waveform output driver to set the playback rate multiplier value for the specified device. {
WaveStreamContext *pWaveStream = (WaveStreamContext *)dwUser;
dwRet = pWaveStream->SetRate(dwParam1);
break;
}
case WODM_GETPLAYBACKRATE:// This message is to request a waveform output driver to return the current playback rate multiplier value for the specified device.
{
WaveStreamContext *pWaveStream = (WaveStreamContext *)dwUser;
dwRet = pWaveStream->GetRate((DWORD *)dwParam1);
break;
}
case MM_WOM_SETSECONDARYGAINCLASS:
{
dwRet = pStreamContext->SetSecondaryGainClass(dwParam1);
break;
}
case MM_WOM_SETSECONDARYGAINLIMIT:
{
DeviceContext *pDeviceContext;
if (pStreamContext)
{
pDeviceContext = pStreamContext->GetDeviceContext();
}
else
{
pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);
}
dwRet = pDeviceContext->SetSecondaryGainLimit(dwParam1,dwParam2);
break;
}
case MM_MOM_MIDIMESSAGE:
{
CMidiStream *pMidiStream = (CMidiStream *)dwUser;
dwRet = pMidiStream->MidiMessage(dwParam1);
break;
}
// For Debug Register
case WPDM_PRIVATE_WRITE_CODEC:
case WPDM_PRIVATE_READ_CODEC:
//{
// dwRet=g_pHWContext->Private_AudioMessage(pParams->uMsg, pParams->dwParam1, pParams->dwParam2);
// break;
//}
// unsupported messages
case WODM_GETPITCH:
case WODM_SETPITCH:
case WODM_PREPARE:
case WODM_UNPREPARE:
case WIDM_PREPARE:
case WIDM_UNPREPARE:
default:
dwRet = MMSYSERR_NOTSUPPORTED;
break;
}
}
_except (GetExceptionCode() == STATUS_ACCESS_VIOLATION ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
ERRORMSG(1, (TEXT("Access violation in HandleWaveMessage!!!!/r/n")));
SetLastError(E_FAIL);
}
g_pHWContext->Unlock();
// Pass the return code back via pBufOut
if (pdwResult)
{
*pdwResult = dwRet;
}
return(TRUE);
}