Freigeben über


Exclusive-Mode Streams

Wie bereits erläutert, hat die Anwendung, wenn eine Anwendung einen Datenstrom im exklusiven Modus öffnet, ausschließlich die Verwendung des Audioendpunktgeräts, das den Datenstrom wiedergibt oder zeichnet. Im Gegensatz dazu können mehrere Anwendungen ein Audioendpunktgerät freigeben, indem Datenströme im freigegebenen Modus auf dem Gerät geöffnet werden.

Der exklusive Zugriff auf ein Audiogerät kann wichtige Systemsounds blockieren, die Interoperabilität mit anderen Anwendungen verhindern und andernfalls die Benutzererfahrung beeinträchtigen. Um diese Probleme zu beheben, gibt eine Anwendung mit einem Exklusivmodusdatenstrom in der Regel die Kontrolle des Audiogeräts zurück, wenn die Anwendung nicht der Vordergrundprozess ist oder nicht aktiv streamingt.

Die Streamlatenz ist die Verzögerung, die dem Datenpfad inhärent ist, der den Endpunktpuffer einer Anwendung mit einem Audioendpunktgerät verbindet. Bei einem Renderingdatenstrom ist die Latenz die maximale Verzögerung von der Zeit, zu der eine Anwendung ein Beispiel in einen Endpunktpuffer schreibt, in die Zeit, zu der das Beispiel durch die Lautsprecher gehört wird. Bei einem Aufnahmedatenstrom ist die Latenz die maximale Verzögerung von der Zeit, zu der ein Sound in das Mikrofon wechselt, bis zum Zeitpunkt, zu dem eine Anwendung das Beispiel für diesen Sound aus dem Endpunktpuffer lesen kann.

Anwendungen, die Exklusivmodusdatenströme verwenden, tun dies häufig, da sie niedrige Latenzen in den Datenpfaden zwischen den Audioendpunktgeräten und den Anwendungsthreads erfordern, die auf die Endpunktpuffer zugreifen. In der Regel werden diese Threads mit relativ hoher Priorität ausgeführt und planen, dass sie in regelmäßigen Intervallen ausgeführt werden, die nahe oder identisch mit dem periodischen Intervall sind, das nachfolgende Verarbeitungsdurchläufe durch die Audiohardware trennt. Bei jedem Durchlauf verarbeitet die Audiohardware die neuen Daten in den Endpunktpuffern.

Um die kleinsten Datenstromlatenzen zu erzielen, erfordert eine Anwendung möglicherweise sowohl spezielle Audiohardware als auch ein Computersystem, das leicht geladen ist. Das Steuern der Audiohardware über die Zeitlimits hinaus oder das Laden des Systems mit konkurrierenden Aufgaben mit hoher Priorität kann zu einem Glitch in einem Audiodatenstrom mit geringer Latenz führen. Bei einem Renderingdatenstrom kann z. B. ein Glitch auftreten, wenn die Anwendung vor dem Lesen des Puffers nicht in einen Endpunktpuffer schreibt, oder wenn die Hardware den Puffer vor der Geplanten Wiedergabe des Puffers nicht lesen kann. In der Regel sollte eine Anwendung, die auf einer Vielzahl von Audiohardware ausgeführt werden soll, und in einer breiten Palette von Systemen ihre Anzeigedauern ausreichend entspannen, um Störungen in allen Zielumgebungen zu vermeiden.

Windows Vista bietet mehrere Features zur Unterstützung von Anwendungen, die Audiodatenströme mit geringer Latenz erfordern. Wie in User-Mode Audiokomponentenerläutert, können Anwendungen, die zeitkritische Vorgänge ausführen, die MmCSS-Funktionen (Multimedia Class Scheduler Service) aufrufen, um die Threadpriorität zu erhöhen, ohne CPU-Ressourcen an Anwendungen mit niedrigerer Priorität zu verweigern. Darüber hinaus unterstützt die IAudioClient::Initialize-Methode ein AUDCLNT_STREAMFLAGS_EVENTCALLBACK Flag, mit dem der Pufferwartungsthread einer Anwendung die Ausführung planen kann, wenn ein neuer Puffer vom Audiogerät verfügbar wird. Durch die Verwendung dieser Features kann ein Anwendungsthread Die Unsicherheit darüber verringern, wann er ausgeführt wird, wodurch das Risiko von Störungen in einem Audiodatenstrom mit geringer Latenz verringert wird.

