Media Foundation学习笔记(七)Media Foundation的架构 Source Reader

来源:互联网 发布:蛋疼 知乎 编辑:程序博客网 时间:2024/06/05 10:47



Source Reader提供了除了使用Media Session和Pipeline处理媒体数据之外的一种可选的方法。

Source Reader的典型应用就是从文件、网络流和视音频抓取设备等获取Media Sample。Source Reader内部使用一个Media Source对象,Source Reader管理所有的对Media Source对象的方法调用。如果Media Source传递压缩数据,我们也能使用Source Reader解码压缩数据,这种情况下,Source Reader会加载解码器并管理Media Source和解码器之间的数据流动。Source Reader也可以做一些有限的视频处理,例如YUV转RGB-32。下面的图展示了Source Reader的功能:


使用Source Reader,一般有以下几个基本的步骤:


  1. 创建一个SourceReader实例;

  2. 枚举可能的输出格式;

  3. 设置各个流实际的输出格式;

  4. 处理数据。


创建Source Reader实例


MFCreateSourceReaderFromURL:用于创建网络流或者文件的Source Reader

MFCreateSourceReaderFromByteStream:根据一段字节流创建Source Reader

MFCreateSourceReaderFromMediaSource:根据给定的Media Source对象创建Source Reader,可用于视音频抓取设备。


枚举输出格式


每一个Media Source对象拥有至少一个流,例如,一个视频文件可能包含一个视频流和一个音频流。每个流提供一个可能的媒体类型的列表。通过IMFSourceReader::GetNativeMediaType获取媒体类型,该函数要提供流索引和类型索引2个参数,如果流索引不正确,该函数返回MF_E_INVALIDSTREAMNUMBER,如果类型的索引参数不正确,该函数返回MF_E_NO_MORE_TYPE,以下是一个枚举输出格式的例子:

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


设置输出格式


通过IMFSourceReader::SetCurrentMediaType方法。该函数需要提供一个流索引参数和一个IMFMediaType对象。


对已IMFMediaType对象,如果是从IMFSourceReader::GetNativeMediaType获取到的,则Source Reader直接输出数据,如果是通过MFCreateMediaType创建的不同于从IMFSourceReader::GetNativeMediaType获取到的所有类型的非压缩数据,则Source Reader自动插入一个解码器,这种情况,请在SetCurrentMediaType后调用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,第二参数可以为0,第三个参数是返回的媒体数据的流索引,第四个参数是返回的状态参数,第五个参数是返回的时间戳,第六个参数是返回的媒体数据(例如,如果输出是非压缩视频,则为一帧图像,如果输出是非压缩音频,则为一个音频帧的序列)。

以下的例子展示了怎样循环地获取媒体数据:

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


Draining Data

有时候,解码器可能会缓冲几帧数据。如下图标所示:


调用ReadSample可能返回时间戳是t1的数据,但是时间戳是t2t3的数据虽然已经从Media Source中读出来了,但是被解码器缓存了,没有传递出来,下一次调用ReadSample,时间戳是t4的数据被传入解码器,时间戳是t2的数据被传出来。

如果要解码所有当前被缓存在解码器中的数据,则不要在传给解码器新的数据,ReadSample的第二个参数设置为MF_SOURCE_READER_CONTROL_DREAIN,循环调用ReadSample直到ReadSample的第六个参数返回NULL的指针。


获取媒体文件时长

例子:

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


返回的是以1/10^7秒为单位的值。


Seeking

一般文件支持Seek操作。视音频数据抓取设备不支持Seek操作,因为它们是实时地。网络流是否能Seek,取决于网络协议。

 

下面的代码可以测试一个Source Reader是否支持Seek操作:

1)获取属性的函数

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


2)测试是否支持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;}


除了MFMEDIASOURCE_CAN_SEEK属性,还有一个属性是MFMEDIASOURCE_HAS_SLOW_SEEK,这个属性指明Source Reader支持Seek,但是Seek操作可能会很耗时(例如,可能需要从网络下载整个文件之后才能Seek)。

 

下面的函数执行Seek操作:

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


时间单位都是1/10^7秒。


Playback Rate

使用Source Reader的目的是获取音视频数据而不是播放,设置回放速率没有什么意义,Source Reader也不支持反向回放。


异步模式使用Source Reader


同步模式的问题就是容易阻塞,例如,IMFSourceReader::ReadSample读取磁盘文件或者网络流的时候,可能会由于磁盘IO的问题或者网络速度的问题导致阻塞。而异步模式下,ReadSample可以立即返回,在ReadSample操作完成后,Source Reader通过IMFSourceReaderCallback回调接口通知应用程序。


 步骤如下:

  1. 通过MFCreateAttributes创建一个属性;

  2. IMFSourceReaderCallback指针设置MF_SOURCE_READER_ASYNC_CALLBACK

  3. 创建SourceReader的时候,将属性传给SourceReader的构造函数。

例子:

