Udostępnij za pośrednictwem


strumienie Exclusive-Mode

Jak wyjaśniono wcześniej, jeśli aplikacja otworzy strumień w trybie wyłączności, aplikacja ma wyłączne użycie urządzenia punktu końcowego audio, które odtwarza lub rejestruje strumień. Z kolei kilka aplikacji może współużytkować urządzenie punktu końcowego audio, otwierając strumienie trybu udostępnionego na urządzeniu.

Dostęp w trybie wyłączności do urządzenia audio może blokować kluczowe dźwięki systemowe, zapobiegać współdziałaniu z innymi aplikacjami i w inny sposób obniżyć wydajność środowiska użytkownika. Aby rozwiązać te problemy, aplikacja ze strumieniem trybu wyłącznego zwykle rezygnuje z kontroli urządzenia audio, gdy aplikacja nie jest procesem pierwszego planu lub nie jest aktywnie przesyłana strumieniowo.

Opóźnienie strumienia to opóźnienie, które jest związane ze ścieżką danych łączącą bufor punktu końcowego aplikacji z urządzeniem punktu końcowego audio. W przypadku strumienia renderowania opóźnienie jest maksymalnym opóźnieniem od momentu zapisu przykładu przez aplikację do buforu punktu końcowego do czasu, w jaki próbka jest słyszana przez głośniki. W przypadku strumienia przechwytywania opóźnienie jest maksymalnym opóźnieniem od momentu wejścia dźwięku do czasu, w jaki aplikacja może odczytać próbkę dla tego dźwięku z buforu punktu końcowego.

Aplikacje korzystające ze strumieni trybu wyłącznego często to robią, ponieważ wymagają małych opóźnień w ścieżkach danych między urządzeniami punktu końcowego audio a wątkami aplikacji, które uzyskują dostęp do punktów końcowych. Zazwyczaj te wątki działają na względnie wysokim priorytcie i mają być uruchamiane w okresowych odstępach czasu, które są bliskie lub takie same jak okresowe interwały oddzielające kolejne przetwarzanie przechodzi przez sprzęt audio. Podczas każdego przekazywania sprzęt audio przetwarza nowe dane w punktu końcowego.

Aby osiągnąć najmniejsze opóźnienia strumienia, aplikacja może wymagać zarówno specjalnego sprzętu audio, jak i systemu komputerowego, który jest lekko załadowany. Napędzanie sprzętu audio poza limity czasu lub ładowanie systemu z konkurencyjnymi zadaniami o wysokim priorytcie może spowodować usterkę w strumieniu audio o małych opóźnieniach. Na przykład w przypadku strumienia renderowania może wystąpić usterka, jeśli aplikacja nie może zapisać w buforze punktu końcowego przed odczytaniem buforu przez sprzęt audio lub jeśli sprzęt nie odczytuje buforu przed upływem czasu zaplanowanego odtwarzania buforu. Zazwyczaj aplikacja przeznaczona do uruchamiania na szerokim sprzęcie audio i w szerokim zakresie systemów powinna złagodzić wymagania dotyczące chronometrażu na tyle, aby uniknąć usterek we wszystkich środowiskach docelowych.

System Windows Vista oferuje kilka funkcji do obsługi aplikacji wymagających strumieni audio o małych opóźnieniach. Zgodnie z opisem w User-Mode Audio Components, aplikacje wykonujące operacje krytyczne dla czasu mogą wywoływać funkcje usługi Harmonogramu klas multimedialnych (MMCSS) w celu zwiększenia priorytetu wątku bez odmowy zasobów procesora CPU do aplikacji o niższym priorytcie. Ponadto metoda IAudioClient::Initialize obsługuje flagę AUDCLNT_STREAMFLAGS_EVENTCALLBACK, która umożliwia wątkowi obsługi buforu aplikacji zaplanowanie jego wykonania, gdy nowy bufor stanie się dostępny na urządzeniu audio. Korzystając z tych funkcji, wątek aplikacji może zmniejszyć niepewność co do tego, kiedy zostanie wykonana, zmniejszając ryzyko wystąpienia usterki w strumieniu audio o małych opóźnieniach.

