备用视频呈现器

[与此页面关联的功能 DirectShow 是一项旧功能。 它已被 MediaPlayerIMFMediaEngine媒体基金会中的音频/视频捕获取代。 这些功能已针对Windows 10和Windows 11进行了优化。 Microsoft 强烈建议新代码尽可能使用 MediaPlayerIMFMediaEngineMedia Foundation 中的音频/视频捕获 ,而不是 DirectShow。 如果可能,Microsoft 建议重写使用旧 API 的现有代码以使用新 API。]

本主题介绍如何为 DirectShow 编写自定义视频呈现器。

注意

建议为视频混合呈现器编写插件分配器,而不是编写自定义视频呈现器, (VMR) 或 增强的视频呈现器 (EVR) 。 此方法将为你提供 VMR/EVR 的所有优势,包括支持 DirectX 视频加速 (DXVA) 、硬件去交错和帧步进,并且可能比自定义视频呈现器更可靠。 有关详细信息,请参阅下列主题:

 

编写备用呈现器

Microsoft DirectShow 提供基于窗口的视频呈现器;它还在运行时安装中提供全屏呈现器。 可以使用 DirectShow 基类编写备用视频呈现器。 要使备用呈现器能够与基于 DirectShow 的应用程序正确交互,呈现器必须遵守本文中概述的准则。 可以使用 CBaseRendererCBaseVideoRenderer 类来帮助在实现备用视频呈现时遵循这些准则。 由于 DirectShow 的持续开发,请定期查看实现,以确保呈现器与最新版本的 DirectShow 兼容。

本主题讨论呈现器负责处理的许多通知。 简要回顾 DirectShow 通知可能有助于设置阶段。 DirectShow 中基本上有三种类型的通知:

  • 流通知,即媒体流中发生的事件,从一个筛选器传递到下一个筛选器。 这些通知可以是开始刷新、结束刷新或流结束通知,并通过调用下游筛选器的输入引脚上的相应方法发送 (例如 IPin::BeginFlush) 。
  • 筛选图通知,这些通知是从筛选器发送到筛选器关系图管理器的事件,例如 EC_COMPLETE。 这是通过在 Filter Graph 管理器上调用 IMediaEventSink::Notify 方法实现的。
  • 应用程序通知,由控制应用程序从 Filter Graph Manager 检索。 应用程序调用 Filter Graph 管理器上的 IMediaEvent::GetEvent 方法来检索这些事件。 通常,筛选器关系图管理器会将其接收的事件传递给应用程序。

本主题讨论呈现器筛选器在处理接收的流通知和发送相应筛选器图通知方面的责任。

处理流结束通知和刷新通知

流结束通知从上游筛选器开始, (例如源筛选器) ,当该筛选器检测到无法发送更多数据时。 它通过图形中的每个筛选器传递,最终在呈现器处结束,呈现器负责随后向筛选器图管理器发送 EC_COMPLETE 通知。 处理这些通知时,呈现器负有特殊责任。

当上游筛选器调用其输入引脚的 IPin::EndOfStream 方法时,呈现器会收到流结束通知。 呈现器应记下此通知并继续呈现它已收到的任何数据。 收到所有剩余数据后,呈现器应向筛选器关系图管理器发送 EC_COMPLETE 通知。 每次到达流的末尾时,呈现器只能发送一次 EC_COMPLETE 通知。 此外,除非筛选器图正在运行,否则不得发送 EC_COMPLETE 通知。 因此,如果在源筛选器发送流结束通知时暂停筛选器图,则在筛选器图最终运行之前,不应发送 EC_COMPLETE

在发出流结束通知信号后,应拒绝对 IMemInputPin::ReceiveIMemInputPin::ReceiveMultiple 方法的任何调用。 在这种情况下,E_UNEXPECTED 是返回的最合适的错误消息。

当筛选器图停止时,任何缓存的流结束通知都应被清除,并且不会在下次启动时重新发送。 这是因为筛选器关系图管理器始终在运行筛选器之前暂停所有筛选器,以便进行适当的刷新。 因此,例如,如果暂停筛选器图并收到流结束通知,然后筛选器图停止,则呈现器不应在随后运行时发送 EC_COMPLETE 通知。 如果未发生搜寻,源筛选器将在运行状态之前的暂停状态期间自动发送另一个流结束通知。 另一方面,如果在筛选器图停止时发生了查找,则源筛选器可能有要发送的数据,因此不会发送流结束通知。

