From 3901b9520d63408a3eb154c7128d5211386f220a Mon Sep 17 00:00:00 2001 From: OleRoss Date: Wed, 8 Jul 2026 22:02:28 +0200 Subject: [PATCH 1/5] refactor: Move to aot compatible dependencies --- .github/workflows/build.yaml | 76 +- Directory.Build.props | 4 +- FSharp.FlashCap/FSharp.FlashCap.fsproj | 7 +- FlashCap.Core/CaptureDevice.cs | 2 +- FlashCap.Core/CaptureDeviceDescriptor.cs | 17 +- FlashCap.Core/CaptureDevices.cs | 24 +- FlashCap.Core/Devices/AVFoundationDevice.cs | 14 +- .../Devices/AVFoundationDeviceDescriptor.cs | 2 + FlashCap.Core/Devices/AVFoundationDevices.cs | 2 + FlashCap.Core/Devices/DirectShowDevice.cs | 370 ------ .../Devices/DirectShowDeviceDescriptor.cs | 40 - FlashCap.Core/Devices/DirectShowDevices.cs | 59 - .../Devices/MediaFoundationDevice.cs | 706 ++++++++++++ ....cs => MediaFoundationDeviceDescriptor.cs} | 27 +- .../Devices/MediaFoundationDevices.cs | 271 +++++ FlashCap.Core/Devices/V4L2Device.cs | 22 +- FlashCap.Core/Devices/V4L2DeviceDescriptor.cs | 2 + FlashCap.Core/Devices/V4L2Devices.cs | 2 + .../Devices/VideoForWindowsDevice.cs | 289 ----- .../Devices/VideoForWindowsDevices.cs | 73 -- FlashCap.Core/FlashCap.Core.csproj | 18 +- .../FrameProcessors/ScatteringProcessor.cs | 4 +- .../Internal/AVFoundation/LibAVFoundation.cs | 2 + .../IndependentSingleApartmentContext.cs | 5 +- .../MediaFoundation/MediaFoundationCom.cs | 70 ++ .../MediaFoundationInterfaces.cs | 291 +++++ .../MediaFoundationMediaTypes.cs | 154 +++ FlashCap.Core/Internal/NativeMethods.cs | 88 +- .../Internal/NativeMethods_AVFoundation.cs | 321 +++--- .../Internal/NativeMethods_DirectShow.cs | 1025 ----------------- .../Internal/NativeMethods_MediaFoundation.cs | 118 ++ FlashCap.Core/Internal/NativeMethods_V4L2.cs | 132 ++- .../Internal/NativeMethods_VideoForWindows.cs | 270 ----- FlashCap.Core/Properties/AssemblyInfo.cs | 1 + .../FlashCap.V4L2Generator.csproj | 2 +- FlashCap/FlashCap.csproj | 8 +- .../FlashCap.Avalonia.UI.csproj | 40 +- .../FlashCap.Avalonia.csproj | 39 +- .../FlashCap.OneShot/FlashCap.OneShot.csproj | 6 +- samples/FlashCap.OneShot/Program.cs | 2 +- .../FlashCap.WindowsForms.csproj | 7 +- samples/FlashCap.WindowsForms/MainForm.cs | 2 +- samples/FlashCap.WindowsForms/Program.cs | 2 - samples/FlashCap.Wpf/FlashCap.Wpf.csproj | 4 +- .../ViewModels/MainWindowViewModel.cs | 2 +- 45 files changed, 2094 insertions(+), 2528 deletions(-) delete mode 100644 FlashCap.Core/Devices/DirectShowDevice.cs delete mode 100644 FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs delete mode 100644 FlashCap.Core/Devices/DirectShowDevices.cs create mode 100644 FlashCap.Core/Devices/MediaFoundationDevice.cs rename FlashCap.Core/Devices/{VideoForWindowsDeviceDescriptor.cs => MediaFoundationDeviceDescriptor.cs} (57%) create mode 100644 FlashCap.Core/Devices/MediaFoundationDevices.cs delete mode 100644 FlashCap.Core/Devices/VideoForWindowsDevice.cs delete mode 100644 FlashCap.Core/Devices/VideoForWindowsDevices.cs create mode 100644 FlashCap.Core/Internal/MediaFoundation/MediaFoundationCom.cs create mode 100644 FlashCap.Core/Internal/MediaFoundation/MediaFoundationInterfaces.cs create mode 100644 FlashCap.Core/Internal/MediaFoundation/MediaFoundationMediaTypes.cs delete mode 100644 FlashCap.Core/Internal/NativeMethods_DirectShow.cs create mode 100644 FlashCap.Core/Internal/NativeMethods_MediaFoundation.cs delete mode 100644 FlashCap.Core/Internal/NativeMethods_VideoForWindows.cs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 94dcc1e..61501a9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,7 +4,20 @@ on: [push] jobs: build: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + build_target: FlashCap.sln + pack: true + - os: ubuntu-latest + build_target: tests/FlashCap.Tests/FlashCap.Tests.csproj + pack: false + - os: macos-latest + build_target: tests/FlashCap.Tests/FlashCap.Tests.csproj + pack: false + runs-on: ${{ matrix.os }} steps: #----------------------------------------------------------------------- @@ -19,47 +32,74 @@ jobs: - name: Extract branch name id: extract_branch_name + shell: bash run: | - export branch_name=`git name-rev --name-only --exclude=tags/* HEAD` - echo "Detected current branch: ${branch_name}" - echo "branch_name=${branch_name}" >> $GITHUB_OUTPUT + branchName="$(git name-rev --name-only --exclude=tags/* HEAD)" + echo "Detected current branch: $branchName" + echo "branch_name=$branchName" >> "$GITHUB_OUTPUT" #----------------------------------------------------------------------- # 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: Install Native AOT prerequisites + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev - name: Setup NuGet package reference run: | # dotnet nuget add source ${{secrets.GH_LOCAL_NUGET_URL}} -n ref1 -u ${{secrets.GH_LOCAL_NUGET_USER}} -p ${{secrets.GH_LOCAL_NUGET_PASSWORD}} --store-password-in-clear-text --configfile nuget.config # dotnet nuget add source ${{secrets.GH_NUGET_URL}} -n ref2 -u ${{secrets.GH_NUGET_USER}} -p ${{secrets.GH_NUGET_PASSWORD}} --store-password-in-clear-text --configfile nuget.config + - name: Select Native AOT RID + id: aot_rid + shell: bash + run: | + case "$RUNNER_OS" in + Windows) + rid="win-x64" + ;; + Linux) + rid="linux-x64" + ;; + macOS) + if [ "$(uname -m)" = "arm64" ]; then + rid="osx-arm64" + else + rid="osx-x64" + fi + ;; + *) + echo "Unsupported runner OS: $RUNNER_OS" >&2 + exit 1 + ;; + esac + echo "Using Native AOT RID: $rid" + echo "rid=$rid" >> "$GITHUB_OUTPUT" + #----------------------------------------------------------------------- # Build - name: Build - run: dotnet build -p:Configuration=Release -p:Platform="Any CPU" -p:RestoreNoCache=True -p:BuildIdentifier=${GITHUB_RUN_NUMBER} FlashCap.sln + run: dotnet build ${{ matrix.build_target }} -c Release -p:RestoreNoCache=True -p:BuildIdentifier=${{ github.run_number }} - - name: Build NuGet packages - run: dotnet pack -p:Configuration=Release -p:Platform="Any CPU" -p:BuildIdentifier=${GITHUB_RUN_NUMBER} -o artifacts FlashCap.sln + - name: Test + run: dotnet test --project tests/FlashCap.Tests/FlashCap.Tests.csproj -c Release --no-build + timeout-minutes: 10 - #----------------------------------------------------------------------- - # Test + - name: Publish Native AOT smoke test + run: dotnet publish tests/FlashCap.Tests/FlashCap.Tests.csproj -c Release -f net10.0 -r ${{ steps.aot_rid.outputs.rid }} -p:PublishAot=true --self-contained true - #- name: Test - # run: dotnet test --no-restore --verbosity normal -p:CITest=True FlashCap.sln - # timeout-minutes: 10 + - name: Build NuGet packages + if: matrix.pack + run: dotnet pack FlashCap.sln -c Release -p:BuildIdentifier=${{ github.run_number }} -o artifacts #----------------------------------------------------------------------- # Deploy packages (develop) 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..6f64b6f 100644 --- a/FlashCap.Core/CaptureDevice.cs +++ b/FlashCap.Core/CaptureDevice.cs @@ -33,7 +33,7 @@ protected CaptureDevice(object identity, string name) [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif public void Dispose() => - _ = this.DisposeAsync().ConfigureAwait(false); + this.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1 ValueTask IAsyncDisposable.DisposeAsync() => diff --git a/FlashCap.Core/CaptureDeviceDescriptor.cs b/FlashCap.Core/CaptureDeviceDescriptor.cs index f2ea72a..a14469b 100644 --- a/FlashCap.Core/CaptureDeviceDescriptor.cs +++ b/FlashCap.Core/CaptureDeviceDescriptor.cs @@ -23,6 +23,7 @@ public enum DeviceTypes DirectShow, V4L2, AVFoundation, + MediaFoundation, } public enum TranscodeFormats @@ -121,7 +122,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 +140,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..ab96e29 --- /dev/null +++ b/FlashCap.Core/Devices/MediaFoundationDevice.cs @@ -0,0 +1,706 @@ +//////////////////////////////////////////////////////////////////////////// +// +// 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( + NativeMethods_MediaFoundation.MF_SOURCE_READER_ANY_STREAM, + 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 + { + var sample = MediaFoundationCom.Wrap(samplePointer); + if (sample.ConvertToContiguousBuffer(out bufferPointer) < 0 || + bufferPointer == IntPtr.Zero) + { + return; + } + + var buffer = MediaFoundationCom.Wrap(bufferPointer); + if (buffer.Lock(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 + { + buffer.Unlock(); + } + } + 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; + } + } + +#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.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..e43c6e6 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; } } 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/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/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/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/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/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/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj index 4f7ad8c..86828a9 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,19 @@ - - - - netstandard2.0 - true - true - - - - - - - - - - - - - - - + + + + net10.0 + true + true + + + + + + + + + + + + + diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj index 63e5bf8..d5c9f6b 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 + true + + + + + + + + + + + + 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); From 8f883f50ad681017f15fb77d70da6c5c0095becb Mon Sep 17 00:00:00 2001 From: OleRoss Date: Thu, 9 Jul 2026 08:52:57 +0200 Subject: [PATCH 2/5] fix: Remove some now unnecessary framework constraints --- FlashCap.Core/CaptureDevice.cs | 4 ---- FlashCap.Core/CaptureDeviceDescriptor.cs | 2 -- FlashCap.Core/Devices/MediaFoundationDevice.cs | 2 -- FlashCap.Core/Devices/V4L2Device.cs | 2 -- FlashCap.Core/FrameProcessor.cs | 12 ------------ FlashCap.Core/Internal/BitmapTranscoder.cs | 2 -- FlashCap.Core/PixelBufferScope.cs | 6 ------ FlashCap/CaptureDeviceExtension.cs | 6 ------ FlashCap/ObservableCaptureDeviceExtension.cs | 6 ------ FlashCap/PixelBufferExtension.cs | 6 ------ FlashCap/PixelBufferScopeExtension.cs | 2 -- 11 files changed, 50 deletions(-) diff --git a/FlashCap.Core/CaptureDevice.cs b/FlashCap.Core/CaptureDevice.cs index 6f64b6f..c40e116 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).GetAwaiter().GetResult(); @@ -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 a14469b..5ebd5e1 100644 --- a/FlashCap.Core/CaptureDeviceDescriptor.cs +++ b/FlashCap.Core/CaptureDeviceDescriptor.cs @@ -79,9 +79,7 @@ public override string ToString() => ////////////////////////////////////////////////////////////////////////// -#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif internal Task InternalOpenWithFrameProcessorAsync( VideoCharacteristics characteristics, TranscodeFormats transcodeFormat, diff --git a/FlashCap.Core/Devices/MediaFoundationDevice.cs b/FlashCap.Core/Devices/MediaFoundationDevice.cs index ab96e29..360d40e 100644 --- a/FlashCap.Core/Devices/MediaFoundationDevice.cs +++ b/FlashCap.Core/Devices/MediaFoundationDevice.cs @@ -687,9 +687,7 @@ private void ReleaseMediaFoundationObjects() } } -#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP [MethodImpl(MethodImplOptions.AggressiveInlining)] -#endif protected override void OnCapture( IntPtr pData, int size, diff --git a/FlashCap.Core/Devices/V4L2Device.cs b/FlashCap.Core/Devices/V4L2Device.cs index e43c6e6..f5581cf 100644 --- a/FlashCap.Core/Devices/V4L2Device.cs +++ b/FlashCap.Core/Devices/V4L2Device.cs @@ -458,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/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/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/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/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/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(); From ea29a2ee71d140186f961e527af293c001fc0774 Mon Sep 17 00:00:00 2001 From: OleRoss Date: Thu, 9 Jul 2026 17:59:00 +0200 Subject: [PATCH 3/5] feat: Get avalonia running with aot publish --- .../Devices/MediaFoundationDevice.cs | 10 ++- .../MediaFoundationNativeCom.cs | 64 +++++++++++++++++++ .../FlashCap.Avalonia.UI.csproj | 8 ++- .../Views/MainWindow.axaml | 6 +- .../Views/MainWindow.axaml.cs | 60 ++++++++++------- .../FlashCap.Avalonia.csproj | 6 +- 6 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 FlashCap.Core/Internal/MediaFoundation/MediaFoundationNativeCom.cs diff --git a/FlashCap.Core/Devices/MediaFoundationDevice.cs b/FlashCap.Core/Devices/MediaFoundationDevice.cs index 360d40e..e5a5d65 100644 --- a/FlashCap.Core/Devices/MediaFoundationDevice.cs +++ b/FlashCap.Core/Devices/MediaFoundationDevice.cs @@ -400,7 +400,7 @@ private void ThreadEntry(TaskCompletionSource started) while (this.IsRunning) { var hr = sourceReader.ReadSample( - NativeMethods_MediaFoundation.MF_SOURCE_READER_ANY_STREAM, + VideoStreamIndex, 0, out _, out var streamFlags, @@ -456,15 +456,13 @@ private unsafe void ProcessSample(IntPtr samplePointer, long timestamp) var bufferPointer = IntPtr.Zero; try { - var sample = MediaFoundationCom.Wrap(samplePointer); - if (sample.ConvertToContiguousBuffer(out bufferPointer) < 0 || + if (MediaFoundationNativeCom.ConvertToContiguousBuffer(samplePointer, out bufferPointer) < 0 || bufferPointer == IntPtr.Zero) { return; } - var buffer = MediaFoundationCom.Wrap(bufferPointer); - if (buffer.Lock(out var data, out _, out var currentLength) < 0) + if (MediaFoundationNativeCom.Lock(bufferPointer, out var data, out _, out var currentLength) < 0) { return; } @@ -508,7 +506,7 @@ private unsafe void ProcessSample(IntPtr samplePointer, long timestamp) } finally { - buffer.Unlock(); + MediaFoundationNativeCom.Unlock(bufferPointer); } } finally 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/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj index 86828a9..bde5c6e 100644 --- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj +++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/FlashCap.Avalonia.UI.csproj @@ -4,12 +4,14 @@ net10.0 true true + false - - - + + + + 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 d5c9f6b..b22911a 100644 --- a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj +++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj @@ -4,12 +4,12 @@ WinExe net10.0 true - true + false - - + + From d7d51366cb79a8f752e0f246865982f767c665c6 Mon Sep 17 00:00:00 2001 From: OleRoss Date: Thu, 9 Jul 2026 18:18:39 +0200 Subject: [PATCH 4/5] build: Revert github actions commit --- .github/workflows/build.yaml | 71 +++++++----------------------------- 1 file changed, 13 insertions(+), 58 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 61501a9..1cdab57 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,20 +4,7 @@ on: [push] jobs: build: - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - build_target: FlashCap.sln - pack: true - - os: ubuntu-latest - build_target: tests/FlashCap.Tests/FlashCap.Tests.csproj - pack: false - - os: macos-latest - build_target: tests/FlashCap.Tests/FlashCap.Tests.csproj - pack: false - runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest steps: #----------------------------------------------------------------------- @@ -32,11 +19,10 @@ jobs: - name: Extract branch name id: extract_branch_name - shell: bash run: | - branchName="$(git name-rev --name-only --exclude=tags/* HEAD)" - echo "Detected current branch: $branchName" - echo "branch_name=$branchName" >> "$GITHUB_OUTPUT" + export branch_name=`git name-rev --name-only --exclude=tags/* HEAD` + echo "Detected current branch: ${branch_name}" + echo "branch_name=${branch_name}" >> $GITHUB_OUTPUT #----------------------------------------------------------------------- # Setup environments @@ -49,57 +35,26 @@ jobs: 9.0.x 10.0.x - - name: Install Native AOT prerequisites - if: runner.os == 'Linux' - run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev - - name: Setup NuGet package reference run: | # dotnet nuget add source ${{secrets.GH_LOCAL_NUGET_URL}} -n ref1 -u ${{secrets.GH_LOCAL_NUGET_USER}} -p ${{secrets.GH_LOCAL_NUGET_PASSWORD}} --store-password-in-clear-text --configfile nuget.config # dotnet nuget add source ${{secrets.GH_NUGET_URL}} -n ref2 -u ${{secrets.GH_NUGET_USER}} -p ${{secrets.GH_NUGET_PASSWORD}} --store-password-in-clear-text --configfile nuget.config - - name: Select Native AOT RID - id: aot_rid - shell: bash - run: | - case "$RUNNER_OS" in - Windows) - rid="win-x64" - ;; - Linux) - rid="linux-x64" - ;; - macOS) - if [ "$(uname -m)" = "arm64" ]; then - rid="osx-arm64" - else - rid="osx-x64" - fi - ;; - *) - echo "Unsupported runner OS: $RUNNER_OS" >&2 - exit 1 - ;; - esac - echo "Using Native AOT RID: $rid" - echo "rid=$rid" >> "$GITHUB_OUTPUT" - #----------------------------------------------------------------------- # Build - + - name: Build - run: dotnet build ${{ matrix.build_target }} -c Release -p:RestoreNoCache=True -p:BuildIdentifier=${{ github.run_number }} + run: dotnet build -p:Configuration=Release -p:Platform="Any CPU" -p:RestoreNoCache=True -p:BuildIdentifier=${GITHUB_RUN_NUMBER} FlashCap.sln - - name: Test - run: dotnet test --project tests/FlashCap.Tests/FlashCap.Tests.csproj -c Release --no-build - timeout-minutes: 10 + - name: Build NuGet packages + run: dotnet pack -p:Configuration=Release -p:Platform="Any CPU" -p:BuildIdentifier=${GITHUB_RUN_NUMBER} -o artifacts FlashCap.sln - - name: Publish Native AOT smoke test - run: dotnet publish tests/FlashCap.Tests/FlashCap.Tests.csproj -c Release -f net10.0 -r ${{ steps.aot_rid.outputs.rid }} -p:PublishAot=true --self-contained true + #----------------------------------------------------------------------- + # Test - - name: Build NuGet packages - if: matrix.pack - run: dotnet pack FlashCap.sln -c Release -p:BuildIdentifier=${{ github.run_number }} -o artifacts + #- name: Test + # run: dotnet test --no-restore --verbosity normal -p:CITest=True FlashCap.sln + # timeout-minutes: 10 #----------------------------------------------------------------------- # Deploy packages (develop) From d374657e1a9e501997c30ab60fca247d7a31c742 Mon Sep 17 00:00:00 2001 From: OleRoss Date: Thu, 9 Jul 2026 18:29:24 +0200 Subject: [PATCH 5/5] chore: Small self review --- FlashCap.Core/CaptureDevice.cs | 2 +- FlashCap.Core/CaptureDeviceDescriptor.cs | 1 - .../ViewModels/MainWindowViewModel.cs | 8 ++++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/FlashCap.Core/CaptureDevice.cs b/FlashCap.Core/CaptureDevice.cs index c40e116..92a1000 100644 --- a/FlashCap.Core/CaptureDevice.cs +++ b/FlashCap.Core/CaptureDevice.cs @@ -31,7 +31,7 @@ protected CaptureDevice(object identity, string name) [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Dispose() => - this.DisposeAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + _ = this.DisposeAsync().ConfigureAwait(false); #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1 ValueTask IAsyncDisposable.DisposeAsync() => diff --git a/FlashCap.Core/CaptureDeviceDescriptor.cs b/FlashCap.Core/CaptureDeviceDescriptor.cs index 5ebd5e1..5722b44 100644 --- a/FlashCap.Core/CaptureDeviceDescriptor.cs +++ b/FlashCap.Core/CaptureDeviceDescriptor.cs @@ -20,7 +20,6 @@ namespace FlashCap; public enum DeviceTypes { VideoForWindows, - DirectShow, V4L2, AVFoundation, MediaFoundation, 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); }