ストリームのキャプチャ
クライアントは 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 呼び出しでストリームが閉じられます。
関連トピック