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);
}
}