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

来源:互联网 发布:龙江网络佳木斯分公司 编辑:程序博客网 时间:2024/06/01 09:53

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,以下是一个枚举输出格式的例子:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)  
  2. {  
  3.     HRESULT hr = S_OK;  
  4.     DWORD dwMediaTypeIndex = 0;  
  5.   
  6.     while (SUCCEEDED(hr))  
  7.     {  
  8.         IMFMediaType *pType = NULL;  
  9.         hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);  
  10.         if (hr == MF_E_NO_MORE_TYPES)  
  11.         {  
  12.             hr = S_OK;  
  13.             break;  
  14.         }  
  15.         else if (SUCCEEDED(hr))  
  16.         {  
  17.             // Examine the media type. (Not shown.)  
  18.   
  19.             pType->Release();  
  20.         }  
  21.         ++dwMediaTypeIndex;  
  22.     }  
  23.     return hr;  
  24. }  


设置输出格式


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


对已IMFMediaType对象,如果是从IMFSourceReader::GetNativeMediaType获取到的,则Source Reader直接输出数据,如果是通过MFCreateMediaType创建的不同于从IMFSourceReader::GetNativeMediaType获取到的所有类型的非压缩数据,则Source Reader自动插入一个解码器,这种情况,请在SetCurrentMediaType后调用GetCurrentMediaType获取解码格式的细节。

下面的例子展示了将视频流格式设置为RGB-32和将音频流格式设置为PCM

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)  
  2. {  
  3.     IMFMediaType *pNativeType = NULL;  
  4.     IMFMediaType *pType = NULL;  
  5.   
  6.     // Find the native format of the stream.  
  7.     HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);  
  8.     if (FAILED(hr))  
  9.     {  
  10.         return hr;  
  11.     }  
  12.   
  13.     GUID majorType, subtype;  
  14.   
  15.     // Find the major type.  
  16.     hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);  
  17.     if (FAILED(hr))  
  18.     {  
  19.         goto done;  
  20.     }  
  21.   
  22.     // Define the output type.  
  23.     hr = MFCreateMediaType(&pType);  
  24.     if (FAILED(hr))  
  25.     {  
  26.         goto done;  
  27.     }  
  28.   
  29.     hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);  
  30.     if (FAILED(hr))  
  31.     {  
  32.         goto done;  
  33.     }  
  34.   
  35.     // Select a subtype.  
  36.     if (majorType == MFMediaType_Video)  
  37.     {  
  38.         subtype= MFVideoFormat_RGB32;  
  39.     }  
  40.     else if (majorType == MFMediaType_Audio)  
  41.     {  
  42.         subtype = MFAudioFormat_PCM;  
  43.     }  
  44.     else  
  45.     {  
  46.         // Unrecognized type. Skip.  
  47.         goto done;  
  48.     }  
  49.   
  50.     hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);  
  51.     if (FAILED(hr))  
  52.     {  
  53.         goto done;  
  54.     }  
  55.   
  56.     // Set the uncompressed format.  
  57.     hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);  
  58.     if (FAILED(hr))  
  59.     {  
  60.         goto done;  
  61.     }  
  62.   
  63. done:  
  64.     SafeRelease(&pNativeType);  
  65.     SafeRelease(&pType);  
  66.     return hr;  
  67. }  


处理媒体数据

使用IMFSourceReader::ReadSample方法获取媒体数据,典型应用:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. DWORD streamIndex, flags;  
  2. LONGLONG llTimeStamp;  
  3.   
  4. hr = pReader->ReadSample(  
  5.     MF_SOURCE_READER_ANY_STREAM,    // Stream index.  
  6.     0,                              // Flags.  
  7.     &streamIndex,                   // Receives the actual stream index.   
  8.     &flags,                         // Receives status flags.  
  9.     &llTimeStamp,                   // Receives the time stamp.  
  10.     &pSample                        // Receives the sample or NULL.  
  11.     );  