Sterowniki starszych kart audio mogą używać interfejsu sterowników urządzeń WaveCyclic lub WavePci (DDI), natomiast sterowniki nowszych kart audio są bardziej prawdopodobne, aby obsługiwać interfejs DDI WaveRT. W przypadku aplikacji w trybie wyłącznym sterowniki WaveRT mogą zapewnić lepszą wydajność niż sterowniki WaveCyclic lub WavePci, ale sterowniki WaveRT wymagają dodatkowych możliwości sprzętowych. Te możliwości obejmują możliwość udostępniania sprzętowych bezpośrednio aplikacjom. W przypadku bezpośredniego udostępniania żadna interwencja systemowa nie jest wymagana do przesyłania danych między aplikacją w trybie wyłącznym a sprzętem audio. Natomiast sterowniki WaveCyclic i WavePci są odpowiednie dla starszych, mniej zdolnych kart audio. Te karty polegają na oprogramowaniu systemowym do transportu bloków danych (dołączonych do pakietów żądań we/wy systemu lub irps) między aplikacji i sprzętowymi. Ponadto urządzenia audio USB polegają na oprogramowaniu systemowym do transportu danych między aplikacji a sprzętowymi. Aby zwiększyć wydajność aplikacji w trybie wyłączności, które łączą się z urządzeniami audio, które korzystają z systemu do transportu danych, INTERFEJS WASAPI automatycznie zwiększa priorytet wątków systemowych, które przesyłają dane między aplikacjami a sprzętem. Interfejs WASAPI używa programu MMCSS do zwiększenia priorytetu wątku. W systemie Windows Vista, jeśli wątek systemowy zarządza transportem danych dla strumienia odtwarzania audio w trybie wyłączności z formatem PCM i okresem urządzenia krótszym niż 10 milisekund, WASAPI przypisuje nazwę zadania MMCSS "Pro Audio" do wątku. Jeśli okres urządzenia strumienia jest większy lub równy 10 milisekund, WASAPI przypisuje nazwę zadania MMCSS "Audio" do wątku. Aby uzyskać więcej informacji na temat identyfikatorów DDI WaveCyclic, WavePci i WaveRT, zobacz dokumentację zestawu DDK systemu Windows. Aby uzyskać informacje na temat wybierania odpowiedniego okresu urządzenia, zobacz IAudioClient::GetDevicePeriod.

Zgodnie z opisem w kontrolek woluminów sesjiWASAPI udostępnia interfejsy ISimpleAudioVolume, IChannelAudioVolumei interfejsy IAudioStreamVolume do kontrolowania poziomów głośności strumieni audio w trybie udostępnionym. Jednak kontrolki w tych interfejsach nie mają wpływu na strumienie trybu wyłącznego. Zamiast tego aplikacje, które zarządzają strumieniami w trybie wyłączności, zwykle używają interfejsuIAudioEndpointVolumew interfejsie API EndpointVolume do kontrolowania poziomów głośności tych strumieni. Aby uzyskać informacje na temat tego interfejsu, zobacz Endpoint Volume Controls.

Dla każdego urządzenia odtwarzania i przechwytywania urządzenia w systemie użytkownik może kontrolować, czy urządzenie może być używane w trybie wyłącznym. Jeśli użytkownik wyłączy korzystanie z urządzenia w trybie wyłączności, urządzenie może być używane do odtwarzania lub rejestrowania tylko strumieni w trybie udostępnionym.

Jeśli użytkownik włączy korzystanie z urządzenia w trybie wyłączności, użytkownik może również kontrolować, czy żądanie przez aplikację do korzystania z urządzenia w trybie wyłączności wywłaszcza użycie urządzenia przez aplikacje, które mogą obecnie odtwarzać lub nagrywać strumienie w trybie udostępnionym za pośrednictwem urządzenia. Jeśli wywłaszczanie jest włączone, żądanie przez aplikację przejęcia wyłącznej kontroli nad urządzeniem powiedzie się, jeśli urządzenie nie jest obecnie używane lub jeśli urządzenie jest używane w trybie udostępnionym, ale żądanie kończy się niepowodzeniem, jeśli inna aplikacja ma już wyłączną kontrolę nad urządzeniem. Jeśli wywłaszczanie jest wyłączone, żądanie przez aplikację przejęcia wyłącznej kontroli nad urządzeniem powiedzie się, jeśli urządzenie nie jest obecnie używane, ale żądanie zakończy się niepowodzeniem, jeśli urządzenie jest już używane w trybie udostępnionym lub w trybie wyłączności.

