Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
O cliente chama os métodos na interface do IAudioRenderClient para gravar dados de renderização em um buffer de ponto final. Para um fluxo em modo partilhado, o cliente partilha o buffer endpoint com o motor de áudio. Para um fluxo em modo exclusivo, o cliente compartilha o buffer do ponto de extremidade com o dispositivo de áudio. Para solicitar um buffer de endpoint 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 IAudioClient::GetBufferSize método.
Para mover um fluxo de dados de renderização através do buffer de ponto de extremidade, o cliente chama alternadamente o método IAudioRenderClient::GetBuffer e o método IAudioRenderClient::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 para que o cliente possa preenchê-lo com dados de renderização. Depois de gravar os dados no pacote, o cliente chama ReleaseBuffer para adicionar o pacote concluído à fila de renderização.
Para um buffer de renderização, o valor de preenchimento relatado pelo método IAudioClient::GetCurrentPadding representa a quantidade de dados de renderização enfileirados para reprodução no buffer. Uma aplicação de renderização pode usar o valor de preenchimento para determinar a quantidade de novos dados que pode gravar com segurança no buffer, sem correr o risco de sobrescrever dados anteriormente gravados que o motor de áudio ainda não leu do buffer. O espaço disponível é simplesmente o tamanho do buffer menos o tamanho do acolchoamento. O cliente pode solicitar um tamanho de pacote que represente parte ou todo esse espaço disponível em sua próxima chamada GetBuffer.
O tamanho de um pacote é expresso em quadros de áudio. Um quadro de áudio num fluxo PCM é um conjunto de amostras (o conjunto contém uma amostra para cada canal no fluxo) que são reproduzidas ou gravadas ao mesmo tempo (pulso de relógio). Assim, o tamanho de um quadro de áudio é o tamanho da amostra multiplicado pelo número de canais no fluxo. Por exemplo, o tamanho do quadro para um fluxo estéreo (2 canais) com amostras de 16 bits é de quatro bytes.
O exemplo de código a seguir mostra como reproduzir um fluxo de áudio no dispositivo de renderização padrão:
//-----------------------------------------------------------
// 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;
}
No exemplo anterior, a função PlayAudioStream usa um único parâmetro, pMySource, que é um ponteiro para um objeto que pertence a uma classe definida pelo cliente, MyAudioSource, com duas funções de membro, LoadData e SetFormat. O código de exemplo não inclui a implementação de MyAudioSource 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 ler os dados de renderização de um arquivo WAV e executar a conversão instantânea para o formato de fluxo.)
No entanto, algumas informações sobre o funcionamento das duas funções são úteis para entender o exemplo.
A função LoadData grava um número especificado de quadros de áudio (primeiro parâmetro) em um local de buffer especificado (segundo parâmetro). (O tamanho de um quadro de áudio é o número de canais no fluxo multiplicado pelo tamanho da amostra.) A função PlayAudioStream usa LoadData para preencher partes do buffer compartilhado com dados de áudio. A função SetFormat especifica o formato da função LoadData a ser usada para os dados. Se a função LoadData for capaz de gravar pelo menos um quadro no local do buffer especificado, mas ficar sem dados antes de gravar o número especificado de quadros, ela gravará silêncio nos quadros restantes.
Contanto que LoadData consiga gravar pelo menos um quadro de dados reais (não silêncio) no local do buffer especificado, ele produz 0 através de seu terceiro parâmetro, que, no exemplo de código anterior, é um ponteiro de saída para a variável flags. Quando LoadData está sem dados e não pode gravar nem mesmo um único quadro no local do buffer especificado, ele não grava nada no buffer (nem mesmo silêncio) e grava o valor AUDCLNT_BUFFERFLAGS_SILENT na variável flags. A variável flags transmite esse valor para o método IAudioRenderClient::ReleaseBuffer, que responde ao preencher com silêncio o número especificado de quadros no buffer.
Em sua chamada para o IAudioClient::Initialize método, a função PlayAudioStream no exemplo anterior solicita um buffer compartilhado que tem uma duração de um segundo. (O buffer alocado pode ter uma duração um pouco maior.) Nas suas chamadas iniciais para os métodos IAudioRenderClient::GetBuffer e IAudioRenderClient::ReleaseBuffer, a função preenche todo o buffer antes de chamar o método IAudioClient::Start para começar a reproduzir o buffer.
Dentro do loop principal, a função preenche iterativamente metade do buffer em intervalos de meio segundo. Pouco antes de cada chamada para a função Sleepdo Windows, no loop principal, o buffer está cheio ou quase cheio. Quando a chamada Sleep retorna, o buffer está cerca de metade cheio. O loop termina após a chamada final para a função LoadData define a variável flags para o valor AUDCLNT_BUFFERFLAGS_SILENT. Nesse ponto, o buffer contém pelo menos um quadro de dados reais e pode conter até meio segundo de dados reais. O restante do buffer contém silêncio. A chamada Sleep que segue o loop fornece tempo suficiente (meio segundo) para reproduzir todos os dados restantes. O silêncio que segue os dados impede sons indesejados antes que o método IAudioClient::Stop interrompa o fluxo de áudio. Para obter mais informações sobre o Sleep, consulte a documentação do SDK do Windows.
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 a interfaces de serviço que o cliente obteve por meio do método IAudioClient::GetService. A chamada final do Release fecha o fluxo.
A função PlayAudioStream no exemplo de código anterior chama a função CoCreateInstance para criar um enumerador para os dispositivos de ponto de extremidade de áudio no sistema. A menos que o programa que realiza a chamada tenha chamado anteriormente a função CoCreateInstance ou a função CoInitializeEx para inicializar a biblioteca COM, a chamada CoCreateInstance falhará. Para obter mais informações sobre CoCreateInstance, CoCreateInstancee CoInitializeEx, consulte a documentação do SDK do Windows.
Tópicos relacionados