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