基于Media Foundation的基本FLV Media Source开发(一)
Media Foundation
Microsoft Media Foundation用于开发使用数字媒体的应用程序或组件(Windows Vista or Later)。
微软Media Foundation(MF)是Windows的下一代多媒体开发平台,它使得开发者、消费者和内容提供者可以拥抱带有增强的健壮性、无比的质量和无缝互操作性的优质内容新浪潮。
Media Foundation是DirectShow的替代者。相比DirectShow,它目前还不够成熟,但也有某些提升和改进。关于Media Foundation的详细信息请查阅MSDN:http://msdn.microsoft.com/en-us/library/ms694197(v=vs.85)
Media Source
Media sources是在MF pipeline中产生媒体数据的对象。若你的应用使用控制层(control layer),它只能使用media source APIs的一个有限子集。关于此信息,见Using Media Sources with the Media Session。
有关Media Source的详细信息请查阅MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms697527(v=vs.85).aspx
Media source必须实现2个对象:
- 1个presentation descriptor,它描述了源的内容,包括流的数目以及每个流的格式。有关此对象的更多信息,见Presentation Descriptor
- 1个或多个媒体流(media streams),它产生源数据 直到播放开始,源才会产生流。
开发自己的Media Source
原文参阅MSDN:http://msdn.microsoft.com/en-us/library/windows/desktop/ms700134(v=vs.85).aspx
此主题描述在MMF中怎样实现自定义的media source。包括以下小节:
创建Presentation Descriptor
IMFMediaSource::CreatePresentationDescriptor方法返回源的PD的拷贝。要创建PD,必须知道源内容的流数目,以及每个流的可能格式。对于每个流,如下创建stream descriptor:
- 创建一个media types数组。数组中的每个media type(MT)表示此流的一个可能格式。更多信息见Media Types
- 调用MFCreateStreamDescriptor创建stream descriptor(SD)。传入MT数组。此函数返回IMFStreamDescriptor指针
- 调用IMFStreamDescriptor::GetMediaTypeHandler取得SD的MT handler
- 调用IMFMediaTypeHandler::SetCurrentMediaType设置默认流格式。使用在第1步中创建的MT之一。一般来说,应该使用质量最高的那个格式
- 可选地,设置SD上的attribute。有关用于SD的attributes,见Stream Descriptor Attributes
现在开始创建PD:
- 调用MFCreatePresentationDescriptor并传入SD数组。此函数返回IMFPresentationDescriptor指针
- 调用IMFPresentationDescriptor::SelectStream select一个或多个流来选择默认流selection。在默认配置下至少必须有一个流被selected
- 可选地,设置PD上的attributes。有关用于PD的attributes,见Presentation Descriptor Attributes
应该创建PD一次,在启动时,或者在源解析足够的数据来确定内容之后。CreatePresentationDescriptor方法应该返回PD的拷贝。要创建此拷贝,调用IMFPresentationDescritor::Clone。返回拷贝避免了客户修改原始PD的状态,例如流selection或attributes。然而,要知道Clone创建一个浅拷贝,因此客户还是可能潜在地修改底层的SD。
开启media source
IMFMediaSource::Start方法开启media source或者seek到一个新位置。当先前状态是暂停或运行时,调用Start导致一个seek,且需指定一个新的开始时间。否则,调用Start导致一个start。当Start操作完成后,发送以下事件:
- 为新流(即以前deselected现在selected的流)发送MENewStream事件。事件数据是此流的指针
- 为以前selected现在还是selected的流发送MEUpdatedSteam事件。事件数据是此流的指针(不要为deselected流发送事件)
- 如果源在seeking,发送MESourceSeeked事件。否则,发送MESourceStarted事件。事件数据是Start方法中指定的开始时间。对于MESourceStarted事件,若开始时间为VT_EMPTY,设置事件的ME_EVENT_SOURCE_ACTUAL_START attribute。此attribute的值是实际的开始时间
- 对于每个流,如果源在seeking,发送MEStreamSeeked事件。否则,发送MEStreamStarted事件。事件数据是开始时间。(media source可以调用此流的IMFMediaEventGenerator::QueueEvent方法来排队事件)
当流为deselected时,关闭此流。此时流不应再排队事件。
Start方法的时间格式在pguidTimeFormat参数中给出。标准时间格式,由GUID_NULL表示,以100ns(即10-7秒)为单位。media source必须支持此时间格式。
Seeking
当seeking时,请求的开始位置可能不会落在精确的sample边界。另外,对于压缩格式,开始位置可能会落在关键帧之间。流应该传送产生在请求开始位置的未压缩sample所需的最早时间点的samples。对于视频,这意味着从前一个关键帧开始。pipeline负责从解码器丢弃额外的帧,以便从请求位置开始播放。
在源事件(MESourceStarted, MESourceSeeked, MEStreamStarted, MEStreamSeedked)中给出的开始时间是请求开始时间(此值在Start方法中给出),而不是实际开始时间。
例如,设某视频流的开头几帧有以下特征:
Sample | 1 | 2 | 3 | 4 |
时间 | 33 msec | 66 msec | 100 msec | 133 msec |
关键帧? | Y | N | N | Y |
如果Start方法的时间参数是100毫秒,那么源需要输出从第1帧开始的视频,即在此时间之前的第1个关键帧。start事件仍然在事件数据中表示100毫秒。
暂停media source
IMFMediaSource::Pause方法暂停media source。
当source暂停时,流可以创建新samles并将其存储到队列中,但流不会传递这些sample。但此规则有以下例外:
- Live源在暂停时应丢弃数据
- 如果源从网络取得数据,它会暂停服务器
如果客户在源暂停时调用IMFMediaStream::RequestSample,请求也会被加入队列,直到源被再次启动。请求不应被丢弃。
仅允许在stated状态执行暂停,否则Pause会返回MF_E_INVALID_STATE_TRANSITION。
产生源数据
MF使用pull模式,即流对来自pipeline的请求做出响应才产生和传递samples。流在media source在运行中且流为selected时传递samples。流只会在客户请求新sample时传递数据。
请求sample
客户通过调用IMFMediaStream::RequestSample请求新sample。下面是操作顺序:
- 客户调用IMFMediaStream::RequestSample。参数是一个可选的token对象的指针,客户使用此对象来跟踪请求。客户实现此token。Token必须暴露IUnknown接口。客户也可传入一个NULL指针而不是token
- 如果客户提供了token,media stream调用此token的AddRef并将其加入FIFO队列。方法返回,其余步骤异步发生
- 当有更多可用数据时,media stream创建新sample(这一步会在下一小节详细描述)
- media stream从队列中pull第一个token
- 如果token不为NULL,media stream设定media sample的MFSampleExtension_Token attribute。此attribute的值是token的指针
- media stream发送MEMediaSample事件。事件数据是此sample的IMFSample接口指针
- 如果客户提供了token,media stream调用此token对象的Release
如果media stream不能满足客户的RequestSample请求,它从队列中pull出token,并调用其上的Release,但不会发送MEMediaSample事件。
客户可以使用token来跟踪请求的状态。当客户收到MEMediaSample事件时,它可以从sample中取得token并与原始的请求相警醒。客户还可以使用token来探测media source是否丢弃了请求。如果token的引用计数减为0且media stream并没有发送MEMediaSample事件,就意味着此请求被丢弃了。
这里列出的步骤假定RequestSample方法作为异步操作实现。如果此方法是同步的,不需要将请求token放入队列。然而,如果数据的产生花费了大量的时间,推荐使用异步方法——例如,源从字节流中读取数据。
流负责缓存在RequestSample调用之间的累积的数据。
当media stream到达流的末尾时,它在最后一个sample之后发送MEEndOfStream事件。在每个流都结束之后,media source发送MEEndOfPresentation事件。在media stream发送MEEndOfStream事件之后,RequestSample方法返回MF_E_END_OF_STREAM,直到源被重启。
分配sample
当流准备填充一个未决的sample请求时,它创建新的sample并将一个或多个media buffers添加到此sample。有关创建media buffers的更多信息,见Media Buffers。
stream必须设置设置时间戳和持续时间,如果知道的话。时间戳与源有关。在大多数情况下,内容的开始对应的时间戳为0。例如,如果源从媒体文件读取,文件开头的时间戳为0.
sample的时间戳不必与presentation时间相等。Media Session把source time翻译为presentation time。对于压缩数据,流应该产生从开始时间之前的最近关键帧开始的数据。这使得解码器可以传递出现在请求开始时间的帧。(否则,解码器需要等待下一关键帧)
如果播放速率快于或慢于1.0,pipeline调整presentation clock的速率。源不会调整sample上的时间戳。
源可以通过设置attributes来为sample设定额外信息。有关sample attributes的列表,见Sample Attributes。
流中的间隔(gap)
如果流包含很大的gap,推荐此流发送MEStreamTick事件。此事件通知客户sample丢失。事件数据是丢失sample的时间戳,以100ns为单位(VT_18)。此事件可以防止下游组件等待不会到达的sample。流可以根据需要发送很多MEStreamTick事件来跨越流中的间隔。
关闭media source
当客户用完media source时,它调用IMFMediaSource::Shutdown。在方法内部,media source应该break任何循环引用计数。一般来说,在media source和media stream之间存在循环引用。
如果使用事件队列实现IMFMediaEventGenerator,调用事件队列的IMFMediaEventQueue::Shutdown。此方法关闭事件队列并通知正在等待事件的任何调用者。
在关闭后,source上的所有方法返回MF_E_SHUTDOWN,除了IUnknown方法以外。
Live源
从Windows 7开始,MF自动支持音频和视频捕获设备。对于视频,设备必须提供在视频捕获类别中提供一个kernel streaming(KS)minidriver。MF使用PnP path来枚举设备。对于音频,MF使用Windows Multimedia Device(MMDevice) API来枚举音频终端设备。如果设备符合这些标准,不需要实现自定义的media source。
然而,我们可能需要为一些其他类型的设备或其他live数据源实现自定义media source。live source和其他media source只有一些不同:
- 在IMFMediaSource::GetCharacteristics方法中,返回MEMEDIASOURCE_IS_LIVE标志
- 第一个sample的时间戳应为0
- 跟media sources一样地处理事件和流状态,除了暂停状态
- 当暂停时,不会把sample加入队列。丢弃在暂停时产生的所有数据
- live source一般不支持seek, 倒放以及速率控制。