Die Treiber für ältere Audioadapter verwenden wahrscheinlich die WaveCyclic- oder WavePci-Gerätetreiberschnittstelle (DDI), während die Treiber für neuere Audioadapter wahrscheinlicher die WaveRT DDI unterstützen. Für Exklusivmodusanwendungen können WaveRT-Treiber eine bessere Leistung bieten als WaveCyclic- oder WavePci-Treiber, aber WaveRT-Treiber erfordern zusätzliche Hardwarefunktionen. Diese Funktionen umfassen die Möglichkeit, Hardwarepuffer direkt mit Anwendungen zu teilen. Bei direkter Freigabe ist kein Systemeingriff erforderlich, um Daten zwischen einer Exklusivmodusanwendung und der Audiohardware zu übertragen. Im Gegensatz dazu eignen sich WaveCyclic- und WavePci-Treiber für ältere, weniger fähige Audioadapter. Diese Adapter basieren auf Systemsoftware zum Transport von Datenblöcken (die an System-E/A-Anforderungspakete oder IRPs angefügt sind) zwischen Anwendungspuffern und Hardwarepuffern. Darüber hinaus verlassen sich USB-Audiogeräte auf Systemsoftware, um Daten zwischen Anwendungspuffern und Hardwarepuffern zu übertragen. Um die Leistung von Exklusivmodusanwendungen zu verbessern, die eine Verbindung mit Audiogeräten herstellen, die auf dem System für den Datentransport basieren, erhöht WASAPI automatisch die Priorität der Systemthreads, die Daten zwischen den Anwendungen und der Hardware übertragen. WASAPI verwendet MMCSS, um die Threadpriorität zu erhöhen. Wenn in Windows Vista ein Systemthread den Datentransport für einen Exklusivmodus-Audiowiedergabedatenstrom mit einem PCM-Format und einem Gerätezeitraum von weniger als 10 Millisekunden verwaltet, weist WASAPI dem Thread den MMCSS-Aufgabennamen "Pro Audio" zu. Wenn der Gerätezeitraum des Datenstroms größer oder gleich 10 Millisekunden ist, weist WASAPI dem Thread den MMCSS-Aufgabennamen "Audio" zu. Weitere Informationen zu WaveCyclic, WavePci und WaveRT DDIs finden Sie in der Windows DDK-Dokumentation. Informationen zum Auswählen eines geeigneten Gerätezeitraums finden Sie unter IAudioClient::GetDevicePeriod.

Wie in Session Volume Controlsbeschrieben, stellt WASAPI- die ISimpleAudioVolume, IChannelAudioVolumeund IAudioStreamVolume Schnittstellen zum Steuern der Lautstärke von Audiodatenströmen für den gemeinsam genutzten Modus bereit. Die Steuerelemente in diesen Schnittstellen wirken sich jedoch nicht auf Exklusivmodusdatenströme aus. Stattdessen verwenden Anwendungen, die Exklusivmodusdatenströme verwalten, in der Regel die IAudioEndpointVolume- Schnittstelle in der EndpointVolume-API, um die Volumeebenen dieser Datenströme zu steuern. Informationen zu dieser Schnittstelle finden Sie unter Endpoint Volume Controls.

Für jedes Wiedergabegerät und das Aufnahmegerät im System kann der Benutzer steuern, ob das Gerät im exklusiven Modus verwendet werden kann. Wenn der Benutzer die Verwendung des Geräts im exklusiven Modus deaktiviert, kann das Gerät verwendet werden, um nur Datenströme im freigegebenen Modus wiederzugeben oder aufzuzeichnen.

Wenn der Benutzer die Verwendung des Geräts im exklusiven Modus aktiviert, kann der Benutzer auch steuern, ob eine Anforderung einer Anwendung zur Verwendung des Geräts im exklusiven Modus die Verwendung des Geräts durch Anwendungen vornimmt, die derzeit über das Gerät wiedergegeben oder aufgezeichnet werden können. Wenn die Einstellung aktiviert ist, ist eine Anforderung einer Anwendung, die exklusive Kontrolle über das Gerät zu übernehmen, erfolgreich, wenn das Gerät derzeit nicht verwendet wird oder wenn das Gerät im gemeinsam genutzten Modus verwendet wird, aber die Anforderung fehlschlägt, wenn bereits eine andere Anwendung die exklusive Kontrolle über das Gerät hat. Wenn die Einstellung deaktiviert ist, ist eine Anforderung einer Anwendung, die exklusive Kontrolle über das Gerät zu übernehmen, erfolgreich, wenn das Gerät derzeit nicht verwendet wird, aber die Anforderung schlägt fehl, wenn das Gerät bereits im gemeinsam genutzten Modus oder im exklusiven Modus verwendet wird.

