using DirectShowLib;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using WPFMediaKit.MediaFoundation;
using WPFMediaKit.MediaFoundation.Interop;
using WPFMediaKit.Threading;
using Size = System.Windows.Size;
namespace WPFMediaKit.DirectShow.MediaPlayers;
public enum MediaState
{
Manual,
Play,
Stop,
Close,
Pause
}
public enum PlayerState
{
Closed,
Playing,
Paused,
Stopped,
SteppingFrames
}
///
/// The types of position formats that
/// are available for seeking media
///
public enum MediaPositionFormat
{
MediaTime,
Frame,
Byte,
Field,
Sample,
None
}
///
/// Delegate signature to notify of a new surface
///
/// The sender of the event
/// The pointer to the D3D surface
public delegate void NewAllocatorSurfaceDelegate(object sender, IntPtr pSurface);
///
/// The arguments that store information about a failed media attempt
///
public class MediaFailedEventArgs : EventArgs
{
public MediaFailedEventArgs(string message, Exception exception)
{
Message = message;
Exception = exception;
}
public Exception Exception { get; protected set; }
public string Message { get; protected set; }
}
///
/// The custom allocator interface. All custom allocators need
/// to implement this interface.
///
public interface ICustomAllocator : IDisposable
{
///
/// Invokes when a new frame has been allocated
/// to a surface
///
event Action NewAllocatorFrame;
///
/// Invokes when a new surface has been allocated
///
event NewAllocatorSurfaceDelegate NewAllocatorSurface;
}
[ComImport, Guid("FA10746C-9B63-4b6c-BC49-FC300EA5F256")]
internal class EnhancedVideoRenderer
{
}
///
/// A low level window class that is used to provide interop with libraries
/// that require an hWnd
///
public class HiddenWindow : NativeWindow
{
public delegate IntPtr WndProcHookDelegate(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled);
private readonly List m_handlerlist = new List();
public void AddHook(WndProcHookDelegate method)
{
if (m_handlerlist.Contains(method))
return;
lock (((System.Collections.ICollection)m_handlerlist).SyncRoot)
m_handlerlist.Add(method);
}
public void RemoveHook(WndProcHookDelegate method)
{
lock (((System.Collections.ICollection)m_handlerlist).SyncRoot)
m_handlerlist.Remove(method);
}
///
/// Invokes the windows procedure associated to this window
///
/// The window message to send to window
protected override void WndProc(ref Message m)
{
bool isHandled = false;
lock (((System.Collections.ICollection)m_handlerlist).SyncRoot)
{
foreach (WndProcHookDelegate method in m_handlerlist)
{
method.Invoke(m.HWnd, m.Msg, m.WParam, m.LParam, ref isHandled);
if (isHandled)
break;
}
}
base.WndProc(ref m);
}
}
///
/// Specifies different types of DirectShow
/// Video Renderers
///
public enum VideoRendererType
{
VideoMixingRenderer9 = 0,
EnhancedVideoRenderer
}
///
/// The MediaPlayerBase is a base class to build raw, DirectShow based players.
/// It inherits from DispatcherObject to allow easy communication with COM objects
/// from different apartment thread models.
///
public abstract class MediaPlayerBase : WorkDispatcherObject
{
[DllImport("user32.dll", SetLastError = false)]
private static extern IntPtr GetDesktopWindow();
///
/// A static value to hold a count for all graphs. Each graph
/// has it's own value that it uses and is updated by the
/// GraphInstanceCookie property in the get method
///
private static int m_graphInstances;
///
/// The custom windows message constant for graph events
///
private const int WM_GRAPH_NOTIFY = 0x0400 + 13;
///
/// One second in 100ns units
///
public const long DSHOW_ONE_SECOND_UNIT = 10000000;
///
/// The IBasicAudio volume value for silence
///
private const int DSHOW_VOLUME_SILENCE = -10000;
///
/// The IBasicAudio volume value for full volume
///
private const int DSHOW_VOLUME_MAX = 0;
///
/// The IBasicAudio balance max absolute value
///
private const int DSHOW_BALACE_MAX_ABS = 10000;
///
/// Rate which our DispatcherTimer polls the graph
///
private const int DSHOW_TIMER_POLL_MS = 33;
///
/// UserId value for the VMR9 Allocator - Not entirely useful
/// for this application of the VMR
///
private readonly IntPtr m_userId = new IntPtr(unchecked((int)0xDEADBEEF));
///
/// Static lock. Seems multiple EVR controls instantiated at the same time crash
///
private static readonly object m_videoRendererInitLock = new object();
///
/// DirectShow interface for controlling audio
/// functions such as volume and balance
///
private IBasicAudio m_basicAudio;
///
/// The custom DirectShow allocator
///
private ICustomAllocator m_customAllocator;
///
/// The DirectShow filter graph reference
///
private IFilterGraph m_graph;
///
/// The hWnd pointer we use for D3D stuffs
///
private HiddenWindow m_window;
///
/// The DirectShow interface for controlling the
/// filter graph. This provides, Play, Pause, Stop, etc
/// functionality.
///
private IMediaControl m_mediaControl;
///
/// The DirectShow interface for getting events
/// that occur in the FilterGraph.
///
private IMediaEventEx m_mediaEvent;
///
/// Flag for if our media has video
///
private bool m_hasVideo;
///
/// The natural video pixel height, if applicable
///
private int m_naturalVideoHeight;
///
/// The natural video pixel width, if applicable
///
private int m_naturalVideoWidth;
///
/// Our Win32 timer to poll the DirectShow graph
///
private System.Timers.Timer m_timer;
///
/// The current state of the player
///
private PlayerState m_playerState = PlayerState.Closed;
///
/// This objects last stand
///
~MediaPlayerBase()
{
Dispose();
}
///
/// The global instance Id of the graph. We use this
/// for the WndProc callback method.
///
private int? m_graphInstanceId;
///
/// The globally unqiue identifier of the graph
///
protected int GraphInstanceId
{
get
{
if (m_graphInstanceId != null)
return m_graphInstanceId.Value;
/* Increment our static value and store the current
* instance id of our player graph */
m_graphInstanceId = Interlocked.Increment(ref m_graphInstances);
return m_graphInstanceId.Value;
}
}
///
/// Helper function to get a valid hWnd to
/// use with DirectShow and Direct3D
///
[MethodImpl(MethodImplOptions.Synchronized)]
private void GetMainWindowHwndHelper()
{
if (m_window == null)
m_window = new HiddenWindow();
else
return;
if (m_window.Handle == IntPtr.Zero)
{
lock (m_window)
{
m_window.CreateHandle(new CreateParams());
}
}
}
protected virtual HiddenWindow HwndHelper
{
get
{
if (m_window != null)
return m_window;
GetMainWindowHwndHelper();
return m_window;
}
}
///
/// Is true if the media contains renderable video
///
public virtual bool HasVideo
{
get
{
return m_hasVideo;
}
protected set
{
m_hasVideo = value;
}
}
///
/// Gets the natural pixel width of the current media.
/// The value will be 0 if there is no video in the media.
///
public virtual int NaturalVideoWidth
{
get
{
VerifyAccess();
return m_naturalVideoWidth;
}
protected set
{
VerifyAccess();
m_naturalVideoWidth = value;
}
}
///
/// Gets the natural pixel height of the current media.
/// The value will be 0 if there is no video in the media.
///
public virtual int NaturalVideoHeight
{
get
{
VerifyAccess();
return m_naturalVideoHeight;
}
protected set
{
VerifyAccess();
m_naturalVideoHeight = value;
}
}
///
/// Gets or sets the audio volume. Specifies the volume, as a
/// number from 0 to 1. Full volume is 1, and 0 is silence.
///
public virtual double Volume
{
get
{
VerifyAccess();
/* Check if we even have an
* audio interface */
if (m_basicAudio == null)
return 0;
int dShowVolume;
/* Get the current volume value from the interface */
m_basicAudio.get_Volume(out dShowVolume);
/* Do calulations to convert to a base of 0 for silence */
dShowVolume -= DSHOW_VOLUME_SILENCE;
return (double)dShowVolume / -DSHOW_VOLUME_SILENCE;
}
set
{
VerifyAccess();
/* Check if we even have an
* audio interface */
if (m_basicAudio == null)
return;
if (value <= 0) /* Value should not be negative or else we treat as silence */
m_basicAudio.put_Volume(DSHOW_VOLUME_SILENCE);
else if (value >= 1)/* Value should not be greater than one or else we treat as maximum volume */
m_basicAudio.put_Volume(DSHOW_VOLUME_MAX);
else
{
/* With the IBasicAudio interface, sound is DSHOW_VOLUME_SILENCE
* for silence and DSHOW_VOLUME_MAX for full volume
* so we calculate that here based off an input of 0 of silence and 1.0
* for full audio */
int dShowVolume = (int)((1 - value) * DSHOW_VOLUME_SILENCE);
m_basicAudio.put_Volume(dShowVolume);
}
}
}
///
/// Gets or sets the balance on the audio.
/// The value can range from -1 to 1. The value -1 means the right channel is attenuated by 100 dB
/// and is effectively silent. The value 1 means the left channel is silent. The neutral value is 0,
/// which means that both channels are at full volume. When one channel is attenuated, the other
/// remains at full volume.
///
public virtual double Balance
{
get
{
VerifyAccess();
/* Check if we even have an
* audio interface */
if (m_basicAudio == null)
return 0;
int balance;
/* Get the interface supplied balance value */
m_basicAudio.get_Balance(out balance);
/* Calc and return the balance based on 0 == silence */
return (double)balance / DSHOW_BALACE_MAX_ABS;
}
set
{
VerifyAccess();
/* Check if we even have an
* audio interface */
if (m_basicAudio == null)
return;
/* Calc the dshow balance value */
int balance = (int)value * DSHOW_BALACE_MAX_ABS;
m_basicAudio.put_Balance(balance);
}
}
///
/// Get the current state of the player
///
public virtual PlayerState PlayerState
{
get { return this.m_playerState; }
protected set
{
PlayerState oldVal = m_playerState;
m_playerState = value;
if (PlayerStateChanged != null && oldVal != value)
PlayerStateChanged(oldVal, value);
}
}
///
/// Event notifies when there is a new video frame
/// to be rendered
///
public event Action NewAllocatorFrame;
///
/// Event notifies when there is a new surface allocated
///
public event NewAllocatorSurfaceDelegate NewAllocatorSurface;
///
/// Event notifies when the player changes state
///
public event Action PlayerStateChanged;
///
/// Frees any remaining memory
///
public void Dispose()
{
Dispose(true);
//GC.SuppressFinalize(this);
}
///
/// Part of the dispose pattern
///
protected virtual void Dispose(bool disposing)
{
//if (m_disposed)
// return;
if (!disposing)
return;
if (m_window != null)
{
m_window.RemoveHook(WndProcHook);
m_window.DestroyHandle();
m_window = null;
}
if (m_timer != null)
m_timer.Dispose();
m_timer = null;
if (CheckAccess())
{
FreeResources();
Dispatcher.BeginInvokeShutdown();
}
else
{
Dispatcher.BeginInvoke((Action)delegate
{
FreeResources();
Dispatcher.BeginInvokeShutdown();
});
}
}
///
/// Polls the graph for various data about the media that is playing
///
protected virtual void OnGraphTimerTick()
{
}
///
/// Is called when a new media event code occurs on the graph
///
/// The event code that occured
/// The first parameter sent by the graph
/// The second parameter sent by the graph
protected virtual void OnMediaEvent(EventCode code, IntPtr param1, IntPtr param2)
{
switch (code)
{
case EventCode.Complete:
InvokeMediaEnded(null);
StopGraphPollTimer();
break;
case EventCode.Paused:
break;
default:
break;
}
}
///
/// Starts the graph polling timer to update possibly needed
/// things like the media position
///
protected void StartGraphPollTimer()
{
if (m_timer == null)
{
m_timer = new System.Timers.Timer();
m_timer.Interval = DSHOW_TIMER_POLL_MS;
m_timer.Elapsed += TimerElapsed;
}
m_timer.Enabled = true;
/* Make sure we get windows messages */
AddWndProcHook();
}
private void ProcessGraphEvents()
{
Dispatcher.BeginInvoke((Action)delegate
{
if (m_mediaEvent != null)
{
IntPtr param1;
IntPtr param2;
EventCode code;
/* Get all the queued events from the interface */
while (m_mediaEvent.GetEvent(out code, out param1, out param2, 0) == 0)
{
/* Handle anything for this event code */
OnMediaEvent(code, param1, param2);
/* Free everything..we only need the code */
m_mediaEvent.FreeEventParams(code, param1, param2);
}
}
});
}
private void TimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Dispatcher.BeginInvoke((Action)delegate
{
ProcessGraphEvents();
OnGraphTimerTick();
});
}
///
/// Stops the graph polling timer
///
protected void StopGraphPollTimer()
{
if (m_timer != null)
{
m_timer.Stop();
m_timer.Dispose();
m_timer = null;
}
/* Stop listening to windows messages */
RemoveWndProcHook();
}
///
/// Removes our hook that listens to windows messages
///
private void RemoveWndProcHook()
{
/* Make sure to stop our IMediaEventEx also */
UnsetMediaEventExNotifyWindow();
//HwndHelper.RemoveHook(WndProcHook);
}
///
/// Adds a hook that listens to windows messages
///
private void AddWndProcHook()
{
// HwndHelper.AddHook(WndProcHook);
}
///
/// Receives windows messages. This is primarily used to get
/// events that happen on our graph
///
/// The window handle
/// The message Id
/// The message's wParam value
/// The message's lParam value
/// A value that indicates whether the message was handled. Set the value to true if the message was handled; otherwise, false.
private IntPtr WndProcHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
ProcessGraphEvents();
return IntPtr.Zero;
}
///
/// Unhooks the IMediaEventEx from the notification hWnd
///
private void UnsetMediaEventExNotifyWindow()
{
if (m_mediaEvent == null)
return;
/* Setting the notify window to IntPtr.Zero unsubscribes the events */
//int hr = m_mediaEvent.SetNotifyWindow(IntPtr.Zero, WM_GRAPH_NOTIFY, (IntPtr)GraphInstanceId);
}
///
/// Sets the MediaEventEx interface
///
private void SetMediaEventExInterface(IMediaEventEx mediaEventEx)
{
m_mediaEvent = mediaEventEx;
//int hr = m_mediaEvent.SetNotifyWindow(HwndHelper.Handle, WM_GRAPH_NOTIFY, (IntPtr)GraphInstanceId);
}
///
/// Configures all general DirectShow interfaces that the
/// FilterGraph supplies.
///
/// The FilterGraph to setup
protected virtual void SetupFilterGraph(IFilterGraph graph)
{
m_graph = graph;
/* Setup the interfaces and query basic information
* on the graph that is passed */
SetBasicAudioInterface(m_graph as IBasicAudio);
SetMediaControlInterface(m_graph as IMediaControl);
SetMediaEventExInterface(m_graph as IMediaEventEx);
}
///
/// Sets the MediaControl interface
///
private void SetMediaControlInterface(IMediaControl mediaControl)
{
m_mediaControl = mediaControl;
}
///
/// Sets the basic audio interface for controlling
/// volume and balance
///
protected void SetBasicAudioInterface(IBasicAudio basicAudio)
{
m_basicAudio = basicAudio;
}
///
/// Notifies when the media has successfully been opened
///
public event Action MediaOpened;
///
/// Notifies when the media has been closed
///
public event Action MediaClosed;
///
/// Notifies when the media has failed and produced an exception
///
public event EventHandler MediaFailed;
///
/// Notifies when the media has completed
///
public event Action MediaEnded;
///
/// Registers the custom allocator and hooks into it's supplied events
///
protected void RegisterCustomAllocator(ICustomAllocator allocator)
{
FreeCustomAllocator();
if (allocator == null)
return;
m_customAllocator = allocator;
m_customAllocator.NewAllocatorFrame += CustomAllocatorNewAllocatorFrame;
m_customAllocator.NewAllocatorSurface += CustomAllocatorNewAllocatorSurface;
}
///
/// Local event handler for the custom allocator's new surface event
///
private void CustomAllocatorNewAllocatorSurface(object sender, IntPtr pSurface)
{
InvokeNewAllocatorSurface(pSurface);
}
///
/// Local event handler for the custom allocator's new frame event
///
private void CustomAllocatorNewAllocatorFrame()
{
InvokeNewAllocatorFrame();
}
///
/// Disposes of the current allocator
///
protected void FreeCustomAllocator()
{
if (m_customAllocator == null)
return;
m_customAllocator.NewAllocatorFrame -= CustomAllocatorNewAllocatorFrame;
m_customAllocator.NewAllocatorSurface -= CustomAllocatorNewAllocatorSurface;
m_customAllocator.Dispose();
if (Marshal.IsComObject(m_customAllocator))
Marshal.ReleaseComObject(m_customAllocator);
m_customAllocator = null;
}
///
/// Resets the local graph resources to their
/// default settings
///
private void ResetLocalGraphResources()
{
m_graph = null;
if (m_basicAudio != null)
Marshal.ReleaseComObject(m_basicAudio);
m_basicAudio = null;
if (m_mediaControl != null)
Marshal.ReleaseComObject(m_mediaControl);
m_mediaControl = null;
if (m_mediaEvent != null)
Marshal.ReleaseComObject(m_mediaEvent);
m_mediaEvent = null;
}
///
/// Frees any allocated or unmanaged resources
///
[MethodImpl(MethodImplOptions.Synchronized)]
protected virtual void FreeResources()
{
StopGraphPollTimer();
ResetLocalGraphResources();
FreeCustomAllocator();
}
///
/// Creates a new renderer and configures it with a custom allocator
///
/// The type of renderer we wish to choose
/// The DirectShow graph to add the renderer to
/// Number of input pins for the renderer
/// An initialized DirectShow renderer
protected IBaseFilter CreateVideoRenderer(VideoRendererType rendererType, IGraphBuilder graph, int streamCount)
{
IBaseFilter renderer;
switch (rendererType)
{
case VideoRendererType.VideoMixingRenderer9:
renderer = CreateVideoMixingRenderer9(graph, streamCount);
break;
case VideoRendererType.EnhancedVideoRenderer:
renderer = CreateEnhancedVideoRenderer(graph, streamCount);
break;
default:
throw new ArgumentOutOfRangeException("rendererType");
}
return renderer;
}
///
/// Creates a new renderer and configures it with a custom allocator
///
/// The type of renderer we wish to choose
/// The DirectShow graph to add the renderer to
/// An initialized DirectShow renderer
protected IBaseFilter CreateVideoRenderer(VideoRendererType rendererType, IGraphBuilder graph)
{
return CreateVideoRenderer(rendererType, graph, 1);
}
///
/// Creates an instance of the EVR
///
private IBaseFilter CreateEnhancedVideoRenderer(IGraphBuilder graph, int streamCount)
{
EvrPresenter presenter;
IBaseFilter filter;
lock (m_videoRendererInitLock)
{
var evr = new EnhancedVideoRenderer();
filter = evr as IBaseFilter;
int hr = graph.AddFilter(filter, string.Format("Renderer: {0}", VideoRendererType.EnhancedVideoRenderer));
DsError.ThrowExceptionForHR(hr);
/* QueryInterface for the IMFVideoRenderer */
var videoRenderer = filter as IMFVideoRenderer;
if (videoRenderer == null)
throw new WPFMediaKitException("Could not QueryInterface for the IMFVideoRenderer");
/* Create a new EVR presenter */
presenter = EvrPresenter.CreateNew();
/* Initialize the EVR renderer with the custom video presenter */
hr = videoRenderer.InitializeRenderer(null, presenter.VideoPresenter);
DsError.ThrowExceptionForHR(hr);
var presenterSettings = presenter.VideoPresenter as IEVRPresenterSettings;
if (presenterSettings == null)
throw new WPFMediaKitException("Could not QueryInterface for the IEVRPresenterSettings");
presenterSettings.SetBufferCount(3);
/* Use our interop hWnd */
IntPtr handle = GetDesktopWindow();//HwndHelper.Handle;
/* QueryInterface the IMFVideoDisplayControl */
var displayControl = presenter.VideoPresenter as IMFVideoDisplayControl;
if (displayControl == null)
throw new WPFMediaKitException("Could not QueryInterface the IMFVideoDisplayControl");
/* Configure the presenter with our hWnd */
hr = displayControl.SetVideoWindow(handle);
DsError.ThrowExceptionForHR(hr);
var filterConfig = filter as IEVRFilterConfig;
if (filterConfig != null)
filterConfig.SetNumberOfStreams(streamCount);
}
RegisterCustomAllocator(presenter);
return filter;
}
///
/// Creates a new VMR9 renderer and configures it with an allocator.
///
/// COMException is transalted to the WPFMediaKitException.
///
///
/// An initialized DirectShow VMR9 renderer.
/// When creating of VMR9 fails.
private IBaseFilter CreateVideoMixingRenderer9(IGraphBuilder graph, int streamCount)
{
try
{
return CreateVideoMixingRenderer9Inner(graph, streamCount);
}
catch (COMException ex)
{
throw new WPFMediaKitException("Could not create VMR9. " + Vmr9Allocator.VMR9_ERROR, ex);
}
}
///
/// Creates a new VMR9 renderer and configures it with an allocator.
///
/// An initialized DirectShow VMR9 renderer.
/// When creating of VMR9 fails.
/// When creating of VMR9 fails.
private IBaseFilter CreateVideoMixingRenderer9Inner(IGraphBuilder graph, int streamCount)
{
IBaseFilter vmr9 = new VideoMixingRenderer9() as IBaseFilter;
var filterConfig = vmr9 as IVMRFilterConfig9;
if (filterConfig == null)
throw new WPFMediaKitException("Could not query VMR9 filter configuration. " + Vmr9Allocator.VMR9_ERROR);
/* We will only have one video stream connected to the filter */
int hr = filterConfig.SetNumberOfStreams(streamCount);
DsError.ThrowExceptionForHR(hr);
/* Setting the renderer to "Renderless" mode
* sounds counter productive, but its what we
* need to do for setting up a custom allocator */
hr = filterConfig.SetRenderingMode(VMR9Mode.Renderless);
DsError.ThrowExceptionForHR(hr);
/* Query the allocator interface */
var vmrSurfAllocNotify = vmr9 as IVMRSurfaceAllocatorNotify9;
if (vmrSurfAllocNotify == null)
throw new WPFMediaKitException("Could not query the VMR surface allocator. " + Vmr9Allocator.VMR9_ERROR);
var allocator = new Vmr9Allocator();
/* We supply our custom allocator to the renderer */
hr = vmrSurfAllocNotify.AdviseSurfaceAllocator(m_userId, allocator);
DsError.ThrowExceptionForHR(hr);
hr = allocator.AdviseNotify(vmrSurfAllocNotify);
DsError.ThrowExceptionForHR(hr);
RegisterCustomAllocator(allocator);
hr = graph.AddFilter(vmr9,
string.Format("Renderer: {0}", VideoRendererType.VideoMixingRenderer9));
DsError.ThrowExceptionForHR(hr);
return vmr9;
}
///
/// Plays the media
///
public virtual void Play()
{
VerifyAccess();
if (m_basicAudio != null)
{
//Balance = Balance;
//Volume = Volume;
}
if (m_mediaControl != null)
{
m_mediaControl.Run();
StartGraphPollTimer();
PlayerState = PlayerState.Playing;
}
}
///
/// Stops the media
///
public virtual void Stop()
{
VerifyAccess();
StopInternal();
}
///
/// Stops the media, but does not VerifyAccess() on
/// the Dispatcher. This can be used by destructors
/// because it happens on another thread and our
/// DirectShow graph and COM run in MTA
///
protected void StopInternal()
{
if (m_mediaControl != null)
{
m_mediaControl.Stop();
FilterState filterState;
m_mediaControl.GetState(0, out filterState);
while (filterState != FilterState.Stopped)
m_mediaControl.GetState(2, out filterState);
PlayerState = PlayerState.Stopped;
}
}
///
/// Closes the media and frees its resources
///
public virtual void Close()
{
VerifyAccess();
StopInternal();
FreeResources();
PlayerState = PlayerState.Closed;
}
///
/// Pauses the media
///
public virtual void Pause()
{
VerifyAccess();
if (m_mediaControl != null)
{
m_mediaControl.Pause();
PlayerState = PlayerState.Paused;
}
}
#region Event Invokes
///
/// Invokes the MediaEnded event, notifying any subscriber that
/// media has reached the end
///
protected void InvokeMediaEnded(EventArgs e)
{
var mediaEndedHandler = MediaEnded;
if (mediaEndedHandler != null)
mediaEndedHandler();
}
///
/// Invokes the MediaOpened event, notifying any subscriber that
/// media has successfully been opened
///
protected void InvokeMediaOpened()
{
/* This is generally a good place to start
* our polling timer */
StartGraphPollTimer();
var mediaOpenedHandler = MediaOpened;
if (mediaOpenedHandler != null)
mediaOpenedHandler();
}
///
/// Invokes the MediaClosed event, notifying any subscriber that
/// the opened media has been closed
///
protected void InvokeMediaClosed(EventArgs e)
{
StopGraphPollTimer();
var mediaClosedHandler = MediaClosed;
if (mediaClosedHandler != null)
mediaClosedHandler();
}
///
/// Invokes the MediaFailed event, notifying any subscriber that there was
/// a media exception.
///
/// The MediaFailedEventArgs contains the exception that caused this event to fire
protected void InvokeMediaFailed(MediaFailedEventArgs e)
{
var mediaFailedHandler = MediaFailed;
if (mediaFailedHandler != null)
mediaFailedHandler(this, e);
}
///
/// Invokes the NewAllocatorFrame event, notifying any subscriber that new frame
/// is ready to be presented.
///
protected void InvokeNewAllocatorFrame()
{
var newAllocatorFrameHandler = NewAllocatorFrame;
if (newAllocatorFrameHandler != null)
newAllocatorFrameHandler();
}
///
/// Invokes the NewAllocatorSurface event, notifying any subscriber of a new surface
///
/// The COM pointer to the D3D surface
protected void InvokeNewAllocatorSurface(IntPtr pSurface)
{
var del = NewAllocatorSurface;
if (del != null)
del(this, pSurface);
}
#endregion Event Invokes
#region Helper Methods
///
/// Sets the natural pixel resolution the video in the graph
///
/// The video renderer
protected void SetNativePixelSizes(IBaseFilter renderer)
{
Size size = GetVideoSize(renderer, PinDirection.Input, 0);
NaturalVideoHeight = (int)size.Height;
NaturalVideoWidth = (int)size.Width;
HasVideo = true;
}
///
/// Gets the video resolution of a pin on a renderer.
///
/// The renderer to inspect
/// The direction the pin is
/// The zero based index of the pin to inspect
/// If successful a video resolution is returned. If not, a 0x0 size is returned
protected static Size GetVideoSize(IBaseFilter renderer, PinDirection direction, int pinIndex)
{
var size = new Size();
var mediaType = new AMMediaType();
IPin pin = DsFindPin.ByDirection(renderer, direction, pinIndex);
if (pin == null)
goto done;
int hr = pin.ConnectionMediaType(mediaType);
if (hr != 0)
goto done;
/* Check to see if its a video media type */
if (mediaType.formatType != FormatType.VideoInfo2 &&
mediaType.formatType != FormatType.VideoInfo)
{
goto done;
}
var videoInfo = new VideoInfoHeader();
/* Read the video info header struct from the native pointer */
Marshal.PtrToStructure(mediaType.formatPtr, videoInfo);
Rectangle rect = videoInfo.SrcRect.ToRectangle();
size = new Size(rect.Width, rect.Height);
done:
DsUtils.FreeAMMediaType(mediaType);
if (pin != null)
Marshal.ReleaseComObject(pin);
return size;
}
///
/// Removes all filters from a DirectShow graph
///
/// The DirectShow graph to remove all the filters from
protected static void RemoveAllFilters(IGraphBuilder graphBuilder)
{
if (graphBuilder == null)
return;
IEnumFilters enumFilters;
/* The list of filters from the DirectShow graph */
var filtersArray = new List();
if (graphBuilder == null)
throw new ArgumentNullException("graphBuilder");
/* Gets the filter enumerator from the graph */
int hr = graphBuilder.EnumFilters(out enumFilters);
DsError.ThrowExceptionForHR(hr);
try
{
/* This array is filled with reference to a filter */
var filters = new IBaseFilter[1];
IntPtr fetched = IntPtr.Zero;
/* Get reference to all the filters */
while (enumFilters.Next(filters.Length, filters, fetched) == 0)
{
/* Add the filter to our array */
filtersArray.Add(filters[0]);
}
}
finally
{
/* Enum filters is a COM, so release that */
Marshal.ReleaseComObject(enumFilters);
}
/* Loop over and release each COM */
for (int i = 0; i < filtersArray.Count; i++)
{
graphBuilder.RemoveFilter(filtersArray[i]);
while (Marshal.ReleaseComObject(filtersArray[i]) > 0)
{ }
}
}
///
/// Adds a filter to a DirectShow graph based on it's name and filter category
///
/// The graph builder to add the filter to
/// The category the filter belongs to
/// The friendly name of the filter
/// Reference to the IBaseFilter that was added to the graph or returns null if unsuccessful
protected static IBaseFilter AddFilterByName(IGraphBuilder graphBuilder, Guid deviceCategory, string friendlyName)
{
var devices = DsDevice.GetDevicesOfCat(deviceCategory);
var deviceList = (from d in devices
where d.Name == friendlyName
select d).ToList();
DsDevice device = deviceList.FirstOrDefault();
foreach (var item in deviceList)
{
if (item != device)
item.Dispose();
}
return AddFilterByDevice(graphBuilder, device);
}
protected static IBaseFilter AddFilterByDevicePath(IGraphBuilder graphBuilder, Guid deviceCategory, string devicePath)
{
var devices = DsDevice.GetDevicesOfCat(deviceCategory);
var deviceList = (from d in devices
where d.DevicePath == devicePath
select d).ToList();
DsDevice device = deviceList.FirstOrDefault();
foreach (var item in deviceList)
{
if (item != device)
item.Dispose();
}
return AddFilterByDevice(graphBuilder, device);
}
private static IBaseFilter AddFilterByDevice(IGraphBuilder graphBuilder, DsDevice device)
{
if (graphBuilder == null)
throw new ArgumentNullException("graphBuilder");
if (device == null)
return null;
var filterGraph = graphBuilder as IFilterGraph2;
if (filterGraph == null)
return null;
IBaseFilter filter = null;
int hr = filterGraph.AddSourceFilterForMoniker(device.Mon, null, device.Name, out filter);
DsError.ThrowExceptionForHR(hr);
return filter;
}
///
/// Finds a pin that exists in a graph.
///
/// The GUID of the major or minor type of the media
/// The direction of the pin - in/out
/// The graph to search in
/// Returns null if the pin was not found, or if a pin is found, returns the first instance of it
protected static IPin FindPinInGraphByMediaType(Guid majorOrMinorMediaType, PinDirection pinDirection, IGraphBuilder graph)
{
IEnumFilters enumFilters;
/* Get the filter enum */
graph.EnumFilters(out enumFilters);
/* Init our vars */
var filters = new IBaseFilter[1];
var fetched = IntPtr.Zero;
IPin pin = null;
IEnumMediaTypes mediaTypesEnum = null;
/* Loop over each filter in the graph */
while (enumFilters.Next(1, filters, fetched) == 0)
{
var filter = filters[0];
int i = 0;
/* Loop over each pin in the filter */
while ((pin = DsFindPin.ByDirection(filter, pinDirection, i)) != null)
{
/* Get the pin enumerator */
pin.EnumMediaTypes(out mediaTypesEnum);
var mediaTypesFetched = IntPtr.Zero;
var mediaTypes = new AMMediaType[1];
/* Enumerate the media types on the pin */
while (mediaTypesEnum.Next(1, mediaTypes, mediaTypesFetched) == 0)
{
/* See if the major or subtype meets our requirements */
if (mediaTypes[0].majorType.Equals(majorOrMinorMediaType) || mediaTypes[0].subType.Equals(majorOrMinorMediaType))
{
/* We found a match */
goto done;
}
}
i++;
}
}
done:
if (mediaTypesEnum != null)
{
mediaTypesEnum.Reset();
Marshal.ReleaseComObject(mediaTypesEnum);
}
enumFilters.Reset();
Marshal.ReleaseComObject(enumFilters);
return pin;
}
#endregion Helper Methods
}