Exclusive-Mode Streams

Wie bereits erläutert, verwendet die Anwendung, wenn eine Anwendung einen Stream im exklusiven Modus öffnet, exklusiv das Audioendpunktgerät, das den Stream abspielt oder aufzeichnet. Im Gegensatz dazu können mehrere Anwendungen ein Audioendpunktgerät gemeinsam nutzen, indem Datenströme im freigegebenen Modus auf dem Gerät geöffnet werden.

Der Zugriff im exklusiven Modus 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 Datenstrom im exklusiven Modus in der Regel die Kontrolle über das Audiogerät auf, wenn die Anwendung nicht der Vordergrundprozess ist oder nicht aktiv gestreamt wird.

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, bis zu der Zeit, zu der das Beispiel über die Lautsprecher gehört wird. Bei einem Aufzeichnungsstream ist die Latenz die maximale Verzögerung von dem Zeitpunkt, zu dem ein Sound in das Mikrofon gelangt, bis zu dem Zeitpunkt, zu dem eine Anwendung das Beispiel für diesen Sound aus dem Endpunktpuffer lesen kann.

Anwendungen, die Datenströme im exklusiven Modus verwenden, tun dies häufig, da sie geringe Latenzen in den Datenpfaden zwischen den Audioendpunktgeräten und den Anwendungsthreads benötigen, 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 dem periodischen Intervall entsprechen, das die aufeinanderfolgenden Verarbeitungsdurchläufe von der Audiohardware trennt. Bei jedem Durchlauf verarbeitet die Audiohardware die neuen Daten in den Endpunktpuffern.

Um die geringsten Datenstromlatenzen zu erzielen, benötigt eine Anwendung möglicherweise sowohl spezielle Audiohardware als auch ein Computersystem, das leicht geladen ist. Wenn Sie die Audiohardware über ihre Zeitlimits hinausfahren oder das System mit konkurrierenden Aufgaben mit hoher Priorität laden, kann dies zu einem Fehler in einem Audiostream mit geringer Latenz führen. Bei einem Renderingdatenstrom kann beispielsweise ein Fehler auftreten, wenn die Anwendung nicht in einen Endpunktpuffer schreibt, bevor die Audiohardware den Puffer liest, oder wenn die Hardware den Puffer nicht vor dem Zeitpunkt liest, zu dem der Puffer wiedergegeben werden soll. In der Regel sollte eine Anwendung, die auf einer Vielzahl von Audiohardware und in einer vielzahl von Systemen ausgeführt werden soll, ihre Zeitanforderungen so weit lockern, dass Störungen in allen Zielumgebungen vermieden werden.

Windows Vista verfügt über mehrere Features zur Unterstützung von Anwendungen, die Audiodatenströme mit geringer Latenz erfordern. Wie unter Benutzermodus-Audiokomponenten erlä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 für 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 über das 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 Audiostream 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 eher den WaveRT DDI unterstützen. Für Anwendungen im exklusiven Modus können WaveRT-Treiber eine bessere Leistung als WaveCyclic- oder WavePci-Treiber bieten, aber WaveRT-Treiber erfordern zusätzliche Hardwarefunktionen. Zu diesen Funktionen gehört die Möglichkeit, Hardwarepuffer direkt für Anwendungen gemeinsam zu nutzen. Bei der direkten Freigabe ist kein Systemeingriff erforderlich, um Daten zwischen einer Anwendung im exklusiven Modus und der Audiohardware zu übertragen. Im Gegensatz dazu eignen sich WaveCyclic- und WavePci-Treiber für ältere, weniger leistungsfähige Audioadapter. Diese Adapter sind auf Systemsoftware angewiesen, um Datenblöcke (die an System-E/A-Anforderungspakete oder IRPs angefügt sind) zwischen Anwendungspuffern und Hardwarepuffern zu transportieren. Darüber hinaus sind USB-Audiogeräte auf Systemsoftware angewiesen, um Daten zwischen Anwendungspuffern und Hardwarepuffern zu transportieren. Um die Leistung von Anwendungen im exklusiven Modus zu verbessern, die eine Verbindung mit Audiogeräten herstellen, die für den Datentransport vom System abhängig sind, 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 Audiowiedergabestream im exklusiven Modus mit einem PCM-Format und einem Gerätezeitraum von weniger als 10 Millisekunden verwaltet, weist WASAPI dem Thread den MMCSS-Tasknamen "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 den 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 unter Session Volume Controls beschrieben, stellt WASAPI die Schnittstellen ISimpleAudioVolume, IChannelAudioVolume und IAudioStreamVolume zum Steuern der Lautstärkepegel von Audiostreams im freigegebenen Modus bereit. Die Steuerelemente in diesen Schnittstellen haben jedoch keine Auswirkungen auf Datenströme im exklusiven Modus. Stattdessen verwenden Anwendungen, die Datenströme im exklusiven Modus verwalten, in der Regel die IAudioEndpointVolume-Schnittstelle in der EndpointVolume-API , um die Volumeebenen dieser Streams zu steuern. Informationen zu dieser Schnittstelle finden Sie unter Endpunktvolumesteuerungen.

