Click here to Skip to main content
15,886,110 members
Articles / Desktop Programming / WPF

WPF: Webcam Control

Rate me:
Please Sign up or sign in to vote.
4.92/5 (150 votes)
25 Sep 2017CPOL3 min read 1.3M   90.3K   369  
A WPF control for displaying and recording video from a webcam
Imports Microsoft.Expression.Encoder
Imports Microsoft.Expression.Encoder.Devices
Imports Microsoft.Expression.Encoder.Live
Imports Microsoft.Expression.Encoder.Profiles
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.ComponentModel

Public Class Webcam
    Implements IDisposable

#Region "VideoFileFormat dependency property"
    ''' <summary>
    ''' Gets the video format in which the webcam video will be saved.
    ''' </summary>
    Public ReadOnly Property VideoFileFormat As String
        Get
            Return GetValue(Webcam.VideoFileFormatProperty)
        End Get
    End Property

    Private Shared ReadOnly VideoFileFormatPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly("VideoFileFormat", GetType(String), GetType(Webcam), New PropertyMetadata(".wmv"))

    Public Shared ReadOnly VideoFileFormatProperty As DependencyProperty = VideoFileFormatPropertyKey.DependencyProperty
#End Region

#Region "SnapshotFormat dependency property"
    ''' <summary>
    ''' Gets or Sets format used when saving snapshot of webcam image.
    ''' </summary>    
    Public Property SnapshotFormat() As ImageFormat
        Get
            Return CType(GetValue(SnapshotFormatProperty), ImageFormat)
        End Get
        Set(ByVal value As ImageFormat)
            SetValue(SnapshotFormatProperty, value)
        End Set
    End Property

    Public Shared ReadOnly SnapshotFormatProperty As DependencyProperty =
        DependencyProperty.Register("SnapshotFormat", GetType(ImageFormat), GetType(Webcam), New PropertyMetadata(ImageFormat.Jpeg))
#End Region

