捕获流
客户端调用 IAudioCaptureClient 接口中的方法,从终结点缓冲区读取捕获到的数据。 客户端在共享模式下与音频引擎共享终结点缓冲区,在独享模式下与音频设备共享终结点缓冲区。 要请求特定大小的终结点缓冲区,客户端会调用 IAudioClient::Initialize 方法。 要获取已分配缓冲区的大小(可能与请求的大小不同),客户端会调用 IAudioClient::GetBufferSize 方法。
要通过终结点缓冲区来移动捕获的数据流,客户端会交替调用 IAudioCaptureClient::GetBuffer 方法和 IAudioCaptureClient::ReleaseBuffer 方法。 客户端会以一系列数据包的形式访问终结点缓冲区中的数据。 GetBuffer 调用将从缓冲区检索下一个捕获的数据包。 从数据包中读取数据后,客户端会调用 ReleaseBuffer 以释放数据包,并使其可用于更多捕获的数据。
数据包大小可能会随着 GetBuffer 调用的不同而变化。 在调用 GetBuffer 之前,客户端可以选择调用 IAudioCaptureClient::GetNextPacketSize 方法,以便提前获取下一个数据包的大小。 此外,客户端还可以调用 IAudioClient::GetCurrentPadding 方法来获取缓冲区中可用的捕获数据总量。 在任何时刻,数据包大小总是会小于或等于缓冲区中捕获的数据总量。
在每次处理传递过程中,客户端都可以选择以下列方式之一来处理所捕获的数据:
- 客户端交替调用 GetBuffer 和 ReleaseBuffer,每调用一次读取一个数据包,直到 GetBuffer 返回 AUDCNT_S_BUFFEREMPTY,表示缓冲区已空。
- 在在每次调用 GetBuffer 和 ReleaseBuffer 之前,客户端都会调用 GetNextPacketSize,直到 GetNextPacketSize 报告数据包大小为 0,表明缓冲区已为空。
这两种技术产生的结果相当。
下面的代码示例显示了如何从默认捕获设备录制音频流:
//-----------------------------------------------------------
// 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 函数接收一个参数 pMySink
,该参数是指向属于客户端定义的类 MyAudioSink 的对象的指针,而该类包含两个函数:CopyData 和 SetFormat。 示例代码中不包括 MyAudioSink 的实现,这是因为:
- 没有一个类成员可直接与 WASAPI 接口中的任何方法通信。
- 该类能以多种方式实现,具体取决于客户端的要求。 (例如,它可以将捕获数据写入 WAV 文件。)
但是,有关这两种方法操作的信息对于理解示例还是很有用的。
CopyData 函数从指定的缓冲区位置复制指定数量的音频帧。 RecordAudioStream 函数使用 CopyData 函数从共享缓冲区读取和保存音频数据。 SetFormat 函数指定 CopyData 函数要使用的数据格式。
只要 MyAudioSink 对象需要额外数据,CopyData 函数就会通过其第三个参数输出值 FALSE,在前面的代码示例中,这个参数是指向变量 bDone
的指针。 当 MyAudioSink 对象获得所需的全部数据后,CopyData 函数会将 bDone
设置为 TRUE,从而使程序退出 RecordAudioStream 函数的循环。
RecordAudioStream 函数分配了一个持续时间为一秒的共享缓冲区。 (已分配的缓冲区可能持续时间稍长。)在主循环中,调用 Windows Sleep 函数会让程序等待半秒。 在每次 Sleep 调用开始时,共享缓冲区为空或几乎为空。 当 Sleep 调用返回时,共享缓冲区已填充了一半的捕获数据。
调用 IAudioClient::Initialize 方法后,数据流将保持打开状态,直到客户端释放对 IAudioClient 接口的所有引用,以及对客户端通过 IAudioClient::GetService 方法获得的服务接口的所有引用。 最后的 Release 调用将关闭流。
相关主题