擷取數據流
用戶端會呼叫 IAudioCaptureClient 介面中的方法,從端點緩衝區讀取擷取的數據。 用戶端會以共用模式與音訊引擎共用端點緩衝區,並以獨佔模式與音訊裝置共用。 若要要求特定大小的端點緩衝區,用戶端會呼叫 IAudioClient::Initialize 方法。 若要取得配置緩衝區的大小,這可能與要求的大小不同,用戶端會呼叫 IAudioClient::GetBufferSize 方法。
若要透過端點緩衝區移動所擷取數據的數據流,用戶端會替代呼叫 IAudioCaptureClient::GetBuffer 方法和 IAudioCaptureClient::ReleaseBuffer 方法。 用戶端會將端點緩衝區中的數據當做一系列數據封包來存取。 GetBuffer 呼叫會從緩衝區擷取下一個擷取的數據封包。 從封包讀取數據之後,用戶端會呼叫 ReleaseBuffer 來釋放封包,並使它可供更多擷取的數據使用。
封包大小可能會因下一個 GetBuffer 呼叫而有所不同。 在呼叫 GetBuffer 之前,用戶端可以選擇 呼叫 IAudioCaptureClient::GetNextPacketSize 方法,以事先取得下一個封包的大小。 此外,用戶端可以呼叫 IAudioClient::GetCurrentPadding 方法來取得緩衝區中可用的擷取數據總數。 在任何時間,封包大小一律小於或等於緩衝區中擷取的數據總量。
在每個處理階段期間,用戶端可以選擇下列其中一種方式來處理擷取的數據:
- 用戶端會交替呼叫 GetBuffer 和 ReleaseBuffer,讀取一個封包與每對呼叫,直到 GetBuffer 傳回AUDCNT_S_BUFFEREMPTY為止,表示緩衝區是空的。
- 用戶端會先呼叫 GetNextPacketSize,再呼叫 GetBuffer 和 ReleaseBuffer,直到 GetNextPacketSize 報告封包大小為 0 為止,表示緩衝區是空的。
這兩種技術會產生對等的結果。
下列程式代碼範例示範如何從預設擷取裝置錄製音訊數據流:
//-----------------------------------------------------------
// Record an audio stream from the default audio capture
// device. The RecordAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data from the
// capture device. The main loop runs every 1/2 second.
//-----------------------------------------------------------
// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
#define EXIT_ON_ERROR(hres) \
if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk) \
if ((punk) != NULL) \
{ (punk)->Release(); (punk) = NULL; }
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
HRESULT RecordAudioStream(MyAudioSink *pMySink)
{
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
REFERENCE_TIME hnsActualDuration;
UINT32 bufferFrameCount;
UINT32 numFramesAvailable;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioClient *pAudioClient = NULL;
IAudioCaptureClient *pCaptureClient = NULL;
WAVEFORMATEX *pwfx = NULL;
UINT32 packetLength = 0;
BOOL bDone = FALSE;
BYTE *pData;
DWORD flags;
hr = CoCreateInstance(
CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**)&pEnumerator);
EXIT_ON_ERROR(hr)
hr = pEnumerator->GetDefaultAudioEndpoint(
eCapture, eConsole, &pDevice);
EXIT_ON_ERROR(hr)
hr = pDevice->Activate(
IID_IAudioClient, CLSCTX_ALL,
NULL, (void**)&pAudioClient);
EXIT_ON_ERROR(hr)
hr = pAudioClient->GetMixFormat(&pwfx);
EXIT_ON_ERROR(hr)
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_SHARED,
0,
hnsRequestedDuration,
0,
pwfx,
NULL);
EXIT_ON_ERROR(hr)
// Get the size of the allocated buffer.
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
EXIT_ON_ERROR(hr)
hr = pAudioClient->GetService(
IID_IAudioCaptureClient,
(void**)&pCaptureClient);
EXIT_ON_ERROR(hr)
// Notify the audio sink which format to use.
hr = pMySink->SetFormat(pwfx);
EXIT_ON_ERROR(hr)
// Calculate the actual duration of the allocated buffer.
hnsActualDuration = (double)REFTIMES_PER_SEC *
bufferFrameCount / pwfx->nSamplesPerSec;
hr = pAudioClient->Start(); // Start recording.
EXIT_ON_ERROR(hr)
// Each loop fills about half of the shared buffer.
while (bDone == FALSE)
{
// Sleep for half the buffer duration.
Sleep(hnsActualDuration/REFTIMES_PER_MILLISEC/2);
hr = pCaptureClient->GetNextPacketSize(&packetLength);
EXIT_ON_ERROR(hr)
while (packetLength != 0)
{
// Get the available data in the shared buffer.
hr = pCaptureClient->GetBuffer(
&pData,
&numFramesAvailable,
&flags, NULL, NULL);
EXIT_ON_ERROR(hr)
if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
{
pData = NULL; // Tell CopyData to write silence.
}
// Copy the available capture data to the audio sink.
hr = pMySink->CopyData(
pData, numFramesAvailable, &bDone);
EXIT_ON_ERROR(hr)
hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
EXIT_ON_ERROR(hr)
hr = pCaptureClient->GetNextPacketSize(&packetLength);
EXIT_ON_ERROR(hr)
}
}
hr = pAudioClient->Stop(); // Stop recording.
EXIT_ON_ERROR(hr)
Exit:
CoTaskMemFree(pwfx);
SAFE_RELEASE(pEnumerator)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pAudioClient)
SAFE_RELEASE(pCaptureClient)
return hr;
}
在上述範例中,RecordAudioStream 函式會採用單一參數, pMySink
這是屬於用戶端定義類別 MyAudioSink 之物件的指標,其中包含兩個函式 CopyData 和 SetFormat。 範例程式代碼不包含 MyAudioSink 的實作,因為:
- 類別成員都不會直接與 WASAPI 介面中的任何方法通訊。
- 類別可以透過各種方式實作,視用戶端的需求而定。 (例如,它可能會將擷取數據寫入 WAV 檔案。
不過,這兩種方法之作業的相關信息對於瞭解此範例很有用。
CopyData 函式會從指定的緩衝區位置複製指定的音訊畫面數。 RecordAudioStream 函式會使用 CopyData 函式,從共用緩衝區讀取和儲存音訊數據。 SetFormat 函式會指定要用於數據的 CopyData 函式格式。
只要 MyAudioSink 物件需要其他數據,CopyData 函式就會透過其第三個參數輸出 FALSE 值,這在上述程式代碼範例中是變數bDone
的指標。 當 MyAudioSink 物件具有它所需的所有數據時,CopyData 函式會設定 bDone
為 TRUE,這會導致程式在 RecordAudioStream 函式中結束迴圈。
RecordAudioStream 函式會配置具有一秒持續時間的共用緩衝區。 (配置緩衝區的持續時間可能稍長。在 main 迴圈中,Windows Sleep 函式的呼叫會導致程式等候半秒。 在每個 睡眠 呼叫開始時,共用緩衝區是空的或幾乎空白的。 當睡眠呼叫傳回時,共用緩衝區大約會填入擷取數據的一半。
在呼叫 IAudioClient::Initialize 方法之後,數據流會保持開啟狀態,直到客戶端釋放其 IAudioClient 介面的所有參考,以及用戶端透過 IAudioClient::GetService 方法取得之服務介面的所有參考為止。 最終 的 Release 呼叫會關閉數據流。
相關主題