现在的位置: 首页 > 综合 > 正文

Waveform Audio 驱动(Wavedev2)之:WAV API模拟

2012年08月09日 ⁄ 综合 ⁄ 共 11629字 ⁄ 字号 评论关闭

Waveform Audio 
驱动(Wavedev2)之:WAV API模拟

 

Waveform 驱动对Windows Mobile来说是一个非常重要的驱动,控制着所有有关声音的操作,包括喇叭、耳机、麦克、听筒等。


    要
想对驱动的整个架构和流程都非常的了解,我们必须从上层来入手,需要知道上层的API是如何调用到驱动的,其数据结构是如何封装的。由于微软不提供中间层
的代码,只能只是自己去猜测。这篇文章就是去模仿WAV API的实现方法的。顺便提及下,之前几个开发人员还讨论过微软的半开放模式和Android的
完全开源模式哪个更好。先做个总结。

完全开源优点:

1. 

加新功能容易:比如做Android双卡双待就比Windows Mobile容易的多,之前做Windows Mobile双卡的项目时,那真是非常的
痛苦,微软没有接口,只能自己想尽一起方法往微软原有的程序中去插入新的功能,想COM接口,Dll注入,窗口Hook等等,能用的变态方法都用上了。花
的时间的很大部分都是在寻找插入功能的方法上,而不是实现另一张卡的功能上。而Android就十分简单了,直接在原有的代码上增加代码就行。

2. 
开发人员很容易了解整个架构和流程

微软的半开放模式优点:

1. 

维护: 由于微软的中间层都是以dll形式封装好的,开发人员不能去修改,只能按照微软的接口去做,当微软从Windows Mobile 5.0升级到
Windows Mobile 6.0的时候,BSP不需要做任何修改就可以在新的系统上用,软件也是如此。而Android的完全开源模式,开发人员会
去修改中间层,Android的版本号从1.5,1.6,2.0再到2.1,不断的进行升级,其中间层也在改变中,添加了某些功能,优化了某些部分。像我
们公司做Android的从1.5升级到1.6就花了很长的时间。不仅驱动要修改,应用也都需要做修改。

先不谈这个,回到正题。

    微软上层的WAV API分为waveOut和waveIn两套,表一中,我只列了部分的wave out API。由于wave In相对于wave Out比较简单,wave In就不做讲解了。

 

waveOutGetNumDevs

 

Retrieves the number of waveform output devices present in the system.

waveOutGetPitch

 

Queries the current pitch setting of a waveform output device.

waveOutGetPlaybackRate

 

Queries the current playback rate setting of a waveform output device.

waveOutGetPosition

 

Retrieves the current playback position of the specified waveform output device.

waveOutGetProperty

 

Queries the value of a specific property in a property set for waveform audio output.

waveOutGetVolume

 

Queries the current volume setting of a waveform output device.

waveOutMessage

 

Sends messages to the waveform output device drivers.

waveOutOpen

 

Opens a specified waveform output device for playback.

表一:WaveOut部分函数

W
ave Out API是如何调用的驱动部分的呢?现在就来一步步的模拟来实现wave Out API。先看下waveOutOpen的函数参数

 

  1. MMRESULT waveOutOpen(  
  2.   LPHWAVEOUT phwo,  
  3.   UINT
     uDeviceID,  
  4.   LPWAVEFORMATEX pwfx,  
  5.   DWORD
     dwCallback,  
  6.   DWORD
     dwInstance,  
  7.   DWORD
     fdwOpen  
  8.   
  9. );  

其中phwo是我们要返回的WAVEOUT对象的句柄,
uDeviceID
指设置的ID号,一般情况下设置为0就可以,pwfx是声音格式的描述,dwCallback的通知,可以是回调函数,也可以是事件或者窗体消息,主要通过fdwOpen来指定其类型。具体看waveOutOpen的SDK帮助文档。

