c++ - 如何在 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 中水平镜像视频?
解决方案
如果选项 E 不起作用(参见上面的评论;源矩形和目标矩形都不允许镜像),并且考虑到它是 DirectShow,我会提供研究选项 F。
但是,如果您以前从未这样做过,编写一个完整的过滤器可能不是那么简单。不过这里有一些捷径。您不需要开发完整的过滤器:至少可以使用两种替代方法来实现类似的功能:
- 带有
ISampleGrabberCB::SampleCB
回调的示例 Grabber 过滤器。您会发现很多关于此技术的提及:当插入到图形中时,您的代码可以接收到每个处理帧的回调。如果在回调中重新排列帧缓冲区中的像素,图像将被镜像。 - 在 DMO Wrapper Filter 的帮助下,实现一个 DMO 并将其插入到过滤器图中。您将有机会以类似的方式重新排列帧的像素,并以更多的代码为代价来获得更多的灵活性。
两者都将更容易做到,因为您不必使用 DirectShow BaseClasses,后者在 2020 年众所周知已过时。
两者都不需要了解 DirectShow 过滤器中的数据流。两者以及开发完整的 DirectShow 过滤器都假定您的代码支持以有限的一组像素格式重新排列。例如,您可以使用 24 位 RGB,或典型的网络摄像头格式,例如 NV12(现在)。
如果您的像素数据重新排列做得很好,无需对代码进行超级优化,您可以忽略性能影响——无论哪种方式,在大多数情况下都可以忽略不计。
我预计 Media Foundation 解决方案的集成会更加复杂,如果 Media Foundation 解决方案要真正得到很好的优化,则要复杂得多。
问题的复杂性首先是以下因素的组合。
首先,您混合了不同的解决方案:
- 直接在网络摄像头(驱动程序)中进行镜像,您的镜像设置会导致视频帧一开始就已经镜像。
- 在数据流经管道时进行镜像。尽管这听起来很简单,但事实并非如此:有时帧尚未压缩(网络摄像头经常发送 JPEG),有时帧可以由视频内存支持,有多种像素格式等
- 镜像作为视频呈现。
您的方法 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 年前已经解释过类似的内容。
推荐阅读
- go - 使用异常选择的意外行为
- python - 如何在 Dash Store 中使用 Dash 加载?
- python - 在另一个函数中调用一个函数,但一个变量是从另一个函数声明/实例化/初始化/分配的
- postgresql - 为什么不遵守 PostgreSQL 规则条件?
- reactjs - React-create-app index.js 文件不会自动重新加载
- mysql - MySQL查询如果不相等则复制整行
- generics - 如何在 Dart 中创建更通用的 isEmpty() 函数?
- python - python线程中的返回值
- javascript - 在 Vue 挂载期间未定义 Cordova 插件
- javascript - 从 MongoDB 中的嵌套数组中查找公共属性