WPF MediaKit sharing MediaPlayer base

792 Views Asked by At

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).

1

There are 1 best solutions below

1
On BEST ANSWER

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