其中第一个参数是指定的流索引,不指定则为MF_SOURCE_READER_ANY_STREAM,第二参数可以为0,第三个参数是返回的媒体数据的流索引,第四个参数是返回的状态参数,第五个参数是返回的时间戳,第六个参数是返回的媒体数据(例如,如果输出是非压缩视频,则为一帧图像,如果输出是非压缩音频,则为一个音频帧的序列)。

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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT ProcessSamples(IMFSourceReader *pReader)  
  2. {  
  3.     HRESULT hr = S_OK;  
  4.     IMFSample *pSample = NULL;  
  5.     size_t  cSamples = 0;  
  6.   
  7.     bool quit = false;  
  8.     while (!quit)  
  9.     {  
  10.         DWORD streamIndex, flags;  
  11.         LONGLONG llTimeStamp;  
  12.   
  13.         hr = pReader->ReadSample(  
  14.             MF_SOURCE_READER_ANY_STREAM,    // Stream index.  
  15.             0,                              // Flags.  
  16.             &streamIndex,                   // Receives the actual stream index.   
  17.             &flags,                         // Receives status flags.  
  18.             &llTimeStamp,                   // Receives the time stamp.  
  19.             &pSample                        // Receives the sample or NULL.  
  20.             );  
  21.   
  22.         if (FAILED(hr))  
  23.         {  
  24.             break;  
  25.         }  
  26.   
  27.         wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);  
  28.         if (flags & MF_SOURCE_READERF_ENDOFSTREAM)  
  29.         {  
  30.             wprintf(L"\tEnd of stream\n");  
  31.             quit = true;  
  32.         }  
  33.         if (flags & MF_SOURCE_READERF_NEWSTREAM)  
  34.         {  
  35.             wprintf(L"\tNew stream\n");  
  36.         }  
  37.         if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)  
  38.         {  
  39.             wprintf(L"\tNative type changed\n");  
  40.         }  
  41.         if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)  
  42.         {  
  43.             wprintf(L"\tCurrent type changed\n");  
  44.         }  
  45.         if (flags & MF_SOURCE_READERF_STREAMTICK)  
  46.         {  
  47.             wprintf(L"\tStream tick\n");  
  48.         }  
  49.   
  50.         if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)  
  51.         {  
  52.             // The format changed. Reconfigure the decoder.  
  53.             hr = ConfigureDecoder(pReader, streamIndex);  
  54.             if (FAILED(hr))  
  55.             {  
  56.                 break;  
  57.             }  
  58.         }  
  59.   
  60.         if (pSample)  
  61.         {  
  62.             ++cSamples;  
  63.         }  
  64.   
  65.         SafeRelease(&pSample);  
  66.     }  
  67.   
  68.     if (FAILED(hr))  
  69.     {  
  70.         wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);  
  71.     }  
  72.     else  
  73.     {  
  74.         wprintf(L"Processed %d samples\n", cSamples);  
  75.     }  
  76.     SafeRelease(&pSample);  
  77.     return hr;  
  78. }  


Draining Data

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


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

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


获取媒体文件时长

例子:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)  
  2. {  
  3.     PROPVARIANT var;  
  4.     HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,   
  5.         MF_PD_DURATION, &var);  
  6.     if (SUCCEEDED(hr))  
  7.     {  
  8.         hr = PropVariantToInt64(var, phnsDuration);  
  9.         PropVariantClear(&var);  
  10.     }  
  11.     return hr;  
  12. }  


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


Seeking

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

 

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

1)获取属性的函数

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)  
  2. {  
  3.     ULONG flags = 0;  
  4.   
  5.     PROPVARIANT var;  
  6.     PropVariantInit(&var);  
  7.   
  8.     HRESULT hr = pReader->GetPresentationAttribute(  
  9.         MF_SOURCE_READER_MEDIASOURCE,   
  10.         MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS,   
  11.         &var);  
  12.   
  13.     if (SUCCEEDED(hr))  
  14.     {  
  15.         hr = PropVariantToUInt32(var, &flags);  
  16.     }  
  17.     if (SUCCEEDED(hr))  
  18.     {  
  19.         *pulFlags = flags;  
  20.     }  
  21.   
  22.     PropVariantClear(&var);  
  23.     return hr;  
  24. }  