在WaveApi中的工作就是把waveOutOpen中的参数封装起来,然后发到Wave驱动中想要的结构,下面是waveOutOpen的调用流程。

1. 
W
ave Api中封装结构

2. 
调用Wavedev2的 
WAV_IOControl
函数,调用
IOCTL_WAV_MESSAGE
分支。

3. 
调用
HandleWaveMessage

WODM_OPEN
分支

HandleWaveMessage
需要传入两个参数,其中一个是
PMMDRV_MESSAGE_PARAMS
,另一个是函数执行的结果pdwResult,见
HandleWaveMessage
原型和
MMDRV_MESSAGE_PARAMS
结构体定义。

BOOL HandleWaveMessage(PMMDRV_MESSAGE_PARAMS pParams, DWORD *pdwResult)

 

  1. BOOL
     HandleWaveMessage(PMMDRV_MESSAGE_PARAMS pParams, 
    DWORD
     *pdwResult)  
  2. typedef
     
    struct
     {  
  3.     UINT
     uDeviceId;  
  4.     UINT
     uMsg;  
  5.     DWORD
     dwUser;  
  6.     DWORD
     dwParam1;  
  7.     DWORD
     dwParam2;  
  8. } MMDRV_MESSAGE_PARAMS, *PMMDRV_MESSAGE_PARAMS;  
  9. MMRESULT waveOutMessage(  
  10.   HWAVEOUT hwo,   
  11.   UINT
     uMsg,   
  12.   DWORD
     dw1,   
  13.   DWORD
     dw2   
  14. );   


中参数dwUser指向Wavedev2驱动的StreamContext对象指针,如果调用的是waveOutOpen,则dwUser做出传出参数,
来保存StreamContext对象,否则就是作为传入参数。waveOutMessage的uMsg会传入驱动变成

MDRV_MESSAGE_PARAMS
中的uMsg, 同样的dw1变dwParam1,dw2变dwParam2,所以的上层调用都是调用
waveOutMessage
这个函数实现的。

 

好了,现在我们来开始显示吧。
HWAVEOUT
要么直接指向对象,要么是对象的在数组中的索引。我们只是模拟,所以把
HWAVEOUT
直接指向对象。

先定义一个
CWAVEOut
对象,来保存必要的数据,其他几个参数就不做解释了,我们看m_hWave和m_pStream,m_hWave是来保存打开Wave驱动CreateFile返回的句柄,而m_pStream是保存创建的StreamContext对象的。

 

  1. class
     CWAVEOut  
  2. {  
  3. public
    :  
  4.     MMRESULT open();  
  5.   
  6. private
    :  
  7.     DWORD
     m_dwCallback;  
  8.     DWORD
     m_dwInstance;  
  9.     UINT
     m_uDeviceID;  
  10.     DWORD
     m_fdwOpen;  
  11.     WAVEFORMATEX m_wfx;  
  12.     HANDLE
     m_hWave;  
  13.     LPVOID
      m_pStream;  
  14. };  

好,现在我们来模拟
waveOutOpen
的实现。在waveOutOpen中,只是新建一个CWAVEOut对象,然后把外部传入的数据保存到这个对象中,最后调用open来打开音频设备。代码如下:

 

  1. MMRESULT waveOutOpen(  
  2.                      LPHWAVEOUT phwo,  
  3.                      UINT
     uDeviceID,  
  4.                      LPWAVEFORMATEX pwfx,  
  5.                      DWORD
     dwCallback,  
  6.                      DWORD
     dwInstance,  
  7.                      DWORD
     fdwOpen  
  8.                      )  
  9. {  
  10.     CWAVEOut pWaveOut = new
     CWAVEOut;  
  11.     pWaveOut->m_dwCallback = dwCallback;  
  12.     pWaveOut->m_fdwOpen = fdwOpen;  
  13.     pWaveOut->m_wfx = *pwfx;  
  14.       
  15.     *pwfx = pWaveOut;  
  16.       
  17.     phwo = (LPHWAVEOUT)pWaveOut;  
  18.     return
     pWaveOut->open();  
  19. }  

