Rendern eines Datenstroms

Der Client ruft die Methoden in der IAudioRenderClient-Schnittstelle auf, um Renderingdaten in einen Endpunktpuffer zu schreiben. Bei einem Stream im freigegebenen Modus teilt der Client den Endpunktpuffer mit der Audio-Engine. Für einen Datenstrom im exklusiven Modus teilt der Client den Endpunktpuffer mit dem Audiogerät. Um einen Endpunktpuffer einer bestimmten Größe anzufordern, ruft der Client die IAudioClient::Initialize-Methode auf . Um die Größe des zugeordneten Puffers abzurufen, die sich von der angeforderten Größe unterscheiden kann, ruft der Client die IAudioClient::GetBufferSize-Methode auf .

Um einen Datenstrom von Renderingdaten durch den Endpunktpuffer zu verschieben, ruft der Client abwechselnd die IAudioRenderClient::GetBuffer-Methode und die IAudioRenderClient::ReleaseBuffer-Methode auf. Der Client greift als Reihe von Datenpaketen auf die Daten im Endpunktpuffer zu. Der GetBuffer-Aufruf ruft das nächste Paket ab, damit der Client es mit Renderingdaten füllen kann. Nachdem die Daten in das Paket geschrieben wurden, ruft der Client ReleaseBuffer auf, um das fertige Paket der Renderingwarteschlange hinzuzufügen.

Bei einem Renderingpuffer stellt der von der IAudioClient::GetCurrentPadding-Methode gemeldete Abstandswert die Menge der Renderingdaten dar, die in die Warteschlange gestellt werden, um im Puffer wiedergegeben zu werden. Eine Renderinganwendung kann den Abstandswert verwenden, um zu bestimmen, wie viele neue Daten sicher in den Puffer geschrieben werden können, ohne dass das Risiko besteht, dass zuvor geschriebene Daten überschrieben werden, die die Audio-Engine noch nicht aus dem Puffer gelesen hat. Der verfügbare Speicherplatz ist einfach die Puffergröße abzüglich der Abstandsgröße. Der Client kann eine Paketgröße anfordern, die einen Teil oder den gesamten verfügbaren Speicherplatz im nächsten GetBuffer-Aufruf darstellt.

Die Größe eines Pakets wird in Audioframes ausgedrückt. Ein Audioframe in einem PCM-Stream ist eine Reihe von Beispielen (die Gruppe enthält ein Beispiel für jeden Kanal im Stream), die gleichzeitig wiedergegeben werden oder aufgezeichnet werden (Takttakt). Daher entspricht die Größe eines Audioframes der Samplegröße multipliziert mit der Anzahl der Kanäle im Stream. Beispielsweise beträgt die Framegröße für einen Stereodatenstrom (2 Kanäle) mit 16-Bit-Beispielen vier Bytes.

Das folgende Codebeispiel zeigt, wie Sie einen Audiodatenstrom auf dem Standardrenderinggerät wiedergeben:

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

Im vorherigen Beispiel verwendet die PlayAudioStream-Funktion einen einzelnen Parameter, pMySource. Hierbei handelt es sich um einen Zeiger auf ein Objekt, das zu einer clientdefinierten Klasse myAudioSource mit den beiden Memberfunktionen LoadData und SetFormat gehört. Der Beispielcode enthält die Implementierung von MyAudioSource aus folgenden Gründen nicht:

  • Keines der Klassenmember kommuniziert direkt mit einer der Methoden in den Schnittstellen in WASAPI.
  • Die -Klasse kann abhängig von den Anforderungen des Clients auf verschiedene Arten implementiert werden. (Es kann z. B. die Renderingdaten aus einer WAV-Datei lesen und eine On-the-Fly-Konvertierung in das Streamformat durchführen.)

Einige Informationen zum Betrieb der beiden Funktionen sind jedoch hilfreich, um das Beispiel zu verstehen.

