fluxos de Exclusive-Mode
Conforme explicado anteriormente, se um aplicativo abrir um fluxo no modo exclusivo, o aplicativo terá o uso exclusivo do dispositivo de ponto de extremidade de áudio que reproduz ou registra 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 de modo exclusivo a um dispositivo de áudio pode bloquear sons cruciais do sistema, impedir a interoperabilidade com outros aplicativos e, de outra forma, prejudicar 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 em 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 com 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 um exemplo em um buffer de ponto de extremidade até o momento em que o exemplo é ouvido 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 o exemplo desse som do buffer do ponto de extremidade.
Os aplicativos que usam fluxos de modo exclusivo geralmente fazem isso porque exigem latências baixas 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 agendam para serem executados em intervalos periódicos próximos ou iguais ao intervalo periódico que separa as sucessivas passagens 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 tempo ou carregar o sistema com tarefas de alta prioridade concorrentes pode causar uma falha em um fluxo de áudio de baixa latência. Por exemplo, para um fluxo de renderização, uma falha poderá ocorrer se o aplicativo não gravar em um buffer de ponto de extremidade antes que o hardware de áudio leia o buffer ou se o hardware não ler o buffer antes da hora em que o buffer está programado para ser reproduzido. 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 tempo suficientes para evitar falhas em todos os ambientes de destino.
O Windows Vista tem vários recursos para dar suporte a aplicativos que exigem fluxos de áudio de baixa latência. Conforme discutido em User-Mode componentes de áudio, aplicativos que executam operações críticas em tempo podem chamar as funções MMCSS (Serviço de Agendador de Classe Multimídia) para aumentar a prioridade do thread sem negar recursos de CPU a aplicativos de menor prioridade. Além disso, o método IAudioClient::Initialize dá suporte a um sinalizador de AUDCLNT_STREAMFLAGS_EVENTCALLBACK que permite que o thread de manutençã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 DDI (interface de driver de dispositivo) WaveCyclic ou WavePci, enquanto os drivers para adaptadores de áudio mais recentes são mais propensos a dar suporte à 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. Por outro lado, 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 (anexados a pacotes de solicitação de E/S do sistema ou IRPs) entre buffers de aplicativos e buffers de hardware. Além disso, os dispositivos de áudio USB dependem do software do sistema para transportar dados entre buffers de aplicativo 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 o 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 de menos de 10 milissegundos, 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, WASAPI atribuirá o nome da tarefa MMCSS "Audio" ao thread. Para obter mais informações sobre os 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, WASAPI fornece as interfaces ISimpleAudioVolume, IChannelAudioVolumee 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 em 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 de modo exclusivo do dispositivo, o dispositivo poderá ser usado para reproduzir ou gravar somente fluxos de modo compartilhado.
Se o usuário habilitar o uso de modo exclusivo do dispositivo, o usuário também poderá controlar se uma solicitação de um aplicativo para usar o dispositivo no modo exclusivo irá prever o uso do dispositivo por aplicativos que atualmente podem 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 terá êxito 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 desabilitada, uma solicitação de um aplicativo para assumir o controle exclusivo do dispositivo terá êxito 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 preempõe qualquer fluxo de modo compartilhado que esteja sendo reproduzido ou gravado no 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, que está 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, em uma janela do Prompt de Comando. Para obter mais informações, consulte Comentários em DEVICE_STATE_XXX Constantes.)
- Depois que a janela Som for exibida, selecione de Reprodução ou de Gravação. Em seguida, selecione uma entrada na lista de nomes de dispositivo e clique em Propriedades.
- Depois que a janela propriedades do for exibida, clique em Advanced.
- Para permitir que os aplicativos usem o dispositivo no modo exclusivo, marque a caixa rotulada Permitir que os aplicativos assumam o controle exclusivo deste dispositivo. Para desabilitar o uso de modo exclusivo do dispositivo, desmarque a caixa de seleção.
- Se o uso de modo exclusivo do dispositivo estiver habilitado, você poderá especificar se uma solicitação de controle exclusivo do dispositivo terá êxito se o dispositivo estiver reproduzindo ou gravando fluxos de modo compartilhado no momento. Para dar prioridade a aplicativos de modo exclusivo em aplicativos de modo compartilhado, marque a caixa rotulada Dar prioridade a aplicativos de modo exclusivo. Para negar prioridade de aplicativos de modo exclusivo em 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 stream.
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 dá suporte a um formato de fluxo de modo exclusivo adequado para uso pelo aplicativo. O código da função GetStreamFormat não aparece no exemplo de código; isso ocorre 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 dá suporte a um formato adequado. Os requisitos do aplicativo determinam quais formatos GetStreamFormat apresenta ao método IsFormatSupported e a ordem na qual ele os apresenta. Para obter mais informações sobre IsFormatSupported, consulte Device Formats.
Após a chamada GetStreamFormat, a função PlayExclusiveStream chama o método IAudioClient::GetDevicePeriod para obter o período mínimo de dispositivo compatível com o 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 deles igual à duração mínima. Posteriormente, 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 serem preenchidos.
- 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 o PlayExclusiveStream é executado. (Quando o fluxo para de ser executado, a chamada de função AvRevertMmThreadCharacteristics restaura a prioridade de 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 enquanto-loop no exemplo de código anterior preenche um buffer de ponto de extremidade. Entre iterações, o WaitForSingleObject chamada de função aguarda que o identificador de evento seja sinalizado. Quando o identificador é sinalizado, 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 evento será vinculada às notificações de transferência de 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 evento está vinculada às conclusões dos IRPs que transferem dados do buffer de aplicativo para o buffer de hardware.
O exemplo de código anterior envia por push o hardware de áudio e o sistema de 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 ao qual o hardware de áudio dará suporte. Em segundo lugar, para garantir que o thread seja executado de forma confiável em cada período de 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 do aplicativo podem ser relaxados sem comprometer sua utilidade. Por exemplo, o aplicativo pode agendar seu thread de manutenção de buffer para usar um período maior 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