O
pen函数封装
WAVEOPENDESC
作为waveOutMessage的第一个传入参数,第二个参数是m_fdwOpen。

 

  1. MMRESULT CWAVEOut::open()  
  2. {  
  3.     MMRESULT mmResult;  
  4.     m_hWave = CreateFile(L"WAV1:"
    , GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,   
  5.         NULL, OPEN_EXISTING, 0, NULL);  
  6.       
  7.     WAVEOPENDESC waveOpenDesc;  
  8.     waveOpenDesc.hWave = HWAVE(this
    );  
  9.     waveOpenDesc.lpFormat = &m_wfx;  
  10.     waveOpenDesc.dwInstance = m_dwInstance;  
  11.     waveOpenDesc.uMappedDeviceID = 0;  
  12.     waveOpenDesc.dwCallback = m_dwCallback;  
  13.   
  14.     return
     waveOutMessage((HWAVEOUT)
    this
    , WODM_OPEN, &waveOpenDesc, m_fdwOpen);  
  15. }  

waveOutMessage的工作就是只要把uMsg,dw1,dw2封装到
MMDRV_MESSAGE_PARAMS
结构体,然后调用
DeviceIoControl
调用驱动的IO Control。这里有一点需要注意,如果uMsg是WODM_OPEN,也就是打开音频流的操作的时候,把
&
pWaveOut
->
m_pStream
作为参数传入,因为在底层通过调用
OpenStream
,传入指针的指针,来保存对象的。

pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)dwUser);

 

 

 

  1. MMRESULT waveOutMessage(  
  2.                         HWAVEOUT hwo,   
  3.                         UINT
     uMsg,   
  4.                         DWORD
     dw1,   
  5.                         DWORD
     dw2   
  6.                         )  
  7. {  
  8.     CWAVEOut * pWaveOut = (CWAVEOut *)hwo;  
  9.     MMDRV_MESSAGE_PARAMS paramInput;  
  10.     paramInput.dwParam1 = dw1;  
  11.     paramInput.dwParam2 = dw2;  
  12.       
  13.     if
    (WODM_OPEN == uMsg)  
  14.         paramInput.dwUser = (DWORD
    )&pWaveOut->m_pStream;  
  15.     else
      
  16.         paramInput.dwUser = (DWORD
    )pWaveOut->m_pStream;  
  17.       
  18.     paramInput.uMsg = uMsg;  
  19.   
  20.     MMRESULT    dwOutput = 0;  
  21.       
  22.     if
    (!DeviceIoControl(pWaveOut->m_hWave, IOCTL_WAV_MESSAGE, ¶mInput, 
    sizeof
    (paramInput), &dwOutput, 
    sizeof
    (dwOutput), NULL, NULL))  
  23.     {  
  24.         return
     MMSYSERR_ERROR;  
  25.     }  
  26.       
  27.     return
     dwOutput;  
  28. }  

我们现在模拟实现了waveOutMessage,那么其他一些函数的实现要比waveOutOpen更加的简单。如WaveOutReset和
waveOutSetVolume
,只要调用下
waveOutMessage
就可以了。

  1. MMRESULT waveOutReset(  
  2.                       HWAVEOUT hwo   
  3.                       )  
  4. {  
  5.     return
     waveOutMessage(hwo, WODM_RESET, 0, 0);  
  6. }  
  7.   
  8. MMRESULT waveOutSetVolume(  
  9.                           HWAVEOUT hwo,   
  10.                           DWORD
     dwVolume   
  11.                           )  
  12. {  
  13.     return
     waveOutMessage(hwo, WODM_SETVOLUME, dwVolume, 0);  
  14. }  

对于上层来说,只是简单的进行了下封装。当然我的封装里面还没有考虑到具体的一些东西,如callback函数是怎么返回的,如函数调用是hwo为空,是怎么样的,也没有对错误进行处理。

