Przetwarzanie danych multimedialnych przy użyciu czytnika źródłowego
W tym temacie opisano sposób używania czytnika źródeł do przetwarzania danych multimedialnych.
Aby użyć czytnika źródła, wykonaj następujące podstawowe kroki:
- Utwórz instancję czytnika źródłowego.
- Wyliczanie możliwych formatów danych wyjściowych.
- Ustaw rzeczywisty format danych wyjściowych dla każdego strumienia.
- Przetwarzanie danych ze źródła.
W pozostałej części tego tematu szczegółowo opisano te kroki.
- Tworzenie czytnika źródłowego
- Wyliczanie formatów danych wyjściowych
- ustawianie formatów danych wyjściowych
- Przetwarzanie danych multimedialnych
- opróżnianie rurociągu danych
- Uzyskiwanie czasu trwania pliku
- Szukanie
- częstotliwość odtwarzania
- Przyspieszanie sprzętowe
- Tematy pokrewne
Tworzenie czytnika źródłowego
Aby utworzyć wystąpienie czytnika źródła, wywołaj jedną z następujących funkcji:
Funkcja | Opis |
---|---|
MFCreateSourceReaderFromURL |
Przyjmuje adres URL jako dane wejściowe. Ta funkcja używa Source Resolver do utworzenia źródła multimediów na podstawie adresu URL. |
MFCreateSourceReaderFromByteStream |
Pobiera wskaźnik do strumienia bajtów. Ta funkcja używa również narzędzia Source Resolver do utworzenia źródła multimediów. |
MFCreateSourceReaderFromMediaSource |
Przechwytuje wskaźnik do źródła multimediów, które zostało już utworzone. Ta funkcja jest przydatna w przypadku źródeł multimedialnych, których program rozpoznawania źródła nie może utworzyć, takich jak urządzenia przechwytywania lub niestandardowe źródła multimediów. |
Zazwyczaj w przypadku plików multimedialnych należy użyć MFCreateSourceReaderFromURL. W przypadku urządzeń, takich jak kamery internetowe, użyj MFCreateSourceReaderFromMediaSource. (Aby uzyskać więcej informacji na temat urządzeń do przechwytywania w programie Microsoft Media Foundation, zobacz Audio/Video Capture.)
Każda z tych funkcji przyjmuje opcjonalny wskaźnik IMFAttributes, który służy do ustawiania różnych opcji w czytniku źródła, zgodnie z opisem w tematach referencyjnych dotyczących tych funkcji. Aby uzyskać domyślne zachowanie, ustaw ten parametr na wartość null. Każda funkcja zwraca wskaźnik IMFSourceReader jako parametr wyjściowy. Przed wywołaniem dowolnej z tych funkcji należy wywołać funkcję CoInitialize(Ex) i MFStartup.
Poniższy kod tworzy czytnik źródła na podstawie adresu URL.
int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
if (argc < 2)
{
return 1;
}
const WCHAR *pszURL = argv[1];
// Initialize the COM runtime.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
// Initialize the Media Foundation platform.
hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
// Create the source reader.
IMFSourceReader *pReader;
hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
if (SUCCEEDED(hr))
{
ReadMediaFile(pReader);
pReader->Release();
}
// Shut down Media Foundation.
MFShutdown();
}
CoUninitialize();
}
}
Wyliczanie formatów danych wyjściowych
Każde źródło multimediów ma co najmniej jeden strumień. Na przykład plik wideo może zawierać strumień wideo i strumień audio. Format każdego strumienia jest opisywany przy użyciu typu nośnika reprezentowanego przez interfejs IMFMediaType. Aby uzyskać więcej informacji na temat typów multimediów, zobacz Typy multimediów. Musisz sprawdzić typ nośnika, aby zrozumieć format danych pobieranych z czytnika źródła.
Początkowo każdy strumień ma domyślny format, który można znaleźć, wywołując metodę IMFSourceReader::GetCurrentMediaType:
Dla każdego strumienia źródło multimediów oferuje listę możliwych typów multimediów dla tego strumienia. Liczba typów zależy od źródła. Jeśli źródło reprezentuje plik multimedialny, zazwyczaj istnieje tylko jeden typ na strumień. Kamera internetowa, z drugiej strony, może być w stanie przesyłać strumieniowo wideo w kilku różnych formatach. W takim przypadku aplikacja może wybrać format używany z listy typów multimediów.
Aby uzyskać jeden z typów multimediów dla strumienia, wywołaj metodę IMFSourceReader::GetNativeMediaType. Ta metoda przyjmuje dwa parametry indeksu: indeks strumienia i indeks do listy typów multimediów dla strumienia. Aby wyliczyć wszystkie typy dla strumienia, należy zwiększać indeks listy, zachowując stały indeks strumienia. Gdy indeks listy wykracza poza granice, GetNativeMediaType zwraca wartość MF_E_NO_MORE_TYPES.
HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
HRESULT hr = S_OK;
DWORD dwMediaTypeIndex = 0;
while (SUCCEEDED(hr))
{
IMFMediaType *pType = NULL;
hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
if (hr == MF_E_NO_MORE_TYPES)
{
hr = S_OK;
break;
}
else if (SUCCEEDED(hr))
{
// Examine the media type. (Not shown.)
pType->Release();
}
++dwMediaTypeIndex;
}
return hr;
}
Aby wyliczyć typy multimediów dla każdego strumienia, zwiększ indeks strumienia. Gdy indeks strumienia wykracza poza granice, GetNativeMediaType zwraca wartość MF_E_INVALIDSTREAMNUMBER.
HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
HRESULT hr = S_OK;
DWORD dwStreamIndex = 0;
while (SUCCEEDED(hr))
{
hr = EnumerateTypesForStream(pReader, dwStreamIndex);
if (hr == MF_E_INVALIDSTREAMNUMBER)
{
hr = S_OK;
break;
}
++dwStreamIndex;
}
return hr;
}
Ustawianie formatów wyjściowych
Aby zmienić format danych wyjściowych, wywołaj metodę IMFSourceReader::SetCurrentMediaType. Ta metoda przyjmuje indeks strumienia i typ nośnika:
hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);
W przypadku typu nośnika zależy to od tego, czy chcesz wstawić dekoder.
- Aby pobrać dane bezpośrednio ze źródła bez dekodowania, użyj jednego z typów zwracanych przez GetNativeMediaType.
- Aby zdekodować strumień, utwórz nowy typ nośnika opisujący żądany format nieskompresowany.
W przypadku dekodera utwórz typ nośnika w następujący sposób:
- Wywołaj MFCreateMediaType, aby utworzyć nowy typ nośnika.
- Ustaw atrybut MF_MT_MAJOR_TYPE, aby określić dźwięk lub wideo.
- Ustaw atrybut MF_MT_SUBTYPE, aby określić podtyp formatu dekodowania. (Zobacz identyfikatory GUID podtypu audio i identyfikatory GUID podtypu wideo ).
- Wywołaj IMFSourceReader::SetCurrentMediaType.
Czytnik źródła automatycznie załaduje dekoder. Aby uzyskać pełne szczegóły zdekodowanego formatu, wywołaj metodę IMFSourceReader::GetCurrentMediaType po wywołaniu metody SetCurrentMediaType
Poniższy kod konfiguruje strumień wideo dla RGB-32 i strumień audio dla dźwięku PCM.
HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
IMFMediaType *pNativeType = NULL;
IMFMediaType *pType = NULL;
// Find the native format of the stream.
HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
if (FAILED(hr))
{
return hr;
}
GUID majorType, subtype;
// Find the major type.
hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
if (FAILED(hr))
{
goto done;
}
// Define the output type.
hr = MFCreateMediaType(&pType);
if (FAILED(hr))
{
goto done;
}
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
if (FAILED(hr))
{
goto done;
}
// Select a subtype.
if (majorType == MFMediaType_Video)
{
subtype= MFVideoFormat_RGB32;
}
else if (majorType == MFMediaType_Audio)
{
subtype = MFAudioFormat_PCM;
}
else
{
// Unrecognized type. Skip.
goto done;
}
hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
if (FAILED(hr))
{
goto done;
}
// Set the uncompressed format.
hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
if (FAILED(hr))
{
goto done;
}
done:
SafeRelease(&pNativeType);
SafeRelease(&pType);
return hr;
}
Przetwarzanie danych multimedialnych
Aby pobrać dane multimedialne ze źródła, wywołaj metodę IMFSourceReader::ReadSample, jak pokazano w poniższym kodzie.
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
hr = pReader->ReadSample(
MF_SOURCE_READER_ANY_STREAM, // Stream index.
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llTimeStamp, // Receives the time stamp.
&pSample // Receives the sample or NULL.
);
Pierwszy parametr to indeks strumienia, dla którego chcesz pobrać dane. Możesz również określić MF_SOURCE_READER_ANY_STREAM, aby uzyskać następne dostępne dane z dowolnego strumienia. Drugi parametr zawiera opcjonalne flagi; zobacz MF_SOURCE_READER_CONTROL_FLAG, aby zapoznać się z listą tych elementów. Trzeci parametr odbiera indeks strumienia, który faktycznie generuje dane. Te informacje będą potrzebne, jeśli ustawisz pierwszy parametr na MF_SOURCE_READER_ANY_STREAM. Czwarty parametr odbiera flagi stanu wskazujące różne zdarzenia, które mogą wystąpić podczas odczytywania danych, takie jak zmiany formatu w strumieniu. Aby uzyskać listę flag stanu, zobacz MF_SOURCE_READER_FLAG.
Jeśli źródło multimediów może wygenerować dane dla żądanego strumienia, ostatni parametr ReadSample odbiera wskaźnik do interfejsu IMFSample próbki multimedialnej. Użyj przykładowego materiału multimedialnego, aby:
- Pobierz wskaźnik do danych multimedialnych.
- Pobierz czas prezentacji i przykładowy czas trwania.
- Pobierz atrybuty opisujące przeplatanie, dominację pola i inne aspekty próbki.
Zawartość danych multimedialnych zależy od formatu strumienia. W przypadku nieskompresowanego strumienia wideo każda próbka multimedialna zawiera jedną ramkę wideo. W przypadku nieskompresowanego strumienia audio każdy przykładowy nośnik zawiera sekwencję klatek dźwiękowych.
Metoda ReadSample może zwrócić S_OK, a mimo to nie zwrócić próbki multimediów w parametrze pSample. Na przykład, po osiągnięciu końca pliku, ReadSample ustawia flagę MF_SOURCE_READERF_ENDOFSTREAM w dwFlags i ustawia pSample na NULL. W takim przypadku metoda ReadSample zwraca S_OK, ponieważ nie wystąpił błąd, mimo że parametr pSample jest ustawiony na wartość NULL. Dlatego zawsze należy sprawdzić wartość pSample przed jej wyłuszczeniem.
Poniższy kod pokazuje, jak wywołać ReadSample w pętli i sprawdzać zwrócone informacje przez metodę, aż do osiągnięcia końca pliku multimedialnego.
HRESULT ProcessSamples(IMFSourceReader *pReader)
{
HRESULT hr = S_OK;
IMFSample *pSample = NULL;
size_t cSamples = 0;
bool quit = false;
while (!quit)
{
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
hr = pReader->ReadSample(
MF_SOURCE_READER_ANY_STREAM, // Stream index.
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llTimeStamp, // Receives the time stamp.
&pSample // Receives the sample or NULL.
);
if (FAILED(hr))
{
break;
}
wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
wprintf(L"\tEnd of stream\n");
quit = true;
}
if (flags & MF_SOURCE_READERF_NEWSTREAM)
{
wprintf(L"\tNew stream\n");
}
if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
{
wprintf(L"\tNative type changed\n");
}
if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
wprintf(L"\tCurrent type changed\n");
}
if (flags & MF_SOURCE_READERF_STREAMTICK)
{
wprintf(L"\tStream tick\n");
}
if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
{
// The format changed. Reconfigure the decoder.
hr = ConfigureDecoder(pReader, streamIndex);
if (FAILED(hr))
{
break;
}
}
if (pSample)
{
++cSamples;
}
SafeRelease(&pSample);
}
if (FAILED(hr))
{
wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
}
else
{
wprintf(L"Processed %d samples\n", cSamples);
}
SafeRelease(&pSample);
return hr;
}
Opróżnianie potoku danych
Podczas przetwarzania danych dekoder lub inna transformacja mogą buforować próbki wejściowe. Na poniższym diagramie aplikacja wywołuje ReadSample i otrzymuje próbkę z czasem prezentacji równym t1. Dekoder przechowuje próbki dla t2 i t3.
Przy następnym wywołaniu polecenia ReadSampleczytnik źródła może przekazać t4 do dekodera i zwrócićt2 do aplikacji.
Jeśli chcesz zdekodować wszystkie próbki, które są obecnie buforowane w dekoderze, bez przekazywania nowych próbek do dekodera, ustaw flagę MF_SOURCE_READER_CONTROLF_DRAIN w dwControlFlags parametru ReadSample. Kontynuuj wykonywanie tej czynności w pętli, dopóki ReadSample nie zwróci wskaźnika próbki NULL. W zależności od tego, jak dekoder buforuje próbki, może to nastąpić natychmiast lub po kilku wywołaniach ReadSample.
Pobieranie czasu trwania pliku
Aby uzyskać czas trwania pliku multimedialnego, wywołaj metodę IMFSourceReader::GetPresentationAttribute i zażądaj atrybutu MF_PD_DURATION, jak pokazano w poniższym kodzie.
HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
PROPVARIANT var;
HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,
MF_PD_DURATION, &var);
if (SUCCEEDED(hr))
{
hr = PropVariantToInt64(var, phnsDuration);
PropVariantClear(&var);
}
return hr;
}
Pokazana tutaj funkcja pobiera czas trwania w jednostkach 100-nanosekundowych. Podziel przez 10 000 000, aby uzyskać czas trwania w sekundach.
Szukając
Źródło multimediów, które pobiera dane z pliku lokalnego, zwykle może wyszukiwać dowolne pozycje w pliku. Urządzenia do przechwytywania, takie jak kamery internetowe, zazwyczaj nie mogą przewijać, ponieważ dane są na żywo. Źródło, które przesyła strumieniowo dane za pośrednictwem sieci, może być w stanie wyszukiwać w zależności od protokołu przesyłania strumieniowego sieci.
Aby dowiedzieć się, czy źródło multimediów umożliwia przeszukiwanie, wywołaj IMFSourceReader::GetPresentationAttribute i uzyskaj atrybut MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS, jak pokazano w poniższym kodzie:
HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
ULONG flags = 0;
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hr = pReader->GetPresentationAttribute(
MF_SOURCE_READER_MEDIASOURCE,
MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS,
&var);
if (SUCCEEDED(hr))
{
hr = PropVariantToUInt32(var, &flags);
}
if (SUCCEEDED(hr))
{
*pulFlags = flags;
}
PropVariantClear(&var);
return hr;
}
Ta funkcja pobiera zestaw flag możliwości ze źródła. Te flagi są definiowane w enumeracji MFMEDIASOURCE_CHARACTERISTICS. Dwie flagi odnoszą się do wyszukiwania:
Flaga | Opis |
---|---|
MFMEDIASOURCE_CAN_SEEK |
Źródło może poszukiwać. |
MFMEDIASOURCE_HAS_SLOW_SEEK |
Poszukiwanie może zająć dużo czasu. Na przykład źródło może wymagać pobrania całego pliku, zanim będzie można go przeszukać. (Nie ma ścisłych kryteriów, aby źródło zwracało tę flagę). |
Następujące testy kodu dla flagi MFMEDIASOURCE_CAN_SEEK.
BOOL SourceCanSeek(IMFSourceReader *pReader)
{
BOOL bCanSeek = FALSE;
ULONG flags;
if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
{
bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
}
return bCanSeek;
}
Aby wyszukać, wywołaj metodę IMFSourceReader::SetCurrentPosition, jak to pokazano w poniższym kodzie.
HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
PROPVARIANT var;
HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
if (SUCCEEDED(hr))
{
hr = pReader->SetCurrentPosition(GUID_NULL, var);
PropVariantClear(&var);
}
return hr;
}
Pierwszy parametr udostępnia format czasu używany do określania pozycji wyszukiwania. Wszystkie źródła multimediów w programie Media Foundation muszą obsługiwać jednostki 100-nanosekundowe, oznaczane przez wartość GUID_NULL. Drugi parametr jest PROPVARIANT, który zawiera pozycję przeszukiwania. W przypadku 100-nanosekundowych jednostek czasu typ danych jest LONGLONG.
Należy pamiętać, że nie każde źródło multimediów pozwala na precyzyjne przeszukiwanie ramek. Dokładność wyszukiwania zależy od kilku czynników, takich jak interwał klatek kluczowych, czy plik multimedialny zawiera indeks i czy dane mają stałą lub zmienną szybkość bitów. W związku z tym po dążeniu do pozycji w pliku nie ma gwarancji, że sygnatura czasowa na następnej próbce będzie dokładnie zgodna z żądaną pozycją. Ogólnie rzecz biorąc, rzeczywista pozycja nie będzie późniejsza niż żądana, ponieważ można odrzucać próbki aż do osiągnięcia żądanego punktu w strumieniu.
Częstotliwość odtwarzania
Chociaż szybkość odtwarzania można ustawić przy użyciu czytnika źródła, zazwyczaj nie jest to bardzo przydatne, z następujących powodów:
- Czytnik źródła nie obsługuje odtwarzania wstecznego, nawet jeśli źródło multimediów.
- Aplikacja kontroluje czas prezentacji, dzięki czemu aplikacja może implementować szybkie lub powolne odtwarzanie bez ustawiania szybkości w źródle.
- Niektóre źródła multimediów obsługują tryb cieńszy, w którym źródło dostarcza mniej próbek — zazwyczaj tylko klatek kluczowych. Jeśli jednak chcesz usunąć ramki niekluczowe, możesz sprawdzić każdą próbkę dla atrybutu MFSampleExtension_CleanPoint.
Aby ustawić szybkość odtwarzania przy użyciu Source Reader, wywołaj metodę IMFSourceReader::GetServiceForStream, aby uzyskać interfejsy IMFRateSupport i IMFRateControl ze źródła multimediów.
Przyspieszanie sprzętowe
Czytnik źródłowy jest zgodny z funkcją Microsoft DirectX Video Acceleration (DXVA) 2.0 na potrzeby sprzętowego dekodowania wideo przyspieszonego. Aby użyć DXVA z odczytywaczem źródła, wykonaj następujące kroki.
- Utwórz urządzenie Microsoft Direct3D.
- Wywołaj funkcję DXVA2CreateDirect3DDeviceManager9, aby utworzyć menedżera urządzeń Direct3D. Ta funkcja pobiera wskaźnik do interfejsu IDirect3DDeviceManager9.
- Wywołaj metodę IDirect3DDeviceManager9::ResetDevice, przekazując wskaźnik do urządzenia Direct3D.
- Utwórz magazyn atrybutów, wywołując funkcję MFCreateAttributes.
- Utwórz czytnik źródła. Przekaż magazyn atrybutów w parametrze pAttributes funkcji tworzenia.
Po podaniu urządzenia Direct3D czytnik źródła przydziela przykłady wideo, które są zgodne z interfejsem API procesora wideo DXVA. Możesz użyć przetwarzania wideo DXVA do wykonania deinterlacingu sprzętowego lub mieszania wideo. Aby uzyskać więcej informacji, zobacz DXVA Video Processing. Ponadto jeśli dekoder obsługuje DXVA 2.0, użyje urządzenia Direct3D do wykonania dekodowania przyspieszonego sprzętowo.
Ważny
Począwszy od systemu Windows 8, można użyć IMFDXGIDeviceManager zamiast IDirect3DDeviceManager9. W przypadku aplikacji ze Sklepu Windows należy użyć IMFDXGIDeviceManager. Aby uzyskać więcej informacji, zobacz interfejsy API wideo Direct3D 11 .
Tematy pokrewne