ストリームのキャプチャ

クライアントは IAudioCaptureClient インターフェイス内のメソッドを呼び出して、エンドポイント バッファーからキャプチャされたデータを読み取ります。 クライアントは、エンドポイント バッファーを、共有モードのオーディオ エンジンおよび排他的モードのオーディオ デバイスと共有します。 特定のサイズのエンドポイント バッファーを要求するために、クライアントは IAudioClient::Initialize メソッドを呼び出します。 割り当てられたバッファーのサイズ (要求されたサイズとは異なる可能性があります) を取得するために、クライアントは IAudioClient::GetBufferSize メソッドを呼び出します。

キャプチャされたデータのストリームをエンドポイント バッファー経由で移動するために、クライアントは代わりに IAudioCaptureClient::GetBuffer メソッドと IAudioCaptureClient::ReleaseBuffer メソッドを呼び出します。 クライアントは、エンドポイント バッファー内のデータに一連のデータ パケットとしてアクセスします。 GetBuffer 呼び出しは、キャプチャされたデータの次のパケットをバッファーから取得します。 パケットからデータを読み取った後、クライアントは ReleaseBuffer を呼び出してパケットを解放し、キャプチャされたデータをより多く使用できるようにします。

パケット サイズは、1 つの GetBuffer 呼び出しから次の呼び出しまで異なる場合があります。 クライアントには、GetBuffer を呼び出す前に、IAudioCaptureClient::GetNextPacketSize メソッドを呼び出して、次のパケットのサイズを事前に取得するオプションがあります。 さらに、クライアントは IAudioClient::GetCurrentPadding メソッドを呼び出し、バッファー内で使用可能なキャプチャされたデータの合計量を取得できます。 パケット サイズは、いつでもバッファー内のキャプチャされたデータの合計量以下になります。

各処理パスの間、クライアントには、次のいずれかの方法でキャプチャされたデータを処理するオプションがあります。

  • クライアントは代わりに、GetBuffer がAUDCNT_S_BUFFEREMPTYを返し、バッファーが空であることが示されるまで、GetBufferReleaseBuffer を呼び出し、呼び出しのペアごとに 1 つのパケットを読み取ります。
  • クライアントは、GetNextPacketSize がパケット サイズ 0 を報告し、バッファーが空であることが示されるまで、GetBufferReleaseBuffer の呼び出しの各ペアの前に GetNextPacketSize を呼び出します。

2 つの手法で同等の結果が得られます。

次のコード例は、既定のキャプチャ デバイスからオーディオ ストリームを記録する方法を示しています。

//-----------------------------------------------------------
// 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 関数は、2 つの関数 CopyData と SetFormat を持つクライアント定義クラス MyAudioSink に属するオブジェクトへのポインターである 1 つのパラメーター pMySink を受け取ります。 次の理由から、サンプル コードには MyAudioSink の実装は含まれません。

  • どのクラス メンバーも、WASAPI のインターフェイス内のどのメソッドとも直接通信を行いません。
  • このクラスは、クライアントの要件に応じて、さまざまな方法で実装できます。 (たとえば、キャプチャ データを WAV ファイルに書き込むことができます)。

ただし、2 つのメソッドの操作に関する情報は、この例を理解するために役立ちます。

CopyData 関数は、指定された数のオーディオ フレームを、指定されたバッファー位置からコピーします。 RecordAudioStream 関数は、CopyData 関数を使用して、共有バッファーからオーディオ データを読み取って保存します。 SetFormat 関数は、データに使用する CopyData 関数の形式を指定します。

MyAudioSink オブジェクトが追加のデータを必要とする限り、CopyData 関数は、3 番目のパラメーターを通じて値 FALSE を出力します。これは、前のコード例では変数 bDone へのポインターです。 MyAudioSink オブジェクトに必要なすべてのデータがある場合、CopyData 関数は bDoneTRUE に設定します。これにより、プログラムが RecordAudioStream 関数のループを終了します。

RecordAudioStream 関数は、期間が 1 秒の共有バッファーを割り当てます。 (割り当てられたバッファーの期間が少し長くなる可能性があります)。メイン ループ内では、Windows Sleep 関数を呼び出すと、プログラムは 2 分の 1 秒待機します。 各 Sleep 呼び出しの開始時、共有バッファーは空であるか、ほぼ空です。 Sleep 呼び出しが返されるときまでに、共有バッファーの約半分がキャプチャ データによって満たされます。

IAudioClient::Initialize メソッドへの呼び出しの後、クライアントが、IAudioClient インターフェイスへのすべての参照と、クライアントが IAudioClient::GetService メソッドを介して取得したサービス インターフェイスへのすべての参照を解放するまで、ストリームは開いたままとなります。 最後の Release 呼び出しでストリームが閉じられます。

ストリームの管理