mfreadwrite.hをインクルードしたときに、
1>c:\program files (x86)\***\mfreadwrite.h(273): error C2065: 'IMFMediaSource': 定義されていない識別子です。 1>c:\program files (x86)\***\mfreadwrite.h(273): error C2065: 'pMediaSource': 定義されていない識別子です。 1>c:\program files (x86)\***\mfreadwrite.h(274): error C2065: 'pAttributes': 定義されていない識別子です。 ...
のようにC2065でエラーとなるときには、先にmfidl.hをインクルードします。c++ - Error in windows file mfreadwrite.h - Stack Overflow
URLからSource Readerを作成できます。
HRESULT MFCreateSourceReaderFromURL( LPCWSTR pwszURL, // 開くMedia FileのURL IMFAttributes *pAttributes, // Source Readerを設定するための、IMFAttributesへのポインタ IMFSourceReader **ppSourceReader // 結果を受け取るための、IMFSourceReaderへのポインタ );MFCreateSourceReaderFromURL function | Microsoft Learn
無効なファイルを指定すると、次のようなエラーが返されます。
これで開いたファイルは、このSource Readerを解放するまでロックされます。
Media SourceからSource Readerを作成できます。
HRESULT MFCreateSourceReaderFromMediaSource( IMFMediaSource *pMediaSource, IMFAttributes *pAttributes, IMFSourceReader **ppSourceReader );MFCreateSourceReaderFromMediaSource function (mfreadwrite.h) | Microsoft Learn
Media Sourceから、次のMedia Sampleを読み込めます。
読み込まれたMedia Sampleに含まれる音声サンプルはMedia Dataの一部であり、すべてを読み込むにはMedia SourceからすべてのMedia Sampleを読み込む必要があります。
HRESULT ReadSample( DWORD dwStreamIndex, // データを入力するストリーム DWORD dwControlFlags, // MF_SOURCE_READER_CONTROL_FLAG列挙型または0 DWORD *pdwActualStreamIndex, // ストリームのインデックスを受け取るためのポインタ DWORD *pdwStreamFlags, // 結果の状態を受け取るためのポインタ。状態はMF_SOURCE_READER_FLAG列挙型の値 LONGLONG *pllTimestamp, // サンプルのタイムスタンプ、またはpdwStreamFlagsで示されるストリームイベントの時間 [100nsec単位] IMFSample **ppSample // サンプルを受け取るためのポインタ );IMFSourceReader::ReadSample | Microsoft Learn
読み込んだ結果は、引数のpdwStreamFlagsから確認できます。
識別子 | 意味 |
---|---|
MF_SOURCE_READERF_ERROR | An error occurred. If you receive this flag, do not make any further calls to IMFSourceReader methods. |
MF_SOURCE_READERF_ENDOFSTREAM | source readerはストリームの最後に到達した。 |
MF_SOURCE_READERF_NEWSTREAM | One or more new streams were created. Respond to this flag by doing at least one of the following:
|
MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED | The native format has changed for one or more streams. The native format is the format delivered by the media source before any decoders are inserted. |
MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED | The current media has type changed for one or more streams. To get the current media type, call the IMFSourceReader::GetCurrentMediaType method. |
MF_SOURCE_READERF_STREAMTICK | There is a gap in the stream. This flag corresponds to an MEStreamTick event from the media source. |
MF_SOURCE_READERF_ALLEFFECTSREMOVED | All transforms inserted by the application have been removed for a particular stream. This could be due to a dynamic format change from a source or decoder that prevents custom transforms from being used because they cannot handle the new media type. |
同期モードでは、次のMedia Sampleが得られるまでスレッドがブロックされます。Synchronous Mode - IMFSourceReader::ReadSample (mfreadwrite.h) - Win32 apps | Microsoft Learn
HRESULT hr = S_OK; // COMライブラリを初期化する hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); // Media Foundationを初期化する hr = MFStartup(MF_VERSION); // ファイルを開く LPCWSTR pwszURL = L"C:\\sample.wav"; IMFSourceReader* pSourceReader = NULL; hr = MFCreateSourceReaderFromURL(pwszURL, NULL, &pSourceReader); // 出力フォーマットを指定する UINT32 sampleRate = 2000; UINT32 bitsPerSample = 8; UINT32 channels = 1; UINT32 blockAlign = channels * (bitsPerSample / 8); UINT32 bytesPerSecond = blockAlign * sampleRate; IMFMediaType* pNewMediaType = NULL; hr = MFCreateMediaType(&pNewMediaType); hr = pNewMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); hr = pNewMediaType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); hr = pNewMediaType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels); hr = pNewMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate); hr = pNewMediaType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, blockAlign); hr = pNewMediaType->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSecond); hr = pNewMediaType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample); hr = pNewMediaType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE); hr = pSourceReader->SetCurrentMediaType( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, NULL, pNewMediaType ); // ストリームが確実に選択されるようにする hr = pSourceReader->SetStreamSelection( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE ); // データを読み込む BYTE* pBuffer = NULL; IMFSample* pSample = NULL; IMFMediaBuffer* pMediaBuffer = NULL; while (true) { DWORD dwStreamFlags = 0; hr = pSourceReader->ReadSample( (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &dwStreamFlags, NULL, &pSample ); if (dwStreamFlags) break; if (pSample == NULL) continue; // サンプルからバッファへのポインタを取得する hr = pSample->ConvertToContiguousBuffer(&pMediaBuffer); DWORD cbCurrentLength = 0; hr = pMediaBuffer->Lock(&pBuffer, NULL, &cbCurrentLength); // データが格納されている pBufferを処理する hr = pMediaBuffer->Unlock(); pMediaBuffer->Release(); pBuffer = NULL; pSample->Release(); pSample = NULL; } pSourceReader->Release(); pNewMediaType->Release(); if (pBuffer) pMediaBuffer->Unlock(); pMediaBuffer->Release(); if (pSample) pSample->Release(); // 終了処理 MFShutdown(); CoUninitialize();Audio Clip Sample - Win32 apps | Microsoft Learn
非同期モードでは即座に制御が戻り、操作が完了したときにIMFSourceReaderCallback::OnReadSample()が呼ばれます。Asynchronous Mode - IMFSourceReader::ReadSample (mfreadwrite.h) - Win32 apps | Microsoft Learn
まず、IMFSourceReaderCallbackを実装したクラスを定義します。
class SourceReaderCallback : public IMFSourceReaderCallback { public: long m_nRefCount; // 参照カウント CRITICAL_SECTION m_critsec; // クリティカル セクション HANDLE m_hEvent; // 同期オブジェクトへのハンドル HRESULT m_hrStatus; DWORD m_dwStreamFlags; IMFSample *m_pSample; public: SourceReaderCallback(HANDLE hEvent) : m_nRefCount(1), m_hEvent(hEvent), m_hrStatus(S_OK), m_dwStreamFlags(0), m_pSample(NULL) { InitializeCriticalSection(&m_critsec); } ~SourceReaderCallback() { CloseHandle(m_hEvent); DeleteCriticalSection(&m_critsec); } // IMFSourceReaderCallbackはIUnknownを継承するため、それを実装する HRESULT QueryInterface(REFIID iid, void** ppv) { static const QITAB qit[] = { QITABENT(SourceReaderCallback, IMFSourceReaderCallback),{ 0 } }; return QISearch(this, qit, iid, ppv); } ULONG AddRef() { return InterlockedIncrement(&m_nRefCount); } ULONG Release() { ULONG uCount = InterlockedDecrement(&m_nRefCount); if (uCount == 0) delete this; return uCount; } // 操作が完了したときに呼ばれる関数 HRESULT OnReadSample( HRESULT hrStatus, DWORD /* dwStreamIndex */, DWORD dwStreamFlags, LONGLONG /* llTimestamp */, IMFSample *pSample) { EnterCriticalSection(&m_critsec); // クリティカル セクションの所有権を獲得 // 結果をクラス フィールドで保持 m_hrStatus = hrStatus; m_dwStreamFlags = dwStreamFlags; m_pSample = pSample; if (m_pSample != NULL) m_pSample->AddRef(); LeaveCriticalSection(&m_critsec); SetEvent(m_hEvent); // 同期オブジェクトをシグナル状態にして、操作の完了を通知 return S_OK; } HRESULT OnEvent(DWORD, IMFMediaEvent *) { return S_OK; } HRESULT OnFlush(DWORD) { return S_OK; } HRESULT Wait(DWORD dwMilliseconds, DWORD *dwStreamFlags, IMFSample **pSample) { // 同期オブジェクトがシグナル状態になるか、タイムアウトするまで待機 DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds); if (dwResult == WAIT_TIMEOUT) // タイムアウト { return E_PENDING; } EnterCriticalSection(&m_critsec); // OnReadSample()で取得した値を返す *dwStreamFlags = m_dwStreamFlags; *pSample = m_pSample; if (*pSample != NULL) (*pSample)->AddRef(); SafeRelease(&_pSample); HRESULT hr = m_hrStatus; LeaveCriticalSection(&m_critsec); return hr; } };
次にIMFSourceReaderを作成するときに渡すIMFAttributesに、このコールバックのポインタを関連付けます。
// 同期オブジェクトへのハンドルを作成 HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // コールバックを作成 SourceReaderCallback *pCallback = new SourceReaderCallback(hEvent); hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
そしてReadSample()を呼ぶと、コールバックを関連付けているため即座に制御が戻されます。このとき最後の4つの引数pdwActualStreamIndex、pdwStreamFlags、pllTimestamp、ppSampleはNULLとします。
hr = pSourceReader->ReadSample( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, NULL, NULL, NULL); DWORD dwTimeout = 1000; DWORD dwStreamFlags = 0; IMFSample *pSampleTmp = NULL; // OnReadSample()が呼ばれて、結果が返されるまで待機 hr = pCallback->Wait(dwTimeout, &dwStreamFlags, &pSampleTmp); if (hr == E_PENDING) // timed out
非同期とすることをより意義あるものとするには、この例のように結果が返されるまで待機せず、OnReadSample()で処理するようにします。Using the Source Reader in Asynchronous Mode - Win32 apps | Microsoft Learn
非同期のReadSample()の呼び出しは、Flush()で放棄できます。IMFSourceReader::Flush (mfreadwrite.h) - Win32 apps | Microsoft Learn
指定のストリームにMedia Typeを設定できます。元とは異なる形式を指定した場合には、ReadSample()で読み込むときにその形式に変換されます。
HRESULT SetCurrentMediaType( DWORD dwStreamIndex, // 設定するストリーム DWORD *pdwReserved, // 予約されている引数。NULLを指定する IMFMediaType *pMediaType );IMFSourceReader::SetCurrentMediaType | Microsoft Learn
下表の値が返されます。
値 | 識別子 | 意味 |
---|---|---|
0x0 | S_OK | The method succeeded. |
0xC00D36B4 | MF_E_INVALIDMEDIATYPE | At least one decoder was found for the native stream type, but the type specified by pMediaType was rejected.
(メディアの種類に指定されたデータが無効か、矛盾するか、またはこのオブジェクトではサポートされていません。) |
0xC00D36B2 | MF_E_INVALIDREQUEST | One or more sample requests are still pending. |
0xC00D36B3 | MF_E_INVALIDSTREAMNUMBER | The dwStreamIndex parameter is invalid. |
0xC00D5212 | MF_E_TOPO_CODEC_NOT_FOUND | Could not find a decoder for the native stream type. |
指定のストリームの、現在のMedia Typeを取得できます。SetCurrentMediaType()で設定したストリームからは、その値が返されます。
HRESULT GetCurrentMediaType(
DWORD dwStreamIndex, // 要求するストリーム
IMFMediaType **ppMediaType
);
IMFSourceReader::GetCurrentMediaType | Microsoft Learn
dwStreamIndexで取得するストリームの種類を指定します。
値 | 種類 |
---|---|
MF_SOURCE_READER_FIRST_VIDEO_STREAM | ストリームの最初の映像 |
MF_SOURCE_READER_FIRST_AUDIO_STREAM | ストリームの最初の音声 |
結果は、Media Type Attributesで定義される属性を含む型で返されます。
Media SourceのもともとのMedia Typeを取得できます。SetCurrentMediaType()で新しい値を設定していなければ、GetCurrentMediaType()と同じ結果となります。IMFSourceReader::GetNativeMediaType (mfreadwrite.h) | Microsoft Learn
Media Sourceの新しい位置まで移動させられます。
HRESULT SetCurrentPosition( REFGUID guidTimeFormat, // 時間の書式を指定するGUID。GUID_NULLとすることで、100nsec単位となる REFPROPVARIANT varPosition // 再生が開始される位置 );IMFSourceReader::SetCurrentPosition | Microsoft Learn
実際に移動する位置はメディアの内容に依存するため、正確性は保証されません。メディアに映像ストリームが含まれる場合には、キーフレーム (key frame) に最も近い位置に移動します。
PROPVARIANT varPosition; PropVariantInit(&varPosition); varPosition.vt = VT_I8; // guidTimeFormatをGUID_NULLとするならば、VT_I8とする varPosition.hVal.QuadPart = 10000000; // 1秒 (10000000[100nsec]) HRESULT hr = pSourceReader->SetCurrentPosition(GUID_NULL, varPosition); PropVariantClear(&varPosition);
指定の位置へ移動できないときには「現在のオフセットでの操作は許可されていません。(The operation on the current offset is not permitted.)」として、MF_E_INVALID_POSITIONが返されます。
基礎となるMedia Sourceから属性を取得できます。
HRESULT GetPresentationAttribute( DWORD dwStreamIndex, // 要求するストリーム REFGUID guidAttribute, // 検索する属性のGUID PROPVARIANT *pvarAttribute );IMFSourceReader::GetPresentationAttribute | Microsoft Learn
guidAttributeは、要求するdwStreamIndexによって
にある値を用います。
たとえばメディアの時間は、次のように取得できます。
HRESULT hr = S_OK; PROPVARIANT varAttribute; LONGLONG duration; hr = pSourceReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &varAttribute); hr = PropVariantToInt64(varAttribute, &duration); PropVariantClear(&varAttribute);
IMFSourceReader::ReadSample()の呼び出し前に、
IMFMediaType* pNewMediaType = NULL; hr = MFCreateMediaType(&pNewMediaType); hr = pNewMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); hr = pNewMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);VideoThumbnail Sample - Win32 apps | Microsoft Learn
のようにサブタイプを指定することで、指定の画像形式で取得できるようになります。また取得する画像の位置は、IMFSourceReader::SetCurrentPosition()で指定します。ただしSetCurrentPosition()で指定できる位置は不正確なため、ReadSample()の引数pllTimestampで実際に取得された位置を確認しながら、指定の位置に近づくようにReadSample()の呼び出しをくり返します。
RGB-32への変換が必要なファイル形式に対しては、Source Reader作成時にMF_SOURCE_READER_ENABLE_VIDEO_PROCESSING属性を指定します。これを怠るとSetCurrentMediaType()呼び出し時にMF_E_INVALIDMEDIATYPE (0xC00D36B4) が返され、Media Typeの設定に失敗します。
IMFAttributes* pAttributes = NULL; hr = MFCreateAttributes(&pAttributes, 1); hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE); IMFSourceReader* pSourceReader = NULL; hr = MFCreateSourceReaderFromURL( pwszURL, pAttributes, &pSourceReader );
HRESULT hr = S_OK; hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); hr = MFStartup(MF_VERSION); // ファイルを開く LPCWSTR pwszURL = L"sample.mp4"; IMFAttributes* pAttributes = NULL; hr = MFCreateAttributes(&pAttributes, 1); hr = pAttributes->SetUINT32(MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING, TRUE); IMFSourceReader* pSourceReader = NULL; hr = MFCreateSourceReaderFromURL(pwszURL, pAttributes, &pSourceReader); // ビデオ ストリームを選択する IMFMediaType* pNewMediaType = NULL; hr = MFCreateMediaType(&pNewMediaType); hr = pNewMediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); hr = pNewMediaType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32); hr = pSourceReader->SetCurrentMediaType( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, pNewMediaType ); SafeRelease(&pNewMediaType); hr = pSourceReader->SetStreamSelection( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE ); // 動画フォーマットを取得する IMFMediaType* pMediaType = NULL; hr = pSourceReader->GetCurrentMediaType( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, &pMediaType ); GUID subtype = { 0 }; hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &subtype); if (subtype != MFVideoFormat_RGB32) { hr = E_UNEXPECTED; } UINT32 width = 0; UINT32 height = 0; hr = MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, &width, &height); // メディアの長さを取得する PROPVARIANT var; PropVariantInit(&var); hr = pSourceReader->GetPresentationAttribute( (DWORD)MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var ); LONGLONG hnsDuration = 0; if (SUCCEEDED(hr)) { hnsDuration = var.hVal.QuadPart; } PropVariantClear(&var); // GDI+を初期化する ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartupInput gdiplusInput; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusInput, NULL); const int COUNT = 10; LONGLONG hnsIncrement = hnsDuration / COUNT; for (int i = 0; i < COUNT; i++) { // メディアの位置を指定する PROPVARIANT var; PropVariantInit(&var); var.vt = VT_I8; var.hVal.QuadPart = i * hnsIncrement; hr = pSourceReader->SetCurrentPosition(GUID_NULL, var); PropVariantClear(&var); // ソースリーダーからビデオフレームを取得する IMFSample* pSample = NULL; while (TRUE) { DWORD dwStreamFlags = 0; LONGLONG pllTimestamp = 0; IMFSample* pSampleTmp = NULL; hr = pSourceReader->ReadSample( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &dwStreamFlags, &pllTimestamp, &pSampleTmp ); if (dwStreamFlags & MF_SOURCE_READERF_ENDOFSTREAM) break; if (dwStreamFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { // 現在のMedia Typeが変更されたため、動画フォーマットを再取得する hr = pSourceReader->GetCurrentMediaType( (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, &pMediaType ); hr = pMediaType->GetGUID(MF_MT_SUBTYPE, &subtype); if (subtype != MFVideoFormat_RGB32) { hr = E_UNEXPECTED; } hr = MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, &width, &height); } if (pSampleTmp == NULL) continue; SafeRelease(&pSample); pSample = pSampleTmp; pSample->AddRef(); SafeRelease(&pSampleTmp); break; } if (pSample) { // サンプルからバッファへのポインタを取得する IMFMediaBuffer* pMediaBuffer = NULL; hr = pSample->ConvertToContiguousBuffer(&pMediaBuffer); BYTE* pBuffer = NULL; DWORD cbCurrentLength = 0; hr = pMediaBuffer->Lock(&pBuffer, NULL, &cbCurrentLength); // Bitmapを作成する const UINT BYTES_PER_PIXEL = 32 / 8; // PixelFormat32bppRGB Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap( width, height, width * BYTES_PER_PIXEL, PixelFormat32bppRGB, pBuffer ); wchar_t filename[100]; swprintf(filename, 100, L"image_%d.bmp", i); CLSID clsidEncoder; CLSIDFromString(L"{557CF400-1A04-11D3-9A73-0000F81EF32E}", &clsidEncoder); Gdiplus::Status status = bitmap->Save(filename, &clsidEncoder); pMediaBuffer->Unlock(); SafeRelease(&pMediaBuffer); } else { hr = MF_E_END_OF_STREAM; } SafeRelease(&pSample); } Gdiplus::GdiplusShutdown(gdiplusToken); SafeRelease(&pSourceReader); MFShutdown(); CoUninitialize();