In Windows Vista sind die Standardeinstellungen für ein Audioendpunktgerät die folgenden:

  • Das Gerät kann verwendet werden, um Datenströme im Exklusivmodus wiederzugeben oder aufzuzeichnen.
  • Eine Anforderung, ein Gerät zum Wiedergeben oder Aufzeichnen eines Exklusivmodusdatenstroms zu verwenden, übergibt jeden Stream im freigegebenen Modus, der derzeit über das Gerät wiedergegeben oder aufgezeichnet wird.

So ändern Sie die Exklusivmoduseinstellungen eines Wiedergabe- oder Aufzeichnungsgeräts

  1. Klicken Sie im Infobereich mit der rechten Maustaste auf das Lautsprechersymbol, das sich auf der rechten Seite der Taskleiste befindet, und wählen Sie Wiedergabegeräte oder Aufzeichnungsgeräteaus. (Führen Sie alternativ die Windows-Multimedia-Systemsteuerung Mmsys.cplüber ein Eingabeaufforderungsfenster aus. Weitere Informationen finden Sie in DEVICE_STATE_XXX Konstanten.)
  2. Nachdem das Fenster Sound angezeigt wurde, wählen Sie Wiedergabe oder Aufzeichnungaus. Wählen Sie als Nächstes einen Eintrag in der Liste der Gerätenamen aus, und klicken Sie auf Eigenschaften.
  3. Nachdem das Fenster Eigenschaften angezeigt wird, klicken Sie auf Erweiterte.
  4. Damit Anwendungen das Gerät im exklusiven Modus verwenden können, aktivieren Sie das Kontrollkästchen Anwendungen die exklusive Kontrolle über dieses Geräterlauben. Deaktivieren Sie das Kontrollkästchen, um die Ausschließlichkeitsmodusverwendung des Geräts zu deaktivieren.
  5. Wenn die ausschließliche Verwendung des Geräts aktiviert ist, können Sie angeben, ob eine Anforderung für die exklusive Kontrolle des Geräts erfolgreich ist, wenn das Gerät derzeit wiedergegeben wird oder Datenströme im freigegebenen Modus aufgezeichnet werden. Aktivieren Sie das Kontrollkästchen mit der Bezeichnung Ausschließliche Modusanwendungen Priorität. Deaktivieren Sie das Kontrollkästchen, um die Priorität von Exklusivmodusanwendungen gegenüber Anwendungen für den gemeinsam genutzten Modus zu verweigern.

Das folgende Codebeispiel zeigt, wie ein Audiodatenstrom mit geringer Latenz auf einem Audiorenderinggerät wiedergegeben wird, das für die Verwendung im exklusiven Modus konfiguriert ist:

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

Im vorherigen Codebeispiel wird die PlayExclusiveStream-Funktion im Anwendungsthread ausgeführt, der die Endpunktpuffer während der Wiedergabe eines Renderingdatenstroms verwendet. Die Funktion verwendet einen einzelnen Parameter, pMySource, ein Zeiger auf ein Objekt, das zu einer clientdefiniertEn Klasse gehört, MyAudioSource. Diese Klasse verfügt über zwei Memberfunktionen, LoadData und SetFormat, die im Codebeispiel aufgerufen werden. MyAudioSource wird in Rendern eines Stream-beschrieben.

Die PlayExclusiveStream-Funktion ruft eine Hilfsfunktion auf, GetStreamFormat, die mit dem Standardrenderinggerät aushandelt, um zu bestimmen, ob das Gerät ein Exklusivmodusstreamformat unterstützt, das für die Verwendung durch die Anwendung geeignet ist. Der Code für die GetStreamFormat-Funktion wird im Codebeispiel nicht angezeigt. das liegt daran, dass die Details der Implementierung vollständig von den Anforderungen der Anwendung abhängen. Der Vorgang der GetStreamFormat-Funktion kann jedoch einfach beschrieben werden– sie ruft die IAudioClient::IsFormatSupported Methode auf, um zu bestimmen, ob das Gerät ein geeignetes Format unterstützt. Die Anforderungen der Anwendung bestimmen, welche Formate GetStreamFormat für die IsFormatSupported--Methode und die Reihenfolge darstellt, in der sie angezeigt wird. Weitere Informationen zu IsFormatSupportedfinden Sie unter Geräteformate.

