Transmissões em modo exclusivo
Como explicado anteriormente, se um aplicativo abre um fluxo no modo exclusivo, o aplicativo tem uso exclusivo do dispositivo de ponto de extremidade de áudio que reproduz ou grava o fluxo. Por outro lado, vários aplicativos podem compartilhar um dispositivo de ponto de extremidade de áudio abrindo fluxos de modo compartilhado no dispositivo.
O acesso em modo exclusivo a um dispositivo de áudio pode bloquear sons cruciais do sistema, impedir a interoperabilidade com outros aplicativos e, de outra forma, degradar a experiência do usuário. Para atenuar esses problemas, um aplicativo com um fluxo de modo exclusivo normalmente abre mão do controle do dispositivo de áudio quando o aplicativo não é o processo de primeiro plano ou não está transmitindo ativamente.
A latência de fluxo é o atraso inerente ao caminho de dados que conecta o buffer de ponto de extremidade de um aplicativo a um dispositivo de ponto de extremidade de áudio. Para um fluxo de renderização, a latência é o atraso máximo desde o momento em que um aplicativo grava uma amostra em um buffer de ponto de extremidade até o momento em que a amostra é ouvida pelos alto-falantes. Para um fluxo de captura, a latência é o atraso máximo desde o momento em que um som entra no microfone até o momento em que um aplicativo pode ler a amostra desse som do buffer de ponto de extremidade.
Os aplicativos que usam fluxos de modo exclusivo geralmente fazem isso porque exigem baixas latências nos caminhos de dados entre os dispositivos de ponto de extremidade de áudio e os threads de aplicativo que acessam os buffers de ponto de extremidade. Normalmente, esses threads são executados com prioridade relativamente alta e se programam para serem executados em intervalos periódicos próximos ou iguais ao intervalo periódico que separa sucessivos passos de processamento pelo hardware de áudio. Durante cada passagem, o hardware de áudio processa os novos dados nos buffers de ponto de extremidade.
Para obter as menores latências de fluxo, um aplicativo pode exigir hardware de áudio especial e um sistema de computador levemente carregado. Conduzir o hardware de áudio além de seus limites de temporização ou carregar o sistema com tarefas concorrentes de alta prioridade pode causar uma falha em um fluxo de áudio de baixa latência. Por exemplo, para um fluxo de renderização, uma falha pode ocorrer se o aplicativo falhar ao gravar em um buffer de ponto de extremidade antes que o hardware de áudio leia o buffer ou se o hardware não conseguir ler o buffer antes da hora em que o buffer está programado para reproduzir. Normalmente, um aplicativo que se destina a ser executado em uma ampla variedade de hardware de áudio e em uma ampla gama de sistemas deve relaxar seus requisitos de temporização o suficiente para evitar falhas em todos os ambientes de destino.
O Windows Vista tem vários recursos para oferecer suporte a aplicativos que exigem fluxos de áudio de baixa latência. Conforme discutido em Componentes de áudio no modo de usuário, os aplicativos que executam operações de tempo crítico podem chamar as funções do MMCSS (Serviço de Agendador de Classe Multimídia) para aumentar a prioridade do thread sem negar recursos da CPU a aplicativos de prioridade mais baixa. Além disso, o método IAudioClient::Initialize oferece suporte a um sinalizador de AUDCLNT_STREAMFLAGS_EVENTCALLBACK que permite que o thread de serviço de buffer de um aplicativo agende sua execução para ocorrer quando um novo buffer estiver disponível no dispositivo de áudio. Usando esses recursos, um thread de aplicativo pode reduzir a incerteza sobre quando ele será executado, diminuindo assim o risco de falhas em um fluxo de áudio de baixa latência.
Os drivers para adaptadores de áudio mais antigos provavelmente usarão a interface de driver de dispositivo (DDI) WaveCyclic ou WavePci, enquanto os drivers para adaptadores de áudio mais recentes provavelmente suportarão o DDI WaveRT. Para aplicativos de modo exclusivo, os drivers WaveRT podem fornecer melhor desempenho do que os drivers WaveCyclic ou WavePci, mas os drivers WaveRT exigem recursos de hardware adicionais. Esses recursos incluem a capacidade de compartilhar buffers de hardware diretamente com aplicativos. Com o compartilhamento direto, nenhuma intervenção do sistema é necessária para transferir dados entre um aplicativo de modo exclusivo e o hardware de áudio. Em contraste, os drivers WaveCyclic e WavePci são adequados para adaptadores de áudio mais antigos e menos capazes. Esses adaptadores dependem do software do sistema para transportar blocos de dados (conectados a pacotes de solicitação de E/S do sistema ou IRPs) entre buffers de aplicativo e buffers de hardware. Além disso, os dispositivos de áudio USB dependem do software do sistema para transportar dados entre buffers de aplicativos e buffers de hardware. Para melhorar o desempenho de aplicativos de modo exclusivo que se conectam a dispositivos de áudio que dependem do sistema para transporte de dados, o WASAPI aumenta automaticamente a prioridade dos threads do sistema que transferem dados entre os aplicativos e o hardware. O WASAPI usa MMCSS para aumentar a prioridade do thread. No Windows Vista, se um thread do sistema gerencia o transporte de dados para um fluxo de reprodução de áudio de modo exclusivo com um formato PCM e um período de dispositivo inferior a 10 milissegundos, o WASAPI atribui o nome da tarefa MMCSS "Pro Audio" ao thread. Se o período do dispositivo do fluxo for maior ou igual a 10 milissegundos, o WASAPI atribuirá o nome da tarefa MMCSS "Áudio" ao thread. Para obter mais informações sobre as DDIs WaveCyclic, WavePci e WaveRT, consulte a documentação do DDK do Windows. Para obter informações sobre como selecionar um período de dispositivo apropriado, consulte IAudioClient::GetDevicePeriod.
Conforme descrito em Controles de Volume de Sessão, o WASAPI fornece as interfaces ISimpleAudioVolume, IChannelAudioVolume e IAudioStreamVolume para controlar os níveis de volume de fluxos de áudio de modo compartilhado. No entanto, os controles nessas interfaces não têm efeito sobre fluxos de modo exclusivo. Em vez disso, os aplicativos que gerenciam fluxos de modo exclusivo normalmente usam a interface IAudioEndpointVolume na API EndpointVolume para controlar os níveis de volume desses fluxos. Para obter informações sobre essa interface, consulte Controles de volume de ponto de extremidade.
Para cada dispositivo de reprodução e dispositivo de captura no sistema, o usuário pode controlar se o dispositivo pode ser usado no modo exclusivo. Se o usuário desabilitar o uso exclusivo do dispositivo, ele poderá ser usado para reproduzir ou gravar apenas fluxos no modo compartilhado.
Se o usuário habilitar o uso do dispositivo em modo exclusivo, ele também poderá controlar se uma solicitação de um aplicativo para usar o dispositivo no modo exclusivo impedirá o uso do dispositivo por aplicativos que possam estar reproduzindo ou gravando fluxos de modo compartilhado por meio do dispositivo. Se a preempção estiver habilitada, uma solicitação de um aplicativo para assumir o controle exclusivo do dispositivo será bem-sucedida se o dispositivo não estiver em uso no momento ou se o dispositivo estiver sendo usado no modo compartilhado, mas a solicitação falhará se outro aplicativo já tiver controle exclusivo do dispositivo. Se a preempção estiver desativada, uma solicitação de um aplicativo para assumir o controle exclusivo do dispositivo será bem-sucedida se o dispositivo não estiver em uso no momento, mas a solicitação falhará se o dispositivo já estiver sendo usado no modo compartilhado ou no modo exclusivo.
No Windows Vista, as configurações padrão para um dispositivo de ponto de extremidade de áudio são as seguintes:
- O dispositivo pode ser usado para reproduzir ou gravar fluxos de modo exclusivo.
- Uma solicitação para usar um dispositivo para reproduzir ou gravar um fluxo de modo exclusivo antecipa qualquer fluxo de modo compartilhado que esteja sendo reproduzido ou gravado no momento por meio do dispositivo.
Para alterar as configurações de modo exclusivo de um dispositivo de reprodução ou gravação
- Clique com o botão direito do mouse no ícone do alto-falante na área de notificação, localizada no lado direito da barra de tarefas, e selecione Dispositivos de reprodução ou Dispositivos de gravação. (Como alternativa, execute o painel de controle multimídia do Windows, Mmsys.cpl, a partir de uma janela do Prompt de Comando. Para obter mais informações, consulte Comentários em constantes DEVICE_STATE_XXX.)
- Depois que a janela Som for exibida, selecione Reprodução ou Gravação. Em seguida, selecione uma entrada na lista de nomes de dispositivos e clique em Propriedades.
- Depois que a janela Propriedades for exibida, clique em Avançado.
- Para permitir que os aplicativos usem o dispositivo no modo exclusivo, marque a caixa Permitir que os aplicativos assumam o controle exclusivo desse dispositivo. Para desativar o uso exclusivo do dispositivo, desmarque a caixa de seleção.
- Se o uso em modo exclusivo do dispositivo estiver habilitado, você poderá especificar se uma solicitação de controle exclusivo do dispositivo será bem-sucedida se o dispositivo estiver sendo reproduzido ou gravando fluxos de modo compartilhado. Para dar prioridade aos aplicativos de modo exclusivo em relação aos aplicativos de modo compartilhado, marque a caixa Conceder prioridade aos aplicativos de modo exclusivo. Para negar prioridade a aplicativos de modo exclusivo sobre aplicativos de modo compartilhado, desmarque a caixa de seleção.
O exemplo de código a seguir mostra como reproduzir um fluxo de áudio de baixa latência em um dispositivo de renderização de áudio configurado para uso no modo exclusivo:
//-----------------------------------------------------------
// Play an exclusive-mode stream on the default audio
// rendering device. The PlayExclusiveStream function uses
// event-driven buffering and MMCSS to play the stream at
// the minimum latency supported by the device.
//-----------------------------------------------------------
// 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 PlayExclusiveStream(MyAudioSource *pMySource)
{
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = 0;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDevice *pDevice = NULL;
IAudioClient *pAudioClient = NULL;
IAudioRenderClient *pRenderClient = NULL;
WAVEFORMATEX *pwfx = NULL;
HANDLE hEvent = NULL;
HANDLE hTask = NULL;
UINT32 bufferFrameCount;
BYTE *pData;
DWORD flags = 0;
DWORD taskIndex = 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)
// Call a helper function to negotiate with the audio
// device for an exclusive-mode stream format.
hr = GetStreamFormat(pAudioClient, &pwfx);
EXIT_ON_ERROR(hr)
// Initialize the stream to play at the minimum latency.
hr = pAudioClient->GetDevicePeriod(NULL, &hnsRequestedDuration);
EXIT_ON_ERROR(hr)
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration,
hnsRequestedDuration,
pwfx,
NULL);
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
// Align the buffer if needed, see IAudioClient::Initialize() documentation
UINT32 nFrames = 0;
hr = pAudioClient->GetBufferSize(&nFrames);
EXIT_ON_ERROR(hr)
hnsRequestedDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC / pwfx->nSamplesPerSec * nFrames + 0.5);
hr = pAudioClient->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
hnsRequestedDuration,
hnsRequestedDuration,
pwfx,
NULL);
}
EXIT_ON_ERROR(hr)
// Tell the audio source which format to use.
hr = pMySource->SetFormat(pwfx);
EXIT_ON_ERROR(hr)
// Create an event handle and register it for
// buffer-event notifications.
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hEvent == NULL)
{
hr = E_FAIL;
goto Exit;
}
hr = pAudioClient->SetEventHandle(hEvent);
EXIT_ON_ERROR(hr);
// Get the actual size of the two allocated buffers.
hr = pAudioClient->GetBufferSize(&bufferFrameCount);
EXIT_ON_ERROR(hr)
hr = pAudioClient->GetService(
IID_IAudioRenderClient,
(void**)&pRenderClient);
EXIT_ON_ERROR(hr)
// To reduce latency, load the first buffer with data
// from the audio source before starting the stream.
hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
EXIT_ON_ERROR(hr)
hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
EXIT_ON_ERROR(hr)
hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
EXIT_ON_ERROR(hr)
// Ask MMCSS to temporarily boost the thread priority
// to reduce glitches while the low-latency stream plays.
hTask = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
if (hTask == NULL)
{
hr = E_FAIL;
EXIT_ON_ERROR(hr)
}
hr = pAudioClient->Start(); // Start playing.
EXIT_ON_ERROR(hr)
// Each loop fills one of the two buffers.
while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
{
// Wait for next buffer event to be signaled.
DWORD retval = WaitForSingleObject(hEvent, 2000);
if (retval != WAIT_OBJECT_0)
{
// Event handle timed out after a 2-second wait.
pAudioClient->Stop();
hr = ERROR_TIMEOUT;
goto Exit;
}
// Grab the next empty buffer from the audio device.
hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
EXIT_ON_ERROR(hr)
// Load the buffer with data from the audio source.
hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
EXIT_ON_ERROR(hr)
hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
EXIT_ON_ERROR(hr)
}
// Wait for the last buffer to play before stopping.
Sleep((DWORD)(hnsRequestedDuration/REFTIMES_PER_MILLISEC));
hr = pAudioClient->Stop(); // Stop playing.
EXIT_ON_ERROR(hr)
Exit:
if (hEvent != NULL)
{
CloseHandle(hEvent);
}
if (hTask != NULL)
{
AvRevertMmThreadCharacteristics(hTask);
}
CoTaskMemFree(pwfx);
SAFE_RELEASE(pEnumerator)
SAFE_RELEASE(pDevice)
SAFE_RELEASE(pAudioClient)
SAFE_RELEASE(pRenderClient)
return hr;
}
No exemplo de código anterior, a função PlayExclusiveStream é executada no thread do aplicativo que atende aos buffers de ponto de extremidade enquanto um fluxo de renderização está sendo reproduzido. A função usa um único parâmetro, pMySource, que é um ponteiro para um objeto que pertence a uma classe definida pelo cliente, MyAudioSource. Essa classe tem duas funções de membro, LoadData e SetFormat, que são chamadas no exemplo de código. MyAudioSource é descrito em Renderizando um fluxo.
A função PlayExclusiveStream chama uma função auxiliar, GetStreamFormat, que negocia com o dispositivo de renderização padrão para determinar se o dispositivo oferece suporte a um formato de fluxo de modo exclusivo adequado para uso pelo aplicativo. O código para a função GetStreamFormat não aparece no exemplo de código; Isso porque os detalhes de sua implementação dependem inteiramente dos requisitos do aplicativo. No entanto, a operação da função GetStreamFormat pode ser descrita de forma simples — ela chama o método IAudioClient::IsFormatSupported uma ou mais vezes para determinar se o dispositivo oferece suporte a um formato adequado. Os requisitos do aplicativo ditam quais formatos GetStreamFormat apresenta para o método IsFormatSupported e a ordem em que ele os apresenta. Para obter mais informações sobre IsFormatSupported, consulte Formatos de dispositivo.
Após a chamada GetStreamFormat, a função PlayExclusiveStream chama o método IAudioClient::GetDevicePeriod para obter o período mínimo de dispositivo suportado pelo hardware de áudio. Em seguida, a função chama o método IAudioClient::Initialize para solicitar uma duração de buffer igual ao período mínimo. Se a chamada for bem-sucedida, o método Initialize alocará dois buffers de ponto de extremidade, cada um dos quais é igual em duração ao período mínimo. Mais tarde, quando o fluxo de áudio começar a ser executado, o aplicativo e o hardware de áudio compartilharão os dois buffers de forma "ping-pong" — ou seja, enquanto o aplicativo grava em um buffer, o hardware lê do outro buffer.
Antes de iniciar o fluxo, a função PlayExclusiveStream faz o seguinte:
- Cria e registra o identificador de evento por meio do qual ele receberá notificações quando os buffers estiverem prontos para preencher.
- Preenche o primeiro buffer com dados da fonte de áudio para reduzir o atraso de quando o fluxo começa a ser executado até quando o som inicial é ouvido.
- Chama a função AvSetMmThreadCharacteristics para solicitar que o MMCSS aumente a prioridade do thread no qual PlayExclusiveStream é executado. (Quando o fluxo para de ser executado, o A chamada de função AvRevertMmThreadCharacteristics restaura a prioridade do thread original.)
Para obter mais informações sobre AvSetMmThreadCharacteristics e AvRevertMmThreadCharacteristics, consulte a documentação do SDK do Windows.
Enquanto o fluxo está em execução, cada iteração do while-loop no exemplo de código anterior preenche um buffer de ponto de extremidade. Entre as iterações, a chamada de função WaitForSingleObject aguarda que o identificador de evento seja sinalizado. Quando a alça é sinalizada, o corpo do loop faz o seguinte:
- Chama o método IAudioRenderClient::GetBuffer para obter o próximo buffer.
- Preenche o buffer.
- Chama o método IAudioRenderClient::ReleaseBuffer para liberar o buffer.
Para obter mais informações sobre WaitForSingleObject, consulte a documentação do SDK do Windows.
Se o adaptador de áudio for controlado por um driver WaveRT, a sinalização do identificador de eventos estará vinculada às notificações de transferência DMA do hardware de áudio. Para um dispositivo de áudio USB ou para um dispositivo de áudio controlado por um driver WaveCyclic ou WavePci, a sinalização do identificador de eventos está vinculada às conclusões dos IRPs que transferem dados do buffer do aplicativo para o buffer de hardware.
O exemplo de código anterior empurra o hardware de áudio e o sistema do computador para seus limites de desempenho. Primeiro, para reduzir a latência do fluxo, o aplicativo agenda seu thread de manutenção de buffer para usar o período mínimo de dispositivo que o hardware de áudio suportará. Em segundo lugar, para garantir que o thread seja executado de forma confiável dentro de cada período do dispositivo, a chamada de função AvSetMmThreadCharacteristics define o parâmetro TaskName como "Pro Audio", que é, no Windows Vista, o nome da tarefa padrão com a prioridade mais alta. Considere se os requisitos de tempo de seu aplicativo podem ser relaxados sem comprometer sua utilidade. Por exemplo, o aplicativo pode agendar seu thread de serviço de buffer para usar um período maior do que o mínimo. Um período mais longo pode permitir com segurança o uso de uma prioridade de thread mais baixa.
Tópicos relacionados