Rendu d’un flux

Le client appelle les méthodes dans l’interface IAudioRenderClient pour écrire des données de rendu dans une mémoire tampon de point de terminaison. Pour un flux en mode partagé, le client partage la mémoire tampon de point de terminaison avec le moteur audio. Pour un flux en mode exclusif, le client partage la mémoire tampon du point de terminaison avec le périphérique audio. Pour demander une mémoire tampon de point de terminaison d’une taille particulière, le client appelle la méthode IAudioClient::Initialize . Pour obtenir la taille de la mémoire tampon allouée, qui peut être différente de la taille demandée, le client appelle la méthode IAudioClient::GetBufferSize .

Pour déplacer un flux de données de rendu via la mémoire tampon du point de terminaison, le client appelle alternativement la méthode IAudioRenderClient::GetBuffer et la méthode IAudioRenderClient::ReleaseBuffer . Le client accède aux données dans la mémoire tampon du point de terminaison sous la forme d’une série de paquets de données. L’appel GetBuffer récupère le paquet suivant afin que le client puisse le remplir avec des données de rendu. Après avoir écrit les données dans le paquet, le client appelle ReleaseBuffer pour ajouter le paquet terminé à la file d’attente de rendu.

Pour une mémoire tampon de rendu, la valeur de remplissage signalée par la méthode IAudioClient::GetCurrentPadding représente la quantité de données de rendu mises en file d’attente pour être lues dans la mémoire tampon. Une application de rendu peut utiliser la valeur de remplissage pour déterminer la quantité de nouvelles données qu’elle peut écrire en toute sécurité dans la mémoire tampon sans risquer de remplacer les données précédemment écrites que le moteur audio n’a pas encore lues à partir de la mémoire tampon. L’espace disponible est simplement la taille de la mémoire tampon moins la taille de remplissage. Le client peut demander une taille de paquet qui représente tout ou partie de cet espace disponible dans son prochain appel GetBuffer .

La taille d’un paquet est exprimée en trames audio. Une trame audio dans un flux PCM est un ensemble d’exemples (l’ensemble contient un échantillon pour chaque canal du flux) qui lisent ou sont enregistrés en même temps (graduation d’horloge). Ainsi, la taille d’une trame audio est la taille de l’échantillon multipliée par le nombre de canaux dans le flux. Par exemple, la taille d’image d’un flux stéréo (2 canaux) avec des exemples 16 bits est de quatre octets.

L’exemple de code suivant montre comment lire un flux audio sur le périphérique de rendu par défaut :

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

Dans l’exemple précédent, la fonction PlayAudioStream prend un paramètre unique, pMySource, qui est un pointeur vers un objet qui appartient à une classe définie par le client, MyAudioSource, avec deux fonctions membres, LoadData et SetFormat. L’exemple de code n’inclut pas l’implémentation de MyAudioSource, car :

  • Aucun des membres de la classe ne communique directement avec l’une des méthodes dans les interfaces dans WASAPI.
  • La classe peut être implémentée de différentes façons, en fonction des exigences du client. (Par exemple, il peut lire les données de rendu d’un fichier WAV et effectuer une conversion à la volée au format de flux.)

Toutefois, certaines informations sur le fonctionnement des deux fonctions sont utiles pour comprendre l’exemple.

La fonction LoadData écrit un nombre spécifié de trames audio (premier paramètre) dans un emplacement de mémoire tampon spécifié (deuxième paramètre). (La taille d’une trame audio est le nombre de canaux dans le flux multiplié par la taille de l’échantillon.) La fonction PlayAudioStream utilise LoadData pour remplir des parties de la mémoire tampon partagée avec des données audio. La fonction SetFormat spécifie le format de la fonction LoadData à utiliser pour les données. Si la fonction LoadData est en mesure d’écrire au moins une trame dans l’emplacement de mémoire tampon spécifié, mais qu’elle manque de données avant d’avoir écrit le nombre spécifié de trames, elle écrit le silence dans les images restantes.

Tant que LoadData réussit à écrire au moins une trame de données réelles (et non un silence) dans l’emplacement de mémoire tampon spécifié, elle génère 0 via son troisième paramètre, qui, dans l’exemple de code précédent, est un pointeur de sortie vers la flags variable. Lorsque LoadData est en dehors des données et ne peut pas écrire une seule image dans l’emplacement de mémoire tampon spécifié, il n’écrit rien dans la mémoire tampon (même pas le silence) et écrit la valeur AUDCLNT_BUFFERFLAGS_SILENT dans la flags variable. La flags variable transmet cette valeur à la méthode IAudioRenderClient::ReleaseBuffer , qui répond en remplissant le nombre spécifié de trames dans la mémoire tampon avec le silence.

Dans son appel à la méthode IAudioClient::Initialize , la fonction PlayAudioStream dans l’exemple précédent demande une mémoire tampon partagée d’une durée d’une seconde. (La mémoire tampon allouée peut avoir une durée légèrement plus longue.) Dans ses appels initiaux aux méthodes IAudioRenderClient::GetBuffer et IAudioRenderClient::ReleaseBuffer , la fonction remplit la mémoire tampon entière avant d’appeler la méthode IAudioClient::Start pour commencer à lire la mémoire tampon.

Dans la boucle main, la fonction remplit de façon itérative la moitié de la mémoire tampon à intervalles d’une demi-seconde. Juste avant chaque appel à la fonction Windows Sleep dans la boucle main, la mémoire tampon est saturée ou presque. Lorsque l’appel Veille est retourné, la mémoire tampon est à peu près à moitié pleine. La boucle se termine après l’appel final à la fonction LoadData qui définit la flags variable sur la valeur AUDCLNT_BUFFERFLAGS_SILENT. À ce stade, la mémoire tampon contient au moins une trame de données réelles et peut contenir jusqu’à une demi-seconde de données réelles. Le reste de la mémoire tampon contient le silence. L’appel veille qui suit la boucle fournit suffisamment de temps (une demi-seconde) pour lire toutes les données restantes. Le silence qui suit les données empêche les sons indésirables avant que l’appel à la méthode IAudioClient::Stop arrête le flux audio. Pour plus d’informations sur la veille, consultez la documentation du Kit de développement logiciel (SDK) Windows.

Après l’appel à la méthode IAudioClient::Initialize , le flux reste ouvert jusqu’à ce que le client libère toutes ses références à l’interface IAudioClient et à toutes les références aux interfaces de service que le client a obtenues via la méthode IAudioClient::GetService . L’appel release final ferme le flux.

La fonction PlayAudioStream dans l’exemple de code précédent appelle la fonction CoCreateInstance pour créer un énumérateur pour les appareils de point de terminaison audio dans le système. Sauf si le programme appelant a précédemment appelé la fonction CoCreateInstance ou CoInitializeEx pour initialiser la bibliothèque COM, l’appel CoCreateInstance échoue. Pour plus d’informations sur CoCreateInstance, CoCreateInstance et CoInitializeEx, consultez la documentation du Kit de développement logiciel (SDK) Windows.

Gestion des flux