ストリームのキャプチャ

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

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

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

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

  • クライアントは代わりに、GetBuffer がAUDCNT_S_BUFFEREMPTYを返し、バッファーが空であることが示されるまで、GetBuffer と ReleaseBuffer を呼び出し、呼び出しのペアごとに 1 つのパケットを読み取ります。
  • クライアントは、GetNextPacketSize がパケット サイズ 0 を報告し、バッファーが空であることが示されるまで、GetBuffer と ReleaseBuffer の呼び出しの各ペアの前に 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 関数は bDone を TRUE に設定します。これにより、プログラムが RecordAudioStream 関数のループを終了します。

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

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

ストリームの管理