HRESULT CreateSourceReaderAsync(    PCWSTR pszURL,     IMFSourceReaderCallback *pCallback,     IMFSourceReader **ppReader){    HRESULT hr = S_OK;    IMFAttributes *pAttributes = NULL;    hr = MFCreateAttributes(&pAttributes, 1);    if (FAILED(hr))    {        goto done;    }    hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);    if (FAILED(hr))    {        goto done;    }    hr = MFCreateSourceReaderFromURL(pszURL, pAttributes, ppReader);done:    SafeRelease(&pAttributes);    return hr;}


设置了异步模式后,就不能切换到同步模式,异步模式像这样调用ReadSample(最后4个参数全部为NULL):

hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,         0, NULL, NULL, NULL, NULL);

ReadSample方法完成后,Source Reader会调用IMFSourceReaderCallback对象的OnReadSample方法。OnReadSample方法原型定义如下:


STDMETHODIMPOnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,

       DWORD dwStreamFlags, LONGLONGllTimestamp, IMFSample *pSample);


hrStatus就是同步通用返回的值,其他参数同ReadSample


另外,该回调接口还定义了以下2个方法:

  1. OnEvent:当Media Source中的一些事件(例如:缓冲或者网络连接事件)发生了通知应用程序;

  2. OnFlushFlush方法完成时调用。

     

    OnReadSample必须是线程安全的,应为该函数是在Source Reader的工作线程中被调用。一次ReadSample的调用,就有一次OnReadSample的调用。

     

实现IMFSourceReaderCallback的例子:

#include <shlwapi.h>class SourceReaderCB : public IMFSourceReaderCallback{public:    SourceReaderCB(HANDLE hEvent) :       m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)    {        InitializeCriticalSection(&m_critsec);    }    // IUnknown methods    STDMETHODIMP QueryInterface(REFIID iid, void** ppv)    {        static const QITAB qit[] =        {            QITABENT(SourceReaderCB, IMFSourceReaderCallback),            { 0 },        };        return QISearch(this, qit, iid, ppv);    }    STDMETHODIMP_(ULONG) AddRef()    {        return InterlockedIncrement(&m_nRefCount);    }    STDMETHODIMP_(ULONG) Release()    {        ULONG uCount = InterlockedDecrement(&m_nRefCount);        if (uCount == 0)        {            delete this;        }        return uCount;    }    // IMFSourceReaderCallback methods    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample){EnterCriticalSection(&m_critsec);    if (SUCCEEDED(hrStatus))    {        if (pSample)        {            // Do something with the sample.            wprintf(L"Frame @ %I64d\n", llTimestamp);        }    }    else    {        // Streaming error.        NotifyError(hrStatus);    }    if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)    {        // Reached the end of the stream.        m_bEOS = TRUE;    }    m_hrStatus = hrStatus;    LeaveCriticalSection(&m_critsec);    SetEvent(m_hEvent);    return S_OK;}    STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *)    {        return S_OK;    }    STDMETHODIMP OnFlush(DWORD)    {        return S_OK;    }public:    HRESULT Wait(DWORD dwMilliseconds, BOOL *pbEOS)    {        *pbEOS = FALSE;        DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);        if (dwResult == WAIT_TIMEOUT)        {            return E_PENDING;        }        else if (dwResult != WAIT_OBJECT_0)        {            return HRESULT_FROM_WIN32(GetLastError());        }        *pbEOS = m_bEOS;        return m_hrStatus;    }    private:        // Destructor is private. Caller should call Release.    virtual ~SourceReaderCB()     {    }    void NotifyError(HRESULT hr)    {        wprintf(L"Source Reader error: 0x%X\n", hr);    }private:    long                m_nRefCount;        // Reference count.    CRITICAL_SECTION    m_critsec;    HANDLE              m_hEvent;    BOOL                m_bEOS;    HRESULT             m_hrStatus;};


以下的代码展示了怎样使用这个回调类:

HRESULT ReadMediaFile(PCWSTR pszURL){    HRESULT hr = S_OK;    IMFSourceReader *pReader = NULL;    SourceReaderCB *pCallback = NULL;    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);    if (hEvent == NULL)    {        hr = HRESULT_FROM_WIN32(GetLastError());        goto done;    }    // Create an instance of the callback object.    pCallback = new (std::nothrow) SourceReaderCB(hEvent);    if (pCallback == NULL)    {        hr = E_OUTOFMEMORY;        goto done;    }    // Create the Source Reader.    hr = CreateSourceReaderAsync(pszURL, pCallback, &pReader);    if (FAILED(hr))    {        goto done;    }    hr = ConfigureDecoder(pReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM);    if (FAILED(hr))    {        goto done;    }    // Request the first sample.    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,         0, NULL, NULL, NULL, NULL);    if (FAILED(hr))    {        goto done;    }    while (SUCCEEDED(hr))    {        BOOL bEOS;        hr = pCallback->Wait(INFINITE, &bEOS);        if (FAILED(hr) || bEOS)        {            break;        }        hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,            0, NULL, NULL, NULL, NULL);    }done:    SafeRelease(&pReader);    SafeRelease(&pCallback);    return hr;}










0 0
原创粉丝点击