Partilhar via


Exclusive-Mode Streams

Como explicado anteriormente, se um aplicativo abre um fluxo no modo exclusivo, o aplicativo tem uso exclusivo do dispositivo de ponto final de áudio que reproduz ou grava o fluxo. Em contraste, vários aplicativos podem compartilhar um dispositivo de ponto final 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 outras aplicações e degradar a experiência do utilizador. 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 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 através dos 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 a partir do buffer de ponto final.

Os aplicativos que usam fluxos de modo exclusivo geralmente o fazem porque exigem baixas latências nos caminhos de dados entre os dispositivos de ponto de extremidade de áudio e os threads de aplicativos 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 sucessivas passagens de processamento pelo hardware de áudio. Durante cada passagem, o hardware de áudio processa os novos dados nos buffers de ponto final.

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 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 não conseguir 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 do horário em que o buffer está agendado para reprodução. 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 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 User-Mode Audio Components, os aplicativos que executam operações críticas de tempo podem chamar as funções MMCSS (Multimedia Class Scheduler Service) para aumentar a prioridade do thread sem negar recursos da CPU para aplicativos de prioridade mais baixa. Além disso, o método IAudioClient::Initialize suporta 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 fica disponível no dispositivo de áudio. Ao usar esses recursos, um thread de aplicativo pode reduzir a incerteza sobre quando 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 são mais propensos a suportar o DDI WaveRT. Para aplicações 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 aplicativos 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. WASAPI usa MMCSS para aumentar a prioridade de 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 atribui o nome da tarefa MMCSS "Audio" ao thread. Para obter mais informações sobre as DDIs WaveCyclic, WavePci e WaveRT, consulte a documentação do Windows DDK. Para obter informações sobre como selecionar um período de dispositivo apropriado, consulte IAudioClient::GetDevicePeriod.

Conforme descrito em de 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 sobre fluxos de modo exclusivo. Em vez disso, os aplicativos que gerenciam fluxos de modo exclusivo normalmente usam a interface IAudioEndpointVolume no da API EndpointVolume para controlar os níveis de volume desses fluxos. Para obter informações sobre essa interface, consulte Endpoint Volume Controls.

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 desativar o uso exclusivo do dispositivo, o dispositivo pode ser usado para reproduzir ou gravar apenas fluxos de modo compartilhado.

Se o usuário permitir o uso de modo exclusivo do dispositivo, o usuário também pode controlar se uma solicitação de um aplicativo para usar o dispositivo no modo exclusivo irá antecipar o uso do dispositivo por aplicativos que podem estar atualmente reproduzindo ou gravando fluxos de modo compartilhado através do dispositivo. Se a preempção estiver ativada, 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 streams 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 atualmente através do dispositivo.

Para alterar as configurações de modo exclusivo de um dispositivo de reprodução ou gravação

  1. Clique com o botão direito do rato no ícone do altifalante 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 controlo multimédia do Windows, Mmsys.cpl, a partir de uma janela da Linha de Comandos. Para obter mais informações, consulte Comentários em DEVICE_STATE_XXX constantes.)
  2. Depois que a janela de som for exibida, selecione de reprodução ou Gravação. Em seguida, selecione uma entrada na lista de nomes de dispositivos e clique em Propriedades.
  3. Depois que a janela Propriedades for exibida, clique em Avançado .
  4. 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 desativar o uso exclusivo do dispositivo, desmarque a caixa de seleção.
  5. Se a utilização em modo exclusivo do dispositivo estiver ativada, pode especificar se um pedido de controlo exclusivo do dispositivo terá êxito se o dispositivo estiver atualmente a reproduzir ou a gravar fluxos em modo partilhado. Para dar prioridade aos aplicativos de modo exclusivo sobre os aplicativos de modo compartilhado, marque a caixa rotulada Dar prioridadeaplicativos de modo exclusivo . Para negar prioridade aos aplicativos de modo exclusivo sobre os 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 os 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 suporta 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; Tal deve-se ao facto de os pormenores da sua aplicação dependerem inteiramente dos requisitos do pedido. No entanto, a operação da função GetStreamFormat pode ser descrita de forma simples — ela chama o IAudioClient::IsFormatSupported método uma ou mais vezes para determinar se o dispositivo suporta um formato adequado. Os requisitos do aplicativo ditam quais formatos GetStreamFormat apresenta ao IsFormatSupported método e a ordem em que 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étodoIAudioClient::GetDevicePeriod para obter o período mínimo do 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 "pingue-pongue", 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 através do qual ele receberá notificações quando os buffers estiverem prontos para preenchimento.
  • 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 o função AvSetMmThreadCharacteristics para solicitar que o MMCSS aumente a prioridade do thread no qual 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 final. Entre 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:

  1. Chama o IAudioRenderClient::GetBuffer método para obter o próximo buffer.
  2. Preenche o buffer.
  3. Chama o IAudioRenderClient::ReleaseBuffer método 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 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 à conclusão 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 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 do 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 do seu 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 do que o mínimo. Um período mais longo pode permitir com segurança o uso de uma prioridade de thread mais baixa.

de Gestão de Fluxos