W systemie Windows Vista ustawienia domyślne dla urządzenia punktu końcowego audio są następujące:

  • Urządzenie może służyć do odtwarzania lub rejestrowania strumieni w trybie wyłącznym.
  • Żądanie użycia urządzenia do odtwarzania lub rejestrowania strumienia w trybie wyłącznym wywłaszcza wszelkie strumienie trybu udostępnionego, który jest obecnie odtwarzany lub rejestrowany za pośrednictwem urządzenia.

Aby zmienić ustawienia trybu wyłącznego urządzenia odtwarzania lub nagrywania

  1. Kliknij prawym przyciskiem myszy ikonę osoby mówiącej w obszarze powiadomień, który znajduje się po prawej stronie paska zadań, a następnie wybierz pozycję urządzenia odtwarzania lub urządzenia do nagrywania . (Alternatywnie uruchom panel sterowania multimediami systemu Windows, Mmsys.cpl, w oknie wiersza polecenia. Aby uzyskać więcej informacji, zobacz Uwagi w DEVICE_STATE_XXX stałe.)
  2. Po pojawieniu się okna dźwięku wybierz pozycję Odtwarzanie lub Nagrywanie. Następnie wybierz wpis na liście nazw urządzeń, a następnie kliknij pozycję Właściwości.
  3. Po pojawieniu się okna właściwości kliknij pozycję Advanced.
  4. Aby umożliwić aplikacjom używanie urządzenia w trybie wyłączności, zaznacz pole wyboru z etykietą Zezwalaj aplikacjom na przejęcie wyłącznej kontroli nad tym urządzeniem. Aby wyłączyć używanie urządzenia w trybie wyłączności, wyczyść pole wyboru.
  5. Jeśli włączono tryb wyłączności użycia urządzenia, możesz określić, czy żądanie wyłącznej kontroli urządzenia zakończy się powodzeniem, jeśli urządzenie jest obecnie odtwarzane lub nagrywa strumienie w trybie udostępnionym. Aby przyznać priorytet aplikacji w trybie wyłącznym na aplikacje w trybie udostępnionym, zaznacz pole wyboru z etykietą Nadaj priorytet aplikacjom trybu wyłącznego. Aby odmówić priorytetu aplikacji w trybie wyłącznym na aplikacje w trybie udostępnionym, wyczyść to pole wyboru.

Poniższy przykład kodu przedstawia sposób odtwarzania strumienia audio o małych opóźnieniach na urządzeniu renderowania audio skonfigurowanym do użycia w trybie wyłączności:

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

W poprzednim przykładzie kodu funkcja PlayExclusiveStream jest uruchamiana w wątku aplikacji, który obsługuje punktu końcowego podczas odtwarzania strumienia renderowania. Funkcja przyjmuje pojedynczy parametr pMySource, który jest wskaźnikiem do obiektu należącego do klasy zdefiniowanej przez klienta MyAudioSource. Ta klasa ma dwie funkcje składowe LoadData i SetFormat, które są wywoływane w przykładzie kodu. Plik MyAudioSource został opisany w Renderowanie strumienia.

Funkcja PlayExclusiveStream wywołuje funkcję pomocnika GetStreamFormat, która negocjuje z domyślnym urządzeniem renderowania w celu określenia, czy urządzenie obsługuje format strumienia w trybie wyłącznym, który jest odpowiedni do użycia przez aplikację. Kod funkcji GetStreamFormat nie jest wyświetlany w przykładzie kodu; wynika to z faktu, że szczegóły jego implementacji zależą całkowicie od wymagań aplikacji. Jednak operację funkcji GetStreamFormat można opisać po prostu — wywołuje IAudioClient::IsFormatSupported metody co najmniej raz, aby określić, czy urządzenie obsługuje odpowiedni format. Wymagania aplikacji określają, które formaty GetStreamFormat przedstawia IsFormatSupported metodę i kolejność ich prezentowania. Aby uzyskać więcej informacji na temat IsFormatSupported, zobacz Device Formats.

