flux Exclusive-Mode

Comme expliqué précédemment, si une application ouvre un flux en mode exclusif, l’application utilise exclusivement l’appareil de point de terminaison audio qui lit ou enregistre le flux. En revanche, plusieurs applications peuvent partager un appareil de point de terminaison audio en ouvrant des flux en mode partagé sur l’appareil.

L’accès en mode exclusif à un périphérique audio peut bloquer les sons système essentiels, empêcher l’interopérabilité avec d’autres applications et dégrader l’expérience utilisateur. Pour atténuer ces problèmes, une application avec un flux en mode exclusif abandonne généralement le contrôle du périphérique audio lorsque l’application n’est pas le processus de premier plan ou n’est pas activement diffusée.

La latence de flux est le délai inhérent au chemin des données qui connecte la mémoire tampon de point de terminaison d’une application à un appareil de point de terminaison audio. Pour un flux de rendu, la latence est le délai maximal entre le moment où une application écrit un exemple dans une mémoire tampon de point de terminaison et le moment où l’exemple est entendu par les haut-parleurs. Pour un flux de capture, la latence est le délai maximal entre le moment où un son entre dans le microphone et le moment où une application peut lire l’exemple pour ce son à partir de la mémoire tampon du point de terminaison.

Les applications qui utilisent des flux en mode exclusif le font souvent, car elles nécessitent de faibles latences dans les chemins de données entre les périphériques de point de terminaison audio et les threads d’application qui accèdent aux mémoires tampons de point de terminaison. En règle générale, ces threads s’exécutent avec une priorité relativement élevée et se planifient pour s’exécuter à des intervalles périodiques proches ou identiques à l’intervalle périodique qui sépare les passes de traitement successives par le matériel audio. Pendant chaque passage, le matériel audio traite les nouvelles données dans les mémoires tampons de point de terminaison.

Pour obtenir les plus petites latences de flux, une application peut nécessiter à la fois du matériel audio spécial et un système informatique légèrement chargé. Conduire le matériel audio au-delà de ses limites de minutage ou charger le système avec des tâches de haute priorité concurrentes peut entraîner un problème dans un flux audio à faible latence. Par exemple, pour un flux de rendu, un problème peut se produire si l’application ne parvient pas à écrire dans une mémoire tampon de point de terminaison avant que le matériel audio ne lise la mémoire tampon, ou si le matériel ne parvient pas à lire la mémoire tampon avant l’heure de lecture planifiée de la mémoire tampon. En règle générale, une application destinée à s’exécuter sur une grande variété de matériel audio et dans un large éventail de systèmes doit assouplir suffisamment ses exigences de minutage pour éviter les problèmes dans tous les environnements cibles.

Windows Vista dispose de plusieurs fonctionnalités pour prendre en charge les applications qui nécessitent des flux audio à faible latence. Comme indiqué dans Composants audio en mode utilisateur, les applications qui effectuent des opérations temporelles critiques peuvent appeler les fonctions MMCSS (Multimedia Class Scheduler Service) pour augmenter la priorité des threads sans refuser des ressources processeur aux applications de priorité inférieure. En outre, la méthode IAudioClient::Initialize prend en charge un indicateur de AUDCLNT_STREAMFLAGS_EVENTCALLBACK qui permet au thread de maintenance de la mémoire tampon d’une application de planifier son exécution lorsqu’une nouvelle mémoire tampon devient disponible à partir du périphérique audio. En utilisant ces fonctionnalités, un thread d’application peut réduire l’incertitude quant au moment où il s’exécutera, réduisant ainsi le risque de problèmes dans un flux audio à faible latence.

Les pilotes des cartes audio plus anciennes sont susceptibles d’utiliser l’interface de pilote de périphérique WaveCyclique ou WavePci, tandis que les pilotes des adaptateurs audio plus récents sont plus susceptibles de prendre en charge waveRT DDI. Pour les applications en mode exclusif, les pilotes WaveRT peuvent fournir de meilleures performances que les pilotes WaveCyclique ou WavePci, mais les pilotes WaveRT nécessitent des fonctionnalités matérielles supplémentaires. Ces fonctionnalités incluent la possibilité de partager des mémoires tampons matérielles directement avec les applications. Avec le partage direct, aucune intervention du système n’est nécessaire pour transférer des données entre une application en mode exclusif et le matériel audio. En revanche, les pilotes WaveCyclique et WavePci conviennent aux adaptateurs audio plus anciens et moins performants. Ces adaptateurs s’appuient sur le logiciel système pour transporter des blocs de données (attachés à des paquets de demandes d’E/S système ou IRP) entre les mémoires tampons d’application et les mémoires tampons matérielles. En outre, les périphériques audio USB s’appuient sur le logiciel système pour transporter les données entre les mémoires tampons d’application et les mémoires tampons matérielles. Pour améliorer les performances des applications en mode exclusif qui se connectent à des périphériques audio qui s’appuient sur le système pour le transport de données, WASAPI augmente automatiquement la priorité des threads système qui transfèrent des données entre les applications et le matériel. WASAPI utilise MMCSS pour augmenter la priorité du thread. Dans Windows Vista, si un thread système gère le transport de données pour un flux de lecture audio en mode exclusif avec un format PCM et une période d’appareil inférieure à 10 millisecondes, WASAPI affecte le nom de tâche MMCSS « Pro Audio » au thread. Si la période de l’appareil du flux est supérieure ou égale à 10 millisecondes, WASAPI affecte le nom de tâche MMCSS « Audio » au thread. Pour plus d’informations sur les DDIs WaveCyclique, WavePci et WaveRT, consultez la documentation du DDK Windows. Pour plus d’informations sur la sélection d’une période d’appareil appropriée, consultez IAudioClient::GetDevicePeriod.

