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的函数参数
- MMRESULT waveOutOpen(
- LPHWAVEOUT phwo,
- UINT uDeviceID,
- LPWAVEFORMATEX pwfx,
- DWORD dwCallback,
- DWORD dwInstance,
- DWORD fdwOpen
- );
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)
- 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
- );
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对象的。
- 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;
- };
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来打开音频设备。代码如下:
- 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();
- }
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。
- 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);
- }
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);
- 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;
- }
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 就可以了。
- MMRESULT waveOutReset(
- HWAVEOUT hwo
- )
- {
- return waveOutMessage(hwo, WODM_RESET, 0, 0);
- }
- MMRESULT waveOutSetVolume(
- HWAVEOUT hwo,
- DWORD dwVolume
- )
- {
- return waveOutMessage(hwo, WODM_SETVOLUME, dwVolume, 0);
- }
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声音的函数,从代码中去解析
- 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;
- }
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模拟相关推荐
- Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析
Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析 上篇文章中,我们模拟了WAV API.现在进入我们正在要解析的Wave 驱动的架构.我们了解一个驱动的时候,先不去看具体跟硬 ...
- audio驱动之codec和codec_dai
平台 os版本 内核 MT6765 Android 9.0 kernel-4.9 在嵌入式设备中,codec的作用可以简单的分为4种: 对PCM等信号进行D/A转换,把数字的隐僻信号转换为模拟信号. ...
- Wave Driver介绍-5(Waveform Audio Driver Test测试Case描述)
http://blog.csdn.net/daydayupfromnowon/article/details/6003500 因为要对Audio Driver做CETK测试,所以今天了解了一下CETK ...
- HTML5 Audio标签方法和函数API介绍
问说网 > 文章教程 > 网页制作 > HTML5 Audio标签方法和函数API介绍 Audio APIHTML5HTML5 Audio预加载 HTML5 Audio标签方法和函 ...
- MTK 驱动(60)---Audio驱动开发之音频链路
Audio驱动开发之音频链路 [元器件说明] 本文中使用的 Codec 芯片为 ALC5677. [音频链路模型] 一个常见的音频链路如 图1 所示,包含 音频输入.ADC.DSP.DAC.音频输出 ...
- win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方法
win7(windows7旗舰版)声卡High Definition Audio驱动 (安装失败)解决方案 前几天装了一下windows7体验一下,结果声卡驱动安装有问题,这电脑没声音我可没法活啊. ...
- audio驱动之cpu_dai
平台 os版本 内核 MT6765 Android 9.0 kernel-4.9 audio驱动相关结构体 注释 snd_soc_component 当底层驱动注册platform.codec+cod ...
- Python发送微信消息(文字、图片、文件)给指定好友和微信群(调用Win32 API模拟人的手动操作来发送消息)
本示例是调用Windows API模拟发送,用Python调用win32api这个库来调用Windows API模拟人的手动操作来发送消息. 在使用前,请将你微信的窗口设置为在最前面,这样就便于程序找 ...
- 在MTK平台配置一个支持smartPA的audio驱动
文章目录 smartPA概述 smartPA AW87319概述 smartPA AW87319功能特性 在kernel中添加对smartPA的支持 1. 在配置文件中添加对smartPA的支持 2. ...
最新文章
- 企业分布式微服务云SpringCloud SpringBoot mybatis (十一)docker部署spring cloud项目
- 一周一论文(翻译)——[SIGMOD 2015] Congestion Control for Large-Scale RDMA
- 阿里工作流引擎_免费开源,一款快速开发模块化脚手架,含工作流引擎
- 算法与数据结构(part2)--Python内置类型性能分析
- 【Hihocoder - offer编程练习赛39 - D】前缀后缀查询(后缀字典树,哈希,思维)
- 网络数据采集技术—Java网络爬虫入门与实战 书稿纠错
- 在VS2005中设计WinForms应用程序已完成设计的界面突然丢失的解决
- 【虚拟机】VMware启动时报错:该虚拟机似乎正在使用中....请按“获取所有权(T)”按钮获取它的所有权
- Elasticsearch学习--elasticsearch介绍与安装
- 火车头伪原创接口【基于ai伪原创】
- Hibernate事务与并发问题处理(乐观锁与悲观锁)【转】
- python实现whois查询_python实现whois查询功能的方法
- npm切换到百度镜像源
- 无频闪护眼灯哪个好?盘点四款无频闪的护眼台灯
- bboss es对比直接使用es客户端的优势
- VM Tools 安装
- Halcon 简单入门3D点云计算高度
- 艺术学毕业论文题目【最新】
- 你画我猜 计算机题目,你画我猜:你知道这些题目的答案是什么吗?
- mysql增加ip访问
热门文章
- 在你的网站集成Wiki系统 WikiPlex
- 根据定制的 XML 文件进行随机抽取节
- MYSQL 去除重复 记录
- 截取最后一个下划线前面的字符
- 在js在页面中添加百度统计代码
- 由手机上网带来病毒引发的三大疑问?
- open source protocols
- 我的世界java怎么玩起床战争_我的世界怎么玩起床战争_我的世界起床战争怎么玩_52pk单机游戏...
- java date linux,Java中Date,SimpleDateFormat
- K for the Price of One(EASY HARD)