2)测试是否支持Seek

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. BOOL SourceCanSeek(IMFSourceReader *pReader)  
  2. {  
  3.     BOOL bCanSeek = FALSE;  
  4.     ULONG flags;  
  5.     if (SUCCEEDED(GetSourceFlags(pReader, &flags)))  
  6.     {  
  7.         bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);  
  8.     }  
  9.     return bCanSeek;  
  10. }  


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

 

下面的函数执行Seek 操作:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)  
  2. {  
  3.     PROPVARIANT var;  
  4.     HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);  
  5.     if (SUCCEEDED(hr))  
  6.     {  
  7.         hr = pReader->SetCurrentPosition(GUID_NULL, var);  
  8.         PropVariantClear(&var);  
  9.     }  
  10.     return hr;  
  11. }  


时间单位都是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的构造函数。

例子:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT CreateSourceReaderAsync(  
  2.     PCWSTR pszURL,   
  3.     IMFSourceReaderCallback *pCallback,   
  4.     IMFSourceReader **ppReader)  
  5. {  
  6.     HRESULT hr = S_OK;  
  7.     IMFAttributes *pAttributes = NULL;  
  8.   
  9.     hr = MFCreateAttributes(&pAttributes, 1);  
  10.     if (FAILED(hr))  
  11.     {  
  12.         goto done;  
  13.     }  
  14.   
  15.     hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);  
  16.     if (FAILED(hr))  
  17.     {  
  18.         goto done;  
  19.     }  
  20.   
  21.     hr = MFCreateSourceReaderFromURL(pszURL, pAttributes, ppReader);  
  22.   
  23. done:  
  24.     SafeRelease(&pAttributes);  
  25.     return hr;  
  26. }  


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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,   
  2.         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的例子:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <shlwapi.h>  
  2.   
  3. class SourceReaderCB : public IMFSourceReaderCallback  
  4. {  
  5. public:  
  6.     SourceReaderCB(HANDLE hEvent) :   
  7.       m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)  
  8.     {  
  9.         InitializeCriticalSection(&m_critsec);  
  10.     }  
  11.   
  12.     // IUnknown methods  
  13.     STDMETHODIMP QueryInterface(REFIID iid, void** ppv)  
  14.     {  
  15.         static const QITAB qit[] =  
  16.         {  
  17.             QITABENT(SourceReaderCB, IMFSourceReaderCallback),  
  18.             { 0 },  
  19.         };  
  20.         return QISearch(this, qit, iid, ppv);  
  21.     }  
  22.     STDMETHODIMP_(ULONG) AddRef()  
  23.     {  
  24.         return InterlockedIncrement(&m_nRefCount);  
  25.     }  
  26.     STDMETHODIMP_(ULONG) Release()  
  27.     {  
  28.         ULONG uCount = InterlockedDecrement(&m_nRefCount);  
  29.         if (uCount == 0)  
  30.         {  
  31.             delete this;  
  32.         }  
  33.         return uCount;  
  34.     }  
  35.   
  36.     // IMFSourceReaderCallback methods  
  37.     STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,  
  38.         DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample)  
  39. {  
  40.     EnterCriticalSection(&m_critsec);  
  41.   
  42.     if (SUCCEEDED(hrStatus))  
  43.     {  
  44.         if (pSample)  
  45.         {  
  46.             // Do something with the sample.  
  47.             wprintf(L"Frame @ %I64d\n", llTimestamp);  
  48.         }  
  49.     }  
  50.     else  
  51.     {  
  52.         // Streaming error.  
  53.         NotifyError(hrStatus);  
  54.     }  
  55.   
  56.     if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)  
  57.     {  
  58.         // Reached the end of the stream.  
  59.         m_bEOS = TRUE;  
  60.     }  
  61.     m_hrStatus = hrStatus;  
  62.   
  63.     LeaveCriticalSection(&m_critsec);  
  64.     SetEvent(m_hEvent);  
  65.     return S_OK;  
  66. }  
  67.   
  68.     STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *)  
  69.     {  
  70.         return S_OK;  
  71.     }  
  72.   
  73.     STDMETHODIMP OnFlush(DWORD)  
  74.     {  
  75.         return S_OK;  
  76.     }  
  77.   
  78. public:  
  79.     HRESULT Wait(DWORD dwMilliseconds, BOOL *pbEOS)  
  80.     {  
  81.         *pbEOS = FALSE;  
  82.   
  83.         DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);  
  84.         if (dwResult == WAIT_TIMEOUT)  
  85.         {  
  86.             return E_PENDING;  
  87.         }  
  88.         else if (dwResult != WAIT_OBJECT_0)  
  89.         {  
  90.             return HRESULT_FROM_WIN32(GetLastError());  
  91.         }  
  92.   
  93.         *pbEOS = m_bEOS;  
  94.         return m_hrStatus;  
  95.     }  
  96.       
  97. private:  
  98.       
  99.     // Destructor is private. Caller should call Release.  
  100.     virtual ~SourceReaderCB()   
  101.     {  
  102.     }  
  103.   
  104.     void NotifyError(HRESULT hr)  
  105.     {  
  106.         wprintf(L"Source Reader error: 0x%X\n", hr);  
  107.     }  
  108.   
  109. private:  
  110.     long                m_nRefCount;        // Reference count.  
  111.     CRITICAL_SECTION    m_critsec;  
  112.     HANDLE              m_hEvent;  
  113.     BOOL                m_bEOS;  
  114.     HRESULT             m_hrStatus;  
  115.   
  116. };  


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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. HRESULT ReadMediaFile(PCWSTR pszURL)  
  2. {  
  3.     HRESULT hr = S_OK;  
  4.   
  5.     IMFSourceReader *pReader = NULL;  
  6.     SourceReaderCB *pCallback = NULL;  
  7.   
  8.     HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);  
  9.     if (hEvent == NULL)  
  10.     {  
  11.         hr = HRESULT_FROM_WIN32(GetLastError());  
  12.         goto done;  
  13.     }  
  14.   
  15.     // Create an instance of the callback object.  
  16.     pCallback = new (std::nothrow) SourceReaderCB(hEvent);  
  17.     if (pCallback == NULL)  
  18.     {  
  19.         hr = E_OUTOFMEMORY;  
  20.         goto done;  
  21.     }  
  22.   
  23.     // Create the Source Reader.  
  24.     hr = CreateSourceReaderAsync(pszURL, pCallback, &pReader);  
  25.     if (FAILED(hr))  
  26.     {  
  27.         goto done;  
  28.     }  
  29.   
  30.     hr = ConfigureDecoder(pReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM);  
  31.     if (FAILED(hr))  
  32.     {  
  33.         goto done;  
  34.     }  
  35.   
  36.     // Request the first sample.  
  37.     hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,   
  38.         0, NULL, NULL, NULL, NULL);  
  39.     if (FAILED(hr))  
  40.     {  
  41.         goto done;  
  42.     }  
  43.   
  44.     while (SUCCEEDED(hr))  
  45.     {  
  46.         BOOL bEOS;  
  47.         hr = pCallback->Wait(INFINITE, &bEOS);  
  48.         if (FAILED(hr) || bEOS)  
  49.         {  
  50.             break;  
  51.         }  
  52.         hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,  
  53.             0, NULL, NULL, NULL, NULL);  
  54.     }  
  55.   
  56. done:  
  57.     SafeRelease(&pReader);  
  58.     SafeRelease(&pCallback);  
  59.     return hr;  
  60. }  

0 0
原创粉丝点击