// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DirectShowLib;
using System.Windows.Media.Imaging;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using Tango.Video.DirectShow.Delegates;
using Tango.Video.DirectShow.EventArguments;
namespace Tango.Video.DirectShow
{
public class DirectShowPlayer : DependencyObject
{
int counter = 0;
double currentTime;
public InteropBitmap Source
{
get { return (InteropBitmap)GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
}
// Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("Source", typeof(InteropBitmap), typeof(DirectShowPlayer), new PropertyMetadata(null));
#region Members
private IGraphBuilder _pGraph = null;
private SampleGrabberImpl _sampleGrabberImpl;
private readonly ConcurrentQueue<WriteableBitmap> _bitmapQueue = new ConcurrentQueue<WriteableBitmap>();
Thread _bitmapThread = null;
private int _totalFrames = 0;
private double _totalDuration = 0;
private int _captureBitCount;
private int _captureWidth;
private int _captureHeight;
private double _captureFramerate;
private System.Timers.Timer _playbackTimer = null;
private static int TIMER_PERIOD = 50;
private MsgOnlyWindow _msgFrm = null;
private IntPtr _mappedFile = IntPtr.Zero;
private IntPtr _destFrame = IntPtr.Zero;
private InteropBitmap _myBitmap = null;
#if DEBUG
//private long _copyBufferElapsed = 0;
//private long _frames = 0;
//private long _frameReceivedElapsed = 0;
//private long _frameReceivedCount = 0;
#endif
#endregion
#region Constructor
public DirectShowPlayer()
{
Loop = true;
EnableAudio = true;
FlipImage = true;
State = PlaybackState.NotLoaded;
_playbackTimer = new System.Timers.Timer(TIMER_PERIOD);
_playbackTimer.Elapsed += _playbackTimer_Elapsed;
UseQueue = false;
}
#endregion
#region events
public event FrameReceivedEventDelegate FrameReceived;
public event PlayerProgressEventDelegate PlayerProgress;
#endregion
#region Methods
public void Load(string fileName)
{
IBaseFilter pSourceFilter = null;
IBaseFilter pVideoRenderFilter = null;
IBaseFilter pAudioRenderFilter = null;
IBaseFilter pSampleGrabberFilter = null;
IBaseFilter pSplitterFilter = null;
try
{
if (!File.Exists(fileName))
{
throw new Exception(string.Format("File '{0}' does not exists", fileName));
}
Close();
_pGraph = (IGraphBuilder)new FilterGraph();
int hr = _pGraph.AddSourceFilter(fileName, fileName, out pSourceFilter);
DsError.ThrowExceptionForHR(hr);
pVideoRenderFilter = (IBaseFilter)new NullRenderer();
_pGraph.AddFilter(pVideoRenderFilter, "Null Renderer");
if (EnableAudio)
{
pAudioRenderFilter = (IBaseFilter)new DSoundRender();
_pGraph.AddFilter(pAudioRenderFilter, "Audio Renderer");
}
pSampleGrabberFilter = (IBaseFilter)new SampleGrabber();
_pGraph.AddFilter(pSampleGrabberFilter, "Sample Grabber");
_sampleGrabberImpl = new SampleGrabberImpl(OnBuffer);
DSUtils.SetSampleGrabber(pSampleGrabberFilter, _sampleGrabberImpl);
bool result = DSUtils.ConnectFiltersTryAllPins(_pGraph,
pSourceFilter, pSampleGrabberFilter);
if (!result)
{
throw new Exception("Failed to connect source filter with sample grabber");
}
result = DSUtils.ConnectFiltersTryAllPins(_pGraph,
pSampleGrabberFilter, pVideoRenderFilter);
if (!result)
{
throw new Exception("Failed to connect sample grabber filter with video renderer");
}
if (EnableAudio)
{
result = DSUtils.ConnectFiltersTryAllPins(_pGraph,
pSourceFilter, pAudioRenderFilter);
if (!result)
{
pSplitterFilter = DSUtils.GetNextConnectedFilter(_pGraph, pSourceFilter);
if (pSplitterFilter != null)
{
result = DSUtils.ConnectFiltersTryAllPins(_pGraph,
pSplitterFilter, pAudioRenderFilter);
if (!result)
{
// Maybe there's no audio... be silent
//throw new Exception("Failed to connect splitter filter with audio renderer");
}
}
else
{
// Maybe there's no audio... be silent
//throw new Exception("Failed to find splitter filter");
}
}
}
// Now create the frmMsgQue window, from where we will receieve DS messages
_msgFrm = new MsgOnlyWindow(this);
var meEx = _pGraph as IMediaEventEx;
if (meEx != null)
meEx.SetNotifyWindow(_msgFrm.Handle, WM_GRAPH_NOTIFY, IntPtr.Zero);
// Now get the connection media types.
// Find the video sample grabber input pin and get the media type
var mtVideo = new AMMediaType();
IPin videoPin = DsFindPin.ByDirection(pSampleGrabberFilter, PinDirection.Input, 0);
if (videoPin != null)
{
videoPin.ConnectionMediaType(mtVideo);
var vih = (VideoInfoHeader)Marshal.PtrToStructure(mtVideo.formatPtr, typeof(VideoInfoHeader));
_captureBitCount = vih.BmiHeader.BitCount;
_captureWidth = vih.BmiHeader.Width;
_captureHeight = vih.BmiHeader.Height;
_captureFramerate = 10000000.0 / vih.AvgTimePerFrame;
Marshal.ReleaseComObject(videoPin);
DsUtils.FreeAMMediaType(mtVideo);
}
IMediaSeeking ms = _pGraph as IMediaSeeking;
long rtDuration;
ms.GetDuration(out rtDuration);
_totalDuration = rtDuration / 10000000.0;
_totalFrames = (int)(_totalDuration * _captureFramerate);
if (PlayerProgress != null)
{
PlayerProgress(this, new PlayerProgressEventArgs(0, _totalFrames, 0, _totalDuration));
}
State = PlaybackState.Loaded;
}
finally
{
if (pSourceFilter != null)
Marshal.ReleaseComObject(pSourceFilter);
if (pVideoRenderFilter != null)
Marshal.ReleaseComObject(pVideoRenderFilter);
if (pAudioRenderFilter != null)
Marshal.ReleaseComObject(pAudioRenderFilter);
if (pSampleGrabberFilter != null)
Marshal.ReleaseComObject(pSampleGrabberFilter);
if (pSplitterFilter != null)
Marshal.ReleaseComObject(pSplitterFilter);
}
}
public void Play()
{
if (State < PlaybackState.Loaded ||
State == PlaybackState.Running)
return;
IMediaControl mc = _pGraph as IMediaControl;
int hr = mc.Run();
State = PlaybackState.Running;
_playbackTimer.Start();
if (UseQueue)
{
_bitmapThread = new Thread(new ThreadStart(BitmapThread)) { Name = "Bitmap Thread" };
_bitmapThread.Start();
}
}
public void Pause()
{
if (State < PlaybackState.Loaded) return;
IMediaControl mc = _pGraph as IMediaControl;
int hr = mc.Pause();
State = PlaybackState.Paused;
_playbackTimer.Stop();
}
public void Stop()
{
if (State < PlaybackState.Loaded) return;
IMediaControl mc = _pGraph as IMediaControl;
int hr = mc.Stop();
State = PlaybackState.Paused;
_playbackTimer.Stop();
if (!UseQueue)
{
_myBitmap = null;
Win32Interop.UnmapViewOfFile(_destFrame);
Win32Interop.CloseHandle(_mappedFile);
_destFrame = IntPtr.Zero;
_mappedFile = IntPtr.Zero;
}
#if DEBUG
//System.Diagnostics.Debug.WriteLine(string.Format("DirectShowPlayer. Average copy buffer time: {0}, Frames: {1}", _copyBufferElapsed / (double)_frames, _frames));
//System.Diagnostics.Debug.WriteLine(string.Format("DirectShowPlayer. Average WPF image processing time: {0}, EventNr: {1}", _frameReceivedElapsed / (double)_frameReceivedCount, _frameReceivedCount));
//_copyBufferElapsed = 0;
//_frames = 0;
//_frameReceivedElapsed = 0;
//_frameReceivedCount = 0;
#endif
Source = null;
}
public void Close()
{
Stop();
if (_msgFrm != null)
{
_msgFrm.DestroyHandle();
_msgFrm = null;
}
_msgFrm = null;
State = PlaybackState.NotLoaded;
if (_bitmapThread != null)
{
_bitmapThread.Join(5000);
}
_bitmapThread = null;
if (_pGraph != null)
{
var meEx = _pGraph as IMediaEventEx;
if (meEx != null)
meEx.SetNotifyWindow(IntPtr.Zero, 0, IntPtr.Zero);
Marshal.ReleaseComObject(_pGraph);
}
_pGraph = null;
_sampleGrabberImpl = null;
WriteableBitmap bitmapSource;
while (_bitmapQueue.TryDequeue(out bitmapSource)) ;
}
private void Seek(double pos)
{
var ms = _pGraph as IMediaSeeking;
var lCurPos = (long)(pos * 10000000);
ms.SetPositions(lCurPos, AMSeekingSeekingFlags.AbsolutePositioning, 0,
AMSeekingSeekingFlags.NoPositioning);
}
#endregion
#region Properties
public bool Loop { get; set; }
public bool EnableAudio { get; set; }
public bool FlipImage { get; set; }
public PlaybackState State { get; private set; }
public double Duration
{
get
{
return _totalDuration;
}
}
/// <summary>
/// Sets the current position of the playing stream(s) in seconds
/// </summary>
public double CurrentTime
{
set
{
if (State < PlaybackState.Loaded || _pGraph == null) return;
Seek(value);
}
get
{
return currentTime;
}
}
/// <summary>
/// Sets the current position of the playing stream(s) in frames.
/// </summary>
public int CurrentFrame
{
set
{
if (State < PlaybackState.Loaded || _pGraph == null) return;
Seek(value / (double)_captureFramerate);
}
}
/// <summary>
/// Gets or sets the speed control of the playback. 1.0 is normal playback,
/// 0.5 is halftime slower and 1.5 is half time faster. negative values are
/// not supported.
/// </summary>
public double Rate
{
get
{
if (State < PlaybackState.Loaded || _pGraph == null) return 1.0;
var ms = _pGraph as IMediaSeeking;
double rate;
ms.GetRate(out rate);
return rate;
}
set
{
if (State < PlaybackState.Loaded || _pGraph == null) return;
var ms = _pGraph as IMediaSeeking;
try
{
DsError.ThrowExceptionForHR(ms.SetRate(value));
}
catch { }
}
}
/// <summary>
/// If true, it will use BitmapSource queue internally and client can get frames
/// in FrameReceived, if false, FrameReceived is not fires, queue is not used and
/// the client can grab frames at Source property.
/// </summary>
public bool UseQueue { get; set; }
private bool _paused;
public bool IsPaused
{
get
{
if (State == PlaybackState.Paused)
{
return true;
}
else
{
return false;
}
}
set
{
_paused = value;
if (_paused)
{
State = PlaybackState.Paused;
Pause();
}
else
{
State = PlaybackState.Running;
Play();
}
}
}
#endregion
#region Const Members
internal const int WM_GRAPH_NOTIFY = 0x8000 + 1;
#endregion
#region BufferCallback
protected int OnBuffer(double SampleTime, IntPtr pBuffer, int BufferLen)
{
if (!UseQueue)
{
int stride = ((_captureWidth * _captureBitCount / 8) + 3) & ~3;
if (_destFrame == IntPtr.Zero)
{
_mappedFile = Win32Interop.CreateFileMapping(new IntPtr(-1), IntPtr.Zero, PageAccess.ReadWrite, 0, BufferLen, null);
_destFrame = Win32Interop.MapViewOfFile(_mappedFile, FileMapAccess.AllAccess, 0, 0, (uint)BufferLen);
this.Dispatcher.Invoke(() =>
{
_myBitmap = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(_mappedFile, _captureWidth, _captureHeight,
System.Windows.Media.PixelFormats.Bgr24, stride, 0);
Source = _myBitmap;
});
}
if (FlipImage)
{
unsafe
{
var pDest = (byte*)_destFrame;
var pSrc = (byte*)pBuffer;
pSrc += BufferLen;
for (int i = 0; i < _captureHeight; i++)
{
byte* pDstTmp = pDest + i * stride;
byte* pSrcTemp = pSrc - (i + 1) * stride;
BitmapHelper.CopyMemory((IntPtr)pDstTmp, (IntPtr)pSrcTemp, (uint)stride);
}
}
}
else
{
BitmapHelper.CopyMemory(_destFrame, pBuffer, (uint)(_captureWidth * _captureHeight * _captureBitCount / 8));
}
this.Dispatcher.BeginInvoke(new Action(() =>
{
if (_myBitmap != null)
{
_myBitmap.Invalidate();
}
}), System.Windows.Threading.DispatcherPriority.Send, null);
}
else
{
WriteableBitmap bs = BitmapHelper.FromNativePointer(pBuffer, BufferLen, _captureWidth, _captureHeight, _captureBitCount == 24 ? 3 : 4, FlipImage);
bs.Freeze();
_bitmapQueue.Enqueue(bs);
}
counter++;
if (counter > 30)
{
//GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized, blocking: false);
counter = 0;
}
return 0;
}
#endregion
#region BitmapThread
protected void BitmapThread()
{
do
{
if (_bitmapQueue.Count > 0)
{
WriteableBitmap bs;
if (_bitmapQueue.TryDequeue(out bs))
{
if (FrameReceived != null)
{
FrameReceived(this, new FrameReceivedEventArgs(bs));
}
}
}
else
{
Thread.Sleep(5);
}
} while (State > PlaybackState.NotLoaded);
}
#endregion
#region Playback Timer
void _playbackTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
if (_pGraph != null)
{
if (PlayerProgress != null)
{
var ms = _pGraph as IMediaSeeking;
long rtCurrent;
ms.GetCurrentPosition(out rtCurrent);
double curTime = rtCurrent / 10000000.0;
currentTime = curTime;
var args = new PlayerProgressEventArgs((int)(curTime * _captureFramerate), _totalFrames, curTime, _totalDuration);
PlayerProgress(this, args);
}
}
}
#endregion
#region Handle Event
internal void HandleEvent()
{
if (State < PlaybackState.Loaded || _pGraph == null) return;
var pMediaEvent = _pGraph as IMediaEventEx;
if (pMediaEvent == null) return;
bool completed = false;
EventCode evc;
IntPtr lParam1, lParam2;
while (pMediaEvent.GetEvent(out evc, out lParam1, out lParam2, 0) == 0)
{
switch (evc)
{
case EventCode.Complete:
completed = true;
break;
}
pMediaEvent.FreeEventParams(evc, lParam1, lParam2);
}
if (completed)
{
if (Loop)
{
CurrentTime = 0;
}
}
}
#endregion
}
}