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的函数参数

view plain copy to clipboard print ?
  1. MMRESULT waveOutOpen(
  2. LPHWAVEOUT phwo,
  3. UINT  uDeviceID,
  4. LPWAVEFORMATEX pwfx,
  5. DWORD  dwCallback,
  6. DWORD  dwInstance,
  7. DWORD  fdwOpen
  8. );

MMRESULT waveOutOpen( LPHWAVEOUT phwo, UINT uDeviceID, LPWAVEFORMATEX pwfx, DWORD dwCallback, DWORD dwInstance, DWORD fdwOpen );

其中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)

view plain copy to clipboard print ?
  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. );

BOOL HandleWaveMessage(PMMDRV_MESSAGE_PARAMS pParams, DWORD *pdwResult) typedef struct { UINT uDeviceId; UINT uMsg; DWORD dwUser; DWORD dwParam1; DWORD dwParam2; } MMDRV_MESSAGE_PARAMS, *PMMDRV_MESSAGE_PARAMS; MMRESULT waveOutMessage( HWAVEOUT hwo, UINT uMsg, DWORD dw1, DWORD dw2 );

其 中参数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对象的。

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

class CWAVEOut { public: MMRESULT open(); private: DWORD m_dwCallback; DWORD m_dwInstance; UINT m_uDeviceID; DWORD m_fdwOpen; WAVEFORMATEX m_wfx; HANDLE m_hWave; LPVOID m_pStream; };

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

view plain copy to clipboard print ?
  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. *pwfx = pWaveOut;
  15. phwo = (LPHWAVEOUT)pWaveOut;
  16. return  pWaveOut->open();
  17. }

MMRESULT waveOutOpen( LPHWAVEOUT phwo, UINT uDeviceID, LPWAVEFORMATEX pwfx, DWORD dwCallback, DWORD dwInstance, DWORD fdwOpen ) { CWAVEOut pWaveOut = new CWAVEOut; pWaveOut->m_dwCallback = dwCallback; pWaveOut->m_fdwOpen = fdwOpen; pWaveOut->m_wfx = *pwfx; *pwfx = pWaveOut; phwo = (LPHWAVEOUT)pWaveOut; return pWaveOut->open(); }

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

view plain copy to clipboard print ?
  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. WAVEOPENDESC waveOpenDesc;
  7. waveOpenDesc.hWave = HWAVE(this );
  8. waveOpenDesc.lpFormat = &m_wfx;
  9. waveOpenDesc.dwInstance = m_dwInstance;
  10. waveOpenDesc.uMappedDeviceID = 0;
  11. waveOpenDesc.dwCallback = m_dwCallback;
  12. return  waveOutMessage((HWAVEOUT) this , WODM_OPEN, &waveOpenDesc, m_fdwOpen);
  13. }

MMRESULT CWAVEOut::open() { MMRESULT mmResult; m_hWave = CreateFile(L"WAV1:", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); WAVEOPENDESC waveOpenDesc; waveOpenDesc.hWave = HWAVE(this); waveOpenDesc.lpFormat = &m_wfx; waveOpenDesc.dwInstance = m_dwInstance; waveOpenDesc.uMappedDeviceID = 0; waveOpenDesc.dwCallback = m_dwCallback; return waveOutMessage((HWAVEOUT)this, WODM_OPEN, &waveOpenDesc, m_fdwOpen); }

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

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

view plain copy to clipboard print ?
  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. if (WODM_OPEN == uMsg)
  13. paramInput.dwUser = (DWORD )&pWaveOut->m_pStream;
  14. else
  15. paramInput.dwUser = (DWORD )pWaveOut->m_pStream;
  16. paramInput.uMsg = uMsg;
  17. MMRESULT    dwOutput = 0;
  18. if (!DeviceIoControl(pWaveOut->m_hWave, IOCTL_WAV_MESSAGE, ¶mInput,  sizeof (paramInput), &dwOutput,  sizeof (dwOutput), NULL, NULL))
  19. {
  20. return  MMSYSERR_ERROR;
  21. }
  22. return  dwOutput;
  23. }

MMRESULT waveOutMessage( HWAVEOUT hwo, UINT uMsg, DWORD dw1, DWORD dw2 ) { CWAVEOut * pWaveOut = (CWAVEOut *)hwo; MMDRV_MESSAGE_PARAMS paramInput; paramInput.dwParam1 = dw1; paramInput.dwParam2 = dw2; if(WODM_OPEN == uMsg) paramInput.dwUser = (DWORD)&pWaveOut->m_pStream; else paramInput.dwUser = (DWORD)pWaveOut->m_pStream; paramInput.uMsg = uMsg; MMRESULT dwOutput = 0; if(!DeviceIoControl(pWaveOut->m_hWave, IOCTL_WAV_MESSAGE, ¶mInput, sizeof(paramInput), &dwOutput, sizeof(dwOutput), NULL, NULL)) { return MMSYSERR_ERROR; } return dwOutput; }

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

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