Comme décrit dans Contrôles de volume de session, WASAPI fournit les interfaces ISimpleAudioVolume, IChannelAudioVolume et IAudioStreamVolume pour contrôler les niveaux de volume des flux audio en mode partagé. Toutefois, les contrôles de ces interfaces n’ont aucun effet sur les flux en mode exclusif. Au lieu de cela, les applications qui gèrent les flux en mode exclusif utilisent généralement l’interface IAudioEndpointVolume dans l’API EndpointVolume pour contrôler les niveaux de volume de ces flux. Pour plus d’informations sur cette interface, consultez Endpoint Volume Controls.

Pour chaque périphérique de lecture et de capture dans le système, l’utilisateur peut contrôler si l’appareil peut être utilisé en mode exclusif. Si l’utilisateur désactive l’utilisation en mode exclusif de l’appareil, l’appareil peut être utilisé pour lire ou enregistrer uniquement des flux en mode partagé.

Si l’utilisateur active l’utilisation de l’appareil en mode exclusif, il peut également contrôler si une demande d’une application d’utiliser l’appareil en mode exclusif préempte l’utilisation de l’appareil par les applications qui peuvent actuellement lire ou enregistrer des flux en mode partagé via l’appareil. Si la préemption est activée, une demande d’une application pour prendre le contrôle exclusif de l’appareil réussit si l’appareil n’est actuellement pas utilisé, ou si l’appareil est utilisé en mode partagé, mais la demande échoue si une autre application a déjà le contrôle exclusif de l’appareil. Si la préemption est désactivée, une demande d’une application pour prendre le contrôle exclusif de l’appareil réussit si l’appareil n’est pas actuellement utilisé, mais la demande échoue si l’appareil est déjà utilisé en mode partagé ou en mode exclusif.

Dans Windows Vista, les paramètres par défaut d’un appareil de point de terminaison audio sont les suivants :

  • L’appareil peut être utilisé pour lire ou enregistrer des flux en mode exclusif.
  • Une demande d’utilisation d’un appareil pour lire ou enregistrer un flux en mode exclusif préempte tout flux en mode partagé en cours de lecture ou d’enregistrement via l’appareil.

Pour modifier les paramètres en mode exclusif d’un périphérique de lecture ou d’enregistrement

  1. Cliquez avec le bouton droit sur l’icône du haut-parleur dans la zone de notification, située sur le côté droit de la barre des tâches, puis sélectionnez Périphériques de lecture ou Périphériques d’enregistrement. (Vous pouvez également exécuter le panneau de configuration multimédia Windows, Mmsys.cpl, à partir d’une fenêtre d’invite de commandes. Pour plus d’informations, consultez Remarques dans DEVICE_STATE_XXX constantes.)
  2. Une fois la fenêtre Son affichée, sélectionnez Lecture ou Enregistrement. Ensuite, sélectionnez une entrée dans la liste des noms d’appareils, puis cliquez sur Propriétés.
  3. Une fois la fenêtre Propriétés affichée, cliquez sur Avancé.
  4. Pour permettre aux applications d’utiliser l’appareil en mode exclusif, case activée la zone intitulée Autoriser les applications à prendre le contrôle exclusif de cet appareil. Pour désactiver l’utilisation en mode exclusif de l’appareil, désactivez la zone case activée.
  5. Si l’utilisation en mode exclusif de l’appareil est activée, vous pouvez spécifier si une demande de contrôle exclusif de l’appareil réussit si l’appareil est en train de lire ou d’enregistrer des flux en mode partagé. Pour donner la priorité aux applications en mode exclusif par rapport aux applications en mode partagé, case activée la zone intitulée Priorité Donner la priorité aux applications en mode exclusif. Pour refuser la priorité des applications en mode exclusif par rapport aux applications en mode partagé, désactivez la zone case activée.

L’exemple de code suivant montre comment lire un flux audio à faible latence sur un appareil de rendu audio configuré pour une utilisation en mode exclusif :

//-----------------------------------------------------------
// 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;
}

