Delen via


Exclusive-Mode streams

Zoals eerder uitgelegd, heeft de toepassing, als een toepassing een stream opent in de exclusieve modus, exclusief gebruik van het audio-eindpuntapparaat dat de stream afspeelt of registreert. Verschillende toepassingen kunnen daarentegen een audio-eindpuntapparaat delen door streams in de gedeelde modus op het apparaat te openen.

Exclusieve toegang tot een audioapparaat kan cruciale systeemgeluiden blokkeren, interoperabiliteit met andere toepassingen voorkomen en anders de gebruikerservaring verminderen. Om deze problemen te verhelpen, zorgt een toepassing met een exclusieve-modusstroom meestal voor controle van het audioapparaat wanneer de toepassing niet het voorgrondproces is of niet actief wordt gestreamd.

Streamlatentie is de vertraging die inherent is aan het gegevenspad dat de eindpuntbuffer van een toepassing verbindt met een audio-eindpuntapparaat. Voor een renderingstream is de latentie de maximale vertraging vanaf het moment dat een toepassing een voorbeeld naar een eindpuntbuffer schrijft tot het moment dat het voorbeeld via de luidsprekers wordt gehoord. Voor een opnamestroom is de latentie de maximale vertraging vanaf het moment dat een geluid de microfoon binnenkomt tot het moment dat een toepassing het voorbeeld voor dat geluid kan lezen vanuit de eindpuntbuffer.

Toepassingen die gebruikmaken van exclusieve modusstreams doen dit vaak omdat ze lage latenties in de gegevenspaden tussen de audio-eindpuntapparaten en de toepassingsthreads vereisen die toegang hebben tot de eindpuntbuffers. Normaal gesproken worden deze threads met een relatief hoge prioriteit uitgevoerd en worden ze gepland om te worden uitgevoerd met periodieke intervallen die dicht bij of hetzelfde zijn als het periodieke interval dat de opeenvolgende verwerking door de audiohardware scheidt. Tijdens elke pass verwerkt de audiohardware de nieuwe gegevens in de eindpuntbuffers.

Voor het bereiken van de kleinste stroomlatenties vereist een toepassing mogelijk zowel speciale audiohardware als een computersysteem dat licht wordt geladen. Het stimuleren van de audiohardware buiten de timinglimieten of het laden van het systeem met concurrerende taken met hoge prioriteit kan leiden tot een storing in een audiostream met lage latentie. Voor een renderingstream kan bijvoorbeeld een storing optreden als de toepassing niet naar een eindpuntbuffer kan schrijven voordat de audiohardware de buffer leest of als de hardware de buffer niet kan lezen voordat de buffer wordt afgespeeld. Normaal gesproken moet een toepassing die is bedoeld om te worden uitgevoerd op een breed scala aan audiohardware en in een breed scala aan systemen de timingvereisten versoepelen om glitches in alle doelomgevingen te voorkomen.

Windows Vista biedt verschillende functies ter ondersteuning van toepassingen die audiostreams met lage latentie vereisen. Zoals besproken in User-Mode audioonderdelen, kunnen toepassingen die tijdkritieke bewerkingen uitvoeren de MMCSS-functies (Multimedia Class Scheduler Service) aanroepen om de threadprioriteit te verhogen zonder CPU-resources te weigeren voor toepassingen met een lagere prioriteit. Daarnaast ondersteunt de methode IAudioClient::Initialize een AUDCLNT_STREAMFLAGS_EVENTCALLBACK vlag waarmee de bufferservicethread van een toepassing de uitvoering kan plannen wanneer er een nieuwe buffer beschikbaar is vanaf het audioapparaat. Door deze functies te gebruiken, kan een toepassingsthread onzekerheid verminderen over wanneer deze wordt uitgevoerd, waardoor het risico op glitches in een audiostroom met lage latentie wordt verminderd.

