using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using DirectShowLib; using System.Runtime.InteropServices; using System.Windows.Media.Imaging; using System.Collections.Concurrent; using System.Threading; using System.Windows.Media; using System.Drawing; using System.Diagnostics; using System.Windows.Interop; using System.Windows; using Tango.Video.DirectShow.EventArguments; using Tango.Video.DirectShow.Delegates; namespace Tango.Video.DirectShow { public class DirectShowCapture : DependencyObject { int counter = 0; 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(DirectShowCapture), new PropertyMetadata(null)); #region Members private IGraphBuilder _graphBuilder; private ICaptureGraphBuilder2 _captureGraphBuilder2; private SampleGrabberImpl _sampleGrabberImpl; private int _captureBitCount; private int _captureWidth; private int _captureHeight; private readonly ConcurrentQueue _bitmapQueue = new ConcurrentQueue(); Thread _bitmapThread = 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 events public event FrameReceivedEventDelegate FrameReceived; #endregion #region Constructor public DirectShowCapture() { IsStarted = false; UseFrameSize = true; FrameSize = new FrameSize(640, 480); UseFrameRate = true; FrameRate = 5.0; FlipImage = true; UseQueue = false; } #endregion #region Methods /// /// Returns a list of video input devices. /// /// public static List GetAvailableDevices() { return DSUtils.GetVideoInputDevices(); } /// /// Opens the DirectShow Capture /// public void Start() { Stop(); if (VideoDevice == null) { VideoDevice = GetAvailableDevices().FirstOrDefault(); if (VideoDevice == null) { throw new Exception("No video device found."); } } IBaseFilter videoDevice = null; IBaseFilter nullRenderer = null; IBaseFilter sampleGrabberFilter = null; try { _graphBuilder = (IGraphBuilder)new FilterGraph(); _captureGraphBuilder2 = (ICaptureGraphBuilder2)new CaptureGraphBuilder2(); DsError.ThrowExceptionForHR(_captureGraphBuilder2.SetFiltergraph(_graphBuilder)); videoDevice = DSUtils.GetVideoInputDeviceFilter(VideoDevice); if (videoDevice == null) { throw new Exception("Video input device not found."); } DsError.ThrowExceptionForHR(_graphBuilder.AddFilter(videoDevice, "Video input device")); nullRenderer = (IBaseFilter)new NullRenderer(); DsError.ThrowExceptionForHR(_graphBuilder.AddFilter(nullRenderer, "Null Renderer")); sampleGrabberFilter = (IBaseFilter)new SampleGrabber(); DsError.ThrowExceptionForHR(_graphBuilder.AddFilter(sampleGrabberFilter, "Sample Grabber")); _sampleGrabberImpl = new SampleGrabberImpl(OnBuffer); DSUtils.SetSampleGrabber(sampleGrabberFilter, _sampleGrabberImpl); SetupVideoSource(videoDevice); // Connect DsError.ThrowExceptionForHR(_captureGraphBuilder2.RenderStream(PinCategory.Capture, MediaType.Video, videoDevice, sampleGrabberFilter, nullRenderer)); // Now get the connection media types. // Find the video sample grabber input pin and get the media type IPin videoPin = DsFindPin.ByDirection(sampleGrabberFilter, PinDirection.Input, 0); if (videoPin != null) { var mtVideo = new AMMediaType(); videoPin.ConnectionMediaType(mtVideo); var vih = (VideoInfoHeader)Marshal.PtrToStructure(mtVideo.formatPtr, typeof(VideoInfoHeader)); _captureBitCount = vih.BmiHeader.BitCount; _captureWidth = vih.BmiHeader.Width; _captureHeight = vih.BmiHeader.Height; Marshal.ReleaseComObject(videoPin); DirectShowLib.DsUtils.FreeAMMediaType(mtVideo); } var mc = _graphBuilder as IMediaControl; mc.Run(); IsStarted = true; //if (UseQueue) //{ _bitmapThread = new Thread(new ThreadStart(BitmapThread)) { Name = "Bitmap Thread" }; _bitmapThread.IsBackground = true; _bitmapThread.Start(); //} } finally { if (videoDevice != null) Marshal.ReleaseComObject(videoDevice); videoDevice = null; if (nullRenderer != null) Marshal.ReleaseComObject(nullRenderer); nullRenderer = null; if (sampleGrabberFilter != null) Marshal.ReleaseComObject(sampleGrabberFilter); sampleGrabberFilter = null; } } /// /// Stop directshow capture. /// public void Stop() { if (!IsStarted) return; IsStarted = false; if (_bitmapThread != null) { _bitmapThread.Join(5000); } _bitmapThread = null; if (_graphBuilder != null) { var mc = _graphBuilder as IMediaControl; if (mc != null) mc.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("DirectShowCapture. Average copy buffer time: {0}, Frames: {1}", _copyBufferElapsed/(double)_frames, _frames)); //System.Diagnostics.Debug.WriteLine(string.Format("DirectShowCapture. Average WPF image processing time: {0}, EventNr: {1}", _frameReceivedElapsed / (double)_frameReceivedCount, _frameReceivedCount)); //_copyBufferElapsed = 0; //_frames = 0; //_frameReceivedElapsed = 0; //_frameReceivedCount = 0; #endif if (_captureGraphBuilder2 != null) Marshal.ReleaseComObject(_captureGraphBuilder2); _captureGraphBuilder2 = null; if (_graphBuilder != null) { Marshal.ReleaseComObject(_graphBuilder); } _graphBuilder = null; _sampleGrabberImpl = null; WriteableBitmap bitmapSource; while (_bitmapQueue.TryDequeue(out bitmapSource)) ; } #endregion #region Properties public bool UseFrameSize { get; set; } public FrameSize FrameSize { get; set; } public bool UseFrameRate { get; set; } public double FrameRate { get; set; } public Device VideoDevice { get; set; } public bool IsStarted { get; private set; } public bool FlipImage { get; set; } /// /// 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; } #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 DEBUG var sw = new Stopwatch(); sw.Start(); #endif if (FrameReceived != null) { FrameReceived(this, new FrameReceivedEventArgs(bs)); } #if DEBUG sw.Stop(); //_frameReceivedElapsed += sw.ElapsedMilliseconds; //_frameReceivedCount++; #endif } } else { Thread.Sleep(2); } } while (IsStarted); } #endregion #region Helper Methods private void SetupVideoSource(IBaseFilter pVideoSource) { object oStreamConfig = null; int hr = _captureGraphBuilder2.FindInterface(PinCategory.Capture, MediaType.Video, pVideoSource, typeof(IAMStreamConfig).GUID, out oStreamConfig); var pStreamConfig = (IAMStreamConfig)oStreamConfig; var mt = new AMMediaType(); pStreamConfig.GetFormat(out mt); var vfi = (VideoInfoHeader)Marshal.PtrToStructure(mt.formatPtr, typeof(VideoInfoHeader)); if (UseFrameSize) { vfi.BmiHeader.Width = FrameSize.Width; vfi.BmiHeader.Height = FrameSize.Height; } if (UseFrameRate) { vfi.AvgTimePerFrame = (long)(10000000.0 / FrameRate); } vfi.BmiHeader.BitCount = 24; vfi.BmiHeader.ImageSize = FrameSize.Width * FrameSize.Height * vfi.BmiHeader.BitCount / 8; vfi.BmiHeader.Compression = 0;// BI_RGB Marshal.StructureToPtr(vfi, mt.formatPtr, false); hr = pStreamConfig.SetFormat(mt); DsUtils.FreeAMMediaType(mt); } #endregion } }