一. Windows CE的驱动程序的区分
1.从加载以及接口方式来区分
可以分为本机设备驱动(Built-In Driver)、可加载驱动(Loadable Driver)以及混合型驱动。
(1)本机设备驱动
本机设备驱动即Native Device Drivers。这些驱动程序在系统启动时,在GWES的进程空间内被加载,因此它们不是以独立的DLL形式存在。这些驱动对应的设备通常在系统启动时就被要求加载,如果没有串口,也没有LCD的话,整个系统就不能和用户信息交流。另外,流驱动程序也能作为本机设备驱动而存在。
(2)可加载驱动
也被称为流驱动。
这些驱动可以在系统启动时或者和启动后的任何时候由设备管理器动态加载。通常它们以DLL动态链接库的形式存在,系统加载它们后,这些驱动程序也只是以用户态的角色运行。可加载驱动程序通过文件操作API来从设备管理器和应用程序获得命令。
在Windows CE中典型的可加载驱动有以下各类:
n PCMCIA driver(PCMCIA.dll)
n Serial driver(SERIAL.dll)
n ATAFLASH driver(ATA.dll)
n Ethernet driver(NE2000.dll,SMSC100FD.dll)
(3)混合型驱动
这类驱动综合了前两种驱动的特性。它同时使用了stream接口和custom-purpose接口。
混合型驱动主要是提供custom-purpose 接口,但是由于需要和系统中只允许使用stream接口的那些模块进行交互,因此也必须提供stream接口。例如,PC card socket驱动同时拥有两套接口。
2.从驱动层次上分
可以分为独立驱动和层次型驱动,下图是这两种驱动在系统中的位置。
(1)独立驱动程序
可以将驱动程序编写成同时包含MDD和PDD层的独立驱动。独立驱动的代码应当包括中断服务例程和平台相关处理函数。使用独立驱动的好处在于可以省去MDD和PDD层驱动之间的信息传递,这一点在实时处理中非常重要。另外,如果设备的操作和MDD驱动层的接口描述相吻合,可以使用独立驱动程序提高处理性能。
(2)层次型驱动
层次型驱动分为两层,较上层的Model Device Driver(MDD)和比较下层的Platform Dependent Driver(PDD)。MDD实现的是和平台无关的功能,它描述了一个通用的驱动程序框架。而PDD是和硬件以及平台相关的代码组成。MDD调用PDD中特定的接口来获取硬件相关的信息。当使用层次型驱动的时候,一般只需要基于相近的样列驱动程序,针对特定的硬件修改PDD程序,MDD建立的框架可继续使用。由于层次间接口的层层调用以及消息的传递,使得处理速度相对独立驱动程序要慢,因此在时间要求苛刻的环境下,层次型驱动显得不是很适合。
一般MDD将完成以下任务。
n 连接PDD层,并且定义它要使用到的Device Driver Service Provider Interface(DDSI)函数集;
n 向设备管理器提供Device Driver Interface(DDI)接口集;
n 处理复杂的事件,如中断等等。
每一种MDD驱动都处理不同种类的设备。DDI是由MDD层驱动以及独立型驱动提供给设备管理器的一组接口集。DDSI是由PDD向MDD层提供的接口集。公司的设备可以用同样的DDI。
在开发过程中,MDD层驱动是不需要被修改的。微软公司不保证被修改的MDD能在系统中正确运行的。和MDD层驱动不同的是,PDD层驱动必须被修改成和特定硬件相匹配的代码。程序员可以自己开发一个PDD程序,多数情况下建议开发者在Platform Builder提供的样例驱动程序上进行修改。例如,Platform Builder提供了Wavedev驱动程序,它的代码位于%WINCEROOT%\public\common\oak\drivers\WAVEDEV下,这是一个容易理解的流接口层次型驱动程序。此样例audio驱动程序仅提供了播放及录音功能,只提供播放功能的结构框架,播放功能和音频设备的交互还需要 PDD层来解决。
二. 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
PDD和MDD都依靠调用DDSI函数来实现相互通信,所以若采用分层式来编写驱动,只需找到微软提供的MDD,然后根据其DDSI来编写PDD层即可。
三. 流接口驱动
不论是UAM或者PDD/MDD的架构,它们都是流接口驱动
流接口驱动有一套标准的接口,这和本机驱动是不一样的。
n 对于I/O设备来说是非常适合的。
n 操作接口和文件系统API十分类似,比如ReadFile,IOControl等。
n 应用程序可以和流接口驱动进行交互,并且可以把流驱动当成文件来操作。
流驱动与驱动接口、提供设备的种类无关,因为这组接口有统一的接口规范。对于需要数据流的设备来说,这种驱动是十分适合的,如串口就是个典型的例子。可以把使用流驱动的设备近似地看作是文件,这样可以通过文件系统API来操作设备,如ReadFile,IOControl。由于采用了文件系统的API,使得驱动程序能通过文件系统进行访问,这点和独立驱动程序是不同的。
下图是流驱动程序在整个系统中的结构示意图。
上图显示了作为本机驱动而存在的流驱动程序,在启动时被设备管理器所加载。一般本机驱动是指custom接口的驱动程序,但是流驱动也可以成为本机驱动,例如串口。
流驱动通过文件系统API来和应用程序交互,同时又通过流接口接受设备管理器的管理。无论流驱动管理的是本机设备还是动态加载的设备,它们自身是在启动时被加载还是启动后由设备管理器动态加载,这和系统中其他模块的交互模型是一样的。
实现流驱动程序大致需要完成以下步骤。
(1)选择代表设备的文件名前缀;
(2)实现驱动的各个入口点;
(3)建立.DEF文件;
(4)在注册表中为驱动程序建立表项。
以下是创建流驱动的具体步骤。
(1)首先确定设备名的前缀。前缀非常重要,设备管理器在注册表中通过前缀来识别设备。同时,在流接口命名时,也将这个前缀作为入口点函数的前缀,如果设备前缀为XXX,那么流接口对应为XXX_Close,XXX_Init等。
(2)实现流接口的各个入口点。所谓入口点是指提供给设备管理器的标准文件I/O接口。
下表是对这些接口的介绍:
接 口 名 | 功能描述 |
XXX_Close | 关闭hOpenContext参数指定的设备上下文 |
XXX_Deinit | 通知设备管理器回收设备初始化时分配的资源 |
XXX_Init | 通知设备管理器为设备初始化时分配资源 |
XXX_Open | 打开设备,这个接口可以由应用程序直接调用createfile,然后通过文件系统映射为XXX_Open |
XXX_IOControl | I/O控制指令 |
XXX_PowerUp | 设备加电时,此接口会被自动调用,可以在这里分配资源等 |
XXX_PowerDown | 如果设备能由软件控制断电,则在设备断电前,设备管理器会调用这个接口做些安全性检查 |
XX_Read | 从打开的设备文件中读取数据 |
XXX_Seek | 文件定位,如果设备支持的话 |
XXX_Write | 写数据到设备文件 |
对于流驱动,×××_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是驱动程序被加载的顺序
以下详细说明下流接口函数的各入口
Streams入口:Open和Close
(1)XXX_Open入口
用于读/写打开一个设备文件。当应用程序调用CreateFile的时候,文件系统会自动调用本接口,打开一个已经存在的设备文件。当这个接口被调用的时候,设备驱动可以向设备管理器申请分配资源,并且为读/写文件做好准备。
下面是接口的原形:
DWORD XXX_Open( DWORD hDeviceContext, DWORD AccessCode, DWORD ShareMode );
参数解释:
hDeviceContext
指向XXX_Init返回的设备句柄(上下文)。
AccessCode
打开设备的权限描述符,这些权限包括读设备、写设备、读 /写等。
所谓的设备上下文指的是代表图形设备接口graphics device interface(GDI)的数据结构。它包含了在特定区域内设备显示图象的信息。设备上下文包含了图形对象(例如pen、brush、字体等),不断地修改和调用它们,从而达到显示不同图象的效果。
(2)XXX_Close入口
在关闭设备的时候被操作系统调用。对应于CloseHandle接口。
BOOL XXX_Close( DWORD hOpenContext );
参数解释:
hOpenContext
指向XXX_Open返回的已经打开的设备句柄(上下文)。
应用程序可以调用CloseHandle来关闭正在使用的流驱动程序,然后设备管理器会相应地调用本接口来关闭设备,当设备关闭后hOpenContext描述的设备句柄将不再有效。
Streams入口:Init and Deinit
(1)XXX_Init入口
XXX_Init要完成以下任务。
n 在驱动被系统加载时,本接口被调用;
n 初始化需要的资源在本接口处理中被分配;
n 创建内存映射。
下面是接口的原型:
DWORD XXX_Init( DWORD dwContext );
参数解释:
n dwContext
指向一个字符串,它描述了注册表中的一个流设备接口。
当调用设备ActivateDeviceEx函数后,设备管理器自动调用这个函数。当用户激活一个新的设备时,如插入USB设备后,当总线自检时,设备就会被激活,这个接口就会被调用,这个接口是不允许应用程序直接调用的。
当这个接口的处理结果返回时,设备管理器就在注册表中寻找驱动的Ioctl子键。如果这个子键存在,设备管理器将调用XXX_IOControl接口将dwCode参数传入驱动入口点。
(2)XXX_Deinit入口
在驱动被系统卸载的时候,本接口将被调用,它将释放所有占用的阻援,并且停止IST。
下面是接口的原型:
BOOL XXX_Deinit( DWORD hDeviceContext );
参数解释:
hDeviceContext
指向设备上下文的句柄。这个句柄应该是由XXX_Init返回的。
当程序调用DeactivateDevice时,设备管理器将自动调用本接口。流接口将释放全部它申请的资源,并且停止设备的运行。
Streams入口:Read,Write,Seek
(1)XXX_Read入口
当应用程序直接调用ReadFile函数时,设备管理器将调用这个接口。
下面是接口的原型:
DWORD XXX_Read( DWORD hOpenContext, LPVOID pBuffer, DWORD Count );
参数解释:
hOpenContext
指向XXX_Open接口返回的设备上下文。
pBuffer
指向缓冲区,这个缓冲区将用来存放从设备中读出的数据,以字节为单位。
Count
指定要从设备读取多少字节的数据存入pBuffer指向的缓冲区中。
这个从指定的设备中读取指定数量的字节数据,它对应的应用层API为ReadFile。ReadFile函数的参数hFile是指向设备的句柄,ReadFile函数的参数hFile将被填写到count参数中,ReadFile中的pSizeRead将存放实际读取数据的字节数。本接口的返回值是pSizeRead中填充的数值,若返回-1,代表发生了错误。
(2)XXX_Write入口
当应用程序调用WriteFile的时候,设备管理器将调用本接口。
下面是接口的原型:
DWORD XXX_Write( DWORD hOpenContext, LPCVOID pBuffer, DWORD Count );
参数解释:
hOpenContext
指向XXX_Open接口返回的设备上下文。
pBuffer
指向缓冲区,这个缓冲区将用来存放要向设备中写入的数据,以字节为单位。
Count
指定要从pBuffer指向的缓冲区向设备读取写入多少字节的数据。
(3)XXX_Seek入口
在定位I/O指针的时候被调用。
下面是接口的原型:
DWORD XXX_Seek( DWORD hOpenContext, long Amount, WORD Type );
参数解释:
hOpenContext
指向XXX_Open接口返回的设备上下文。
Amount
指定指针要移动多少距离,以字节为单位。正值代表向文件尾端移动,负值则相反。
Type
描述了起始点的位置。当应用程序调用了SetFilePointer函数后,设备管理器就会调用本接口。
如果设备是可以重复打开的,本接口用到的指针只是针对hOpenContext的。
Streams入口:PowerUp和PowerDown
(1)XXX_PowerDown入口
这个接口是在停止对设备供应电源时被调用。下面是接口的原型:
void XXX_PowerDown( DWORD hDeviceContext );
参数解释:
hDeviceContext
指向由XXX_Init返回的设备上下文。
这个函数应当执行停止设备供电的操作,此设备必须支持软关电的功能。在I/O control接口中,如果I/O命令字为IOCTL_POWER_XXX,那么就应该调用本接口。这个接口是对应与应用程序的,系统电源管理并不会用到这个接口。
设备管理器在将设备设置成节电模式之前将调用本接口,本接口将尽可能避免引起阻塞的操作,并尽快返回。
(2)XXX_PowerUp入口
恢复了设备的供电。
下面是接口的原型:
void XXX_PowerUp( DWORD hDeviceContext );
参数解释:
hDeviceContext
指向XXX_Open接口建立并且返回的设备上下文。
这个接口在需要恢复设备电源供应时被调用。在I/O control接口中,如果I/O命令字为IOCTL_POWER_XXX,那么就应该调用本接口。这个接口是对应与应用程序的,系统电源管理并不会用到这个接口。本接口应尽可能避免引起阻塞的操作,将尽快返回,并设置全局变量来表明电源已经被恢复,可以进行后续操作。
Streams入口:IOControl
XXX_IOControl入口
允许应用程序进行非文件的操作。
I/O控制字可以用来识别命令类别,普通的读写操作通常是不能完全满足程序要求的,I/O控制字是和设备相关的。
下面是接口的原型:
BOOL XXX_IOControl( DWORD hOpenContext, DWORD dwCode, PBYTE
pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD
pdwActualOut );
参数解释:
hOpenContext
指向XXX_Open接口建立并且返回的设备上下文。
dwCode
指定驱动程序要操作的I/O操作的标识码。这是由设备特定的,一般在头文件中有I/O操作的标识码的定义。
pBufIn
指向存放要向设备传输的数据的缓冲区。
dwLenIn
指定要从pBufIn指向的缓冲区向设备读取写入多少字节的数据。
pBufOut
指向缓冲区,这个缓冲区将用来存放从设备中读出的数据,以字节为单位。
dwLenOut
指定要从设备读取多少字节的数据存入pBuffer指向的缓冲区中。
pdwActualOut
DWORD型指针,指向的内容反映了从设备中读取的实际字节数。
这个接口主要是传递了包括读写在内的I/O控制命令给设备。和Windows桌面平台类似,应用程序可以通过直接调用DeviceIOControl函数使设备管理器激活本接口。dwCode参数指定了命令的类型,这些命令类型是由驱动程序指定的,并且通过头文件的形式提供给应用程序,在音频驱动中就有IOCTL_WAV_MESSAGE, IOCTL_DSDVR_MESSAGE, and IOCTL_MIX_MESSAGE.等。
如果注册表中有HKEY_LOCAL_MACHINE\Drivers\BuiltIn\YourDevice\Ioctl键,设备管理器在加载驱动的时候就会调用本接口,并且使用注册表中相应项的值作为dwCode的值,把NULL填写入pBufIn和pBufOut中。驱动程序可以在这个时候加载其他模块以及其他不适合在XXX_Init出现的功能,在设备交互的过程中,以上各个接口基本遵循如下的调用顺序:XXX_Init,XXX_Open,XXX_IOControl,XXX_Close。XXX_Open接口是要获得设备句柄所必须的操作,XXX_Close是释放资源所必须的操作。
上层调用I/O CONTROL函数后,I/O CONTROL便会使用消息或者结构体发送到各个子Driver上
下表中的结构体包含了器件的扩展信息
Programming element | Description |
MMDRV_MESSAGE_PARAMS | Passed to the WAV_IOControl function. |
WAVEOPENDESC | Contains information needed by waveform input and output drivers. |
WAVEOUTEXTCAPS | Contains extended device caps information. |
下表中列出了各个输入driver消息
Programming element | Description |
WIDM_ADDBUFFER | This message is used to request a waveform input driver to add an empty input buffer to its input buffer queue. |
WIDM_CLOSE | This message is used to request a waveform input driver to close a specified device instance previously opened with WIDM_OPEN. |
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;
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);
}