MMRESULT waveOutReset( HWAVEOUT hwo ) { return waveOutMessage(hwo, WODM_RESET, 0, 0); } MMRESULT waveOutSetVolume( HWAVEOUT hwo, DWORD dwVolume ) { return waveOutMessage(hwo, WODM_SETVOLUME, dwVolume, 0); }

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

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

view plain copy to clipboard print ?
  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. HANDLE  hevDone = CreateEvent(NULL, FALSE, FALSE, NULL);
  11. if  (hevDone == NULL) {
  12. return  MMSYSERR_NOMEM;
  13. }
  14. mr = ReadWaveFile(pszFilename,&pwfx,&dwBufferSize,&pBufferBits);
  15. MRCHECK(mr, ReadWaveFile, ERROR_READ);
  16. // Note: Cast to UINT64 below is to avoid potential DWORD overflow for large (>~4MB) files.
  17. dwDuration = (DWORD )((( UINT64 )dwBufferSize) * 1000 / pwfx->nAvgBytesPerSec);
  18. HWAVEOUT hwo;
  19. mr = waveOutOpen(&hwo, WAVE_MAPPER, pwfx, (DWORD ) hevDone, NULL, CALLBACK_EVENT);
  20. MRCHECK(mr, waveOutOpen, ERROR_OPEN);
  21. WAVEHDR hdr;
  22. memset(&hdr, 0, sizeof (hdr));
  23. hdr.dwBufferLength = dwBufferSize;
  24. hdr.lpData = (char  *) pBufferBits;
  25. mr = waveOutPrepareHeader(hwo, &hdr, sizeof (hdr));
  26. MRCHECK(mr, waveOutPrepareHeader, ERROR_PLAY);
  27. mr = waveOutWrite(hwo, &hdr, sizeof (hdr));
  28. MRCHECK(mr, waveOutWrite, ERROR_PLAY);
  29. // wait for play + 1 second slop
  30. dwSlop = 1000;
  31. dwWait = WaitForSingleObject(hevDone, dwDuration + dwSlop);
  32. if  (dwWait != WAIT_OBJECT_0) {
  33. // not much to here, other than issue a warning
  34. RETAILMSG(1, (TEXT("Timeout waiting for playback to complete/r/n" )));
  35. }
  36. mr = waveOutUnprepareHeader(hwo, &hdr, sizeof (hdr));
  37. MRCHECK(mr, waveOutUnprepareHeader, ERROR_PLAY);
  38. ERROR_PLAY:
  39. mr = waveOutClose(hwo);
  40. MRCHECK(mr, waveOutClose, ERROR_OPEN);
  41. ERROR_OPEN:
  42. delete  [] pBufferBits;
  43. delete  [] pwfx;
  44. ERROR_READ:
  45. CloseHandle(hevDone);
  46. return  mr;
  47. }

