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

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.   
  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)

 

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. );   

其 中参数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.   
  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来打开音频设备。代码如下:

 

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.       
  15.     *pwfx = pWaveOut;  
  16.       
  17.     phwo = (LPHWAVEOUT)pWaveOut;  
  18.     return  pWaveOut->open();  
  19. }  

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.       
  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);

 

 

 

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.       
  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 就可以了。

view plain copy to clipboard print ?
  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声音的函数,从代码中去解析

 

 

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.   
  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。

 

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/243186.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移位运算与乘法

移位运算与乘法 题目描述 已知d为一个8位数,请在每个时钟周期分别输出该数乘1/3/7/8,并输出一个信号通知此时刻输入的d有效(d给出的信号的上升沿表示写入有效) 信号示意图 波形示意图 timescale 1ns/1ns module multi_sel( input [7:0]d …

Snapshot Instance 操作详解 - 每天5分钟玩转 OpenStack(36)

本节我们通过日志详细讨论 instance 的 snapshot 操作。 有时候操作系统损坏得很严重,通过 Rescue 操作无法修复,那么我们就得考虑通过备份恢复了。当然前提是我们之前对instance做过备份。 Nova 备份的操作叫 Snapshot,其工作原理是对 insta…

Rebuild Instance 操作详解 - 每天5分钟玩转 OpenStack(37)

上一节我们讨论了 snapshot,snapshot 的一个重要作用是对 instance 做备份。 如果 instance 损坏了,可以通过 snapshot 恢复,这个恢复的操作就是 Rebuild。 Rebuild 会用 snapshot 替换 instance 当前的镜像文件,同时保持 instanc…

突发传输模式

突发传输模式 突发传输(Burst transmission),一般也称为数据突发,其在通信领域中一般指在短时间内进行相对高带宽的数据传输。 突发传输一般表示的是两个设备之间进行数据传送的一种模式,也可将其称为突发模式下的数据传输。而突发(Burst)是指在同一行中相邻的存储单元…

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

Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析 上篇文章中,我们模拟了WAV API。现在进入我们正在要解析的Wave 驱动的架构。我们了解一个驱动的时候,先不去看具体跟硬件操作相关的东西,而是从流程入手,把整个流程搞清…

Unshelve Instance 操作详解 - 每天5分钟玩转 OpenStack(39)

上一节我们 shelve instance 到 Glance,本节讨论如何通过 unshelve 操作恢复该 instance。 因为 Glance 中保存了 instance 的 image,unshelve 的过程其实就是通过该 image launch 一个新的 instance,nova-scheduler 也会调度合适的计算节点来…

位拆分与运算

位拆分与运算 题目描述 现在输入了一个压缩的16位数据,实际上包含了四个数据[3:0][7:4][11:8][15:12] 按照sel选择输出四个数据的相加结果,并输出valid_out信号(在不输出时候拉低) 0:输出[3:0][7:4] 1:输出…

得意而忘乎形:谈葛水平的水墨画

不知道葛水平早年学过画没有,只知道她搞过戏曲,好像也有过舞台表演的经验。现在主要写小说,成绩斐然,创作之余画水墨。贾平凹看了她的画后说:“每个人都有绘画潜质,只是大与小和开发与不开发。”这话有理。…

Eclipse安装Perl插件

http://www.cnblogs.com/emanlee/archive/2012/08/11/2633701.html Eclipse安装Perl EPIC插件,在Eclipse中开发Perl项目 前提: 已经安装了 Eclipse,Java,Perl(例如 ActivePerl)。 步骤: 启动Eclipse,Eclipse中&#x…

风鬣霜蹄马王出

今年夏天,在内蒙古莱德马业繁殖基地的母马放养草场,我看到一群特殊的马点缀在绿色的草原之中。它们的颜色令我大开眼界:黑、白、枣红、黄骠、铁青……远远望去,五颜六色,神态各异。莱德马业的负责人朱方清告诉我,这里除…

多功能数据处理器

多功能数据处理器 题目描述 根据指示信号select,对输入信号a,b实现不同的运算。输入信号a,b为8bit有符号数,当select信号为0,输出a;当select信号为1,输出b;当select信号为2,输出ab;当select信号为3&#x…

Live Migrate 操作 - 每天5分钟玩转 OpenStack(42)

Migrate 操作会先将 instance 停掉,也就是所谓的“冷迁移”。而 Live Migrate 是“热迁移”,也叫“在线迁移”,instance不会停机。 Live Migrate 分两种: 源和目标节点没有共享存储,instance 在迁移的时候需要将其镜像…

计算节点宕机了怎么办?- 每天5分钟玩转 OpenStack(43)

Rebuild 可以恢复损坏的 instance。 那如果是宿主机坏了怎么办呢? 比如硬件故障或者断电造成整台计算节点无法工作,该节点上运行的 instance 如何恢复呢? 用 Shelve 或者 Migrate 可不可以? 很不幸,这两个操作都要求 i…

eclipse 全屏插件

eclipse-fullscreen 插件下载链接: http://code.google.com/p/eclipse-fullscreen/, 安装方式:将下载下来的压缩文件解压,将里边的jar包放到eclipse目录下的plugin文件夹中,重启eclipse即可。 在Fedora & Windows …

使用子模块实现三输入数的大小比较

使用子模块实现三输入数的大小比较 题目描述 在数字芯片设计中,通常把完成特定功能且相对独立的代码编写成子模块。在需要的时候再在主模块中例化使用,以提高代码的可复用性和设计的层次性,方便后续的修改。 请编写一个子模块,将…

寻求神谕的词语:谈海日寒诗集《空山集》

诗人杨炼曾用两个“他者”概括了当代中国诗歌所面对的两个检验体系:“背后是中文古典诗歌杰作”,“面前是古今世界文学精品”1。他提出:“全球化语境中,我们能否找到——创造一种更深也更新的标准来判断作品?去建立那个…

批量给Linux服务器推送文件、执行指令的工具推荐 - wgcloud-bach-agent

wgcloud-bach-agent是wgcloud官方开发的一个工具,具有批量给Linux主机上传文件,执行指令的功能 当我们有一个文件,需要上传到很多主机,或需要在很多主机执行同一条指令的时候,这个工具就非常实用了,可以极…

Neutron Router 工作原理 - 每天5分钟玩转 OpenStack(142)

上一节我们创建了 router 连通了 vlan100 和 vlan101, 今天分析router是如何工作的。 首先查看控制节点的网络结构发生了什么变化: br-int 上多了两个 port: 1. qr-d295b258-45,从命名上可以推断该 interface 对应 router_100_101 的 interf…

访问 Neutron 外部网络 - 每天5分钟玩转 OpenStack(143)

前面我们学习了位于不同 Neutron subnet 的 instance 可以通过 router 通信,今天开始讨论 instance 如何访问外部网络。 这里的外部网络是指的租户网络以外的网络。租户网络是由 Neutron 创建和维护的网络。 外部网络不由 Neutron 创建。如果是私有云,外…

使用函数实现数据大小端转换

使用函数实现数据大小端转换 题目描述 在数字芯片设计中,经常把实现特定功能的模块编写成函数,在需要的时候再在主模块中调用,以提高代码的复用性和提高设计的层次,分别后续的修改。 请用函数实现一个4bit数据大小端转换的功能。…