下面是播放一个wave声音的函数,从代码中去解析

 

 

  1. MMRESULT  
  2. PlayFile(LPCTSTR
     pszFilename)  
  3. { MMRESULT mr;  
  4.   DWORD
     dwBufferSize;  
  5.   PBYTE
     pBufferBits = NULL;  
  6.   PWAVEFORMATEX pwfx = NULL;  
  7.   DWORD
     dwSlop;  
  8.   DWORD
     dwWait;  
  9.   DWORD
     dwDuration;  
  10.   
  11.     HANDLE
     hevDone = CreateEvent(NULL, FALSE, FALSE, NULL);  
  12.     if
     (hevDone == NULL) {  
  13.         return
     MMSYSERR_NOMEM;  
  14.     }  
  15.   
  16.     mr = ReadWaveFile(pszFilename,&pwfx,&dwBufferSize,&pBufferBits);  
  17.     MRCHECK(mr, ReadWaveFile, ERROR_READ);  
  18.   
  19.     // Note: Cast to UINT64 below is to avoid potential DWORD overflow for large (>~4MB) files.
      
  20.     dwDuration = (DWORD
    )(((
    UINT64
    )dwBufferSize) * 1000 / pwfx->nAvgBytesPerSec);  
  21.   
  22.     HWAVEOUT hwo;  
  23.     mr = waveOutOpen(&hwo, WAVE_MAPPER, pwfx, (DWORD
    ) hevDone, NULL, CALLBACK_EVENT);  
  24.     MRCHECK(mr, waveOutOpen, ERROR_OPEN);  
  25.   
  26.     WAVEHDR hdr;  
  27.     memset(&hdr, 0, sizeof
    (hdr));  
  28.     hdr.dwBufferLength = dwBufferSize;  
  29.     hdr.lpData = (char
     *) pBufferBits;  
  30.   
  31.     mr = waveOutPrepareHeader(hwo, &hdr, sizeof
    (hdr));  
  32.     MRCHECK(mr, waveOutPrepareHeader, ERROR_PLAY);  
  33.   
  34.     mr = waveOutWrite(hwo, &hdr, sizeof
    (hdr));  
  35.     MRCHECK(mr, waveOutWrite, ERROR_PLAY);  
  36.   
  37.     // wait for play + 1 second slop
      
  38.     dwSlop = 1000;  
  39.     dwWait = WaitForSingleObject(hevDone, dwDuration + dwSlop);  
  40.     if
     (dwWait != WAIT_OBJECT_0) {  
  41.         // not much to here, other than issue a warning
      
  42.         RETAILMSG(1, (TEXT("Timeout waiting for playback to complete/r/n"
    )));  
  43.     }  
  44.   
  45.     mr = waveOutUnprepareHeader(hwo, &hdr, sizeof
    (hdr));  
  46.     MRCHECK(mr, waveOutUnprepareHeader, ERROR_PLAY);  
  47.   
  48. ERROR_PLAY:  
  49.     mr = waveOutClose(hwo);  
  50.     MRCHECK(mr, waveOutClose, ERROR_OPEN);  
  51.   
  52. ERROR_OPEN:  
  53.     delete
     [] pBufferBits;  
  54.     delete
     [] pwfx;  
  55.   
  56. ERROR_READ:  
  57.     CloseHandle(hevDone);  
  58.     return
     mr;  
  59. }  

先调用waveOutOpen初始化音频流。在调用waveOutPrepareHeader准备好数据头,告诉驱动要播放多大的数据,在驱动中
waveOutPrepareHeader
 
调用
WODM_PREPARE
分支,一般情况下驱动没有去实现
WODM_PREPARE
,直接返回
MMSYSERR_NOTSUPPORTED
。准备好Header后,调用waveOutWrite写出buffer。

 

好了,就写到这里,如有错误之处,请更正。

【上篇】
【下篇】

抱歉!评论已关闭.