ソース リーダーを使用したメディア データの処理

このトピックでは、 ソース リーダー を使用してメディア データを処理する方法について説明します。

ソース リーダーを使用するには、次の基本的な手順に従います。

  1. ソース リーダーのインスタンスを作成します。
  2. 使用可能な出力形式を列挙します。
  3. 各ストリームの実際の出力形式を設定します。
  4. ソースからのデータを処理します。

このトピックの残りの部分では、これらの手順について詳しく説明します。

ソース リーダーの作成

ソース リーダーのインスタンスを作成するには、次のいずれかの関数を呼び出します。

機能 Description
MFCreateSourceReaderFromURL
URL を入力として受け取ります。 この関数では、 Source Resolver を使用して、URL からメディア ソースを作成します。
MFCreateSourceReaderFromByteStream
バイト ストリームへのポインターを受け取ります。 この関数では、ソース リゾルバーを使用してメディア ソースを作成することもできます。
MFCreateSourceReaderFromMediaSource
既に作成されているメディア ソースへのポインターを受け取ります。 この関数は、ソース リゾルバーで作成できないメディア ソース (キャプチャ デバイスやカスタム メディア ソースなど) に役立ちます。

 

通常、メディア ファイルの場合は、 MFCreateSourceReaderFromURL を使用します。 Web カメラなどのデバイスの場合は、 MFCreateSourceReaderFromMediaSource を使用します。 (Microsoft Media Foundation でのデバイスのキャプチャの詳細については、「 オーディオ/ビデオ キャプチャ」を参照してください)。

これらの各関数は、オプションの IMFAttributes ポインターを受け取ります。これは、これらの関数のリファレンス トピックで説明されているように、ソース リーダーのさまざまなオプションを設定するために使用されます。 既定の動作を取得するには、このパラメーターを NULL に設定します。 各関数は、出力パラメーターとして IMFSourceReader ポインターを返します。 これらの関数を呼び出す前に、 CoInitialize(Ex) 関数と MFStartup 関数を呼び出す必要があります。

次のコードでは、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();
    }
}

出力形式の列挙

すべてのメディア ソースには、少なくとも 1 つのストリームがあります。 たとえば、ビデオ ファイルにはビデオ ストリームとオーディオ ストリームが含まれている場合があります。 各ストリームの形式は、 IMFMediaType インターフェイスで表されるメディアの種類を使用して記述されます。 メディアの種類の詳細については、「 メディアの種類」を参照してください。 ソース リーダーから取得するデータの形式を理解するには、メディアの種類を調べる必要があります。

最初は、すべてのストリームに既定の形式があります。これは、 IMFSourceReader::GetCurrentMediaType メソッドを呼び出すことで確認できます。

メディア ソースは、ストリームごとに、そのストリームに使用可能なメディアの種類の一覧を提供します。 型の数はソースによって異なります。 ソースがメディア ファイルを表す場合、通常、ストリームごとに 1 つの型のみが存在します。 一方、Web カメラでは、複数の異なる形式でビデオをストリーミングできる場合があります。 その場合、アプリケーションはメディアの種類の一覧から使用する形式を選択できます。

ストリームのメディアの種類のいずれかを取得するには、 IMFSourceReader::GetNativeMediaType メソッドを呼び出します。 このメソッドは、ストリームのインデックスと、ストリームのメディアの種類のリストにインデックスの 2 つのインデックス パラメーターを受け取ります。 ストリームのすべての型を列挙するには、ストリーム インデックスの定数を維持しながら、リスト インデックスをインクリメントします。 リスト インデックスが範囲外になると、 GetNativeMediaTypeMF_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;
}

すべてのストリームのメディアの種類を列挙するには、ストリーム インデックスをインクリメントします。 ストリーム インデックスが範囲外になると、 GetNativeMediaTypeMF_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;
}

出力形式の設定

出力形式を変更するには、 IMFSourceReader::SetCurrentMediaType メソッドを呼び出します。 このメソッドは、ストリーム インデックスとメディアの種類を受け取ります。

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

メディアの種類については、デコーダーを挿入するかどうかによって異なります。

  • デコードせずにソースから直接データを取得するには、 GetNativeMediaType によって返される型のいずれかを使用します。
  • ストリームをデコードするには、目的の圧縮されていない形式を記述する新しいメディアの種類を作成します。

