using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; using System.Windows.Media.Imaging; using Tango.Video.DirectShow; using Tango.Video.DirectShow.EventArguments; using Tango.Video.DirectShow.Delegates; namespace Tango.Video.DirectCapture { /// /// Represents a capture device object. This is a dependency object. /// public class CaptureDevice : DependencyObject, INotifyPropertyChanged, IDisposable { private DirectShowCapture captureDevice; private WriteableBitmap blackSource; private int updateSourceCounter; #region Events /// /// Raises then a new frame has been received from the capture device. /// /// ///void capture_FrameReceived(object sender, DirectShow.EventArguments.FrameReceivedEventArgs args) ///{ /// //Use this event to draw on, or manipulate the frames. /// //This will be triggered only if RaiseFrameReceivedEvent is set to true. /// //To show the manipulated image, unbind the img args from the VideoSource property in the xaml file. /// /// args.BitmapSource.Lock(); /// /// System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(args.BitmapSource.PixelWidth, args.BitmapSource.PixelHeight, args.BitmapSource.BackBufferStride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, args.BitmapSource.BackBuffer); /// System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bmp); /// /// //Draw on the frame using good old GDI+ /// g.FillEllipse(System.Drawing.Brushes.Red, new System.Drawing.Rectangle(20, 20, 100, 100)); /// g.DrawString("Process The Frames ;)", new System.Drawing.Font("Arial", 24), System.Drawing.Brushes.White, new System.Drawing.RectangleF(140, 50, 400, 50)); /// /// g.Dispose(); /// bmp.Dispose(); /// /// args.BitmapSource.AddDirtyRect(new Int32Rect(0, 0, args.BitmapSource.PixelWidth, args.BitmapSource.PixelHeight)); /// args.BitmapSource.Unlock(); /// args.BitmapSource.Freeze(); /// /// this.Dispatcher.BeginInvoke(new Action(() => /// { /// postImage.Source = args.BitmapSource; //This event is raising from a seperate thread so we need to invoke the UI dispatcher. /// })); ///} /// public event FrameReceivedEventDelegate FrameReceived; /// /// Occurs when a video capture has started. /// public event Action CaptureStarted; /// /// Occurs when a video capture has stoped. /// public event Action CaptureStoped; #endregion #region Constructors public CaptureDevice() { captureDevice = new DirectShowCapture(); captureDevice.FrameReceived += captureDevice_FrameReceived; captureDevice.UseFrameRate = false; captureDevice.UseFrameSize = false; captureDevice.UseQueue = false; updateSourceCounter = 0; UpdateSourceRatio = 1; //Initialize Black Source blackSource = new WriteableBitmap(Tango.Video.DirectShow.BitmapHelper.CreateEmptySource(new Size(1920, 1080), Colors.Transparent)); blackSource.Freeze(); //Initialize Available Sources. List list = Tango.Video.DirectShow.DirectShowCapture.GetAvailableDevices(); AvailableCaptureDevices = new ObservableCollection(list); //Initialize Standard Resolutions. List res = new List(); res.Add(new Resolution(320, 240)); res.Add(new Resolution(640, 480)); res.Add(new Resolution(720, 576)); res.Add(new Resolution(800, 600)); res.Add(new Resolution(1024, 768)); res.Add(new Resolution(1280, 720)); res.Add(new Resolution(1280, 1024)); res.Add(new Resolution(1440, 900)); res.Add(new Resolution(1366, 768)); res.Add(new Resolution(1600, 900)); res.Add(new Resolution(1680, 1050)); res.Add(new Resolution(1600, 1200)); res.Add(new Resolution(1920, 1080)); res.Add(new Resolution(1920, 1200)); StandardResolutions = new ObservableCollection(res); //Initialize Standard Frame Rates. List rates = new List(); rates.Add(new FrameRate(5)); rates.Add(new FrameRate(10)); rates.Add(new FrameRate(15)); rates.Add(new FrameRate(20)); rates.Add(new FrameRate(23.98)); rates.Add(new FrameRate(29.97)); rates.Add(new FrameRate(50)); rates.Add(new FrameRate(60)); StandardFrameRates = new ObservableCollection(rates); } #endregion #region Event Handlers private void captureDevice_FrameReceived(object sender, FrameReceivedEventArgs args) { this.Dispatcher.BeginInvoke(new Action(() => { updateSourceCounter++; if (updateSourceCounter >= UpdateSourceRatio) { VideoSource = args.BitmapSource; updateSourceCounter = 0; } if (!_isStarted && ClearSourceOnStop) { BindSourceToBlackSource(); } }), System.Windows.Threading.DispatcherPriority.Send); if (_raiseFrameReceivedEvent) { OnFrameReceived(this, new FrameReceivedEventArgs(BitmapHelper.BitmapSourceToWriteableBitmap(args.BitmapSource))); } } #endregion #region Properties private bool _emulatedMode; /// /// Gets or sets a value indicating whether [video available]. /// public bool EmulatedMode { get { return _emulatedMode; } set { _emulatedMode = value; RaisePropertyChanged(nameof(EmulatedMode)); } } /// /// The name of the device to capture. /// public Device Device { get { return (Device)GetValue(DeviceProperty); } set { SetValue(DeviceProperty, value); } } public static readonly DependencyProperty DeviceProperty = DependencyProperty.Register("Device", typeof(Device), typeof(CaptureDevice), new PropertyMetadata(null, new PropertyChangedCallback(DeviceNameChanged))); private static void DeviceNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { CaptureDevice instance = GetInstance(d); instance.captureDevice.VideoDevice = instance.Device; if (instance.IsStarted && instance.InvalidateSourceOnPropertiesChange) { instance.InvalidateDevice(); } } /// /// Sets the frame rate of the capture device. /// public FrameRate FrameRate { get { return (FrameRate)GetValue(FrameRateProperty); } set { SetValue(FrameRateProperty, value); } } public static readonly DependencyProperty FrameRateProperty = DependencyProperty.Register("FrameRate", typeof(FrameRate), typeof(CaptureDevice), new PropertyMetadata(new FrameRate(15), new PropertyChangedCallback(FrameRateChanged))); private static void FrameRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); if (instance.FrameRate != null) { instance.captureDevice.FrameRate = instance.FrameRate.Rate; if (instance.IsStarted && instance.InvalidateSourceOnPropertiesChange) { instance.InvalidateDevice(); } } } /// /// When set to true, the capture device will use the default frame rate. /// public bool AutoFrameRate { get { return (bool)GetValue(AutoFrameRateProperty); } set { SetValue(AutoFrameRateProperty, value); captureDevice.UseFrameRate = !AutoFrameRate; } } public static readonly DependencyProperty AutoFrameRateProperty = DependencyProperty.Register("AutoFrameRate", typeof(bool), typeof(CaptureDevice), new PropertyMetadata(true, new PropertyChangedCallback(AutoFrameRateChanged))); private static void AutoFrameRateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); instance.captureDevice.UseFrameRate = !instance.AutoFrameRate; } /// /// Sets the capture device frame width. /// public int FrameWidth { get { return (int)GetValue(FrameWidthProperty); } set { SetValue(FrameWidthProperty, value); captureDevice.FrameSize = new FrameSize(FrameWidth, FrameHeight); } } public static readonly DependencyProperty FrameWidthProperty = DependencyProperty.Register("FrameWidth", typeof(int), typeof(CaptureDevice), new PropertyMetadata(1280, new PropertyChangedCallback(FrameWidthChanged))); private static void FrameWidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); instance.captureDevice.FrameSize = new FrameSize(instance.FrameWidth, instance.FrameHeight); } /// /// Sets the capture device frame height. /// public int FrameHeight { get { return (int)GetValue(FrameHeightProperty); } set { SetValue(FrameHeightProperty, value); captureDevice.FrameSize = new FrameSize(FrameWidth, FrameHeight); } } public static readonly DependencyProperty FrameHeightProperty = DependencyProperty.Register("FrameHeight", typeof(int), typeof(CaptureDevice), new PropertyMetadata(720, new PropertyChangedCallback(FrameHeightChanged))); private static void FrameHeightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); instance.captureDevice.FrameSize = new FrameSize(instance.FrameWidth, instance.FrameHeight); } /// /// Then set to true, the capture device will use the default frame size. /// public bool AutoFrameSize { get { return (bool)GetValue(AutoFrameSizeProperty); } set { SetValue(AutoFrameSizeProperty, value); } } public static readonly DependencyProperty AutoFrameSizeProperty = DependencyProperty.Register("AutoFrameSize", typeof(bool), typeof(CaptureDevice), new PropertyMetadata(true, new PropertyChangedCallback(AutoFrameSizeChanged))); private static void AutoFrameSizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); instance.captureDevice.UseFrameSize = !instance.AutoFrameSize; } /// /// Use this property to bind your image args to. /// public BitmapSource VideoSource { get { return (BitmapSource)GetValue(VideoSourceProperty); } set { SetValue(VideoSourceProperty, value); } } public static readonly DependencyProperty VideoSourceProperty = DependencyProperty.Register("VideoSource", typeof(BitmapSource), typeof(CaptureDevice), new PropertyMetadata(null)); /// /// Contains a list of available capture device names. /// public ObservableCollection AvailableCaptureDevices { get { return (ObservableCollection)GetValue(AvailableCaptureDevicesProperty); } set { SetValue(AvailableCaptureDevicesProperty, value); } } public static readonly DependencyProperty AvailableCaptureDevicesProperty = DependencyProperty.Register("AvailableCaptureDevices", typeof(ObservableCollection), typeof(CaptureDevice), new PropertyMetadata(null)); /// /// Use this property as an alternative to set the capture device frame width and height. /// public Resolution Resolution { get { return (Resolution)GetValue(ResolutionProperty); } set { SetValue(ResolutionProperty, value); } } public static readonly DependencyProperty ResolutionProperty = DependencyProperty.Register("Resolution", typeof(Resolution), typeof(CaptureDevice), new PropertyMetadata(null, new PropertyChangedCallback(ResolutionChanged))); private static void ResolutionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var instance = GetInstance(d); if (instance.Resolution != null) { instance.captureDevice.FrameSize = new FrameSize(instance.Resolution.Width, instance.Resolution.Height); if (instance.IsStarted && instance.InvalidateSourceOnPropertiesChange) { instance.InvalidateDevice(); } } } /// /// Contains a list of standard resolutions for any capture device. /// public ObservableCollection StandardResolutions { get { return (ObservableCollection)GetValue(StandardResolutionsProperty); } set { SetValue(StandardResolutionsProperty, value); } } public static readonly DependencyProperty StandardResolutionsProperty = DependencyProperty.Register("StandardResolutions", typeof(ObservableCollection), typeof(CaptureDevice), new PropertyMetadata(null)); /// /// Contains a list of standard frame rates for any capture device. /// public ObservableCollection StandardFrameRates { get { return (ObservableCollection)GetValue(StandardFrameRatesProperty); } set { SetValue(StandardFrameRatesProperty, value); } } public static readonly DependencyProperty StandardFrameRatesProperty = DependencyProperty.Register("StandardFrameRates", typeof(ObservableCollection), typeof(CaptureDevice), new PropertyMetadata(null)); /// /// Gets or sets whether to reconnect the capture device if resolution, frame rate or device name were changed. /// public bool InvalidateSourceOnPropertiesChange { get { return (bool)GetValue(InvalidateSourceOnPropertiesChangeProperty); } set { SetValue(InvalidateSourceOnPropertiesChangeProperty, value); } } public static readonly DependencyProperty InvalidateSourceOnPropertiesChangeProperty = DependencyProperty.Register("InvalidateSourceOnPropertiesChange", typeof(bool), typeof(CaptureDevice), new PropertyMetadata(false)); /// /// Gets or sets whether to clear the image args when the capture device stops. /// public bool ClearSourceOnStop { get { return (bool)GetValue(ClearSourceOnStopProperty); } set { SetValue(ClearSourceOnStopProperty, value); } } public static readonly DependencyProperty ClearSourceOnStopProperty = DependencyProperty.Register("ClearSourceOnStop", typeof(bool), typeof(CaptureDevice), new PropertyMetadata(false)); private bool _raiseFrameReceivedEvent; /// /// Gets or sets whether to raise the FrameReceived event. (Set to true if you want to manipulate the args). /// public bool RaiseFrameReceivedEvent { get { return _raiseFrameReceivedEvent; } set { if (_raiseFrameReceivedEvent && !value && _isStarted) { BindSourceToDirectShow(); } _raiseFrameReceivedEvent = value; RaisePropertyChanged("RaiseFrameReceivedEvent"); captureDevice.UseQueue = value; } } private bool _isStarted; /// /// Gets or sets whether the capture device is capturing. /// public bool IsStarted { get { return _isStarted; } set { _isStarted = value; RaisePropertyChanged("IsStarted"); if (value) { Start(); } else { Stop(); } } } private int updateSourceRatio; /// /// Gets or sets the dependepncy property video source update frequency. /// public int UpdateSourceRatio { get { return updateSourceRatio; } set { updateSourceRatio = value; RaisePropertyChanged("UpdateSourceRatio"); } } #endregion #region Public Methods /// /// Starts the capture device. /// public void Start() { BindSourceToDirectShow(); _isStarted = true; RaisePropertyChanged("IsStarted"); if (!IsInDesignMode()) { captureDevice.Start(); } if (CaptureStarted != null) CaptureStarted(); } /// /// Stops the capture device. /// public void Stop() { _isStarted = false; RaisePropertyChanged("IsStarted"); if (!IsInDesignMode()) { captureDevice.Stop(); } if (ClearSourceOnStop) { BindSourceToBlackSource(); if (_raiseFrameReceivedEvent) { OnFrameReceived(this, new FrameReceivedEventArgs(new WriteableBitmap(BitmapHelper.CreateEmptySource(new Size(1920, 1080), Colors.Transparent)))); } } if (CaptureStoped != null) CaptureStoped(); } /// /// Use this method to bind an image control to the video args property. /// /// The image control /// The binding instance that was used to create the binding. public Binding BindImageSourceToVideoSource(Image image) { Binding b = new Binding(); b.Source = this; b.Path = new PropertyPath(CaptureDevice.VideoSourceProperty); b.Mode = BindingMode.OneWay; b.IsAsync = true; b.BindsDirectlyToSource = true; image.SetBinding(Image.SourceProperty, b); return b; } /// /// Use this method to bind any control background to the video args property. /// /// Any type that is a control or inharits from control. /// The binding instance that was used to create the binding. public Binding BindControlBackgroundToVideoSource(Control control) { Binding b = new Binding(); b.Source = this; b.Path = new PropertyPath(CaptureDevice.VideoSourceProperty); b.Mode = BindingMode.OneWay; b.IsAsync = true; b.BindsDirectlyToSource = true; control.SetBinding(Control.BackgroundProperty, b); return b; } public void DisableSourceUpdate() { EmulatedMode = true; this.Unbind(CaptureDevice.VideoSourceProperty); } public void EnableSourceUpdate() { EmulatedMode = false; BindSourceToDirectShow(); } #endregion #region Private Methods private void BindSourceToDirectShow() { this.BindAsync(CaptureDevice.VideoSourceProperty, captureDevice, DirectShowCapture.SourceProperty, BindingMode.OneWay); } private void BindSourceToBlackSource() { VideoSource = blackSource; } private bool IsInDesignMode() { return (DesignerProperties.GetIsInDesignMode(this)); } private void InvalidateDevice() { try { captureDevice.Stop(); captureDevice.Start(); } catch { } } #endregion #region Static Methods private static CaptureDevice GetInstance(DependencyObject d) { return d as CaptureDevice; } /// /// Retrieves all the available capture devices supporting direct show. /// /// Device names. public static List GetAvailableCaptureDevices() { return DirectShowCapture.GetAvailableDevices(); } #endregion #region Virtuals protected virtual void OnFrameReceived(object sender, FrameReceivedEventArgs args) { if (FrameReceived != null) FrameReceived(sender, args); } #endregion #region IDisposable Members /// /// Stops and disposes the current instance. /// public void Dispose() { _isStarted = false; captureDevice.Stop(); } #endregion #region Notify Property Changed Members public event PropertyChangedEventHandler PropertyChanged; private void RaisePropertyChanged(String propName) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propName)); } #endregion } }