ストリームのレンダリング
クライアントは 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 呼び出しは失敗します。 CoCreateInstance、CoCreateInstance、CoInitializeEx の詳細については、Windows SDK のドキュメントを参照してください。
関連トピック