デコーダーの場合は、次のようにメディアの種類を作成します。

  1. MFCreateMediaType を呼び出して、新しいメディアの種類を作成します。
  2. MF_MT_MAJOR_TYPE属性を設定して、オーディオまたはビデオを指定します。
  3. デコード形式のサブタイプを指定するには、 MF_MT_SUBTYPE 属性を設定します。 ( オーディオ サブタイプ GUIDビデオ サブタイプ GUID に関するページを参照してください)。
  4. IMFSourceReader::SetCurrentMediaType を呼び出します。

ソース リーダーによってデコーダーが自動的に読み込まれます。 デコードされた形式の完全な詳細を取得するには、SetCurrentMediaType の呼び出し後に IMFSourceReader::GetCurrentMediaType を呼び出します。

次のコードでは、RGB-32 のビデオ ストリームと 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;
}

メディア データの処理

ソースからメディア データを取得するには、次のコードに示すように、 IMFSourceReader::ReadSample メソッドを呼び出します。

        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.
            );

最初のパラメーターは、データを取得するストリームのインデックスです。 MF_SOURCE_READER_ANY_STREAMを指定して、任意のストリームから次に使用可能なデータを取得することもできます。 2 番目のパラメーターには省略可能なフラグが含まれています。これらの一覧については 、「MF_SOURCE_READER_CONTROL_FLAG 」を参照してください。 3 番目のパラメーターは、実際にデータを生成するストリームのインデックスを受け取ります。 この情報は、最初のパラメーターを MF_SOURCE_READER_ANY_STREAM に設定した場合に必要になります。 4 番目のパラメーターは状態フラグを受け取り、ストリームの形式変更など、データの読み取り中に発生する可能性のあるさまざまなイベントを示します。 状態フラグの一覧については、「 MF_SOURCE_READER_FLAG」を参照してください。

メディア ソースが要求されたストリームのデータを生成できる場合、 ReadSample の最後のパラメーターは、メディア サンプル オブジェクトの IMFSample インターフェイスへのポインターを受け取ります。 メディア サンプルを使用して、次の手順を実行します。

  • メディア データへのポインターを取得します。
  • プレゼンテーション時間とサンプル期間を取得します。
  • インターレース、フィールド支配、およびサンプルのその他の側面を記述する属性を取得します。

メディア データの内容は、ストリームの形式によって異なります。 圧縮されていないビデオ ストリームの場合、各メディア サンプルには 1 つのビデオ フレームが含まれます。 圧縮されていないオーディオ ストリームの場合、各メディア サンプルには一連のオーディオ フレームが含まれます。

ReadSample メソッドは、S_OKを返すことができますが、pSample パラメーターにメディア サンプルを返しません。 たとえば、ファイルの末尾に達すると、ReadSampledwFlagsMF_SOURCE_READERF_ENDOFSTREAM フラグを設定し、pSample を NULL に設定します。 この場合、pSample パラメーターが NULL に設定されていても、エラーが発生していないため、ReadSample メソッドはS_OKを返します。 したがって、逆参照する前に、常に pSample の値をチェックします。

次のコードは、ReadSample をループで呼び出し、メディア ファイルの末尾に達するまで、 メソッドによって返される情報をチェックする方法を示しています。

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

データ パイプラインのドレイン

データ処理中に、デコーダーまたはその他の変換によって入力サンプルがバッファーに格納される場合があります。 次の図では、アプリケーションは ReadSample を呼び出し、 プレゼンテーション時間が t1 のサンプルを受け取ります。 デコーダーは t2 と t3 のサンプルを保持 しています

デコーダーでのバッファリングを示す図。

ReadSample の次の呼び出しでは、ソース リーダーがデコーダーに t4 を渡し、アプリケーションに t2 を返す場合があります。

デコーダーに新しいサンプルを渡さずに、デコーダーで現在バッファーに格納されているすべてのサンプルをデコードする場合は、readSampledwControlFlags パラメーターに MF_SOURCE_READER_CONTROLF_DRAIN フラグを設定します。 ReadSampleNULL サンプル ポインターを返すまで、ループ内でこれを行い続けます。 デコーダーがサンプルをバッファーする方法によっては、 ReadSample を複数回呼び出した直後または後に発生する可能性があります。

ファイルの期間の取得

メディア ファイルの期間を取得するには、次のコードに示すように、 IMFSourceReader::GetPresentationAttribute メソッドを呼び出し、 MF_PD_DURATION 属性を要求します。

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

ここで示す関数は、100 ナノ秒単位で期間を取得します。 時間を秒単位で取得するには、10,000,000 で除算します。

求めて

ローカル ファイルからデータを取得するメディア ソースは、通常、ファイル内の任意の位置をシークできます。 Web カメラなどのキャプチャ デバイスは、データがライブであるため、通常はシークできません。 ネットワーク経由でデータをストリーミングするソースは、ネットワーク ストリーミング プロトコルによってはシークできる場合があります。

