如何使用 DirectShow 检测 PAL 或 NTSC 信号的存在?

How to detect the presence of a PAL or NTSC signal using DirectShow?

背景

为了记录来自各种模拟摄像机的复合视频信号,我使用了 AverMedia (C039) 生产的基本 USB 视频采集设备。

我有两台模拟摄像机,一台产生 PAL 信号,另一台产生 NTSC 信号:

  1. PAL B,625 线,25 帧/秒
  2. NTSC M,525 线,29.97 fps(即 30/1.001)

遗憾的是,AverMedia C039 采集卡的驱动程序不会根据连接的相机自动设置正确的视频标准。

目标

我希望捕获驱动程序根据连接的相机自动配置为正确的视频标准,PAL 或 NTSC。

方法

基本思路是设定一个视频标准,例如PAL,检查信号,如果没有检测到信号,则切换到其他制式。

通过拼凑 DirectShow documentation 中的一些示例,我能够从命令行手动设置正确的视频标准。

所以,我需要做的就是弄清楚如何在切换到 PAL 或 NTSC 后检测是否存在信号。

我知道必须可以自动检测信号类型,例如在书中"Video Demystified"。 此外,(商业)AMCap viewer 软件实际上证明它可以做到。

然而,尽管我尽了最大的努力,我还是没能完成这项工作。

有人可以解释如何使用 C++ 中的 DirectShow 检测是否存在 PAL 或 NTSC 信号吗?

Windows/COM/DirectShow 编程世界对我来说还是个新手,欢迎任何帮助。

我试过的

使用IAMAnalogVideoDecoder接口,我可以读取当前标准(get_TVFormat()),写入标准(put_TVFormat()),读取行数等。

我的步骤可以总结如下:

// summary of steps used to set the video standard, given a device moniker
// NOTE: declarations, error handling, cleanup, and details of device enumeration are omitted, for brevity
InitCaptureGraphBuilder(&pGraph, &pBuild);
pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
pGraph->AddFilter(pCap, L"Capture Filter");
pBuild->FindInterface(&PIN_CATEGORY_ANALOGVIDEOIN, &MEDIATYPE_AnalogVideo, pCap, IID_IAMAnalogVideoDecoder, (void**)&pDecoder);
pDecoder->put_TVFormat(AnalogVideo_PAL_B);  // or AnalogVideo_NTSC_M

上面的步骤实际上没有 运行 宁图形。

IAMAnalogVideoDecoder接口还定义了一个get_HorizontalLocked()方法,returns成功,但无论是否连接相机,输出值似乎没有变化。

我可以想象可能有必要 运行 该图以获得更新的信息,例如水平同步,但这似乎没有什么不同,虽然我不确定我的方法是否正确。

一些观察

下面描述的对话框是 AMCap viewer 软件(选项->视频设备->属性)的屏幕截图。 这与随 Windows SDK 提供的 DirectShow 的 AmCap sample 不同(尽管它可能基于此)。

当我(断开)连接符合指定标准的相机时,此对话框中的“检测到信号”值会发生变化。 (尽管无论是否连接相机,“检测到的线条”值都保持不变。)

“检测到信号”值正是我要查找的值。 但是,我在 DirectShow 文档和 property set for analog video decoder devices 中都找不到任何提及它的地方。 这可能与水平同步有关吗?

该对话框看起来与我使用 ffmpeg:

打开视频设备时出现的对话框相同
ffmpeg -f dshow -show_video_device_dialog true -i video="..." ...

但是,在这种情况下,“检测到信号”值在相机连接(断开)时不会改变。

我想这两个程序都使用 filter property pages.

生成此对话框

AverMedia SDK 确实定义了一个 AVerGetSignalPresence() 函数。 不确定这是否可以完成这项工作,但如果可以使用“纯”DirectShow 完成,我宁愿不引入依赖项。

更新

GraphEdit 中使用捕获设备后,我注意到“检测到信号”值仅在连接了(视频)渲染器时才会更新(并且图表是 运行宁或暂停):

提到的 属性 页面可能会使用 IAMAnalogVideoDecoder and get_HorizontalLocked method in particular. Note that you might be limited in receiving valid status by requirement to have the filter graph in paused or running state, which in turn might require that you connect a renderer to complete the data path (Video Renderer or Null Renderer 或您选择的其他渲染器提取数据。

另请参阅 this question 空渲染器弃用和最坏情况下替换源代码。

感谢 和评论,我能够验证 IAMAnalogVideoDecoder->get_HorizontalLocked() 确实可以用来检测 PAL 或 NTSC 视频信号的存在。

为了从 get_HorizontalLocked 中获得有意义的值,似乎有必要:

  • 将过滤器连接到捕获过滤器的捕获输出引脚
  • 运行(或暂停)图表

请注意,图表 运行 成功,即使没有任何东西连接到捕获输出引脚,但在这种情况下,来自 get_HorizontalLocked 的值仍然存在零,无论信号是否存在。

而不是 Video Renderer or Null Renderer, I connected a Smart Tee filter to the output pin of the capture filter. This appears to be sufficient to make get_HorizontalLocked work properly, and it's simpler than including a video renderer or working around the Null Renderer deprecation 问题。

只是为了说明基本步骤,这是我所做的,假设相机已经连接:

// assuming we have a pMoniker from device enumeration
IBaseFilter *pCap;
IGraphBuilder *pGraph;
ICaptureGraphBuilder2 *pBuild;
IMediaControl *pControl;
IAMAnalogVideoDecoder *pDecoder;
IBaseFilter *pRender;
long lLocked;

// build graph
InitCaptureGraphBuilder(&pGraph, &pBuild);
pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
pGraph->AddFilter(pCap, L"Capture Filter");
AddFilterByCLSID(pGraph, CLSID_SmartTee, &pRender, L"Smart tee");
pBuild->RenderStream(NULL, NULL, pCap, NULL, pRender);  // connect the filters

// get interfaces
pBuild->FindInterface(&PIN_CATEGORY_ANALOGVIDEOIN, &MEDIATYPE_AnalogVideo, 
    pCap, IID_IAMAnalogVideoDecoder, (void**)&pDecoder);
pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);

// pause graph (or run)
pControl->Pause();

// set standard, check horizontal sync lock
pDecoder->put_TVFormat(AnalogVideo_NTSC_M);
pDecoder->get_HorizontalLocked(&lLocked);
if (!lLocked)
{
    pDecoder->put_TVFormat(AnalogVideo_PAL_B);
    pDecoder->get_HorizontalLocked(&lLocked);
    ...
}

请注意:这不是一个完整的工作示例,只是对调用顺序的说明。

为了简洁起见,我省略了设备枚举、错误处理、清理等。我包含了简化的声明,只是为了显示使用了哪些接口。

根据应用程序,可能需要重复 get_HorizontalLocked() 调用以提高稳健性。

DirectShow 文档提供了 InitCaptureGraphBuilder, AddFilterByCLSID, and device enumeration 的工作示例。