视频呈现器通常依赖于流结束通知,而不是 发送EC_COMPLETE 通知。 例如,如果一个流已完成播放 (即发送流结束通知) 另一个窗口被拖动到视频呈现器窗口上,则将生成大量 WM_PAINT 窗口消息。 运行视频呈现器的典型做法是在收到 WM_PAINT 消息 (时避免重新绘制当前帧,前提是) 接收要绘制的另一帧。 但是,当发送流结束通知时,呈现器处于等待状态;它仍在运行,但知道不会收到任何其他数据。 在这些情况下,呈现器通常将播放区域绘制为黑色。

处理刷新是呈现器的另一个复杂问题。 刷新是通过一对名为 BeginFlushEndFlushIPin 方法执行的。 刷新本质上是呈现器必须处理的附加状态。 源筛选器在不调用 EndFlush 的情况下调用 BeginFlush 是非法的,因此希望状态为简短且离散;但是,呈现器必须正确处理在刷新转换期间收到的数据或通知。

调用 BeginFlush 后收到的任何数据应立即通过返回 S_FALSE来拒绝。 此外,刷新呈现器时,还应清除任何缓存的流结束通知。 呈现器通常会刷新以响应查找。 刷新可确保在发送新样本之前从筛选器图中清除旧数据。 (通常,流中两个部分的播放(一个接一个)最好通过延迟的命令进行处理,而不是等待一个部分完成,然后发出 seek 命令。)

处理状态更改和暂停完成

当呈现器筛选器的状态发生更改时,呈现器筛选器的行为与筛选器图中的任何其他筛选器的行为相同,但有以下例外情况。 暂停后,呈现器将有一些数据排队,准备在随后运行时呈现。 视频呈现器停止时,它会保留此排队的数据。 这是 DirectShow 规则的例外,即筛选器在停止筛选器关系图时不应保留任何资源。

出现此异常的原因是,通过持有资源,呈现器将始终具有一个图像,如果它收到 WM_PAINT 消息,它将使用该图像重新绘制窗口。 它还具有一个图像,用于满足请求当前映像副本的方法,例如 CBaseControlVideo::GetStaticImage。 持有资源的另一个影响是,按住映像会阻止分配器解除提交,这反过来又会使下一个状态更改发生得更快,因为已分配映像缓冲区。

视频呈现器应仅在运行时呈现和释放示例。 暂停时,筛选器可能会 (呈现它们,例如,在) 的窗口中绘制静态海报图像,但不应释放它们。 音频呈现器在暂停 (不执行渲染,尽管它们可以执行其他活动,例如准备波形设备,例如) 。 通过将示例中的流时间与作为 参数传递给 IMediaControl::Run 方法的引用时间相结合,获取应呈现样本的时间。 呈现器应拒绝开始时间小于或等于结束时间的样本。

当应用程序暂停筛选器图时,筛选器图不会从其 IMediaControl::P ause 方法返回,直到呈现器上存在排队的数据。 为了确保这一点,当呈现器暂停时,如果没有等待呈现的数据,则呈现器应返回S_FALSE。 如果数据已排队,则可以返回 S_OK

筛选器关系图管理器在暂停筛选图时检查所有返回值,以确保呈现器的数据已排队。 如果一个或多个筛选器未就绪,筛选器关系图管理器将通过调用 IMediaFilter::GetState 轮询图中的筛选器。 GetState 方法采用超时参数。 筛选器 (通常呈现器) ,在完成状态更改之前仍在等待数据到达,如果 GetState 方法过期,则返回VFW_S_STATE_INTERMEDIATE。 数据到达呈现器后,应立即返回 GetStateS_OK

在中间和已完成状态下,将State_Paused报告的筛选器状态。 只有返回值指示筛选器是否确实就绪。 如果在呈现器等待数据到达时,其源筛选器会发送流结束通知,则该通知也应完成状态更改。

所有筛选器实际上都有等待呈现的数据后,筛选器图将完成其暂停状态更改。

处理终止

视频呈现器必须正确处理来自用户的终止事件。 这意味着正确隐藏窗口,并知道在随后强制显示窗口时要执行的操作。 此外,视频呈现器必须在 (或更准确地销毁其窗口时通知筛选器关系图管理器,当呈现器从筛选器图中删除时,) 释放资源。

如果用户关闭视频窗口 (例如按 Alt+F4) ,约定是立即隐藏窗口,并将 EC_USERABORT 通知发送到 Filter Graph 管理器。 此通知将传递到应用程序,该应用程序将停止播放图形。 发送 EC_USERABORT后,视频呈现器应拒绝交付给它的任何其他样本。

