您的位置:首页 > 其它

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
点击打开链接
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: