编写自定义媒体源

本主题介绍如何在 Microsoft Media Foundation 中实现自定义媒体源。 它包含以下部分:

创建演示文稿描述符

IMFMediaSource::CreatePresentationDescriptor 方法返回源的表示描述符的副本。 若要创建演示文稿描述符,必须知道源内容中的流数以及每个流的可能格式。 为每个流创建一个流描述符,如下所示:

  1. 创建媒体类型的数组。 数组中的每个媒体类型都表示流的可能格式。 有关创建媒体类型的详细信息,请参阅 媒体类型
  2. 调用 MFCreateStreamDescriptor 以创建流描述符。 传入媒体类型的数组。 函数返回 IMFStreamDescriptor 指针。
  3. 调用 IMFStreamDescriptor::GetMediaTypeHandler 以获取流描述符的媒体类型处理程序。
  4. 调用 IMFMediaTypeHandler::SetCurrentMediaType 以设置默认流格式。 使用在步骤 1 中创建的媒体类型之一。 通常,应使用质量最高的格式。
  5. (可选)在流描述符上设置属性。 有关应用于流描述符的属性列表,请参阅 流描述符属性

现在创建演示文稿描述符:

  1. 调用 MFCreatePresentationDescriptor 并传入流描述符的数组。 函数返回 IMFPresentationDescriptor 指针。
  2. 通过调用 IMFPresentationDescriptor::SelectStream 选择默认流选择,以选择一个或多个流。 在默认配置中必须至少选择一个流。
  3. (可选)在演示文稿描述符上设置属性。 有关适用于流描述符的属性列表,请参阅 Presentation 描述符属性

应在启动时或在源分析足够多的源数据以确定内容之后创建一次演示文稿描述符。 CreatePresentationDescriptor 方法应返回演示文稿描述符的副本。 若要创建副本,请调用 IMFPresentationDescriptor::Clone。 返回副本会阻止客户端修改原始演示文稿描述符的状态,例如属性或流选择。 但是,请注意 ,克隆 会创建一个浅表副本,因此客户端可能会修改基础流描述符。

启动媒体源

IMFMediaSource::Start 方法启动媒体源或寻求新位置。 当上一个状态已暂停或正在运行,并且指定了新的开始时间时,调用 Start 会导致 查找 。 否则, Start 方法会导致 启动启动操作完成后,发送以下事件。

  1. 为每个新 流发送 MENewStream 事件,即之前已取消选择且现在选中的每个流。 事件数据是指向流的指针。
  2. 为之前选择且仍处于选中状态的每个流发送 MEUpdatedStream 事件。 事件数据是指向流的指针。 (不要为取消选择的 streams.) 发送事件
  3. 如果源正在查找,请发送 MESourceSeeked 事件。 否则,请发送 MESourceStarted 事件。 事件数据是在 Start 方法中指定的开始时间。 对于 MESourceStarted 事件,如果开始时间VT_EMPTY,请在事件上设置 MF_EVENT_SOURCE_ACTUAL_START 属性。 属性值为实际开始时间。
  4. 对于每个流,如果源正在查找,请发送 MEStreamSeeked 事件。 否则,请发送 MEStreamStarted 事件。 事件数据是开始时间。 (媒体源可以通过调用流的 IMFMediaEventGenerator::QueueEvent method.)

取消选择流后,关闭该流。 此时,流不应再将事件排队。

pguidTimeFormat 参数中提供了 Start 方法的时间格式。 标准时间格式( 由 GUID_NULL 指示)为 100 纳秒单位。 媒体源必须支持此时间格式。

寻求

在查找时,请求的起始位置可能不会位于确切的样本边界上。 此外,对于压缩内容,起始位置可能位于关键帧之间。 流应从所需的最早点传送样本,以在请求的起始位置生成未压缩的样本。 对于视频,这意味着从上一个关键帧开始。 管道负责从解码器中删除额外的帧,以便播放在请求的时间开始。

源事件 (MESourceStarted、MESourceSeekedMEStreamStartedMEStreamSeeked) 中给定的开始时间是请求的开始时间 (Start 方法) 中给定的值,而不管实际开始位置如何。

例如,假设视频流的前几个帧具有以下特征:

示例 1 2 3 4
时间 33 毫秒 66 毫秒 100 毫秒 133 毫秒
关键帧?

 

如果使用 100 毫秒的值为 100 毫秒调用 Start 方法,则源需要从第 1 帧(在此时间之前的第一个关键帧)开始输出视频。 启动事件仍将指示事件数据中的 100 毫秒。

暂停媒体源

IMFMediaSource::P ause 方法暂停媒体源。

暂停源时,流可以创建新示例并将其存储在队列中,但流不会提供示例。 下面是此规则的一些例外情况:

  • 实时源应在暂停时删除数据。
  • 如果源从网络获取数据,它可能会暂停服务器。

如果客户端在源暂停时调用 IMFMediaStream::RequestSample ,则请求也会排队,直到再次启动源。 不应删除请求。

仅允许从启动状态暂停。 否则, Pause 应返回 MF_E_INVALID_STATE_TRANSITION

生成源数据

媒体基础使用 拉取模型,这意味着流生成和交付示例以响应来自管道的请求。 当媒体源正在运行且流处于选中状态时,流可以传送示例。 仅当客户端请求新示例时,流才会提供数据。

