备用视频呈现器

[与此页面关联的功能(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)。
  • 筛选器图形通知,这些通知是从筛选器发送到 Filter Graph 管理器的事件,例如 EC_COMPLETE。 这是通过在 Filter Graph 管理器上调用 IMediaEventSink::Notify 方法来实现的。
  • 应用程序通知,这些通知由控制应用程序从 Filter Graph 管理器中检索。 应用程序在 Filter Graph 管理器上调用 IMediaEvent::GetEvent 方法以检索这些事件。 筛选器图形管理器通常会将它收到的事件传递给应用程序。

本主题讨论呈现器筛选器在处理它收到的流通知以及发送适当的筛选器图形通知时的责任。

处理流结束和刷新通知

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

当呈现器 IPin::EndOfStream 方法被上游筛选器调用时,呈现器会收到流结束通知。 呈现器应注意此通知,并继续呈现已收到的任何数据。 收到所有剩余数据后,呈现器应向 Filter Graph 管理器发送 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。 此外,刷新呈现器时,还应清除任何缓存的流结束通知。 呈现器通常会刷新以响应查找。 刷新可确保在发送新样本之前从筛选器图中清除旧数据。 (通常,一个流两个部分的播放是最好通过延迟的命令处理,而不是等待一个部分完成,然后发出查找命令。

处理状态更改和暂停完成

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

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

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

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

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

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

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

处理终止

视频呈现器必须正确处理来自用户的终止事件。 这意味着正确隐藏窗口,并知道在随后强制显示窗口时该怎么做。 此外,视频呈现器必须在窗口被销毁时(或者更准确地说,当呈现器从筛选器图中删除)以释放资源时通知筛选器图形管理器。

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

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

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

处理动态格式更改

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

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

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

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

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

使用某些解码器,将 biHeight 设置为 YUV 类型的正数会导致解码器将图像倒置。 (这不正确,应被视为解码器中的 bug。

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

每当视频呈现器检测到流中的调色板更改时,它都应将 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 Manager。 如果在暂停时收到 EC_REPAINT 通知,则 Filter Graph 管理器会调用 IMediaPosition::p ut_CurrentPosition 以及当前位置(即,查找当前位置)。 这会导致源筛选器刷新筛选器图,并导致通过筛选器图发送新数据。

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

如果呈现器不监视其 EC_REPAINT 通知,它将用 EC_REPAINT 请求(处理成本相对较高)淹没筛选器图形管理器。 例如,如果呈现器没有要绘制的图像,并且呈现器在完全拖动作中跨呈现器窗口拖动另一个窗口,则呈现器将接收多个 WM_PAINT 消息。 只有其中第一个应生成从呈现器到 Filter Graph 管理器 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 通知在退出全屏模式时发送到 Filter Graph 管理器时,Filter Graph Manager 会将 EC_FULLSCREEN_LOST 通知发送到控制应用程序。 例如,应用程序可能使用此通知来还原全屏按钮的状态。 DirectShow 在内部使用 EC_ACTIVATE 通知来管理来自视频呈现器的提示的全屏切换。

通知摘要

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

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

 

编写视频呈现器