Dans l’exemple de code précédent, la fonction PlayExclusiveStream s’exécute dans le thread d’application qui gère les mémoires tampons de point de terminaison pendant la lecture d’un flux de rendu. La fonction prend un paramètre unique, pMySource, qui est un pointeur vers un objet qui appartient à une classe définie par le client, MyAudioSource. Cette classe a deux fonctions membres, LoadData et SetFormat, qui sont appelées dans l’exemple de code. MyAudioSource est décrit dans Rendu d’un flux.

La fonction PlayExclusiveStream appelle une fonction d’assistance, GetStreamFormat, qui négocie avec le périphérique de rendu par défaut pour déterminer si l’appareil prend en charge un format de flux en mode exclusif qui convient à l’application. Le code de la fonction GetStreamFormat n’apparaît pas dans l’exemple de code ; cela est dû au fait que les détails de son implémentation dépendent entièrement des exigences de l’application. Toutefois, l’opération de la fonction GetStreamFormat peut être décrite simplement: elle appelle la méthode IAudioClient::IsFormatSupported une ou plusieurs fois pour déterminer si l’appareil prend en charge un format approprié. Les exigences de l’application déterminent les formats que GetStreamFormat présente à la méthode IsFormatSupported et l’ordre dans lequel elle les présente. Pour plus d’informations sur IsFormatSupported, consultez Formats d’appareil.

Après l’appel GetStreamFormat, la fonction PlayExclusiveStream appelle la méthode IAudioClient::GetDevicePeriod pour obtenir la période minimale prise en charge par le matériel audio. Ensuite, la fonction appelle la méthode IAudioClient::Initialize pour demander une durée de mémoire tampon égale à la période minimale. Si l’appel réussit, la méthode Initialize alloue deux mémoires tampons de point de terminaison, chacune d’elles étant égale en durée à la période minimale. Plus tard, lorsque le flux audio commence à s’exécuter, l’application et le matériel audio partagent les deux mémoires tampons de façon « ping-pong », c’est-à-dire que pendant que l’application écrit dans une mémoire tampon, le matériel lit à partir de l’autre mémoire tampon.

Avant de démarrer le flux, la fonction PlayExclusiveStream effectue les opérations suivantes :

  • Crée et inscrit le handle d’événement par le biais duquel il reçoit des notifications lorsque les mémoires tampons sont prêtes à être remplies.
  • Remplit la première mémoire tampon avec les données de la source audio pour réduire le délai entre le début de l’exécution du flux et le moment où le son initial est entendu.
  • Appelle la fonction AvSetMmThreadCharacteristics pour demander que MMCSS augmente la priorité du thread dans lequel PlayExclusiveStream s’exécute. (Lorsque le flux cesse de s’exécuter, l’appel de fonction AvRevertMmThreadCharacteristics restaure la priorité du thread d’origine.)

Pour plus d’informations sur AvSetMmThreadCharacteristics et AvRevertMmThreadCharacteristics, consultez la documentation du Kit de développement logiciel (SDK) Windows.

Pendant l’exécution du flux, chaque itération de la boucle while dans l’exemple de code précédent remplit une mémoire tampon de point de terminaison. Entre les itérations, l’appel de fonction WaitForSingleObject attend que le handle d’événement soit signalé. Lorsque le handle est signalé, le corps de la boucle effectue les opérations suivantes :

  1. Appelle la méthode IAudioRenderClient::GetBuffer pour obtenir la mémoire tampon suivante.
  2. Remplit la mémoire tampon.
  3. Appelle la méthode IAudioRenderClient::ReleaseBuffer pour libérer la mémoire tampon.

Pour plus d’informations sur WaitForSingleObject, consultez la documentation du Kit de développement logiciel (SDK) Windows.

Si la carte audio est contrôlée par un pilote WaveRT, la signalisation du handle d’événement est liée aux notifications de transfert DMA à partir du matériel audio. Pour un périphérique audio USB ou pour un périphérique audio contrôlé par un pilote WaveCyclique ou WavePci, la signalisation du handle d’événement est liée aux achèvements des IRP qui transfèrent des données de la mémoire tampon de l’application vers la mémoire tampon matérielle.

L’exemple de code précédent pousse le matériel audio et le système informatique à leurs limites de performances. Tout d’abord, pour réduire la latence du flux, l’application planifie son thread de maintenance de mémoire tampon pour utiliser la période minimale de l’appareil prise en charge par le matériel audio. Deuxièmement, pour s’assurer que le thread s’exécute de manière fiable pendant chaque période d’appareil, l’appel de fonction AvSetMmThreadCharacteristics définit le paramètre TaskName sur « Audio Pro », qui est, dans Windows Vista, le nom de tâche par défaut avec la priorité la plus élevée. Déterminez si les exigences de délai de votre application peuvent être assouplies sans compromettre son utilité. Par exemple, l’application peut planifier son thread de maintenance de mémoire tampon pour utiliser une période supérieure au minimum. Une période plus longue peut permettre en toute sécurité l’utilisation d’une priorité de thread inférieure.

Gestion des flux