#Region "VideoDevice dependency property"
    ''' <summary>
    ''' Gets or Sets the name of the video device to be used.
    ''' </summary>    
    Public Property VideoDevice() As EncoderDevice
        Get
            Return CType(GetValue(VideoDeviceProperty), EncoderDevice)
        End Get
        Set(ByVal value As EncoderDevice)
            SetValue(VideoDeviceProperty, value)
        End Set
    End Property

    Public Shared ReadOnly VideoDeviceProperty As DependencyProperty =
        DependencyProperty.Register("VideoDevice", GetType(EncoderDevice), GetType(Webcam),
                                    New PropertyMetadata(New PropertyChangedCallback(AddressOf VideoDeviceChange)))

    Private _videoDevice As EncoderDevice
    Private Shared Sub VideoDeviceChange(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim device As EncoderDevice = CType(e.NewValue, EncoderDevice)
        Dim devices As IEnumerable(Of EncoderDevice) =
            EncoderDevices.FindDevices(EncoderDeviceType.Video).Where(Function(dv) dv.Name = device.Name)

        If (devices.Count <> 0) Then
            Dim src As Webcam = CType(source, Webcam)
            src._videoDevice = devices.First
            If (src.isPreviewing) Then
                src.StartPreview()
            End If
        End If
    End Sub
#End Region

#Region "AudioDevice dependency property"
    ''' <summary>
    ''' Gets or Sets the name of the audio device to be used.
    ''' </summary>    
    Public Property AudioDevice() As EncoderDevice
        Get
            Return CType(GetValue(AudioDeviceProperty), EncoderDevice)
        End Get
        Set(ByVal value As EncoderDevice)
            SetValue(AudioDeviceProperty, value)
        End Set
    End Property

    Public Shared ReadOnly AudioDeviceProperty As DependencyProperty =
        DependencyProperty.Register("AudioDevice", GetType(EncoderDevice), GetType(Webcam),
                                    New PropertyMetadata(New PropertyChangedCallback(AddressOf AudioDeviceChange)))

    Private _audioDevice As EncoderDevice
    Private Shared Sub AudioDeviceChange(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim device As EncoderDevice = CType(e.NewValue, EncoderDevice)
        Dim devices As IEnumerable(Of EncoderDevice) =
            EncoderDevices.FindDevices(EncoderDeviceType.Audio).Where(Function(dv) dv.Name = device.Name)

        If (devices.Count <> 0) Then
            Dim src As Webcam = CType(source, Webcam)
            src._audioDevice = devices.First
            If (src.isPreviewing) Then
                src.StartPreview()
            End If
        End If
    End Sub
#End Region

#Region "VideoDirectory dependency property"
    ''' <summary>
    ''' Gets or Sets the folder where the recorded webcam video will be saved.
    ''' </summary>    
    Public Property VideoDirectory() As String
        Get
            Return CType(GetValue(VideoDirectoryProperty), String)
        End Get
        Set(ByVal value As String)
            SetValue(VideoDirectoryProperty, value)
        End Set
    End Property

    Public Shared ReadOnly VideoDirectoryProperty As DependencyProperty =
        DependencyProperty.Register("VideoDirectory", GetType(String), GetType(Webcam), New PropertyMetadata(Nothing))
#End Region

#Region "ImageDirectory dependency property"
    ''' <summary>
    ''' Gets or Sets the folder where video snapshot will be saved.
    ''' </summary>    
    Public Property ImageDirectory() As String
        Get
            Return CType(GetValue(ImageDirectoryProperty), String)
        End Get
        Set(ByVal value As String)
            SetValue(ImageDirectoryProperty, value)
        End Set
    End Property

    Public Shared ImageDirectoryProperty As DependencyProperty =
        DependencyProperty.Register("ImageDirectory", GetType(String), GetType(Webcam), New PropertyMetadata(Nothing))
#End Region

#Region "FrameRate dependency property"
    ''' <summary>
    ''' Gets or sets the frame rate, in frames per second. Default value is 15.
    ''' </summary>    
    Public Property FrameRate As Integer
        Get
            Return CType(GetValue(FrameRateProperty), Integer)
        End Get
        Set(value As Integer)
            SetValue(FrameRateProperty, value)
        End Set
    End Property

    Public Shared ReadOnly FrameRateProperty As DependencyProperty =
        DependencyProperty.Register("FrameRate", GetType(Integer), GetType(Webcam),
                                    New PropertyMetadata(15, New PropertyChangedCallback(AddressOf FrameRateChange)))

    Private Shared Sub FrameRateChange(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim src As Webcam = CType(source, Webcam)
        If (src.isPreviewing) Then
            src.StartPreview()
        End If
    End Sub
#End Region

#Region "FrameSize dependency property"
    ''' <summary>
    ''' Gets or sets the size of the video profile. Default is 320x240.
    ''' </summary>    
    Public Property FrameSize As Size
        Get
            Return CType(GetValue(FrameSizeProperty), Size)
        End Get
        Set(value As Size)
            SetValue(FrameSizeProperty, value)
        End Set
    End Property

    Public Shared ReadOnly FrameSizeProperty As DependencyProperty =
        DependencyProperty.Register("FrameSize", GetType(Size), GetType(Webcam),
                                    New PropertyMetadata(New Size(320, 240), New PropertyChangedCallback(AddressOf FrameSizeChange)))

    Private Shared Sub FrameSizeChange(ByVal source As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim src As Webcam = CType(source, Webcam)
        If (src.isPreviewing) Then
            src.StartPreview()
        End If
    End Sub
#End Region

#Region "IsRecording dependency property"
    ''' <summary>
    ''' Gets a value indicating whether video recording is taking place.
    ''' </summary>
    Public ReadOnly Property IsRecording As Boolean
        Get
            Return GetValue(Webcam.IsRecordingProperty)
        End Get
    End Property

    Private Shared ReadOnly IsRecordingPropertyKey As DependencyPropertyKey =
        DependencyProperty.RegisterReadOnly("IsRecording", GetType(Boolean), GetType(Webcam), New PropertyMetadata(Nothing))

    Public Shared ReadOnly IsRecordingProperty As DependencyProperty = IsRecordingPropertyKey.DependencyProperty
#End Region

    Private deviceSource As LiveDeviceSource
    Private job As LiveJob
    Private isPreviewing As Boolean

    ''' <summary>
    ''' Displays webcam video.
    ''' </summary>
    Public Sub StartPreview()
        Try
            If (_videoDevice IsNot Nothing) Then
                StopRecording()
                StopPreview()

                job = New LiveJob
                Dim frameDuration As Long = CLng(FrameRate * Math.Pow(10, 7))

                deviceSource = job.AddDeviceSource(_videoDevice, _audioDevice)
                deviceSource.PickBestVideoFormat(FrameSize, frameDuration)
                deviceSource.PreviewWindow = New PreviewWindow(New HandleRef(WebcamPanel, WebcamPanel.Handle))

                job.OutputFormat.VideoProfile = New AdvancedVC1VideoProfile
                job.OutputFormat.VideoProfile.Size = FrameSize
                job.OutputFormat.VideoProfile.FrameRate = FrameRate

                job.ActivateSource(deviceSource)

                isPreviewing = True
            End If
        Catch ex As Microsoft.Expression.Encoder.SystemErrorException
            Throw New Microsoft.Expression.Encoder.SystemErrorException
        End Try
    End Sub

    ''' <summary>
    ''' Stops the display of webcam video.
    ''' </summary>
    Public Sub StopPreview()
        If (job IsNot Nothing) Then
            StopRecording()
            deviceSource = Nothing
            job.Dispose()
            isPreviewing = False
        End If
    End Sub

    Private Function TimeStamp() As String
        Dim ts As String = DateTime.Now.ToString
        ts = ts.Replace("/", "-")
        ts = ts.Replace(":", ".")
        Return ts
    End Function

    ''' <summary>
    ''' Starts the recording of webcam images to a video file.
    ''' </summary>
    Public Sub StartRecording()
        Dim vidDir As String = CType(GetValue(VideoDirectoryProperty), String)

        If (vidDir = String.Empty) Then
            Throw New DirectoryNotFoundException("Video directory has not been specified")
            Exit Sub
        ElseIf (Not Directory.Exists(vidDir)) Then
            Throw New DirectoryNotFoundException("The specified video directory does not exist")
            Exit Sub
        End If

        If (job IsNot Nothing AndAlso isPreviewing) Then
            StopRecording()

            Dim filePath As String = Path.Combine(vidDir, "Webcam " & TimeStamp() & ".wmv")
            Dim fileArcvFormat As New FileArchivePublishFormat(filePath)

            job.PublishFormats.Clear()
            job.PublishFormats.Add(fileArcvFormat)
            job.StartEncoding()

            SetValue(IsRecordingPropertyKey, True)
        End If
    End Sub

    ''' <summary>
    ''' Stops the recording of webcam video.
    ''' </summary>
    Public Sub StopRecording()
        Dim recording As Boolean = CType(GetValue(IsRecordingProperty), Boolean)
        If (job IsNot Nothing AndAlso recording) Then
            job.StopEncoding()
            SetValue(IsRecordingPropertyKey, False)
        End If
    End Sub

    ''' <summary>
    ''' Takes a snapshot of an webcam image.
    ''' The size of the image will be equal to the size of the control.
    ''' </summary>
    Public Sub TakeSnapshot()
        Dim imgDir As String = CType(GetValue(ImageDirectoryProperty), String)

        If (imgDir = String.Empty) Then
            Throw New DirectoryNotFoundException("Image directory has not been specified")
            Exit Sub
        ElseIf (Not Directory.Exists(imgDir)) Then
            Throw New DirectoryNotFoundException("The specified image directory does not exist")
            Exit Sub
        End If

        If (job IsNot Nothing AndAlso isPreviewing) Then
            Dim panelWidth As Integer = WebcamPanel.Width
            Dim panelHeight As Integer = WebcamPanel.Height
            Dim imgFormat As ImageFormat = CType(GetValue(SnapshotFormatProperty), ImageFormat)
            Dim filePath As String = Path.Combine(imgDir, "Snapshot " & TimeStamp() & "." & imgFormat.ToString)
            Dim pnt As Point = WebcamPanel.PointToScreen(New Point(WebcamPanel.ClientRectangle.X, WebcamPanel.ClientRectangle.Y))

            Using bmp As New Bitmap(panelWidth, panelHeight)
                Using grx As Graphics = Graphics.FromImage(bmp)
                    grx.CopyFromScreen(pnt, Point.Empty, New Size(panelWidth, panelHeight))
                End Using
                bmp.Save(filePath, imgFormat)
            End Using
        End If
    End Sub

#Region "IDisposable Support"
    Private disposedValue As Boolean ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: dispose managed state (managed objects).
                If (deviceSource IsNot Nothing) Then
                    deviceSource.PreviewWindow.Dispose()
                    deviceSource.PreviewWindow = Nothing
                    deviceSource.Dispose()
                    deviceSource = Nothing
                End If

                If (job IsNot Nothing) Then
                    job.Dispose()
                    job = Nothing
                End If

                WinFormsHost.Dispose()
                WinFormsHost = Nothing

                WebcamPanel.Dispose()
                WebcamPanel = Nothing
            End If
        End If
        Me.disposedValue = True
    End Sub

    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

    Private Sub Webcam_SizeChanged(sender As Object, e As SizeChangedEventArgs) Handles Me.SizeChanged
        If (deviceSource IsNot Nothing) Then
            deviceSource.PreviewWindow.SetSize(New Size(CInt(Me.ActualWidth), CInt(Me.ActualHeight)))
        End If
    End Sub

    Private Sub Webcam_Unloaded(sender As Object, e As RoutedEventArgs) Handles Me.Unloaded
        Dispose()
    End Sub
End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Kenya Kenya
Experienced C# software developer with a passion for WPF.

Awards,
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • CodeProject MVP 2021

Comments and Discussions