ストリームのレンダリング

クライアントは IAudioRenderClient インターフェイス内のメソッドを呼び出して、レンダリング データをエンドポイント バッファーに書き込みます。 共有モード ストリームの場合、クライアントはエンドポイント バッファーをオーディオ エンジンと共有します。 排他モード ストリームの場合、クライアントはエンドポイント バッファーをオーディオ デバイスと共有します。 特定のサイズのエンドポイント バッファーを要求するために、クライアントは IAudioClient::Initialize メソッドを呼び出します。 割り当てられたバッファーのサイズ (要求されたサイズとは異なる可能性があります) を取得するために、クライアントは IAudioClient::GetBufferSize メソッドを呼び出します。

エンドポイント バッファーを介してレンダリング データのストリームを移動するために、クライアントは代わりに IAudioRenderClient::GetBuffer メソッドと IAudioRenderClient::ReleaseBuffer メソッドを呼び出します。 クライアントは、エンドポイント バッファー内のデータに一連のデータ パケットとしてアクセスします。 GetBuffer 呼び出しは、クライアントがレンダリング データを格納できるように、次のパケットを取得します。 パケットにデータを書き込んだ後、クライアントは ReleaseBuffer を呼び出して、完成したパケットをレンダリング キューに追加します。

レンダリング バッファーの場合、IAudioClient::GetCurrentPadding メソッドによって報告される埋め込み値は、バッファー内で再生するためにキューに格納されるレンダリング データの量を表します。 レンダリング アプリケーションでは、埋め込み値を使用して、以前に書き込まれたデータのうちオーディオ エンジンがまだバッファーから読み取っていないデータを上書きするリスクなしに、バッファーに安全に書き込むことができる、新しいデータの量を決定できます。 使用可能な領域は、単純にバッファー サイズから埋め込みサイズを引いた値です。 クライアントは、次の GetBuffer 呼び出しで、この使用可能な領域の一部またはすべてを表すパケット サイズを要求できます。

パケットのサイズは、オーディオ フレームで表されます。 PCM ストリーム内のオーディオ フレームは、再生または同時に (クロック ティック) 記録されるサンプルのセットです (ストリーム内のチャネルごとに 1 つのサンプルがセットに含まれています)。 したがって、オーディオ フレームのサイズは、ストリーム内のチャンネル数を乗算したサンプル サイズです。 たとえば、16 ビットのサンプルを含むステレオ (2 チャネル) ストリームのフレーム サイズは 4 バイトです。

次のコード例は、既定のレンダリング デバイスでオーディオ ストリームを再生する方法を示しています。

//-----------------------------------------------------------
// Play an audio stream on the default audio rendering
// device. The PlayAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data to the
// rendering device. The inner 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_IAudioRenderClient = __uuidof(IAudioRenderClient);

HRESULT PlayAudioStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    REFERENCE_TIME hnsActualDuration;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    UINT32 numFramesPadding;
    BYTE *pData;
    DWORD flags = 0;

    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eRender, 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)

    // Tell the audio source which format to use.
    hr = pMySource->SetFormat(pwfx);
    EXIT_ON_ERROR(hr)

    // Get the actual size of the allocated buffer.
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(
                         IID_IAudioRenderClient,
                         (void**)&pRenderClient);
    EXIT_ON_ERROR(hr)

    // Grab the entire buffer for the initial fill operation.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    // Load the initial data into the shared buffer.
    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
    EXIT_ON_ERROR(hr)

    // Calculate the actual duration of the allocated buffer.
    hnsActualDuration = (double)REFTIMES_PER_SEC *
                        bufferFrameCount / pwfx->nSamplesPerSec;

    hr = pAudioClient->Start();  // Start playing.
    EXIT_ON_ERROR(hr)

    // Each loop fills about half of the shared buffer.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Sleep for half the buffer duration.
        Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

        // See how much buffer space is available.
        hr = pAudioClient->GetCurrentPadding(&numFramesPadding);
        EXIT_ON_ERROR(hr)

        numFramesAvailable = bufferFrameCount - numFramesPadding;

        // Grab all the available space in the shared buffer.
        hr = pRenderClient->GetBuffer(numFramesAvailable, &pData);
        EXIT_ON_ERROR(hr)

        // Get next 1/2-second of data from the audio source.
        hr = pMySource->LoadData(numFramesAvailable, pData, &flags);
        EXIT_ON_ERROR(hr)

        hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
        EXIT_ON_ERROR(hr)
    }

    // Wait for last data in buffer to play before stopping.
    Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

    hr = pAudioClient->Stop();  // Stop playing.
    EXIT_ON_ERROR(hr)