De stuurprogramma's voor oudere audioadapters gebruiken waarschijnlijk de WaveCyclic- of WavePci-apparaatstuurprogrammainterface (DDI), terwijl de stuurprogramma's voor nieuwere audioadapters waarschijnlijk de WaveRT DDI ondersteunen. Voor exclusieve toepassingen kunnen WaveRT-stuurprogramma's betere prestaties bieden dan WaveCyclic- of WavePci-stuurprogramma's, maar WaveRT-stuurprogramma's vereisen extra hardwaremogelijkheden. Deze mogelijkheden omvatten de mogelijkheid om hardwarebuffers rechtstreeks te delen met toepassingen. Bij direct delen is er geen systeemingreep vereist om gegevens over te dragen tussen een exclusieve toepassing en de audiohardware. Daarentegen zijn WaveCyclic- en WavePci-stuurprogramma's geschikt voor oudere, minder geschikte audioadapters. Deze adapters zijn afhankelijk van systeemsoftware voor het transport van gegevensblokken (gekoppeld aan systeem-I/O-aanvraagpakketten of IRP's) tussen toepassingsbuffers en hardwarebuffers. Bovendien zijn USB-audioapparaten afhankelijk van systeemsoftware voor het transport van gegevens tussen toepassingsbuffers en hardwarebuffers. Ter verbetering van de prestaties van exclusieve-modustoepassingen die verbinding maken met audioapparaten die afhankelijk zijn van het systeem voor gegevenstransport, verhoogt WASAPI automatisch de prioriteit van de systeemthreads die gegevens overdragen tussen de toepassingen en de hardware. WASAPI maakt gebruik van MMCSS om de prioriteit van de thread te verhogen. Als in Windows Vista een systeemthread gegevenstransport beheert voor een exclusieve audio-afspeelstroom met een PCM-indeling en een apparaatperiode van minder dan 10 milliseconden, wijst WASAPI de mmCSS-taaknaam Pro Audio toe aan de thread. Als de apparaatperiode van de stream groter is dan of gelijk is aan 10 milliseconden, wijst WASAPI de MMCSS-taaknaam 'Audio' toe aan de thread. Zie de Windows DDK-documentatie voor meer informatie over de WaveCyclic-, WavePci- en WaveRT-DDK-DDIS's. Zie IAudioClient::GetDevicePeriodvoor informatie over het selecteren van een geschikte apparaatperiode.

Zoals beschreven in Session Volume Controls, biedt WASAPI de ISimpleAudioVolume, IChannelAudioVolumeen IAudioStreamVolume interfaces voor het beheren van de volumeniveaus van audiostreams in de gedeelde modus. De besturingselementen in deze interfaces hebben echter geen invloed op streams in de exclusieve modus. In plaats daarvan gebruiken toepassingen die exclusieve modusstreams beheren doorgaans de interface IAudioEndpointVolume in de EndpointVolume-API om de volumeniveaus van deze streams te beheren. Zie Endpoint Volume Controlsvoor meer informatie over deze interface.

Voor elk afspeelapparaat en opnameapparaat in het systeem kan de gebruiker bepalen of het apparaat kan worden gebruikt in de exclusieve modus. Als de gebruiker exclusief gebruik van het apparaat uitschakelt, kan het apparaat worden gebruikt om alleen gedeelde modusstreams af te spelen of op te nemen.

Als de gebruiker exclusief gebruik van het apparaat inschakelt, kan de gebruiker ook bepalen of een aanvraag van een toepassing voor het gebruik van het apparaat in de exclusieve modus het gebruik van het apparaat voorkomt door toepassingen die momenteel gedeelde modusstreams afspelen of opnemen via het apparaat. Als voorrang is ingeschakeld, slaagt een aanvraag van een toepassing om exclusief beheer van het apparaat te nemen als het apparaat momenteel niet in gebruik is of als het apparaat wordt gebruikt in de gedeelde modus, maar de aanvraag mislukt als een andere toepassing al exclusief beheer van het apparaat heeft. Als voorrang is uitgeschakeld, slaagt een aanvraag van een toepassing om exclusief beheer van het apparaat uit te voeren als het apparaat momenteel niet in gebruik is, maar de aanvraag mislukt als het apparaat al wordt gebruikt in de gedeelde modus of in de exclusieve modus.

In Windows Vista zijn de standaardinstellingen voor een audio-eindpuntapparaat het volgende:

  • Het apparaat kan worden gebruikt om exclusieve modusstreams af te spelen of op te nemen.
  • Een verzoek om een apparaat te gebruiken om een exclusieve stroomstroom af te spelen of op te nemen, maakt gebruik van een stream in de gedeelde modus die momenteel wordt afgespeeld of opgenomen via het apparaat.

De exclusieve modusinstellingen van een afspeel- of opnameapparaat wijzigen

  1. Klik met de rechtermuisknop op het luidsprekerpictogram in het systeemvak, dat zich aan de rechterkant van de taakbalk bevindt en selecteer Afspeelapparaten of Opnameapparaten. (Als alternatief voert u het Configuratiescherm van Windows multimedia uit, Mmsys.cpl, vanuit een opdrachtpromptvenster. Zie Opmerkingen in DEVICE_STATE_XXX Constantenvoor meer informatie.)
  2. Nadat het venster Geluid wordt weergegeven, selecteert u Afspelen of Opnemen. Selecteer vervolgens een vermelding in de lijst met apparaatnamen en klik op Eigenschappen.
  3. Nadat het venster Eigenschappen wordt weergegeven, klikt u op Geavanceerde.
  4. Als u wilt dat toepassingen het apparaat in de exclusieve modus kunnen gebruiken, schakelt u het selectievakje Toestaan dat toepassingen exclusieve controle over dit apparaat overnemen. Als u het gebruik van de exclusieve modus van het apparaat wilt uitschakelen, schakelt u het selectievakje uit.
  5. Als het gebruik van de exclusieve modus van het apparaat is ingeschakeld, kunt u opgeven of een aanvraag voor exclusief beheer van het apparaat slaagt als het apparaat momenteel gedeelde modusstreams afspeelt of opneemt. Als u toepassingen in de exclusieve modus prioriteit wilt geven boven toepassingen in de gedeelde modus, schakelt u het selectievakje in Exclusieve modustoepassingen prioriteit geven. Als u de prioriteit van toepassingen voor exclusieve modus wilt weigeren ten opzichte van toepassingen in de gedeelde modus, schakelt u het selectievakje uit.

In het volgende codevoorbeeld ziet u hoe u een audiostream met lage latentie afspeelt op een audiorenderingsapparaat dat is geconfigureerd voor gebruik in de exclusieve modus:

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

In het voorgaande codevoorbeeld wordt de functie PlayExclusiveStream uitgevoerd in de toepassingsthread die het eindpunt buffert terwijl een renderingstream wordt afgespeeld. De functie heeft één parameter, pMySource, een aanwijzer naar een object dat deel uitmaakt van een door de client gedefinieerde klasse MyAudioSource. Deze klasse heeft twee lidfuncties, LoadData en SetFormat, die worden aangeroepen in het codevoorbeeld. MyAudioSource wordt beschreven in Rendering a Stream.

Met de functie PlayExclusiveStream wordt een helperfunctie aangeroepen, GetStreamFormat, die onderhandelt met het standaard renderingapparaat om te bepalen of het apparaat een streamindeling met exclusieve modus ondersteunt die geschikt is voor gebruik door de toepassing. De code voor de functie GetStreamFormat wordt niet weergegeven in het codevoorbeeld; dat komt doordat de details van de implementatie volledig afhankelijk zijn van de vereisten van de toepassing. De werking van de functie GetStreamFormat kan echter eenvoudig worden beschreven. Hiermee wordt de methode IAudioClient::IsFormatSupported methode een of meer keren aangeroepen om te bepalen of het apparaat een geschikte indeling ondersteunt. De vereisten van de toepassing bepalen welke indelingen GetStreamFormat aan de methode IsFormatSupported en de volgorde waarin deze worden weergegeven. Zie Device Formatsvoor meer informatie over IsFormatSupported.

Na de aanroep GetStreamFormat roept de functie PlayExclusiveStream de IAudioClient::GetDevicePeriod methode aan om de minimale apparaatperiode te verkrijgen die wordt ondersteund door de audiohardware. Vervolgens roept de functie de IAudioClient::Initialiseer methode om een bufferduur aan te vragen die gelijk is aan de minimale periode. Als de aanroep slaagt, wijst de methode twee eindpuntbuffers toe, die elk gelijk zijn aan de minimale periode. Wanneer de audiostream later wordt uitgevoerd, delen de toepassings- en audiohardware de twee buffers op ping-pong-wijze, dat wil gezegd, terwijl de toepassing naar één buffer schrijft, de hardware wordt gelezen uit de andere buffer.

Voordat u de stream start, doet de functie PlayExclusiveStream het volgende:

  • Hiermee maakt en registreert u de gebeurtenisgreep waarmee deze meldingen ontvangt wanneer buffers klaar zijn om te worden ingevuld.
  • Vult de eerste buffer met gegevens van de audiobron om de vertraging te verminderen van wanneer de stream wordt uitgevoerd wanneer het eerste geluid wordt gehoord.
  • Roept de AvSetMmThreadCharacteristics functie aan om aan te vragen dat MMCSS de prioriteit van de thread verhoogt waarin PlayExclusiveStream wordt uitgevoerd. (Wanneer de stream niet meer wordt uitgevoerd, herstelt de AvRevertMmThreadCharacteristics functieaanroep de oorspronkelijke threadprioriteit.)

Zie de Windows SDK-documentatie voor meer informatie over AvSetMmThreadCharacteristics en AvRevertMmThreadCharacteristics.

Terwijl de stream wordt uitgevoerd, vult elke iteratie van de terwijl-lus in het voorgaande codevoorbeeld één eindpuntbuffer vult. Tussen iteraties wacht de WaitForSingleObject functieaanroep totdat de gebeurtenisgreep wordt gesignaleerd. Wanneer de greep wordt gesignaleerd, doet de lustekst het volgende:

  1. Roept de methode IAudioRenderClient::GetBuffer aan om de volgende buffer op te halen.
  2. Vult de buffer.
  3. Roept de IAudioRenderClient::ReleaseBuffer methode aan om de buffer vrij te geven.

Zie de Windows SDK-documentatie voor meer informatie over WaitForSingleObject.

Als de audioadapter wordt beheerd door een WaveRT-stuurprogramma, wordt de signalering van de gebeurtenisgreep gekoppeld aan de DMA-overdrachtsmeldingen van de audiohardware. Voor een USB-audioapparaat of voor een audioapparaat dat wordt beheerd door een WaveCyclic- of WavePci-stuurprogramma, is de signalering van de gebeurtenisgreep gekoppeld aan voltooiingen van de IRP's die gegevens overdragen van de toepassingsbuffer naar de hardwarebuffer.

In het voorgaande codevoorbeeld worden de audiohardware en het computersysteem naar hun prestatielimieten gepusht. Om eerst de stroomlatentie te verminderen, plant de toepassing de bufferservicethread om de minimale apparaatperiode te gebruiken die door de audiohardware wordt ondersteund. Ten tweede, om ervoor te zorgen dat de thread betrouwbaar wordt uitgevoerd binnen elke apparaatperiode, stelt de AvSetMmThreadCharacteristics functie-aanroep de parameter TaskName in op 'Pro Audio', wat in Windows Vista de standaardtaaknaam met de hoogste prioriteit is. Overweeg of de timingvereisten van uw toepassing mogelijk worden versoepeld zonder de bruikbaarheid ervan in gevaar te brengen. De toepassing kan bijvoorbeeld plannen dat de bufferservicethread een periode gebruikt die langer is dan het minimum. Een langere periode kan het gebruik van een lagere threadprioriteit veilig toestaan.

Stream Management-