3

Background

In order to record the composite-video signal from a variety of analog cameras, I use a basic USB video capture device produced by AverMedia (C039).

enter image description here

I have two analog cameras, one produces a PAL signal, the other produces an NTSC signal:

  1. PAL B, 625 lines, 25 fps
  2. NTSC M, 525 lines, 29.97 fps (i.e. 30/1.001)

Unfortunately, the driver for the AverMedia C039 capture card does not automatically set the correct video standard based on which camera is connected.

Goal

I would like the capture driver to be configured automatically for the correct video standard, either PAL or NTSC, based on the camera that is connected.

Approach

The basic idea is to set one video standard, e.g. PAL, check for signal, and switch to the other standard if no signal is detected.

By cobbling together some examples from the DirectShow documentation, I am able to set the correct video standard manually, from the command line.

So, all I need to do is figure out how to detect whether a signal is present, after switching to PAL or NTSC.

I know it must be possible to auto-detect the type of signal, as described e.g. in the book "Video Demystified". Moreover, the (commercial) AMCap viewer software actually proves it can be done.

However, despite my best efforts, I have not been able to make this work.

Could someone explain how to detect whether a PAL or NTSC signal is present, using DirectShow in C++?

The world of Windows/COM/DirectShow programming is still new to me, so any help is welcome.

What I tried

Using the IAMAnalogVideoDecoder interface, I can read the current standard (get_TVFormat()), write the standard (put_TVFormat()), read the number of lines, and so on.

The steps I took can be summarized as follows:

// 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

The steps above work without actually running the graph.

The IAMAnalogVideoDecoder interface also defines a get_HorizontalLocked() method, which returns successful, but the output value does not appear to change, regardless of whether a camera is connected.

I can imagine that it may be necessary to run the graph in order to get updated information regarding e.g. horizontal sync, but that did not seem to make a difference, although I'm not sure my approach was correct.

Some observations

The dialog depicted below is a screenshot from the AMCap viewer software (Options->Video Device->Properties). This is not the same thing as the AmCap sample for DirectShow, supplied with the Windows SDK (although it maybe based upon that).

The "Signal Detected" value in this dialog changes when I (dis)connect a camera matching the specified standard. (Although the "Lines detected" value remains the same regardless of whether a camera is connected.)

property page

The "Signal Detected" value is actually what I am looking for. However, I could not find any mention of it in the DirectShow docs, nor in the property set for analog video decoder devices. Could this be related to the horizontal sync, somehow?

The dialog looks identical to the dialog that appears when I open the video device using ffmpeg:

ffmpeg -f dshow -show_video_device_dialog true -i video="..." ...

However, in this case the "Signal detected" value does not change when a camera is (dis)connected.

I suppose both programs produce this dialog using the filter property pages.

The AverMedia SDK does define an AVerGetSignalPresence() function. Not sure if that would do the job, but I would rather not introduce the dependency if it can be done using "pure" DirectShow.

UPDATE

After playing around with the capture device in GraphEdit, I noticed that the "Signal Detected" value is only updated if a (Video) Renderer is connected (and the graph is either running or paused):

graphedit

djvg
  • 11,722
  • 5
  • 72
  • 103
  • My guess is it's still `get_HorizontalLocked` and the property page just polls for it so it changes. – Roman R. Nov 15 '21 at 20:45
  • 1
    Essentially you need to keep calling `get_HorizontalLocked` in a timer handler or alike. I believe this is what property page is doing. Something to keep in mind: this method might be returning valid information only when pins are connected and/or when graph is paused/running. Ignoring this you might be confused that sometimes it work and sometimes it does not. Programmatically you'd just build the graph, pause it and keep calling the method checking the status such as once every second... – Roman R. Nov 15 '21 at 22:15
  • 1
    @RomanR: After playing around with it in GraphEdit, I noticed that the "Signal Detected" value on the filter property page is only updated if a Video Renderer is connected (and if the graph is either running or paused). – djvg Nov 16 '21 at 09:20
  • 1
    This is exactly what I supposed, the property page is most likely using `IAMAnalogVideoDecoder` and esp. `get_HorizontalLocked` but the returned value is correct in paused/running state. I guess you don't have to connect exactly video renderer, any renderer will do, [Null Renderer](https://learn.microsoft.com/en-us/windows/win32/directshow/null-renderer-filter) in particular. – Roman R. Nov 16 '21 at 09:27
  • 1
    @RomanR.: The Null Renderer does indeed work. That's even better, although I do notice that it is marked as "deprecated" in the [docs](https://learn.microsoft.com/en-us/windows/win32/directshow/null-renderer-filter). – djvg Nov 16 '21 at 09:38
  • 2
    The entire DirectShow is at its end of life, Null Renderer is just a bit ahead of other stuff. Nevertheless, there is an option to pick up ancient SDK and build it from source if it's gone. – Roman R. Nov 16 '21 at 09:46
  • Note: I would highly suggest transitioning to [MediaFoundation](https://learn.microsoft.com/en-us/windows/win32/medfound/microsoft-media-foundation-sdk) which replaces DirectShow, which is EOL and deprecated. – Mgetz Nov 16 '21 at 15:51
  • @Mgetz: Thanks. I started on DirectShow because that's [what ffmpeg uses](https://ffmpeg.org/ffmpeg-devices.html#dshow), and I was certain it could solve my problem. Also, it seems [opinions are divided](https://stackoverflow.com/questions/4407446/directshow-vs-media-foundation-for-video-capture#comment122604967_4426723). If you could provide a working solution using Media Foundation, that would be great. :) – djvg Nov 16 '21 at 16:13
  • @djvg depends on what you're using it for. I disagree with the answer in the question you linked. MS can absolutely pull the plug on it and basically has because no new dshow filters are supported and all new codecs are media foundation. The interface you're using `IAMAnalogVideoDecoder` is not deprecated. Media foundation would get you potentially GPU accelerated encoding however via NVENC or similar which is media foundation only out of the box. But lacking an official announcement you're free to continue, MF does have QOS enhancements though and better support. – Mgetz Nov 16 '21 at 17:51

2 Answers2

1

The mentioned property page is likely to pull the data using 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, or another renderer of your choice).

See also this question on Null Renderer deprecation and source code for the worst case scenario replacement.

djvg
  • 11,722
  • 5
  • 72
  • 103
Roman R.
  • 68,205
  • 6
  • 94
  • 158
1

Thanks to Roman R.'s answer and comments, I was able to verify that IAMAnalogVideoDecoder->get_HorizontalLocked() can indeed be used to detect the presence of a PAL or NTSC video signal.

In order to obtain a meaningful value from get_HorizontalLocked, it appears to be necessary to:

  • connect a filter to the capture output pin of the capture filter
  • run (or pause) the graph

Note that the graph will run successfully even if nothing is connected to the capture output pin, but in that case the value from get_HorizontalLocked remains zero, regardless of signal presence.

Instead of a 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 issue.

Just to illustrate the basic steps, here's what I did, assuming that a camera is already connected:

// 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);
    ...
}

Please note: This is not a full working example, just an illustration of the sequence in which the calls are made.

To keep it brief, I omitted device enumeration, error handling, clean-up, etc. I included simplified declarations, just to show which interfaces are used.

Depending on the application, it may be necessary to repeat the get_HorizontalLocked() call for improved robustness.

The DirectShow documentation provides working examples for InitCaptureGraphBuilder, AddFilterByCLSID, and device enumeration.

djvg
  • 11,722
  • 5
  • 72
  • 103