Search code examples
c#wpfvideodirectshowdirectshow.net

IVMRWindowlessControl9 not being rendered into parent WPF window


I'm creating a webcam control using DirectShow.NET. I want to render the video of the camera into a WPF window. What is happening currently is that the IVMRWindowlessControl9 doesn't seem to be going into windowless mode and is not being parented to the window that I'm specifying, even though I'm calling the appropriate methods.

Why are these methods not being invoked? Is there something else that I'm not doing?

Below is a snippet of the relevant code:

IGraphBuilder graphBuilder = (IGraphBuilder) new FilterGraph();
ICaptureGraphBuilder2 captureGraphBuilder = (ICaptureGraphBuilder2)new CaptureGraphBuilder2();            
IMediaControl mediaControl = (IMediaControl) this.graphBuilder;
IBaseFilter renderFilter = (IBaseFilter) new VideoMixingRenderer9();

hr = this.captureGraphBuilder.SetFiltergraph(this.graphBuilder);
DsError.ThrowExceptionForHR(hr);

IBaseFilter sourceFilter = FindCaptureDevice();

hr = this.graphBuilder.AddFilter(sourceFilter, "Video Capture");                        
DsError.ThrowExceptionForHR(hr);

SetCaptureResolution();

IVMRFilterConfig9 filterConfig = (IVMRFilterConfig9)renderFilter;

hr = filterConfig.SetNumberOfStreams(1);
DsError.ThrowExceptionForHR(hr);

hr = filterConfig.SetRenderingMode(VMR9Mode.Windowless);
DsError.ThrowExceptionForHR(hr);

windowlessControl = (IVMRWindowlessControl9)renderFilter;

hr = this.graphBuilder.AddFilter(renderFilter, "Video Capture");
DsError.ThrowExceptionForHR(hr);

Window window = Window.GetWindow(this);
var wih = new WindowInteropHelper(window);
IntPtr hWnd = wih.Handle;
hr = windowlessControl.SetVideoClippingWindow(hWnd);
DsError.ThrowExceptionForHR(hr);

hr = windowlessControl.SetAspectRatioMode(VMR9AspectRatioMode.LetterBox);
DsError.ThrowExceptionForHR(hr);

hr = this.captureGraphBuilder.RenderStream(PinCategory.Capture, MediaType.Video, sourceFilter, null, null);
DsError.ThrowExceptionForHR(hr);

Marshal.ReleaseComObject(sourceFilter);

hr = this.mediaControl.Run();
DsError.ThrowExceptionForHR(hr);

Here is an image of what is happening (I made the background green to make it easier to see): WPF & IVMRWindowlessControl9

This is a diagram of the filter graph: enter image description here

To answer a potential question (because I've had this issue previously), yes, hWnd is getting set/has a value - so the windowlessControl does have a pointer to the window.


Solution

  • A popup "ActiveMovie Window" created when you run a filter graph is a symptom of video renderer filter inserted into pipeline and running in default mode, without being configured to be a part of other UI: embedded as a child window etc.

    Your reversing your graph sheds light on what is going on:

    You insert and set up one video renderer filter, then there is another one added by the API and connected to your input. While embedded into your UI is the first one, it remains idle and the other one renders video into popup.

    The code line which gives the problem is:

    hr = this.captureGraphBuilder.RenderStream(PinCategory.Capture,
        MediaType.Video, sourceFilter, null, null);
    

    The problem is quite typical for those who build graphs by inserting a video renderer and expecting them to be picked up and connected, especially that it sometimes works and such code snippets might be found online.

    MSDN says:

    If the pSink parameter is NULL, the method tries to use a default renderer. For video it uses the Video Renderer, and for audio it uses the DirectSound Renderer.

    The call added another instance and you expected your existing one to be connected. While RenderStream is a powerful call and does filter magic to get things connected, it is easy to end up having it done something in wrong way.

    If you already have your video renderer, you could use it as a sink argument in this call. Or, you could avoid doing RenderStream and incrementally add filters you need to make sure everything is built following your expectations. Or, another option is IFilterGraph2::RenderEx call instead with AM_RENDEREX_RENDERTOEXISTINGRENDERERS flag:

    ...the method attempts to use renderers already in the filter graph. It will not add new renderers to the graph. (It will add intermediate transform filters, if needed.) For the method to succeed, the graph must contain the appropriate renderers, and they must have unconnected input pins.