Exit:
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pRenderClient)

    return hr;
}

前の例では、PlayAudioStream 関数は、2 つのメンバー関数 LoadData と SetFormat を持つクライアント定義クラス MyAudioSource に属するオブジェクトへのポインターである 1 つのパラメーター、pMySource を受け取ります。 次の理由から、サンプル コードには MyAudioSource の実装は含まれません。

  • どのクラス メンバーも、WASAPI のインターフェイス内のどのメソッドとも直接通信を行いません。
  • このクラスは、クライアントの要件に応じて、さまざまな方法で実装できます。 (たとえば、WAV ファイルからレンダリング データを読み取り、ストリーム形式への変換を即座で実行する場合があります。)

とは言え、2 つの関数の操作に関するいくつかの情報は、この例を理解するのに役立ちます。

LoadData 関数は、指定した数のオーディオ フレーム (最初のパラメーター) を指定したバッファー位置 (2 番目のパラメーター) に書き込みます。 (オーディオ フレームのサイズは、ストリーム内のチャンネル数にサンプル サイズを乗算したものです。) PlayAudioStream 関数は、LoadData を使用して、共有バッファーの一部をオーディオ データで埋めます。 SetFormat 関数は、データに使用する LoadData 関数の形式を指定します。 LoadData 関数は、指定したバッファー位置に少なくとも 1 つのフレームを書き込むことができますが、指定した数のフレームを書き込む前にデータが不足してしまった場合は、残りのフレームに無音を書き込みます。

LoadData は、指定されたバッファー位置への実データの少なくとも 1 つのフレーム (無音ではない) の書き込みに成功する限り、0 から 3 番目のパラメーターを出力します。これは、前のコード例では、flags 変数への出力ポインターです。 LoadData がデータから外れ、指定したバッファー位置に 1 つのフレームを書き込むことができない場合、バッファーに何も書き込まない (無音でもない) と、AUDCLNT_BUFFERFLAGS_SILENT 値が flags 変数に書き込まれます。 flags 変数は、この値を IAudioRenderClient::ReleaseBuffer メソッドに伝達します。このメソッドは、バッファー内の指定されたフレーム数を無音で埋めることで応答します。

IAudioClient::Initialize メソッドへの呼び出しで、前の例の PlayAudioStream 関数は、期間が 1 秒の共有バッファーを要求します。 (割り当てられたバッファーの期間が少し長くなる場合があります。) IAudioRenderClient::GetBuffer メソッドと IAudioRenderClient::ReleaseBuffer メソッドへの最初の呼び出しでは、IAudioClient::Start メソッドを呼び出してバッファーの再生を開始する前に、関数がバッファー全体を埋めます。

メイン ループ内では、関数は 0.5 秒間隔でバッファーの半分を繰り返し埋めます。 メイン ループでの Windows Sleep 関数への各呼び出しの直前に、バッファーが一杯またはほぼ一杯になります。 Sleep 呼び出しが返されると、バッファーの約半分が一杯になります。 LoadData 関数の最後の呼び出し後にループが終了し、flags 変数が AUDCLNT_BUFFERFLAGS_SILENT 値に設定されます。 この時点で、バッファーには少なくとも 1 フレームの実データが含まれており、半秒の実データが含まれている可能性があります。 残りのバッファーには無音が含まれています。 ループに続く Sleep 呼び出しは、残りのデータをすべて再生するのに十分な時間 (0.5 秒) を提供します。 データに続く無音は、IAudioClient::Stop メソッドへの呼び出しがオーディオ ストリームを停止する前に不要なサウンドを防ぎます。 Sleep の詳細については、Windows SDK のドキュメントを参照してください。

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

前のコード例の PlayAudioStream 関数は、CoCreateInstance 関数を呼び出して、システム内のオーディオ エンドポイント デバイスの列挙子を作成します。 呼び出し元のプログラムが COM ライブラリを初期化するために CoCreateInstance 関数または CoInitializeEx 関数を以前に呼び出していない限り、CoCreateInstance 呼び出しは失敗します。 CoCreateInstanceCoCreateInstanceCoInitializeEx の詳細については、Windows SDK のドキュメントを参照してください。

ストリームの管理