Die LoadData-Funktion schreibt eine angegebene Anzahl von Audioframes (erster Parameter) in einen angegebenen Pufferspeicherort (zweiter Parameter). (Die Größe eines Audioframes ist die Anzahl der Kanäle im Stream multipliziert mit der Beispielgröße.) Die PlayAudioStream-Funktion verwendet LoadData, um Teile des freigegebenen Puffers mit Audiodaten zu füllen. Die SetFormat-Funktion gibt das Format für die LoadData-Funktion an, die für die Daten verwendet werden soll. Wenn die LoadData-Funktion in der Lage ist, mindestens einen Frame in den angegebenen Pufferspeicherort zu schreiben, aber die Daten ausgehen, bevor sie die angegebene Anzahl von Frames geschrieben hat, schreibt sie Stille in die verbleibenden Frames.

Solange LoadData es schafft, mindestens einen Frame mit echten Daten (nicht Stille) in den angegebenen Pufferspeicherort zu schreiben, gibt es 0 über den dritten Parameter aus, der im vorherigen Codebeispiel ein Ausgabezeiger auf die flags Variable ist. Wenn LoadData keine Daten enthält und nicht einmal einen einzelnen Frame in den angegebenen Pufferspeicherort schreiben kann, schreibt es nichts in den Puffer (nicht einmal still), und es schreibt den Wert AUDCLNT_BUFFERFLAGS_SILENT in die flags Variable. Die flags Variable übermittelt diesen Wert an die IAudioRenderClient::ReleaseBuffer-Methode , die antwortet, indem die angegebene Anzahl von Frames im Puffer mit Stille gefüllt wird.

In ihrem Aufruf der IAudioClient::Initialize-Methode fordert die PlayAudioStream-Funktion im vorherigen Beispiel einen freigegebenen Puffer an, der eine Dauer von einer Sekunde hat. (Der zugeordnete Puffer kann eine etwas längere Dauer haben.) Bei den ersten Aufrufen der Methoden IAudioRenderClient::GetBuffer und IAudioRenderClient::ReleaseBuffer füllt die Funktion den gesamten Puffer aus, bevor die IAudioClient::Start-Methode aufgerufen wird, um mit der Wiedergabe des Puffers zu beginnen.

Innerhalb der Standard-Schleife füllt die Funktion iterativ die Hälfte des Puffers in Halb-Sekunden-Intervallen aus. Kurz vor jedem Aufruf der Windows-Standbyfunktion in der Standard-Schleife ist der Puffer voll oder fast voll. Wenn der Standbyaufruf zurückgibt, ist der Puffer etwa halb voll. Die Schleife endet, nachdem der letzte Aufruf der LoadData-Funktion die flags Variable auf den Wert AUDCLNT_BUFFERFLAGS_SILENT festgelegt hat. An diesem Punkt enthält der Puffer mindestens einen Frame mit realen Daten und kann bis zu einer halben Sekunde an realen Daten enthalten. Der Rest des Puffers enthält Stille. Der Standby-Aufruf , der auf die Schleife folgt, bietet ausreichend Zeit (eine halbe Sekunde), um alle verbleibenden Daten wiederzugeben. Die Stille, die auf die Daten folgt, verhindert unerwünschte Sounds, bevor der Aufruf der IAudioClient::Stop-Methode den Audiodatenstrom beendet. Weitere Informationen zum Energiesparmodus finden Sie in der Windows SDK-Dokumentation.

Nach dem Aufruf der IAudioClient::Initialize-Methode bleibt der Stream geöffnet, bis der Client alle Verweise auf die IAudioClient-Schnittstelle und auf alle Verweise auf Dienstschnittstellen freigibt, die der Client über die IAudioClient::GetService-Methode abgerufen hat. Der letzte Release-Aufruf schließt den Stream.

Die PlayAudioStream-Funktion im vorherigen Codebeispiel ruft die CoCreateInstance-Funktion auf, um einen Enumerator für die Audioendpunktgeräte im System zu erstellen. Wenn das aufrufende Programm zuvor entweder die Funktion CoCreateInstance oder CoInitializeEx aufgerufen hat, um die COM-Bibliothek zu initialisieren, schlägt der CoCreateInstance-Aufruf fehl. Weitere Informationen zu CoCreateInstance, CoCreateInstance und CoInitializeEx finden Sie in der Windows SDK-Dokumentation.

Streamverwaltung