Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
1. modify some code
2. add extenstion method for wpf preview
  • Loading branch information
oven425 committed Apr 10, 2024
1 parent c2e573f commit a4f1164
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 67 deletions.
2 changes: 1 addition & 1 deletion QSoft.MediaCapture.sln
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions QSoft.MediaCapture/QSoft.MediaCapture.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,25 @@
<RepositoryUrl>https://github.com/oven425/QSoft.MediaCapture.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/oven425/QSoft.MediaCapture</PackageProjectUrl>
<Authors>BEN HSU</Authors>
<PackageTags>Media Foundation; webcam</PackageTags>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net6.0-windows7.0'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'net8.0-windows7.0'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net460'">
<Reference Include="PresentationCore" />
<Reference Include="WindowsBase" />
<PackageReference Include="DirectN" Version="1.15.0.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net472'">
<Reference Include="PresentationCore" />
<Reference Include="WindowsBase" />
<PackageReference Include="DirectN" Version="1.15.0.2" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net6.0-windows7.0'">
Expand All @@ -29,4 +43,5 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
177 changes: 113 additions & 64 deletions QSoft.MediaCapture/WebCam_MF.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -263,21 +248,35 @@ void SafeRelease<T>(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<HRESULT>? m_TaskStartPreview;
async public Task<HRESULT> 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<HRESULT>();

if (m_IsPreviewing)
{
return HRESULTS.S_OK;
}
if (m_IsPreviewing) return HRESULTS.S_OK;

IMFCaptureSink? pSink = null;
IMFMediaType? pMediaType = null;
Expand Down Expand Up @@ -338,49 +337,103 @@ async public Task<HRESULT> StartPreview(IntPtr handle)
return hr;
}

HRESULT CloneVideoMediaType(IMFMediaType pSrcMediaType, Guid guidSubType, out IMFMediaType? ppNewMediaType)
async public Task<HRESULT> 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<HRESULT>();

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<uint>());
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;
Expand Down Expand Up @@ -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; }
}
}

84 changes: 84 additions & 0 deletions QSoft.MediaCapture/WebCam_MF_Extension.cs
Original file line number Diff line number Diff line change
@@ -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<HRESULT> StartPreview(this QSoft.MediaCapture.WebCam_MF src, Action<WriteableBitmap> 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;
}
}
}
3 changes: 2 additions & 1 deletion WpfAppNET472/Window_NET472.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<WindowsFormsHost x:Name="host">
<WindowsFormsHost x:Name="host" Visibility="Collapsed">
<winform:PictureBox SizeChanged="picture_SizeChanged" x:Name="picture"/>
</WindowsFormsHost>
<Image x:Name="image"></Image>
<WrapPanel Grid.Row="1" ItemWidth="100">
<Button x:Name="button_stratpreivew" Click="button_stratpreivew_Click">Start preview</Button>
<Button x:Name="button_stoppreview" Click="button_stoppreview_Click">Stop preview</Button>
Expand Down
4 changes: 3 additions & 1 deletion WpfAppNET472/Window_NET472.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using System.Windows.Shapes;
using DirectN;
using QSoft.MediaCapture;
using QSoft.MediaCapture.WPF;

namespace WpfAppNET472
{
Expand All @@ -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);
}
}
Expand Down

0 comments on commit a4f1164

Please sign in to comment.