diff --git a/FSharp.FlashCap/CaptureDeviceDescriptorExtension.fs b/FSharp.FlashCap/CaptureDeviceDescriptorExtension.fs index 25a4d1d..d2d697b 100644 --- a/FSharp.FlashCap/CaptureDeviceDescriptorExtension.fs +++ b/FSharp.FlashCap/CaptureDeviceDescriptorExtension.fs @@ -39,7 +39,7 @@ module public CaptureDeviceDescriptorExtension = ?ct: CancellationToken) : Async = self.InternalOpenWithFrameProcessorAsync( characteristics, TranscodeFormats.Auto, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), + new DelegatedQueuingProcessor(pixelBufferArrived, 1, self.defaultBufferPool), asCT ct) |> Async.AwaitTask member self.openDevice( @@ -50,7 +50,7 @@ module public CaptureDeviceDescriptorExtension = self.InternalOpenWithFrameProcessorAsync( characteristics, transcodeFormat, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), + new DelegatedQueuingProcessor(pixelBufferArrived, 1, self.defaultBufferPool), asCT ct) |> Async.AwaitTask member self.openDevice( @@ -64,8 +64,8 @@ module public CaptureDeviceDescriptorExtension = characteristics, transcodeFormat, (match isScattering with - | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), + | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor) + | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor)), asCT ct) |> Async.AwaitTask ////////////////////////////////////////////////////////////////////////////////// @@ -77,7 +77,7 @@ module public CaptureDeviceDescriptorExtension = self.InternalOpenWithFrameProcessorAsync( characteristics, TranscodeFormats.Auto, - new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1), + new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1, self.defaultBufferPool), asCT ct) |> Async.AwaitTask member self.openDevice( @@ -88,7 +88,7 @@ module public CaptureDeviceDescriptorExtension = self.InternalOpenWithFrameProcessorAsync( characteristics, transcodeFormat, - new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1), + new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1, self.defaultBufferPool), asCT ct) |> Async.AwaitTask member self.openDevice( @@ -102,8 +102,8 @@ module public CaptureDeviceDescriptorExtension = characteristics, transcodeFormat, (match isScattering with - | true -> (new DelegatedScatteringTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), + | true -> (new DelegatedScatteringTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor) + | false -> (new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor)), asCT ct) |> Async.AwaitTask ////////////////////////////////////////////////////////////////////////////////// @@ -116,7 +116,7 @@ module public CaptureDeviceDescriptorExtension = characteristics, TranscodeFormats.Auto, (new DelegatedQueuingProcessor( - new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1)), asCT ct) |> Async.AwaitTask + new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1, self.defaultBufferPool)), asCT ct) |> Async.AwaitTask return new ObservableCaptureDevice(captureDevice, observerProxy) } @@ -129,7 +129,7 @@ module public CaptureDeviceDescriptorExtension = characteristics, transcodeFormat, (new DelegatedQueuingProcessor( - new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1)), asCT ct) |> Async.AwaitTask + new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1, self.defaultBufferPool)), asCT ct) |> Async.AwaitTask return new ObservableCaptureDevice(captureDevice, observerProxy) } @@ -145,8 +145,8 @@ module public CaptureDeviceDescriptorExtension = characteristics, transcodeFormat, (match isScattering with - | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), asCT ct) |> Async.AwaitTask + | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor) + | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames, self.defaultBufferPool) :> FrameProcessor)), asCT ct) |> Async.AwaitTask return new ObservableCaptureDevice(captureDevice, observerProxy) } @@ -168,163 +168,3 @@ module public CaptureDeviceDescriptorExtension = characteristics, transcodeFormat, asCT ct) |> Async.AwaitTask - - ////////////////////////////////////////////////////////////////////////////////// - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - pixelBufferArrived: PixelBufferScope -> unit, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, TranscodeFormats.Auto, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), - asCT ct) |> Async.AwaitTask - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - pixelBufferArrived: PixelBufferScope -> unit, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), - asCT ct) |> Async.AwaitTask - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - isScattering: bool, - maxQueuingFrames: int, - pixelBufferArrived: PixelBufferScope -> unit, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - (match isScattering with - | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), - asCT ct) |> Async.AwaitTask - - ////////////////////////////////////////////////////////////////////////////////// - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - pixelBufferArrived: PixelBufferScope -> Async, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, - TranscodeFormats.Auto, - new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1), - asCT ct) |> Async.AwaitTask - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - pixelBufferArrived: PixelBufferScope -> Async, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, 1), - asCT ct) |> Async.AwaitTask - - [] - [] - member self.openAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - isScattering: bool, - maxQueuingFrames: int, - pixelBufferArrived: PixelBufferScope -> Async, - ?ct: CancellationToken) : Async = - self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - (match isScattering with - | true -> (new DelegatedScatteringTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingTaskProcessor(asTask pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), - asCT ct) |> Async.AwaitTask - - ////////////////////////////////////////////////////////////////////////////////// - - [] - [] - member self.asObservableAsync( - characteristics: VideoCharacteristics, - ?ct: CancellationToken) : Async = async { - let observerProxy = new ObservableCaptureDevice.ObserverProxy() - let! captureDevice = self.InternalOpenWithFrameProcessorAsync( - characteristics, - TranscodeFormats.Auto, - (new DelegatedQueuingProcessor( - new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1)), asCT ct) |> Async.AwaitTask - return new ObservableCaptureDevice(captureDevice, observerProxy) - } - - [] - [] - member self.asObservableAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - ?ct: CancellationToken) : Async = async { - let observerProxy = new ObservableCaptureDevice.ObserverProxy() - let! captureDevice = self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - (new DelegatedQueuingProcessor( - new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived), 1)), asCT ct) |> Async.AwaitTask - return new ObservableCaptureDevice(captureDevice, observerProxy) - } - - [] - [] - member self.asObservableAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - isScattering: bool, - maxQueuingFrames: int, - ?ct: CancellationToken) : Async = async { - let observerProxy = new ObservableCaptureDevice.ObserverProxy() - let pixelBufferArrived = new PixelBufferArrivedDelegate(observerProxy.OnPixelBufferArrived) - let! captureDevice = self.InternalOpenWithFrameProcessorAsync( - characteristics, - toFormat transcodeIfYUV, - (match isScattering with - | true -> (new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor) - | false -> (new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames) :> FrameProcessor)), asCT ct) |> Async.AwaitTask - return new ObservableCaptureDevice(captureDevice, observerProxy) - } - - ////////////////////////////////////////////////////////////////////////////////// - - [] - [] - member self.takeOneShotAsync( - characteristics: VideoCharacteristics, - ?ct: CancellationToken) : Async = - self.InternalTakeOneShotAsync( - characteristics, - TranscodeFormats.Auto, - asCT ct) |> Async.AwaitTask - - [] - [] - member self.takeOneShotAsync( - characteristics: VideoCharacteristics, - transcodeIfYUV: bool, - ?ct: CancellationToken) : Async = - self.InternalTakeOneShotAsync( - characteristics, - toFormat transcodeIfYUV, - asCT ct) |> Async.AwaitTask diff --git a/FSharp.FlashCap/CaptureDeviceExtension.fs b/FSharp.FlashCap/CaptureDeviceExtension.fs index c1e24fc..27ebd58 100644 --- a/FSharp.FlashCap/CaptureDeviceExtension.fs +++ b/FSharp.FlashCap/CaptureDeviceExtension.fs @@ -25,11 +25,3 @@ module public CaptureDeviceExtension = member self.showPropertyPage(parentWindow: nativeint, ?ct: CancellationToken) = self.InternalShowPropertyPageAsync(parentWindow, asCT ct) |> Async.AwaitTask - - [] - member self.startAsync(?ct: CancellationToken) = - self.InternalStartAsync(asCT ct) |> Async.AwaitTask - - [] - member self.stopAsync(?ct: CancellationToken) = - self.InternalStopAsync(asCT ct) |> Async.AwaitTask diff --git a/FSharp.FlashCap/ObservableCaptureDeviceExtension.fs b/FSharp.FlashCap/ObservableCaptureDeviceExtension.fs index cb6c614..0dd3910 100644 --- a/FSharp.FlashCap/ObservableCaptureDeviceExtension.fs +++ b/FSharp.FlashCap/ObservableCaptureDeviceExtension.fs @@ -23,13 +23,5 @@ module public ObservableCaptureDeviceExtension = member self.stop(?ct: CancellationToken) = self.InternalStopAsync(asCT ct) |> Async.AwaitTask - [] - member self.startAsync(?ct: CancellationToken) = - self.InternalStartAsync(asCT ct) |> Async.AwaitTask - - [] - member self.stopAsync(?ct: CancellationToken) = - self.InternalStopAsync(asCT ct) |> Async.AwaitTask - member self.subscribe(observer: IObserver) = self.InternalSubscribe(observer) diff --git a/FlashCap.Core/BufferPool.cs b/FlashCap.Core/BufferPool.cs new file mode 100644 index 0000000..b485609 --- /dev/null +++ b/FlashCap.Core/BufferPool.cs @@ -0,0 +1,129 @@ +//////////////////////////////////////////////////////////////////////////// +// +// FlashCap - Independent camera capture library. +// Copyright (c) Kouji Matsui (@kozy_kekyo, @kekyo@mastodon.cloud) +// +// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0 +// +//////////////////////////////////////////////////////////////////////////// + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace FlashCap; + +public abstract class BufferPool +{ + protected BufferPool() + { + } + + public abstract byte[] Rent(int minimumSize); + public abstract void Return(byte[] buffer); +} + +public sealed class DefaultBufferPool : + BufferPool +{ + private sealed class BufferElement + { + private readonly int size; + private readonly WeakReference wr; + + public BufferElement(byte[] buffer) + { + this.size = buffer.Length; + this.wr = new WeakReference(buffer); + } + + public bool IsAvailable => + this.wr.IsAlive; + + public bool IsAvailableAndFit(int minimumSize) => + this.wr.IsAlive && (minimumSize <= this.size); + + public byte[]? ExtractBuffer() => + (byte[]?)this.wr.Target; + } + + private readonly BufferElement?[] bufferElements; + + public DefaultBufferPool() : this(16) + { + } + + public DefaultBufferPool(int maxReservedBufferElements) => + this.bufferElements = new BufferElement?[maxReservedBufferElements]; + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public int UnsafeAvailableCount => + this.bufferElements.Count(bufferHolder => bufferHolder?.IsAvailable ?? false); + + public override byte[] Rent(int minimumSize) + { + for (var index = 0; index < this.bufferElements.Length; index++) + { + var bufferElement = this.bufferElements[index]; + + // First phase: + // * Determined: size and exactSize + // * NOT determined: Availability + if (bufferElement?.IsAvailableAndFit(minimumSize) ?? false) + { + if (object.ReferenceEquals( + Interlocked.CompareExchange( + ref this.bufferElements[index], + null, + bufferElement), + bufferElement) && + // Second phase + // * Determined: size, exactSize and availability + bufferElement.ExtractBuffer() is { } buffer) + { + Debug.WriteLine($"DefaultBufferPool: Rend: Size={buffer.Length}/{minimumSize}, Index={index}"); + return buffer; + } + } + else if (!(bufferElement?.IsAvailable ?? true)) + { + // Remove corrected element (and forgot). + Interlocked.CompareExchange( + ref this.bufferElements[index], + null, + bufferElement); + } + } + + Debug.WriteLine($"DefaultBufferPool: Created: Size={minimumSize}"); + return new byte[minimumSize]; + } + + public override void Return(byte[] buffer) + { + var newBufferElement = new BufferElement(buffer); + + for (var index = 0; index < this.bufferElements.Length; index++) + { + var bufferElement = this.bufferElements[index]; + if (bufferElement == null || !bufferElement.IsAvailable) + { + if (object.ReferenceEquals( + Interlocked.CompareExchange( + ref this.bufferElements[index], + newBufferElement, + bufferElement), + bufferElement)) + { + Debug.WriteLine($"DefaultBufferPool: Returned: Size={buffer.Length}, Index={index}"); + return; + } + } + } + + // It was better to simply discard a buffer instance than the cost of extending the table. + Debug.WriteLine($"DefaultBufferPool: Discarded: Size={buffer.Length}"); + } +} diff --git a/FlashCap.Core/CaptureDeviceDescriptor.cs b/FlashCap.Core/CaptureDeviceDescriptor.cs index 01e4321..d98a88f 100644 --- a/FlashCap.Core/CaptureDeviceDescriptor.cs +++ b/FlashCap.Core/CaptureDeviceDescriptor.cs @@ -46,13 +46,17 @@ public abstract class CaptureDeviceDescriptor { private readonly AsyncLock locker = new(); + internal readonly BufferPool defaultBufferPool; + protected CaptureDeviceDescriptor( string name, string description, - VideoCharacteristics[] characteristics) + VideoCharacteristics[] characteristics, + BufferPool defaultBufferPool) { this.Name = name; this.Description = description; this.Characteristics = characteristics; + this.defaultBufferPool = defaultBufferPool; } public abstract object Identity { get; } @@ -129,7 +133,7 @@ internal async Task InternalTakeOneShotAsync( pixelBuffer.InternalReleaseNow(); tcs.TrySetResult(image.Array); - }, 1), + }, 1, new DefaultBufferPool()), ct); await device.InternalStartAsync(ct); diff --git a/FlashCap.Core/CaptureDevices.cs b/FlashCap.Core/CaptureDevices.cs index ddeece5..bdd2a85 100644 --- a/FlashCap.Core/CaptureDevices.cs +++ b/FlashCap.Core/CaptureDevices.cs @@ -18,14 +18,24 @@ namespace FlashCap; public class CaptureDevices { + protected readonly BufferPool DefaultBufferPool; + + public CaptureDevices() : + this(new DefaultBufferPool()) + { + } + + public CaptureDevices(BufferPool defaultBufferPool) => + this.DefaultBufferPool = defaultBufferPool; + protected virtual IEnumerable OnEnumerateDescriptors() => NativeMethods.CurrentPlatform switch { NativeMethods.Platforms.Windows => - new DirectShowDevices().OnEnumerateDescriptors(). - Concat(new VideoForWindowsDevices().OnEnumerateDescriptors()), + new DirectShowDevices(this.DefaultBufferPool).OnEnumerateDescriptors(). + Concat(new VideoForWindowsDevices(this.DefaultBufferPool).OnEnumerateDescriptors()), NativeMethods.Platforms.Linux => - new V4L2Devices().OnEnumerateDescriptors(), + new V4L2Devices(this.DefaultBufferPool).OnEnumerateDescriptors(), _ => ArrayEx.Empty(), }; diff --git a/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs b/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs index ee343b3..3ae2756 100644 --- a/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs +++ b/FlashCap.Core/Devices/DirectShowDeviceDescriptor.cs @@ -18,8 +18,9 @@ public sealed class DirectShowDeviceDescriptor : CaptureDeviceDescriptor internal DirectShowDeviceDescriptor( string devicePath, string name, string description, - VideoCharacteristics[] characteristics) : - base(name, description, characteristics) => + VideoCharacteristics[] characteristics, + BufferPool defaultBufferPool) : + base(name, description, characteristics, defaultBufferPool) => this.devicePath = devicePath; public override object Identity => diff --git a/FlashCap.Core/Devices/DirectShowDevices.cs b/FlashCap.Core/Devices/DirectShowDevices.cs index 83ca4a4..31ed806 100644 --- a/FlashCap.Core/Devices/DirectShowDevices.cs +++ b/FlashCap.Core/Devices/DirectShowDevices.cs @@ -16,6 +16,16 @@ 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). @@ -42,7 +52,8 @@ cs is NativeMethods_DirectShow.IBaseFilter captureSource ? Distinct(). OrderByDescending(vc => vc). ToArray()) : - ArrayEx.Empty()) : + ArrayEx.Empty(), + this.DefaultBufferPool) : null) : null); } diff --git a/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs b/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs index 1c8b47b..772cfc4 100644 --- a/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs +++ b/FlashCap.Core/Devices/V4L2DeviceDescriptor.cs @@ -18,8 +18,9 @@ public sealed class V4L2DeviceDescriptor : CaptureDeviceDescriptor internal V4L2DeviceDescriptor( string devicePath, string name, string description, - VideoCharacteristics[] characteristics) : - base(name, description, characteristics) => + VideoCharacteristics[] characteristics, + BufferPool defaultBufferPool) : + base(name, description, characteristics, defaultBufferPool) => this.devicePath = devicePath; public override object Identity => diff --git a/FlashCap.Core/Devices/V4L2Devices.cs b/FlashCap.Core/Devices/V4L2Devices.cs index eccab1c..10d6ffb 100644 --- a/FlashCap.Core/Devices/V4L2Devices.cs +++ b/FlashCap.Core/Devices/V4L2Devices.cs @@ -22,6 +22,16 @@ namespace FlashCap.Devices; public sealed class V4L2Devices : CaptureDevices { + public V4L2Devices() : + this(new DefaultBufferPool()) + { + } + + public V4L2Devices(BufferPool defaultBufferPool) : + base(defaultBufferPool) + { + } + private static IEnumerable EnumerateFormatDesc( int fd) => Enumerable.Range(0, 1000). @@ -203,7 +213,8 @@ protected override IEnumerable OnEnumerateDescriptors() frmsize.IsDiscrete && framesPerSecond.IsDiscrete)))). Distinct(). OrderByDescending(vc => vc). - ToArray()); + ToArray(), + this.DefaultBufferPool); } else { diff --git a/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs b/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs index 5c1174a..d447bdf 100644 --- a/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs +++ b/FlashCap.Core/Devices/VideoForWindowsDeviceDescriptor.cs @@ -18,8 +18,9 @@ public sealed class VideoForWindowsDeviceDescriptor : CaptureDeviceDescriptor internal VideoForWindowsDeviceDescriptor( int deviceIndex, string name, string description, - VideoCharacteristics[] characteristics) : - base(name, description, characteristics) => + VideoCharacteristics[] characteristics, + BufferPool defaultBufferPool) : + base(name, description, characteristics, defaultBufferPool) => this.deviceIndex = deviceIndex; public override object Identity => diff --git a/FlashCap.Core/Devices/VideoForWindowsDevices.cs b/FlashCap.Core/Devices/VideoForWindowsDevices.cs index d788d3c..0cc20cf 100644 --- a/FlashCap.Core/Devices/VideoForWindowsDevices.cs +++ b/FlashCap.Core/Devices/VideoForWindowsDevices.cs @@ -16,6 +16,16 @@ 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 => @@ -52,7 +62,8 @@ protected override IEnumerable OnEnumerateDescriptors() NativeMethods.Compression.YUYV, 640, 480, 16, 30, false)!, NativeMethods.CreateVideoCharacteristics( NativeMethods.Compression.YUYV, 640, 480, 16, 15, false)!, - }); + }, + this.DefaultBufferPool); } else { diff --git a/FlashCap.Core/FrameProcessor.cs b/FlashCap.Core/FrameProcessor.cs index dedb43c..b24cddb 100644 --- a/FlashCap.Core/FrameProcessor.cs +++ b/FlashCap.Core/FrameProcessor.cs @@ -16,12 +16,17 @@ namespace FlashCap; public abstract class FrameProcessor { + private readonly BufferPool bufferPool; private readonly Stack reserver = new(); - protected FrameProcessor() + protected FrameProcessor() : + this(new DefaultBufferPool()) { } + protected FrameProcessor(BufferPool bufferPool) => + this.bufferPool = bufferPool; + [Obsolete("Dispose method overriding is obsoleted. Switch OnDisposeAsync instead.", true)] protected virtual void Dispose() => throw new InvalidOperationException(); @@ -59,7 +64,7 @@ protected PixelBuffer GetPixelBuffer() } if (buffer == null) { - buffer = new PixelBuffer(); + buffer = new PixelBuffer(this.bufferPool); } return buffer; } @@ -111,8 +116,9 @@ public void Dispose() { if (this.parent is { } parent) { + var buffer = this.Buffer; base.OnReleaseNow(); - this.parent.ReleasePixelBuffer(this.Buffer); + this.parent.ReleasePixelBuffer(buffer); this.parent = null; } } diff --git a/FlashCap.Core/FrameProcessors/QueuingProcessor.cs b/FlashCap.Core/FrameProcessors/QueuingProcessor.cs index 54eda55..2750754 100644 --- a/FlashCap.Core/FrameProcessors/QueuingProcessor.cs +++ b/FlashCap.Core/FrameProcessors/QueuingProcessor.cs @@ -15,7 +15,7 @@ namespace FlashCap.FrameProcessors; -internal abstract class QueuingProcessor : +public abstract class QueuingProcessor : FrameProcessor { private readonly int maxQueuingFrames; @@ -25,7 +25,8 @@ internal abstract class QueuingProcessor : private volatile bool aborting; private Task? worker; - protected QueuingProcessor(int maxQueuingFrames) + private protected QueuingProcessor(int maxQueuingFrames, BufferPool bufferPool) : + base(bufferPool) { this.maxQueuingFrames = maxQueuingFrames; this.worker = Task.Factory.StartNew( @@ -135,14 +136,14 @@ public override sealed void OnFrameArrived( protected abstract void ThreadEntry(); } -internal sealed class DelegatedQueuingProcessor : +public sealed class DelegatedQueuingProcessor : QueuingProcessor { private PixelBufferArrivedDelegate pixelBufferArrived; public DelegatedQueuingProcessor( - PixelBufferArrivedDelegate pixelBufferArrived, int maxQueuingFrames) : - base(maxQueuingFrames) => + PixelBufferArrivedDelegate pixelBufferArrived, int maxQueuingFrames, BufferPool bufferPool) : + base(maxQueuingFrames, bufferPool) => this.pixelBufferArrived = pixelBufferArrived; protected override void ThreadEntry() @@ -176,14 +177,14 @@ protected override void ThreadEntry() } } -internal sealed class DelegatedQueuingTaskProcessor : +public sealed class DelegatedQueuingTaskProcessor : QueuingProcessor { private PixelBufferArrivedTaskDelegate pixelBufferArrived; public DelegatedQueuingTaskProcessor( - PixelBufferArrivedTaskDelegate pixelBufferArrived, int maxQueuingFrames) : - base(maxQueuingFrames) => + PixelBufferArrivedTaskDelegate pixelBufferArrived, int maxQueuingFrames, BufferPool bufferPool) : + base(maxQueuingFrames, bufferPool) => this.pixelBufferArrived = pixelBufferArrived; protected override async void ThreadEntry() diff --git a/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs b/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs index 86a53cd..f838419 100644 --- a/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs +++ b/FlashCap.Core/FrameProcessors/ScatteringProcessor.cs @@ -15,17 +15,18 @@ namespace FlashCap.FrameProcessors; -internal abstract class ScatteringProcessor : +public abstract class ScatteringProcessor : FrameProcessor { private readonly int maxQueuingFrames; private readonly WaitCallback pixelBufferArrivedEntry; - protected volatile int processing = 1; - protected volatile bool aborting; - protected AsyncManualResetEvent final = new(); + private protected volatile int processing = 1; + private protected volatile bool aborting; + private protected AsyncManualResetEvent final = new(); - protected ScatteringProcessor(int maxQueuingFrames) + private protected ScatteringProcessor(int maxQueuingFrames, BufferPool bufferPool) : + base(bufferPool) { this.maxQueuingFrames = maxQueuingFrames; this.pixelBufferArrivedEntry = this.PixelBufferArrivedEntry; @@ -80,14 +81,14 @@ public override sealed void OnFrameArrived( protected abstract void PixelBufferArrivedEntry(object? parameter); } -internal sealed class DelegatedScatteringProcessor : +public sealed class DelegatedScatteringProcessor : ScatteringProcessor { private PixelBufferArrivedDelegate pixelBufferArrived; public DelegatedScatteringProcessor( - PixelBufferArrivedDelegate pixelBufferArrived, int maxQueuingFrames) : - base(maxQueuingFrames) => + PixelBufferArrivedDelegate pixelBufferArrived, int maxQueuingFrames, BufferPool bufferPool) : + base(maxQueuingFrames, bufferPool) => this.pixelBufferArrived = pixelBufferArrived; protected override async Task OnDisposeAsync() @@ -129,14 +130,14 @@ protected override void PixelBufferArrivedEntry(object? parameter) } } -internal sealed class DelegatedScatteringTaskProcessor : +public sealed class DelegatedScatteringTaskProcessor : ScatteringProcessor { private PixelBufferArrivedTaskDelegate pixelBufferArrived; public DelegatedScatteringTaskProcessor( - PixelBufferArrivedTaskDelegate pixelBufferArrived, int maxQueuingFrames) : - base(maxQueuingFrames) => + PixelBufferArrivedTaskDelegate pixelBufferArrived, int maxQueuingFrames, BufferPool bufferPool) : + base(maxQueuingFrames, bufferPool) => this.pixelBufferArrived = pixelBufferArrived; protected override async Task OnDisposeAsync() diff --git a/FlashCap.Core/PixelBuffer.cs b/FlashCap.Core/PixelBuffer.cs index 121d858..cdd0ddb 100644 --- a/FlashCap.Core/PixelBuffer.cs +++ b/FlashCap.Core/PixelBuffer.cs @@ -15,16 +15,17 @@ namespace FlashCap; public sealed class PixelBuffer { + private readonly BufferPool bufferPool; + private byte[]? imageContainer; private int imageContainerSize; - private byte[]? transcodedImageContainer = null; + private byte[]? transcodedImageContainer; private bool isValidTranscodedImage; private long timestampMicroseconds; private TranscodeFormats transcodeFormat; - internal PixelBuffer() - { - } + internal PixelBuffer(BufferPool bufferPool) => + this.bufferPool = bufferPool; internal unsafe void CopyIn( IntPtr pih, IntPtr pData, int size, @@ -47,18 +48,25 @@ internal unsafe void CopyIn( lock (this) { - if (this.imageContainer == null || - this.imageContainer.Length < totalSize) + var imageContainer = this.imageContainer; + + if (imageContainer == null || + imageContainer.Length < totalSize) { - Debug.WriteLine($"Allocated: CurrentSize={this.imageContainer?.Length ?? 0}, Size={totalSize}"); + if (imageContainer != null) + { + this.bufferPool.Return(imageContainer); + } + imageContainer = this.bufferPool.Rent(totalSize); + this.imageContainer = imageContainer; - this.imageContainer = new byte[totalSize]; + Debug.WriteLine($"Allocated: Size={totalSize}"); } this.imageContainerSize = totalSize; this.isValidTranscodedImage = false; - fixed (byte* pImageContainer = this.imageContainer!) + fixed (byte* pImageContainer = imageContainer) { if (pBih->biCompression == NativeMethods.Compression.MJPG || pBih->biCompression == NativeMethods.Compression.BI_JPEG || @@ -116,24 +124,28 @@ internal unsafe ArraySegment InternalExtractImage(BufferStrategies strateg { lock (this) { - if (this.imageContainer == null) + var imageContainer = this.imageContainer; + if (imageContainer == null) { throw new InvalidOperationException("Extracted before capture."); } if (this.transcodeFormat != TranscodeFormats.DoNotTranscode) { - if (this.isValidTranscodedImage && this.transcodedImageContainer != null) + var transcodedImageContainer = this.transcodedImageContainer; + if (this.isValidTranscodedImage && transcodedImageContainer != null) { if (strategy == BufferStrategies.ForceReuse) { - return new ArraySegment(this.transcodedImageContainer); + return new ArraySegment(transcodedImageContainer); } else { - var copied1 = new byte[this.transcodedImageContainer.Length]; - Array.Copy(this.transcodedImageContainer, copied1, copied1.Length); - return new ArraySegment(copied1); + var copiedImageContainer = new byte[transcodedImageContainer.Length]; + Array.Copy(transcodedImageContainer, + copiedImageContainer, copiedImageContainer.Length); + + return new ArraySegment(copiedImageContainer); } } @@ -150,13 +162,14 @@ internal unsafe ArraySegment InternalExtractImage(BufferStrategies strateg sizeof(NativeMethods.BITMAPINFOHEADER) + sizeImage; - if (this.transcodedImageContainer == null || - this.transcodedImageContainer.Length != totalSize) + if (transcodedImageContainer == null) { - this.transcodedImageContainer = new byte[totalSize]; + transcodedImageContainer = new byte[totalSize]; + this.transcodedImageContainer = transcodedImageContainer; } + Debug.Assert(transcodedImageContainer.Length == totalSize); - fixed (byte* pTranscodedImageContainer = this.transcodedImageContainer) + fixed (byte* pTranscodedImageContainer = transcodedImageContainer) { var pBfhTo = (NativeMethods.BITMAPFILEHEADER*)pTranscodedImageContainer; var pBihTo = (NativeMethods.BITMAPINFOHEADER*)(pBfhTo + 1); @@ -195,13 +208,11 @@ internal unsafe ArraySegment InternalExtractImage(BufferStrategies strateg if (strategy == BufferStrategies.ForceReuse) { this.isValidTranscodedImage = true; - return new ArraySegment(this.transcodedImageContainer); + return new ArraySegment(transcodedImageContainer); } else { - var copied1 = this.transcodedImageContainer; - this.transcodedImageContainer = null; - return new ArraySegment(copied1); + return new ArraySegment(transcodedImageContainer); } } } @@ -210,19 +221,16 @@ internal unsafe ArraySegment InternalExtractImage(BufferStrategies strateg switch (strategy) { case BufferStrategies.ForceReuse: - return new ArraySegment(this.imageContainer, 0, this.imageContainerSize); - case BufferStrategies.CopyWhenDifferentSizeOrReuse: - if (this.imageContainer.Length == this.imageContainerSize) - { - return new ArraySegment(this.imageContainer); - } - break; + return new ArraySegment(imageContainer, 0, this.imageContainerSize); + case BufferStrategies.CopyWhenDifferentSizeOrReuse when + imageContainer.Length == this.imageContainerSize: + return new ArraySegment(imageContainer); } var copied = new byte[this.imageContainerSize]; - Array.Copy(this.imageContainer, copied, copied.Length); + Array.Copy(imageContainer, copied, copied.Length); - Debug.WriteLine($"Copied: CurrentSize={this.imageContainer.Length}, Size={this.imageContainerSize}"); + Debug.WriteLine($"Copied: CurrentSize={imageContainer.Length}, Size={this.imageContainerSize}"); return new ArraySegment(copied); } diff --git a/FlashCap/CaptureDeviceDescriptorExtension.cs b/FlashCap/CaptureDeviceDescriptorExtension.cs index e1e9931..4bdafcc 100644 --- a/FlashCap/CaptureDeviceDescriptorExtension.cs +++ b/FlashCap/CaptureDeviceDescriptorExtension.cs @@ -16,19 +16,6 @@ namespace FlashCap; public static class CaptureDeviceDescriptorExtension { - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task OpenWithFrameProcessorAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - FrameProcessor frameProcessor, - CancellationToken ct = default) => - descriptor.InternalOpenWithFrameProcessorAsync( - characteristics, - transcodeIfYUV ? TranscodeFormats.Auto : TranscodeFormats.DoNotTranscode, - frameProcessor, - ct); - public static Task OpenWithFrameProcessorAsync( this CaptureDeviceDescriptor descriptor, VideoCharacteristics characteristics, @@ -47,19 +34,7 @@ public static Task OpenAsync( CancellationToken ct = default) => descriptor.OpenWithFrameProcessorAsync( characteristics, TranscodeFormats.Auto, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), - ct); - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task OpenAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - PixelBufferArrivedDelegate pixelBufferArrived, - CancellationToken ct = default) => - descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), + new DelegatedQueuingProcessor(pixelBufferArrived, 1, descriptor.defaultBufferPool), ct); public static Task OpenAsync( @@ -70,23 +45,7 @@ public static Task OpenAsync( CancellationToken ct = default) => descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, - new DelegatedQueuingProcessor(pixelBufferArrived, 1), - ct); - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task OpenAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - bool isScattering, - int maxQueuingFrames, - PixelBufferArrivedDelegate pixelBufferArrived, - CancellationToken ct = default) => - descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - isScattering ? - new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames), + new DelegatedQueuingProcessor(pixelBufferArrived, 1, descriptor.defaultBufferPool), ct); public static Task OpenAsync( @@ -100,8 +59,8 @@ public static Task OpenAsync( descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, isScattering ? - new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames), + new DelegatedScatteringProcessor(pixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool) : + new DelegatedQueuingProcessor(pixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool), ct); ////////////////////////////////////////////////////////////////////////////////// @@ -113,19 +72,7 @@ public static Task OpenAsync( CancellationToken ct = default) => descriptor.OpenWithFrameProcessorAsync( characteristics, TranscodeFormats.Auto, - new DelegatedQueuingTaskProcessor(pixelBufferArrived, 1), - ct); - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task OpenAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - PixelBufferArrivedTaskDelegate pixelBufferArrived, - CancellationToken ct = default) => - descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - new DelegatedQueuingTaskProcessor(pixelBufferArrived, 1), + new DelegatedQueuingTaskProcessor(pixelBufferArrived, 1, descriptor.defaultBufferPool), ct); public static Task OpenAsync( @@ -136,23 +83,7 @@ public static Task OpenAsync( CancellationToken ct = default) => descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, - new DelegatedQueuingTaskProcessor(pixelBufferArrived, 1), - ct); - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task OpenAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - bool isScattering, - int maxQueuingFrames, - PixelBufferArrivedTaskDelegate pixelBufferArrived, - CancellationToken ct = default) => - descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - isScattering ? - new DelegatedScatteringTaskProcessor(pixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingTaskProcessor(pixelBufferArrived, maxQueuingFrames), + new DelegatedQueuingTaskProcessor(pixelBufferArrived, 1, descriptor.defaultBufferPool), ct); public static Task OpenAsync( @@ -166,8 +97,8 @@ public static Task OpenAsync( descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, isScattering ? - new DelegatedScatteringTaskProcessor(pixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingTaskProcessor(pixelBufferArrived, maxQueuingFrames), + new DelegatedScatteringTaskProcessor(pixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool) : + new DelegatedQueuingTaskProcessor(pixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool), ct); ////////////////////////////////////////////////////////////////////////////////// @@ -180,24 +111,7 @@ public static async Task AsObservableAsync( var observerProxy = new ObservableCaptureDevice.ObserverProxy(); var captureDevice = await descriptor.OpenWithFrameProcessorAsync( characteristics, TranscodeFormats.Auto, - new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, 1), - ct). - ConfigureAwait(false); - - return new ObservableCaptureDevice(captureDevice, observerProxy); - } - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static async Task AsObservableAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - CancellationToken ct = default) - { - var observerProxy = new ObservableCaptureDevice.ObserverProxy(); - var captureDevice = await descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, 1), + new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, 1, descriptor.defaultBufferPool), ct). ConfigureAwait(false); @@ -213,28 +127,7 @@ public static async Task AsObservableAsync( var observerProxy = new ObservableCaptureDevice.ObserverProxy(); var captureDevice = await descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, - new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, 1), - ct). - ConfigureAwait(false); - - return new ObservableCaptureDevice(captureDevice, observerProxy); - } - - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static async Task AsObservableAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - bool isScattering, - int maxQueuingFrames, - CancellationToken ct = default) - { - var observerProxy = new ObservableCaptureDevice.ObserverProxy(); - var captureDevice = await descriptor.OpenWithFrameProcessorAsync( - characteristics, transcodeIfYUV, - isScattering ? - new DelegatedScatteringProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames), + new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, 1, descriptor.defaultBufferPool), ct). ConfigureAwait(false); @@ -253,8 +146,8 @@ public static async Task AsObservableAsync( var captureDevice = await descriptor.OpenWithFrameProcessorAsync( characteristics, transcodeFormat, isScattering ? - new DelegatedScatteringProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames) : - new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames), + new DelegatedScatteringProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool) : + new DelegatedQueuingProcessor(observerProxy.OnPixelBufferArrived, maxQueuingFrames, descriptor.defaultBufferPool), ct). ConfigureAwait(false); @@ -269,17 +162,6 @@ public static Task TakeOneShotAsync( CancellationToken ct = default) => descriptor.InternalTakeOneShotAsync(characteristics, TranscodeFormats.Auto, ct); - [Obsolete("This overload is obsoleted, please use TranscodeFormats parameter instead.")] - public static Task TakeOneShotAsync( - this CaptureDeviceDescriptor descriptor, - VideoCharacteristics characteristics, - bool transcodeIfYUV, - CancellationToken ct = default) => - descriptor.InternalTakeOneShotAsync( - characteristics, - transcodeIfYUV ? TranscodeFormats.Auto : TranscodeFormats.DoNotTranscode, - ct); - public static Task TakeOneShotAsync( this CaptureDeviceDescriptor descriptor, VideoCharacteristics characteristics, diff --git a/FlashCap/CaptureDeviceExtension.cs b/FlashCap/CaptureDeviceExtension.cs index 58bdf06..b0a40d0 100644 --- a/FlashCap/CaptureDeviceExtension.cs +++ b/FlashCap/CaptureDeviceExtension.cs @@ -34,12 +34,4 @@ public static Task StopAsync(this CaptureDevice captureDevice, CancellationToken public static Task ShowPropertyPageAsync( this CaptureDevice captureDevice, IntPtr parentWindow, CancellationToken ct = default) => captureDevice.InternalShowPropertyPageAsync(parentWindow, ct); - - [Obsolete("Start method will be deprecated. Switch to use StartAsync method.")] - public static void Start(this CaptureDevice captureDevice) => - _ = captureDevice.InternalStartAsync(default); - - [Obsolete("Stop method will be deprecated. Switch to use StopAsync method.")] - public static void Stop(this CaptureDevice captureDevice) => - _ = captureDevice.InternalStopAsync(default); } diff --git a/FlashCap/ObservableCaptureDeviceExtension.cs b/FlashCap/ObservableCaptureDeviceExtension.cs index 0bc8bfd..ab25044 100644 --- a/FlashCap/ObservableCaptureDeviceExtension.cs +++ b/FlashCap/ObservableCaptureDeviceExtension.cs @@ -28,14 +28,6 @@ public static Task StartAsync(this ObservableCaptureDevice observableCaptureDevi public static Task StopAsync(this ObservableCaptureDevice observableCaptureDevice, CancellationToken ct = default) => observableCaptureDevice.InternalStopAsync(ct); - [Obsolete("Start method will be deprecated. Switch to use StartAsync method.")] - public static void Start(this ObservableCaptureDevice observableCaptureDevice) => - _ = observableCaptureDevice.InternalStartAsync(default); - - [Obsolete("Stop method will be deprecated. Switch to use StopAsync method.")] - public static void Stop(this ObservableCaptureDevice observableCaptureDevice) => - _ = observableCaptureDevice.InternalStopAsync(default); - #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif diff --git a/README.md b/README.md index d3beadd..a1ef6bb 100644 --- a/README.md +++ b/README.md @@ -531,6 +531,68 @@ deviceObservable. // ... ``` +## Customize buffer pooling (Advanced topic) + +FlashCap has a buffer pooling interface for reused buffers. +It is implemented by the `BufferPool` base class, which extends this class. + +The default implementation is the `DefaultBufferPool` class, which is used automatically. +This class is a simple implementation, +but uses weak references to allow the GC to reclaim buffers that are no longer in use. + +If you want to replace buffer pooling with your own implementation, +implement the following two abstract methods: + +```csharp +// Base class for buffer pooling. +public abstract class BufferPool +{ + protected BufferPool() + { /* ... */ } + + // Get the buffer. + public abstract byte[] Rent(int minimumSize); + + // Release the buffer. + public abstract void Return(byte[] buffer); +} +``` + +* The `Rent()` method should return a buffer of the size specified or larger in the argument. +* The `Return()` method should pool to take back the buffer specified in the argument, since it is no longer used. + +.NET has GC, the simplest (and non-pooling) implementation would be: + +```csharp +public sealed class FakeBufferPool : BufferPool +{ + public override byte[] Rent(int minimumSize) => + // Always generate a buffer. + new byte[minimumSize]; + + public override void Return(byte[] buffer) + { + // (Unfollow the `buffer` reference and let the GC collect it.) + } +} +``` + +For example, some of you may know that the .NET Core version `System.Buffers` has an `ArrayPool` class. +By extending `BufferPool`, you can use such an existing buffer pooling implementation or your own implementation. + +If you implement your own class in this way, pass it to the constructor of `CaptureDevices` for FlashCap to use: + +```csharp +// Create and use a buffer pooling instance. +var bufferPool = new FakeBufferPool(); + +var devices = new CaptureDevices(bufferPool); + +// ... +``` + +It is used as a common buffer pooling for all devices enumerated from this instance. + ## Master for frame processor (Advanced topic) Welcome to the underground dungeon, where FlashCap's frame processor is a polished gem. diff --git a/README_ja.md b/README_ja.md index 1a0c64c..e51916c 100644 --- a/README_ja.md +++ b/README_ja.md @@ -487,6 +487,66 @@ deviceObservable. // ... ``` +## バッファプーリングのカスタマイズ (Advanced topic) + +FlashCapは、再利用されるバッファのための、バッファプーリングインターフェイスを持っています。 +これは、`BufferPool` 基底クラスで、このクラスを継承して実装します。 + +既定の実装は `DefaultBufferPool` クラスで、自動的に使用されます。 +このクラスは単純な実装ですが、弱参照を使用して、使われなくなったバッファをGCが回収可能にしています。 + +バッファプーリングを独自の実装で置き換えたい場合は、以下の2個の抽象メソッドを実装します: + +```csharp +// バッファプーリングの基底クラス +public abstract class BufferPool +{ + protected BufferPool() + { /* ... */ } + + // バッファを取得する + public abstract byte[] Rent(int minimumSize); + + // バッファを解放する + public abstract void Return(byte[] buffer); +} +``` + +* `Rent()`メソッドは、引数で指定されたサイズ以上のバッファを返す必要があります。 +* `Return()`メソッドは、引数で指定されたバッファがもう使われないため、プーリングで引き取るようにします。 + +.NETにはGCがあるため、最も単純な(かつ、プーリングを行わない)実装は、以下のようになります: + +```csharp +public sealed class FakeBufferPool : BufferPool +{ + public override byte[] Rent(int minimumSize) => + // 常に生成 + new byte[minimumSize]; + + public override void Return(byte[] buffer) + { + // (`buffer` 参照を放置して、GCが回収するに任せる) + } +} +``` + +例えば、.NET Core世代の `System.Buffers` には `ArrayPool` クラスがあることをご存じの方も居るでしょう。 +`BufferPool`を拡張することで、このような既存のバッファプーリング実装や、独自の実装を使用することが出来ます。 + +このようにして独自のクラスを実装した場合は、`CaptureDevices`のコンストラクタに渡して、FlashCapに使用させます: + +```csharp +// バッファプーリングインスタンスを生成して使用 +var bufferPool = new FakeBufferPool(); + +var devices = new CaptureDevices(bufferPool); + +// ... +``` + +この`CaptureDevices`のインスタンスから列挙された全てのデバイスで、共通のバッファプーリングとして使用されます。 + ## フレームプロセッサをマスターする (Advanced topic) 地下ダンジョンへようこそ。FlashCapのフレームプロセッサは、磨けば光る宝石です。しかし、余程のことが無い限り、フレームプロセッサを理解する必要はありません。この解説は、やむを得ずフレームプロセッサを扱う場合の参考にして下さい。また、FlashCapが[デフォルトで内蔵するフレームプロセッサの実装](https://github.com/kekyo/FlashCap/tree/main/FlashCap/FrameProcessors)も参考になるでしょう。 diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs index 530d151..5f7d786 100644 --- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs +++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/ViewModels/MainWindowViewModel.cs @@ -9,7 +9,6 @@ using Avalonia.Controls; using Epoxy; -using FlashCap.Devices; using SkiaSharp; using System; using System.Collections.ObjectModel; @@ -188,8 +187,10 @@ private ValueTask OnDeviceChangedAsync(CaptureDeviceDescriptor? descriptor) this.Characteristics = this.CharacteristicsList.FirstOrDefault(); #endif - - this.UpdateCurrentState(States.Ready); + if (this.Characteristics != null) + { + this.UpdateCurrentState(States.Ready); + } } else { diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml index cf8678b..44e1ad3 100644 --- a/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml +++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia.UI/Views/MainWindow.axaml @@ -37,13 +37,16 @@ SelectedItem="{Binding Characteristics, Mode=TwoWay}" /> -