示例请求

客户端通过调用 IMFMediaStream::RequestSample 请求新示例。 操作顺序如下:

  1. 客户端调用 IMFMediaStream::RequestSample。 参数是指向客户端用于跟踪请求的可选 令牌 对象的指针。 客户端实现令牌。 令牌必须公开 IUnknown 接口。 客户端还可以传入 NULL 指针而不是令牌。

  2. 如果客户端提供了令牌,媒体流将调用令牌上的 AddRef ,并将该令牌置于先入先出队列中。 方法返回 ,其余步骤以异步方式执行。

  3. 当有更多数据可用时,媒体流会创建一个新示例。 (下一部分更详细地介绍了此步骤。)

  4. 媒体流从队列中拉取第一个令牌。

  5. 如果令牌不为 NULL,则媒体流在媒体示例中设置 MFSampleExtension_Token 属性。 属性的值是指向令牌的指针。

  6. 媒体流发送 MEMediaSample 事件。 事件数据是指向示例的 IMFSample 接口的指针。

  7. 如果客户端提供了令牌,则媒体流对令牌对象调用 Release

如果媒体流无法满足客户端的 RequestSample 请求,它将从队列中提取令牌并对令牌调用 Release ,但不发送 MEMediaSample 事件。

客户端可以使用令牌来跟踪请求的状态。 当客户端收到 MEMediaSample 事件时,它可以从示例中获取令牌,并将其与原始请求进行匹配。 客户端还可以使用该令牌来检测媒体源是否丢弃了请求。 如果令牌的引用计数下降到零,并且媒体流不发送 MEMediaSample 事件,则意味着请求已删除。

此处列出的步骤假定 RequestSample 方法作为异步操作实现。 如果方法是同步的,则无需将请求令牌放在队列中。 但是,如果生成数据需要很长时间,建议使用异步方法,例如,如果源从字节流读取数据。

流负责缓冲调用 RequestSample 之间累积的任何数据。

当媒体流到达流的末尾时,它会在最后一个示例之后发送 MEEndOfStream 事件。 每个流结束后,媒体源将发送 MEEndOfPresentation 事件。 媒体流发送 MEEndOfStream 事件后, RequestSample 方法将返回 MF_E_END_OF_STREAM ,直到源重新启动为止。

分配示例

当流准备好填充挂起的示例请求时,它会创建一个新示例,并将一个或多个媒体缓冲区添加到示例。 有关创建媒体缓冲区的详细信息,请参阅 媒体缓冲区

流必须设置时间戳和持续时间(如果已知)。 时间戳相对于源。 在大多数情况下,内容的开头对应于零的时间戳。 例如,如果源从媒体文件读取数据,则文件的开头时间戳将为零。

示例上的时间戳不一定等于演示时间。 媒体会话将源时间转换为演示时间。 对于压缩数据,流应在开始时间之前从最近的关键帧开始生成数据。 这使解码器能够传送在请求的开始时间显示的帧。 (否则,解码器需要等到以下关键帧。)

如果播放速率快于或慢于 1.0,则管道会调整演示时钟的速率。 源不会调整样本上的时间戳。

源可以通过设置属性来设置有关示例的其他信息。 有关示例属性的列表,请参阅 示例属性

流中的间隙

如果流包含长度很长的间隙,建议该流发送 MEStreamTick 事件。 此事件通知客户端缺少示例。 事件数据是缺失样本的时间戳,以 100 纳秒为单位 (VT_I8) 。 此事件可以避免下游组件等待不会到达的样本。 流可以根据需要发送任意数量的 MEStreamTick 事件,以跨越流中的间隙。

关闭媒体源

当客户端使用完媒体源时,它将调用 IMFMediaSource::Shutdown。 在此方法中,媒体源应中断任何循环引用计数。 通常,媒体源和媒体流之间会有循环引用。

如果使用事件队列实现 IMFMediaEventGenerator,请在事件队列上调用 IMFMediaEventQueue::Shutdown 。 此方法关闭事件队列,并向当前正在等待事件的任何调用方发出信号。

关闭后,除 IUnknown 方法之外,源上的所有方法都返回MF_E_SHUTDOWN

实时源

从 Windows 7 开始,Media Foundation 会自动支持音频和视频捕获设备。 对于视频,设备必须在视频捕获类别中提供内核流式处理 (KS) 微型驱动程序。 Media Foundation 使用 PnP 路径枚举设备。 对于音频,Media Foundation 使用 Windows 多媒体设备 (MMDevice) API 来枚举音频终结点设备。 如果设备满足这些条件,则无需实现自定义媒体源。

但是,你可能希望为某种其他类型的设备或其他实时数据源实现自定义媒体源。 实时源与其他媒体源之间只有一些差异:

  • IMFMediaSource::GetCharacteristics 方法中,返回 MFMEDIASOURCE_IS_LIVE 标志。
  • 第一个示例的时间戳应为零。
  • 事件和流式处理状态与媒体源相同,但暂停状态除外。
  • 暂停时,不要将样本排队。 删除暂停时生成的任何数据。
  • 实时源通常不支持查找、反向播放或速率控制。

媒体源

教程:编写自定义媒体源