Dela via


Återge en ström

Klienten anropar metoderna i gränssnittet IAudioRenderClient för att skriva renderingsdata till en slutpunktsbuffert. För en dataström i delat läge delar klienten slutpunktsbufferten med ljudmotorn. För en ström i exklusivt läge delar klienten slutpunktsbufferten med ljudenheten. Om du vill begära en slutpunktsbuffert av en viss storlek anropar klienten metoden IAudioClient::Initialize. För att få storleken på den allokerade bufferten, som kan skilja sig från den begärda storleken, anropar klienten metoden IAudioClient::GetBufferSize.

För att flytta en ström av återgivningsdata genom slutpunktsbufferten anropar klienten växelvis metoden IAudioRenderClient::GetBuffer och metoden IAudioRenderClient::ReleaseBuffer. Klienten kommer åt data i slutpunktsbufferten som en serie datapaket. Anropet GetBuffer hämtar nästa paket så att klienten kan fylla det med återgivningsdata. När du har skrivit data till paketet anropar klienten ReleaseBuffer- för att lägga till det slutförda paketet i renderingskön.

För en renderingsbuffert representerar utfyllnadsvärdet som rapporteras av IAudioClient::GetCurrentPadding-metoden mängden återgivningsdata som är i kö för att spelas upp i bufferten. Ett renderingsprogram kan använda utfyllnadsvärdet för att avgöra hur mycket nya data som kan skrivas till bufferten utan risk för att skriva över tidigare skrivna data som ljudmotorn ännu inte har läst från bufferten. Det tillgängliga utrymmet är helt enkelt buffertstorleken minus utfyllnadsstorleken. Klienten kan begära en paketstorlek som representerar en del av eller allt tillgängligt utrymme i nästa GetBuffer--anrop.

Storleken på ett paket uttrycks i ljudramar. En ljudram i en PCM-ström är en uppsättning prov (uppsättningen innehåller ett prov för varje kanal i strömmen) som spelas upp eller spelas in vid samma tidpunkt (klockslag). Storleken på en ljudram är alltså exempelstorleken multiplicerad med antalet kanaler i strömmen. Till exempel är ramstorleken för en stereoström (2-kanals) med 16-bitarsprover fyra byte.

I följande kodexempel visas hur du spelar upp en ljudström på standardåtergivningsenheten:

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

I föregående exempel tar funktionen PlayAudioStream en enda parameter, pMySource, som är en pekare till ett objekt som tillhör en klientdefinierad klass, MyAudioSource, med två medlemsfunktioner, LoadData och SetFormat. Exempelkoden inkluderar inte implementeringen av MyAudioSource eftersom:

  • Ingen av klassmedlemmarna kommunicerar direkt med någon av metoderna i gränssnitten i WASAPI.
  • Klassen kan implementeras på flera olika sätt, beroende på klientens krav. (Den kan till exempel läsa återgivningsdata från en WAV-fil och utföra direktkonvertering till stream-formatet.)

Viss information om hur de två funktionerna fungerar är dock användbar för att förstå exemplet.

Funktionen LoadData skriver ett angivet antal ljudramar (första parametern) till en angiven buffertplats (andra parametern). (Storleken på en ljudram är antalet kanaler i strömmen multiplicerat med exempelstorleken.) Funktionen PlayAudioStream använder LoadData för att fylla delar av den delade bufferten med ljuddata. Funktionen SetFormat anger formatet för den LoadData-funktion som ska användas för data. Om funktionen LoadData kan skriva minst en bildruta till den angivna buffertplatsen men får slut på data innan den har skrivit det angivna antalet bildrutor, skriver den tystnad till de återstående bildrutorna.

Så länge LoadData lyckas skriva minst en bildruta med verkliga data (inte tystnad) till den angivna buffertplatsen, matar den ut 0 via den tredje parametern, vilket i föregående kodexempel är en utdatapekare till variabeln flags. När LoadData har slut på data och inte kan skriva en enda bildruta till den angivna buffertplatsen, skrivs ingenting till bufferten (inte ens en tystnadssignal), och värdet AUDCLNT_BUFFERFLAGS_SILENT skrivs till variabeln flags. Variabeln flags förmedlar det här värdet till metoden IAudioRenderClient::ReleaseBuffer, som svarar genom att fylla det angivna antalet bildrutor i bufferten med tystnad.

I sitt anrop till IAudioClient::Initiera-metoden begär funktionen PlayAudioStream i föregående exempel en delad buffert som har en varaktighet på en sekund. (Den allokerade bufferten kan ha en något längre varaktighet.) I sina första anrop till IAudioRenderClient::GetBuffer och IAudioRenderClient::ReleaseBuffer metoder fyller funktionen hela bufferten innan den anropar metoden IAudioClient::Start för att börja spela upp bufferten.

I huvudloopen fyller funktionen iterativt hälften av bufferten med halvsekundersintervall. Precis innan varje anrop till funktionen Windows Sleep i huvudloopen är bufferten full eller nästan full. När anropet Viloläge returneras är bufferten ungefär halvfull. Loopen avslutas efter det sista anropet till funktionen LoadData sätter variabeln flags till värdet AUDCLNT_BUFFERFLAGS_SILENT. Då innehåller bufferten minst en bildruta med verkliga data, och den kan innehålla så mycket som en halv sekund av verkliga data. Resten av bufferten innehåller tystnad. Anropet Viloläge som följer loopen ger tillräckligt med tid (en halv sekund) för att spela upp alla återstående data. Tystnaden som följer data förhindrar oönskade ljud innan anropet till IAudioClient::Stop-metoden stoppar ljudströmmen. Mer information om Vilolägefinns i Windows SDK-dokumentationen.

Efter anropet till IAudioClient::Initiera-metoden förblir dataströmmen öppen tills klienten släpper alla sina referenser till gränssnittet IAudioClient och till alla referenser till tjänstgränssnitt som klienten hämtade via IAudioClient::GetService-metoden. Det sista Release-anrop stänger strömmen.

Funktionen PlayAudioStream i föregående kodexempel anropar funktionen CoCreateInstance för att skapa en uppräknare för ljudslutpunktsenheterna i systemet. Om inte det anropande programmet tidigare anropade antingen funktionen CoCreateInstance eller CoInitializeEx för att initiera COM-biblioteket misslyckas CoCreateInstance--anropet. Mer information om CoCreateInstance, CoCreateInstanceoch CoInitializeExfinns i Windows SDK-dokumentationen.

Stream Management-