WPF利用MediaFoundation打开摄像头捕捉图片
2016-12-14 09:09
288 查看
主要代码 以同步的方式获得帧
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 设备名字 ''' <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() 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 ''' <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 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
点击打开链接
相关文章推荐
- 利用Flash获取摄像头视频进行动态捕捉
- windows mobile中使用DirectShow开发视频流之从摄像头流中捕捉一张图片
- 利用Qt与OpenCV简单实现摄像头图像捕捉
- Android学习之利用Intent打开图片、网页和拨打电话。
- 利用ffmpeg打开windows系统下面的摄像头源代码分析
- c#利用windowsapi捕捉屏幕图片
- 利用qt打开一张图片并转成灰度矩阵
- DirectShow:图片的抓取 zz从摄像头流中捕捉一张图片zzDirectshow中的视频捕捉zz
- (wpf)利用dll存放图片并在程序中读取
- 利用html5调用本地摄像头拍照上传图片
- wpf 利用 RenderTargetBitmap把控件保存为图片
- 利用ov511的webeye v2000摄像头实现YUV420P格式转RGB24格式来抓取一张图片
- c#,利用WPF的ScaleTransform和TranslateTransform实现图片的缩放效果
- 利用opencv打开摄像头读取视频
- 利用ov511的webeye v2000摄像头实现YUV420P格式转RGB24格式来抓取一张图片
- [转] 从摄像头流中捕捉一张图片
- 如何在C#下利用RichTextBox打开一个有文字格式和图片的Word文档
- 打开摄像头 拍照 存储拍照图片
- 在ubuntu下利用opencv打开摄像头
- 利用qt打开一张图片并转成灰度矩阵