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 _bitmapQueue = new ConcurrentQueue(); 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; } } /// /// Sets the current position of the playing stream(s) in seconds /// public double CurrentTime { set { if (State < PlaybackState.Loaded || _pGraph == null) return; Seek(value); } get { return currentTime; } } /// /// Sets the current position of the playing stream(s) in frames. /// public int CurrentFrame { set { if (State < PlaybackState.Loaded || _pGraph == null) return; Seek(value / (double)_captureFramerate); } } /// /// 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. /// 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 { } } } /// /// 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. /// 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 } }