diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 94dcc1e..1cdab57 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -26,19 +26,14 @@ jobs:
#-----------------------------------------------------------------------
# Setup environments
- # Setup environments
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
- 2.2.x
- 3.1.x
- 5.0.x
- 6.0.x
- 7.0.x
8.0.x
9.0.x
+ 10.0.x
- name: Setup NuGet package reference
run: |
@@ -47,7 +42,7 @@ jobs:
#-----------------------------------------------------------------------
# Build
-
+
- name: Build
run: dotnet build -p:Configuration=Release -p:Platform="Any CPU" -p:RestoreNoCache=True -p:BuildIdentifier=${GITHUB_RUN_NUMBER} FlashCap.sln
diff --git a/Directory.Build.props b/Directory.Build.props
index cc24217..2927a62 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -18,7 +18,7 @@
FlashCap
false
true
- $(NoWarn);CS1570;CS1591;CA1416;CS8981;NETSDK1215
+ $(NoWarn);CS1570;CS1591;CS8981;NETSDK1215
FlashCap
FlashCap
@@ -30,7 +30,7 @@
Apache-2.0
https://github.com/kekyo/FlashCap
FlashCap.100.png
- image;camera;capture;independent;multi-platform;frame-grabber;direct-show;video-for-windows;v4l2;windows;linux
+ image;camera;capture;independent;multi-platform;frame-grabber;media-foundation;v4l2;avfoundation;windows;linux;macos
.pdb
$(NoWarn);NU1605;NU1701;NU1803;NU1902;NU1903
diff --git a/FSharp.FlashCap/FSharp.FlashCap.fsproj b/FSharp.FlashCap/FSharp.FlashCap.fsproj
index b337f3d..587a7c5 100644
--- a/FSharp.FlashCap/FSharp.FlashCap.fsproj
+++ b/FSharp.FlashCap/FSharp.FlashCap.fsproj
@@ -1,13 +1,14 @@
- net48;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
+ true
true
+ true
-
-
+
diff --git a/FlashCap.Core/CaptureDevice.cs b/FlashCap.Core/CaptureDevice.cs
index 1dbca6b..92a1000 100644
--- a/FlashCap.Core/CaptureDevice.cs
+++ b/FlashCap.Core/CaptureDevice.cs
@@ -29,9 +29,7 @@ protected CaptureDevice(object identity, string name)
this.Name = name;
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public void Dispose() =>
_ = this.DisposeAsync().ConfigureAwait(false);
@@ -101,9 +99,7 @@ internal async Task InternalStopAsync(CancellationToken ct)
await this.OnStopAsync(ct);
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
internal void InternalOnCapture(
IntPtr pData, int size, long timestampMicroseconds, long frameIndex, PixelBuffer buffer) =>
this.OnCapture(pData, size, timestampMicroseconds, frameIndex, buffer);
diff --git a/FlashCap.Core/CaptureDeviceDescriptor.cs b/FlashCap.Core/CaptureDeviceDescriptor.cs
index f2ea72a..5722b44 100644
--- a/FlashCap.Core/CaptureDeviceDescriptor.cs
+++ b/FlashCap.Core/CaptureDeviceDescriptor.cs
@@ -20,9 +20,9 @@ namespace FlashCap;
public enum DeviceTypes
{
VideoForWindows,
- DirectShow,
V4L2,
AVFoundation,
+ MediaFoundation,
}
public enum TranscodeFormats
@@ -78,9 +78,7 @@ public override string ToString() =>
//////////////////////////////////////////////////////////////////////////
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
internal Task InternalOpenWithFrameProcessorAsync(
VideoCharacteristics characteristics,
TranscodeFormats transcodeFormat,
@@ -121,7 +119,8 @@ internal async Task InternalTakeOneShotAsync(
TranscodeFormats transcodeFormat,
CancellationToken ct)
{
- var tcs = new TaskCompletionSource();
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ using var registration = ct.Register(() => tcs.TrySetCanceled(ct));
using var device = await this.OnOpenWithFrameProcessorAsync(
characteristics, transcodeFormat,
@@ -138,9 +137,14 @@ internal async Task InternalTakeOneShotAsync(
ct);
await device.InternalStartAsync(ct);
- var image = await tcs.Task;
- await device.InternalStopAsync(ct);
-
- return image;
+ try
+ {
+ return await tcs.Task.ConfigureAwait(false);
+ }
+ finally
+ {
+ await device.InternalStopAsync(default).
+ ConfigureAwait(false);
+ }
}
}
diff --git a/FlashCap.Core/CaptureDevices.cs b/FlashCap.Core/CaptureDevices.cs
index 8426cb0..9a330ac 100644
--- a/FlashCap.Core/CaptureDevices.cs
+++ b/FlashCap.Core/CaptureDevices.cs
@@ -13,7 +13,6 @@
using FlashCap.Internal;
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Runtime.CompilerServices;
namespace FlashCap;
@@ -30,19 +29,16 @@ public CaptureDevices() :
public CaptureDevices(BufferPool defaultBufferPool) =>
this.DefaultBufferPool = defaultBufferPool;
- protected virtual IEnumerable OnEnumerateDescriptors() =>
- NativeMethods.CurrentPlatform switch
- {
- NativeMethods.Platforms.Windows =>
- new DirectShowDevices(this.DefaultBufferPool).OnEnumerateDescriptors().
- Concat(new VideoForWindowsDevices(this.DefaultBufferPool).OnEnumerateDescriptors()),
- NativeMethods.Platforms.Linux =>
- new V4L2Devices().OnEnumerateDescriptors(),
- NativeMethods.Platforms.MacOS =>
- new AVFoundationDevices().OnEnumerateDescriptors(),
- _ =>
- ArrayEx.Empty(),
- };
+ protected virtual IEnumerable OnEnumerateDescriptors()
+ {
+ if (OperatingSystem.IsWindows())
+ return new MediaFoundationDevices(this.DefaultBufferPool).OnEnumerateDescriptors();
+ if (OperatingSystem.IsLinux())
+ return new V4L2Devices(this.DefaultBufferPool).OnEnumerateDescriptors();
+ if (OperatingSystem.IsMacOS())
+ return new AVFoundationDevices(this.DefaultBufferPool).OnEnumerateDescriptors();
+ return ArrayEx.Empty();
+ }
internal IEnumerable InternalEnumerateDescriptors() =>
this.OnEnumerateDescriptors();
diff --git a/FlashCap.Core/Devices/AVFoundationDevice.cs b/FlashCap.Core/Devices/AVFoundationDevice.cs
index 8724472..f932038 100644
--- a/FlashCap.Core/Devices/AVFoundationDevice.cs
+++ b/FlashCap.Core/Devices/AVFoundationDevice.cs
@@ -12,6 +12,7 @@
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
using FlashCap.Internal;
@@ -23,6 +24,7 @@
namespace FlashCap.Devices;
+[SupportedOSPlatform("macos")]
public sealed class AVFoundationDevice : CaptureDevice
{
private readonly string uniqueID;
@@ -54,7 +56,11 @@ protected override async Task OnDisposeAsync()
this.deviceOutput?.Dispose();
this.queue?.Dispose();
- Marshal.FreeHGlobal(this.bitmapHeader);
+ if (this.bitmapHeader != IntPtr.Zero)
+ {
+ NativeMethods.FreeMemory(this.bitmapHeader);
+ this.bitmapHeader = IntPtr.Zero;
+ }
if (frameProcessor is not null)
{
@@ -160,7 +166,11 @@ format.FormatDescription.Dimensions is var dimensions &&
}
catch
{
- NativeMethods.FreeMemory(this.bitmapHeader);
+ if (this.bitmapHeader != IntPtr.Zero)
+ {
+ NativeMethods.FreeMemory(this.bitmapHeader);
+ this.bitmapHeader = IntPtr.Zero;
+ }
throw;
}
}
diff --git a/FlashCap.Core/Devices/AVFoundationDeviceDescriptor.cs b/FlashCap.Core/Devices/AVFoundationDeviceDescriptor.cs
index a270dee..b017fcd 100644
--- a/FlashCap.Core/Devices/AVFoundationDeviceDescriptor.cs
+++ b/FlashCap.Core/Devices/AVFoundationDeviceDescriptor.cs
@@ -8,11 +8,13 @@
//
////////////////////////////////////////////////////////////////////////////
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace FlashCap.Devices;
+[SupportedOSPlatform("macos")]
public sealed class AVFoundationDeviceDescriptor : CaptureDeviceDescriptor
{
private readonly string uniqueId;
diff --git a/FlashCap.Core/Devices/AVFoundationDevices.cs b/FlashCap.Core/Devices/AVFoundationDevices.cs
index 8d55537..94a9b6b 100644
--- a/FlashCap.Core/Devices/AVFoundationDevices.cs
+++ b/FlashCap.Core/Devices/AVFoundationDevices.cs
@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Runtime.Versioning;
using System.Threading.Tasks;
using FlashCap.Internal;
using FlashCap.Utilities;
@@ -18,6 +19,7 @@
namespace FlashCap.Devices;
+[SupportedOSPlatform("macos")]
public sealed class AVFoundationDevices : CaptureDevices
{
public AVFoundationDevices() :
diff --git a/FlashCap.Core/Devices/DirectShowDevice.cs b/FlashCap.Core/Devices/DirectShowDevice.cs
deleted file mode 100644
index 345a3b7..0000000
--- a/FlashCap.Core/Devices/DirectShowDevice.cs
+++ /dev/null
@@ -1,370 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using FlashCap.Internal;
-using System;
-using System.Diagnostics;
-using System.Linq;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace FlashCap.Devices;
-
-public sealed class DirectShowDevice :
- CaptureDevice
-{
- private sealed class SampleGrabberSink :
- NativeMethods_DirectShow.ISampleGrabberCB
- {
- private DirectShowDevice parent;
- private FrameProcessor frameProcessor;
- private long frameIndex;
-
- public SampleGrabberSink(
- DirectShowDevice parent,
- FrameProcessor frameProcessor)
- {
- this.parent = parent;
- this.frameProcessor = frameProcessor;
- }
-
- public void ResetFrameIndex() =>
- this.frameIndex = 0;
-
- // whichMethodToCallback: 0
- [PreserveSig] public int SampleCB(
- double sampleTime, NativeMethods_DirectShow.IMediaSample sample) =>
- unchecked((int)0x80004001); // E_NOTIMPL
-
- // whichMethodToCallback: 1
- [PreserveSig] public int BufferCB(
- double sampleTime, IntPtr pBuffer, int bufferLen)
- {
- // HACK: Avoid stupid camera devices...
- if (bufferLen >= 64)
- {
- try
- {
- this.frameProcessor.OnFrameArrived(
- this.parent, pBuffer, bufferLen,
- (long)(sampleTime * 1_000_000),
- this.frameIndex++);
- }
- // DANGER: Stop leaking exception around outside of unmanaged area...
- catch (Exception ex)
- {
- Trace.WriteLine(ex);
- }
- }
- return 0;
- }
- }
-
- // DirectShow objects are sandboxed in the working context.
- private IndependentSingleApartmentContext? workingContext = new();
- private TranscodeFormats transcodeFormat;
- private FrameProcessor frameProcessor;
- private NativeMethods_DirectShow.IGraphBuilder? graphBuilder;
- private SampleGrabberSink? sampleGrabberSink;
- private IntPtr pBih;
-
-#pragma warning disable CS8618
- internal DirectShowDevice(object identity, string name) :
- base(identity, name)
-#pragma warning restore CS8618
- {
- }
-
- protected override Task OnInitializeAsync(
- VideoCharacteristics characteristics,
- TranscodeFormats transcodeFormat,
- FrameProcessor frameProcessor,
- CancellationToken ct)
- {
- var devicePath = (string)this.Identity;
-
- this.transcodeFormat = transcodeFormat;
- this.frameProcessor = frameProcessor;
-
- return this.workingContext!.InvokeAsync(() =>
- {
- if (NativeMethods_DirectShow.EnumerateDeviceMoniker(
- NativeMethods_DirectShow.CLSID_VideoInputDeviceCategory).
- Where(moniker =>
- moniker.GetPropertyBag() is { } pb &&
- pb.SafeReleaseBlock(pb =>
- pb.GetValue("DevicePath", default(string))?.Trim() is { } dp &&
- dp.Equals(devicePath))).
- Collect(moniker =>
- moniker.BindToObject(null, null, in NativeMethods_DirectShow.IID_IBaseFilter, out var captureSource) == 0 ?
- captureSource as NativeMethods_DirectShow.IBaseFilter : null).
- FirstOrDefault() is { } captureSource)
- {
- try
- {
- if (captureSource.EnumeratePins().
- Collect(pin =>
- pin.GetPinInfo() is { } pinInfo &&
- pinInfo.dir == NativeMethods_DirectShow.PIN_DIRECTION.Output ?
- pin : null).
- SelectMany(pin =>
- pin.EnumerateFormats().
- Collect(format =>
- {
- var vfc = format.CreateVideoCharacteristics();
- return characteristics.Equals(vfc) ?
- new { pin, format, vfc } : null;
- })).
- FirstOrDefault() is { } entry)
- {
- this.Characteristics = entry.vfc;
- entry.pin.SetFormat(entry.format);
- }
- else
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set video format: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- this.graphBuilder = NativeMethods_DirectShow.CreateGraphBuilder();
- if (this.graphBuilder.AddFilter(captureSource, "Capture source") < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't add capture source: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- var sampleGrabber = NativeMethods_DirectShow.CreateSampleGrabber();
- if (this.graphBuilder.AddFilter(sampleGrabber, "Sample grabber") < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't add sample grabber: DevicePath={devicePath}");
- }
-
- if (sampleGrabber.SetOneShot(false) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set oneshot mode: DevicePath={devicePath}");
- }
- if (sampleGrabber.SetBufferSamples(true) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't start sampling: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- var nullRenderer = NativeMethods_DirectShow.CreateNullRenderer();
- if (this.graphBuilder.AddFilter(nullRenderer, "Null renderer") < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't add null renderer: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- var captureGraphBuilder = NativeMethods_DirectShow.CreateCaptureGraphBuilder();
- if (captureGraphBuilder.SetFiltergraph(this.graphBuilder) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set graph builder: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- if (captureGraphBuilder.RenderStream(
- in NativeMethods_DirectShow.PIN_CATEGORY_CAPTURE,
- in NativeMethods_DirectShow.MEDIATYPE_Video,
- captureSource,
- sampleGrabber,
- nullRenderer) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set render stream: DevicePath={devicePath}");
- }
-
- ///////////////////////////////
-
- if (sampleGrabber.GetConnectedMediaType(out var mediaType) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't get media type: DevicePath={devicePath}");
- }
-
- this.pBih = mediaType.AllocateAndGetBih();
-
- ///////////////////////////////
-
- this.sampleGrabberSink =
- new SampleGrabberSink(this, frameProcessor);
- if (sampleGrabber.SetCallback(this.sampleGrabberSink, 1) < 0)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't get grabbing media type: DevicePath={devicePath}");
- }
- }
- catch
- {
- if (this.graphBuilder != null)
- {
- Marshal.ReleaseComObject(this.graphBuilder);
- }
- throw;
- }
- }
- else
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't find a device: DevicePath={devicePath}");
- }
- }, ct);
- }
-
- ~DirectShowDevice()
- {
- if (this.pBih != IntPtr.Zero)
- {
- NativeMethods.FreeMemory(this.pBih);
- this.pBih = IntPtr.Zero;
- }
- }
-
- protected override async Task OnDisposeAsync()
- {
- if (this.graphBuilder != null)
- {
- await this.frameProcessor.DisposeAsync().
- ConfigureAwait(false);
-
- await this.OnStopAsync(default).
- ConfigureAwait(false);
-
- await this.workingContext!.InvokeAsync(() =>
- {
- Marshal.ReleaseComObject(this.graphBuilder);
- this.graphBuilder = null!;
- this.sampleGrabberSink = null!;
- NativeMethods.FreeMemory(this.pBih);
- this.pBih = IntPtr.Zero;
- }, default);
-
- this.workingContext.Dispose();
- this.workingContext = null;
- }
- }
-
- protected override Task OnStartAsync(CancellationToken ct)
- {
- if (!this.IsRunning)
- {
- return this.workingContext!.InvokeAsync(() =>
- {
- if (this.graphBuilder is NativeMethods_DirectShow.IMediaControl mediaControl)
- {
- this.sampleGrabberSink!.ResetFrameIndex();
-
- mediaControl.Run();
- this.IsRunning = true;
- }
- else
- {
- throw new InvalidOperationException();
- }
- }, ct);
- }
- else
- {
- return TaskCompat.CompletedTask;
- }
- }
-
- protected override Task OnStopAsync(CancellationToken ct)
- {
- if (this.IsRunning)
- {
- return this.workingContext!.InvokeAsync(() =>
- {
- if (this.graphBuilder is NativeMethods_DirectShow.IMediaControl mediaControl)
- {
- this.IsRunning = false;
- mediaControl.Stop();
- }
- else
- {
- throw new InvalidOperationException();
- }
- }, ct);
- }
- else
- {
- return TaskCompat.CompletedTask;
- }
- }
-
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
- protected override void OnCapture(
- IntPtr pData, int size,
- long timestampMicroseconds, long frameIndex,
- PixelBuffer buffer) =>
- buffer.CopyIn(this.pBih, pData, size, timestampMicroseconds, frameIndex, this.transcodeFormat);
-
- /////////////////////////////////////////////////////////////////////////////////////////////////
-
- // Property page implementation.
- // https://learn.microsoft.com/en-us/windows/win32/directshow/displaying-a-filters-property-pages
-
- public override bool HasPropertyPage => true;
-
- protected override Task OnShowPropertyPageAsync(
- IntPtr parentWindow, CancellationToken ct) =>
- this.workingContext!.InvokeAsync(() =>
- {
- var devicePath = (string)this.Identity;
-
- if (NativeMethods_DirectShow.EnumerateDeviceMoniker(
- NativeMethods_DirectShow.CLSID_VideoInputDeviceCategory).
- Where(moniker =>
- moniker.GetPropertyBag() is { } pb &&
- pb.SafeReleaseBlock(pb =>
- pb.GetValue("DevicePath", default(string))?.Trim() is { } dp &&
- dp.Equals(devicePath))).
- Collect(moniker =>
- moniker.BindToObject(null, null, in NativeMethods_DirectShow.IID_IBaseFilter, out var captureSource) == 0 ?
- captureSource as NativeMethods_DirectShow.IBaseFilter : null).
- FirstOrDefault() is { } captureSource)
- {
- if (captureSource is NativeMethods_DirectShow.ISpecifyPropertyPages specifyPropertyPages &&
- captureSource is object sourceAsObject &&
- specifyPropertyPages.GetPages(out var pPages) == 0)
- {
- try
- {
- NativeMethods_DirectShow.OleCreatePropertyFrame(
- parentWindow, 0, 0, this.Name, 1, ref sourceAsObject,
- pPages.cElems, pPages.pElems, 0, 0, IntPtr.Zero);
-
- return true;
- }
- finally
- {
- Marshal.FreeCoTaskMem(pPages.pElems);
- }
- }
- }
-
- return false;
- }, ct);
-}
diff --git a/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs b/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs
deleted file mode 100644
index 01f36a1..0000000
--- a/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace FlashCap.Devices;
-
-public sealed class DirectShowDeviceDescriptor : CaptureDeviceDescriptor
-{
- private readonly string devicePath;
-
- internal DirectShowDeviceDescriptor(
- string devicePath, string name, string description,
- VideoCharacteristics[] characteristics,
- BufferPool defaultBufferPool) :
- base(name, description, characteristics, defaultBufferPool) =>
- this.devicePath = devicePath;
-
- public override object Identity =>
- this.devicePath;
-
- public override DeviceTypes DeviceType =>
- DeviceTypes.DirectShow;
-
- protected override Task OnOpenWithFrameProcessorAsync(
- VideoCharacteristics characteristics,
- TranscodeFormats transcodeFormat,
- FrameProcessor frameProcessor,
- CancellationToken ct) =>
- this.InternalOnOpenWithFrameProcessorAsync(
- new DirectShowDevice(this.devicePath, this.Name),
- characteristics, transcodeFormat, frameProcessor, ct);
-}
diff --git a/FlashCap.Core/Devices/DirectShowDevices.cs b/FlashCap.Core/Devices/DirectShowDevices.cs
deleted file mode 100644
index b5d97fd..0000000
--- a/FlashCap.Core/Devices/DirectShowDevices.cs
+++ /dev/null
@@ -1,59 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using FlashCap.Internal;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace FlashCap.Devices;
-
-public sealed class DirectShowDevices : CaptureDevices
-{
- public DirectShowDevices() :
- this(new DefaultBufferPool())
- {
- }
-
- public DirectShowDevices(BufferPool defaultBufferPool) :
- base(defaultBufferPool)
- {
- }
-
- protected override IEnumerable OnEnumerateDescriptors() =>
- NativeMethods_DirectShow.EnumerateDeviceMoniker(
- NativeMethods_DirectShow.CLSID_VideoInputDeviceCategory).
- Collect(moniker => moniker.GetPropertyBag() is { } pb ?
- pb.SafeReleaseBlock(pb =>
- pb.GetValue("FriendlyName", default(string))?.Trim() is { } n &&
- (string.IsNullOrEmpty(n) ? "Unknown" : n!) is { } name &&
- pb.GetValue("DevicePath", default(string))?.Trim() is { } devicePath ?
- (CaptureDeviceDescriptor)new DirectShowDeviceDescriptor(
- devicePath, name,
- pb.GetValue("Description", default(string))?.Trim() ?? $"{name} (DirectShow)",
- moniker.BindToObject(
- null, null, in NativeMethods_DirectShow.IID_IBaseFilter, out var cs) == 0 &&
- cs is NativeMethods_DirectShow.IBaseFilter captureSource ?
- captureSource.SafeReleaseBlock(
- captureSource => captureSource.EnumeratePins().
- Collect(pin =>
- pin.GetPinInfo() is { } pinInfo &&
- pinInfo.dir == NativeMethods_DirectShow.PIN_DIRECTION.Output ?
- pin : null).
- SelectMany(pin =>
- pin.EnumerateFormats().
- Collect(format => format.CreateVideoCharacteristics())).
- Distinct().
- OrderByDescending(vc => vc).
- ToArray()) :
- ArrayEx.Empty(),
- this.DefaultBufferPool) :
- null) :
- null);
-}
diff --git a/FlashCap.Core/Devices/MediaFoundationDevice.cs b/FlashCap.Core/Devices/MediaFoundationDevice.cs
new file mode 100644
index 0000000..e5a5d65
--- /dev/null
+++ b/FlashCap.Core/Devices/MediaFoundationDevice.cs
@@ -0,0 +1,702 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using System.Threading;
+using System.Threading.Tasks;
+using FlashCap.Internal;
+using FlashCap.Internal.MediaFoundation;
+
+namespace FlashCap.Devices;
+
+[SupportedOSPlatform("windows")]
+public sealed class MediaFoundationDevice : CaptureDevice
+{
+ private const int VideoStreamIndex = 0;
+
+ private readonly TimestampCounter counter = new();
+ private readonly string symbolicLink;
+
+ private VideoCharacteristics requestedCharacteristics = null!;
+ private TranscodeFormats transcodeFormat;
+ private FrameProcessor frameProcessor = null!;
+ private IMFSourceReader? sourceReader;
+ private IntPtr sourceReaderPointer;
+ private IntPtr mediaSourcePointer;
+ private IntPtr bitmapHeader;
+ private int sourceStride;
+ private int targetStride;
+ private int frameRowBytes;
+ private bool sourceBottomUp;
+ private bool targetBottomUp;
+ private byte[]? repackBuffer;
+ private Task? task;
+ private CancellationTokenSource? stopping;
+ private long frameIndex;
+
+ internal MediaFoundationDevice(string symbolicLink, string name) :
+ base(symbolicLink, name) =>
+ this.symbolicLink = symbolicLink;
+
+ private static unsafe IntPtr CreateBitmapHeader(
+ VideoCharacteristics characteristics,
+ MediaFoundationMediaTypes.FormatInfo formatInfo)
+ {
+ var pointer = NativeMethods.AllocateMemory((IntPtr)sizeof(NativeMethods.BITMAPINFOHEADER));
+ try
+ {
+ var bitmapInfoHeader = (NativeMethods.BITMAPINFOHEADER*)pointer.ToPointer();
+ bitmapInfoHeader->biSize = sizeof(NativeMethods.BITMAPINFOHEADER);
+ bitmapInfoHeader->biCompression = formatInfo.Compression;
+ bitmapInfoHeader->biPlanes = 1;
+ bitmapInfoHeader->biBitCount = formatInfo.BitCount;
+ bitmapInfoHeader->biWidth = characteristics.Width;
+ bitmapInfoHeader->biHeight = characteristics.Height;
+ bitmapInfoHeader->biSizeImage = bitmapInfoHeader->CalculateImageSize();
+ return pointer;
+ }
+ catch
+ {
+ NativeMethods.FreeMemory(pointer);
+ throw;
+ }
+ }
+
+ private static bool IsRgbLike(PixelFormats pixelFormat) =>
+ pixelFormat switch
+ {
+ PixelFormats.RGB8 => true,
+ PixelFormats.RGB15 => true,
+ PixelFormats.RGB16 => true,
+ PixelFormats.RGB24 => true,
+ PixelFormats.RGB32 => true,
+ PixelFormats.ARGB32 => true,
+ _ => false,
+ };
+
+ private static bool IsCompressed(PixelFormats pixelFormat) =>
+ pixelFormat switch
+ {
+ PixelFormats.JPEG => true,
+ PixelFormats.PNG => true,
+ _ => false,
+ };
+
+ private static int AlignToDWord(int value) =>
+ (value + 3) & ~3;
+
+ private static int CalculateRowBytes(VideoCharacteristics characteristics) =>
+ characteristics.PixelFormat switch
+ {
+ PixelFormats.RGB8 => characteristics.Width,
+ PixelFormats.RGB15 => characteristics.Width * 2,
+ PixelFormats.RGB16 => characteristics.Width * 2,
+ PixelFormats.RGB24 => characteristics.Width * 3,
+ PixelFormats.RGB32 => characteristics.Width * 4,
+ PixelFormats.ARGB32 => characteristics.Width * 4,
+ PixelFormats.UYVY => characteristics.Width * 2,
+ PixelFormats.YUYV => characteristics.Width * 2,
+ PixelFormats.NV12 => characteristics.Width,
+ _ => 0,
+ };
+
+ private static int CalculateTargetStride(VideoCharacteristics characteristics) =>
+ IsRgbLike(characteristics.PixelFormat) ?
+ AlignToDWord(CalculateRowBytes(characteristics)) :
+ CalculateRowBytes(characteristics);
+
+ private static IntPtr FindActivate(string symbolicLink)
+ {
+ foreach (var activatePointer in MediaFoundationDevices.EnumerateDeviceActivates())
+ {
+ var release = true;
+ try
+ {
+ var activate = MediaFoundationCom.Wrap(activatePointer);
+ var currentSymbolicLink = MediaFoundationCom.GetAllocatedString(
+ activate,
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK).Trim();
+ if (string.Equals(currentSymbolicLink, symbolicLink, StringComparison.OrdinalIgnoreCase))
+ {
+ release = false;
+ return activatePointer;
+ }
+ }
+ finally
+ {
+ if (release)
+ {
+ MediaFoundationCom.Release(activatePointer);
+ }
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+
+ private static IntPtr FindMediaType(
+ IMFSourceReader sourceReader,
+ VideoCharacteristics characteristics,
+ out MediaFoundationMediaTypes.FormatInfo formatInfo)
+ {
+ for (var index = 0; ; index++)
+ {
+ var hr = sourceReader.GetNativeMediaType(
+ VideoStreamIndex,
+ index,
+ out var mediaTypePointer);
+ if (hr == NativeMethods_MediaFoundation.MF_E_NO_MORE_TYPES)
+ {
+ break;
+ }
+ if (hr < 0)
+ {
+ break;
+ }
+ if (mediaTypePointer == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ var release = true;
+ try
+ {
+ var mediaType = MediaFoundationCom.Wrap(mediaTypePointer);
+ if (MediaFoundationMediaTypes.TryCreateVideoCharacteristics(
+ mediaType,
+ out var currentCharacteristics,
+ out formatInfo) &&
+ characteristics.Equals(currentCharacteristics))
+ {
+ release = false;
+ return mediaTypePointer;
+ }
+ }
+ finally
+ {
+ if (release)
+ {
+ MediaFoundationCom.Release(mediaTypePointer);
+ }
+ }
+ }
+
+ formatInfo = default;
+ return IntPtr.Zero;
+ }
+
+ protected override Task OnInitializeAsync(
+ VideoCharacteristics characteristics,
+ TranscodeFormats transcodeFormat,
+ FrameProcessor frameProcessor,
+ CancellationToken ct)
+ {
+ ct.ThrowIfCancellationRequested();
+
+ MediaFoundationSession.EnsureStarted();
+
+ this.Characteristics = characteristics;
+ this.requestedCharacteristics = characteristics;
+ this.transcodeFormat = transcodeFormat;
+ this.frameProcessor = frameProcessor;
+
+ return TaskCompat.CompletedTask;
+ }
+
+ private void InitializeMediaFoundationObjects()
+ {
+ MediaFoundationSession.EnsureStarted();
+
+ var activatePointer = FindActivate(this.symbolicLink);
+ if (activatePointer == IntPtr.Zero)
+ {
+ throw new ArgumentException(
+ $"FlashCap: Couldn't find a Media Foundation device: SymbolicLink={this.symbolicLink}");
+ }
+
+ try
+ {
+ var activate = MediaFoundationCom.Wrap(activatePointer);
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ activate.ActivateObject(
+ in NativeMethods_MediaFoundation.IID_IMFMediaSource,
+ out this.mediaSourcePointer),
+ "IMFActivate.ActivateObject(IMFMediaSource)");
+
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ NativeMethods_MediaFoundation.MFCreateSourceReaderFromMediaSource(
+ this.mediaSourcePointer,
+ IntPtr.Zero,
+ out this.sourceReaderPointer),
+ nameof(NativeMethods_MediaFoundation.MFCreateSourceReaderFromMediaSource));
+
+ this.sourceReader = MediaFoundationCom.Wrap(this.sourceReaderPointer);
+
+ this.sourceReader.SetStreamSelection(
+ NativeMethods_MediaFoundation.MF_SOURCE_READER_ALL_STREAMS,
+ 0);
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ this.sourceReader.SetStreamSelection(
+ VideoStreamIndex,
+ 1),
+ "IMFSourceReader.SetStreamSelection(video stream)");
+
+ var mediaTypePointer = FindMediaType(
+ this.sourceReader,
+ this.requestedCharacteristics,
+ out var formatInfo);
+ if (mediaTypePointer == IntPtr.Zero)
+ {
+ throw new ArgumentException(
+ $"FlashCap: Couldn't set Media Foundation video format: SymbolicLink={this.symbolicLink}");
+ }
+
+ try
+ {
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ this.sourceReader.SetCurrentMediaType(
+ VideoStreamIndex,
+ IntPtr.Zero,
+ mediaTypePointer),
+ "IMFSourceReader.SetCurrentMediaType");
+
+ this.ConfigureFrameLayout(mediaTypePointer, formatInfo);
+ this.bitmapHeader = CreateBitmapHeader(this.requestedCharacteristics, formatInfo);
+ }
+ finally
+ {
+ MediaFoundationCom.Release(mediaTypePointer);
+ }
+ }
+ catch
+ {
+ this.ReleaseMediaFoundationObjects();
+ throw;
+ }
+ finally
+ {
+ MediaFoundationCom.Release(activatePointer);
+ }
+ }
+
+ private void ConfigureFrameLayout(
+ IntPtr mediaTypePointer,
+ MediaFoundationMediaTypes.FormatInfo formatInfo)
+ {
+ var mediaType = MediaFoundationCom.Wrap(mediaTypePointer);
+
+ this.frameRowBytes = CalculateRowBytes(this.requestedCharacteristics);
+ this.targetStride = CalculateTargetStride(this.requestedCharacteristics);
+ this.targetBottomUp = IsRgbLike(formatInfo.PixelFormat);
+
+ if (!IsCompressed(formatInfo.PixelFormat) &&
+ mediaType.GetUINT32(
+ in NativeMethods_MediaFoundation.MF_MT_DEFAULT_STRIDE,
+ out var defaultStride) >= 0 &&
+ defaultStride != 0)
+ {
+ this.sourceBottomUp = defaultStride < 0;
+ this.sourceStride = Math.Abs(defaultStride);
+ }
+ else
+ {
+ this.sourceBottomUp = false;
+ this.sourceStride = this.targetStride;
+ }
+ }
+
+ protected override async Task OnStartAsync(CancellationToken ct)
+ {
+ if (this.IsRunning)
+ {
+ return;
+ }
+
+ this.frameIndex = 0;
+ this.counter.Restart();
+ this.stopping = CancellationTokenSource.CreateLinkedTokenSource(ct);
+ this.IsRunning = true;
+ var started = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ this.task = Task.Factory.StartNew(
+ () => this.ThreadEntry(started),
+ this.stopping.Token,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default);
+
+ try
+ {
+ await started.Task.WaitAsync(ct).
+ ConfigureAwait(false);
+ }
+ catch
+ {
+ await this.OnStopAsync(default).
+ ConfigureAwait(false);
+ throw;
+ }
+ }
+
+ protected override async Task OnStopAsync(CancellationToken ct)
+ {
+ if (!this.IsRunning && this.task == null)
+ {
+ return;
+ }
+
+ this.IsRunning = false;
+ var stopping = Interlocked.Exchange(ref this.stopping, null);
+ var task = Interlocked.Exchange(ref this.task, null);
+
+ stopping?.Cancel();
+ try
+ {
+ this.sourceReader?.Flush(NativeMethods_MediaFoundation.MF_SOURCE_READER_ALL_STREAMS);
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+
+ if (task != null)
+ {
+ await task.ConfigureAwait(false);
+ }
+
+ stopping?.Dispose();
+ }
+
+ private void ThreadEntry(TaskCompletionSource started)
+ {
+ var initializedCom = false;
+ var coInitializeHr = NativeMethods.CoInitializeEx(IntPtr.Zero, NativeMethods.COINIT.MULTITHREADED);
+ if (coInitializeHr >= 0)
+ {
+ initializedCom = true;
+ }
+
+ try
+ {
+ this.InitializeMediaFoundationObjects();
+ started.TrySetResult();
+
+ var sourceReader = this.sourceReader;
+ if (sourceReader == null)
+ {
+ return;
+ }
+
+ while (this.IsRunning)
+ {
+ var hr = sourceReader.ReadSample(
+ VideoStreamIndex,
+ 0,
+ out _,
+ out var streamFlags,
+ out var timestamp,
+ out var samplePointer);
+ if (hr < 0)
+ {
+ Trace.WriteLine($"FlashCap: IMFSourceReader.ReadSample failed: HR=0x{hr:x8}");
+ break;
+ }
+
+ if ((streamFlags & NativeMethods_MediaFoundation.MF_SOURCE_READERF_ENDOFSTREAM) != 0)
+ {
+ break;
+ }
+
+ if ((streamFlags &
+ (NativeMethods_MediaFoundation.MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED |
+ NativeMethods_MediaFoundation.MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED |
+ NativeMethods_MediaFoundation.MF_SOURCE_READERF_STREAMTICK)) != 0)
+ {
+ MediaFoundationCom.Release(samplePointer);
+ continue;
+ }
+
+ if (samplePointer == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ this.ProcessSample(samplePointer, timestamp);
+ }
+ }
+ catch (Exception ex)
+ {
+ started.TrySetException(ex);
+ Trace.WriteLine(ex);
+ }
+ finally
+ {
+ this.IsRunning = false;
+ this.ReleaseMediaFoundationObjects();
+
+ if (initializedCom)
+ {
+ NativeMethods.CoUninitialize();
+ }
+ }
+ }
+
+ private unsafe void ProcessSample(IntPtr samplePointer, long timestamp)
+ {
+ var bufferPointer = IntPtr.Zero;
+ try
+ {
+ if (MediaFoundationNativeCom.ConvertToContiguousBuffer(samplePointer, out bufferPointer) < 0 ||
+ bufferPointer == IntPtr.Zero)
+ {
+ return;
+ }
+
+ if (MediaFoundationNativeCom.Lock(bufferPointer, out var data, out _, out var currentLength) < 0)
+ {
+ return;
+ }
+
+ try
+ {
+ if (!this.TryRepackFrame(
+ data,
+ currentLength,
+ out var repacked,
+ out var frameLength))
+ {
+ return;
+ }
+
+ if (repacked is { })
+ {
+ fixed (byte* repackedData = repacked)
+ {
+ this.frameProcessor.OnFrameArrived(
+ this,
+ (IntPtr)repackedData,
+ frameLength,
+ timestamp > 0 ? timestamp / 10 : this.counter.ElapsedMicroseconds,
+ this.frameIndex++);
+ }
+ }
+ else
+ {
+ this.frameProcessor.OnFrameArrived(
+ this,
+ data,
+ frameLength,
+ timestamp > 0 ? timestamp / 10 : this.counter.ElapsedMicroseconds,
+ this.frameIndex++);
+ }
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+ finally
+ {
+ MediaFoundationNativeCom.Unlock(bufferPointer);
+ }
+ }
+ finally
+ {
+ MediaFoundationCom.Release(bufferPointer);
+ MediaFoundationCom.Release(samplePointer);
+ }
+ }
+
+ private unsafe bool TryRepackFrame(
+ IntPtr data,
+ int currentLength,
+ out byte[]? repacked,
+ out int frameLength)
+ {
+ repacked = null;
+ frameLength = 0;
+
+ if (IsCompressed(this.requestedCharacteristics.PixelFormat))
+ {
+ frameLength = currentLength;
+ return currentLength >= 1;
+ }
+
+ var width = this.requestedCharacteristics.Width;
+ var height = this.requestedCharacteristics.Height;
+ if (width <= 0 || height <= 0 ||
+ this.sourceStride <= 0 || this.targetStride <= 0 || this.frameRowBytes <= 0)
+ {
+ return false;
+ }
+
+ var uvRows = this.requestedCharacteristics.PixelFormat == PixelFormats.NV12 ?
+ (height + 1) / 2 :
+ 0;
+ var sourceStride = this.sourceStride;
+ var sourceBottomUp = this.sourceBottomUp;
+ var sourceRows = height + uvRows;
+ var sourceLength = sourceStride * sourceRows;
+ if (currentLength < sourceLength &&
+ currentLength >= this.frameRowBytes * sourceRows &&
+ currentLength % sourceRows == 0)
+ {
+ sourceStride = currentLength / sourceRows;
+ sourceBottomUp = false;
+ sourceLength = currentLength;
+ }
+
+ if (sourceStride < this.frameRowBytes || this.targetStride < this.frameRowBytes)
+ {
+ return false;
+ }
+
+ var targetLength = this.targetStride * (height + uvRows);
+ if (currentLength < sourceLength)
+ {
+ return false;
+ }
+
+ frameLength = targetLength;
+ if (sourceStride == this.targetStride &&
+ sourceBottomUp == this.targetBottomUp)
+ {
+ return true;
+ }
+
+ if (this.repackBuffer == null || this.repackBuffer.Length < targetLength)
+ {
+ this.repackBuffer = new byte[targetLength];
+ }
+
+ repacked = this.repackBuffer;
+ Array.Clear(repacked, 0, targetLength);
+
+ fixed (byte* target = repacked)
+ {
+ var source = (byte*)data.ToPointer();
+ if (this.requestedCharacteristics.PixelFormat == PixelFormats.NV12)
+ {
+ this.CopyRows(
+ source,
+ target,
+ height,
+ sourceStride,
+ sourceBottomUp,
+ this.frameRowBytes,
+ false);
+ this.CopyRows(
+ source + sourceStride * height,
+ target + this.targetStride * height,
+ uvRows,
+ sourceStride,
+ sourceBottomUp,
+ this.frameRowBytes,
+ false);
+ }
+ else
+ {
+ this.CopyRows(
+ source,
+ target,
+ height,
+ sourceStride,
+ sourceBottomUp,
+ this.frameRowBytes,
+ this.targetBottomUp);
+ }
+ }
+
+ return true;
+ }
+
+ private unsafe void CopyRows(
+ byte* source,
+ byte* target,
+ int rows,
+ int sourceStride,
+ bool sourceBottomUp,
+ int rowBytes,
+ bool targetBottomUp)
+ {
+ for (var row = 0; row < rows; row++)
+ {
+ var sourceRow = sourceBottomUp ? rows - row - 1 : row;
+ var targetRow = targetBottomUp ? rows - row - 1 : row;
+
+ Buffer.MemoryCopy(
+ source + sourceRow * sourceStride,
+ target + targetRow * this.targetStride,
+ this.targetStride,
+ rowBytes);
+ }
+ }
+
+ protected override async Task OnDisposeAsync()
+ {
+ await this.OnStopAsync(default).
+ ConfigureAwait(false);
+
+ if (this.frameProcessor != null)
+ {
+ await this.frameProcessor.DisposeAsync().
+ ConfigureAwait(false);
+ this.frameProcessor = null!;
+ }
+
+ this.ReleaseMediaFoundationObjects();
+ }
+
+ private void ReleaseMediaFoundationObjects()
+ {
+ this.sourceReader = null;
+
+ MediaFoundationCom.Release(this.sourceReaderPointer);
+ this.sourceReaderPointer = IntPtr.Zero;
+
+ if (this.mediaSourcePointer != IntPtr.Zero)
+ {
+ try
+ {
+ MediaFoundationCom.Wrap(this.mediaSourcePointer).Shutdown();
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+
+ MediaFoundationCom.Release(this.mediaSourcePointer);
+ this.mediaSourcePointer = IntPtr.Zero;
+ }
+
+ if (this.bitmapHeader != IntPtr.Zero)
+ {
+ NativeMethods.FreeMemory(this.bitmapHeader);
+ this.bitmapHeader = IntPtr.Zero;
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ protected override void OnCapture(
+ IntPtr pData,
+ int size,
+ long timestampMicroseconds,
+ long frameIndex,
+ PixelBuffer buffer) =>
+ buffer.CopyIn(
+ this.bitmapHeader,
+ pData,
+ size,
+ timestampMicroseconds,
+ frameIndex,
+ this.transcodeFormat);
+}
diff --git a/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs b/FlashCap.Core/Devices/MediaFoundationDeviceDescriptor.cs
similarity index 57%
rename from FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs
rename to FlashCap.Core/Devices/MediaFoundationDeviceDescriptor.cs
index 240b185..618985d 100644
--- a/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs
+++ b/FlashCap.Core/Devices/MediaFoundationDeviceDescriptor.cs
@@ -1,4 +1,4 @@
-////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////
//
// FlashCap - Independent camera capture library.
// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
@@ -7,27 +7,31 @@
//
////////////////////////////////////////////////////////////////////////////
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace FlashCap.Devices;
-public sealed class VideoForWindowsDeviceDescriptor : CaptureDeviceDescriptor
+[SupportedOSPlatform("windows")]
+public sealed class MediaFoundationDeviceDescriptor : CaptureDeviceDescriptor
{
- private readonly int deviceIndex;
+ private readonly string symbolicLink;
- internal VideoForWindowsDeviceDescriptor(
- int deviceIndex, string name, string description,
+ internal MediaFoundationDeviceDescriptor(
+ string symbolicLink,
+ string name,
+ string description,
VideoCharacteristics[] characteristics,
BufferPool defaultBufferPool) :
base(name, description, characteristics, defaultBufferPool) =>
- this.deviceIndex = deviceIndex;
+ this.symbolicLink = symbolicLink;
public override object Identity =>
- this.deviceIndex;
+ this.symbolicLink;
public override DeviceTypes DeviceType =>
- DeviceTypes.VideoForWindows;
+ DeviceTypes.MediaFoundation;
protected override Task OnOpenWithFrameProcessorAsync(
VideoCharacteristics characteristics,
@@ -35,6 +39,9 @@ protected override Task OnOpenWithFrameProcessorAsync(
FrameProcessor frameProcessor,
CancellationToken ct) =>
this.InternalOnOpenWithFrameProcessorAsync(
- new VideoForWindowsDevice(this.deviceIndex, this.Name),
- characteristics, transcodeFormat, frameProcessor, ct);
+ new MediaFoundationDevice(this.symbolicLink, this.Name),
+ characteristics,
+ transcodeFormat,
+ frameProcessor,
+ ct);
}
diff --git a/FlashCap.Core/Devices/MediaFoundationDevices.cs b/FlashCap.Core/Devices/MediaFoundationDevices.cs
new file mode 100644
index 0000000..0ce6de4
--- /dev/null
+++ b/FlashCap.Core/Devices/MediaFoundationDevices.cs
@@ -0,0 +1,271 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+using FlashCap.Internal;
+using FlashCap.Internal.MediaFoundation;
+
+namespace FlashCap.Devices;
+
+[SupportedOSPlatform("windows")]
+public sealed class MediaFoundationDevices : CaptureDevices
+{
+ public MediaFoundationDevices() :
+ this(new DefaultBufferPool())
+ {
+ }
+
+ public MediaFoundationDevices(BufferPool defaultBufferPool) :
+ base(defaultBufferPool)
+ {
+ }
+
+ private static IntPtr CreateVideoCaptureAttributes()
+ {
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ NativeMethods_MediaFoundation.MFCreateAttributes(out var attributesPointer, 1),
+ nameof(NativeMethods_MediaFoundation.MFCreateAttributes));
+
+ try
+ {
+ var attributes = MediaFoundationCom.Wrap(attributesPointer);
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ attributes.SetGUID(
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID),
+ "IMFAttributes.SetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE)");
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ attributes.GetGUID(
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
+ out var sourceType),
+ "IMFAttributes.GetGUID(MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE)");
+ if (sourceType != NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)
+ {
+ throw new InvalidOperationException(
+ "FlashCap: Media Foundation device source attribute verification failed.");
+ }
+ return attributesPointer;
+ }
+ catch
+ {
+ MediaFoundationCom.Release(attributesPointer);
+ throw;
+ }
+ }
+
+ internal static IEnumerable EnumerateDeviceActivates()
+ {
+ MediaFoundationSession.EnsureStarted();
+
+ var attributesPointer = CreateVideoCaptureAttributes();
+ try
+ {
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ NativeMethods_MediaFoundation.MFEnumDeviceSources(
+ attributesPointer,
+ out var activateArray,
+ out var count),
+ nameof(NativeMethods_MediaFoundation.MFEnumDeviceSources));
+
+ try
+ {
+ for (var index = 0; index < count; index++)
+ {
+ var activatePointer = Marshal.ReadIntPtr(activateArray, index * IntPtr.Size);
+ if (activatePointer != IntPtr.Zero)
+ {
+ yield return activatePointer;
+ }
+ }
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(activateArray);
+ }
+ }
+ finally
+ {
+ MediaFoundationCom.Release(attributesPointer);
+ }
+ }
+
+ private static VideoCharacteristics[] EnumerateCharacteristics(IMFActivate activate)
+ {
+ var mediaSourcePointer = IntPtr.Zero;
+ var sourceReaderPointer = IntPtr.Zero;
+
+ try
+ {
+ var hr = activate.ActivateObject(
+ in NativeMethods_MediaFoundation.IID_IMFMediaSource,
+ out mediaSourcePointer);
+ if (hr < 0 || mediaSourcePointer == IntPtr.Zero)
+ {
+ return ArrayEx.Empty();
+ }
+
+ hr = NativeMethods_MediaFoundation.MFCreateSourceReaderFromMediaSource(
+ mediaSourcePointer,
+ IntPtr.Zero,
+ out sourceReaderPointer);
+ if (hr < 0 || sourceReaderPointer == IntPtr.Zero)
+ {
+ return ArrayEx.Empty();
+ }
+
+ var sourceReader = MediaFoundationCom.Wrap(sourceReaderPointer);
+ return EnumerateCharacteristics(sourceReader).
+ Distinct().
+ OrderByDescending(vc => vc).
+ ToArray();
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ return ArrayEx.Empty();
+ }
+ finally
+ {
+ MediaFoundationCom.Release(sourceReaderPointer);
+
+ if (mediaSourcePointer != IntPtr.Zero)
+ {
+ try
+ {
+ MediaFoundationCom.Wrap(mediaSourcePointer).Shutdown();
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+ MediaFoundationCom.Release(mediaSourcePointer);
+ }
+
+ try
+ {
+ activate.ShutdownObject();
+ }
+ catch (Exception ex)
+ {
+ Trace.WriteLine(ex);
+ }
+ }
+ }
+
+ internal static IEnumerable EnumerateCharacteristics(IMFSourceReader sourceReader)
+ {
+ for (var index = 0; ; index++)
+ {
+ var hr = sourceReader.GetNativeMediaType(
+ 0,
+ index,
+ out var mediaTypePointer);
+ if (hr == NativeMethods_MediaFoundation.MF_E_NO_MORE_TYPES)
+ {
+ yield break;
+ }
+ if (hr < 0)
+ {
+ yield break;
+ }
+ if (mediaTypePointer == IntPtr.Zero)
+ {
+ continue;
+ }
+
+ try
+ {
+ var mediaType = MediaFoundationCom.Wrap(mediaTypePointer);
+ if (MediaFoundationMediaTypes.TryCreateVideoCharacteristics(
+ mediaType,
+ out var characteristics,
+ out _))
+ {
+ yield return characteristics;
+ }
+ }
+ finally
+ {
+ MediaFoundationCom.Release(mediaTypePointer);
+ }
+ }
+ }
+
+ protected override IEnumerable OnEnumerateDescriptors()
+ {
+ if (!OperatingSystem.IsWindows())
+ throw new UnreachableException();
+
+ var descriptors = new List();
+ foreach (var activatePointer in EnumerateDeviceActivates())
+ {
+ try
+ {
+ var activate = MediaFoundationCom.Wrap(activatePointer);
+ var name = MediaFoundationCom.GetAllocatedString(
+ activate,
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME).Trim();
+ var symbolicLink = MediaFoundationCom.GetAllocatedString(
+ activate,
+ in NativeMethods_MediaFoundation.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK).Trim();
+
+ if (string.IsNullOrEmpty(symbolicLink))
+ {
+ continue;
+ }
+
+ if (string.IsNullOrEmpty(name))
+ {
+ name = "Media Foundation camera";
+ }
+
+ descriptors.Add(new MediaFoundationDeviceDescriptor(
+ symbolicLink,
+ name,
+ $"{name} (Media Foundation)",
+ EnumerateCharacteristics(activate),
+ this.DefaultBufferPool));
+ }
+ finally
+ {
+ MediaFoundationCom.Release(activatePointer);
+ }
+ }
+
+ return descriptors;
+ }
+}
+
+[SupportedOSPlatform("windows")]
+internal static class MediaFoundationSession
+{
+ private static readonly object SyncLock = new();
+ private static bool started;
+
+ public static void EnsureStarted()
+ {
+ lock (SyncLock)
+ {
+ if (!started)
+ {
+ NativeMethods_MediaFoundation.ThrowIfFailed(
+ NativeMethods_MediaFoundation.MFStartup(
+ NativeMethods_MediaFoundation.MF_VERSION,
+ NativeMethods_MediaFoundation.MFSTARTUP_FULL),
+ nameof(NativeMethods_MediaFoundation.MFStartup));
+ started = true;
+ }
+ }
+ }
+}
diff --git a/FlashCap.Core/Devices/V4L2Device.cs b/FlashCap.Core/Devices/V4L2Device.cs
index c6a23d9..f5581cf 100644
--- a/FlashCap.Core/Devices/V4L2Device.cs
+++ b/FlashCap.Core/Devices/V4L2Device.cs
@@ -12,6 +12,7 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -20,6 +21,7 @@
namespace FlashCap.Devices;
+[SupportedOSPlatform("linux")]
public sealed class V4L2Device : CaptureDevice
{
private const int BufferCount = 2;
@@ -36,11 +38,11 @@ public sealed class V4L2Device : CaptureDevice
private IntPtr[] pBuffers = new IntPtr[BufferCount];
private int[] bufferLength = new int[BufferCount];
- private int fd;
+ private int fd = -1;
private IntPtr pBih;
private Task task;
- private int abortrfd;
- private int abortwfd;
+ private int abortrfd = -1;
+ private int abortwfd = -1;
#pragma warning disable CS8618
internal V4L2Device(object identity, string name) :
@@ -151,11 +153,21 @@ protected override async Task OnDisposeAsync()
{
if (this.IsRunning)
{
- await this.frameProcessor.DisposeAsync().
+ await this.OnStopAsync(default).
ConfigureAwait(false);
+ }
- await this.InternalStopAsync(default).
+ if (this.frameProcessor != null)
+ {
+ await this.frameProcessor.DisposeAsync().
ConfigureAwait(false);
+ this.frameProcessor = null!;
+ }
+
+ if (this.pBih != IntPtr.Zero)
+ {
+ NativeMethods.FreeMemory(this.pBih);
+ this.pBih = IntPtr.Zero;
}
}
@@ -446,9 +458,7 @@ static bool IsIgnore(int code) =>
}
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected override void OnCapture(
IntPtr pData, int size,
long timestampMicroseconds, long frameIndex,
diff --git a/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs b/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs
index 6049751..f4f9ca4 100644
--- a/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs
+++ b/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs
@@ -7,11 +7,13 @@
//
////////////////////////////////////////////////////////////////////////////
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace FlashCap.Devices;
+[SupportedOSPlatform("linux")]
public sealed class V4L2DeviceDescriptor : CaptureDeviceDescriptor
{
private readonly string devicePath;
diff --git a/FlashCap.Core/Devices/V4L2Devices.cs b/FlashCap.Core/Devices/V4L2Devices.cs
index b99a772..e009b1e 100644
--- a/FlashCap.Core/Devices/V4L2Devices.cs
+++ b/FlashCap.Core/Devices/V4L2Devices.cs
@@ -13,6 +13,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Runtime.Versioning;
using System.Text;
using static FlashCap.Internal.NativeMethods_V4L2;
@@ -20,6 +21,7 @@
namespace FlashCap.Devices;
+[SupportedOSPlatform("linux")]
public sealed class V4L2Devices : CaptureDevices
{
public V4L2Devices() :
diff --git a/FlashCap.Core/Devices/VideoForWindowsDevice.cs b/FlashCap.Core/Devices/VideoForWindowsDevice.cs
deleted file mode 100644
index 1ea729e..0000000
--- a/FlashCap.Core/Devices/VideoForWindowsDevice.cs
+++ /dev/null
@@ -1,289 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using FlashCap.Internal;
-using System;
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using System.Threading;
-
-namespace FlashCap.Devices;
-
-public sealed class VideoForWindowsDevice : CaptureDevice
-{
- private readonly TimestampCounter counter = new();
- private int deviceIndex;
- private TranscodeFormats transcodeFormat;
- private FrameProcessor frameProcessor;
- private long frameIndex;
-
- private IndependentSingleApartmentContext? workingContext = new();
- private IntPtr handle;
- private GCHandle thisPin;
- private NativeMethods_VideoForWindows.CAPVIDEOCALLBACK? callback;
- private IntPtr pBih;
-
-#pragma warning disable CS8618
- internal VideoForWindowsDevice(object identity, string name) :
- base(identity, name)
-#pragma warning restore CS8618
- {
- }
-
- protected override unsafe Task OnInitializeAsync(
- VideoCharacteristics characteristics,
- TranscodeFormats transcodeFormat,
- FrameProcessor frameProcessor,
- CancellationToken ct)
- {
- this.deviceIndex = (int)this.Identity;
- this.Characteristics = characteristics;
- this.transcodeFormat = transcodeFormat;
- this.frameProcessor = frameProcessor;
-
- if (!NativeMethods.GetCompressionAndBitCount(
- characteristics.PixelFormat, out var compression, out var bitCount))
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set video format [1]: DeviceIndex={this.deviceIndex}");
- }
-
- return this.workingContext!.InvokeAsync(() =>
- {
- var handle = NativeMethods_VideoForWindows.CreateVideoSourceWindow(this.deviceIndex);
- if (handle == IntPtr.Zero)
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't allocate video source window: DeviceIndex={this.deviceIndex}");
- }
- try
- {
- NativeMethods_VideoForWindows.capDriverConnect(handle, this.deviceIndex);
- try
- {
- NativeMethods_VideoForWindows.capSetPreviewScale(handle, false);
- NativeMethods_VideoForWindows.capSetPreviewFPS(handle, 15);
- NativeMethods_VideoForWindows.capSetOverlay(handle, true);
-
- ///////////////////////////////////////
-
- // At first set 5fps, because can't set both fps and video format atomicity.
- NativeMethods_VideoForWindows.capCaptureGetSetup(handle, out var cp);
- cp.dwRequestMicroSecPerFrame = 1_000_000 / 5; // 5fps
- if (!NativeMethods_VideoForWindows.capCaptureSetSetup(handle, cp))
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set video frame rate [1]: DeviceIndex={this.deviceIndex}");
- }
- NativeMethods_VideoForWindows.capCaptureGetSetup(handle, out cp);
-
- var pih = NativeMethods.AllocateMemory((IntPtr)sizeof(NativeMethods.BITMAPINFOHEADER));
- try
- {
- var pBih = (NativeMethods.BITMAPINFOHEADER*)pih.ToPointer();
-
- pBih->biSize = sizeof(NativeMethods.BITMAPINFOHEADER);
- pBih->biCompression = compression;
- pBih->biPlanes = 1;
- pBih->biBitCount = bitCount;
- pBih->biWidth = characteristics.Width;
- pBih->biHeight = characteristics.Height;
- pBih->biSizeImage = pBih->CalculateImageSize();
-
- // Try to set video format.
- if (!NativeMethods_VideoForWindows.capSetVideoFormat(handle, pih))
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set video format [2]: DeviceIndex={this.deviceIndex}");
- }
-
- // Try to set fps, but VFW API may cause ignoring it silently...
- cp.dwRequestMicroSecPerFrame =
- (int)(1_000_000 / characteristics.FramesPerSecond);
- if (!NativeMethods_VideoForWindows.capCaptureSetSetup(handle, cp))
- {
- throw new ArgumentException(
- $"FlashCap: Couldn't set video frame rate [2]: DeviceIndex={this.deviceIndex}");
- }
- NativeMethods_VideoForWindows.capCaptureGetSetup(handle, out cp);
- }
- finally
- {
- NativeMethods.FreeMemory(pih);
- }
-
- // Get final video format.
- NativeMethods_VideoForWindows.capGetVideoFormat(handle, out this.pBih);
- }
- catch
- {
- NativeMethods_VideoForWindows.capDriverDisconnect(handle, this.deviceIndex);
- throw;
- }
- }
- catch
- {
- NativeMethods_VideoForWindows.DestroyWindow(handle);
- throw;
- }
-
- ///////////////////////////////////////
-
- this.handle = handle;
-
- // https://stackoverflow.com/questions/4097235/is-it-necessary-to-gchandle-alloc-each-callback-in-a-class
- this.thisPin = GCHandle.Alloc(this, GCHandleType.Normal);
- this.callback = this.CallbackEntry;
-
- NativeMethods_VideoForWindows.capSetCallbackFrame(handle, this.callback);
- }, ct);
- }
-
- ~VideoForWindowsDevice()
- {
- if (this.handle != IntPtr.Zero)
- {
- var handle = this.handle;
- this.handle = IntPtr.Zero;
-
- this.workingContext?.Post(_ =>
- {
- NativeMethods_VideoForWindows.capSetCallbackFrame(handle, null);
- NativeMethods_VideoForWindows.capDriverDisconnect(handle, this.deviceIndex);
- NativeMethods_VideoForWindows.DestroyWindow(handle);
-
- this.thisPin.Free();
- this.callback = null;
- NativeMethods.FreeMemory(this.pBih);
- this.pBih = IntPtr.Zero;
-
- workingContext.Dispose();
- }, null);
- }
- else
- {
- this.workingContext?.Dispose();
- }
- }
-
- protected override async Task OnDisposeAsync()
- {
- if (this.handle != IntPtr.Zero)
- {
- var handle = this.handle;
- this.handle = IntPtr.Zero;
-
- try
- {
- await this.frameProcessor.DisposeAsync().
- ConfigureAwait(false);
- }
- catch
- {
- }
-
- try
- {
- await this.OnStopAsync(default).
- ConfigureAwait(false);
- }
- catch
- {
- }
-
- var workingContext = this.workingContext!;
- this.workingContext = null;
-
- workingContext.Post(_ =>
- {
- NativeMethods_VideoForWindows.capSetCallbackFrame(handle, null);
- NativeMethods_VideoForWindows.capDriverDisconnect(handle, this.deviceIndex);
- NativeMethods_VideoForWindows.DestroyWindow(handle);
-
- this.thisPin.Free();
- this.callback = null;
- NativeMethods.FreeMemory(this.pBih);
- this.pBih = IntPtr.Zero;
-
- workingContext.Dispose();
- }, null);
- }
- else
- {
- this.workingContext?.Dispose();
- }
- }
-
- private void CallbackEntry(
- IntPtr hWnd, in NativeMethods_VideoForWindows.VIDEOHDR hdr)
- {
- // HACK: Avoid stupid camera devices...
- if (hdr.dwBytesUsed >= 64)
- {
- try
- {
- this.frameProcessor.OnFrameArrived(
- this,
- hdr.lpData, hdr.dwBytesUsed,
- // HACK: `hdr.dwTimeCaptured` always zero on my environment...
- this.counter.ElapsedMicroseconds,
- this.frameIndex++);
- }
- // DANGER: Stop leaking exception around outside of unmanaged area...
- catch (Exception ex)
- {
- Trace.WriteLine(ex);
- }
- }
- }
-
- protected override Task OnStartAsync(CancellationToken ct)
- {
- if (!this.IsRunning)
- {
- return this.workingContext!.InvokeAsync(() =>
- {
- this.frameIndex = 0;
- this.counter.Restart();
- NativeMethods_VideoForWindows.capShowPreview(this.handle, true);
- this.IsRunning = true;
- }, default);
- }
- else
- {
- return TaskCompat.CompletedTask;
- }
- }
-
- protected override Task OnStopAsync(CancellationToken ct)
- {
- if (this.IsRunning)
- {
- return this.workingContext!.InvokeAsync(() =>
- {
- this.IsRunning = false;
- NativeMethods_VideoForWindows.capShowPreview(this.handle, false);
- }, default);
- }
- else
- {
- return TaskCompat.CompletedTask;
- }
- }
-
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
- protected override void OnCapture(
- IntPtr pData, int size, long timestampMicroseconds, long frameIndex,
- PixelBuffer buffer) =>
- buffer.CopyIn(this.pBih, pData, size, timestampMicroseconds, frameIndex, this.transcodeFormat);
-}
diff --git a/FlashCap.Core/Devices/VideoForWindowsDevices.cs b/FlashCap.Core/Devices/VideoForWindowsDevices.cs
deleted file mode 100644
index 88992a7..0000000
--- a/FlashCap.Core/Devices/VideoForWindowsDevices.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using FlashCap.Internal;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace FlashCap.Devices;
-
-public sealed class VideoForWindowsDevices : CaptureDevices
-{
- public VideoForWindowsDevices() :
- this(new DefaultBufferPool())
- {
- }
-
- public VideoForWindowsDevices(BufferPool defaultBufferPool) :
- base(defaultBufferPool)
- {
- }
-
- protected override IEnumerable OnEnumerateDescriptors() =>
- Enumerable.Range(0, NativeMethods_VideoForWindows.MaxVideoForWindowsDevices).
- Collect(index =>
- {
- var name = new StringBuilder(256);
- var description = new StringBuilder(256);
-
- if (NativeMethods_VideoForWindows.capGetDriverDescription(
- (uint)index, name, name.Length, description, description.Length))
- {
- var n = name.ToString().Trim();
- var d = description.ToString().Trim();
-
- return (CaptureDeviceDescriptor)new VideoForWindowsDeviceDescriptor(
- index,
- string.IsNullOrEmpty(n) ? "Default" : n,
- string.IsNullOrEmpty(d) ? "VideoForWindows default" : d,
- new[] {
- // DIRTY: VFW couldn't enumerate device specific strictly video formats.
- // So there're predefined (major?) formats.
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 1920, 1080, 0, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 1600, 1200, 0, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 1280, 960, 0, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 1024, 768, 0, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 640, 480, 0, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.MJPG, 640, 480, 0, 15, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.YUYV, 640, 480, 16, 30, false)!,
- NativeMethods.CreateVideoCharacteristics(
- NativeMethods.Compression.YUYV, 640, 480, 16, 15, false)!,
- },
- this.DefaultBufferPool);
- }
- else
- {
- return null;
- }
- });
-}
diff --git a/FlashCap.Core/FlashCap.Core.csproj b/FlashCap.Core/FlashCap.Core.csproj
index c59cbb8..5ba4f1f 100644
--- a/FlashCap.Core/FlashCap.Core.csproj
+++ b/FlashCap.Core/FlashCap.Core.csproj
@@ -1,25 +1,13 @@
- net35;net40;net45;net461;net48;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0
- True
+ net8.0;net9.0;net10.0
+ true
$(NoWarn);CS0649
true
+ true
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FlashCap.Core/FrameProcessor.cs b/FlashCap.Core/FrameProcessor.cs
index c8d31d8..9d5a484 100644
--- a/FlashCap.Core/FrameProcessor.cs
+++ b/FlashCap.Core/FrameProcessor.cs
@@ -49,9 +49,7 @@ await this.OnDisposeAsync().
}
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected PixelBuffer GetPixelBuffer()
{
PixelBuffer? buffer = null;
@@ -69,9 +67,7 @@ protected PixelBuffer GetPixelBuffer()
return buffer;
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public void ReleasePixelBuffer(PixelBuffer buffer)
{
lock (this.reserver)
@@ -80,9 +76,7 @@ public void ReleasePixelBuffer(PixelBuffer buffer)
}
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected void Capture(CaptureDevice captureDevice,
IntPtr pData, int size,
long timestampMicroseconds, long frameIndex,
@@ -98,18 +92,14 @@ protected sealed class AutoPixelBufferScope :
{
private FrameProcessor? parent;
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public AutoPixelBufferScope(
FrameProcessor parent,
PixelBuffer buffer) :
base(buffer) =>
this.parent = parent;
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public void Dispose()
{
lock (this)
@@ -124,9 +114,7 @@ public void Dispose()
}
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected override void OnReleaseNow() =>
this.Dispose();
}
diff --git a/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs b/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs
index c53f9d9..fd65749 100644
--- a/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs
+++ b/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs
@@ -93,7 +93,7 @@ public DelegatedScatteringProcessor(
protected override async Task OnDisposeAsync()
{
- await base.DisposeAsync().
+ await base.OnDisposeAsync().
ConfigureAwait(false);
this.pixelBufferArrived = null!;
}
@@ -142,7 +142,7 @@ public DelegatedScatteringTaskProcessor(
protected override async Task OnDisposeAsync()
{
- await base.DisposeAsync().
+ await base.OnDisposeAsync().
ConfigureAwait(false);
this.pixelBufferArrived = null!;
}
diff --git a/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs b/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs
index 4690e56..8bd718c 100644
--- a/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs
+++ b/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs
@@ -12,10 +12,12 @@
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Linq;
+using System.Runtime.Versioning;
using static FlashCap.Internal.NativeMethods_AVFoundation;
namespace FlashCap.Internal.AVFoundation;
+[SupportedOSPlatform("macos")]
internal static partial class LibAVFoundation
{
public const string Path = "/System/Library/Frameworks/AVFoundation.framework/AVFoundation";
diff --git a/FlashCap.Core/Internal/BitmapTranscoder.cs b/FlashCap.Core/Internal/BitmapTranscoder.cs
index fc5971b..32dac5f 100644
--- a/FlashCap.Core/Internal/BitmapTranscoder.cs
+++ b/FlashCap.Core/Internal/BitmapTranscoder.cs
@@ -323,9 +323,7 @@ private static unsafe void TranscodeFromYUV(
}
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
private static byte Clip(int value) =>
value < 0 ? (byte)0 :
value > 255 ? (byte)255 :
diff --git a/FlashCap.Core/Internal/IndependentSingleApartmentContext.cs b/FlashCap.Core/Internal/IndependentSingleApartmentContext.cs
index c78c7ba..e6f33d1 100644
--- a/FlashCap.Core/Internal/IndependentSingleApartmentContext.cs
+++ b/FlashCap.Core/Internal/IndependentSingleApartmentContext.cs
@@ -14,11 +14,14 @@
using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
namespace FlashCap.Internal;
+
+[SupportedOSPlatform("windows")]
internal sealed class IndependentSingleApartmentContext :
SynchronizationContext, IDisposable
{
@@ -113,7 +116,7 @@ public void Wait()
public IndependentSingleApartmentContext()
{
- Debug.Assert(NativeMethods.CurrentPlatform == NativeMethods.Platforms.Windows);
+ Debug.Assert(OperatingSystem.IsWindows());
this.thread = new(this.ThreadEntry);
this.thread.IsBackground = true;
diff --git a/FlashCap.Core/Internal/MediaFoundation/MediaFoundationCom.cs b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationCom.cs
new file mode 100644
index 0000000..65ceed9
--- /dev/null
+++ b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationCom.cs
@@ -0,0 +1,70 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace FlashCap.Internal.MediaFoundation;
+
+internal static class MediaFoundationCom
+{
+ private sealed class MediaFoundationComWrappers : StrategyBasedComWrappers
+ {
+ public void Release(object value) =>
+ base.ReleaseObjects(new[] { value });
+ }
+
+ private static readonly MediaFoundationComWrappers Wrappers = new();
+
+ public static T Wrap(IntPtr pointer)
+ where T : class
+ {
+ if (pointer == IntPtr.Zero)
+ {
+ throw new ArgumentNullException(nameof(pointer));
+ }
+
+ return (T)Wrappers.GetOrCreateObjectForComInstance(pointer, CreateObjectFlags.None);
+ }
+
+ public static void ReleaseObject(object? value)
+ {
+ if (value != null)
+ {
+ Wrappers.Release(value);
+ }
+ }
+
+ public static void Release(IntPtr pointer)
+ {
+ if (pointer != IntPtr.Zero)
+ {
+ Marshal.Release(pointer);
+ }
+ }
+
+ public static string GetAllocatedString(IMFAttributes attributes, in Guid key)
+ {
+ var hr = attributes.GetAllocatedString(in key, out var value, out _);
+ if (hr < 0 || value == IntPtr.Zero)
+ {
+ return string.Empty;
+ }
+
+ try
+ {
+ return Marshal.PtrToStringUni(value) ?? string.Empty;
+ }
+ finally
+ {
+ Marshal.FreeCoTaskMem(value);
+ }
+ }
+}
diff --git a/FlashCap.Core/Internal/MediaFoundation/MediaFoundationInterfaces.cs b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationInterfaces.cs
new file mode 100644
index 0000000..5f7f524
--- /dev/null
+++ b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationInterfaces.cs
@@ -0,0 +1,291 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+
+namespace FlashCap.Internal.MediaFoundation;
+
+[GeneratedComInterface]
+[Guid("2cd2d921-c447-44a7-a13c-4adabfc247e3")]
+internal partial interface IMFAttributes
+{
+ [PreserveSig]
+ int GetItem(in Guid guidKey, IntPtr value);
+
+ [PreserveSig]
+ int GetItemType(in Guid guidKey, out int type);
+
+ [PreserveSig]
+ int CompareItem(in Guid guidKey, IntPtr value, out int result);
+
+ [PreserveSig]
+ int Compare(IntPtr attributes, int matchType, out int result);
+
+ [PreserveSig]
+ int GetUINT32(in Guid guidKey, out int value);
+
+ [PreserveSig]
+ int GetUINT64(in Guid guidKey, out long value);
+
+ [PreserveSig]
+ int GetDouble(in Guid guidKey, out double value);
+
+ [PreserveSig]
+ int GetGUID(in Guid guidKey, out Guid value);
+
+ [PreserveSig]
+ int GetStringLength(in Guid guidKey, out int length);
+
+ [PreserveSig]
+ int GetString(in Guid guidKey, IntPtr value, int size, out int length);
+
+ [PreserveSig]
+ int GetAllocatedString(in Guid guidKey, out IntPtr value, out int length);
+
+ [PreserveSig]
+ int GetBlobSize(in Guid guidKey, out int size);
+
+ [PreserveSig]
+ int GetBlob(in Guid guidKey, IntPtr buffer, int bufferSize, out int blobSize);
+
+ [PreserveSig]
+ int GetAllocatedBlob(in Guid guidKey, out IntPtr buffer, out int size);
+
+ [PreserveSig]
+ int GetUnknown(in Guid guidKey, in Guid riid, out IntPtr value);
+
+ [PreserveSig]
+ int SetItem(in Guid guidKey, IntPtr value);
+
+ [PreserveSig]
+ int DeleteItem(in Guid guidKey);
+
+ [PreserveSig]
+ int DeleteAllItems();
+
+ [PreserveSig]
+ int SetUINT32(in Guid guidKey, int value);
+
+ [PreserveSig]
+ int SetUINT64(in Guid guidKey, long value);
+
+ [PreserveSig]
+ int SetDouble(in Guid guidKey, double value);
+
+ [PreserveSig]
+ int SetGUID(in Guid guidKey, in Guid value);
+
+ [PreserveSig]
+ int SetString(in Guid guidKey, [MarshalAs(UnmanagedType.LPWStr)] string value);
+
+ [PreserveSig]
+ int SetBlob(in Guid guidKey, IntPtr buffer, int size);
+
+ [PreserveSig]
+ int SetUnknown(in Guid guidKey, IntPtr unknown);
+
+ [PreserveSig]
+ int LockStore();
+
+ [PreserveSig]
+ int UnlockStore();
+
+ [PreserveSig]
+ int GetCount(out int count);
+
+ [PreserveSig]
+ int GetItemByIndex(int index, out Guid guidKey, IntPtr value);
+
+ [PreserveSig]
+ int CopyAllItems(IntPtr destination);
+}
+
+[GeneratedComInterface]
+[Guid("44ae0fa8-ea31-4109-8d2e-4cae4997c555")]
+internal partial interface IMFMediaType : IMFAttributes
+{
+ [PreserveSig]
+ int GetMajorType(out Guid majorType);
+
+ [PreserveSig]
+ int IsCompressedFormat(out int compressed);
+
+ [PreserveSig]
+ int IsEqual(IntPtr mediaType, out int flags);
+
+ [PreserveSig]
+ int GetRepresentation(in Guid representation, out IntPtr value);
+
+ [PreserveSig]
+ int FreeRepresentation(in Guid representation, IntPtr value);
+}
+
+[GeneratedComInterface]
+[Guid("7fee9e9a-4a89-47a6-899c-b6a53a70fb67")]
+internal partial interface IMFActivate : IMFAttributes
+{
+ [PreserveSig]
+ int ActivateObject(in Guid riid, out IntPtr value);
+
+ [PreserveSig]
+ int ShutdownObject();
+
+ [PreserveSig]
+ int DetachObject();
+}
+
+[GeneratedComInterface]
+[Guid("2cd0bd52-bcd5-4b89-b62c-eadc0c031e7d")]
+internal partial interface IMFMediaEventGenerator
+{
+ [PreserveSig]
+ int GetEvent(int flags, out IntPtr mediaEvent);
+
+ [PreserveSig]
+ int BeginGetEvent(IntPtr callback, IntPtr state);
+
+ [PreserveSig]
+ int EndGetEvent(IntPtr result, out IntPtr mediaEvent);
+
+ [PreserveSig]
+ int QueueEvent(int met, in Guid extendedType, int status, IntPtr value);
+}
+
+[GeneratedComInterface]
+[Guid("279a808d-aec7-40c8-9c6b-a6b492c78a66")]
+internal partial interface IMFMediaSource : IMFMediaEventGenerator
+{
+ [PreserveSig]
+ int GetCharacteristics(out int characteristics);
+
+ [PreserveSig]
+ int CreatePresentationDescriptor(out IntPtr presentationDescriptor);
+
+ [PreserveSig]
+ int Start(IntPtr presentationDescriptor, in Guid timeFormat, IntPtr startPosition);
+
+ [PreserveSig]
+ int Stop();
+
+ [PreserveSig]
+ int Pause();
+
+ [PreserveSig]
+ int Shutdown();
+}
+
+[GeneratedComInterface]
+[Guid("70ae66f2-c809-4e4f-8915-bdcb406b7993")]
+internal partial interface IMFSourceReader
+{
+ [PreserveSig]
+ int GetStreamSelection(int streamIndex, out int selected);
+
+ [PreserveSig]
+ int SetStreamSelection(int streamIndex, int selected);
+
+ [PreserveSig]
+ int GetNativeMediaType(int streamIndex, int mediaTypeIndex, out IntPtr mediaType);
+
+ [PreserveSig]
+ int GetCurrentMediaType(int streamIndex, out IntPtr mediaType);
+
+ [PreserveSig]
+ int SetCurrentMediaType(int streamIndex, IntPtr reserved, IntPtr mediaType);
+
+ [PreserveSig]
+ int SetCurrentPosition(in Guid timeFormat, IntPtr position);
+
+ [PreserveSig]
+ int ReadSample(
+ int streamIndex,
+ int controlFlags,
+ out int actualStreamIndex,
+ out int streamFlags,
+ out long timestamp,
+ out IntPtr sample);
+
+ [PreserveSig]
+ int Flush(int streamIndex);
+
+ [PreserveSig]
+ int GetServiceForStream(int streamIndex, in Guid service, in Guid riid, out IntPtr value);
+
+ [PreserveSig]
+ int GetPresentationAttribute(int streamIndex, in Guid guidAttribute, IntPtr value);
+}
+
+[GeneratedComInterface]
+[Guid("045fa593-8799-42b8-bc8d-8968c6453507")]
+internal partial interface IMFMediaBuffer
+{
+ [PreserveSig]
+ int Lock(out IntPtr buffer, out int maxLength, out int currentLength);
+
+ [PreserveSig]
+ int Unlock();
+
+ [PreserveSig]
+ int GetCurrentLength(out int currentLength);
+
+ [PreserveSig]
+ int SetCurrentLength(int currentLength);
+
+ [PreserveSig]
+ int GetMaxLength(out int maxLength);
+}
+
+[GeneratedComInterface]
+[Guid("c40a00f2-b93a-4d80-ae8c-5a1c634f58e4")]
+internal partial interface IMFSample : IMFAttributes
+{
+ [PreserveSig]
+ int GetSampleFlags(out int sampleFlags);
+
+ [PreserveSig]
+ int SetSampleFlags(int sampleFlags);
+
+ [PreserveSig]
+ int GetSampleTime(out long sampleTime);
+
+ [PreserveSig]
+ int SetSampleTime(long sampleTime);
+
+ [PreserveSig]
+ int GetSampleDuration(out long sampleDuration);
+
+ [PreserveSig]
+ int SetSampleDuration(long sampleDuration);
+
+ [PreserveSig]
+ int GetBufferCount(out int bufferCount);
+
+ [PreserveSig]
+ int GetBufferByIndex(int index, out IntPtr buffer);
+
+ [PreserveSig]
+ int ConvertToContiguousBuffer(out IntPtr buffer);
+
+ [PreserveSig]
+ int AddBuffer(IntPtr buffer);
+
+ [PreserveSig]
+ int RemoveBufferByIndex(int index);
+
+ [PreserveSig]
+ int RemoveAllBuffers();
+
+ [PreserveSig]
+ int GetTotalLength(out int totalLength);
+
+ [PreserveSig]
+ int CopyToBuffer(IntPtr buffer);
+}
diff --git a/FlashCap.Core/Internal/MediaFoundation/MediaFoundationMediaTypes.cs b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationMediaTypes.cs
new file mode 100644
index 0000000..ad2a1fd
--- /dev/null
+++ b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationMediaTypes.cs
@@ -0,0 +1,154 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.Versioning;
+using FlashCap.Utilities;
+
+namespace FlashCap.Internal.MediaFoundation;
+
+[SupportedOSPlatform("windows")]
+internal static class MediaFoundationMediaTypes
+{
+ public readonly struct FormatInfo
+ {
+ public readonly PixelFormats PixelFormat;
+ public readonly NativeMethods.Compression Compression;
+ public readonly short BitCount;
+ public readonly string Description;
+
+ public FormatInfo(
+ PixelFormats pixelFormat,
+ NativeMethods.Compression compression,
+ short bitCount,
+ string description)
+ {
+ this.PixelFormat = pixelFormat;
+ this.Compression = compression;
+ this.BitCount = bitCount;
+ this.Description = description;
+ }
+ }
+
+ public static bool TryGetFormatInfo(Guid subtype, out FormatInfo formatInfo)
+ {
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_RGB24)
+ {
+ formatInfo = new(PixelFormats.RGB24, NativeMethods.Compression.BI_RGB, 24, "RGB24");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_RGB32)
+ {
+ formatInfo = new(PixelFormats.RGB32, NativeMethods.Compression.BI_RGB, 32, "RGB32");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_ARGB32)
+ {
+ formatInfo = new(PixelFormats.ARGB32, NativeMethods.Compression.ARGB, 32, "ARGB32");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_RGB555)
+ {
+ formatInfo = new(PixelFormats.RGB15, NativeMethods.Compression.BI_RGB, 16, "RGB555");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_RGB565)
+ {
+ formatInfo = new(PixelFormats.RGB16, NativeMethods.Compression.D3D_RGB565, 16, "RGB565");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_MJPG)
+ {
+ formatInfo = new(PixelFormats.JPEG, NativeMethods.Compression.MJPG, 24, "MJPG");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_UYVY)
+ {
+ formatInfo = new(PixelFormats.UYVY, NativeMethods.Compression.UYVY, 16, "UYVY");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_YUY2)
+ {
+ formatInfo = new(PixelFormats.YUYV, NativeMethods.Compression.YUY2, 16, "YUY2");
+ return true;
+ }
+ if (subtype == NativeMethods_MediaFoundation.MFVideoFormat_NV12)
+ {
+ formatInfo = new(PixelFormats.NV12, NativeMethods.Compression.NV12, 12, "NV12");
+ return true;
+ }
+
+ formatInfo = default;
+ return false;
+ }
+
+ public static bool TryCreateVideoCharacteristics(
+ IMFMediaType mediaType,
+ out VideoCharacteristics characteristics,
+ out FormatInfo formatInfo)
+ {
+ characteristics = null!;
+ formatInfo = default;
+
+ if (mediaType.GetGUID(
+ in NativeMethods_MediaFoundation.MF_MT_MAJOR_TYPE,
+ out var majorType) < 0 ||
+ majorType != NativeMethods_MediaFoundation.MFMediaType_Video)
+ {
+ return false;
+ }
+
+ if (mediaType.GetGUID(
+ in NativeMethods_MediaFoundation.MF_MT_SUBTYPE,
+ out var subtype) < 0 ||
+ !TryGetFormatInfo(subtype, out formatInfo))
+ {
+ return false;
+ }
+
+ if (mediaType.GetUINT64(
+ in NativeMethods_MediaFoundation.MF_MT_FRAME_SIZE,
+ out var frameSize) < 0)
+ {
+ return false;
+ }
+
+ var frameSizeValue = unchecked((ulong)frameSize);
+ var width = (int)(frameSizeValue >> 32);
+ var height = (int)(frameSizeValue & 0xffffffff);
+ if (width <= 0 || height <= 0)
+ {
+ return false;
+ }
+
+ var fps = Fraction.Create(30);
+ if (mediaType.GetUINT64(
+ in NativeMethods_MediaFoundation.MF_MT_FRAME_RATE,
+ out var frameRate) >= 0)
+ {
+ var frameRateValue = unchecked((ulong)frameRate);
+ var numerator = (int)(frameRateValue >> 32);
+ var denominator = (int)(frameRateValue & 0xffffffff);
+ if (numerator > 0 && denominator > 0)
+ {
+ fps = new Fraction(numerator, denominator).Reduce();
+ }
+ }
+
+ characteristics = new VideoCharacteristics(
+ formatInfo.PixelFormat,
+ width,
+ height,
+ fps,
+ formatInfo.Description,
+ true,
+ formatInfo.Description);
+ return true;
+ }
+}
diff --git a/FlashCap.Core/Internal/MediaFoundation/MediaFoundationNativeCom.cs b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationNativeCom.cs
new file mode 100644
index 0000000..449ab3c
--- /dev/null
+++ b/FlashCap.Core/Internal/MediaFoundation/MediaFoundationNativeCom.cs
@@ -0,0 +1,64 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace FlashCap.Internal.MediaFoundation;
+
+internal static unsafe class MediaFoundationNativeCom
+{
+ // Verified against Windows SDK 10.0.26100.0 mfobjects.h.
+ private const int IMFMediaBufferLockSlot = 3;
+ private const int IMFMediaBufferUnlockSlot = 4;
+ private const int IMFSampleConvertToContiguousBufferSlot = 41;
+
+ public static int ConvertToContiguousBuffer(IntPtr sample, out IntPtr buffer)
+ {
+ buffer = IntPtr.Zero;
+ fixed (IntPtr* pBuffer = &buffer)
+ {
+ var method = (delegate* unmanaged[Stdcall])GetVTableEntry(
+ sample,
+ IMFSampleConvertToContiguousBufferSlot);
+ return method(sample, pBuffer);
+ }
+ }
+
+ public static int Lock(
+ IntPtr mediaBuffer,
+ out IntPtr data,
+ out int maxLength,
+ out int currentLength)
+ {
+ data = IntPtr.Zero;
+ maxLength = 0;
+ currentLength = 0;
+ fixed (IntPtr* pData = &data)
+ fixed (int* pMaxLength = &maxLength)
+ fixed (int* pCurrentLength = ¤tLength)
+ {
+ var method = (delegate* unmanaged[Stdcall])GetVTableEntry(
+ mediaBuffer,
+ IMFMediaBufferLockSlot);
+ return method(mediaBuffer, pData, pMaxLength, pCurrentLength);
+ }
+ }
+
+ public static int Unlock(IntPtr mediaBuffer)
+ {
+ var method = (delegate* unmanaged[Stdcall])GetVTableEntry(
+ mediaBuffer,
+ IMFMediaBufferUnlockSlot);
+ return method(mediaBuffer);
+ }
+
+ private static IntPtr GetVTableEntry(IntPtr instance, int slot) =>
+ Marshal.ReadIntPtr(Marshal.ReadIntPtr(instance), slot * IntPtr.Size);
+}
diff --git a/FlashCap.Core/Internal/NativeMethods.cs b/FlashCap.Core/Internal/NativeMethods.cs
index dd65909..1d0fabe 100644
--- a/FlashCap.Core/Internal/NativeMethods.cs
+++ b/FlashCap.Core/Internal/NativeMethods.cs
@@ -9,8 +9,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Text;
@@ -19,7 +17,7 @@
namespace FlashCap.Internal;
[SuppressUnmanagedCodeSecurity]
-internal static class NativeMethods
+internal static partial class NativeMethods
{
// https://stackoverflow.com/questions/38790802/determine-operating-system-in-net-core
public enum Platforms
@@ -30,74 +28,40 @@ public enum Platforms
Other,
}
- private static Platforms GetRuntimePlatform()
- {
- var windir = Environment.GetEnvironmentVariable("windir");
- if (!string.IsNullOrEmpty(windir) &&
- windir.Contains(Path.DirectorySeparatorChar.ToString()) &&
- Directory.Exists(windir))
- {
- return Platforms.Windows;
- }
- else if (File.Exists(@"/proc/sys/kernel/ostype"))
- {
- var osType = File.ReadAllText(@"/proc/sys/kernel/ostype");
- if (osType.StartsWith("Linux", StringComparison.OrdinalIgnoreCase))
- {
- return Platforms.Linux;
- }
- else
- {
- return Platforms.Other;
- }
- }
- else if (File.Exists(@"/System/Library/CoreServices/SystemVersion.plist"))
- {
- return Platforms.MacOS;
- }
- else
- {
- return Platforms.Other;
- }
- }
-
- public static readonly Platforms CurrentPlatform =
- GetRuntimePlatform();
-
////////////////////////////////////////////////////////////////////////
// https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa366535(v=vs.85)
- [DllImport("ntdll")]
- private static extern void RtlCopyMemory(IntPtr dest, IntPtr src, IntPtr length);
- [DllImport("kernel32")]
- private static extern void RtlMoveMemory(IntPtr dest, IntPtr src, IntPtr length);
+ [LibraryImport("ntdll", EntryPoint = "RtlCopyMemory")]
+ private static partial void RtlCopyMemory(IntPtr dest, IntPtr src, IntPtr length);
+ [LibraryImport("kernel32", EntryPoint = "RtlMoveMemory")]
+ private static partial void RtlMoveMemory(IntPtr dest, IntPtr src, IntPtr length);
- [DllImport("libc")]
- private static extern void memcpy(IntPtr dest, IntPtr src, IntPtr length);
+ [LibraryImport("libc", EntryPoint = "memcpy")]
+ private static partial void memcpy(IntPtr dest, IntPtr src, IntPtr length);
public delegate void CopyMemoryDelegate(
IntPtr pDestination, IntPtr pSource, IntPtr length);
public static unsafe readonly CopyMemoryDelegate CopyMemory =
- CurrentPlatform == Platforms.Windows ?
+ OperatingSystem.IsWindows() ?
(IntPtr.Size == 4 ? RtlMoveMemory : RtlCopyMemory) :
memcpy;
////////////////////////////////////////////////////////////////////////
- [DllImport("ole32")]
- private static extern IntPtr CoTaskMemAlloc(IntPtr size);
- [DllImport("ole32")]
- private static extern void CoTaskMemFree(IntPtr ptr);
- [DllImport("kernel32")]
- private static extern void RtlZeroMemory(IntPtr ptr, IntPtr size);
+ [LibraryImport("ole32", EntryPoint = "CoTaskMemAlloc")]
+ private static partial IntPtr CoTaskMemAlloc(IntPtr size);
+ [LibraryImport("ole32", EntryPoint = "CoTaskMemFree")]
+ private static partial void CoTaskMemFree(IntPtr ptr);
+ [LibraryImport("kernel32", EntryPoint = "RtlZeroMemory")]
+ private static partial void RtlZeroMemory(IntPtr ptr, IntPtr size);
- [DllImport("libc")]
- private static extern IntPtr malloc(IntPtr size);
- [DllImport("libc")]
- private static extern void free(IntPtr ptr);
- [DllImport("libc")]
- private static extern IntPtr memset(IntPtr ptr, int c, IntPtr size);
+ [LibraryImport("libc", EntryPoint = "malloc")]
+ private static partial IntPtr malloc(IntPtr size);
+ [LibraryImport("libc", EntryPoint = "free")]
+ private static partial void free(IntPtr ptr);
+ [LibraryImport("libc", EntryPoint = "memset")]
+ private static partial IntPtr memset(IntPtr ptr, int c, IntPtr size);
public delegate IntPtr AllocateMemoryDelegate(
IntPtr size);
@@ -118,10 +82,10 @@ private static IntPtr AllocatePosix(IntPtr size)
}
public static readonly AllocateMemoryDelegate AllocateMemory =
- CurrentPlatform == Platforms.Windows ?
+ OperatingSystem.IsWindows() ?
AllocateWindows : AllocatePosix;
public static readonly FreeMemoryDelegate FreeMemory =
- CurrentPlatform == Platforms.Windows ?
+ OperatingSystem.IsWindows() ?
CoTaskMemFree : free;
////////////////////////////////////////////////////////////////////////
@@ -135,12 +99,12 @@ public enum COINIT
SPEED_OVER_MEMORY = 8,
}
- [DllImport("ole32", SetLastError=true)]
- public static extern int CoInitializeEx(
+ [LibraryImport("ole32", SetLastError=true)]
+ public static partial int CoInitializeEx(
IntPtr pvReserved, COINIT dwCoInit);
- [DllImport("ole32", SetLastError=true)]
- public static extern void CoUninitialize();
+ [LibraryImport("ole32", SetLastError=true)]
+ public static partial void CoUninitialize();
////////////////////////////////////////////////////////////////////////
diff --git a/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs b/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs
index cdef71b..198cdcf 100644
--- a/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs
+++ b/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs
@@ -12,13 +12,15 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using FlashCap.Internal.AVFoundation;
namespace FlashCap.Internal;
-internal static class NativeMethods_AVFoundation
+[SupportedOSPlatform("macos")]
+internal static partial class NativeMethods_AVFoundation
{
public static readonly Dictionary PixelFormatMap = new()
{
@@ -33,23 +35,23 @@ internal static class NativeMethods_AVFoundation
};
- public static class Dlfcn
+ public static partial class Dlfcn
{
// Loads the framework
- [DllImport("libdl.dylib", CharSet = CharSet.Ansi)]
- public static extern IntPtr dlopen(string path, int mode);
+ [LibraryImport("libdl.dylib", EntryPoint = "dlopen", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr dlopen(string path, int mode);
- [DllImport("libdl.dylib")]
- public static extern IntPtr dlsym(IntPtr handle, string symbol);
+ [LibraryImport("libdl.dylib", EntryPoint = "dlsym", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr dlsym(IntPtr handle, string symbol);
- [DllImport(LibSystem.Path, EntryPoint = "dlopen")]
- public static extern IntPtr OpenLibrary(string path, Mode mode);
+ [LibraryImport(LibSystem.Path, EntryPoint = "dlopen", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr OpenLibrary(string path, Mode mode);
- [DllImport(LibSystem.Path, EntryPoint = "dlsym")]
- public static extern IntPtr GetSymbol(IntPtr handle, string symbol);
+ [LibraryImport(LibSystem.Path, EntryPoint = "dlsym", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr GetSymbol(IntPtr handle, string symbol);
public static IntPtr GetSymbolIndirect(IntPtr handle, string symbol) =>
- GetSymbol(LibAVFoundation.Handle, symbol) is var indirect && indirect != IntPtr.Zero
+ GetSymbol(handle, symbol) is var indirect && indirect != IntPtr.Zero
? Marshal.ReadIntPtr(indirect)
: IntPtr.Zero;
@@ -61,14 +63,14 @@ public enum Mode : int
}
}
- public static class LibSystem
+ public static partial class LibSystem
{
public const string Path = "/usr/lib/libSystem.dylib";
public static readonly IntPtr Handle = Dlfcn.OpenLibrary(Path, Dlfcn.Mode.None);
- [DllImport(Path, EntryPoint = "dispatch_queue_create")]
- public static extern IntPtr dispatch_queue_create(string label, IntPtr attr);
+ [LibraryImport(Path, EntryPoint = "dispatch_queue_create", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr dispatch_queue_create(string label, IntPtr attr);
public static readonly bool IsOnArm64;
@@ -79,8 +81,8 @@ static unsafe LibSystem()
NXGetLocalArchInfo()->GetName()?.StartsWith("arm64", StringComparison.OrdinalIgnoreCase) is true;
}
- [DllImport(Path)]
- private static extern unsafe NXArchInfo* NXGetLocalArchInfo();
+ [LibraryImport(Path, EntryPoint = "NXGetLocalArchInfo")]
+ private static unsafe partial NXArchInfo* NXGetLocalArchInfo();
private enum NXByteOrder
{
@@ -117,21 +119,21 @@ private struct NXArchInfo
}
}
- private static class LibC
+ private static partial class LibC
{
private const string Path = "/usr/lib/libc.dylib";
- [DllImport(Path, EntryPoint = "dispatch_queue_create")]
- public static extern IntPtr DispatchQueueCreate(string label, IntPtr attr);
+ [LibraryImport(Path, EntryPoint = "dispatch_queue_create", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr DispatchQueueCreate(string label, IntPtr attr);
- [DllImport(Path, EntryPoint = "dispatch_release")]
- public static extern IntPtr DispatchRelease(IntPtr o);
+ [LibraryImport(Path, EntryPoint = "dispatch_release")]
+ public static partial IntPtr DispatchRelease(IntPtr o);
- [DllImport(Path, EntryPoint = "dispatch_retain")]
- public static extern IntPtr DispatchRetain(IntPtr o);
+ [LibraryImport(Path, EntryPoint = "dispatch_retain")]
+ public static partial IntPtr DispatchRetain(IntPtr o);
}
- public static class LibObjC
+ public static partial class LibObjC
{
private const string Path = "/usr/lib/libobjc.A.dylib";
@@ -139,95 +141,98 @@ public static class LibObjC
public const string AllocSelector = "alloc";
public const string ReleaseSelector = "release";
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern void SendNoResult(IntPtr receiver, IntPtr selector, bool arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial void SendNoResult(IntPtr receiver, IntPtr selector, [MarshalAs(UnmanagedType.I1)] bool arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern void SendNoResult(IntPtr receiver, IntPtr selector);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial void SendNoResult(IntPtr receiver, IntPtr selector);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern void SendNoResult(IntPtr receiver, IntPtr selector, IntPtr arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial void SendNoResult(IntPtr receiver, IntPtr selector, IntPtr arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern void SendNoResult(IntPtr receiver, IntPtr selector, LibCoreMedia.CMTime arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial void SendNoResult(IntPtr receiver, IntPtr selector, LibCoreMedia.CMTime arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern void SendNoResult(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial void SendNoResult(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern bool SendAndGetBool(IntPtr receiver, IntPtr selector);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool SendAndGetBool(IntPtr receiver, IntPtr selector);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern bool SendAndGetBool(IntPtr receiver, IntPtr selector, IntPtr arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ [return: MarshalAs(UnmanagedType.I1)]
+ public static partial bool SendAndGetBool(IntPtr receiver, IntPtr selector, IntPtr arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, int arg1);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, int arg1);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2, long arg3);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial IntPtr SendAndGetHandle(IntPtr receiver, IntPtr selector, IntPtr arg1, IntPtr arg2, long arg3);
- [DllImport(Path, EntryPoint = "objc_msgSend")]
- public static extern LibCoreMedia.CMTime SendAndGetCMTime(IntPtr receiver, IntPtr selector);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend")]
+ public static partial LibCoreMedia.CMTime SendAndGetCMTime(IntPtr receiver, IntPtr selector);
- [DllImport(Path, EntryPoint = "objc_msgSend_stret")]
- public static extern void SendAndGetCMTimeStret(out LibCoreMedia.CMTime result, IntPtr receiver, IntPtr selector);
+ [LibraryImport(Path, EntryPoint = "objc_msgSend_stret")]
+ public static partial void SendAndGetCMTimeStret(out LibCoreMedia.CMTime result, IntPtr receiver, IntPtr selector);
- [DllImport(Path, EntryPoint = "objc_getClass")]
- public static extern IntPtr GetClass(string name);
+ [LibraryImport(Path, EntryPoint = "objc_getClass", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr GetClass(string name);
- [DllImport(Path, EntryPoint = "object_getIvar")]
- public static extern IntPtr GetVariable(IntPtr obj, IntPtr ivar);
+ [LibraryImport(Path, EntryPoint = "object_getIvar")]
+ public static partial IntPtr GetVariable(IntPtr obj, IntPtr ivar);
- [DllImport(Path, EntryPoint = "object_setIvar")]
- public static extern void SetVariable(IntPtr obj, IntPtr ivar, IntPtr value);
+ [LibraryImport(Path, EntryPoint = "object_setIvar")]
+ public static partial void SetVariable(IntPtr obj, IntPtr ivar, IntPtr value);
- [DllImport(Path, EntryPoint = "objc_allocateClassPair")]
- public static extern IntPtr AllocateClass(IntPtr superclass, string name, IntPtr extraBytes);
+ [LibraryImport(Path, EntryPoint = "objc_allocateClassPair", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr AllocateClass(IntPtr superclass, string name, IntPtr extraBytes);
- [DllImport(Path, EntryPoint = "objc_registerClassPair")]
- public static extern void RegisterClass(IntPtr cls);
+ [LibraryImport(Path, EntryPoint = "objc_registerClassPair")]
+ public static partial void RegisterClass(IntPtr cls);
- [DllImport(Path, EntryPoint = "objc_getProtocol")]
- public static extern IntPtr GetProtocol(string name);
+ [LibraryImport(Path, EntryPoint = "objc_getProtocol", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr GetProtocol(string name);
- [DllImport(Path, EntryPoint = "sel_registerName")]
- public static extern IntPtr GetSelector(string name);
+ [LibraryImport(Path, EntryPoint = "sel_registerName", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr GetSelector(string name);
- [DllImport(Path, EntryPoint = "class_addMethod")]
+ [LibraryImport(Path, EntryPoint = "class_addMethod", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
- public static extern bool AddMethod(IntPtr cls, IntPtr name, IntPtr imp, string types);
+ public static partial bool AddMethod(IntPtr cls, IntPtr name, IntPtr imp, string types);
- [DllImport(Path, EntryPoint = "class_addIvar")]
+ [LibraryImport(Path, EntryPoint = "class_addIvar", StringMarshalling = StringMarshalling.Utf8)]
[return: MarshalAs(UnmanagedType.U1)]
- public static extern void AddVariable(IntPtr cls, string name, IntPtr size, byte alignment, string types);
+ public static partial void AddVariable(IntPtr cls, string name, IntPtr size, byte alignment, string types);
- [DllImport(Path, EntryPoint = "class_addProtocol")]
+ [LibraryImport(Path, EntryPoint = "class_addProtocol")]
[return: MarshalAs(UnmanagedType.U1)]
- public static extern bool AddProtocol(IntPtr cls, IntPtr protocol);
+ public static partial bool AddProtocol(IntPtr cls, IntPtr protocol);
- [DllImport(Path, EntryPoint = "class_getInstanceVariable")]
- public static extern IntPtr GetVariable(IntPtr cls, string name);
+ [LibraryImport(Path, EntryPoint = "class_getInstanceVariable", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr GetVariable(IntPtr cls, string name);
- [DllImport(Path, EntryPoint = "objc_allocateClassPair")]
- public static extern IntPtr objc_allocateClassPair(IntPtr superClass, string name, IntPtr extraBytes);
+ [LibraryImport(Path, EntryPoint = "objc_allocateClassPair", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr objc_allocateClassPair(IntPtr superClass, string name, IntPtr extraBytes);
- [DllImport(Path, EntryPoint = "class_addMethod")]
- public static extern bool class_addMethod(IntPtr cls, IntPtr sel, IntPtr imp, string types);
+ [LibraryImport(Path, EntryPoint = "class_addMethod", StringMarshalling = StringMarshalling.Utf8)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool class_addMethod(IntPtr cls, IntPtr sel, IntPtr imp, string types);
- [DllImport(Path, EntryPoint = "objc_registerClassPair")]
- public static extern void objc_registerClassPair(IntPtr cls);
+ [LibraryImport(Path, EntryPoint = "objc_registerClassPair")]
+ public static partial void objc_registerClassPair(IntPtr cls);
- [DllImport(Path, EntryPoint = "dispatch_queue_create")]
- public static extern IntPtr dispatch_queue_create(string label, IntPtr attr);
+ [LibraryImport(Path, EntryPoint = "dispatch_queue_create", StringMarshalling = StringMarshalling.Utf8)]
+ public static partial IntPtr dispatch_queue_create(string label, IntPtr attr);
[Flags]
public enum BlockFlags
@@ -423,7 +428,7 @@ public NSError(IntPtr handle, bool retain) :
}
}
- public static class LibCoreFoundation
+ public static partial class LibCoreFoundation
{
private const string Path = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
@@ -432,45 +437,54 @@ public static class LibCoreFoundation
public static readonly IntPtr kCFCopyStringDictionaryKeyCallBacks = Dlfcn.GetSymbolIndirect(Handle, "kCFCopyStringDictionaryKeyCallBacks");
public static readonly IntPtr kCFTypeDictionaryValueCallBacks = Dlfcn.GetSymbolIndirect(Handle, "kCFTypeDictionaryValueCallBacks");
- [DllImport(Path)]
- public static extern void CFRelease(IntPtr cf);
+ [LibraryImport(Path, EntryPoint = "CFRelease")]
+ public static partial void CFRelease(IntPtr cf);
- [DllImport(Path)]
- public static extern IntPtr CFGetTypeID(IntPtr cf);
+ [LibraryImport(Path, EntryPoint = "CFGetTypeID")]
+ public static partial IntPtr CFGetTypeID(IntPtr cf);
- [DllImport(Path)]
- public static extern void CFRetain(IntPtr cf);
+ [LibraryImport(Path, EntryPoint = "CFRetain")]
+ public static partial void CFRetain(IntPtr cf);
- [DllImport(Path)]
- public static extern IntPtr CFArrayCreate(IntPtr allocator, IntPtr values, nint numValues, IntPtr callBacks);
+ [LibraryImport(Path, EntryPoint = "CFArrayCreate")]
+ public static partial IntPtr CFArrayCreate(IntPtr allocator, IntPtr values, nint numValues, IntPtr callBacks);
- [DllImport(Path)]
- public static extern IntPtr CFArrayGetCount(IntPtr theArray);
+ [LibraryImport(Path, EntryPoint = "CFArrayGetCount")]
+ public static partial IntPtr CFArrayGetCount(IntPtr theArray);
- [DllImport(Path)]
- public static extern void CFArrayGetValues(IntPtr theArray, CFRange range, IntPtr values);
+ [LibraryImport(Path, EntryPoint = "CFArrayGetValues")]
+ public static partial void CFArrayGetValues(IntPtr theArray, CFRange range, IntPtr values);
- [DllImport(Path)]
- public static extern IntPtr CFStringGetLength(IntPtr theString);
+ [LibraryImport(Path, EntryPoint = "CFStringGetLength")]
+ public static partial IntPtr CFStringGetLength(IntPtr theString);
- [DllImport(Path)]
- public static extern unsafe char* CFStringGetCharactersPtr(IntPtr theString);
+ [LibraryImport(Path, EntryPoint = "CFStringGetCharactersPtr")]
+ public static unsafe partial char* CFStringGetCharactersPtr(IntPtr theString);
- [DllImport(Path)]
- public static extern unsafe void CFStringGetCharacters(IntPtr theString, CFRange range, char* buffer);
+ [LibraryImport(Path, EntryPoint = "CFStringGetCharacters")]
+ public static unsafe partial void CFStringGetCharacters(IntPtr theString, CFRange range, char* buffer);
- [DllImport(Path)]
- public static extern unsafe IntPtr CFStringCreateWithCharacters(IntPtr allocator, char* str, nint count);
+ [LibraryImport(Path, EntryPoint = "CFStringCreateWithCharacters")]
+ public static unsafe partial IntPtr CFStringCreateWithCharacters(IntPtr allocator, char* str, nint count);
- [DllImport(Path)]
- public static extern unsafe IntPtr CFNumberCreate(IntPtr allocator, CFNumberType theType, void* valuePtr);
+ [LibraryImport(Path, EntryPoint = "CFNumberCreate")]
+ public static unsafe partial IntPtr CFNumberCreate(IntPtr allocator, CFNumberType theType, void* valuePtr);
- [DllImport(Path)]
+ [LibraryImport(Path, EntryPoint = "CFNumberGetValue")]
[return: MarshalAs(UnmanagedType.U1)]
- public static extern unsafe bool CFNumberGetValue(IntPtr number, CFNumberType theType, void* valuePtr);
+ public static unsafe partial bool CFNumberGetValue(IntPtr number, CFNumberType theType, void* valuePtr);
- [DllImport(Path)]
- public static extern unsafe IntPtr CFDictionaryCreate(IntPtr allocator, IntPtr[] keys, IntPtr[] values, nint numValues, IntPtr keyCallBacks, IntPtr valueCallBacks);
+ [LibraryImport(Path, EntryPoint = "CFDictionaryCreate")]
+ private static unsafe partial IntPtr CFDictionaryCreate(IntPtr allocator, IntPtr* keys, IntPtr* values, nint numValues, IntPtr keyCallBacks, IntPtr valueCallBacks);
+
+ public static unsafe IntPtr CFDictionaryCreate(IntPtr allocator, IntPtr[] keys, IntPtr[] values, nint numValues, IntPtr keyCallBacks, IntPtr valueCallBacks)
+ {
+ fixed (IntPtr* keysPointer = keys)
+ fixed (IntPtr* valuesPointer = values)
+ {
+ return CFDictionaryCreate(allocator, keysPointer, valuesPointer, numValues, keyCallBacks, valueCallBacks);
+ }
+ }
[StructLayout(LayoutKind.Sequential)]
public struct CFRange
@@ -633,18 +647,18 @@ public enum CMBlockBufferError : int {
InsufficientSpace = -12708,
}
- public static class LibCoreMedia
+ public static partial class LibCoreMedia
{
private const string Path = "/System/Library/Frameworks/CoreMedia.framework/CoreMedia";
- [DllImport(Path)]
- public static extern IntPtr CMSampleBufferGetAttachments(IntPtr sampleBuffer, int makeWritable);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetAttachments")]
+ public static partial IntPtr CMSampleBufferGetAttachments(IntPtr sampleBuffer, int makeWritable);
- [DllImport(Path)]
- public static extern IntPtr CMGetAttachment(IntPtr target, IntPtr key, out CMAttachmentMode attachmentMode);
+ [LibraryImport(Path, EntryPoint = "CMGetAttachment")]
+ public static partial IntPtr CMGetAttachment(IntPtr target, IntPtr key, out CMAttachmentMode attachmentMode);
- [DllImport(Path, EntryPoint = "CMFormatDescriptionGetMediaType", CallingConvention = CallingConvention.Cdecl)]
- public static extern uint CmFormatDescriptionGetMediaTypeIntCode(IntPtr formatDescription);
+ [LibraryImport(Path, EntryPoint = "CMFormatDescriptionGetMediaType")]
+ public static partial uint CmFormatDescriptionGetMediaTypeIntCode(IntPtr formatDescription);
// Add this enum
public enum CMAttachmentMode
@@ -656,44 +670,45 @@ public enum CMAttachmentMode
// Add this constant
public static readonly FourCharCode kCMMediaType_Video = new FourCharCode('v', 'i', 'd', 'e');
- [DllImport(Path)]
- public static extern CMTime CMTimeMake(long value, int timescale);
+ [LibraryImport(Path, EntryPoint = "CMTimeMake")]
+ public static partial CMTime CMTimeMake(long value, int timescale);
- [DllImport(Path)]
- public static extern double CMTimeGetSeconds(CMTime time);
+ [LibraryImport(Path, EntryPoint = "CMTimeGetSeconds")]
+ public static partial double CMTimeGetSeconds(CMTime time);
- [DllImport(Path)]
- public static extern IntPtr CMSampleBufferGetImageBuffer(IntPtr sbuf);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetImageBuffer")]
+ public static partial IntPtr CMSampleBufferGetImageBuffer(IntPtr sbuf);
- [DllImport(Path)]
- public static extern IntPtr CMSampleBufferGetDataBuffer(IntPtr sbuf);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetDataBuffer")]
+ public static partial IntPtr CMSampleBufferGetDataBuffer(IntPtr sbuf);
- [DllImport(Path)]
- public static extern bool CMSampleBufferIsValid(IntPtr sbuf);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferIsValid")]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static partial bool CMSampleBufferIsValid(IntPtr sbuf);
- [DllImport(Path)]
- public static extern IntPtr CMSampleBufferGetFormatDescription(IntPtr sbuf);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetFormatDescription")]
+ public static partial IntPtr CMSampleBufferGetFormatDescription(IntPtr sbuf);
- [DllImport(Path)]
- public static extern CMTime CMSampleBufferGetDecodeTimeStamp(IntPtr sbuf);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetDecodeTimeStamp")]
+ public static partial CMTime CMSampleBufferGetDecodeTimeStamp(IntPtr sbuf);
- [DllImport(Path)]
- public static extern CMBlockBufferError CMBlockBufferGetDataPointer(IntPtr theBuffer, nuint offset, out IntPtr lengthAtOffset, out IntPtr totalLength, out IntPtr dataPointer);
+ [LibraryImport(Path, EntryPoint = "CMBlockBufferGetDataPointer")]
+ public static partial CMBlockBufferError CMBlockBufferGetDataPointer(IntPtr theBuffer, nuint offset, out IntPtr lengthAtOffset, out IntPtr totalLength, out IntPtr dataPointer);
- [DllImport(Path)]
- public static extern nuint CMBlockBufferGetDataLength(IntPtr theBuffer);
+ [LibraryImport(Path, EntryPoint = "CMBlockBufferGetDataLength")]
+ public static partial nuint CMBlockBufferGetDataLength(IntPtr theBuffer);
- [DllImport(Path, CallingConvention = CallingConvention.Cdecl)]
- public static extern CMMediaType CMFormatDescriptionGetMediaType(IntPtr desc);
+ [LibraryImport(Path, EntryPoint = "CMFormatDescriptionGetMediaType")]
+ public static partial CMMediaType CMFormatDescriptionGetMediaType(IntPtr desc);
- [DllImport(Path)]
- public static extern IntPtr CMSampleBufferGetSampleAttachmentsArray(IntPtr sampleBuffer, bool createIfNecessary);
+ [LibraryImport(Path, EntryPoint = "CMSampleBufferGetSampleAttachmentsArray")]
+ public static partial IntPtr CMSampleBufferGetSampleAttachmentsArray(IntPtr sampleBuffer, [MarshalAs(UnmanagedType.U1)] bool createIfNecessary);
- [DllImport(Path)]
- public static extern uint CMFormatDescriptionGetMediaSubType(IntPtr desc);
+ [LibraryImport(Path, EntryPoint = "CMFormatDescriptionGetMediaSubType")]
+ public static partial uint CMFormatDescriptionGetMediaSubType(IntPtr desc);
- [DllImport(Path)]
- public static extern CMVideoDimensions CMVideoFormatDescriptionGetDimensions(IntPtr videoDesc);
+ [LibraryImport(Path, EntryPoint = "CMVideoFormatDescriptionGetDimensions")]
+ public static partial CMVideoDimensions CMVideoFormatDescriptionGetDimensions(IntPtr videoDesc);
public enum CMMediaType : uint
{
@@ -760,7 +775,7 @@ public CMFormatDescription(IntPtr handle) :
}
}
- public static class LibCoreVideo
+ public static partial class LibCoreVideo
{
private const string Path = "/System/Library/Frameworks/CoreVideo.framework/CoreVideo";
@@ -771,20 +786,20 @@ public static class LibCoreVideo
public static readonly IntPtr kCVPixelBufferMetalCompatibilityKey = Dlfcn.GetSymbolIndirect(Handle, "kCVPixelBufferMetalCompatibilityKey");
- [DllImport(Path)]
- public static extern nuint CVPixelBufferGetDataSize(IntPtr pixelBuffer);
+ [LibraryImport(Path, EntryPoint = "CVPixelBufferGetDataSize")]
+ public static partial nuint CVPixelBufferGetDataSize(IntPtr pixelBuffer);
- [DllImport(Path)]
- public static extern nuint CVPixelBufferGetPlaneCount(IntPtr pixelBuffer);
+ [LibraryImport(Path, EntryPoint = "CVPixelBufferGetPlaneCount")]
+ public static partial nuint CVPixelBufferGetPlaneCount(IntPtr pixelBuffer);
- [DllImport(Path)]
- public static extern IntPtr CVPixelBufferGetBaseAddress(IntPtr pixelBuffer);
+ [LibraryImport(Path, EntryPoint = "CVPixelBufferGetBaseAddress")]
+ public static partial IntPtr CVPixelBufferGetBaseAddress(IntPtr pixelBuffer);
- [DllImport(Path)]
- public static extern int CVPixelBufferLockBaseAddress(IntPtr pixelBuffer, PixelBufferLockFlags lockFlags);
+ [LibraryImport(Path, EntryPoint = "CVPixelBufferLockBaseAddress")]
+ public static partial int CVPixelBufferLockBaseAddress(IntPtr pixelBuffer, PixelBufferLockFlags lockFlags);
- [DllImport(Path)]
- public static extern int CVPixelBufferUnlockBaseAddress(IntPtr pixelBuffer, PixelBufferLockFlags lockFlags);
+ [LibraryImport(Path, EntryPoint = "CVPixelBufferUnlockBaseAddress")]
+ public static partial int CVPixelBufferUnlockBaseAddress(IntPtr pixelBuffer, PixelBufferLockFlags lockFlags);
public enum PixelBufferLockFlags : long
{
diff --git a/FlashCap.Core/Internal/NativeMethods_DirectShow.cs b/FlashCap.Core/Internal/NativeMethods_DirectShow.cs
deleted file mode 100644
index 2d1dc8d..0000000
--- a/FlashCap.Core/Internal/NativeMethods_DirectShow.cs
+++ /dev/null
@@ -1,1025 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Security;
-using FlashCap.Utilities;
-using static FlashCap.Devices.DirectShowDevice;
-
-namespace FlashCap.Internal;
-
-[SuppressUnmanagedCodeSecurity]
-internal static class NativeMethods_DirectShow
-{
- [SuppressUnmanagedCodeSecurity]
- [Guid("0000010c-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IPersist
- {
- [PreserveSig] int GetClassID(out Guid classID);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("00000109-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IPersistStream : IPersist
- {
- [PreserveSig] new int GetClassID(out Guid classID);
-
- [PreserveSig] int IsDirty();
- [PreserveSig] int Load(System.Runtime.InteropServices.ComTypes.IStream stm);
- [PreserveSig] int Save(System.Runtime.InteropServices.ComTypes.IStream stm, bool clearDirty);
- [PreserveSig] int GetSizeMax(out long size);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("0000000f-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMoniker : IPersistStream
- {
- [PreserveSig] new int GetClassID(out Guid classID);
- [PreserveSig] new int IsDirty();
- [PreserveSig] new int Load(System.Runtime.InteropServices.ComTypes.IStream stm);
- [PreserveSig] new int Save(System.Runtime.InteropServices.ComTypes.IStream stm, bool clearDirty);
- [PreserveSig] new int GetSizeMax(out long size);
-
- [PreserveSig] int BindToObject(
- System.Runtime.InteropServices.ComTypes.IBindCtx? bindContext,
- IMoniker? makeToLeft,
- in Guid riidResult,
- [MarshalAs(UnmanagedType.Interface)] out object? result);
- [PreserveSig] int BindToStorage(
- System.Runtime.InteropServices.ComTypes.IBindCtx? bindContext,
- IMoniker? makeToLeft,
- in Guid riidResult,
- [MarshalAs(UnmanagedType.Interface)] out object? result);
-
- // truncated.
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("00000102-0000-0000-C000-000000000046")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IEnumMoniker
- {
- [PreserveSig] int Next(
- int request,
- [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] IMoniker?[] monikers,
- out int fetched);
- [PreserveSig] int Skip(int count);
- [PreserveSig] int Reset();
- [PreserveSig] int Clone(out IEnumMoniker? enumMoniker);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("29840822-5B84-11D0-BD3B-00A0C911CE86")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface ICreateDevEnum
- {
- [PreserveSig] int CreateClassEnumerator(
- in Guid type,
- out IEnumMoniker? enumMoniker,
- uint flags);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86897-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IReferenceClock
- {
- [PreserveSig] int GetTime(out long time);
- [PreserveSig] int AdviseTime(
- long baseTime,
- long streamTime,
- IntPtr hEvent,
- out IntPtr adviseCookie);
- [PreserveSig] int AdvisePeriodic(
- long startTime,
- long periodTime,
- IntPtr hSemaphore,
- out IntPtr adviseCookie);
- [PreserveSig] int Unadvise(IntPtr adviseCookie);
- }
-
- public enum FILTER_STATE
- {
- Stopped,
- Paused,
- Running,
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86899-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMediaFilter : IPersist
- {
- [PreserveSig] new void GetClassID(out Guid classID);
-
- [PreserveSig] int Stop();
- [PreserveSig] int Pause();
- [PreserveSig] int Run(long tStart);
- [PreserveSig] int GetState(uint milliSecsTimeout, out FILTER_STATE state);
- [PreserveSig] int SetSyncSource(IReferenceClock clock);
- [PreserveSig] int GetSyncSource(out IReferenceClock? clock);
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct AM_MEDIA_TYPE
- {
- public Guid majortype; // MEDIATYPE_*
- public Guid subtype; // MEDIASUBTYPE_*
- public int fixedSizeSamples; //Made blittable: [MarshalAs(UnmanagedType.Bool)] public bool fixedSizeSamples;
- public int temporalCompression; //Made blittable: [MarshalAs(UnmanagedType.Bool)] public bool temporalCompression;
- public int sampleSize;
- public Guid formattype; // FORMATTYPE_*
- public IntPtr pUnk;
- public int formatSize;
- public IntPtr pFormat; // VIDEOINFOHEADER / VIDEOINFOHEADER2
-
- public void Release()
- {
- if (this.pUnk != IntPtr.Zero)
- {
- Marshal.Release(this.pUnk);
- this.pUnk = IntPtr.Zero;
- }
- if (this.pFormat != IntPtr.Zero)
- {
- NativeMethods.FreeMemory(this.pFormat);
- this.pFormat = IntPtr.Zero;
- }
- }
-
- public unsafe IntPtr AllocateAndGetBih()
- {
- if (this.formattype == FORMAT_VideoInfo)
- {
- var pVih = (NativeMethods.VIDEOINFOHEADER*)this.pFormat.ToPointer();
- var pBih = (NativeMethods.BITMAPINFOHEADER*)(pVih + 1);
- var pBihCopied = NativeMethods.AllocateMemory((IntPtr)pBih->biSize);
- NativeMethods.CopyMemory(pBihCopied, (IntPtr)pBih, (IntPtr)pBih->biSize);
- return pBihCopied;
- }
- else if (this.formattype == FORMAT_VideoInfo2)
- {
- var pVih = (NativeMethods.VIDEOINFOHEADER2*)this.pFormat.ToPointer();
- var pBih = (NativeMethods.BITMAPINFOHEADER*)(pVih + 1);
- var pBihCopied = NativeMethods.AllocateMemory((IntPtr)pBih->biSize);
- NativeMethods.CopyMemory(pBihCopied, (IntPtr)pBih, (IntPtr)pBih->biSize);
- return pBihCopied;
- }
- else
- {
- throw new ArgumentException();
- }
- }
- }
-
- public enum PIN_DIRECTION
- {
- Input,
- Output,
- }
-
- [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
- public struct PIN_INFO
- {
- public IBaseFilter filter;
- public PIN_DIRECTION dir;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string name;
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("89c31040-846b-11ce-97d3-00aa0055595a")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IEnumMediaTypes
- {
- [PreserveSig] int Next(
- int request,
- [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] AM_MEDIA_TYPE[] mediaTypes,
- out int fetched);
- [PreserveSig] int Skip(int count);
- [PreserveSig] int Reset();
- [PreserveSig] int Clone(out IEnumMediaTypes? enumMediaTypes);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86891-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IPin
- {
- [PreserveSig] int Connect(
- IPin receivePin,
- in AM_MEDIA_TYPE mt);
- [PreserveSig] int ReceiveConnection(
- IPin pReceivePin,
- in AM_MEDIA_TYPE mt);
- [PreserveSig] int Disconnect();
- [PreserveSig] int ConnectedTo(out IPin? pin);
- [PreserveSig] int ConnectionMediaType(out AM_MEDIA_TYPE mt);
- [PreserveSig] int QueryPinInfo(out PIN_INFO info);
- [PreserveSig] int QueryDirection(out PIN_DIRECTION pinDir);
- [PreserveSig] int QueryId([MarshalAs(UnmanagedType.LPWStr)] out string id);
- [PreserveSig] int QueryAccept(in AM_MEDIA_TYPE mt);
- [PreserveSig] int EnumMediaTypes(out IEnumMediaTypes? enumMediaTypes);
- [PreserveSig] int QueryInternalConnections(
- [MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] out IPin?[] pins,
- ref int pin);
- [PreserveSig] int EndOfStream();
- [PreserveSig] int BeginFlush();
- [PreserveSig] int EndFlush();
- [PreserveSig] int NewSegment(
- long tStart,
- long tStop,
- double rate);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86892-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IEnumPins
- {
- [PreserveSig] int Next(
- int request,
- [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] IPin?[] pins,
- out int fetched);
- [PreserveSig] int Skip(int count);
- [PreserveSig] int Reset();
- [PreserveSig] int Clone(out IEnumPins? enumPins);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86893-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IEnumFilters
- {
- [PreserveSig] int Next(
- int request,
- [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0)] IPin?[] filters,
- out int fetched);
- [PreserveSig] int Skip(int count);
- [PreserveSig] int Reset();
- [PreserveSig] int Clone(out IEnumPins? enumPins);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a8689f-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IFilterGraph
- {
- [PreserveSig] int AddFilter(
- IBaseFilter filter,
- [MarshalAs(UnmanagedType.LPWStr)] string name);
- [PreserveSig] int RemoveFilter(IBaseFilter filter);
- [PreserveSig] int EnumFilters(out IEnumFilters? ppEnum);
- [PreserveSig] int FindFilterByName(
- [MarshalAs(UnmanagedType.LPWStr)] string name,
- out IBaseFilter? filter);
- [PreserveSig] int ConnectDirect(
- IPin pinOut, IPin pinIn, in AM_MEDIA_TYPE mt);
- [PreserveSig] int Reconnect(IPin pin);
- [PreserveSig] int Disconnect(IPin pin);
- [PreserveSig] int SetDefaultSyncSource();
- }
-
- [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
- public struct FILTER_INFO
- {
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)] public string chName;
- public IFilterGraph graph;
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a86895-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IBaseFilter : IMediaFilter
- {
- [PreserveSig] new int GetClassID(out Guid classID);
- [PreserveSig] new int Stop();
- [PreserveSig] new int Pause();
- [PreserveSig] new int Run(long tStart);
- [PreserveSig] new int GetState(uint milliSecsTimeout, out FILTER_STATE state);
- [PreserveSig] new int SetSyncSource(IReferenceClock clock);
- [PreserveSig] new int GetSyncSource(out IReferenceClock? clock);
-
- [PreserveSig] int EnumPins(out IEnumPins? enumPins);
- [PreserveSig] int FindPin(
- [MarshalAs(UnmanagedType.LPWStr)] string id,
- out IPin? pin);
- [PreserveSig] int QueryFilterInfo(out FILTER_INFO info);
- [PreserveSig] int JoinFilterGraph(
- IFilterGraph graph,
- [MarshalAs(UnmanagedType.LPWStr)] string name);
- [PreserveSig] int QueryVendorInfo(
- [MarshalAs(UnmanagedType.LPWStr)] out string vendorInfo);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("3127CA40-446E-11CE-8135-00AA004BB851")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IErrorLog
- {
- [PreserveSig] int AddError(
- [MarshalAs(UnmanagedType.LPWStr)] string propName,
- in System.Runtime.InteropServices.ComTypes.EXCEPINFO excepInfo);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("55272A00-42CB-11CE-8135-00AA004BB851")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IPropertyBag
- {
- [PreserveSig] int Read(
- [MarshalAs(UnmanagedType.LPWStr)] string propName,
- out object value,
- IErrorLog? errorLog);
- [PreserveSig] int Write(
- [MarshalAs(UnmanagedType.LPWStr)] string propName,
- in object value);
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct VIDEO_STREAM_CONFIG_CAPS
- {
- public Guid guid;
- public uint VideoStandard;
- [Obsolete] public NativeMethods.SIZE InputSize;
- [Obsolete] public NativeMethods.SIZE MinCroppingSize;
- [Obsolete] public NativeMethods.SIZE MaxCroppingSize;
- [Obsolete] public int CropGranularityX;
- [Obsolete] public int CropGranularityY;
- [Obsolete] public int CropAlignX;
- [Obsolete] public int CropAlignY;
- [Obsolete] public NativeMethods.SIZE MinOutputSize;
- [Obsolete] public NativeMethods.SIZE MaxOutputSize;
- [Obsolete] public int OutputGranularityX;
- [Obsolete] public int OutputGranularityY;
- [Obsolete] public int StretchTapsX;
- [Obsolete] public int StretchTapsY;
- [Obsolete] public int ShrinkTapsX;
- [Obsolete] public int ShrinkTapsY;
- public long MinFrameInterval;
- public long MaxFrameInterval;
- [Obsolete] public int MinBitsPerSecond;
- [Obsolete] public int MaxBitsPerSecond;
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("C6E13340-30AC-11d0-A18C-00A0C9118956")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IAMStreamConfig
- {
- [PreserveSig] int SetFormat(in AM_MEDIA_TYPE mt);
- [PreserveSig] int GetFormat(out AM_MEDIA_TYPE mt);
- [PreserveSig] int GetNumberOfCapabilities(out int count, out int size);
- [PreserveSig] int GetStreamCaps(
- int index, out IntPtr pMediaType, out VIDEO_STREAM_CONFIG_CAPS scc);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a868a9-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IGraphBuilder : IFilterGraph
- {
- [PreserveSig] new int AddFilter(
- IBaseFilter filter,
- [MarshalAs(UnmanagedType.LPWStr)] string name);
- [PreserveSig] new int RemoveFilter(IBaseFilter filter);
- [PreserveSig] new int EnumFilters(out IEnumFilters? ppEnum);
- [PreserveSig] new int FindFilterByName(
- [MarshalAs(UnmanagedType.LPWStr)] string name,
- out IBaseFilter? filter);
- [PreserveSig] new int ConnectDirect(
- IPin pinOut, IPin pinIn, in AM_MEDIA_TYPE mt);
- [PreserveSig] new int Reconnect(IPin pin);
- [PreserveSig] new int Disconnect(IPin pin);
- [PreserveSig] new int SetDefaultSyncSource();
-
- [PreserveSig] int Connect(
- IPin pinOut, IPin pinIn);
- [PreserveSig] int Render(
- IPin pinOut);
- [PreserveSig] int RenderFile(
- [MarshalAs(UnmanagedType.LPWStr)] string strFile,
- [MarshalAs(UnmanagedType.LPWStr)] string strPlayList);
- [PreserveSig] int AddSourceFilter(
- [MarshalAs(UnmanagedType.LPWStr)] string strFileName,
- [MarshalAs(UnmanagedType.LPWStr)] string strFilterName,
- out IBaseFilter? filter);
- [PreserveSig] int SetLogFile(
- IntPtr hFile);
- [PreserveSig] int Abort();
- [PreserveSig] int ShouldOperationContinue();
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a8689a-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMediaSample
- {
- [PreserveSig] int GetPointer(ref IntPtr ppBuffer);
- [PreserveSig] int GetSize();
- [PreserveSig] int GetTime(
- out long timeStart, out long timeEnd);
- [PreserveSig] int SetTime(
- in long timeStart, in long timeEnd);
- [PreserveSig] int IsSyncPoint();
- [PreserveSig] int SetSyncPoint([MarshalAs(UnmanagedType.Bool)] bool isSyncPoint);
- [PreserveSig] int IsPreroll();
- [PreserveSig] int SetPreroll([MarshalAs(UnmanagedType.Bool)] bool isPreroll);
- [PreserveSig] int GetActualDataLength();
- [PreserveSig] int SetActualDataLength(int length);
- [PreserveSig] int GetMediaType(out IntPtr pMediaType); // AM_MEDIA_TYPE**
- [PreserveSig] int SetMediaType(in AM_MEDIA_TYPE mediaType);
- [PreserveSig] int IsDiscontinuity();
- [PreserveSig] int SetDiscontinuity([MarshalAs(UnmanagedType.Bool)] bool discontinuity);
- [PreserveSig] int GetMediaTime(out long pTimeStart, out long pTimeEnd);
- [PreserveSig] int SetMediaTime(in long timeStart, in long timeEnd);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("0579154a-2b53-4994-b0d0-e773148eff85")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface ISampleGrabberCB
- {
- [PreserveSig] int SampleCB(
- double sampleTime,
- IMediaSample sample);
- [PreserveSig] int BufferCB(
- double sampleTime,
- IntPtr pBuffer,
- int bufferLen);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("6b652fff-11fe-4fce-92ad-0266b5d7c78f")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface ISampleGrabber :
- IBaseFilter // ISampleGrabber isn't derived from IBaseFilter, but CLSID_SampleGrabber is implemented it.
- {
- [PreserveSig] int SetOneShot(
- [MarshalAs(UnmanagedType.Bool)] bool oneShot);
- [PreserveSig] int SetMediaType(
- in AM_MEDIA_TYPE type);
- [PreserveSig] int GetConnectedMediaType(
- out AM_MEDIA_TYPE type);
- [PreserveSig] int SetBufferSamples(
- [MarshalAs(UnmanagedType.Bool)] bool bufferThem);
- [PreserveSig] int GetCurrentBuffer(
- ref int bufferSize,
- IntPtr pBuffer);
- [PreserveSig, Obsolete] int GetCurrentSample(
- out IMediaSample? sample);
- [PreserveSig] int SetCallback(
- ISampleGrabberCB callback,
- int whichMethodToCallback);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("93E5A4E0-2D50-11d2-ABFA-00A0C9C6E38D")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface ICaptureGraphBuilder2
- {
- [PreserveSig] int SetFiltergraph(
- IGraphBuilder gb);
-
- [PreserveSig] int GetFiltergraph(
- out IGraphBuilder? gb);
-
- [PreserveSig] int SetOutputFileName(
- in Guid type,
- [MarshalAs(UnmanagedType.LPWStr)] string strFile,
- out IBaseFilter? filter,
- [MarshalAs(UnmanagedType.Interface)] out object? sink); // IFileSinkFilter
-
- [PreserveSig] int FindInterface(
- in Guid category,
- in Guid type,
- IBaseFilter pf,
- in Guid riidResult,
- [MarshalAs(UnmanagedType.Interface)] out object? intf);
-
- [PreserveSig] int RenderStream(
- in Guid category,
- in Guid type,
- [MarshalAs(UnmanagedType.Interface)] object source,
- IBaseFilter? compressor,
- IBaseFilter? renderer);
-
- [PreserveSig] int ControlStream(
- in Guid category,
- in Guid type,
- IBaseFilter? filter,
- in long start,
- in long stop,
- short startCookie,
- short stopCookie);
-
- [PreserveSig] int AllocCapFile(
- [MarshalAs(UnmanagedType.LPWStr)] string str,
- long size);
-
- [PreserveSig] int CopyCaptureFile(
- [MarshalAs(UnmanagedType.LPWStr)] string strOld,
- [MarshalAs(UnmanagedType.LPWStr)] string strNew,
- int fAllowEscAbort,
- [MarshalAs(UnmanagedType.Interface)] object? callback); // IAMCopyCaptureFileProgress
-
- [PreserveSig] int FindPin(
- [MarshalAs(UnmanagedType.IUnknown)] object source,
- PIN_DIRECTION pindir,
- in Guid category,
- in Guid type,
- [MarshalAs(UnmanagedType.Bool)] bool unconnected,
- int num,
- out IPin? pin);
- }
-
- [SuppressUnmanagedCodeSecurity]
- [Guid("56a868b1-0ad4-11ce-b03a-0020af0ba770")]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- public interface IMediaControl
- {
- // These entries are IDispatch members.
- // Couldn't use InterfaceIsIDispatch or InterfaceIsDual, because there're obsoleted.
- [PreserveSig] int vptr_IDispatch_GetTypeInfoCount();
- [PreserveSig] int vptr_IDispatch_GetTypeInfo();
- [PreserveSig] int vptr_IDispatch_GetIDsOfNames();
- [PreserveSig] int vptr_IDispatch_Invoke();
-
- [PreserveSig] int Run();
- [PreserveSig] int Pause();
- [PreserveSig] int Stop();
- [PreserveSig] int GetState(
- int msTimeout, out FILTER_STATE state);
- [PreserveSig] int RenderFile(
- [MarshalAs(UnmanagedType.BStr)] string strFilename);
- [PreserveSig] int AddSourceFilter(
- [MarshalAs(UnmanagedType.BStr)] string strFilename,
- [MarshalAs(UnmanagedType.IUnknown)] out object? unk);
- [PreserveSig] int get_FilterCollection(
- [MarshalAs(UnmanagedType.IUnknown)] out object? unk);
- [PreserveSig] int get_RegFilterCollection(
- [MarshalAs(UnmanagedType.IUnknown)] out object? unk);
- [PreserveSig] int StopWhenReady();
- }
-
- ////////////////////////////////////////////////////////////////////////
-
- public static readonly Guid CLSID_SystemDeviceEnum =
- new Guid("62BE5D10-60EB-11d0-BD3B-00A0C911CE86");
- public static readonly Guid CLSID_VideoInputDeviceCategory =
- new Guid("860BB310-5D01-11d0-BD3B-00A0C911CE86");
- public static readonly Guid CLSID_GraphBuilder =
- new Guid("E436EBB3-524F-11CE-9F53-0020AF0BA770");
- public static readonly Guid CLSID_SampleGrabber =
- new Guid("C1F400A0-3F08-11D3-9F0B-006008039E37");
- public static readonly Guid CLSID_NullRenderer =
- new Guid("C1F400A4-3F08-11D3-9F0B-006008039E37");
- public static readonly Guid CLSID_CaptureGraphBuilder2 =
- new Guid("BF87B6E1-8C27-11d0-B3F0-00AA003761C5");
-
- public static readonly Guid IID_ICreateDevEnum =
- new Guid("29840822-5B84-11D0-BD3B-00A0C911CE86");
- public static readonly Guid IID_IBaseFilter =
- new Guid("56a86895-0ad4-11ce-b03a-0020af0ba770");
- public static readonly Guid IID_IPropertyBag =
- new Guid("55272A00-42CB-11CE-8135-00AA004BB851");
- public static readonly Guid IID_IGraphBuilder =
- new Guid("56a868a9-0ad4-11ce-b03a-0020af0ba770");
- public static readonly Guid IID_ISampleGrabber =
- new Guid("6B652FFF-11FE-4fce-92AD-0266B5D7C78F");
- public static readonly Guid IID_ICaptureGraphBuilder2 =
- new Guid("93E5A4E0-2D50-11d2-ABFA-00A0C9C6E38D");
-
- public static readonly Guid FORMAT_VideoInfo =
- new Guid("05589F80-C356-11CE-BF01-00AA0055595A");
- public static readonly Guid FORMAT_VideoInfo2 =
- new Guid("F72A76A0-EB0A-11d0-ACE4-0000C0CC16BA");
-
- public static readonly Guid MEDIATYPE_Video =
- new Guid("73646976-0000-0010-8000-00AA00389B71");
-
- public static readonly Guid PIN_CATEGORY_CAPTURE =
- new Guid("fb6c4281-0353-11d1-905f-0000c0cc16ba");
-
- ////////////////////////////////////////////////////////////////////////
-
- [Flags]
- public enum CLSCTX : uint
- {
- CLSCTX_INPROC_SERVER = 0x1,
- CLSCTX_INPROC_HANDLER = 0x2,
- CLSCTX_LOCAL_SERVER = 0x4,
- }
-
- [DllImport("ole32")]
- public static extern int CoCreateInstance(
- in Guid classId,
- [MarshalAs(UnmanagedType.IUnknown)] object? outer,
- CLSCTX classContext,
- in Guid iid,
- [MarshalAs(UnmanagedType.IUnknown)] out object? created);
-
- public static void SafeReleaseBlock(this TIF intf, Action action)
- where TIF : notnull
- {
- try
- {
- action(intf);
- }
- finally
- {
- Marshal.ReleaseComObject(intf);
- }
- }
-
- public static TR SafeReleaseBlock(this TIF intf, Func action)
- where TIF : notnull
- {
- try
- {
- return action(intf);
- }
- finally
- {
- Marshal.ReleaseComObject(intf);
- }
- }
-
- public static IEnumerable EnumerateDeviceMoniker(Guid deviceCategory)
- {
- if (CoCreateInstance(
- in CLSID_SystemDeviceEnum,
- null,
- CLSCTX.CLSCTX_INPROC_SERVER,
- in IID_ICreateDevEnum,
- out var cde) == 0 &&
- cde != null)
- {
- if (cde is ICreateDevEnum deviceEnumCreator)
- {
- if (deviceEnumCreator.CreateClassEnumerator(
- in deviceCategory,
- out var enumMoniker, 0) == 0 &&
- enumMoniker is { })
- {
- var monikers = new IMoniker?[1];
- while (enumMoniker.Next(monikers.Length, monikers, out var fetched) == 0 &&
- fetched == monikers.Length &&
- monikers[0] is { })
- {
- yield return monikers[0]!;
- Marshal.ReleaseComObject(monikers[0]!);
- }
- Marshal.ReleaseComObject(enumMoniker);
- }
- }
- Marshal.ReleaseComObject(cde);
- }
- }
-
- public static IPropertyBag? GetPropertyBag(
- this IMoniker moniker) =>
- moniker.BindToStorage(
- null, null, in IID_IPropertyBag, out var pb) == 0 &&
- pb is IPropertyBag propertyBag ?
- propertyBag : null;
-
- public static T GetValue(
- this IPropertyBag pb, string name, T defaultValue = default!) =>
- pb.Read(name, out var value, null) == 0 ?
- (T)value : defaultValue;
-
- public static IEnumerable EnumeratePins(
- this IBaseFilter baseFilter)
- {
- if (baseFilter.EnumPins(out var enumPins) == 0 &&
- enumPins is { })
- {
- var pins = new IPin?[1];
- while (enumPins.Next(pins.Length, pins, out var fetched) == 0 &&
- fetched == pins.Length &&
- pins[0] is { })
- {
- yield return pins[0]!;
- }
- Marshal.ReleaseComObject(enumPins);
- }
- }
-
- public static PIN_INFO? GetPinInfo(
- this IPin pin) =>
- pin.QueryPinInfo(out var pinInfo) == 0 ?
- pinInfo : null;
-
- public sealed class VideoMediaFormat : IDisposable
- {
- public readonly AM_MEDIA_TYPE PartialMediaType;
- public readonly NativeMethods.VIDEOINFOHEADER VideoInformation;
- public readonly VIDEO_STREAM_CONFIG_CAPS Capabilities;
-
- private IntPtr pBih_;
-
- public VideoMediaFormat(
- AM_MEDIA_TYPE partialMediaType,
- in NativeMethods.VIDEOINFOHEADER information,
- IntPtr pBih,
- in VIDEO_STREAM_CONFIG_CAPS capabilities)
- {
- Debug.Assert(partialMediaType.pFormat == IntPtr.Zero);
- Debug.Assert(partialMediaType.pUnk == IntPtr.Zero);
-
- this.PartialMediaType = partialMediaType;
- this.VideoInformation = information;
- this.pBih_ = pBih;
- this.Capabilities = capabilities;
- }
-
- ~VideoMediaFormat() =>
- this.Dispose();
-
- public void Dispose()
- {
- if (this.pBih_ != IntPtr.Zero)
- {
- NativeMethods.FreeMemory(this.pBih_);
- this.pBih_ = IntPtr.Zero;
- }
- }
-
- public IntPtr pBih =>
- this.pBih_;
-
- public unsafe AM_MEDIA_TYPE AllocateFormalMediaType()
- {
- // Copy.
- var mediaType = this.PartialMediaType;
-
- if (mediaType.formattype == FORMAT_VideoInfo)
- {
- var pBihFrom = (NativeMethods.BITMAPINFOHEADER*)this.pBih.ToPointer();
- var bihFromSize = pBihFrom->CalculateRawSize();
-
- mediaType.formatSize = sizeof(NativeMethods.VIDEOINFOHEADER) + bihFromSize;
- mediaType.pFormat = NativeMethods.AllocateMemory((IntPtr)mediaType.formatSize);
-
- var pVihTo = (NativeMethods.VIDEOINFOHEADER*)mediaType.pFormat;
- *pVihTo = this.VideoInformation;
-
- var pBihTo = (NativeMethods.BITMAPINFOHEADER*)(pVihTo + 1);
- NativeMethods.CopyMemory((IntPtr)pBihTo, (IntPtr)pBihFrom, (IntPtr)bihFromSize);
- }
- else if (mediaType.formattype == FORMAT_VideoInfo2)
- {
- var pBihFrom = (NativeMethods.BITMAPINFOHEADER*)this.pBih.ToPointer();
- var bihFromSize = pBihFrom->CalculateRawSize();
-
- mediaType.formatSize = sizeof(NativeMethods.VIDEOINFOHEADER2) + bihFromSize;
- mediaType.pFormat = NativeMethods.AllocateMemory((IntPtr)mediaType.formatSize);
-
- var pVihTo = (NativeMethods.VIDEOINFOHEADER*)mediaType.pFormat;
- *pVihTo = this.VideoInformation;
-
- var pBihTo = (NativeMethods.BITMAPINFOHEADER*)(((byte*)pVihTo) + sizeof(NativeMethods.VIDEOINFOHEADER2));
- NativeMethods.CopyMemory((IntPtr)pBihTo, (IntPtr)pBihFrom, (IntPtr)bihFromSize);
- }
- else
- {
- throw new ArgumentException();
- }
-
- return mediaType;
- }
-
- public VideoCharacteristics? CreateVideoCharacteristics() =>
- NativeMethods.CreateVideoCharacteristics(
- this.pBih,
- new Fraction(
- // Precision is only under 3 digits (0.001)
- (int)(10_000_000_000.0 / this.VideoInformation.AvgTimePerFrame),
- 1_000).Reduce(),
- true, // TODO: support non discrete entries.
- this.PartialMediaType.subtype.ToString());
- }
-
- ////////////////////////////////////////////////////////////////////////
-
- private static unsafe readonly int videoStreamConfigCapsSize =
- sizeof(VIDEO_STREAM_CONFIG_CAPS);
-
- public static bool SetFormat(this IPin pin, VideoMediaFormat format)
- {
- if (pin is IAMStreamConfig streamConfig)
- {
- var mediaType = format.AllocateFormalMediaType();
- try
- {
- if (streamConfig.SetFormat(in mediaType) == 0)
- {
- return true;
- }
- }
- finally
- {
- mediaType.Release();
- }
- }
- return false;
- }
-
- public static IEnumerable EnumerateFormats(
- this IPin pin)
- {
- static unsafe AM_MEDIA_TYPE CloneAndRelease(IntPtr pMediaType)
- {
- var pmt = (AM_MEDIA_TYPE*)pMediaType.ToPointer();
- var mt = *pmt; // Copy.
- NativeMethods.FreeMemory(pMediaType);
- return mt;
- }
-
- static unsafe bool Extract(
- in AM_MEDIA_TYPE mediaType,
- out NativeMethods.VIDEOINFOHEADER vih,
- out IntPtr pBihResult)
- {
- if (mediaType.majortype == MEDIATYPE_Video)
- {
- if (mediaType.formattype == FORMAT_VideoInfo &&
- mediaType.formatSize >=
- (sizeof(NativeMethods.VIDEOINFOHEADER) +
- sizeof(NativeMethods.BITMAPINFOHEADER)))
- {
- var pVih = (NativeMethods.VIDEOINFOHEADER*)mediaType.pFormat.ToPointer();
- vih = *pVih;
-
- var pBih = (NativeMethods.BITMAPINFOHEADER*)(pVih + 1);
- pBihResult = NativeMethods.AllocateMemory((IntPtr)pBih->biSize);
- NativeMethods.CopyMemory(pBihResult, (IntPtr)pBih, (IntPtr)pBih->biSize);
-
- return true;
- }
- if (mediaType.formattype == FORMAT_VideoInfo2 &&
- mediaType.formatSize >=
- (sizeof(NativeMethods.VIDEOINFOHEADER2) +
- sizeof(NativeMethods.BITMAPINFOHEADER)))
- {
- var pVih = (NativeMethods.VIDEOINFOHEADER*)mediaType.pFormat.ToPointer();
- vih = *pVih;
-
- var pVih2 = (NativeMethods.VIDEOINFOHEADER2*)mediaType.pFormat.ToPointer();
- var pBih = (NativeMethods.BITMAPINFOHEADER*)(pVih2 + 1);
- pBihResult = NativeMethods.AllocateMemory((IntPtr)pBih->biSize);
- NativeMethods.CopyMemory(pBihResult, (IntPtr)pBih, (IntPtr)pBih->biSize);
-
- return true;
- }
- }
-
- vih = default;
- pBihResult = default;
- return false;
- }
-
- if (pin is IAMStreamConfig streamConfig)
- {
- if (streamConfig.GetNumberOfCapabilities(out var count, out var size) == 0 &&
- size == videoStreamConfigCapsSize)
- {
- for (var index = 0; index < count; index++)
- {
- if (streamConfig.GetStreamCaps(index, out var pMediaType, out var caps) == 0 &&
- pMediaType != IntPtr.Zero)
- {
- var mediaType = CloneAndRelease(pMediaType);
- try
- {
- if (Extract(in mediaType, out var vih, out var pBih))
- {
- // Copy.
- var partialMediaType = mediaType;
- partialMediaType.pFormat = IntPtr.Zero;
- yield return new VideoMediaFormat(partialMediaType, vih, pBih, caps);
- }
- }
- finally
- {
- mediaType.Release();
- }
- }
- }
- }
- }
- }
-
- public static IGraphBuilder CreateGraphBuilder()
- {
- if (CoCreateInstance(
- in CLSID_GraphBuilder,
- null,
- CLSCTX.CLSCTX_INPROC_SERVER,
- in IID_IGraphBuilder,
- out var gb) == 0 &&
- gb is IGraphBuilder graphBuilder)
- {
- return graphBuilder;
- }
- else
- {
- throw new InvalidOperationException("FlashCap: Couldn't create graph builder.");
- }
- }
-
- public static ISampleGrabber CreateSampleGrabber()
- {
- // OMG, the sample grabber is deplicated.
- // It isn't removed now, but feel remove in Windows future release...
- // https://docs.microsoft.com/en-us/windows/win32/directshow/isamplegrabber
- if (CoCreateInstance(
- in CLSID_SampleGrabber,
- null,
- CLSCTX.CLSCTX_INPROC_SERVER,
- in IID_ISampleGrabber,
- out var sg) == 0 &&
- sg is ISampleGrabber sampleGrabber)
- {
- return sampleGrabber;
- }
- else
- {
- throw new InvalidOperationException("FlashCap: Couldn't create sample grabber.");
- }
- }
-
- public static IBaseFilter CreateNullRenderer()
- {
- // OMG, the null renderer is deplicated.
- // It isn't removed now, but feel remove in Windows future release...
- // https://docs.microsoft.com/en-us/windows/win32/directshow/null-renderer-filter
- if (CoCreateInstance(
- in CLSID_NullRenderer,
- null,
- CLSCTX.CLSCTX_INPROC_SERVER,
- in IID_IBaseFilter,
- out var nr) == 0 &&
- nr is IBaseFilter nullRenderer)
- {
- return nullRenderer;
- }
- else
- {
- throw new InvalidOperationException("FlashCap: Couldn't create null renderer.");
- }
- }
-
- public static ICaptureGraphBuilder2 CreateCaptureGraphBuilder()
- {
- if (CoCreateInstance(
- in CLSID_CaptureGraphBuilder2,
- null,
- CLSCTX.CLSCTX_INPROC_SERVER,
- in IID_ICaptureGraphBuilder2,
- out var cgb) == 0 &&
- cgb is ICaptureGraphBuilder2 captureGraphBuilder)
- {
- return captureGraphBuilder;
- }
- else
- {
- throw new InvalidOperationException("FlashCap: Couldn't create capture graph builder.");
- }
- }
-
- #region SHOW_PROPERTY_PAGES
-
- [ComVisible(false)]
- internal struct CAUUID
- {
- public int cElems;
- public IntPtr pElems;
- }
-
- [ComImport]
- [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
- [Guid("B196B28B-BAB4-101A-B69C-00AA00341D07")]
- internal interface ISpecifyPropertyPages
- {
- [PreserveSig]
- int GetPages(out CAUUID pPages);
- }
-
- [DllImport("oleaut32.dll")]
- public static extern int OleCreatePropertyFrame(IntPtr hwndOwner, int x, int y, [MarshalAs(UnmanagedType.LPWStr)] string caption, int cObjects, [MarshalAs(UnmanagedType.Interface)] ref object ppUnk, int cPages, IntPtr lpPageClsID, int lcid, int dwReserved, IntPtr lpvReserved);
-
- #endregion
-}
diff --git a/FlashCap.Core/Internal/NativeMethods_MediaFoundation.cs b/FlashCap.Core/Internal/NativeMethods_MediaFoundation.cs
new file mode 100644
index 0000000..f0313e6
--- /dev/null
+++ b/FlashCap.Core/Internal/NativeMethods_MediaFoundation.cs
@@ -0,0 +1,118 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
+
+namespace FlashCap.Internal;
+
+[SupportedOSPlatform("windows")]
+internal static partial class NativeMethods_MediaFoundation
+{
+ public const int S_OK = 0;
+ public const int S_FALSE = 1;
+
+ public const int MF_VERSION = 0x00020070;
+ public const int MFSTARTUP_FULL = 0;
+
+ public const int MF_SOURCE_READER_FIRST_VIDEO_STREAM = unchecked((int)0xfffffffc);
+ public const int MF_SOURCE_READER_ALL_STREAMS = unchecked((int)0xfffffffe);
+ public const int MF_SOURCE_READER_ANY_STREAM = unchecked((int)0xfffffffe);
+
+ public const int MF_SOURCE_READERF_ENDOFSTREAM = 0x00000002;
+ public const int MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED = 0x00000010;
+ public const int MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED = 0x00000020;
+ public const int MF_SOURCE_READERF_STREAMTICK = 0x00000100;
+
+ public const int MF_E_NO_MORE_TYPES = unchecked((int)0xc00d36b9);
+
+ public static readonly Guid MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE =
+ new("c60ac5fe-252a-478f-a0ef-bc8fa5f7cad3");
+ public static readonly Guid MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID =
+ new("8ac3587a-4ae7-42d8-99e0-0a6013eef90f");
+ public static readonly Guid MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME =
+ new("60d0e559-52f8-4fa2-bbce-acdb34a8ec01");
+ public static readonly Guid MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK =
+ new("58f0aad8-22bf-4f8a-bb3d-d2c4978c6e2f");
+
+ public static readonly Guid MF_MT_MAJOR_TYPE =
+ new("48eba18e-f8c9-4687-bf11-0a74c9f96a8f");
+ public static readonly Guid MF_MT_SUBTYPE =
+ new("f7e34c9a-42e8-4714-b74b-cb29d72c35e5");
+ public static readonly Guid MF_MT_FRAME_SIZE =
+ new("1652c33d-d6b2-4012-b834-72030849a37d");
+ public static readonly Guid MF_MT_FRAME_RATE =
+ new("c459a2e8-3d2c-4e44-b132-fee5156c7bb0");
+ public static readonly Guid MF_MT_DEFAULT_STRIDE =
+ new("644b4e48-1e02-4516-b0eb-c01ca9d49ac6");
+
+ public static readonly Guid MFMediaType_Video =
+ new("73646976-0000-0010-8000-00aa00389b71");
+
+ public static readonly Guid MFVideoFormat_RGB24 =
+ new("00000014-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_RGB32 =
+ new("00000016-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_ARGB32 =
+ new("00000015-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_RGB555 =
+ new("00000018-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_RGB565 =
+ new("00000017-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_MJPG =
+ new("47504a4d-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_UYVY =
+ new("59565955-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_YUY2 =
+ new("32595559-0000-0010-8000-00aa00389b71");
+ public static readonly Guid MFVideoFormat_NV12 =
+ new("3231564e-0000-0010-8000-00aa00389b71");
+
+ public static readonly Guid IID_IMFMediaSource =
+ new("279a808d-aec7-40c8-9c6b-a6b492c78a66");
+
+ [LibraryImport("mfplat.dll")]
+ public static partial int MFStartup(
+ int version,
+ int flags);
+
+ [LibraryImport("mfplat.dll")]
+ public static partial int MFShutdown();
+
+ [LibraryImport("mfplat.dll")]
+ public static partial int MFCreateAttributes(
+ out IntPtr attributes,
+ int initialSize);
+
+ [LibraryImport("mfplat.dll")]
+ public static partial int MFCreateMediaType(
+ out IntPtr mediaType);
+
+ [LibraryImport("mf.dll")]
+ public static partial int MFEnumDeviceSources(
+ IntPtr attributes,
+ out IntPtr activateArray,
+ out int count);
+
+ [LibraryImport("mfreadwrite.dll")]
+ public static partial int MFCreateSourceReaderFromMediaSource(
+ IntPtr mediaSource,
+ IntPtr attributes,
+ out IntPtr sourceReader);
+
+ public static void ThrowIfFailed(int hr, string operation)
+ {
+ if (hr < 0)
+ {
+ Marshal.ThrowExceptionForHR(hr);
+ throw new InvalidOperationException($"FlashCap: {operation} failed: HR=0x{hr:x8}");
+ }
+ }
+}
diff --git a/FlashCap.Core/Internal/NativeMethods_V4L2.cs b/FlashCap.Core/Internal/NativeMethods_V4L2.cs
index e57e344..79a36c1 100644
--- a/FlashCap.Core/Internal/NativeMethods_V4L2.cs
+++ b/FlashCap.Core/Internal/NativeMethods_V4L2.cs
@@ -11,6 +11,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Runtime.Versioning;
using FlashCap.Internal.V4L2;
using FlashCap.Utilities;
@@ -18,13 +19,14 @@
namespace FlashCap.Internal;
-internal static class NativeMethods_V4L2
+[SupportedOSPlatform("linux")]
+internal static partial class NativeMethods_V4L2
{
public static readonly NativeMethods_V4L2_Interop Interop;
private static readonly Dictionary pixelFormats = new();
- static NativeMethods_V4L2()
+ static unsafe NativeMethods_V4L2()
{
utsname buf;
while (uname(out buf) != 0)
@@ -36,7 +38,8 @@ static NativeMethods_V4L2()
}
}
- switch (buf.machine)
+ var machine = buf.GetMachine();
+ switch (machine)
{
case "x86_64":
case "amd64":
@@ -66,7 +69,7 @@ static NativeMethods_V4L2()
break;
default:
throw new InvalidOperationException(
- $"FlashCap: Architecture '{buf.machine}' is not supported.");
+ $"FlashCap: Architecture '{machine}' is not supported.");
}
pixelFormats.Add((uint)NativeMethods.Compression.BI_RGB, PixelFormats.RGB24);
@@ -115,23 +118,49 @@ public enum OPENBITS
O_RDWR = 2,
}
- [DllImport("libc", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int open(
- [MarshalAs(UnmanagedType.LPStr)] string pathname, OPENBITS flag);
+ [LibraryImport("libc", EntryPoint = "open", StringMarshalling = StringMarshalling.Utf8, SetLastError = true)]
+ public static partial int open(
+ string pathname, OPENBITS flag);
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int read(
- int fd, byte[] buffer, int length);
+ [LibraryImport("libc", EntryPoint = "read", SetLastError = true)]
+ private static unsafe partial int read(
+ int fd, byte* buffer, int length);
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int write(
- int fd, byte[] buffer, int count);
+ public static unsafe int read(
+ int fd, byte[] buffer, int length)
+ {
+ fixed (byte* bufferPointer = buffer)
+ {
+ return read(fd, bufferPointer, length);
+ }
+ }
+
+ [LibraryImport("libc", EntryPoint = "write", SetLastError = true)]
+ private static unsafe partial int write(
+ int fd, byte* buffer, int count);
+
+ public static unsafe int write(
+ int fd, byte[] buffer, int count)
+ {
+ fixed (byte* bufferPointer = buffer)
+ {
+ return write(fd, bufferPointer, count);
+ }
+ }
+
+ [LibraryImport("libc", EntryPoint = "close", SetLastError = true)]
+ public static partial int close(int fd);
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int close(int fd);
+ [LibraryImport("libc", EntryPoint = "pipe", SetLastError = true)]
+ private static unsafe partial int pipe(int* filedes);
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int pipe(int[] filedes);
+ public static unsafe int pipe(int[] filedes)
+ {
+ fixed (int* filedesPointer = filedes)
+ {
+ return pipe(filedesPointer);
+ }
+ }
[Flags]
public enum POLLBITS : short
@@ -159,9 +188,18 @@ public struct pollfd
public POLLBITS revents;
}
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int poll(
- [In, Out] pollfd[] fds, int nfds, int timeout);
+ [LibraryImport("libc", EntryPoint = "poll", SetLastError = true)]
+ private static unsafe partial int poll(
+ pollfd* fds, int nfds, int timeout);
+
+ public static unsafe int poll(
+ pollfd[] fds, int nfds, int timeout)
+ {
+ fixed (pollfd* fdsPointer = fds)
+ {
+ return poll(fdsPointer, nfds, timeout);
+ }
+ }
[Flags]
public enum PROT
@@ -181,17 +219,17 @@ public enum MAP
public static readonly IntPtr MAP_FAILED = (IntPtr)(-1);
- [DllImport("libc", EntryPoint = "mmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern IntPtr mmap3232(
+ [LibraryImport("libc", EntryPoint = "mmap", SetLastError = true)]
+ private static partial IntPtr mmap3232(
IntPtr addr, uint length, PROT prot, MAP flags, int fd, int offset);
- [DllImport("libc", EntryPoint = "mmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern IntPtr mmap3264(
+ [LibraryImport("libc", EntryPoint = "mmap", SetLastError = true)]
+ private static partial IntPtr mmap3264(
IntPtr addr, uint length, PROT prot, MAP flags, int fd, long offset);
- [DllImport("libc", EntryPoint = "mmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern IntPtr mmap6432(
+ [LibraryImport("libc", EntryPoint = "mmap", SetLastError = true)]
+ private static partial IntPtr mmap6432(
IntPtr addr, ulong length, PROT prot, MAP flags, int fd, int offset);
- [DllImport("libc", EntryPoint = "mmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern IntPtr mmap6464(
+ [LibraryImport("libc", EntryPoint = "mmap", SetLastError = true)]
+ private static partial IntPtr mmap6464(
IntPtr addr, ulong length, PROT prot, MAP flags, int fd, long offset);
public static IntPtr mmap(
@@ -221,11 +259,11 @@ public static IntPtr mmap(
}
}
- [DllImport("libc", EntryPoint = "munmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern int munmap32(
+ [LibraryImport("libc", EntryPoint = "munmap", SetLastError = true)]
+ private static partial int munmap32(
IntPtr addr, uint length);
- [DllImport("libc", EntryPoint = "munmap", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern int munmap64(
+ [LibraryImport("libc", EntryPoint = "munmap", SetLastError = true)]
+ private static partial int munmap64(
IntPtr addr, ulong length);
public static int munmap(
@@ -241,8 +279,8 @@ public static int munmap(
}
}
- [DllImport("libc", CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- private static extern int ioctl(
+ [LibraryImport("libc", EntryPoint = "ioctl", SetLastError = true)]
+ private static partial int ioctl(
int fd, UIntPtr request, IntPtr arg);
public static int ioctl(int fd, uint request, T arg)
@@ -270,18 +308,26 @@ public static int ioctl(int fd, uint request, T arg)
private const int _UTSNAME_LENGTH = 65;
[StructLayout(LayoutKind.Sequential)]
- public struct utsname
+ public unsafe struct utsname
{
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string sysname;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string nodename;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string release;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string version;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string machine;
- [MarshalAs(UnmanagedType.ByValTStr, SizeConst = _UTSNAME_LENGTH)] public string domainname;
+ public fixed byte sysname[_UTSNAME_LENGTH];
+ public fixed byte nodename[_UTSNAME_LENGTH];
+ public fixed byte release[_UTSNAME_LENGTH];
+ public fixed byte version[_UTSNAME_LENGTH];
+ public fixed byte machine[_UTSNAME_LENGTH];
+ public fixed byte domainname[_UTSNAME_LENGTH];
+
+ public string GetMachine()
+ {
+ fixed (byte* machinePointer = this.machine)
+ {
+ return Marshal.PtrToStringAnsi((IntPtr)machinePointer) ?? string.Empty;
+ }
+ }
}
- [DllImport("libc", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl, SetLastError = true)]
- public static extern int uname(out utsname buf);
+ [LibraryImport("libc", EntryPoint = "uname", SetLastError = true)]
+ public static partial int uname(out utsname buf);
///////////////////////////////////////////////////////////
diff --git a/FlashCap.Core/Internal/NativeMethods_VideoForWindows.cs b/FlashCap.Core/Internal/NativeMethods_VideoForWindows.cs
deleted file mode 100644
index 6384414..0000000
--- a/FlashCap.Core/Internal/NativeMethods_VideoForWindows.cs
+++ /dev/null
@@ -1,270 +0,0 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using System;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text;
-
-#pragma warning disable CS0649
-
-namespace FlashCap.Internal;
-
-[SuppressUnmanagedCodeSecurity]
-internal static class NativeMethods_VideoForWindows
-{
- public const int WS_CHILD = 0x40000000;
- public const int WS_OVERLAPPEDWINDOW = 0x00CF0000;
- public const int WS_POPUPWINDOW = unchecked ((int)0x80880000);
- public const int WS_VISIBLE = 0x10000000;
- public const int WS_EX_TOOLWINDOW = 0x00000080;
- public const int WS_EX_TRANSPARENT = 0x00000020;
- public const int WS_EX_LAYERED = 0x00080000;
-
- public const int GWL_STYLE = -16;
- public const int GWL_EXSTYLE = -20;
-
- private const int SW_HIDE = 0;
- private const int SW_SHOWNORMAL = 1;
-
- [DllImport("user32", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern IntPtr SendMessage(
- IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);
-
- [DllImport("user32", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- private static extern bool ShowWindow(
- IntPtr hWnd, int nCmdShow);
- [DllImport("user32", SetLastError = true)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool DestroyWindow(IntPtr hWnd);
-
- [DllImport("user32")]
- public static extern int GetWindowLong(
- IntPtr hWnd, int nIndex);
-
- [DllImport("user32")]
- public static extern int SetWindowLong(
- IntPtr hWnd, int nIndex, int dwNewLong);
-
- [DllImport("user32")]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool WaitMessage();
-
- ////////////////////////////////////////////////////////////////////////
-
- [DllImport("avicap32", EntryPoint = "capCreateCaptureWindowW", CharSet = CharSet.Unicode)]
- public static extern IntPtr capCreateCaptureWindow(
- string lpszWindowName, int dwStyle,
- int x, int y, int nWidth, int nHeight,
- IntPtr hWndParent, int nID);
-
- public const int MaxVideoForWindowsDevices = 10;
-
- [DllImport("avicap32", EntryPoint = "capGetDriverDescriptionW", CharSet = CharSet.Unicode)]
- [return: MarshalAs(UnmanagedType.Bool)]
- public static extern bool capGetDriverDescription(
- uint wDriverIndex,
- StringBuilder lpszName,
- int cbName,
- StringBuilder lpszVer,
- int cbVer);
-
- ////////////////////////////////////////////////////////////////////////
-
- private const int WM_CAP_START = 0x400;
- private const int WM_CAP_DRIVER_CONNECT = WM_CAP_START + 10;
- private const int WM_CAP_DRIVER_DISCONNECT = WM_CAP_START + 11;
- private const int WM_CAP_SET_SCALE = WM_CAP_START + 53;
- private const int WM_CAP_SET_PREVIEW = WM_CAP_START + 50;
- private const int WM_CAP_SET_OVERLAY = WM_CAP_START + 51;
- private const int WM_CAP_SET_PREVIEWRATE = WM_CAP_START + 52;
- private const int WM_CAP_GRAB_FRAME_NOSTOP = WM_CAP_START + 61;
- private const int WM_CAP_SET_CALLBACK_FRAME = WM_CAP_START + 5;
- private const int WM_CAP_GET_VIDEOFORMAT = WM_CAP_START + 44;
- private const int WM_CAP_SET_VIDEOFORMAT = WM_CAP_START + 45;
- private const int WM_CAP_DLG_VIDEOFORMAT = WM_CAP_START + 41;
- private const int WM_CAP_DLG_VIDEOSOURCE = WM_CAP_START + 42;
- private const int WM_CAP_DLG_VIDEODISPLAY = WM_CAP_START + 43;
- private const int WM_CAP_DLG_VIDEOCOMPRESSION = WM_CAP_START + 46;
- private const int WM_CAP_GET_SEQUENCE_SETUP = WM_CAP_START + 65;
- private const int WM_CAP_SET_SEQUENCE_SETUP = WM_CAP_START + 64;
-
- [StructLayout(LayoutKind.Sequential)]
- public struct CAPTUREPARMS
- {
- public int dwRequestMicroSecPerFrame;
- public int fMakeUserHitOKToCapture;
- public int wPercentDropForError;
- public int fYield;
- public int dwIndexSize;
- public int wChunkGranularity;
- public int fCaptureAudio;
- public int wNumVideoRequested;
- public int wNumAudioRequested;
- public int fAbortLeftMouse;
- public int fAbortRightMouse;
- public int fMCIControl;
- public int fStepMCIDevice;
- public int dwMCIStartTime;
- public int dwMCIStopTime;
- public int fStepCaptureAt2x;
- public int wStepCaptureAverageFrames;
- public int dwAudioBufferSize;
- }
-
- [Flags]
- public enum VideoStatus
- {
- Done = 1,
- Prepared = 2,
- Inqueue = 4,
- KeyFrame = 8,
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct VIDEOHDR
- {
- public IntPtr lpData;
- public int dwBufferLength;
- public int dwBytesUsed;
- public uint dwTimeCaptured;
- public UIntPtr dwUser;
- public VideoStatus flags;
- [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)]
- private UIntPtr[] dwReserved;
- }
-
- public delegate void CAPVIDEOCALLBACK(IntPtr hWnd, in VIDEOHDR vhdr);
-
- ////////////////////////////////////////////////////////////////////////
-
- public static void capDriverConnect(IntPtr hWnd, int nDevice) =>
- SendMessage(hWnd, WM_CAP_DRIVER_CONNECT, (IntPtr)nDevice, IntPtr.Zero);
- public static void capDriverDisconnect(IntPtr hWnd, int nDevice) =>
- SendMessage(hWnd, WM_CAP_DRIVER_DISCONNECT, (IntPtr)nDevice, IntPtr.Zero);
-
- public static void capSetOverlay(IntPtr hWnd, bool enable) =>
- SendMessage(hWnd, WM_CAP_SET_OVERLAY, (IntPtr)(enable ? 1 : 0), IntPtr.Zero);
-
- public static void capShowPreview(IntPtr hWnd, bool isShow)
- {
- SendMessage(hWnd, WM_CAP_SET_PREVIEW, (IntPtr)(isShow ? 1 : 0), IntPtr.Zero);
- ShowWindow(hWnd, isShow ? SW_SHOWNORMAL : SW_HIDE);
- }
-
- public static void capGetVideoFormat(
- IntPtr hWnd, out IntPtr pBih)
- {
- var size = SendMessage(hWnd, WM_CAP_GET_VIDEOFORMAT, IntPtr.Zero, IntPtr.Zero);
- pBih = NativeMethods.AllocateMemory(size);
- SendMessage(hWnd, WM_CAP_GET_VIDEOFORMAT, size, pBih);
- }
-
- public static unsafe bool capSetVideoFormat(
- IntPtr hWnd, IntPtr pBih)
- {
- var pRawBih = (NativeMethods.BITMAPINFOHEADER*)pBih.ToPointer();
- var result = SendMessage(
- hWnd,
- WM_CAP_SET_VIDEOFORMAT,
- (IntPtr)pRawBih->CalculateRawSize(),
- (IntPtr)pRawBih);
- return result != IntPtr.Zero;
- }
-
- public static unsafe void capCaptureGetSetup(IntPtr hWnd, out CAPTUREPARMS cp)
- {
- fixed (CAPTUREPARMS* p = &cp)
- {
- SendMessage(
- hWnd, WM_CAP_GET_SEQUENCE_SETUP,
- (IntPtr)sizeof(CAPTUREPARMS), (IntPtr)p);
- }
- }
-
- public static unsafe bool capCaptureSetSetup(IntPtr hWnd, in CAPTUREPARMS cp)
- {
- fixed (CAPTUREPARMS* p = &cp)
- {
- var result = SendMessage(
- hWnd, WM_CAP_SET_SEQUENCE_SETUP,
- (IntPtr)sizeof(CAPTUREPARMS), (IntPtr)p);
- return result != IntPtr.Zero;
- }
- }
-
- public static void capSetPreviewScale(IntPtr hWnd, bool scaled) =>
- SendMessage(hWnd, WM_CAP_SET_SCALE, (IntPtr)(scaled ? 1 : 0), IntPtr.Zero);
-
- public static void capSetPreviewFPS(IntPtr hWnd, int framesPerSecond) =>
- SendMessage(hWnd, WM_CAP_SET_PREVIEWRATE, (IntPtr)framesPerSecond, IntPtr.Zero);
-
- public static void capGrabFrame(IntPtr hWnd)
- {
- SendMessage(hWnd, WM_CAP_GRAB_FRAME_NOSTOP, IntPtr.Zero, IntPtr.Zero);
- }
-
- public static void capSetCallbackFrame(IntPtr hWnd, CAPVIDEOCALLBACK? callback)
- {
- var fp = callback is { } ?
- Marshal.GetFunctionPointerForDelegate(callback) : IntPtr.Zero;
- if (SendMessage(hWnd, WM_CAP_SET_CALLBACK_FRAME, IntPtr.Zero, fp) == IntPtr.Zero)
- {
- throw new ArgumentException();
- }
- }
-
- public static void capGrabFrameNonStop(IntPtr hWnd) =>
- SendMessage(hWnd, WM_CAP_GRAB_FRAME_NOSTOP, IntPtr.Zero, IntPtr.Zero);
-
- public static void capDlgVideoFormat(IntPtr hWnd) =>
- SendMessage(hWnd, WM_CAP_DLG_VIDEOFORMAT, IntPtr.Zero, IntPtr.Zero);
- public static void capDlgVideoSource(IntPtr hWnd) =>
- SendMessage(hWnd, WM_CAP_DLG_VIDEOSOURCE, IntPtr.Zero, IntPtr.Zero);
- public static void capDlgVideoDisplay(IntPtr hWnd) =>
- SendMessage(hWnd, WM_CAP_DLG_VIDEODISPLAY, IntPtr.Zero, IntPtr.Zero);
- public static void capDlgVideoCompression(IntPtr hWnd) =>
- SendMessage(hWnd, WM_CAP_DLG_VIDEOCOMPRESSION, IntPtr.Zero, IntPtr.Zero);
-
- [Flags]
- public enum LayeredWindowFlags
- {
- LWA_ALPHA = 0x00000002,
- LWA_COLORKEY = 0x00000001,
- }
-
- [DllImport("user32")]
- private static extern bool SetLayeredWindowAttributes(
- IntPtr hwnd, uint crKey, byte bAlpha, LayeredWindowFlags dwFlags);
-
- public static IntPtr CreateVideoSourceWindow(int index)
- {
- // HACK: VFW couldn't operate without any attached window resources.
- // * It's hider for moving outsite of desktop.
- // * And will make up transparent tool window with tiny opaque value.
- var handle = capCreateCaptureWindow(
- $"FlashCap_{index}", WS_POPUPWINDOW,
- 0, 0, 100, 100, IntPtr.Zero, 0);
- if (handle == IntPtr.Zero)
- {
- var code = Marshal.GetLastWin32Error();
- Marshal.ThrowExceptionForHR(code);
- }
-
- var extyles = GetWindowLong(
- handle, GWL_EXSTYLE);
- SetWindowLong(
- handle, GWL_EXSTYLE, extyles | WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT);
- SetLayeredWindowAttributes(
- handle, 0, 1, LayeredWindowFlags.LWA_ALPHA);
-
- return handle;
- }
-}
diff --git a/FlashCap.Core/PixelBufferScope.cs b/FlashCap.Core/PixelBufferScope.cs
index a0f8353..5f142cc 100644
--- a/FlashCap.Core/PixelBufferScope.cs
+++ b/FlashCap.Core/PixelBufferScope.cs
@@ -13,9 +13,7 @@ namespace FlashCap;
public abstract class PixelBufferScope
{
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected PixelBufferScope(PixelBuffer buffer) =>
this.Buffer = buffer;
@@ -31,15 +29,11 @@ public PixelBuffer Buffer
private set;
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
protected virtual void OnReleaseNow() =>
this.Buffer = null!;
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
internal void InternalReleaseNow() =>
this.OnReleaseNow();
}
diff --git a/FlashCap.Core/Properties/AssemblyInfo.cs b/FlashCap.Core/Properties/AssemblyInfo.cs
index 7c45418..3c83806 100644
--- a/FlashCap.Core/Properties/AssemblyInfo.cs
+++ b/FlashCap.Core/Properties/AssemblyInfo.cs
@@ -11,3 +11,4 @@
[assembly: InternalsVisibleTo("FlashCap")]
[assembly: InternalsVisibleTo("FSharp.FlashCap")]
+[assembly: InternalsVisibleTo("FlashCap.Tests")]
diff --git a/FlashCap.V4L2Generator/FlashCap.V4L2Generator.csproj b/FlashCap.V4L2Generator/FlashCap.V4L2Generator.csproj
index eca144f..d6250fa 100644
--- a/FlashCap.V4L2Generator/FlashCap.V4L2Generator.csproj
+++ b/FlashCap.V4L2Generator/FlashCap.V4L2Generator.csproj
@@ -2,7 +2,7 @@
Exe
- net45
+ net8.0
disable
diff --git a/FlashCap/CaptureDeviceExtension.cs b/FlashCap/CaptureDeviceExtension.cs
index e3a0f53..740f3c9 100644
--- a/FlashCap/CaptureDeviceExtension.cs
+++ b/FlashCap/CaptureDeviceExtension.cs
@@ -16,21 +16,15 @@ namespace FlashCap;
public static class CaptureDeviceExtension
{
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static Task StartAsync(this CaptureDevice captureDevice, CancellationToken ct = default) =>
captureDevice.InternalStartAsync(ct);
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static Task StopAsync(this CaptureDevice captureDevice, CancellationToken ct = default) =>
captureDevice.InternalStopAsync(ct);
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static Task ShowPropertyPageAsync(
this CaptureDevice captureDevice, IntPtr parentWindow, CancellationToken ct = default) =>
captureDevice.InternalShowPropertyPageAsync(parentWindow, ct);
diff --git a/FlashCap/FlashCap.csproj b/FlashCap/FlashCap.csproj
index 2294f6f..797774e 100644
--- a/FlashCap/FlashCap.csproj
+++ b/FlashCap/FlashCap.csproj
@@ -1,15 +1,13 @@
- net35;net40;net45;net461;net48;netstandard1.3;netstandard2.0;netstandard2.1;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2;netcoreapp3.0;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0;net9.0
+ net8.0;net9.0;net10.0
+ true
$(NoWarn);CS0649
true
+ true
-
-
-
-
diff --git a/FlashCap/ObservableCaptureDeviceExtension.cs b/FlashCap/ObservableCaptureDeviceExtension.cs
index b2da478..8af4c62 100644
--- a/FlashCap/ObservableCaptureDeviceExtension.cs
+++ b/FlashCap/ObservableCaptureDeviceExtension.cs
@@ -16,21 +16,15 @@ namespace FlashCap;
public static class ObservableCaptureDeviceExtension
{
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static Task StartAsync(this ObservableCaptureDevice observableCaptureDevice, CancellationToken ct = default) =>
observableCaptureDevice.InternalStartAsync(ct);
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static Task StopAsync(this ObservableCaptureDevice observableCaptureDevice, CancellationToken ct = default) =>
observableCaptureDevice.InternalStopAsync(ct);
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static IDisposable Subscribe(
this ObservableCaptureDevice observableCaptureDevice,
IObserver observer) =>
diff --git a/FlashCap/PixelBufferExtension.cs b/FlashCap/PixelBufferExtension.cs
index 327c4c8..1b24ba9 100644
--- a/FlashCap/PixelBufferExtension.cs
+++ b/FlashCap/PixelBufferExtension.cs
@@ -15,9 +15,7 @@ namespace FlashCap;
public static class PixelBufferExtension
{
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static byte[] ExtractImage(
this PixelBuffer pixelBuffer)
{
@@ -27,9 +25,7 @@ public static byte[] ExtractImage(
return image.Array;
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static byte[] CopyImage(
this PixelBuffer pixelBuffer)
{
@@ -39,9 +35,7 @@ public static byte[] CopyImage(
return image.Array;
}
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static ArraySegment ReferImage(
this PixelBuffer pixelBuffer) =>
pixelBuffer.InternalExtractImage(
diff --git a/FlashCap/PixelBufferScopeExtension.cs b/FlashCap/PixelBufferScopeExtension.cs
index 673abc7..3699122 100644
--- a/FlashCap/PixelBufferScopeExtension.cs
+++ b/FlashCap/PixelBufferScopeExtension.cs
@@ -13,9 +13,7 @@ namespace FlashCap;
public static class PixelBufferScopeExtension
{
-#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
[MethodImpl(MethodImplOptions.AggressiveInlining)]
-#endif
public static void ReleaseNow(
this PixelBufferScope pixelBufferScope) =>
pixelBufferScope.InternalReleaseNow();
diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj
index 4f7ad8c..bde5c6e 100644
--- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj
+++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj
@@ -1,21 +1,21 @@
-
-
-
- netstandard2.0
- true
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ net10.0
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs
index 4a76584..01de9da 100644
--- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs
+++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs
@@ -79,10 +79,10 @@ public MainWindowViewModel()
// Store device list into the combo box.
this.DeviceList.Clear();
- foreach (var descriptor in devices.EnumerateDescriptors().
- // You could filter by device type and characteristics.
- //Where(d => d.DeviceType == DeviceTypes.DirectShow). // Only DirectShow device.
- Where(d => d.Characteristics.Length >= 1)) // One or more valid video characteristics.
+ foreach (var descriptor in devices.EnumerateDescriptors().
+ // You could filter by device type and characteristics.
+ //Where(d => d.DeviceType == DeviceTypes.MediaFoundation). // Only Media Foundation device.
+ Where(d => d.Characteristics.Length >= 1)) // One or more valid video characteristics.
{
this.DeviceList.Add(descriptor);
}
diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml
index 44e1ad3..600284a 100644
--- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml
+++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml
@@ -17,11 +17,7 @@
-
-
-
-
-
+
diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml.cs b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml.cs
index 5cf931d..01b8141 100644
--- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml.cs
+++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml.cs
@@ -1,23 +1,37 @@
-////////////////////////////////////////////////////////////////////////////
-//
-// FlashCap - Independent camera capture library.
-// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
-//
-// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
-//
-////////////////////////////////////////////////////////////////////////////
-
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Markup.Xaml;
-
-namespace FlashCap.Avalonia.Views;
-
-public sealed partial class MainWindow : Window
-{
- public MainWindow() =>
- this.InitializeComponent();
-
- private void InitializeComponent() =>
- AvaloniaXamlLoader.Load(this);
-}
+////////////////////////////////////////////////////////////////////////////
+//
+// FlashCap - Independent camera capture library.
+// Copyright (c) Kouji Matsui (@kekyo@mi.kekyo.net)
+//
+// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
+//
+////////////////////////////////////////////////////////////////////////////
+
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using FlashCap.Avalonia.ViewModels;
+using System;
+
+namespace FlashCap.Avalonia.Views;
+
+public sealed partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ this.InitializeComponent();
+ this.Opened += this.OnOpened;
+ }
+
+ private void InitializeComponent() =>
+ AvaloniaXamlLoader.Load(this);
+
+ private void OnOpened(object? sender, EventArgs e)
+ {
+ if (this.DataContext is MainWindowViewModel { Opened: { } opened } &&
+ opened.CanExecute(null))
+ {
+ opened.Execute(null);
+ }
+ }
+}
diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj
index 63e5bf8..b22911a 100644
--- a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj
+++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj
@@ -1,20 +1,19 @@
-
-
-
- WinExe
- net48;net8.0
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ WinExe
+ net10.0
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/FlashCap.OneShot/FlashCap.OneShot.csproj b/samples/FlashCap.OneShot/FlashCap.OneShot.csproj
index 82b5575..d4fdd86 100644
--- a/samples/FlashCap.OneShot/FlashCap.OneShot.csproj
+++ b/samples/FlashCap.OneShot/FlashCap.OneShot.csproj
@@ -2,15 +2,11 @@
Exe
- net48;net8.0
+ net10.0
latest
Enable
-
-
-
-
diff --git a/samples/FlashCap.OneShot/Program.cs b/samples/FlashCap.OneShot/Program.cs
index 043772b..6e55874 100644
--- a/samples/FlashCap.OneShot/Program.cs
+++ b/samples/FlashCap.OneShot/Program.cs
@@ -28,7 +28,7 @@ private static async Task TakeOneShotToFileAsync(
var devices = new CaptureDevices();
var descriptor0 = devices.EnumerateDescriptors().
// You could filter by device type and characteristics.
- //Where(d => d.DeviceType == DeviceTypes.DirectShow). // Only DirectShow device.
+ //Where(d => d.DeviceType == DeviceTypes.MediaFoundation). // Only Media Foundation device.
FirstOrDefault();
if (descriptor0 == null)
{
diff --git a/samples/FlashCap.WindowsForms/FlashCap.WindowsForms.csproj b/samples/FlashCap.WindowsForms/FlashCap.WindowsForms.csproj
index 46c7043..0d2a445 100644
--- a/samples/FlashCap.WindowsForms/FlashCap.WindowsForms.csproj
+++ b/samples/FlashCap.WindowsForms/FlashCap.WindowsForms.csproj
@@ -2,18 +2,13 @@
WinExe
- net48;net8.0-windows
-
+ net10.0-windows
disable
true
true
true
-
-
-
-
diff --git a/samples/FlashCap.WindowsForms/MainForm.cs b/samples/FlashCap.WindowsForms/MainForm.cs
index 7fddf79..db6cf46 100644
--- a/samples/FlashCap.WindowsForms/MainForm.cs
+++ b/samples/FlashCap.WindowsForms/MainForm.cs
@@ -37,7 +37,7 @@ private async void MainForm_Load(object sender, EventArgs e)
var devices = new CaptureDevices();
var descriptors = devices.EnumerateDescriptors().
// You could filter by device type and characteristics.
- //Where(d => d.DeviceType == DeviceTypes.DirectShow). // Only DirectShow device.
+ //Where(d => d.DeviceType == DeviceTypes.MediaFoundation). // Only Media Foundation device.
Where(d => d.Characteristics.Length >= 1). // One or more valid video characteristics.
ToArray();
diff --git a/samples/FlashCap.WindowsForms/Program.cs b/samples/FlashCap.WindowsForms/Program.cs
index 35d32d9..713170a 100644
--- a/samples/FlashCap.WindowsForms/Program.cs
+++ b/samples/FlashCap.WindowsForms/Program.cs
@@ -20,11 +20,9 @@ internal static class Program
[STAThread]
static void Main()
{
-#if NET6_0_OR_GREATER
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
-#endif
Application.Run(new MainForm());
}
}
\ No newline at end of file
diff --git a/samples/FlashCap.Wpf/FlashCap.Wpf.csproj b/samples/FlashCap.Wpf/FlashCap.Wpf.csproj
index 5c349fb..91a962c 100644
--- a/samples/FlashCap.Wpf/FlashCap.Wpf.csproj
+++ b/samples/FlashCap.Wpf/FlashCap.Wpf.csproj
@@ -2,7 +2,7 @@
WinExe
- net48;net8.0-windows
+ net10.0-windows
latest
Enable
true
@@ -13,11 +13,9 @@
-
-
diff --git a/samples/FlashCap.Wpf/ViewModels/MainWindowViewModel.cs b/samples/FlashCap.Wpf/ViewModels/MainWindowViewModel.cs
index b6320ce..1a76461 100644
--- a/samples/FlashCap.Wpf/ViewModels/MainWindowViewModel.cs
+++ b/samples/FlashCap.Wpf/ViewModels/MainWindowViewModel.cs
@@ -75,7 +75,7 @@ public MainWindowViewModel()
foreach (var descriptor in devices.EnumerateDescriptors().
// You could filter by device type and characteristics.
- //Where(d => d.DeviceType == DeviceTypes.DirectShow). // Only DirectShow device.
+ //Where(d => d.DeviceType == DeviceTypes.MediaFoundation). // Only Media Foundation device.
Where(d => d.Characteristics.Length >= 1)) // One or more valid video characteristics.
{
this.DeviceList.Add(descriptor);