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

f:id:any-programming:20170309225151p:plain

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),
};