diff --git a/QSoft.MediaCapture.sln b/QSoft.MediaCapture.sln index b07216d..b75b0aa 100644 --- a/QSoft.MediaCapture.sln +++ b/QSoft.MediaCapture.sln @@ -15,7 +15,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfAppNET6", "WpfAppNET6\Wp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfAppNET472", "WpfAppNET472\WpfAppNET472.csproj", "{51A0EDC7-191A-497A-9645-F234D142D5D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfAppNET8", "WpfAppNET8\WpfAppNET8.csproj", "{855F8FDB-0C0C-4FB9-9974-2AA011628087}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WpfAppNET8", "WpfAppNET8\WpfAppNET8.csproj", "{855F8FDB-0C0C-4FB9-9974-2AA011628087}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/QSoft.MediaCapture/QSoft.MediaCapture.csproj b/QSoft.MediaCapture/QSoft.MediaCapture.csproj index 78934ed..0818411 100644 --- a/QSoft.MediaCapture/QSoft.MediaCapture.csproj +++ b/QSoft.MediaCapture/QSoft.MediaCapture.csproj @@ -10,11 +10,25 @@ https://github.com/oven425/QSoft.MediaCapture.git git https://github.com/oven425/QSoft.MediaCapture + BEN HSU + Media Foundation; webcam + + + true + true + + + true + true + + + + @@ -29,4 +43,5 @@ \ + diff --git a/QSoft.MediaCapture/WebCam_MF.cs b/QSoft.MediaCapture/WebCam_MF.cs index 89d7809..740c658 100644 --- a/QSoft.MediaCapture/WebCam_MF.cs +++ b/QSoft.MediaCapture/WebCam_MF.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; -using System.Text; using System.Threading.Tasks; namespace QSoft.MediaCapture @@ -183,34 +182,20 @@ void DestroyCaptureEngine() try { hr = CreateD3DManager(); - if (hr != HRESULTS.S_OK) - { - return hr; - } + if (hr != HRESULTS.S_OK) return hr; hr = MFFunctions.MFCreateAttributes(out pAttributes, 1); - if (hr != HRESULTS.S_OK) - { - throw new MFException(); - } + if (hr != HRESULTS.S_OK) return hr; hr = pAttributes.SetUnknown(MFConstants.MF_CAPTURE_ENGINE_D3D_MANAGER, g_pDXGIMan); - if (hr != HRESULTS.S_OK) - { - throw new MFException(); - } - - pFactory = Activator.CreateInstance(Type.GetTypeFromCLSID(DirectN.MFConstants.CLSID_MFCaptureEngineClassFactory)) as IMFCaptureEngineClassFactory; + if (hr != HRESULTS.S_OK) return hr; + var tty = Type.GetTypeFromCLSID(DirectN.MFConstants.CLSID_MFCaptureEngineClassFactory, true); + if(tty == null) return hr; + pFactory = Activator.CreateInstance(tty) as IMFCaptureEngineClassFactory; object? o = null; hr = pFactory?.CreateInstance(DirectN.MFConstants.CLSID_MFCaptureEngine, typeof(IMFCaptureEngine).GUID, out o); - if (hr != HRESULTS.S_OK) - { - return hr; - } + if (hr != HRESULTS.S_OK) return hr; m_pEngine = o as IMFCaptureEngine; hr = m_pEngine?.Initialize(this, pAttributes, null, this.CaptureObj?.Object); - if (hr != HRESULTS.S_OK) - { - return hr; - } + if (hr != HRESULTS.S_OK) return hr; hr = await m_TaskInitialize.Task; } @@ -263,21 +248,35 @@ void SafeRelease(T? obj) where T : class return hr; } + + public HRESULT GetPreviewSize(out int width, out int height) + { + width = 0; height = 0; + if (m_pEngine == null) return HRESULTS.MF_E_NOT_INITIALIZED; + IMFCaptureSource? pSource; + IMFMediaType? pMediaType; + var hr = m_pEngine.GetSource(out pSource); + if (hr != HRESULTS.S_OK) return hr; + + + //// Configure the video format for the preview sink. + hr = pSource.GetCurrentDeviceMediaType((uint)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM.FOR_VIDEO_PREVIEW, out pMediaType); + if (hr != HRESULTS.S_OK) return hr; + pMediaType.GetUINT64(MFConstants.MF_MT_FRAME_SIZE, out var wh); + width = (int)(wh >> 32); + height = (int)(wh & 0xffffffff); + return HRESULTS.S_OK; + } + bool m_IsPreviewing = false; TaskCompletionSource? m_TaskStartPreview; async public Task StartPreview(IntPtr handle) { - if (m_pEngine == null) - { - return HRESULTS.MF_E_NOT_INITIALIZED; - } + if (m_pEngine == null) return HRESULTS.MF_E_NOT_INITIALIZED; m_TaskStartPreview = new TaskCompletionSource(); - if (m_IsPreviewing) - { - return HRESULTS.S_OK; - } + if (m_IsPreviewing) return HRESULTS.S_OK; IMFCaptureSink? pSink = null; IMFMediaType? pMediaType = null; @@ -338,49 +337,103 @@ async public Task StartPreview(IntPtr handle) return hr; } - HRESULT CloneVideoMediaType(IMFMediaType pSrcMediaType, Guid guidSubType, out IMFMediaType? ppNewMediaType) + async public Task StartPreview(IMFCaptureEngineOnSampleCallback samplecallback) { - ppNewMediaType = null; - var hr = MFFunctions.MFCreateMediaType(out var pNewMediaType); - if (hr.IsError) + if (m_pEngine == null) return HRESULTS.MF_E_NOT_INITIALIZED; + + m_TaskStartPreview = new TaskCompletionSource(); + + if (m_IsPreviewing) return HRESULTS.S_OK; + + IMFCaptureSink? pSink = null; + IMFMediaType? pMediaType = null; + IMFMediaType? pMediaType2 = null; + IMFCaptureSource? pSource = null; + int dwSinkStreamIndex = 0; + HRESULT hr = HRESULTS.S_OK; + try { - return hr; + // Get a pointer to the preview sink. + if (m_pPreview == null) + { + hr = m_pEngine.GetSink(MF_CAPTURE_ENGINE_SINK_TYPE.MF_CAPTURE_ENGINE_SINK_TYPE_PREVIEW, out pSink); + if (hr != HRESULTS.S_OK) return hr; + m_pPreview = pSink as IMFCapturePreviewSink; + if (m_pPreview == null) return HRESULTS.E_NOTIMPL; + + + hr = m_pEngine.GetSource(out pSource); + if (hr != HRESULTS.S_OK) return hr; + + + //// Configure the video format for the preview sink. + hr = pSource.GetCurrentDeviceMediaType((uint)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM.FOR_VIDEO_PREVIEW, out pMediaType); + if (hr != HRESULTS.S_OK) return hr; + + hr = CloneVideoMediaType(pMediaType, MFConstants.MFVideoFormat_RGB24, out pMediaType2); + if (hr != HRESULTS.S_OK || pMediaType2 == null) return hr; + + hr = pMediaType2.SetUINT32(MFConstants.MF_MT_ALL_SAMPLES_INDEPENDENT, 1); + if (hr != HRESULTS.S_OK) return hr; + + // Connect the video stream to the preview sink. + using var cm = new ComMemory(Marshal.SizeOf()); + hr = m_pPreview.AddStream((uint)MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM.FOR_VIDEO_PREVIEW, pMediaType2, null, cm.Pointer); + if (hr != HRESULTS.S_OK) return hr; + dwSinkStreamIndex = Marshal.ReadInt32(cm.Pointer); + } + if (samplecallback != null) + { + //uint w = 0; + //uint h = 0; + //MFFunctions1.MFGetAttributeSize(pMediaType2, MFConstants.MF_MT_FRAME_SIZE, out w, out h); + //m_PreviewBmp = new WriteableBitmap((int)w, (int)h, 96, 96, PixelFormats.Bgr24, null); + //m_PreviewCallback = new MFCaptureEngineOnSampleCallback(m_PreviewBmp); + hr = m_pPreview.SetSampleCallback((uint)dwSinkStreamIndex, samplecallback); + if (hr != HRESULTS.S_OK) return hr; + } + + hr = m_pEngine.StartPreview(); + hr = await m_TaskStartPreview.Task; + m_TaskStartPreview = null; + m_IsPreviewing = true; } - hr = pNewMediaType.SetGUID(MFConstants.MF_MT_MAJOR_TYPE, MFConstants.MFMediaType_Video); - if (hr.IsError) + finally { - return hr; + SafeRelease(pSink); + SafeRelease(pMediaType); + SafeRelease(pMediaType2); + SafeRelease(pSource); } + + return hr; + } + + + + HRESULT CloneVideoMediaType(IMFMediaType pSrcMediaType, Guid guidSubType, out IMFMediaType? ppNewMediaType) + { + ppNewMediaType = null; + var hr = MFFunctions.MFCreateMediaType(out var pNewMediaType); + if (hr.IsError) return hr; + hr = pNewMediaType.SetGUID(MFConstants.MF_MT_MAJOR_TYPE, MFConstants.MFMediaType_Video); + if (hr.IsError) return hr; + hr = pNewMediaType.SetGUID(MFConstants.MF_MT_SUBTYPE, guidSubType); - if (hr.IsError) - { - return hr; - } + if (hr.IsError) return hr; hr = CopyAttribute(pSrcMediaType, pNewMediaType, MFConstants.MF_MT_FRAME_SIZE); - if (hr.IsError) - { - return hr; - } + if (hr.IsError) return hr; hr = CopyAttribute(pSrcMediaType, pNewMediaType, MFConstants.MF_MT_FRAME_RATE); - if (hr.IsError) - { - return hr; - } + if (hr.IsError) return hr; hr = CopyAttribute(pSrcMediaType, pNewMediaType, MFConstants.MF_MT_PIXEL_ASPECT_RATIO); - if (hr.IsError) - { - return hr; - } + if (hr.IsError) return hr; hr = CopyAttribute(pSrcMediaType, pNewMediaType, MFConstants.MF_MT_INTERLACE_MODE); - if (hr.IsError) - { - return hr; - } + if (hr.IsError) return hr; ppNewMediaType = pNewMediaType; return hr; @@ -445,9 +498,5 @@ public enum MF_CAPTURE_ENGINE_PREFERRED_SOURCE_STREAM : uint FOR_METADATA = 0xfffffff6, MF_CAPTURE_ENGINE_MEDIASOURCE = 0xffffffff } - - public class MFException : Exception - { - public HRESULT Result { set; get; } - } } + diff --git a/QSoft.MediaCapture/WebCam_MF_Extension.cs b/QSoft.MediaCapture/WebCam_MF_Extension.cs new file mode 100644 index 0000000..c4beaeb --- /dev/null +++ b/QSoft.MediaCapture/WebCam_MF_Extension.cs @@ -0,0 +1,84 @@ +using DirectN; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace QSoft.MediaCapture.WPF +{ + public static class WebCam_MF_Extension + { + public static async Task StartPreview(this QSoft.MediaCapture.WebCam_MF src, Action action) + { + src.GetPreviewSize(out var width, out var height); + WriteableBitmap bmp = new WriteableBitmap(width, height,96,96, PixelFormats.Bgr24, null); + var hr = await src.StartPreview(new MFCaptureEngineOnSampleCallback(bmp)); + action?.Invoke(bmp); + return hr; + } + } + + public partial class MFCaptureEngineOnSampleCallback : IMFCaptureEngineOnSampleCallback + { +#if NET8_0_OR_GREATER + [LibraryImport("kernel32.dll", EntryPoint = "RtlCopyMemory", SetLastError = false)] + internal static partial void CopyMemory(IntPtr dest, IntPtr src, uint count); + +#else + [DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)] + internal static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); +#endif + + WriteableBitmap? m_Bmp; + public MFCaptureEngineOnSampleCallback(WriteableBitmap data) + { + this.m_Bmp = data; + } + int samplecount = 0; + object m_Lock = new object(); + System.Diagnostics.Stopwatch? m_StopWatch; + public HRESULT OnSample(IMFSample pSample) + { + if (System.Threading.Monitor.TryEnter(this.m_Lock)) + { + if (samplecount == 0) + { + m_StopWatch = System.Diagnostics.Stopwatch.StartNew(); + } + samplecount++; + if (samplecount > 100 && m_StopWatch != null) + { + m_StopWatch.Stop(); + var fps = samplecount / m_StopWatch.Elapsed.TotalSeconds; + System.Diagnostics.Trace.WriteLine($"fps:{fps}"); + samplecount = 0; + } + pSample.GetBufferByIndex(0, out var buf); + var ptr = buf.Lock(out var max, out var cur); + + m_Bmp?.Dispatcher.Invoke(() => + { + m_Bmp.Lock(); + CopyMemory(m_Bmp.BackBuffer, ptr, cur); + m_Bmp.AddDirtyRect(new System.Windows.Int32Rect(0, 0, m_Bmp.PixelWidth, m_Bmp.PixelHeight)); + m_Bmp.Unlock(); + }, System.Windows.Threading.DispatcherPriority.Background); + + buf.Unlock(); + Marshal.ReleaseComObject(buf); + Marshal.ReleaseComObject(pSample); + System.Threading.Monitor.Exit(this.m_Lock); + } + else + { + Marshal.ReleaseComObject(pSample); + } + + return HRESULTS.S_OK; + } + } +} diff --git a/WpfAppNET472/Window_NET472.xaml b/WpfAppNET472/Window_NET472.xaml index 9455750..3b69595 100644 --- a/WpfAppNET472/Window_NET472.xaml +++ b/WpfAppNET472/Window_NET472.xaml @@ -13,9 +13,10 @@ - + + diff --git a/WpfAppNET472/Window_NET472.xaml.cs b/WpfAppNET472/Window_NET472.xaml.cs index 46ddfc0..77c4177 100644 --- a/WpfAppNET472/Window_NET472.xaml.cs +++ b/WpfAppNET472/Window_NET472.xaml.cs @@ -14,6 +14,7 @@ using System.Windows.Shapes; using DirectN; using QSoft.MediaCapture; +using QSoft.MediaCapture.WPF; namespace WpfAppNET472 { @@ -36,7 +37,8 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) foreach(var oo in QSoft.MediaCapture.WebCam_MF.GetAllWebCams()) { await oo.InitCaptureEngine(); - await oo.StartPreview(host.Child.Handle); + //await oo.StartPreview(host.Child.Handle); + await oo.StartPreview(x => this.image.Source = x); this.m_MainUI.WebCams.Add(oo); } }