呈现器应保留图形停止标志,直到它随后停止,此时应重置它,以便应用程序可以替代用户操作,并根据需要继续播放图形。 如果在视频运行时按下 Alt+F4,窗口将被隐藏,并且将拒绝交付的所有进一步示例。 如果随后通过 IVideoWindow::p ut_Visible) (显示窗口,则不应生成 EC_REPAINT 通知。

视频呈现器终止时,视频呈现器还应将 EC_WINDOW_DESTROYED 通知发送到筛选器图。 事实上,当使用 null 参数调用呈现器的 IBaseFilter::JoinFilterGraph 方法时,最好处理此问题, (指示呈现器即将从筛选器图中删除) ,而不是等到实际的视频窗口被销毁。 发送此通知可使 Filter Graph 管理器中的插件分发服务器将依赖于窗口焦点的资源传递给其他筛选器,例如音频设备。

处理动态格式更改

在某些情况下,呈现器的上游筛选器可能会尝试在播放视频时更改视频格式。 它通常是视频解压缩器启动动态格式更改。

尝试动态更改格式的上游筛选器应始终调用呈现器输入引脚上的 IPin::QueryAccept 方法。 对于视频呈现器应支持的动态格式更改类型,视频呈现器有一定的回旋余地。 它至少应允许上游筛选器更改调色板。 当上游筛选器更改媒体类型时,它会将媒体类型附加到以新格式提供的第一个示例。 如果呈现器在用于呈现的队列中保存样本,则在呈现具有类型更改的示例之前,它不应更改格式。

视频呈现器还可以从解码器请求格式更改。 例如,它可能会要求解码器提供具有负 biHeight 的 DirectDraw 兼容格式。 当呈现器暂停时,它应在上游引脚上调用 QueryAccept,以查看解码器可以提供的格式。 但是,解码器可能不会枚举它可以接受的所有类型,因此即使解码器不播发它们,呈现器也应该提供某些类型。

如果解码器可以切换到请求的格式,它将从 QueryAccept 返回S_OK。 然后,呈现器将新媒体类型附加到上游分配器上的下一个媒体示例。 为此,呈现器必须提供一个自定义分配器,该分配器实现用于将媒体类型附加到下一个示例的私有方法。 (在此专用方法中,调用 IMediaSample::SetMediaType 以设置 type.)

呈现器的输入插针应在 IMemInputPin::GetAllocator 方法中返回呈现器的自定义分配器。 重写 IMemInputPin::NotifyAllocator,以便在上游筛选器不使用呈现器的分配器时失败。

对于某些解码器,在 YUV 类型上将 biHeight 设置为正数会导致解码器倒置绘制图像。 (不正确,应将其视为 decoder.)

每当视频呈现器检测到格式更改时,它都应发送 EC_DISPLAY_CHANGED 通知。 大多数视频呈现器在连接期间选择格式,以便可以通过 GDI 有效地绘制格式。 如果用户在未重新启动计算机的情况下更改当前显示模式,则呈现器可能会发现自己的图像格式连接不正确,并且应发送此通知。 第一个参数应该是需要重新连接的引脚。 筛选器关系图管理器将安排停止筛选图并重新连接引脚。 在后续重新连接期间,呈现器可以接受更合适的格式。

每当视频呈现器检测到流中的调色板更改时,它都应将 EC_PALETTE_CHANGED 通知发送到筛选器关系图管理器。 DirectShow 视频呈现器检测调色板是否真的以动态格式更改。 视频呈现器这样做不仅可以筛选掉发送 的EC_PALETTE_CHANGED 通知数,还可以减少所需的调色板创建、安装和删除量。

最后,视频呈现器还可能检测到视频大小已更改,在这种情况下,它应发送 EC_VIDEO_SIZE_CHANGED 通知。 应用程序可能使用此通知协商复合文档中的空间。 实际视频尺寸可通过 IBasicVideo 控件接口获得。 DirectShow 呈现器检测视频在发送这些事件之前是否实际更改了大小。

处理持久属性

通过 IBasicVideoIVideoWindow 接口设置的所有属性都意味着跨连接持久。 因此,断开连接和重新连接呈现器不会对窗口大小、位置或样式产生任何影响。 但是,如果连接之间的视频尺寸发生更改,呈现器应将源矩形和目标矩形重置为默认值。 源和目标位置通过 IBasicVideo 接口设置。

IBasicVideoIVideoWindow 都提供对属性的足够访问权限,以允许应用程序以持久格式保存和还原接口中的所有数据。 这对于必须在编辑会话期间保存筛选器图的确切配置和属性并在以后还原它们的应用程序非常有用。

处理EC_REPAINT通知

仅当呈现器暂停或停止时,才会发送 EC_REPAINT 通知。 此通知向筛选器图形管理器发出呈现器需要数据信号。 如果筛选器图在收到其中一条通知时停止,它将暂停筛选器图,等待所有筛选器通过调用 GetState) 接收数据 (,然后再次停止它。 停止时,视频呈现器应保留图像,以便可以处理后续 WM_PAINT 消息。

因此,如果视频呈现器在停止或暂停时收到 WM_PAINT 消息,并且没有用于绘制窗口的内容,则应将 EC_REPAINT 发送到 Filter Graph 管理器。 如果在暂停时收到 EC_REPAINT 通知,则 Filter Graph 管理器使用当前位置 (调用 IMediaPosition::p ut_CurrentPosition ,即查找当前位置) 。 这会导致源筛选器刷新筛选器图,并导致通过筛选器图发送新数据。

呈现器一次只能发送其中一个通知。 因此,一旦呈现器发送通知,它应确保不会再发送一些样本,直到传递了一些样本。 执行此操作的传统方法是使用一个标志来表示可以发送重绘,该标记在发送 EC_REPAINT 通知后关闭。 在传递数据后或刷新输入引脚时,应重置此标志,但如果在输入引脚上发出了流结束信号,则不应重置。

如果呈现器不监视其 EC_REPAINT 通知,它将在筛选器关系图管理器中充斥 EC_REPAINT 请求 (处理) 成本相对较高。 例如,如果呈现器没有要绘制的图像,并且通过完全拖动操作在呈现器窗口上拖动另一个窗口,则呈现器将收到多个 WM_PAINT 消息。 只有其中第一个应生成从呈现器到筛选器关系图管理器的 EC_REPAINT 事件通知。

呈现器应将其输入引脚作为第一个参数发送到 EC_REPAINT 通知。 通过执行此操作,将查询 IMediaEventSink 的附加输出引脚,如果受支持,将首先将 EC_REPAINT 通知发送到该处。 这使输出引脚能够在必须触摸筛选器图之前处理重绘。 如果筛选器图已停止,则不会执行此操作,因为取消提交的呈现器分配器中没有缓冲区可用。

如果输出引脚无法处理请求,并且筛选器图正在运行,则 忽略EC_REPAINT 通知。 输出引脚必须从 IMediaEventSink::Notify 返回S_OK,以指示它已成功处理重绘请求。 输出引脚将在 Filter Graph Manager 工作线程上调用,这避免了呈现器直接调用输出引脚,从而避免出现任何死锁问题。 如果筛选器图已停止或暂停,并且输出未处理请求,则默认处理完成。

在Full-Screen模式下处理通知

筛选器图中的 IVideoWindow 插件分发服务器 (PID) 管理全屏播放。 它将视频呈现器交换为专业全屏呈现器,将呈现器的窗口拉伸为全屏,或让呈现器直接实现全屏播放。 若要在全屏协议中交互,视频呈现器应在其窗口激活或停用时发送 EC_ACTIVATE 通知。 换句话说,应为呈现器接收的每个 WM_ACTIVATEAPP 消息发送EC_ACTIVATE通知。

在全屏模式下使用呈现器时,这些通知将管理进入和退出全屏模式。 当用户按 Alt+TAB 切换到另一个窗口(DirectShow 全屏呈现器将其用作返回到典型呈现模式的提示)时,通常会发生窗口停用。

在退出全屏模式时将 EC_ACTIVATE 通知发送到筛选器关系图管理器时,筛选器关系图管理器会向控制应用程序发送 EC_FULLSCREEN_LOST 通知。 例如,应用程序可能会使用此通知来还原全屏按钮的状态。 directShow 在内部使用EC_ACTIVATE通知来管理视频呈现器提示的全屏切换。

通知摘要

本部分列出了呈现器可以发送的筛选器图通知。

事件通知 说明
EC_ACTIVATE 由视频呈现器在全屏呈现模式下针对收到的每个WM_ACTIVATEAPP消息发送。
EC_COMPLETE 呈现器在呈现所有数据后发送。
EC_DISPLAY_CHANGED 视频呈现器在显示格式更改时发送。
EC_PALETTE_CHANGED 每当视频呈现器检测到流中的调色板更改时发送。
EC_REPAINT 当收到WM_PAINT消息且没有要显示的数据时,由停止或暂停的视频呈现器发送。 这会导致筛选器关系图管理器生成一个要绘制到显示器的框架。
EC_USERABORT 由视频呈现器发送,以指示用户请求关闭 (例如,用户关闭视频窗口) 。
EC_VIDEO_SIZE_CHANGED 每当检测到本机视频大小更改时,视频呈现器发送。
EC_WINDOW_DESTROYED 删除或销毁筛选器时由视频呈现器发送,以便依赖于窗口焦点的资源可以传递给其他筛选器。

 

编写视频呈现器