WPF利用MediaFoundation打开摄像头捕捉图片

主要代码 以同步的方式获得帧

REM MediaFoundation的.net 类库 http://mfnet.sourceforge.net
Imports MediaFoundation
Imports System.Runtime.InteropServices
Imports System.IO
Imports System.Drawing
Imports System.Drawing.Imaging


Class MainWindow
    Private mediaSource As IMFMediaSource
    Private attribute As IMFAttributes
    Private activateDevices() As IMFActivate
    Private deviceName As String REM 设备名字


    ''' 
    ''' 打开摄像头设备
    ''' 
    Private Sub OpenCaptureDevice()
        Dim result As Integer
        result = MFExtern.MFCreateAttributes(attribute, 1) REM 创建一个属性
        If (result <> 0) Then Return
        attribute.SetGUID(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, CLSID.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID) REM 设置属性
        Dim devicescount As Integer
        MFExtern.MFEnumDeviceSources(attribute, activateDevices, devicescount) REM 枚举满足属性的摄像头设备
        If (result <> 0) Then Return
        If (devicescount = 0) Then Return
        activateDevices(0).GetAllocatedString(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, deviceName, 0)
        Console.WriteLine(deviceName)
        activateDevices(0).ActivateObject(GetType(IMFMediaSource).GUID, mediaSource) REM 激活设备
    End Sub


    Private presentationDescriptor As IMFPresentationDescriptor = Nothing
    Private streamDescriptor As IMFStreamDescriptor = Nothing
    Private mediatypeHandler As IMFMediaTypeHandler = Nothing
    Private mediatypeCount As Integer
    Private mediaType As IMFMediaType = Nothing
    ''' 
    ''' 枚举摄像头支持的参数,选择匹配的参数捕捉图像
    ''' 
    ''' 
    ''' 
    ''' 
    ''' 
    Private Sub SetupCatureDevice(width As Integer, height As Integer, fps As Double, typename As String)
        Dim result As Integer
        REM 创建SourceReader
        result = MFExtern.MFCreateSourceReaderFromMediaSource(mediaSource, attribute, sourcereader)
        If (result <> 0) Then Return
        REM 获得一个表现描述符
        result = mediaSource.CreatePresentationDescriptor(presentationDescriptor)
        If (result <> 0) Then Return
        Dim bselected As Boolean
        REM 从表现描述符中获得流描述符
        result = presentationDescriptor.GetStreamDescriptorByIndex(0, bselected, streamDescriptor)
        If (result <> 0) Then Return
        REM 从流描述器中或得媒体类型操作器
        result = streamDescriptor.GetMediaTypeHandler(mediatypeHandler)
        If (result <> 0) Then Return
        REM 获得支持的媒体类型
        result = mediatypeHandler.GetMediaTypeCount(mediatypeCount)
        If (result <> 0) Then Return
        For i = 0 To mediatypeCount - 1 REM 遍历媒体类型,选择合适的读取
            result = mediatypeHandler.GetMediaTypeByIndex(i, mediaType)
            If (result <> 0) Then Continue For
            Dim framesize As UInt64
            mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, framesize)
            Dim w, h As UInt32
            w = (framesize >> 32).ToString()
            h = (framesize And &H0FFFFFFF)
            Dim framerate As UInt64
            Dim frame As Int32 = (framerate >> 32).ToString()
            Dim ratio As Int32 = (framerate And &H00000000FFFFFFFFL).ToString()
            mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_RATE, framerate)
            Dim samplesize As Int32
            mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, samplesize)
            Dim subtype As Guid
            mediaType.GetGUID(MFAttributesClsid.MF_MT_SUBTYPE, subtype)
            Console.WriteLine(w.ToString + " x " + h.ToString() + " @ " + (frame / ratio).ToString("f1") + "hz" _
                + vbTab + "samplesize:" + samplesize.ToString() _
                + vbTab + "type:" + NameofGUID(subtype))
            If (w = width And h = height And frame / ratio = fps And NameofGUID(subtype) = typename) Then
                result = mediatypeHandler.SetCurrentMediaType(mediaType)
                If (result <> 0) Then Return
            End If
        Next
    End Sub


    ''' 
    ''' 工具函数,根据GUID 找名字
    ''' 
    ''' 
    ''' 
    Private Function NameofGUID(guid As Guid) As String
        Dim names() As Reflection.FieldInfo = GetType(MFMediaType).GetFields()
        For Each i In names
            Dim obj As Object = i.GetValue(Nothing)
            If TypeOf (obj) Is Guid AndAlso obj = guid Then
                Return i.Name
            End If
        Next
        Return "unknown"
    End Function


    Dim bufptr As IntPtr
    ''' 
    ''' 开始捕捉
    ''' 
    Private Sub CaptureStart()
        Dim samplesize As Int32
        If (mediaType Is Nothing) Then Throw New Exception("mediatype invailed")
        mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, samplesize) REM 获得一个样本的大小
        bufptr = Marshal.AllocHGlobal(samplesize) REM 设置图片的Buffer大小


        Dim var As New Misc.PropVariant()
        mediaSource.Start(presentationDescriptor, Nothing, var)
    End Sub


    ''' 
    ''' 停止捕捉
    ''' 
    Private Sub CaptureStop()
        mediaSource.Stop()
        Marshal.FreeHGlobal(bufptr)
    End Sub


    Private sourcereader As MediaFoundation.ReadWrite.IMFSourceReader
    Private streamindex As Integer
    Private streamflags As MediaFoundation.ReadWrite.MF_SOURCE_READER_FLAG
    Private timestamp As Long
    Private mfsample As IMFSample


    ''' 
    ''' 捕捉一帧
    ''' 
    Private Sub Capture()
        Dim result As Integer
        result = sourcereader.ReadSample(0, ReadWrite.MF_SOURCE_READER_CONTROL_FLAG.None, streamindex, streamflags, timestamp, mfsample)
        If (result <> 0) Then Return
        If (mfsample Is Nothing) Then Return


        Dim samplesize As Int32
        mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, samplesize)
        Dim mediabuf As IMFMediaBuffer = Nothing
        MFExtern.MFCreateMemoryBuffer(samplesize, mediabuf)
        mfsample.CopyToBuffer(mediabuf) REM 拷贝一个样本到MediaBuffer


        Dim maxlen, curlen As Integer REM 图像大小max,jpg的大小cur,压缩jpg大小不定
        mediabuf.GetMaxLength(maxlen)
        mediabuf.GetCurrentLength(curlen)


        mediabuf.Lock(bufptr, maxlen, curlen) REM 锁定 MediaBuffer
        Dim managebuf(maxlen - 1) As Byte
        Marshal.Copy(bufptr, managebuf, 0, maxlen) REM 从MediaBuffer拷贝到自定的图片buffer


        REM 转换buffer成BitmapSource
        Dim stream As New MemoryStream(managebuf.Length)
        stream.Seek(0, SeekOrigin.Begin)
        stream.Write(managebuf, 0, managebuf.Length)
        stream.Seek(0, SeekOrigin.Begin)
        imagesource = StreamToBitmapSource(stream)
        stream.Close()

        COMBase.SafeRelease(mfSample)
        mediabuf.Unlock()
    End Sub


    Private imagesource As ImageSource
    Private timer As New Windows.Threading.DispatcherTimer
    Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Width = 1280
        Height = 720
        Left = 0
        Top = 0
        OpenCaptureDevice()
        SetupCatureDevice(1280, 720, 30, "MJPG")
        CaptureStart()
        timer.Interval = TimeSpan.FromMilliseconds(10)
        AddHandler timer.Tick, AddressOf TimerTick
        timer.Start()
    End Sub


    Private Sub TimerTick(sender As Object, e As EventArgs)
        Capture()
        Dim bs As BitmapSource = imagesource
        Me.Background = New ImageBrush(bs)
    End Sub


    ''' 
    ''' 流转成BitmapSource
    ''' 
    ''' 流
    ''' 
    Public Shared Function StreamToBitmapSource(s As Stream) As BitmapSource
        Dim jpg As New JpegBitmapDecoder(s, BitmapCreateOptions.None, BitmapCacheOption.OnLoad) REM OnLoad很重要
        Return jpg.Frames(0)
    End Function


End Class

还可以使用异步方式,以异步的方式请求,重写函数给Mediafoundation回调来获得帧:

Imports MediaFoundation
Imports MediaFoundation.ReadWrite
Imports MediaFoundation.Misc
Imports System.IO

Public Class MFDevice
    Implements IDisposable

    Private mActivator As IMFActivate
    Private mFriendlyName As String
    Private mSymbolicName As String

    Public Sub New(a As IMFActivate)
        mActivator = a
        mFriendlyName = Nothing
        mFriendlyName = Nothing
    End Sub

    Public ReadOnly Property Activator As IMFActivate
        Get
            Return mActivator
        End Get
    End Property

    Public ReadOnly Property Name As String
        Get
            If (mFriendlyName Is Nothing) Then
                Dim size As Integer = 0
                Result = mActivator.GetAllocatedString(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, mFriendlyName, size)
            End If
            Return mFriendlyName
        End Get
    End Property

    Public ReadOnly Property SymbolicName As String
        Get
            If (mFriendlyName Is Nothing) Then
                Dim size As Integer = 0
                Result = mActivator.GetAllocatedString(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, mSymbolicName, size)
            End If
            Return mSymbolicName
        End Get
    End Property

    Public Shared Function GetVideoCaptureDevices() As MFDevice()
        Dim devices() As IMFActivate = Nothing
        Dim attrib As IMFAttributes = Nothing
        Result = MFExtern.MFCreateAttributes(attrib, 1)
        attrib.SetGUID(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, CLSID.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)
        Dim devicesCount As Integer
        Result = MFExtern.MFEnumDeviceSources(attrib, devices, devicesCount)
        Dim mfdevices(devicesCount) As MFDevice
        For i = 0 To devicesCount - 1
            mfdevices(i) = New MFDevice(devices(i))
        Next
        If attrib Is Nothing Then
            System.Runtime.InteropServices.Marshal.ReleaseComObject(attrib)
        End If
        Return mfdevices
    End Function

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 要检测冗余调用

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not disposedValue Then
            If disposing Then
                ' TODO: 释放托管状态(托管对象)。
                If (mActivator IsNot Nothing) Then
                    System.Runtime.InteropServices.Marshal.ReleaseComObject(mActivator)
                    mActivator = Nothing
                End If
                mFriendlyName = Nothing
                mSymbolicName = Nothing
                GC.SuppressFinalize(Me)
            End If

            ' TODO: 释放未托管资源(未托管对象)并在以下内容中替代 Finalize()。
            ' TODO: 将大型字段设置为 null。
        End If
        disposedValue = True
    End Sub

    ' TODO: 仅当以上 Dispose(disposing As Boolean)拥有用于释放未托管资源的代码时才替代 Finalize()。
    'Protected Overrides Sub Finalize()
    '    ' 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Visual Basic 添加此代码以正确实现可释放模式。

    Public Sub Dispose() Implements IDisposable.Dispose
        ' 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
        Dispose(True)
        ' TODO: 如果在以上内容中替代了 Finalize(),则取消注释以下行。
    End Sub
#End Region
End Class

Public Class MFCaptureAsync
    Inherits COMBase
    Implements ReadWrite.IMFSourceReaderCallback
    Implements IDisposable

    Private mSourceReaderAsync As Alt.IMFSourceReaderAsync
    Private mSymbolicLink As String
    Private mMediatype As IMFMediaType
    Public Sub New(dispatcher As System.Windows.Threading.Dispatcher)
        mSourceReaderAsync = Nothing
        mSymbolicLink = Nothing
        MFExtern.MFStartup(&H20070, MFStartup.Lite)
        Me.dispatcher = dispatcher
    End Sub

    Public Sub SetDevice(device As MFDevice, width As Integer, height As Integer, fps As Integer, fmt As String)
        Dim activate As IMFActivate = device.Activator
        Dim mediaSource As IMFMediaSource = Nothing
        Dim attrib As IMFAttributes = Nothing
        SyncLock Me
            Try
                CloseDevice()
                Result = activate.ActivateObject(GetType(IMFMediaSource).GUID, mediaSource)
                mSymbolicLink = device.SymbolicName
                Result = MFExtern.MFCreateAttributes(attrib, 1)
                Result = attrib.SetUnknown(MFAttributesClsid.MF_SOURCE_READER_ASYNC_CALLBACK, Me)
                Dim sourcereader As IMFSourceReader = Nothing
                Result = MFExtern.MFCreateSourceReaderFromMediaSource(mediaSource, attrib, sourcereader)
                mSourceReaderAsync = sourcereader
                Dim i As Integer = 0
                Do
                    Dim mediatype As IMFMediaType = Nothing
                    Result = mSourceReaderAsync.GetNativeMediaType(MF_SOURCE_READER.FirstVideoStream, i, mediatype)
                    If Failed(Result) Then
                        Exit Do
                    End If
                    Try
                        Result = TryMediaType(mediatype, width, height, fps, fmt)
                        If Result = HResult.S_OK Then
                            mMediatype = mediatype
                            Exit Do
                        End If
                    Catch ex As Exception
                        Console.WriteLine(ex.ToString())
                    Finally
                        SafeRelease(mediatype)
                    End Try
                    i += 1
                Loop While True
                Result = mSourceReaderAsync.ReadSample(MF_SOURCE_READER.FirstVideoStream, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
                If Failed(Result) Then
                    If mediaSource IsNot Nothing Then
                        mediaSource.Shutdown()
                    End If
                    CloseDevice()
                End If

            Catch ex As Exception
                Console.WriteLine(ex.ToString())
            Finally
                SafeRelease(mediaSource)
                SafeRelease(attrib)
            End Try
        End SyncLock
    End Sub

    Public Function TryMediaType(mediaType As IMFMediaType, width As Integer, height As Integer, fps As Integer, name As String)
        Dim framesize As UInt64
        Dim framerate As UInt64
        Dim subtype As Guid
        Result = mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, framesize)
        Result = mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_RATE, framerate)
        Result = mediaType.GetGUID(MFAttributesClsid.MF_MT_SUBTYPE, subtype)

        Dim w, h As UInt32
        w = (framesize >> 32)
        h = (framesize And &H00000000FFFFFFFFL)
        Dim frame As Int32 = (framerate >> 32)
        Dim ratio As Int32 = (framerate And &H00000000FFFFFFFFL)

        Dim samplesize As Int32
        Result = mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, samplesize)
        Console.WriteLine(w.ToString + " x " + h.ToString() + " @ " + (frame / ratio).ToString("f1") + "hz" _
                + vbTab + "samplesize:" + samplesize.ToString() _
                + vbTab + "type:" + NameofGUID(subtype))
        If (w = width And h = height And frame / ratio = fps And NameofGUID(subtype) = name) Then
            Result = mSourceReaderAsync.SetCurrentMediaType(MF_SOURCE_READER.FirstVideoStream, Nothing, mediaType)

            mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, samplesize) REM 获得一个样本的大小
            bufptr = System.Runtime.InteropServices.Marshal.AllocHGlobal(samplesize) REM 设置图片的Buffer大小
            Return HResult.S_OK
        End If
        Return HResult.S_FALSE
    End Function

    Private Function NameofGUID(guid As Guid) As String
        Dim names() As Reflection.FieldInfo = GetType(MFMediaType).GetFields()
        For Each i In names
            Dim obj As Object = i.GetValue(Nothing)
            If TypeOf (obj) Is Guid AndAlso obj = guid Then
                Return i.Name
            End If
        Next
        Return "unknown"
    End Function

    Public Sub CloseDevice()
        SyncLock Me
            SafeRelease(mSourceReaderAsync)
            mSourceReaderAsync = Nothing
            mSymbolicLink = Nothing
        End SyncLock
    End Sub

    Public Shared Function StreamToBitmapSource(s As Stream) As BitmapSource
        Dim jpg As New JpegBitmapDecoder(s, BitmapCreateOptions.None, BitmapCacheOption.OnLoad) REM OnLoad很重要
        Return jpg.Frames(0)
    End Function

    Private bufptr As IntPtr
    Private imagesource As ImageSource
    Private dispatcher As System.Windows.Threading.Dispatcher
    Public Delegate Sub OnReadSampleHandler(hrStatus As HResult, dwStreamIndex As Integer, dwStreamFlags As MF_SOURCE_READER_FLAG, llTimestamp As Long, pSample As IMFSample)
    Public Event OnCaptureEvent As EventHandler(Of ImageSource)
    Public Function OnReadSample(hrStatus As HResult, dwStreamIndex As Integer, dwStreamFlags As MF_SOURCE_READER_FLAG, llTimestamp As Long, pSample As IMFSample) As HResult Implements IMFSourceReaderCallback.OnReadSample
        SyncLock Me
            Dim mediabuffer As IMFMediaBuffer = Nothing
            Try
                If Succeeded(hrStatus) Then
                    If pSample IsNot Nothing Then
                        Result = pSample.GetBufferByIndex(0, mediabuffer)
                    Else
                        GoTo done
                    End If
                Else
                    GoTo done
                End If

                Dim maxlen, curlen As Integer REM 图像大小max,jpg的大小cur,压缩jpg大小不定
                mediabuffer.GetMaxLength(maxlen)
                mediabuffer.GetCurrentLength(curlen)

                mediabuffer.Lock(bufptr, maxlen, curlen) REM 锁定 MediaBuffer
                Dim managebuf(maxlen - 1) As Byte
                System.Runtime.InteropServices.Marshal.Copy(bufptr, managebuf, 0, maxlen) REM 从MediaBuffer拷贝到自定的图片buffer

                REM 转换buffer成BitmapSource
                Dim stream As New MemoryStream(managebuf.Length)
                stream.Seek(0, SeekOrigin.Begin)
                stream.Write(managebuf, 0, managebuf.Length)
                stream.Seek(0, SeekOrigin.Begin)
                imagesource = StreamToBitmapSource(stream)
                stream.Close()
                mediabuffer.Unlock()
                dispatcher.Invoke(Sub()
                                      RaiseEvent OnCaptureEvent(Me, imagesource)
                                  End Sub)

done:

                REM RCW Runtime Callable Wrapper
                REM STAThread MTAThread
            Catch ex As Exception
                Console.WriteLine(ex.ToString())
            Finally
                SafeRelease(mediabuffer)
                SafeRelease(pSample)
            End Try
        End SyncLock
        Return 0
    End Function

    Public Sub Request()
        Result = mSourceReaderAsync.ReadSample(MF_SOURCE_READER.FirstVideoStream, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
    End Sub

    Public Function OnFlush(dwStreamIndex As Integer) As HResult Implements IMFSourceReaderCallback.OnFlush
        Return HResult.S_OK
    End Function

    Public Function OnEvent(dwStreamIndex As Integer, pEvent As IMFMediaEvent) As HResult Implements IMFSourceReaderCallback.OnEvent
        Return HResult.S_OK
    End Function

#Region "IDisposable Support"
    Private disposedValue As Boolean ' 要检测冗余调用

    ' IDisposable
    Protected Overridable Sub Dispose(disposing As Boolean)
        If Not disposedValue Then
            If disposing Then
                ' TODO: 释放托管状态(托管对象)。
            End If

            ' TODO: 释放未托管资源(未托管对象)并在以下内容中替代 Finalize()。
            ' TODO: 将大型字段设置为 null。
        End If
        disposedValue = True
    End Sub

    ' TODO: 仅当以上 Dispose(disposing As Boolean)拥有用于释放未托管资源的代码时才替代 Finalize()。
    'Protected Overrides Sub Finalize()
    '    ' 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
    '    Dispose(False)
    '    MyBase.Finalize()
    'End Sub

    ' Visual Basic 添加此代码以正确实现可释放模式。
    Public Sub Dispose() Implements IDisposable.Dispose
        ' 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
        Dispose(True)
        ' TODO: 如果在以上内容中替代了 Finalize(),则取消注释以下行。
        ' GC.SuppressFinalize(Me)
    End Sub
#End Region
End Class

重写OnReadSample函数,Mediafoundation这个COM组件是Both套件,但是VB.net只能使用STA套间,C#可以使用MTA套间,但是WPF是不允许用MTA的。(STA套间这个模式是好一点的吧?,这个问题弄了我好久,一开始还以为我的代码有问题)STA套间的组件属于创建的线程,OnReadSample不是主线程回调,而是其他线程回调进去的。所以不要在OnReadSample中使用主线程的组件接口,也不要在主线程使用其他线程的组件接口。但MTA是可以的。不然会出现一些异常System.__ComObject”的 COM 对象强制转换为接口类型。

MainWindow.xaml.vb的部分代码

 Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
        Width = SystemParameters.PrimaryScreenWidth
        Height = SystemParameters.PrimaryScreenHeight
        Left = 0
        Top = 0

        Dim device As MFDevice = MFDevice.GetVideoCaptureDevices()(0)
        mfcapasync = New MFCaptureAsync(Me.Dispatcher)
        AddHandler mfcapasync.OnCaptureEvent, AddressOf OnCapture
        mfcapasync.SetDevice(device, 1920, 1080, 30, "MJPG")
        mfcapasync.Request()
        Play()
    End Sub

    Public Sub OnCapture(sender As Object, e As ImageSource)
        grid.Background = New ImageBrush(e)
        mfcapasync.Request()
    End Sub

在开始的时候请求一帧,在捕捉到一帧时请求下一帧。
工程源码资源链接
http://download.csdn.net/download/llimite/10233371
点击打开链接


你可能感兴趣的:(WPF利用MediaFoundation打开摄像头捕捉图片)