メディア ソースがシークできるかどうかを確認するには、次のコードに示すように 、IMFSourceReader::GetPresentationAttribute を呼び出し、 MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS 属性を要求します。

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

この関数は、ソースから一連の機能フラグを取得します。 これらのフラグは、 MFMEDIASOURCE_CHARACTERISTICS 列挙体で定義されます。 2 つのフラグは、シークに関連します。

フラグ 説明
MFMEDIASOURCE_CAN_SEEK
ソースはシークできます。
MFMEDIASOURCE_HAS_SLOW_SEEK
シークが完了するまでに長い時間がかかる場合があります。 たとえば、ソースがシークする前にファイル全体をダウンロードする必要がある場合があります。 (ソースがこのフラグを返す厳密な条件はありません)。

 

次のコードは 、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;
}

シークするには、次のコードに示すように、 IMFSourceReader::SetCurrentPosition メソッドを呼び出します。

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

最初のパラメーターは、シーク位置を指定するために使用する時間形式を示します。 Media Foundation のすべてのメディア ソースは 、GUID_NULL値で示される 100 ナノ秒単位をサポートする必要があります。 2 番目のパラメーターは、シーク位置を含む PROPVARIANT です。 100 ナノ秒の時間単位の場合、データ型は LONGLONG です。

すべてのメディア ソースがフレーム精度シークを提供するわけではないことに注意してください。 シークの精度は、キー フレーム間隔、メディア ファイルにインデックスが含まれているかどうか、データのビット レートが定数か可変かなど、いくつかの要因によって異なります。 したがって、ファイル内の位置をシークした後、次のサンプルのタイム スタンプが要求された位置と完全に一致する保証はありません。 一般に、実際の位置は要求された位置より遅くないので、ストリーム内の目的のポイントに到達するまでサンプルを破棄できます。

再生速度

ソース リーダーを使用して再生速度を設定することはできますが、通常、次の理由から、これを行うことはあまり役に立ちません。

  • メディア ソースが逆再生する場合でも、ソース リーダーは逆再生をサポートしていません。
  • アプリケーションはプレゼンテーション時間を制御するため、アプリケーションはソースのレートを設定せずに高速または低速再生を実装できます。
  • 一部のメディア ソースでは 間引き モードがサポートされています。このモードでは、ソースが提供するサンプルが少なくなります。通常はキー フレームのみになります。 ただし、キー 以外のフレームを削除する場合は、MFSampleExtension_CleanPoint属性の各サンプルをチェックできます。

ソース リーダーを使用して再生速度を設定するには、 IMFSourceReader::GetServiceForStream メソッドを呼び出して、メディア ソースから IMFRateSupport インターフェイスと IMFRateControl インターフェイスを取得します。

ハードウェアの高速化

ソース リーダーは、ハードウェア アクセラレーション ビデオ デコード用の Microsoft DirectX Video Acceleration (DXVA) 2.0 と互換性があります。 ソース リーダーで DXVA を使用するには、次の手順を実行します。

  1. Microsoft Direct3D デバイスを作成します。
  2. DXVA2CreateDirect3DDeviceManager9 関数を呼び出して、Direct3D デバイス マネージャーを作成します。 この関数は、 IDirect3DDeviceManager9 インターフェイスへのポインターを取得します。
  3. Direct3D デバイスへのポインターを使用して IDirect3DDeviceManager9::ResetDevice メソッドを呼び出します。
  4. MFCreateAttributes 関数を呼び出して属性ストアを作成します。
  5. ソース リーダーを作成します。 作成関数の pAttributes パラメーターに属性ストアを渡します。

Direct3D デバイスを提供すると、ソース リーダーは DXVA ビデオ プロセッサ API と互換性のあるビデオ サンプルを割り当てます。 DXVA ビデオ処理を使用して、ハードウェアのインターレース解除またはビデオミキシングを実行できます。 詳細については、「 DXVA ビデオ処理」を参照してください。 また、デコーダーが DXVA 2.0 をサポートしている場合は、Direct3D デバイスを使用してハードウェアアクセラレータデコードを実行します。

重要

Windows 8 以降では、IDirect3DDeviceManager9 の代わりに IMFDXGIDeviceManager を使用できます。 Windows ストア アプリの場合は、 IMFDXGIDeviceManager を使用する必要があります。 詳細については、「 Direct3D 11 Video API」を参照してください

 

ソース リーダー