Renderowanie strumienia
Klient wywołuje metody w interfejsie IAudioRenderClient do zapisywania danych renderowania w buforze punktu końcowego. W przypadku strumienia w trybie udostępnionym klient udostępnia bufor punktu końcowego aparatowi audio. W przypadku strumienia trybu wyłącznego klient udostępnia bufor końcowy urządzeniu audio. Aby zażądać buforu punktu końcowego o określonym rozmiarze, klient wywołuje metodę IAudioClient::Initialize. Aby uzyskać rozmiar przydzielonego buforu, który może różnić się od żądanego rozmiaru, klient wywołuje metodę IAudioClient::GetBufferSize.
Aby przenieść strumień danych renderowania za pośrednictwem buforu punktu końcowego, klient alternatywnie wywołuje metodę IAudioRenderClient::GetBuffer i metodę IAudioRenderClient::ReleaseBuffer. Klient uzyskuje dostęp do danych w buforze punktu końcowego jako serii pakietów danych. Wywołanie GetBuffer pobiera następny pakiet, aby klient mógł wypełnić go danymi renderowania. Po zapisaniu danych do pakietu klient wywołuje ReleaseBuffer, aby dodać ukończony pakiet do kolejki renderowania.
W przypadku buforu renderowania wartość dopełnienia podawana przez metodę IAudioClient::GetCurrentPadding reprezentuje ilość danych renderowania, które są zakolejkowane do odtwarzania w buforze. Aplikacja renderująca może użyć wartości dopełnienia, aby określić, ile nowych danych może bezpiecznie zapisać w buforze bez ryzyka nadpisania wcześniej zapisanych danych, których silnik dźwiękowy nie odczytał jeszcze z bufora. Dostępne miejsce to po prostu rozmiar buforu pomniejszony o rozmiar wyrównania. Klient może zażądać rozmiaru pakietu, który reprezentuje część lub całość dostępnego miejsca w jego następnym wywołaniu GetBuffer.
Rozmiar pakietu jest wyrażony w ramkach audio . Ramka audio w strumieniu PCM jest zestawem próbek (zestaw zawiera jedną próbkę dla każdego kanału w strumieniu), która jest odtwarzana lub rejestrowana w tym samym czasie (znacznik zegara). W związku z tym rozmiar ramki audio jest rozmiarem próbki pomnożonym przez liczbę kanałów w strumieniu. Na przykład rozmiar ramki dla strumienia stereo (2-kanałowego) z 16-bitowymi próbkami to cztery bajty.
Poniższy przykład kodu przedstawia sposób odtwarzania strumienia audio na domyślnym urządzeniu renderowania:
//-----------------------------------------------------------
// 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;
}
W poprzednim przykładzie funkcja PlayAudioStream przyjmuje jeden parametr, pMySource
, który jest wskaźnikiem do obiektu należącego do klasy zdefiniowanej przez klienta, MyAudioSource, z dwiema funkcjami składowymi LoadData i SetFormat. Przykładowy kod nie zawiera implementacji elementu MyAudioSource, ponieważ:
- Żaden z członków klasy nie komunikuje się bezpośrednio z żadną z metod w interfejsach w WASAPI.
- Klasa może być zaimplementowana na różne sposoby, w zależności od wymagań klienta. (Na przykład może odczytywać dane renderowania z pliku WAV i wykonywać konwersję na bieżąco do formatu strumienia).
Jednak niektóre informacje na temat działania tych dwóch funkcji są przydatne do zrozumienia przykładu.
Funkcja LoadData zapisuje określoną liczbę ramek audio (pierwszy parametr) do wskazanej lokalizacji bufora (drugi parametr). (Rozmiar ramki audio jest liczbą kanałów w strumieniu pomnożonym przez rozmiar próbki). Funkcja PlayAudioStream używa loadData do wypełniania części udostępnionego buforu danymi audio. Funkcja SetFormat określa format funkcji LoadData do użycia dla danych. Jeśli funkcja LoadData może zapisać co najmniej jedną ramkę w określonej lokalizacji buforu, ale zabraknie jej danych zanim zapisze określoną liczbę ramek, to zapisuje ciszę w pozostałych ramkach.
Dopóki LoadData uda się zapisać co najmniej jedną ramkę rzeczywistych danych (nie milczenia) do określonej lokalizacji buforu, zwraca 0 przez jego trzeci parametr, który w poprzednim przykładzie kodu jest wskaźnikiem wyjściowym na zmienną flags
. Gdy LoadData jest poza danymi i nie może zapisać nawet pojedynczej ramki do określonej lokalizacji bufora, nie zapisuje nic do bufora (nawet ciszy) i zapisuje wartość AUDCLNT_BUFFERFLAGS_SILENT do zmiennej flags
. Zmienna flags
przekazuje tę wartość do metody IAudioRenderClient::ReleaseBuffer, która odpowiada, wypełniając określoną liczbę ramek w buforze ciszą.
W wywołaniu metody IAudioClient::Initialize funkcja PlayAudioStream w poprzednim przykładzie żąda udostępnionego buforu, który ma czas trwania jednej sekundy. (Przydzielony bufor może mieć nieco dłuższy czas trwania). W początkowych wywołaniach metody IAudioRenderClient::GetBuffer i metody IAudioRenderClient::ReleaseBuffer, funkcja wypełnia cały bufor przed wywołaniem metody IAudioClient::Start, aby rozpocząć odtwarzanie buforu.
W ramach pętli głównej funkcja iteracyjnie wypełnia połowę buforu w odstępach pół sekundy. Tuż przed każdym wywołaniem funkcji Windows Sleep w pętli głównej bufor jest pełny lub prawie pełny. Gdy zostanie zwrócone wywołanie uśpienia, bufor jest o połowę pełny. Pętla kończy się po tym, jak ostatnie wywołanie funkcji LoadData ustawia zmienną flags
na wartość AUDCLNT_BUFFERFLAGS_SILENT. W tym momencie bufor zawiera co najmniej jedną ramkę danych rzeczywistych i może zawierać nawet pół sekundy rzeczywistych danych. Pozostała część buforu zawiera ciszę. Wywołanie Sleep, które następuje zgodnie z pętlą, zapewnia wystarczający czas (pół sekundy), aby odtworzyć wszystkie pozostałe dane. Cisza zgodna z danymi uniemożliwia niepożądane dźwięki przed wywołaniem IAudioClient::Stop metoda zatrzymuje strumień audio. Aby uzyskać więcej informacji na temat Sleep, zobacz dokumentację Windows SDK.
Po wywołaniu metody IAudioClient::Initialize strumień pozostaje otwarty, dopóki klient nie zwolni wszystkich odwołań do interfejsu IAudioClient oraz wszystkich odwołań do interfejsów usługi uzyskanych przez klienta za pośrednictwem metody IAudioClient::GetService. Ostatnie wywołanie Release zamyka strumień.
Funkcja PlayAudioStream w poprzednim przykładzie kodu wywołuje funkcję CoCreateInstance w celu utworzenia modułu wyliczającego dla urządzeń punktu końcowego audio w systemie. O ile program wywołujący wcześniej nie wykonał wywołania funkcji CoCreateInstance lub CoInitializeEx w celu zainicjowania biblioteki COM, wywołanie CoCreateInstance zakończy się niepowodzeniem. Aby uzyskać więcej informacji na temat CoCreateInstance, CoCreateInstancei CoInitializeEx, zobacz dokumentację zestawu Windows SDK.
Tematy pokrewne