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
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
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