MMRESULT PlayFile(LPCTSTR pszFilename) { MMRESULT mr; DWORD dwBufferSize; PBYTE pBufferBits = NULL; PWAVEFORMATEX pwfx = NULL; DWORD dwSlop; DWORD dwWait; DWORD dwDuration; HANDLE hevDone = CreateEvent(NULL, FALSE, FALSE, NULL); if (hevDone == NULL) { return MMSYSERR_NOMEM; } mr = ReadWaveFile(pszFilename,&pwfx,&dwBufferSize,&pBufferBits); MRCHECK(mr, ReadWaveFile, ERROR_READ); // Note: Cast to UINT64 below is to avoid potential DWORD overflow for large (>~4MB) files. dwDuration = (DWORD)(((UINT64)dwBufferSize) * 1000 / pwfx->nAvgBytesPerSec); HWAVEOUT hwo; mr = waveOutOpen(&hwo, WAVE_MAPPER, pwfx, (DWORD) hevDone, NULL, CALLBACK_EVENT); MRCHECK(mr, waveOutOpen, ERROR_OPEN); WAVEHDR hdr; memset(&hdr, 0, sizeof(hdr)); hdr.dwBufferLength = dwBufferSize; hdr.lpData = (char *) pBufferBits; mr = waveOutPrepareHeader(hwo, &hdr, sizeof(hdr)); MRCHECK(mr, waveOutPrepareHeader, ERROR_PLAY); mr = waveOutWrite(hwo, &hdr, sizeof(hdr)); MRCHECK(mr, waveOutWrite, ERROR_PLAY); // wait for play + 1 second slop dwSlop = 1000; dwWait = WaitForSingleObject(hevDone, dwDuration + dwSlop); if (dwWait != WAIT_OBJECT_0) { // not much to here, other than issue a warning RETAILMSG(1, (TEXT("Timeout waiting for playback to complete/r/n"))); } mr = waveOutUnprepareHeader(hwo, &hdr, sizeof(hdr)); MRCHECK(mr, waveOutUnprepareHeader, ERROR_PLAY); ERROR_PLAY: mr = waveOutClose(hwo); MRCHECK(mr, waveOutClose, ERROR_OPEN); ERROR_OPEN: delete [] pBufferBits; delete [] pwfx; ERROR_READ: CloseHandle(hevDone); return mr; }

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

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

Waveform Audio 驱动(Wavedev2)之:WAV API模拟相关推荐

  1. Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析

    Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析 上篇文章中,我们模拟了WAV API.现在进入我们正在要解析的Wave 驱动的架构.我们了解一个驱动的时候,先不去看具体跟硬 ...

  2. audio驱动之codec和codec_dai

    平台 os版本 内核 MT6765 Android 9.0 kernel-4.9 在嵌入式设备中,codec的作用可以简单的分为4种: 对PCM等信号进行D/A转换,把数字的隐僻信号转换为模拟信号. ...

  3. Wave Driver介绍-5(Waveform Audio Driver Test测试Case描述)

    http://blog.csdn.net/daydayupfromnowon/article/details/6003500 因为要对Audio Driver做CETK测试,所以今天了解了一下CETK ...

  4. HTML5 Audio标签方法和函数API介绍

     问说网 > 文章教程 > 网页制作 > HTML5 Audio标签方法和函数API介绍 Audio APIHTML5HTML5 Audio预加载 HTML5 Audio标签方法和函 ...

  5. MTK 驱动(60)---Audio驱动开发之音频链路

    Audio驱动开发之音频链路 [元器件说明] 本文中使用的 Codec 芯片为 ALC5677. [音频链路模型] 一个常见的音频链路如 图1 所示,包含 音频输入.ADC.DSP.DAC.音频输出 ...

  6. win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方法

    win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方案 前几天装了一下windows7体验一下,结果声卡驱动安装有问题,这电脑没声音我可没法活啊. ...

  7. audio驱动之cpu_dai

    平台 os版本 内核 MT6765 Android 9.0 kernel-4.9 audio驱动相关结构体 注释 snd_soc_component 当底层驱动注册platform.codec+cod ...

  8. Python发送微信消息(文字、图片、文件)给指定好友和微信群(调用Win32 API模拟人的手动操作来发送消息)

    本示例是调用Windows API模拟发送,用Python调用win32api这个库来调用Windows API模拟人的手动操作来发送消息. 在使用前,请将你微信的窗口设置为在最前面,这样就便于程序找 ...

  9. 在MTK平台配置一个支持smartPA的audio驱动

    文章目录 smartPA概述 smartPA AW87319概述 smartPA AW87319功能特性 在kernel中添加对smartPA的支持 1. 在配置文件中添加对smartPA的支持 2. ...

最新文章

  1. 企业分布式微服务云SpringCloud SpringBoot mybatis (十一)docker部署spring cloud项目
  2. 一周一论文(翻译)——[SIGMOD 2015] Congestion Control for Large-Scale RDMA
  3. 阿里工作流引擎_免费开源,一款快速开发模块化脚手架,含工作流引擎
  4. 算法与数据结构(part2)--Python内置类型性能分析
  5. 【Hihocoder - offer编程练习赛39 - D】前缀后缀查询(后缀字典树,哈希,思维)
  6. 网络数据采集技术—Java网络爬虫入门与实战 书稿纠错
  7. 在VS2005中设计WinForms应用程序已完成设计的界面突然丢失的解决
  8. 【虚拟机】VMware启动时报错:该虚拟机似乎正在使用中....请按“获取所有权(T)”按钮获取它的所有权
  9. Elasticsearch学习--elasticsearch介绍与安装
  10. 火车头伪原创接口【基于ai伪原创】
  11. Hibernate事务与并发问题处理(乐观锁与悲观锁)【转】
  12. python实现whois查询_python实现whois查询功能的方法
  13. npm切换到百度镜像源
  14. 无频闪护眼灯哪个好?盘点四款无频闪的护眼台灯
  15. bboss es对比直接使用es客户端的优势
  16. VM Tools 安装
  17. Halcon 简单入门3D点云计算高度
  18. 艺术学毕业论文题目【最新】
  19. 你画我猜 计算机题目,你画我猜:你知道这些题目的答案是什么吗?
  20. mysql增加ip访问

热门文章

  1. 在你的网站集成Wiki系统 WikiPlex
  2. 根据定制的 XML 文件进行随机抽取节
  3. MYSQL 去除重复 记录
  4. 截取最后一个下划线前面的字符
  5. 在js在页面中添加百度统计代码
  6. 由手机上网带来病毒引发的三大疑问?
  7. open source protocols
  8. 我的世界java怎么玩起床战争_我的世界怎么玩起床战争_我的世界起床战争怎么玩_52pk单机游戏...
  9. java date linux,Java中Date,SimpleDateFormat
  10. K for the Price of One(EASY HARD)