Nach dem GetStreamFormat-Aufruf ruft die PlayExclusiveStream-Funktion die IAudioClient::GetDevicePeriod--Methode auf, um den minimalen Gerätezeitraum abzurufen, der von der Audiohardware unterstützt wird. Als Nächstes ruft die Funktion die IAudioClient::Initialize Methode auf, um eine Pufferdauer anzufordern, die dem Mindestzeitraum entspricht. Wenn der Aufruf erfolgreich ist, weist die Initialize-Methode zwei Endpunktpuffer zu, die jeweils der Mindestdauer entsprechen. Wenn der Audiodatenstrom mit der Ausführung beginnt, teilt die Anwendung und die Audiohardware die beiden Puffer auf "ping-pong"-Weise– d. h. während die Anwendung in einen Puffer schreibt, liest die Hardware aus dem anderen Puffer.

Vor dem Starten des Datenstroms führt die PlayExclusiveStream-Funktion folgende Aktionen aus:

  • Erstellt und registriert das Ereignishandle, über das er Benachrichtigungen empfängt, wenn Puffer zum Ausfüllen bereit sind.
  • Füllt den ersten Puffer mit Daten aus der Audioquelle aus, um die Verzögerung beim Start des Datenstroms zu verringern, wenn der anfängliche Sound gehört wird.
  • Ruft die AvSetMmThreadCharacteristics--Funktion auf, um anzufordern, dass MMCSS die Priorität des Threads erhöht, in dem PlayExclusiveStream ausgeführt wird. (Wenn der Datenstrom nicht mehr ausgeführt wird, stellt der AvRevertMmThreadCharacteristics Funktionsaufruf die ursprüngliche Threadpriorität wieder her.)

Weitere Informationen zu AvSetMmThreadCharacteristics und AvRevertMmThreadCharacteristicsfinden Sie in der Windows SDK-Dokumentation.

Während der Datenstrom ausgeführt wird, füllt jede Iteration der während-loop im vorherigen Codebeispiel einen Endpunktpuffer aus. Zwischen Iterationen wartet der WaitForSingleObject Funktionsaufruf auf das Signal des Ereignishandles. Wenn der Ziehpunkt signalisiert wird, führt der Schleifentext die folgenden Aktionen aus:

  1. Ruft die IAudioRenderClient::GetBuffer Methode auf, um den nächsten Puffer abzurufen.
  2. Füllt den Puffer aus.
  3. Ruft die IAudioRenderClient::ReleaseBuffer Methode auf, um den Puffer freizugeben.

Weitere Informationen zu WaitForSingleObjectfinden Sie in der Windows SDK-Dokumentation.

Wenn der Audioadapter von einem WaveRT-Treiber gesteuert wird, ist die Signalisierung des Ereignishandles an die DMA-Übertragungsbenachrichtigungen von der Audiohardware gebunden. Bei einem USB-Audiogerät oder für ein Audiogerät, das von einem WaveCyclic- oder WavePci-Treiber gesteuert wird, ist die Signalisierung des Ereignishandles an Die Fertigstellungen der IRPs gebunden, die Daten aus dem Anwendungspuffer an den Hardwarepuffer übertragen.

Im vorherigen Codebeispiel werden die Audiohardware und das Computersystem an ihre Leistungsgrenzen übertragen. Um die Datenstromlatenz zu verringern, plant die Anwendung den Pufferwartungsthread so, dass der minimale Gerätezeitraum verwendet wird, den die Audiohardware unterstützt. Um sicherzustellen, dass der Thread innerhalb jedes Gerätezeitraums zuverlässig ausgeführt wird, legt der AvSetMmThreadCharacteristics Funktionsaufruf den TaskName-Parameter auf "Pro Audio" fest, der in Windows Vista den Standardaufgabennamen mit der höchsten Priorität ist. Überlegen Sie, ob die Zeitlichen Anforderungen Ihrer Anwendung entspannt sein könnten, ohne ihre Nützlichkeit zu beeinträchtigen. Beispielsweise kann die Anwendung ihren Pufferwartungsthread so planen, dass ein Zeitraum verwendet wird, der länger als das Minimum ist. Ein längerer Zeitraum kann die Verwendung einer niedrigeren Threadpriorität sicher zulassen.

Streamverwaltung