從Vista開始,windows底層的音頻架構發生了改變:本來是底層API的waveXXX、mixerXXX等都在Core Audio APIs的基礎上進行了重構,上升爲了高層API;底層API變爲Core Audio API。 因爲這個緣由,在利用遺留音頻技術(waveXXX、mixerXXX等)進行開發的時候,在WinXp和其餘系統上的表現會不太一致。git
可是若是要在Xp上進行開發的話,就必需要使用這些老舊的技術,沒得選。github
在Xp下進行開發,大概只有DirectX、waveXXX和mixerXXX可選了。 這裏咱們簡單描述它們的優缺點:windows
優勢:api
缺點:架構
咱們選擇waveXXX api來實現這個開發實例,由於waveXXX相對來講比較好用,這樣咱們不用花費過多的時間去了解其餘概念上的細節。app
先調用waveInGetNumDevs()獲取設備總數,而後傳入設備序號(0 ~ 總數-1),並選擇設備支持的PCM數據格式中的一種打開設備,獲取到設備句柄:ide
auto inputAudioDeviceNum = waveInGetNumDevs(); for (int i = 0; i < inputAudioDeviceNum; ++i) { WAVEINCAPS waveInCaps; auto returnValue = waveInGetDevCaps(i, &waveInCaps, sizeof(waveInCaps)) ; ...... WAVEFORMATEX waveFormatEx = chooseAppropriateFormat(); auto returnValue = waveInOpen((LPHWAVEIN)&deviceInfo.handle, index, &waveFormatEx, (DWORD_PTR)CoreAudioHelper::waveInProc, (DWORD_PTR)this, CALLBACK_FUNCTION); ...... }
爲了獲取音頻數據,咱們須要準備一個Buffer,並將這個Buffer添加到你想要獲取數據的音頻設備上,而後開始這個設備的音頻捕獲:oop
bool CoreAudioHelper::startPeakGetter() { Q_ASSERT(m_currentDeviceIndex >= 0 && m_currentDeviceIndex < m_infos.size()); auto& deviceInfo = m_infos[m_currentDeviceIndex]; ZeroMemory(m_buffer, sizeof(m_buffer)); m_waveHdr.dwFlags = 0; m_waveHdr.lpData = (LPSTR)m_buffer; m_waveHdr.dwBufferLength = sizeof(m_buffer); auto returnValue = waveInPrepareHeader(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr)); CHECK_RETURN(returnValue); returnValue = waveInAddBuffer(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr)); CHECK_RETURN(returnValue); returnValue = waveInStart(deviceInfo.handle); CHECK_RETURN(returnValue); deviceInfo.started = true; return true; }
當這個Buffer被數據填滿的時候,系統就會通知你,這時候咱們須要先調用waveInUnprepareHeader()來取消先前準備的Buffer,而後就能夠對數據進行操做了(這裏咱們計算了音頻的音量大小)。在以前打開設備的時候,你能夠選擇多種通知方式:回調、窗口消息、事件或者線程,這裏我選擇使用回調方法。若是要連續的獲取捕獲到的數據,咱們就要在Buffer被填滿的時候不斷添加新的Buffer。注意由於在回調中基本上不能夠調用任何系統api,因此咱們須要另外一個線程來添加新Buffer,並利用信號量來進行同步:測試
void CoreAudioHelper::waveInProc(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { switch (uMsg) { case WIM_OPEN: break; case WIM_CLOSE: { ...... } case WIM_DATA: { ...... break; } default: Q_ASSERT(false && "never receive other msg!"); } } // non-qt thread have no qt event loop which causing signal/slot not working, // we use a queue to keep the value and a semaphore to notify the internal thread // to emit the signal. void CoreAudioHelper::appendPeakValue(qint16 value) { m_peakValueQueue.push(value); // cannot call Win32 api inside a callback, so we notify the buffer waiter thread m_bufferFilled.release(1); } void CoreAudioHelper::BufferWaiterThread::run() { while (true) { m_helper->m_bufferFilled.acquire(1); m_helper->unprepareBuffer(); if (m_helper->m_stopThread) break; if (m_helper->m_emitUnplugged) { emit m_helper->currentDeviceUnplugged(); m_helper->m_emitUnplugged = false; break; } m_helper->emitPeakLevelAndContinue(); } } bool CoreAudioHelper::unprepareBuffer() { auto deviceInfo = m_infos.at(m_currentDeviceIndex); auto returnValue = waveInUnprepareHeader(deviceInfo.handle, &m_waveHdr, sizeof(m_waveHdr)); CHECK_RETURN(returnValue); return true; }
根據PCM數據是8位仍是16位,咱們把Buffer中的比特數據轉換成合適的變量並計算保存最小值和最大值。由於實際音頻波形是以0點爲水平上下波動的,
ui
咱們只須要把最大波動值除以上限值就能夠得到音量大小了(具體見下一小節)。
// buffer already filled with input audio data CoreAudioHelper* helper = reinterpret_cast<CoreAudioHelper*>(dwInstance); Q_ASSERT(helper->m_waveHdr.dwFlags & WHDR_DONE); qint32 peakMin = 255; qint32 peakMax = 0; for (char* ptr = helper->m_buffer; ptr < &helper->m_buffer[16]; ) { qint32 dataValue; if (helper->m_is8BitsSample) { dataValue = *(unsigned char*)ptr; ptr++; } else { dataValue = *(qint16*)ptr; ptr += 2; } if (dataValue < peakMin) peakMin = dataValue; if (dataValue > peakMax) peakMax = dataValue; } helper->appendPeakValue(max(-peakMin, peakMax));
waveXXX API只提供了音頻數據捕獲,所以咱們須要本身來模擬音量和靜音的控制,這裏咱們把這些控制應用在獲取到的音量大小上:
void CoreAudioHelper::emitPeakLevelAndContinue() { if (!m_peakValueQueue.empty()) { qint32 peakValue = m_peakValueQueue.front(); m_peakValueQueue.pop(); if (!m_infos.at(m_currentDeviceIndex).muted) { if (m_is8BitsSample) { // when 8-bit sample, the range is 0--255, the silence data value is 127 emit peakChanged(qint32(abs(peakValue - 127) / 1.27) * m_infos.at(m_currentDeviceIndex).volumeFilterPercent); } else { // when 16-bit sample, the range is -32767--32767, the silence data value is 0 emit peakChanged(qint32(abs(peakValue) / 327.67) * m_infos.at(m_currentDeviceIndex).volumeFilterPercent); } startPeakGetter(); } } }
結果就是這樣啦,完整代碼見此處。