Capturando um fluxo
O cliente chama os métodos na interface IAudioCaptureClient para ler dados capturados de um buffer de ponto de extremidade. O cliente compartilha o buffer de ponto de extremidade com o mecanismo de áudio no modo compartilhado e com o dispositivo de áudio no modo exclusivo. Para solicitar um buffer de ponto de extremidade de um tamanho específico, o cliente chama o método IAudioClient::Initialize . Para obter o tamanho do buffer alocado, que pode ser diferente do tamanho solicitado, o cliente chama o método IAudioClient::GetBufferSize.
Para mover um fluxo de dados capturados pelo buffer de ponto de extremidade, o cliente chama alternadamente o método IAudioCaptureClient::GetBuffer e o método IAudioCaptureClient::ReleaseBuffer. O cliente acessa os dados no buffer de ponto de extremidade como uma série de pacotes de dados. A chamada GetBuffer recupera o próximo pacote de dados capturados do buffer. Depois de ler os dados do pacote, o cliente chama ReleaseBuffer para liberar o pacote e disponibilizá-lo para mais dados capturados.
O tamanho do pacote pode variar de uma chamada GetBuffer para a próxima. Antes de chamar GetBuffer, o cliente tem a opção de chamar o método IAudioCaptureClient::GetNextPacketSize para obter o tamanho do próximo pacote com antecedência. Além disso, o cliente pode chamar o método IAudioClient::GetCurrentPadding para obter a quantidade total de dados capturados que está disponível no buffer. A qualquer instante, o tamanho do pacote é sempre menor ou igual à quantidade total de dados capturados no buffer.
Durante cada passo de processamento, o cliente tem a opção de processar os dados capturados de uma das seguintes maneiras:
- O cliente chama alternadamente GetBuffer e ReleaseBuffer, lendo um pacote com cada par de chamadas, até que GetBuffer retorne AUDCNT_S_BUFFEREMPTY, indicando que o buffer está vazio.
- O cliente chama GetNextPacketSize antes de cada par de chamadas para GetBuffer e ReleaseBuffer até GetNextPacketSize relata um tamanho de pacote de 0, indicando que o buffer está vazio.
As duas técnicas produzem resultados equivalentes.
O exemplo de código a seguir mostra como gravar um fluxo de áudio do dispositivo de captura padrão:
//-----------------------------------------------------------
// 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;
}
No exemplo anterior, a função RecordAudioStream usa um único parâmetro, , que é um ponteiro para um objeto que pertence a uma classe definida pelo cliente, MyAudioSink, com duas funções, pMySink
CopyData e SetFormat. O código de exemplo não inclui a implementação de MyAudioSink porque:
- Nenhum dos membros da classe se comunica diretamente com qualquer um dos métodos nas interfaces no WASAPI.
- A classe pode ser implementada de várias maneiras, dependendo dos requisitos do cliente. (Por exemplo, ele pode gravar os dados de captura em um arquivo WAV.)
No entanto, informações sobre a operação dos dois métodos são úteis para entender o exemplo.
A função CopyData copia um número especificado de quadros de áudio de um local de buffer especificado. A função RecordAudioStream usa a função CopyData para ler e salvar os dados de áudio do buffer compartilhado. A função SetFormat especifica o formato da função CopyData a ser usada para os dados.
Contanto que o objeto MyAudioSink exija dados adicionais, a função CopyData gera o valor FALSE por meio de seu terceiro parâmetro, que, no exemplo de código anterior, é um ponteiro para a variável bDone
. Quando o objeto MyAudioSink tem todos os dados necessários, a função CopyData define bDone
como TRUE, o que faz com que o programa saia do loop na função RecordAudioStream.
A função RecordAudioStream aloca um buffer compartilhado que tem uma duração de um segundo. (O buffer alocado pode ter uma duração um pouco maior.) Dentro do loop principal, a chamada para a função Windows Sleep faz com que o programa aguarde meio segundo. No início de cada chamada de suspensão , o buffer compartilhado está vazio ou quase vazio. No momento em que a chamada de suspensão retorna, o buffer compartilhado está cerca de metade preenchido com dados de captura.
Após a chamada para o método IAudioClient::Initialize, o fluxo permanece aberto até que o cliente libere todas as suas referências à interface IAudioClient e a todas as referências às interfaces de serviço que o cliente obteve por meio do método IAudioClient::GetService. A chamada de lançamento final fecha o fluxo.
Tópicos relacionados