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
}
}