Waveform?Audio? 驅動(Wavedev2)之:WAV?API模擬
?
Waveform?驅動對Windows?Mobile來說是一個非常重要的驅動,控制著所有有關聲音的操作,包括喇叭、耳機、麥克、聽筒等。
????要 想對驅動的整個架構和流程都非常的了解,我們必須從上層來入手,需要知道上層的API是如何調用到驅動的,其數(shù)據(jù)結構是如何封裝的。由于微軟不提供中間層 的代碼,只能只是自己去猜測。這篇文章就是去模仿WAV?API的實現(xiàn)方法的。順便提及下,之前幾個開發(fā)人員還討論過微軟的半開放模式和Android的 完全開源模式哪個更好。先做個總結。
完全開源優(yōu)點:
1.? 添 加新功能容易:比如做Android雙卡雙待就比Windows?Mobile容易的多,之前做Windows?Mobile雙卡的項目時,那真是非常的 痛苦,微軟沒有接口,只能自己想盡一起方法往微軟原有的程序中去插入新的功能,想COM接口,Dll注入,窗口Hook等等,能用的變態(tài)方法都用上了。花 的時間的很大部分都是在尋找插入功能的方法上,而不是實現(xiàn)另一張卡的功能上。而Android就十分簡單了,直接在原有的代碼上增加代碼就行。
2.? 開發(fā)人員很容易了解整個架構和流程
微軟的半開放模式優(yōu)點:
1.? 易 維護:?由于微軟的中間層都是以dll形式封裝好的,開發(fā)人員不能去修改,只能按照微軟的接口去做,當微軟從Windows?Mobile?5.0升級到 Windows?Mobile?6.0的時候,BSP不需要做任何修改就可以在新的系統(tǒng)上用,軟件也是如此。而Android的完全開源模式,開發(fā)人員會 去修改中間層,Android的版本號從1.5,1.6,2.0再到2.1,不斷的進行升級,其中間層也在改變中,添加了某些功能,優(yōu)化了某些部分。像我 們公司做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部分函數(shù)
W ave?Out?API是如何調用的驅動部分的呢?現(xiàn)在就來一步步的模擬來實現(xiàn)wave?Out?API。先看下waveOutOpen的函數(shù)參數(shù)
?
view plain copy to clipboard print ?
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的通知,可以是回調函數(shù),也可以是事件或者窗體消息,主要通過fdwOpen來指定其類型。具體看waveOutOpen的SDK幫助文檔。
在WaveApi中的工作就是把waveOutOpen中的參數(shù)封裝起來,然后發(fā)到Wave驅動中想要的結構,下面是waveOutOpen的調用流程。
1.? W ave?Api中封裝結構
2.? 調用Wavedev2的? WAV_IOControl 函數(shù),調用 IOCTL_WAV_MESSAGE 分支。
3.? 調用 HandleWaveMessage 的 WODM_OPEN 分支
HandleWaveMessage 需要傳入兩個參數(shù),其中一個是 PMMDRV_MESSAGE_PARAMS ,另一個是函數(shù)執(zhí)行的結果pdwResult,見 HandleWaveMessage 原型和 MMDRV_MESSAGE_PARAMS 結構體定義。
BOOL?HandleWaveMessage(PMMDRV_MESSAGE_PARAMS?pParams,?DWORD?*pdwResult)
?
view plain copy to clipboard print ?
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
);
其 中參數(shù)dwUser指向Wavedev2驅動的StreamContext對象指針,如果調用的是waveOutOpen,則dwUser做出傳出參數(shù), 來保存StreamContext對象,否則就是作為傳入?yún)?shù)。waveOutMessage的uMsg會傳入驅動變成 MDRV_MESSAGE_PARAMS 中的uMsg,?同樣的dw1變dwParam1,dw2變dwParam2,所以的上層調用都是調用 waveOutMessage 這個函數(shù)實現(xiàn)的。
?
好了,現(xiàn)在我們來開始顯示吧。 HWAVEOUT 要么直接指向對象,要么是對象的在數(shù)組中的索引。我們只是模擬,所以把 HWAVEOUT 直接指向對象。
先定義一個 CWAVEOut 對象,來保存必要的數(shù)據(jù),其他幾個參數(shù)就不做解釋了,我們看m_hWave和m_pStream,m_hWave是來保存打開Wave驅動CreateFile返回的句柄,而m_pStream是保存創(chuàng)建的StreamContext對象的。
?
view plain copy to clipboard print ?
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;
};
好,現(xiàn)在我們來模擬 waveOutOpen 的實現(xiàn)。在waveOutOpen中,只是新建一個CWAVEOut對象,然后把外部傳入的數(shù)據(jù)保存到這個對象中,最后調用open來打開音頻設備。代碼如下:
?
view plain copy to clipboard print ?
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函數(shù)封裝 WAVEOPENDESC 作為waveOutMessage的第一個傳入?yún)?shù),第二個參數(shù)是m_fdwOpen。
?
view plain copy to clipboard print ?
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 作為參數(shù)傳入,因為在底層通過調用 OpenStream ,傳入指針的指針,來保存對象的。
pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1,?dwParam2,?(StreamContext?**)dwUser);
?
?
?
view plain copy to clipboard print ?
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;
}
我們現(xiàn)在模擬實現(xiàn)了waveOutMessage,那么其他一些函數(shù)的實現(xiàn)要比waveOutOpen更加的簡單。如WaveOutReset和 waveOutSetVolume ,只要調用下 waveOutMessage 就可以了。
view plain copy to clipboard print ?
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函數(shù)是怎么返回的,如函數(shù)調用是hwo為空,是怎么樣的,也沒有對錯誤進行處理。
下面是播放一個wave聲音的函數(shù),從代碼中去解析
?
?
view plain copy to clipboard print ?
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);?? ?? ???? ?? ????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);?? ?? ???? ?? ????dwSlop?=?1000;?? ????dwWait?=?WaitForSingleObject(hevDone,?dwDuration?+?dwSlop);?? ????if ?(dwWait?!=?WAIT_OBJECT_0)?{?? ???????? ?? ????????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 slopdwSlop = 1000;dwWait = WaitForSingleObject(hevDone, dwDuration + dwSlop);if (dwWait != WAIT_OBJECT_0) {// not much to here, other than issue a warningRETAILMSG(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準備好數(shù)據(jù)頭,告訴驅動要播放多大的數(shù)據(jù),在驅動中 waveOutPrepareHeader ? 調用 WODM_PREPARE 分支,一般情況下驅動沒有去實現(xiàn) WODM_PREPARE ,直接返回 MMSYSERR_NOTSUPPORTED 。準備好Header后,調用waveOutWrite寫出buffer。
?
好了,就寫到這里,如有錯誤之處,請更正。
總結
以上是生活随笔為你收集整理的Waveform Audio 驱动(Wavedev2)之:WAV API模拟的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。