Für jedes Wiedergabegerät und erfassungsgerä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 Streams 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, das Gerät im exklusiven Modus zu verwenden, der Verwendung des Geräts durch Anwendungen vorentschließt, die derzeit Datenströme im freigegebenen Modus über das Gerät wiedergeben oder aufzeichnen. Wenn die Vorzeitige Entfernung 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 freigegebenen Modus verwendet wird, aber die Anforderung schlägt fehl, wenn eine andere Anwendung bereits über die exklusive Kontrolle über das Gerät verfügt. Wenn die Entfernung 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. Die Anforderung schlägt jedoch fehl, wenn das Gerät bereits im freigegebenen Modus oder im exklusiven Modus verwendet wird.

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

  • Das Gerät kann zum Wiedergeben oder Aufzeichnen von Streams im exklusiven Modus verwendet werden.
  • Eine Anforderung, ein Gerät zum Wiedergeben oder Aufzeichnen eines Datenstroms im exklusiven Modus zu verwenden, geht jedem Stream im freigegebenen Modus, der derzeit über das Gerät wiedergegeben oder aufgezeichnet wird, vor.

So ändern Sie die Einstellungen für den exklusiven Modus eines Wiedergabe- oder Aufzeichnungsgeräts

  1. Klicken Sie mit der rechten Maustaste auf das Lautsprechersymbol im Infobereich, der sich auf der rechten Seite der Taskleiste befindet, und wählen Sie Wiedergabegeräte oder Aufzeichnungsgeräte aus. (Alternativ können Sie die Windows-Multimedia-Systemsteuerung Mmsys.cpl über ein Eingabeaufforderungsfenster ausführen. Weitere Informationen finden Sie unter Hinweise in DEVICE_STATE_XXX Konstanten.)
  2. Nachdem das Fenster "Sound " angezeigt wird, wählen Sie Wiedergabe oder Aufzeichnung aus. Wählen Sie als Nächstes einen Eintrag in der Liste der Gerätenamen aus, und klicken Sie auf Eigenschaften.
  3. Klicken Sie nach dem Anzeigen des Fensters Eigenschaften auf Erweitert.
  4. Damit Anwendungen das Gerät im exklusiven Modus verwenden können, aktivieren Sie das Kontrollkästchen Anwendungen erlauben, die exklusive Kontrolle über dieses Gerät zu übernehmen. Deaktivieren Sie das Kontrollkästchen, um die Verwendung des Geräts im exklusiven Modus zu deaktivieren.
  5. Wenn die Verwendung des Geräts im exklusiven Modus aktiviert ist, können Sie angeben, ob eine Anforderung zur exklusiven Steuerung des Geräts erfolgreich ist, wenn das Gerät derzeit Streams im freigegebenen Modus abspielt oder aufzeichnet. Um Anwendungen im exklusiven Modus Vorrang vor Anwendungen im freigegebenen Modus zu geben, aktivieren Sie das Kontrollkästchen Anwendungen im exklusiven Modus Priorität geben. Deaktivieren Sie das Kontrollkästchen, um Anwendungen im exklusiven Modus die Priorität gegenüber Anwendungen im freigegebenen Modus zu verweigern.

Das folgende Codebeispiel zeigt, wie Sie einen Audiodatenstrom mit geringer Latenz auf einem Audiorenderinggerät wiedergeben, 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 den Endpunktpuffer bereitstellt, während ein Renderingstream wiedergegeben wird. Die Funktion verwendet einen einzelnen Parameter, pMySource, bei dem es sich um einen Zeiger auf ein Objekt handelt, das zur vom Client definierten Klasse MyAudioSource gehört. Diese Klasse verfügt über zwei Memberfunktionen, LoadData und SetFormat, die im Codebeispiel aufgerufen werden. MyAudioSource wird unter Rendern eines Streams beschrieben.

