日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2023/12/15 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Waveform Audio 驅動(Wavedev2)之:WAV 驅動解析

????? 上篇文章中,我們模擬了WAV API。現在進入我們正在要解析的Wave 驅動的架構。我們了解一個驅動的時候,先不去看具體跟硬件操作相關的東西,而是從流程入手,把整個流程搞清楚了,調試起來就非常的容易了。我們著重看 hwctxt.cpp,hwctxt.H,devctxt.cpp,devctxt.H,strmctxt.cpp,strmctxt.H這幾個源文件。 其中hwctxt是類HardwareContext代碼文件,devctxt是DeviceContext代碼文件,strmctxt是 StreamContext代碼文件。這幾個類的其他一些功能,還在其他一些文件中實現,如output.Cpp,midistrm.Cpp等。

????? 現在我們來看下StreamContext的類圖,StreamContext是管理音頻流的對象,包括播放、暫停、停止、設置音量、獲取播放位置等。從 下面的StreamContext的類圖中,我們可以看到它派生了WaveStreamContext和MidiStream。然后 WaveStreamContext又派生了Input和Output類型的Stream。不用說也可以知道InputStreamContext是針對 于像麥克這種輸入設備流的。

StreamContext類圖

????? 其中OutputStreamContext派生了六個類,M代表單音道,S代表的是立體音,8/16是8/16比特采樣了。 SPDIF(SONY/PHILIPS DIGITAL INTERFACE)是一種最新的音頻傳輸格式,它通過光纖進行數字音頻信號傳輸以取代傳統的模擬信號傳輸方式,因此可以取得更高質量的音質效果。

???? StreamContext是一個管理音頻數據流的對象,像智能手機中可能存在用media player播放音樂,同時又開著FM,突然又來電。從上篇文章中我們知道,要想調用wave驅動的播放功能,每個應用都有一份 StreamContext對象,上面提到的狀況,就會有三個StreamContext對象被創建。 在硬件只要一個的條件下,那么這三個StreamContext是如果協同工作的呢?而DeviceContext正是管理StreamContext對 象的。

如下是DeviceContext類圖:


DeviceContext類圖

DeviceContext派生出InputDeviceContext和OutputDeviceContext,他們分別管理 InputStreamContext和OutputStreamContext。在DeviceContext內部維護了一個雙向鏈表來管理 StreamContext。

HardwareContext是具體操作硬件相關的類,其內部包含InputDeviceContext和OutputDeviceContext對象,下面這種圖,就是三個類的關系圖,一看就知道他們的對應關系了。

DeivceContext和StreamContext關系圖

??? 對于HardwareContext是具體操作硬件的東西,不具有代碼性,只要仔細看看代碼就行了。現在我們主要分析下DeviceContext和StreamContext的關系。

DeviceContext的作用是管理StreamContext,可以分為幾套函數,見Devctxt.h, Devctxt.cpp

音量增益管理:下面這個函數主要是設置設備的整個音量增益,設置了設備音量增益后,對流音量的增益起了限制做用的。

音量函數如下

?

