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,一般有以下几个基本的步骤:
创建一个SourceReader实例;
枚举可能的输出格式;
设置各个流实际的输出格式;
处理数据。
创建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的数据,但是时间戳是t2和t3的数据虽然已经从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回调接口通知应用程序。
步骤如下:
通过MFCreateAttributes创建一个属性;
用IMFSourceReaderCallback指针设置MF_SOURCE_READER_ASYNC_CALLBACK;
创建SourceReader的时候,将属性传给SourceReader的构造函数。
通过MFCreateAttributes创建一个属性;
用IMFSourceReaderCallback指针设置MF_SOURCE_READER_ASYNC_CALLBACK;
创建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个方法:
OnEvent:当Media Source中的一些事件(例如:缓冲或者网络连接事件)发生了通知应用程序;
OnFlush:Flush方法完成时调用。
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;}
- Media Foundation学习笔记(七)Media Foundation的架构 Source Reader
- Media Foundation学习笔记(七)Media Foundation的架构 Source Reader
- Media Foundation学习笔记(六)Media Foundation的架构 Media Session
- Media Foundation学习笔记(六)Media Foundation的架构 Media Session
- Media Foundation学习笔记(二)Media Foundation的架构 概览
- Media Foundation学习笔记(三)Media Foundation的架构 基本对象类型
- Media Foundation学习笔记(四)Media Foundation的架构 Platform API
- Media Foundation学习笔记(五)Media Foundation的架构 Pipeline
- Media Foundation学习笔记(二)Media Foundation的架构 概览
- Media Foundation学习笔记(三)Media Foundation的架构 基本对象类型
- Media Foundation学习笔记(四)Media Foundation的架构 Platform API
- Media Foundation学习笔记(五)Media Foundation的架构 Pipeline
- media foundation (Using the Source Reader to Process Media Data)
- Media Foundation架构简介
- Media foundation——Media source:Media source object module
- Media foundation——Media source:Media Source Events
- Media Foundation
- Media Foundation——架构
- PHP的"::"、"->"和"=>"的区别
- JavaScript中浮点数的保留小数位数的问题
- ZOJ 3765 Lights
- 网站链接
- Android use '@foo' to launch a virtual device named
- Media Foundation学习笔记(七)Media Foundation的架构 Source Reader
- 获取Linux主机信息的5个命令
- c#中比较器的创建
- oracle 创建函数/程序包
- rmmod: can't change directory to '/lib/modules': No such file or directory问题解决
- php获取文件文档大小函数
- [Python] How to use Pyramid?
- ZOJ 3762 Pan's Labyrinth
- How to run OpenMP on Linux?