Die PlayExclusiveStream-Funktion ruft die Hilfsfunktion GetStreamFormat auf, die mit dem Standardrenderinggerät aushandelt, um zu bestimmen, ob das Gerät ein Streamformat im exklusiven Modus unterstützt, das für die Verwendung durch die Anwendung geeignet ist. Der Code für die GetStreamFormat-Funktion wird im Codebeispiel nicht angezeigt. dies liegt daran, dass die Details der Implementierung ausschließlich von den Anforderungen der Anwendung abhängen. Der Vorgang der GetStreamFormat-Funktion kann jedoch einfach beschrieben werden: Sie ruft die IAudioClient::IsFormatSupported-Methode mehrmals 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 darstellt und in welcher Reihenfolge sie angezeigt wird. Weitere Informationen zu IsFormatSupported finden Sie unter Geräteformate.

Nach dem GetStreamFormat-Aufruf ruft die PlayExclusiveStream-Funktion die IAudioClient::GetDevicePeriod-Methode auf, um die von der Audiohardware unterstützte Mindestdauer des Geräts abzurufen. Als Nächstes ruft die Funktion die IAudioClient::Initialize-Methode auf, um eine Pufferdauer in Höhe des Mindestzeitraums anzufordern. Wenn der Aufruf erfolgreich ist, weist die Initialize-Methode zwei Endpunktpuffer zu, die jeweils gleich der Dauer des Mindestzeitraums sind. Später, wenn der Audiostream ausgeführt wird, teilen sich die Anwendung und die Audiohardware die beiden Puffer auf Ping-Pong-Weise. Das heißt, 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 folgendes aus:

  • Erstellt und registriert das Ereignishandle, über das es Benachrichtigungen empfängt, wenn pufferbereit werden.
  • Füllt den ersten Puffer mit Daten aus der Audioquelle, um die Verzögerung von der Ausführung des Datenstroms bis zum Hören des anfänglichen Klangs zu verringern.
  • Ruft die AvSetMmThreadCharacteristics-Funktion auf, um anzufordern, dass MMCSS die Priorität des Threads erhöhen soll, in dem PlayExclusiveStream ausgeführt wird. (Wenn der Stream nicht mehr ausgeführt wird, stellt der Funktionsaufruf avRevertMmThreadCharacteristics die ursprüngliche Threadpriorität wieder her.)

Weitere Informationen zu AvSetMmThreadCharacteristics und AvRevertMmThreadCharacteristics finden Sie in der Dokumentation zum Windows SDK.

Während des Datenstroms füllt jede Iteration der While-Schleife im vorherigen Codebeispiel einen Endpunktpuffer. Zwischen Iterationen wartet der WaitForSingleObject-Funktionsaufruf , bis das Ereignishandle signalisiert wird. Wenn der Handle signalisiert wird, führt der Schleifentext folgendes 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 WaitForSingleObject finden Sie in der Windows SDK-Dokumentation.

Wenn der Audioadapter von einem WaveRT-Treiber gesteuert wird, ist die Signalisierung des Ereignishandles an die DMA-Übertragungsbenachrichtigungen der Audiohardware gebunden. Für ein 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 Vervollständigungen der IRPs gebunden, die Daten aus dem Anwendungspuffer an den Hardwarepuffer übertragen.

Im vorherigen Codebeispiel werden die Audiohardware und das Computersystem an ihre Leistungsgrenzen gepusht. Um die Streamlatenz zu verringern, plant die Anwendung ihren Pufferwartungsthread so, dass sie den mindesten Gerätezeitraum verwendet, 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, was in Windows Vista der Standardtaskname mit der höchsten Priorität ist. Überlegen Sie, ob die Zeitlichkeitsanforderungen Ihrer Anwendung gelockert werden können, ohne ihre Nützlichkeit zu beeinträchtigen. Beispielsweise kann die Anwendung ihren Pufferwartungsthread so planen, dass ein Zeitraum verwendet wird, der länger als der Mindestwert ist. Ein längerer Zeitraum kann die Verwendung einer niedrigeren Threadpriorität sicher zulassen.

Streamverwaltung