view plain copy to clipboard print ?
  • DWORD ?GetGain();??
  • DWORD ?SetGain( DWORD ?dwGain);??
  • DWORD ?GetDefaultStreamGain();??
  • DWORD ?SetDefaultStreamGain( DWORD ?dwGain);??
  • DWORD ?GetSecondaryGainLimit( DWORD ?GainClass);??
  • DWORD ?SetSecondaryGainLimit( DWORD ?GainClass,? DWORD ?Limit);??
  • DWORD GetGain(); DWORD SetGain(DWORD dwGain); DWORD GetDefaultStreamGain(); DWORD SetDefaultStreamGain(DWORD dwGain); DWORD GetSecondaryGainLimit(DWORD GainClass); DWORD SetSecondaryGainLimit(DWORD GainClass, DWORD Limit);

    先來講下設備音量增益(Device Gain)和流音量增益(Stream Gain)的關系。我們從微軟Media Player中,很容易就看到了設備音量和流音量的關系。設備音量時通過音量鍵來控制系統的音量,從而改變整個輸出設備的音量的,但是在Media Player中,還是有一個單獨的音量控制按鈕,它能調節Media Player的音量(不要問我在哪里,自己找),但是調試它是受限制于系統音量,是如何限制,請看下面講解。

    我們現在看下設置系統音量和設置流音量的整個流程,來了解整個音量控制的過程。用戶設置時,會調用waveOutSetVolume

    MMRESULT waveOutSetVolume(

    ? HWAVEOUT hwo,

    ? DWORD dwVolume

    );

    當HWAVEOUT傳入為空時,設置的就是設備音量,當HWAVEOUT是通過調用waveOutOpen返回的句柄是,設置的就是流音量。

    好,我們進入到驅動中區看看,waveOutSetVolume會調用到來看wavemain.Cpp中HandleWaveMessage的WODM_SETVOLUME分支,我在代碼中去掉了不重要的部分,可以看得更清晰些。

    view plain copy to clipboard print ?
  • case ?WODM_SETVOLUME:??
  • {??
  • ????StreamContext?*pStreamContext;??
  • ????pStreamContext?=?(StreamContext?*)?dwUser;??
  • ??
  • ????LONG ?dwGain?=?dwParam1;??
  • ????if ?(pStreamContext)??
  • ????{??
  • ????????dwRet?=?pStreamContext->SetGain(dwGain);??
  • ????}??
  • ????else ??
  • ????{??
  • ????????DeviceContext?*pDeviceContext?=?g_pHWContext->GetOutputDeviceContext(uDeviceId);??
  • ????????dwRet?=?pDeviceContext->SetGain(dwGain);??
  • ????}??
  • }??
  • case WODM_SETVOLUME: {StreamContext *pStreamContext;pStreamContext = (StreamContext *) dwUser;LONG dwGain = dwParam1;if (pStreamContext){dwRet = pStreamContext->SetGain(dwGain);}else{DeviceContext *pDeviceContext = g_pHWContext->GetOutputDeviceContext(uDeviceId);dwRet = pDeviceContext->SetGain(dwGain);} }

    dwUser 指向的是StreamContext對象(在前文中已經講過),如果pStreamContext為空,那么就調用DeviceContext的 SetGain函數,否則調用StreamContext的SetGain函數。調用StreamContext的Gain只對當前的 StreamContext的音量起作用,不影響其他的Stream音量。但是對DeviceContext設置音量增益是對DeviceContext 管理的所有StreamContext起了控制作用,但是具體是如何影響的,還是根據代碼來分析:

    在Devctxt.h中的SetGain函數代碼如下

    view plain copy to clipboard print ?
  • DWORD ?SetGain( DWORD ?dwGain)??
  • ????{??
  • ????????m_dwGain?=?dwGain;??
  • ????????RecalcAllGains();??
  • ????????return ?MMSYSERR_NOERROR;??
  • ????}??
  • DWORD SetGain(DWORD dwGain){m_dwGain = dwGain;RecalcAllGains();return MMSYSERR_NOERROR;}

    用m_dwGain保存設備音量,然后調用RecalcAllGains來重新計算所有StreamContext的音量增益。

    在Devctxt.cpp中的RecalcAllGains的實現如下

    view plain copy to clipboard print ?
  • void ?DeviceContext::RecalcAllGains()??
  • {??
  • ????PLIST_ENTRY?pListEntry;??
  • ????StreamContext?*pStreamContext;??
  • ??
  • ????for ?(pListEntry?=?m_StreamList.Flink;??
  • ????????pListEntry?!=?&m_StreamList;??
  • ????????pListEntry?=?pListEntry->Flink)??
  • ????{??
  • ????????pStreamContext?=?CONTAINING_RECORD(pListEntry,StreamContext,m_Link);??
  • ????????pStreamContext->GainChange();??
  • ????}??
  • ????return ;??
  • }??
  • void DeviceContext::RecalcAllGains(){PLIST_ENTRY pListEntry;StreamContext *pStreamContext;for (pListEntry = m_StreamList.Flink;pListEntry != &m_StreamList;pListEntry = pListEntry->Flink){pStreamContext = CONTAINING_RECORD(pListEntry,StreamContext,m_Link);pStreamContext->GainChange();}return;}

    它便利所有的StreamContext,并調用pStreamContext->GainChange()來改變StreamContext對象的音量。接著看StreamContext類中的GainChange的實現

    view plain copy to clipboard print ?
  • void ?GainChange()??
  • ???{??
  • ???????m_fxpGain?=?MapGain(m_dwGain);??
  • }??
  • DWORD ?StreamContext::MapGain( DWORD ?Gain)??
  • {??
  • ????DWORD ?TotalGain?=?Gain?&?0xFFFF;??
  • ????DWORD ?SecondaryGain?=?m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass)?&?0xFFFF;??
  • ????if ?(m_SecondaryGainClass?<?SECONDARYDEVICEGAINCLASSMAX)??
  • ????{??
  • ????????//?Apply?device?gain ??
  • ????????DWORD ?DeviceGain?=?m_pDeviceContext->GetGain()?&?0xFFFF;??
  • ????????TotalGain?*=?DeviceGain;??
  • ????????TotalGain?+=?0xFFFF;??//?Round?up ??
  • ????????TotalGain?>>=?16;?????//?Shift?to?lowest?16?bits ??
  • ????}??
  • ??
  • ????//?Apply?secondary?gain ??
  • ????TotalGain?*=?SecondaryGain;??
  • ????TotalGain?+=?0xFFFF;??//?Round?up ??
  • ????TotalGain?>>=?16;?????//?Shift?to?lowest?16?bits ??
  • ??
  • ????//?Special?case?0?as?totally?muted ??
  • ????if ?(TotalGain==0)??
  • ????{??
  • ????????return ?0;??
  • ????}??
  • ??
  • ????//?Convert?to?index?into?table ??
  • ????DWORD ?Index?=?63?-?(TotalGain>>10);??
  • ????return ?GainMap[Index];??
  • }??
  • void GainChange(){m_fxpGain = MapGain(m_dwGain);}DWORD StreamContext::MapGain(DWORD Gain){DWORD TotalGain = Gain & 0xFFFF;DWORD SecondaryGain = m_pDeviceContext->GetSecondaryGainLimit(m_SecondaryGainClass) & 0xFFFF;if (m_SecondaryGainClass < SECONDARYDEVICEGAINCLASSMAX){// Apply device gainDWORD DeviceGain = m_pDeviceContext->GetGain() & 0xFFFF;TotalGain *= DeviceGain;TotalGain += 0xFFFF; // Round upTotalGain >>= 16; // Shift to lowest 16 bits}// Apply secondary gainTotalGain *= SecondaryGain;TotalGain += 0xFFFF; // Round upTotalGain >>= 16; // Shift to lowest 16 bits// Special case 0 as totally mutedif (TotalGain==0){return 0;}// Convert to index into tableDWORD Index = 63 - (TotalGain>>10);return GainMap[Index];}

    音量在系統中用一個DWORD值來表示,其高低兩個字節分別來表示左右聲道,一般情況下左聲道和右聲道的音量大小是一樣的,所以只取其低兩個字節,DWORD TotalGain = Gain & 0xFFFF;

    TotalGain是DeviceGain和m_dwGain的乘機,然后再左移16位得到的。其實就是 TotalGain=DeviceGain*m_dwGain/最高音量,如果把DeviceGain/最高音量,用百分比來算的話,就很更容易理解了, 那么最后的公式就變成TotalGain=DeviceGain*系統音量百分比。那么這里就解釋了系統音量是如何限制流音量的疑問。

    我們設置好音量增益后,最終會再哪里體現呢:首先看一下Output.cpp文件,WaveStreamContext::Render之后的數據 就是直接發送到外部聲音芯片的數據,他根據參數以及標志位選擇OutputStreamContextXXX::Render2,XXX表示雙聲道S單聲 道M,bit位是8位還是16位。以雙聲道OutputStreamContextS16::Render2為例,BSP里面的代碼如下:

    view plain copy to clipboard print ?
  • PBYTE ?OutputStreamContextS16::Render2( PBYTE ?pBuffer,? PBYTE ?pBufferEnd,? PBYTE ?pBufferLast)??
  • {??
  • ????LONG ?CurrT?=?m_CurrT;??
  • ????LONG ?DeltaT?=?m_DeltaT;??
  • ????LONG ?CurrSamp0?=?m_CurrSamp[0];??
  • ????LONG ?PrevSamp0?=?m_PrevSamp[0];??
  • ????PBYTE ?pCurrData?=?m_lpCurrData;??
  • ????PBYTE ?pCurrDataEnd?=?m_lpCurrDataEnd;??
  • ????LONG ?fxpGain?=?m_fxpGain;??
  • ????LONG ?OutSamp0;??
  • ??
  • ????__try ??
  • ????{??
  • ????????while ?(pBuffer?<?pBufferEnd)??
  • ????????{??
  • ????????????while ?(CurrT?>=?0x100)??
  • ????????????{??
  • ????????????????if ?(pCurrData>=pCurrDataEnd)??
  • ????????????????{??
  • ????????????????????goto ?Exit;??
  • ????????????????}??
  • ????????????????CurrT?-=?0x100;??
  • ????????????????PrevSamp0?=?CurrSamp0;??
  • ????????????????PPCM_SAMPLE?pSampleSrc?=?(PPCM_SAMPLE)pCurrData;??
  • ????????????????CurrSamp0?=??(LONG )pSampleSrc->s16.sample_left;??
  • ????????????????CurrSamp0?+=?(LONG )pSampleSrc->s16.sample_right;??
  • ????????????????CurrSamp0?=?CurrSamp0>>1;??
  • ????????????????pCurrData+=4;??
  • ????????????}??
  • ????????????OutSamp0?=?PrevSamp0?+?(((CurrSamp0?-?PrevSamp0)?*?CurrT)?>>?8);??
  • ????????????//?設置增益 ??
  • ????????????OutSamp0?=?(OutSamp0?*?fxpGain)?>>?VOLSHIFT;??
  • ????????????CurrT?+=?DeltaT;??
  • ??????
  • ????????????if ?(pBuffer?<?pBufferLast)??
  • ????????????{??
  • ????????????????OutSamp0?+=?*(HWSAMPLE?*)pBuffer;??
  • ????????????}??
  • ????????????*(HWSAMPLE?*)pBuffer?=?(HWSAMPLE)OutSamp0;??
  • ????????????pBuffer?+=?sizeof (HWSAMPLE);??
  • ??
  • ????????}??
  • ????}//end?the?__try?block ??
  • ????__except?(EXCEPTION_EXECUTE_HANDLER)??
  • ????{??
  • ????????RETAILMSG(1,?(TEXT("InputStreamContext::Render2!/r/n" )));??
  • ????????m_lpCurrData?=?m_lpCurrDataEnd?=?NULL;????
  • ????????return ?NULL;??
  • ????}??
  • Exit:??
  • ????m_dwByteCount?+=?(pCurrData?-?m_lpCurrData);??
  • ????m_lpCurrData?=?pCurrData;??
  • ????m_CurrT?=?CurrT;??
  • ????m_PrevSamp[0]?=?PrevSamp0;??
  • ????m_CurrSamp[0]?=?CurrSamp0;??
  • ????return ?pBuffer;??
  • ????}??
  • PBYTE OutputStreamContextS16::Render2(PBYTE pBuffer, PBYTE pBufferEnd, PBYTE pBufferLast) {LONG CurrT = m_CurrT;LONG DeltaT = m_DeltaT;LONG CurrSamp0 = m_CurrSamp[0];LONG PrevSamp0 = m_PrevSamp[0];PBYTE pCurrData = m_lpCurrData;PBYTE pCurrDataEnd = m_lpCurrDataEnd;LONG fxpGain = m_fxpGain;LONG OutSamp0;__try{while (pBuffer < pBufferEnd){while (CurrT >= 0x100){if (pCurrData>=pCurrDataEnd){goto Exit;}CurrT -= 0x100;PrevSamp0 = CurrSamp0;PPCM_SAMPLE pSampleSrc = (PPCM_SAMPLE)pCurrData;CurrSamp0 = (LONG)pSampleSrc->s16.sample_left;CurrSamp0 += (LONG)pSampleSrc->s16.sample_right;CurrSamp0 = CurrSamp0>>1;pCurrData+=4;}OutSamp0 = PrevSamp0 + (((CurrSamp0 - PrevSamp0) * CurrT) >> 8);// 設置增益OutSamp0 = (OutSamp0 * fxpGain) >> VOLSHIFT;CurrT += DeltaT;if (pBuffer < pBufferLast){OutSamp0 += *(HWSAMPLE *)pBuffer;}*(HWSAMPLE *)pBuffer = (HWSAMPLE)OutSamp0;pBuffer += sizeof(HWSAMPLE);}}//end the __try block__except (EXCEPTION_EXECUTE_HANDLER){RETAILMSG(1, (TEXT("InputStreamContext::Render2!/r/n")));m_lpCurrData = m_lpCurrDataEnd = NULL; return NULL;} Exit:m_dwByteCount += (pCurrData - m_lpCurrData);m_lpCurrData = pCurrData;m_CurrT = CurrT;m_PrevSamp[0] = PrevSamp0;m_CurrSamp[0] = CurrSamp0;return pBuffer;}

    從上面看到是與采樣數據相乘,然后在左移16位。跟上面提到的系統音量影響流音量是一樣的。

    上面講了,DeviceContext的音量增益管理,現在來看下它的流管理。

    StreamContext流管理:主要來管理StreamContext的創建、刪除、渲染、傳輸等功能。

    主要有如下幾個函數

    view plain copy to clipboard print ?
  • StreamContext?*CreateStream(LPWAVEOPENDESC?lpWOD);??
  • DWORD ?OpenStream(LPWAVEOPENDESC?lpWOD,? DWORD ?dwFlags,?StreamContext?**ppStreamContext);??
  • HRESULT ?Open(DeviceContext?*pDeviceContext,?LPWAVEOPENDESC?lpWOD,? DWORD ?dwFlags);??
  • void ?NewStream(StreamContext?*pStreamContext);??
  • void ?DeleteStream(StreamContext?*pStreamContext);??
  • void ?StreamReadyToRender(StreamContext?*pStreamContext);??
  • PBYTE ?TransferBuffer( PBYTE ?pBuffer,? PBYTE ?pBufferEnd,? DWORD ?*pNumStreams,? BOOL ?bMuteFlag);??
  • StreamContext *CreateStream(LPWAVEOPENDESC lpWOD); DWORD OpenStream(LPWAVEOPENDESC lpWOD, DWORD dwFlags, StreamContext **ppStreamContext); HRESULT Open(DeviceContext *pDeviceContext, LPWAVEOPENDESC lpWOD, DWORD dwFlags); void NewStream(StreamContext *pStreamContext); void DeleteStream(StreamContext *pStreamContext); void StreamReadyToRender(StreamContext *pStreamContext); PBYTE TransferBuffer(PBYTE pBuffer, PBYTE pBufferEnd, DWORD *pNumStreams, BOOL bMuteFlag);

    在DeviceContext中有個m_StreamList的雙向鏈表(LIST_ENTRY), m_StreamList用來指向鏈表的頭。在StreamConext中也存在一個m_Link(LIST_ENTRY)。StreamContext 是調用DeviceContext的OpenStream來創建的,然后把StreamContext對象加入到DeviceContext的 m_StreamList中。我們從代碼中去直接分析:

    上層調用waveoutOpen,在wavedev2中會調用WODM_OPEN這個分支。在WODM_OPEN中的代碼如下:

    view plain copy to clipboard print ?
  • case ?WODM_OPEN:??
  • {??
  • ????StreamContext?*pStreamContext;??
  • ????pStreamContext?=?(StreamContext?*)?dwUser;??
  • ????dwRet?=?pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1,?dwParam2,?(StreamContext?**)pStreamContext);??
  • ????break ;??
  • }??
  • case WODM_OPEN: {StreamContext *pStreamContext;pStreamContext = (StreamContext *) dwUser;dwRet = pDeviceContext->OpenStream((LPWAVEOPENDESC)dwParam1, dwParam2, (StreamContext **)pStreamContext);break; }

    OpenStream的其流程圖如下

    StreamContext 初始化流程

    CreateStream是根據WAVEFORMATEX這個結構體,來判斷具體要創建StreamContext的哪個派生類,下面是CreateStream的流程圖,不可不提,還是流程圖清晰。

    OutputDeviceContext:: CreateStream流程圖

    上面講了上層通過WODM_OPEN創建一個StreamContext的過程,那么音頻流被打開之后,接下來就是給StreamContext傳 入音頻數據開始播放音樂。Wavedev2提供了WODM_WRITE來向音頻設置寫入數據。我們先看下WODM_WRITE分支的代碼

    view plain copy to clipboard print ?
  • case ?WODM_WRITE:??
  • {??
  • ????StreamContext?*pStreamContext;??
  • ????pStreamContext?=?(StreamContext?*)?dwUser;??
  • ????dwRet?=?pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);??
  • ????break ;??
  • }??
  • case WODM_WRITE: {StreamContext *pStreamContext;pStreamContext = (StreamContext *) dwUser;dwRet = pStreamContext->QueueBuffer((LPWAVEHDR)dwParam1);break; }

    這里調用了StreamContext中的QueueBuffer,QueueBuffer的作用就是把WAVEHDR中的數據加入到StreamContext的隊列中,等待播放。下面是QueueBuffer的流程圖

    QueueBuffer流程圖

    在QueueBuffer中調用DeviceContext中的StreamReadyToReander通知可以開始渲染了,流程圖中的箭頭方向 是StreamReadyToReander調用流程,最終調用SetEvent(hOutputIntEvent),來通知線程數據已經準備好,得到通 知后,就開始播放了。該線程在HardwareContext中的OutputInterruptThread函數中

    OutputInterruptThread流程如下

    結尾時,想起大學教育的一個問題,居然不學語文了。見別人寫的文章文采飛揚,而我的確是很僵硬,風趣和文采都不足。

    還是老話,本人才疏學淺,有錯誤之處,請更正。

    總結

    以上是生活随笔為你收集整理的Waveform Audio 驱动(Wavedev2)之:WAV 驱动解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。