C# VB.net WPF利用MediaFoundation打开摄像头捕捉图片

来源:互联网 发布:python rest接口测试 编辑:程序博客网 时间:2024/06/08 13:27

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

REM MediaFoundation的.net 类库 http://mfnet.sourceforge.netImports MediaFoundationImports System.Runtime.InteropServicesImports System.IOImports System.DrawingImports System.Drawing.ImagingClass MainWindow    Private mediaSource As IMFMediaSource    Private attribute As IMFAttributes    Private activateDevices() As IMFActivate    Private deviceName As String REM 设备名字    ''' <summary>    ''' 打开摄像头设备    ''' </summary>    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    ''' <summary>    ''' 枚举摄像头支持的参数,选择匹配的参数捕捉图像    ''' </summary>    ''' <param name="width"></param>    ''' <param name="height"></param>    ''' <param name="fps"></param>    ''' <param name="typename"></param>    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    ''' <summary>    ''' 工具函数,根据GUID 找名字    ''' </summary>    ''' <param name="guid"></param>    ''' <returns></returns>    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    ''' <summary>    ''' 开始捕捉    ''' </summary>    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    ''' <summary>    ''' 停止捕捉    ''' </summary>    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    ''' <summary>    ''' 捕捉一帧    ''' </summary>    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()        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    ''' <summary>    ''' 流转成BitmapSource    ''' </summary>    ''' <param name="s">流</param>    ''' <returns></returns>    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 FunctionEnd Class

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

Imports MediaFoundationImports MediaFoundation.ReadWriteImports MediaFoundation.MiscImports System.IOPublic 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 RegionEnd ClassPublic 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 RegionEnd 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

在开始的时候请求一帧,在捕捉到一帧时请求下一帧。迟点再放出整个工程源码。


0 0