首页 > 解决方案 > 如何在 DirectShow 中水平镜像视频?

问题描述

我需要在屏幕上显示本地网络摄像头流,水平翻转,使屏幕显示为镜像。我有一个 DirectShow 图表,它可以完成所有这些,除了镜像图像。我尝试了几种方法来镜像图像,但都没有奏效。

方法一:VideoControlFlag_FlipHorizontal

我尝试在网络摄像头过滤器的输出引脚上设置标志VideoControlFlag_FlipHorizontal ,如下所示

IAMVideoControl* pAMVidControl;
IPin* pWebcamOutputPin;
// ...
// Omitting error-handing for brevity
pAMVidControl->SetMode(pWebcamOutputPin, VideoControlFlag_FlipHorizontal);

但是,这没有效果。实际上,网络摄像头过滤器声称没有此功能或任何其他功能:

long supportedModes;
hr = pAMVidControl->GetCaps(pWebcamOutputPin, &supportedModes);

// Prints 0, i.e. no capabilities
printf("Supported modes: %ld\n", supportedModes);

方法 B:SetVideoPosition

我尝试通过翻转传递给SetVideoPosition. (我在无窗口模式下使用增强型视频渲染器过滤器。)有两个矩形:源矩形和目标矩形。我都试过了。这是方法 B(i),翻转源矩形:

MFVideoNormalizedRect srcRect;
srcRect.left = 1.0;  // note flipped
srcRect.right = 0.0; // note flipped
srcRect.top = 0.0;
srcRect.bottom = 0.5;

return m_pVideoDisplay->SetVideoPosition(&srcRect, &destRect);

这导致没有显示任何内容。它适用于其他配置,但似乎不喜欢srcRect.left > srcRect.right.

这是方法 B(ii),翻转目标矩形:

RECT destRect;
GetClientRect(hwnd, &destRect);

LONG left = destRect.left;
destRect.left = destRect.right;
destRect.right = left;

return m_pVideoDisplay->SetVideoPosition(NULL, &destRect);

这也导致不显示任何内容。它适用于其他配置,但似乎不喜欢destRect.left > destRect.right.

方法 C:IMFVideoProcessorControl::SetMirror

IMFVideoProcessorControl::SetMirror(MF_VIDEO_PROCESSOR_MIRROR) 听起来像我想要的。此IMFVideoProcessorControl接口由Video Processor MFT实现。不幸的是,这是一个媒体基础转换,我看不出如何在 DirectShow 中使用它。

方法 D:视频调整器 DSP

Video Resizer DSP 是“一个可以充当 DMO 的 COM 对象”,所以理论上我可以在 DirectShow 中使用它。不幸的是,我没有使用 DMO 的经验,而且无论如何,Video Resizer 的文档都没有说明它是否支持翻转图像。

方法 E:IVMRMixerControl9::SetOutputRect

我发现 IVMRMixerControl9::SetOutputRect,其中明确表示:

因为这个矩形存在于组合空间中,所以不存在“无效”矩形之类的东西。例如,设置 left 大于 right 以在 x 方向上镜像视频。

但是,IVMRMixerControl9似乎已弃用,并且我使用的是 EVR 而不是 VMR,并且没有关于如何获取的文档IVMRMixerControl9

方法 F:编写我自己的 DirectShow 过滤器

我不愿意尝试这个,除非我必须这样做。这将是一项重大投资,而且我不确定它的性能是否足够。

方法 G:从媒体基金会重新开始

Media Foundation 可能会让我解决这个问题,因为它提供了“Media Foundation Transforms”。但甚至不清楚媒体基金会是否符合我的所有其他要求。

我很惊讶我正在为看起来如此标准的转换寻找如此激进的解决方案。还存在哪些其他方法?在我尝试过的方法中有什么我忽略的吗?如何在 DirectShow 中水平镜像视频?

标签: c++winapivideotransformdirectshow

解决方案


如果选项 E 不起作用(参见上面的评论;源矩形和目标矩形都不允许镜像),并且考虑到它是 DirectShow,我会提供研究选项 F。

但是,如果您以前从未这样做过,编写一个完整的过滤器可能不是那么简单。不过这里有一些捷径。您不需要开发完整的过滤器:至少可以使用两种替代方法来实现类似的功能:

  1. 带有ISampleGrabberCB::SampleCB回调的示例 Grabber 过滤器。您会发现很多关于此技术的提及:当插入到图形中时,您的代码可以接收到每个处理帧的回调。如果在回调中重新排列帧缓冲区中的像素,图像将被镜像。
  2. 在 DMO Wrapper Filter 的帮助下,实现一个 DMO 并将其插入到过滤器图中。您将有机会以类似的方式重新排列帧的像素,并以更多的代码为代价来获得更多的灵活性。

两者都将更容易做到,因为您不必使用 DirectShow BaseClasses,后者在 2020 年众所周知已过时。

两者都不需要了解 DirectShow 过滤器中的数据流。两者以及开发完整的 DirectShow 过滤器都假定您的代码支持以有限的一组像素格式重新排列。例如,您可以使用 24 位 RGB,或典型的网络摄像头格式,例如 NV12(现在)。

如果您的像素数据重新排列做得很好,无需对代码进行超级优化,您可以忽略性能影响——无论哪种方式,在大多数情况下都可以忽略不计。

我预计 Media Foundation 解决方案的集成会更加复杂,如果 Media Foundation 解决方案要真正得到很好的优化,则要复杂得多。

问题的复杂性首先是以下因素的组合。

首先,您混合了不同的解决方案:

  1. 直接在网络摄像头(驱动程序)中进行镜像,您的镜像设置会导致视频帧一开始就已经镜像。
  2. 在数据流经管道时进行镜像。尽管这听起来很简单,但事实并非如此:有时帧尚未压缩(网络摄像头经常发送 JPEG),有时帧可以由视频内存支持,有多种像素格式等
  3. 镜像作为视频呈现。

您的方法 A 是上面的#1。但是,如果不支持尊重模式,则无法镜像。

在 EVR 渲染器 #3 中进行镜像在理论上显然是可能的。EVR 使用 Direct3D 9 并在内部将表面(纹理)渲染到场景中,因此绝对有可能以镜像的方式设置表面的 3D 位置。但是,这里的问题是 API 设计和坐标检查阻止传递镜像参数。

然后 Direct3D 9 几乎已被弃用,DirectShow 本身甚至 DirectShow/Media Foundation 的 EVR 都与当前的 Direct3D 11 不兼容。即使可能存在通过硬件进行镜像的功能,您可能很难通过旧版使用它API。

当您想要一个简单的解决方案时,当数据流过时,镜像会受到限制,#2 就是这样。即使这与合理的性能影响相关,您也不需要依赖特定的相机或视频硬件支持:您只需交换每一帧中的像素即可。

正如我所提到的,最简单的方法是SampleCB使用 24 位 RGB 和/或 NV12 像素格式设置回调。这也取决于您的应用程序正在做什么,但是如果没有此类信息,我会说实现 24 位 RGB 并拥有视频帧数据就足够了,您只需逐行交换三字节像素数据宽度/2次。如果应用程序管道允许,您可能希望有额外的代码路径来翻转 NV12,这很相似,但没有首先将视频转换为 RGB,因此效率更高。如果 NV12 不能工作,RGB24 将是备用代码路径。

另请参阅:DirectShow.NET 的镜像效果- 我似乎在 8 年前已经解释过类似的内容。


推荐阅读