捕获流

客户端调用 IAudioCaptureClient 接口中的方法,从终结点缓冲区读取捕获到的数据。 客户端在共享模式下与音频引擎共享终结点缓冲区,在独享模式下与音频设备共享终结点缓冲区。 要请求特定大小的终结点缓冲区,客户端会调用 IAudioClient::Initialize 方法。 要获取已分配缓冲区的大小(可能与请求的大小不同),客户端会调用 IAudioClient::GetBufferSize 方法。

要通过终结点缓冲区来移动捕获的数据流,客户端会交替调用 IAudioCaptureClient::GetBuffer 方法和 IAudioCaptureClient::ReleaseBuffer 方法。 客户端会以一系列数据包的形式访问终结点缓冲区中的数据。 GetBuffer 调用将从缓冲区检索下一个捕获的数据包。 从数据包中读取数据后,客户端会调用 ReleaseBuffer 以释放数据包,并使其可用于更多捕获的数据。

数据包大小可能会随着 GetBuffer 调用的不同而变化。 在调用 GetBuffer 之前,客户端可以选择调用 IAudioCaptureClient::GetNextPacketSize 方法,以便提前获取下一个数据包的大小。 此外,客户端还可以调用 IAudioClient::GetCurrentPadding 方法来获取缓冲区中可用的捕获数据总量。 在任何时刻,数据包大小总是会小于或等于缓冲区中捕获的数据总量。

在每次处理传递过程中,客户端都可以选择以下列方式之一来处理所捕获的数据:

  • 客户端交替调用 GetBufferReleaseBuffer,每调用一次读取一个数据包,直到 GetBuffer 返回 AUDCNT_S_BUFFEREMPTY,表示缓冲区已空。
  • 在在每次调用 GetBufferReleaseBuffer 之前,客户端都会调用 GetNextPacketSize,直到 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 函数分配了一个持续时间为一秒的共享缓冲区。 (已分配的缓冲区可能持续时间稍长。)在主循环中,调用 Windows Sleep 函数会让程序等待半秒。 在每次 Sleep 调用开始时,共享缓冲区为空或几乎为空。 当 Sleep 调用返回时,共享缓冲区已填充了一半的捕获数据。

调用 IAudioClient::Initialize 方法后,数据流将保持打开状态,直到客户端释放对 IAudioClient 接口的所有引用,以及对客户端通过 IAudioClient::GetService 方法获得的服务接口的所有引用。 最后的 Release 调用将关闭流。

流管理