wpfdirectshowdirect3dwpf-mediakit

WPF MediaKit sharing MediaPlayer base


I have been using WPFMediaKit to render a DirectShow graph. Here is my setup.

I am having trouble using one MediaPlayerBase multiple times with multiple D3DRender instances. I have an IVideoEngine that returns a single graph (via MediaPlayerBase) that is used to preview. The IVideoEngine internally manages the DirectShow graph for switching camera inputs/etc. The idea is that this single graph (via MediaPlayerBase) may be used multiple times, simultaneously, or (more likely) at separate times via the D3DRenderer base class.

I created a new RenderElementControl that simply renders a MediaPlayerBase onto the surface. It works great for the first instance used for a particular instance of the MediaPlayerBase, but when using the RenderElementControl again, no video is rendered.

Here is my source code to specifically render a MediaPlayerBase.

public class RenderElementControl : D3DRenderer
{
    private readonly MediaPlayerBase _mediaPlayerBase;

    public RenderElementControl(MediaPlayerBase mediaPlayerBase)
    {
        _mediaPlayerBase = mediaPlayerBase;
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    }

    private void OnUnloaded(object sender, RoutedEventArgs routedEventArgs)
    {
        _mediaPlayerBase.NewAllocatorFrame -= OnMediaPlayerNewAllocatorFramePrivate;
        _mediaPlayerBase.NewAllocatorSurface -= OnMediaPlayerNewAllocatorSurfacePrivate;
    }

    private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
    {
        _mediaPlayerBase.NewAllocatorFrame += OnMediaPlayerNewAllocatorFramePrivate;
        _mediaPlayerBase.NewAllocatorSurface += OnMediaPlayerNewAllocatorSurfacePrivate;
        _mediaPlayerBase.Dispatcher.DoEvents();
    }

    private void OnMediaPlayerNewAllocatorSurfacePrivate(object sender, IntPtr pSurface)
    {
        SetBackBuffer(pSurface);
    }

    private void OnMediaPlayerNewAllocatorFramePrivate()
    {
        InvalidateVideoImage();
    }
}

The question

Why does this control not work for a second instance of a single MediaPlayerBase? How do I make it so that I can use multiple RenderElementControl, possibly at the same time, with the same MediaPlayerBase?

Note: For those not familiar with WPFMediaKit, here is the source code for D3DRenderer and MediaPlayerBase that handles rendering a DirectShow render (VMR or EMR).


Solution

  • I was able to solve the issue by keeping my own internal copy of RenderElementControl, and issuing out cloned D3Renderer clones. Here is the source code.

    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    
    namespace WPF.Controls
    {
        /// <summary>
        /// This element simply renders a, assumingly, continous stream of video from the given MediaPlayer
        /// </summary>
        public class RendererElement : ContentControl
        {
            static readonly Dictionary<int, RenderElementControl> GraphRenders = new Dictionary<int, RenderElementControl>(); 
    
            #region Dependency Properties
    
            public static readonly DependencyProperty PlayerProperty =
                DependencyProperty.Register("Player", typeof(MediaPlayerBase), typeof(RendererElement), new PropertyMetadata(default(MediaPlayerBase), PropertyChangedCallback));
    
            private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
            {
                ((RendererElement)dependencyObject).OnPlayerChanged();
            }
    
            public MediaPlayerBase Player
            {
                get { return (MediaPlayerBase)GetValue(PlayerProperty); }
                set { SetValue(PlayerProperty, value); }
            }
    
            #endregion
    
            protected void OnPlayerChanged()
            {
                Content = null;
    
                if (Player != null)
                {
                    var existingRenderer = GraphRenders.ContainsKey(Player.GraphInstanceId) ? GraphRenders[Player.GraphInstanceId] : null;
    
                    if(existingRenderer == null)
                    {
                        // The first usage needs to add RenderElementControl so it can initialize property.
                        // After it is intially loaded, it is replaced by a clone (See RenderElementControlLoaded).
                        existingRenderer = new RenderElementControl(Player);
                        Content = existingRenderer;
                    }else
                    {
                        // A render has already been created, just grab a clone from it.
                        Content = existingRenderer.CloneD3DRenderer();
                    }
                }
            }
    
            protected void RenderElementControlLoaded(RenderElementControl control)
            {
                GraphRenders.Add(control.GraphInstanceId, control);
                Content = control.CloneD3DRenderer();
            }
    
            #region Nested Classes
    
            /// <summary>
            /// The actual control that renders. This is stored internally, and clones are tooken from it so that we can re-render it.
            /// </summary>
            public class RenderElementControl : D3DRenderer
            {
                private readonly MediaPlayerBase _mediaPlayerBase;
    
                public RenderElementControl(MediaPlayerBase mediaPlayerBase)
                {
                    _mediaPlayerBase = mediaPlayerBase;
                    Loaded += OnLoaded;
                }
    
                private void OnLoaded(object sender, RoutedEventArgs routedEventArgs)
                {
                    _mediaPlayerBase.NewAllocatorFrame += OnMediaPlayerNewAllocatorFramePrivate;
                    _mediaPlayerBase.NewAllocatorSurface += OnMediaPlayerNewAllocatorSurfacePrivate;
                    _mediaPlayerBase.Dispatcher.DoEvents();
                    // let the RenderElement know we have loaded and initialized so that it can cache this instance,
                    // as well as replace this instance with a clone.
                    (Parent as RendererElement).RenderElementControlLoaded(this);
                }
    
                private void OnMediaPlayerNewAllocatorSurfacePrivate(object sender, IntPtr pSurface)
                {
                    SetBackBuffer(pSurface);
                }
    
                private void OnMediaPlayerNewAllocatorFramePrivate()
                {
                    InvalidateVideoImage();
                }
    
                public int GraphInstanceId
                {
                    get { return _mediaPlayerBase.GraphInstanceId; }
                }
            }
    
            #endregion
        }
    }