Po wywołaniu GetStreamFormat funkcja PlayExclusiveStream wywołuje metodę IAudioClient::GetDevicePeriod metody w celu uzyskania minimalnego okresu urządzenia obsługiwanego przez sprzęt audio. Następnie funkcja wywołuje metodę IAudioClient::Initialize, aby zażądać czasu trwania buforu równego minimalnemu okresowi. Jeśli wywołanie powiedzie się, metoda Initialize przydziela dwa punktu końcowego, z których każdy jest równy czasowi trwania do minimalnego okresu. Później, gdy strumień audio zacznie działać, aplikacja i sprzęt audio będą współdzielić dwa w sposób "ping-pong", czyli podczas gdy aplikacja zapisuje w jednym buforze, sprzęt odczytuje z drugiego buforu.

Przed uruchomieniem strumienia funkcja PlayExclusiveStream wykonuje następujące czynności:

  • Tworzy i rejestruje dojście zdarzeń, za pomocą którego będzie otrzymywać powiadomienia, gdy staną się gotowe do wypełnienia.
  • Wypełnia pierwszy bufor danymi ze źródła audio, aby zmniejszyć opóźnienie od momentu uruchomienia strumienia do momentu słyszania początkowego dźwięku.
  • Wywołuje funkcję AvSetMmThreadCharacteristics, aby zażądać, aby usługa MMCSS zwiększyła priorytet wątku, w którym jest wykonywany element PlayExclusiveStream. (Gdy strumień przestanie działać, AvRevertMmThreadCharacteristics wywołanie funkcji przywraca oryginalny priorytet wątku).

Aby uzyskać więcej informacji na temat AvSetMmThreadCharacteristics i AvRevertMmThreadCharacteristics, zobacz dokumentację zestawu Windows SDK.

Gdy strumień jest uruchomiony, każda iteracja podczas-loop w poprzednim przykładzie kodu wypełnia jeden bufor punktu końcowego. Między iteracjami WaitForSingleObject wywołanie funkcji czeka na zasygnalizowanie dojścia zdarzeń. Gdy uchwyt jest zasygnalizowany, treść pętli wykonuje następujące czynności:

  1. Wywołuje metodę IAudioRenderClient::GetBuffer, aby uzyskać następny bufor.
  2. Wypełnia bufor.
  3. Wywołuje metodę IAudioRenderClient::ReleaseBuffer, aby zwolnić bufor.

Aby uzyskać więcej informacji na temat WaitForSingleObject, zobacz dokumentację zestawu Windows SDK.

Jeśli adapter audio jest kontrolowany przez sterownik WaveRT, sygnał dojścia zdarzeń jest powiązany z powiadomieniami TRANSFERU DMA ze sprzętu audio. W przypadku urządzenia audio USB lub urządzenia audio, które jest kontrolowane przez sterownik WaveCyclic lub WavePci, sygnał dojścia zdarzeń jest powiązany z ukończeniami irps, które przesyłają dane z buforu aplikacji do buforu sprzętowego.

Powyższy przykład kodu wypycha sprzęt audio i system komputerowy do ich limitów wydajności. Po pierwsze, aby zmniejszyć opóźnienie strumienia, aplikacja planuje wątek obsługi buforu w celu użycia minimalnego okresu urządzenia obsługiwanego przez sprzęt audio. Po drugie, aby zapewnić niezawodne wykonywanie wątku w każdym okresie urządzenia, AvSetMmThreadCharacteristics wywołanie funkcji ustawia parametr TaskName na "Pro Audio", czyli w systemie Windows Vista, domyślną nazwą zadania o najwyższym priorytecie. Zastanów się, czy wymagania dotyczące chronometrażu aplikacji mogą być złagodzone bez naruszania jej przydatności. Na przykład aplikacja może zaplanować jego wątek obsługi buforu, aby użyć okresu dłuższego niż minimum. Dłuższy okres może bezpiecznie zezwalać na korzystanie z niższego priorytetu wątku.

zarządzania strumieniami