MediaFoundation --- 動画の読み込み
下記記事にてOpenCVで動画を読み書きする方法を調べました。
OpenCVで動画読み込み&書き込み - 何でもプログラミング
今回はWindows特有のMediaFoundationを利用して動画を読み込んでみます。
全体の流れの把握重視の為、エラー処理は全て省いてあります。
アプリケーションコード
動画を読み込んで、そのまま書き出しています。
今回は書き出しにはOpenCVを利用しました。
IMFSourceReaderを作成、デコーダ設定、読み込み、の流れになります。
動画はOpenCVのサンプルである、vtest.aviを利用しています。
MediaFoundationを利用するにあたり、MFStartupを忘れないようにしてください。(CoInitializeも、されていなければ呼び出してください。)
ConfigureVideoDecoder、GetVideoInfo、Captureは後程記載します。
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "mfreadwrite.lib") #include <opencv2\opencv.hpp> #pragma comment(lib, "opencv_world320.lib") int main() { CoInitialize(NULL); MFStartup(MF_VERSION); IMFSourceReader* reader; MFCreateSourceReaderFromURL(L"c:\\lib\\opencv3.2\\sources\\samples\\data\\vtest.avi", NULL, &reader); ConfigureVideoDecoder(reader, MFVideoFormat_RGB24); UINT32 width, height; double fps; GetVideoInfo(reader, &width, &height, &fps); auto writer = cv::VideoWriter("output.avi", cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), fps, cv::Size(width, height)); while (true) { auto data = Capture(reader); if (data.empty()) break; cv::Mat frame(height, width, CV_8UC3, data.data()); writer << frame; } reader->Release(); MFShutdown(); CoUninitialize(); return 0; }
ConfigureVideoDecoder
SourceReaderにどのフォーマットで出力するか設定します。
上記ではMFVideoFormat_RGB24を指定していますが、デコーダによってはMFVideoFormat_YUY2しか出力しない等ありますので、利用の際は注意してください。
対応出力は下記ページのVideo Codecs、Decoderのリンク先を参照してください。
Supported Media Formats in Media Foundation (Windows)
void ConfigureVideoDecoder(IMFSourceReader* reader, GUID format) { IMFMediaType* mediaType; MFCreateMediaType(&mediaType); mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video); mediaType->SetGUID(MF_MT_SUBTYPE, format); reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, mediaType); mediaType->Release(); }
GetVideoInfo
SourceReaderからサイズとfpsを取得しています。
void GetVideoInfo(IMFSourceReader* reader, UINT32* width, UINT32* height, double* fps) { IMFMediaType* mediaType; reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, &mediaType); MFGetAttributeSize(mediaType, MF_MT_FRAME_SIZE, width, height); UINT32 nume, denom; MFGetAttributeRatio(mediaType, MF_MT_FRAME_RATE, &nume, &denom); *fps = (double)nume / denom; mediaType->Release(); }
Capture
ReadSampleにてIMFSampleを取得し、そこからIMFMediaBufferを取得、データ取り出しを行っています。
ReadSample時にflagsを渡さないとsampleがnullptrになってしまいます。
std::vector<BYTE> Capture(IMFSourceReader* reader) { DWORD flags; IMFSample* sample; reader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, NULL, &flags, NULL, &sample); if (sample == nullptr) return std::vector<BYTE>(); IMFMediaBuffer* buffer; sample->GetBufferByIndex(0, &buffer); BYTE* p; DWORD size; buffer->Lock(&p, NULL, &size); std::vector<BYTE> data(size); memcpy(data.data(), p, size); buffer->Unlock(); buffer->Release(); sample->Release(); return data; }
シーク
SourceReaderにはSetCurrentPositionがあり、100ns単位で指定します。
ただしその位置に一番近いキーフレームに移動します。
void SeekToKeyframe(IMFSourceReader* reader, int _100ns) { PROPVARIANT var; InitPropVariantFromInt64(_100ns, &var); auto hr = reader->SetCurrentPosition(GUID_NULL, var); PropVariantClear(&var); }
動画時間取得
durationは100ns単位の値になります。
PROPVARIANT var; reader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, MF_PD_DURATION, &var); LONGLONG duration; PropVariantToInt64(var, &duration); PropVariantClear(&var);
Fourcc取得
IMFMediaType* mediaType; reader->GetNativeMediaType(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 0, &mediaType); GUID subtype; mediaType->GetGUID(MF_MT_SUBTYPE, &subtype); char fourcc[] = { (char) (subtype.Data1 & 0XFF), (char)((subtype.Data1 & 0XFF00) >> 8), (char)((subtype.Data1 & 0XFF0000) >> 16), (char)((subtype.Data1 & 0XFF000000) >> 24), };