擷取數據流

用戶端會呼叫 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 函式會設定 bDoneTRUE,這會導致程式在 RecordAudioStream 函式中結束迴圈。

RecordAudioStream 函式會配置具有一秒持續時間的共用緩衝區。 (配置緩衝區的持續時間可能稍長。在 main 迴圈中,Windows Sleep 函式的呼叫會導致程式等候半秒。 在每個 睡眠 呼叫開始時,共用緩衝區是空的或幾乎空白的。 當睡眠呼叫傳回時,共用緩衝區大約會填入擷取數據的一半。

在呼叫 IAudioClient::Initialize 方法之後,數據流會保持開啟狀態,直到客戶端釋放其 IAudioClient 介面的所有參考,以及用戶端透過 IAudioClient::GetService 方法取得之服務介面的所有參考為止。 最終 的 Release 呼叫會關閉數據流。

串流管理