diff --git a/FlashCap.Core/CaptureDevice.cs b/FlashCap.Core/CaptureDevice.cs index 1dbca6b..e04d620 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(true); #if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1 ValueTask IAsyncDisposable.DisposeAsync() => @@ -43,10 +43,10 @@ ValueTask IAsyncDisposable.DisposeAsync() => public async Task DisposeAsync() { using var _ = await locker.LockAsync(default). - ConfigureAwait(false); + ConfigureAwait(true); await this.OnDisposeAsync(). - ConfigureAwait(false); + ConfigureAwait(true); } protected virtual Task OnDisposeAsync() => @@ -87,18 +87,22 @@ internal Task InternalInitializeAsync( internal async Task InternalStartAsync(CancellationToken ct) { + using var _ = await locker.LockAsync(ct). ConfigureAwait(false); await this.OnStartAsync(ct); + } internal async Task InternalStopAsync(CancellationToken ct) { + using var _ = await locker.LockAsync(ct). ConfigureAwait(false); await this.OnStopAsync(ct); + } #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP diff --git a/FlashCap.Core/Devices/AVFoundationDevice.cs b/FlashCap.Core/Devices/AVFoundationDevice.cs index 8724472..9c7c7f3 100644 --- a/FlashCap.Core/Devices/AVFoundationDevice.cs +++ b/FlashCap.Core/Devices/AVFoundationDevice.cs @@ -34,6 +34,9 @@ public sealed class AVFoundationDevice : CaptureDevice private AVCaptureSession? session; private FrameProcessor? frameProcessor; private IntPtr bitmapHeader; + private VideoBufferHandler? videoBufferHandler; + + private GCHandle? videoBufferHandlerHandle; public AVFoundationDevice(string uniqueID, string modelID) : base(uniqueID, modelID) @@ -43,25 +46,49 @@ public AVFoundationDevice(string uniqueID, string modelID) : protected override async Task OnDisposeAsync() { - if (this.session != null) + try { - this.session.StopRunning(); - this.session.Dispose(); - } - - this.device?.Dispose(); - this.deviceInput?.Dispose(); - this.deviceOutput?.Dispose(); - this.queue?.Dispose(); + // Ensure that we stop the session if it's running + if (session != null && IsRunning) + { + session.StopRunning(); + IsRunning = false; + } + + // Clean up the video buffer handler if it exists + if (videoBufferHandlerHandle.HasValue) + { + videoBufferHandlerHandle.Value.Free(); + videoBufferHandlerHandle = null; + } + + // Now dispose of the session and other resources + if (session != null) + { + session.Dispose(); + session = null; + } + device?.Dispose(); device = null; + deviceInput?.Dispose(); deviceInput = null; + deviceOutput?.Dispose(); deviceOutput = null; + queue?.Dispose(); queue = null; - Marshal.FreeHGlobal(this.bitmapHeader); + if (bitmapHeader != IntPtr.Zero) + { + NativeMethods.FreeMemory(bitmapHeader); + bitmapHeader = IntPtr.Zero; + } - if (frameProcessor is not null) + if (frameProcessor != null) + { + await frameProcessor.DisposeAsync().ConfigureAwait(false); + frameProcessor = null; + } + } + finally { - await frameProcessor.DisposeAsync().ConfigureAwait(false); + await base.OnDisposeAsync().ConfigureAwait(false); } - - await base.OnDisposeAsync().ConfigureAwait(false); } protected override Task OnInitializeAsync(VideoCharacteristics characteristics, TranscodeFormats transcodeFormat, @@ -124,19 +151,27 @@ format.FormatDescription.Dimensions is var dimensions && this.deviceInput = new AVCaptureDeviceInput(device); this.deviceOutput = new AVCaptureVideoDataOutput(); + if (this.deviceOutput.AvailableVideoCVPixelFormatTypes?.Any() == true) { var validPixelFormat = this.deviceOutput.AvailableVideoCVPixelFormatTypes.FirstOrDefault(p => p == pixelFormatType); this.deviceOutput.SetPixelFormatType(validPixelFormat); + this.deviceOutput.SetVideoOutputSize(characteristics.Width, characteristics.Height, validPixelFormat); } else { // Fallback to the mapped pixel format if no available list is provided this.deviceOutput.SetPixelFormatType(pixelFormatType); } - - this.deviceOutput.SetSampleBufferDelegate(new VideoBufferHandler(this), this.queue); + + videoBufferHandler = new VideoBufferHandler(this); + + this.deviceOutput.SetSampleBufferDelegate(videoBufferHandler, this.queue); + + // Protect against GC moving the delegate + videoBufferHandlerHandle = GCHandle.Alloc(videoBufferHandler); + this.deviceOutput.AlwaysDiscardsLateVideoFrames = true; } finally @@ -161,31 +196,80 @@ format.FormatDescription.Dimensions is var dimensions && catch { NativeMethods.FreeMemory(this.bitmapHeader); + this.bitmapHeader = IntPtr.Zero; + + this.queue?.Dispose(); + this.queue = null; + this.device?.Dispose(); + this.device = null; + this.deviceInput?.Dispose(); + this.deviceInput = null; + this.deviceOutput?.Dispose(); + this.deviceOutput = null; + throw; } } protected override Task OnStartAsync(CancellationToken ct) { - this.session?.StartRunning(); - return TaskCompat.CompletedTask; + try + { + if(session== null) + throw new InvalidOperationException("Session is null"); + this.session?.StartRunning(); + this.IsRunning = true; + return TaskCompat.CompletedTask; + }catch (Exception ex) + { + Debug.WriteLine($"Error starting session: {ex.Message}"); + throw new InvalidOperationException("Failed to start the capture session.", ex); + } + } protected override Task OnStopAsync(CancellationToken ct) { - this.session?.StopRunning(); + try + { + if(session== null) + throw new InvalidOperationException("Session is null"); + if (this.IsRunning) + { + this.session?.StopRunning(); + this.IsRunning = false; + + } + + }catch (Exception ex) + { + Debug.WriteLine($"Error stopping session: {ex.Message}"); + throw new InvalidOperationException("Failed to stop the capture session.", ex); + } return TaskCompat.CompletedTask; } protected override void OnCapture(IntPtr pData, int size, long timestampMicroseconds, long frameIndex, PixelBuffer buffer) { - buffer.CopyIn(this.bitmapHeader, pData, size, timestampMicroseconds, frameIndex, TranscodeFormats.Auto); + try + { + if (this.bitmapHeader == IntPtr.Zero) return; + if (pData == IntPtr.Zero || size <= 0) + { + throw new ArgumentException("Invalid pixel data or size."); + } + buffer.CopyIn(this.bitmapHeader, pData, size, timestampMicroseconds, frameIndex, TranscodeFormats.Auto); + }catch (Exception ex) + { + Debug.WriteLine($"Error capturing frame: {ex.Message}"); + throw new InvalidOperationException("Failed to capture frame.", ex); + } } internal sealed class VideoBufferHandler : AVCaptureVideoDataOutputSampleBuffer { private readonly AVFoundationDevice device; - private int frameIndex; + private int frameIndex = 0; public VideoBufferHandler(AVFoundationDevice device) { @@ -194,7 +278,7 @@ public VideoBufferHandler(AVFoundationDevice device) public override void DidDropSampleBuffer(IntPtr captureOutput, IntPtr sampleBuffer, IntPtr connection) { - Debug.WriteLine("Dropped"); + //Debug.WriteLine("Dropped"); var valid = CMSampleBufferIsValid(sampleBuffer); } diff --git a/FlashCap.Core/FlashCap.Core.csproj b/FlashCap.Core/FlashCap.Core.csproj index c59cbb8..f4b9917 100644 --- a/FlashCap.Core/FlashCap.Core.csproj +++ b/FlashCap.Core/FlashCap.Core.csproj @@ -5,6 +5,7 @@ True $(NoWarn);CS0649 true + 1.0.0 diff --git a/FlashCap.Core/Internal/AVFoundation/AVCaptureSession.cs b/FlashCap.Core/Internal/AVFoundation/AVCaptureSession.cs index f248612..0070ee5 100644 --- a/FlashCap.Core/Internal/AVFoundation/AVCaptureSession.cs +++ b/FlashCap.Core/Internal/AVFoundation/AVCaptureSession.cs @@ -18,6 +18,11 @@ partial class LibAVFoundation { public sealed class AVCaptureSession : LibObjC.NSObject { + + private AVCaptureVideoDataOutput? _videoDataOutput; + private AVCaptureInput? _videoDataInput; + + public AVCaptureSession() : base(IntPtr.Zero, false) { Init(); @@ -25,59 +30,161 @@ public AVCaptureSession() : base(IntPtr.Zero, false) private void Init() { - var sessionClass = LibObjC.SendAndGetHandle( - LibObjC.GetClass("AVCaptureSession"), - LibObjC.GetSelector(LibObjC.AllocSelector)); + try + { + var sessionClass = LibObjC.SendAndGetHandle( + LibObjC.GetClass("AVCaptureSession"), + LibObjC.GetSelector(LibObjC.AllocSelector)); - var sessionObj = LibObjC.SendAndGetHandle( - sessionClass, - LibObjC.GetSelector("init")); + var sessionObj = LibObjC.SendAndGetHandle( + sessionClass, + LibObjC.GetSelector("init")); + + if (sessionObj == IntPtr.Zero) + { + throw new Exception("Failed to create AVCaptureSession"); + } - Handle = sessionObj; + Handle = sessionObj; - LibCoreFoundation.CFRetain(this.Handle); + LibCoreFoundation.CFRetain(this.Handle); + + } catch (Exception ex) + { + Console.WriteLine($"Error initializing AVCaptureSession: {ex.Message}"); + throw; + } + } + + private void ValidateHandle() + { + if (Handle == IntPtr.Zero) + { + throw new ObjectDisposedException(nameof(AVCaptureSession), "Handle invalid."); + } + } + + public void AddInput(AVCaptureInput input) + { + try + { + ValidateHandle(); + + _videoDataInput = input as AVCaptureInput; + + LibObjC.SendNoResult( + Handle, + LibObjC.GetSelector("addInput:"), + input.Handle); + } catch (Exception ex) + { + Console.WriteLine($"Error calling addInput: {ex.Message}"); + throw; + } } - public void AddInput(AVCaptureInput input) => - LibObjC.SendNoResult( - Handle, - LibObjC.GetSelector("addInput:"), - input.Handle); public void AddOutput(AVCaptureOutput output) { - IntPtr allocSel = LibObjC.GetSelector("alloc"); - IntPtr initSel = LibObjC.GetSelector("init"); - - var videoDataOutputObj = output as AVCaptureVideoDataOutput ; + try + { + ValidateHandle(); + + _videoDataOutput = output as AVCaptureVideoDataOutput ; - if (videoDataOutputObj == null) + if (_videoDataOutput == null) + { + throw new Exception("Failed to get video data output"); + } + + var videoDataOutput = _videoDataOutput.Handle; + + LibObjC.SendNoResult( + Handle, + LibObjC.GetSelector("addOutput:"), + videoDataOutput); + + } catch (Exception ex) { - throw new Exception("Failed to get video data output"); + Console.WriteLine($"Error calling addOutput: {ex.Message}"); + throw; + } + } + + public bool CanAddOutput(AVCaptureOutput output) + { + try + { + ValidateHandle(); + return LibObjC.SendAndGetBool( + Handle, + LibObjC.GetSelector("canAddOutput:"), + output.Handle); + } catch (Exception ex) + { + Console.WriteLine($"Error calling canAddOutput: {ex.Message}"); + throw; + } + } + + + public void StartRunning() + { + try + { + ValidateHandle(); + LibObjC.SendNoResult( + Handle, + LibObjC.GetSelector("startRunning")); + } catch (Exception ex) + { + Console.WriteLine($"Error starting AVCaptureSession: {ex.Message}"); + } + } + + public void StopRunning() + { + try + { + ValidateHandle(); + var selector = LibObjC.GetSelector("stopRunning"); + LibObjC.SendNoResult( + Handle, + selector); + } catch (Exception ex) + { + Console.WriteLine($"Error stopping AVCaptureSession: {ex.Message}"); } - var videoDataOutput = videoDataOutputObj.Handle; - - LibObjC.SendNoResult( - Handle, - LibObjC.GetSelector("addOutput:"), - videoDataOutput); + } + + protected override void Dispose(bool disposing) + { + try + { + /*if (Handle != IntPtr.Zero) + { + LibCoreFoundation.CFRelease(Handle); + Handle = IntPtr.Zero; + }*/ + + if (_videoDataOutput != null) + { + _videoDataOutput.Dispose(); + _videoDataOutput = null; + } + if (_videoDataInput != null) + { + _videoDataInput.Dispose(); + _videoDataInput = null; + } + + base.Dispose(disposing); + } catch (Exception ex) + { + Console.WriteLine($"Error disposing AVCaptureSession: {ex.Message}"); + } } - public bool CanAddOutput(AVCaptureOutput output) => - LibObjC.SendAndGetBool( - Handle, - LibObjC.GetSelector("canAddOutput:"), - output.Handle); - - public void StartRunning() => - LibObjC.SendNoResult( - Handle, - LibObjC.GetSelector("startRunning")); - - public void StopRunning() => - LibObjC.SendNoResult( - Handle, - LibObjC.GetSelector("stopRunning")); } } diff --git a/FlashCap.Core/Internal/AVFoundation/AVCaptureVideoDataOutput.cs b/FlashCap.Core/Internal/AVFoundation/AVCaptureVideoDataOutput.cs index 06189e1..4455b21 100644 --- a/FlashCap.Core/Internal/AVFoundation/AVCaptureVideoDataOutput.cs +++ b/FlashCap.Core/Internal/AVFoundation/AVCaptureVideoDataOutput.cs @@ -21,6 +21,7 @@ partial class LibAVFoundation public sealed class AVCaptureVideoDataOutput : AVCaptureOutput { private AVCaptureVideoDataOutputSampleBuffer.CaptureOutputDidOutputSampleBuffer? callbackDelegate; + private GCHandle? callbackHandle; public AVCaptureVideoDataOutput() : base(IntPtr.Zero, retain: false) { @@ -39,90 +40,230 @@ private void Init() LibCoreFoundation.CFRetain(Handle); } + + private void ValidateHandle() + { + if (Handle == IntPtr.Zero) + { + throw new ObjectDisposedException(nameof(AVCaptureVideoDataOutput), "Handle invalid. 0H01X"); + } + } - public unsafe int[] AvailableVideoCVPixelFormatTypes => - LibCoreFoundation.CFArray.ToArray( - LibObjC.SendAndGetHandle( - Handle, - LibObjC.GetSelector("availableVideoCVPixelFormatTypes")), - static handle => - { - int value; - if (LibCoreFoundation.CFNumberGetValue(handle, LibCoreFoundation.CFNumberType.sInt32Type, &value)) - return value; - throw new InvalidOperationException("The value contained by CFNumber cannot be read as 32-bit signed integer."); - }); + public unsafe int[] AvailableVideoCVPixelFormatTypes + { + get + { + ValidateHandle(); + return LibCoreFoundation.CFArray.ToArray( + LibObjC.SendAndGetHandle( + Handle, + LibObjC.GetSelector("availableVideoCVPixelFormatTypes")), + static handle => + { + int value; + if (LibCoreFoundation.CFNumberGetValue(handle, LibCoreFoundation.CFNumberType.sInt32Type, &value)) + return value; + throw new InvalidOperationException("The value contained by CFNumber cannot be read as 32-bit signed integer."); + }); + } + } public bool AlwaysDiscardsLateVideoFrames { - get => - LibObjC.SendAndGetBool( + get + { + ValidateHandle(); + return LibObjC.SendAndGetBool( Handle, LibObjC.GetSelector("alwaysDiscardsLateVideoFrames")); - set => + } + set + { + ValidateHandle(); LibObjC.SendNoResult( Handle, LibObjC.GetSelector("setAlwaysDiscardsLateVideoFrames:"), value); + } } + public void SetPixelFormatType(int format) { - var pixelFormat = format; - - IntPtr pixelFormatTypeKeyPtr = Dlfcn.dlsym(LibCoreVideo.Handle, "kCVPixelBufferPixelFormatTypeKey"); - if (pixelFormatTypeKeyPtr == IntPtr.Zero) + try { - throw new Exception("Error comunicating with the AVCaptureVideoDataOutput"); + ValidateHandle(); + var pixelFormat = format; + + IntPtr pixelFormatTypeKeyPtr = Dlfcn.dlsym(LibCoreVideo.Handle, "kCVPixelBufferPixelFormatTypeKey"); + if (pixelFormatTypeKeyPtr == IntPtr.Zero) + { + throw new Exception("Error comunicating with the AVCaptureVideoDataOutput"); + } + + IntPtr nsPixelFormatKey = Marshal.ReadIntPtr(pixelFormatTypeKeyPtr); + IntPtr nsNumber = LibObjC.CreateNSNumber(pixelFormat); + + IntPtr nsDictionaryClass = LibObjC.GetClass("NSDictionary"); + IntPtr dictSel = LibObjC.GetSelector("dictionaryWithObject:forKey:"); + IntPtr videoSettings = LibObjC.SendAndGetHandle(nsDictionaryClass, dictSel, nsNumber, nsPixelFormatKey); + IntPtr setVideoSettingsSel = LibObjC.GetSelector("setVideoSettings:"); + LibObjC.SendNoResult(this.Handle, setVideoSettingsSel, videoSettings); + }catch (Exception ex) + { + Debug.WriteLine($"Error setting pixel format type: {ex.Message}"); + throw; } + + } + + public void SetVideoOutputSize(int width, int height, int pixelFormat) + { + ValidateHandle(); - // Get NSString value - IntPtr nsPixelFormatKey = Marshal.ReadIntPtr(pixelFormatTypeKeyPtr); - IntPtr nsNumber = LibObjC.CreateNSNumber(pixelFormat); + // Cria NSNumber para pixelFormat + IntPtr nsPixelFormatKeyPtr = Dlfcn.dlsym(LibCoreVideo.Handle, "kCVPixelBufferPixelFormatTypeKey"); + if (nsPixelFormatKeyPtr == IntPtr.Zero) + throw new Exception("Error comunicating with the AVCaptureVideoDataOutput"); + IntPtr nsPixelFormatKey = Marshal.ReadIntPtr(nsPixelFormatKeyPtr); + IntPtr nsNumberPixelFormat = LibObjC.CreateNSNumber(pixelFormat); + + // Cria NSNumber para width + IntPtr nsWidthKeyPtr = Dlfcn.dlsym(LibCoreVideo.Handle, "kCVPixelBufferWidthKey"); + if (nsWidthKeyPtr == IntPtr.Zero) + throw new Exception("Error comunicating with the AVCaptureVideoDataOutput"); + IntPtr nsWidthKey = Marshal.ReadIntPtr(nsWidthKeyPtr); + IntPtr nsNumberWidth = LibObjC.CreateNSNumber(width); + + // Cria NSNumber para height + IntPtr nsHeightKeyPtr = Dlfcn.dlsym(LibCoreVideo.Handle, "kCVPixelBufferHeightKey"); + if (nsHeightKeyPtr == IntPtr.Zero) + throw new Exception("Error comunicating with the AVCaptureVideoDataOutput"); + IntPtr nsHeightKey = Marshal.ReadIntPtr(nsHeightKeyPtr); + IntPtr nsNumberHeight = LibObjC.CreateNSNumber(height); + + // Cria NSArray de keys e values + IntPtr nsArrayClass = LibObjC.GetClass("NSArray"); + IntPtr arrayWithObjectsSel = LibObjC.GetSelector("arrayWithObjects:count:"); + IntPtr keysArray; + IntPtr valuesArray; + unsafe + { + IntPtr* keys = stackalloc IntPtr[3] { nsPixelFormatKey, nsWidthKey, nsHeightKey }; + IntPtr* values = stackalloc IntPtr[3] { nsNumberPixelFormat, nsNumberWidth, nsNumberHeight }; + + keysArray = LibObjC.SendAndGetHandle(nsArrayClass, arrayWithObjectsSel, (IntPtr)keys, new IntPtr(3)); + valuesArray = LibObjC.SendAndGetHandle(nsArrayClass, arrayWithObjectsSel, (IntPtr)values, new IntPtr(3)); + } + // Cria NSDictionary com as chaves e valores IntPtr nsDictionaryClass = LibObjC.GetClass("NSDictionary"); - IntPtr dictSel = LibObjC.GetSelector("dictionaryWithObject:forKey:"); - IntPtr videoSettings = LibObjC.SendAndGetHandle(nsDictionaryClass, dictSel, nsNumber, nsPixelFormatKey); + IntPtr dictWithObjectsForKeysSel = LibObjC.GetSelector("dictionaryWithObjects:forKeys:"); + IntPtr videoSettings = LibObjC.SendAndGetHandle(nsDictionaryClass, dictWithObjectsForKeysSel, valuesArray, keysArray); + + // Seta o dicionĂ¡rio como video settings IntPtr setVideoSettingsSel = LibObjC.GetSelector("setVideoSettings:"); LibObjC.SendNoResult(this.Handle, setVideoSettingsSel, videoSettings); - } public void SetSampleBufferDelegate(AVFoundationDevice.VideoBufferHandler sampleBufferDelegate, LibCoreFoundation.DispatchQueue sampleBufferCallbackQueue) { - if (sampleBufferDelegate == null) + try { - Debug.WriteLine("AVCaptureVideoDataOutputSampleBufferDelegate is null"); - return; + ValidateHandle(); + if (sampleBufferDelegate == null) + { + Debug.WriteLine("AVCaptureVideoDataOutputSampleBufferDelegate is null"); + return; + } + + // If the handle is already set, free the previous delegate + if (callbackHandle.HasValue) + { + callbackHandle.Value.Free(); + callbackHandle = null; + } + + IntPtr selDidOutput = LibObjC.GetSelector("captureOutput:didOutputSampleBuffer:fromConnection:"); + callbackDelegate = sampleBufferDelegate.CaptureOutputCallback; + callbackHandle = GCHandle.Alloc(callbackDelegate); + IntPtr impCallback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); + + IntPtr allocSel = LibObjC.GetSelector("alloc"); + IntPtr initSel = LibObjC.GetSelector("init"); + IntPtr nsObjectClass = LibObjC.GetClass("NSObject"); + IntPtr delegateClass = + LibObjC.objc_allocateClassPair(nsObjectClass, "CaptureDelegate_" + Handle, IntPtr.Zero); + + string types = "v@:@@@"; + bool added = LibObjC.class_addMethod(delegateClass, selDidOutput, impCallback, types); + if (!added) + { + return; + } + + LibObjC.objc_registerClassPair(delegateClass); + + IntPtr delegateInstanceAlloc = LibObjC.SendAndGetHandle(delegateClass, allocSel); + IntPtr delegateInstance = LibObjC.SendAndGetHandle(delegateInstanceAlloc, initSel); + + IntPtr setDelegateSel = LibObjC.GetSelector("setSampleBufferDelegate:queue:"); + LibObjC.SendNoResult(Handle, setDelegateSel, delegateInstance, sampleBufferCallbackQueue.Handle); + } + catch (Exception ex) + { + Debug.WriteLine($"Error setting sample buffer: {ex.Message}"); + throw new InvalidOperationException("Failed to set sample buffer delegate.", ex); } - IntPtr allocSel = LibObjC.GetSelector("alloc"); - IntPtr initSel = LibObjC.GetSelector("init"); - IntPtr nsObjectClass = LibObjC.GetClass("NSObject"); - IntPtr delegateClass = LibObjC.objc_allocateClassPair(nsObjectClass, "CaptureDelegate_" + Handle, IntPtr.Zero); - IntPtr selDidOutput = LibObjC.GetSelector("captureOutput:didOutputSampleBuffer:fromConnection:"); - - callbackDelegate = sampleBufferDelegate.CaptureOutputCallback; - - IntPtr impCallback = Marshal.GetFunctionPointerForDelegate(callbackDelegate); + } + + public new void Dispose() + { + try + { + Dispose(true); + GC.SuppressFinalize(this); + } + catch (Exception ex) + { + Debug.WriteLine($"Error disposing AVCaptureVideoDataOutput: {ex.Message}"); + throw new InvalidOperationException("Failed to dispose AVCaptureVideoDataOutput.", ex); + } + + } - // "v@:@@@" this means the methood returns void and receives (self, _cmd, output, sampleBuffer, connection). - string types = "v@:@@@"; - bool added = LibObjC.class_addMethod(delegateClass, selDidOutput, impCallback, types); - if (!added) + protected override void Dispose(bool disposing) + { + try + { + // Cleans delegates GCHandle + if (callbackHandle.HasValue) + { + callbackHandle.Value.Free(); + callbackHandle = null; + } + + + if (Handle != IntPtr.Zero) + { + LibCoreFoundation.CFRelease(Handle); + Handle = IntPtr.Zero; + } + + base.Dispose(disposing); + } catch( Exception ex) { - return; + Debug.WriteLine($"Error in Dispose: {ex.Message}"); + throw new InvalidOperationException("Failed to dispose AVCaptureVideoDataOutput.", ex); } - LibObjC.objc_registerClassPair(delegateClass); + } - // Delegate creation - IntPtr delegateInstanceAlloc = LibObjC.SendAndGetHandle(delegateClass, allocSel); - IntPtr delegateInstance = LibObjC.SendAndGetHandle(delegateInstanceAlloc, initSel); - - IntPtr setDelegateSel = LibObjC.GetSelector("setSampleBufferDelegate:queue:"); - LibObjC.SendNoResult(Handle, setDelegateSel, delegateInstance, sampleBufferCallbackQueue.Handle); + ~AVCaptureVideoDataOutput() + { + Dispose(false); } } } diff --git a/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs b/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs index 4690e56..2d65c5f 100644 --- a/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs +++ b/FlashCap.Core/Internal/AVFoundation/LibAVFoundation.cs @@ -24,6 +24,14 @@ internal static partial class LibAVFoundation public delegate void AVRequestAccessStatus(bool accessGranted); + private static void ValidateHandle() + { + if (Handle == IntPtr.Zero) + { + throw new ObjectDisposedException(nameof(LibAVFoundation), "Handle invalid."); + } + } + public sealed class AVFrameRateRange : LibObjC.NSObject { public AVFrameRateRange(IntPtr handle, bool retain) : @@ -108,33 +116,67 @@ public string LocalizedName } } + + private AVCaptureDeviceFormat[]? _formats; + private AVCaptureDeviceFormat? _activeFormat; public AVCaptureDeviceFormat[] Formats { get { + _formats?.ToList().ForEach(f => f.Dispose()); + var handle = LibObjC.SendAndGetHandle( Handle, LibObjC.GetSelector("formats")); - return LibCoreFoundation.CFArray.ToArray(handle, static handle => new AVCaptureDeviceFormat(handle, retain: true)); + _formats = LibCoreFoundation.CFArray.ToArray(handle, static handle => new AVCaptureDeviceFormat(handle, retain: true)); + + return _formats; } } public AVCaptureDeviceFormat ActiveFormat { - get => new AVCaptureDeviceFormat( - LibObjC.SendAndGetHandle( - Handle, - LibObjC.GetSelector("activeFormat")), - retain: true); - set => + get + { + //_activeFormat?.Dispose(); + if(_activeFormat == null) + _activeFormat = new AVCaptureDeviceFormat( + LibObjC.SendAndGetHandle( + Handle, + LibObjC.GetSelector("activeFormat")), + retain: true); + return _activeFormat; + } + set + { LibObjC.SendNoResult( Handle, LibObjC.GetSelector("setActiveFormat:"), value.Handle); + _activeFormat?.Dispose(); + _activeFormat = value; + } + } + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_formats != null) + { + foreach (var format in _formats) + format.Dispose(); + _formats = null; + } + _activeFormat?.Dispose(); + _activeFormat = null; + } + base.Dispose(disposing); + } + public LibCoreMedia.CMTime ActiveVideoMinFrameDuration { get @@ -209,7 +251,7 @@ public unsafe void UnlockForConfiguration() LibObjC.GetSelector("deviceWithUniqueID:"), nativeDeviceUniqueID.Handle); - return handle == IntPtr.Zero ? null : new AVCaptureDevice(handle, retain: false); + return handle == IntPtr.Zero ? null : new AVCaptureDevice(handle, retain: true); } public static AVAuthorizationStatus GetAuthorizationStatus(IntPtr mediaType) @@ -226,6 +268,7 @@ public static AVAuthorizationStatus GetAuthorizationStatus(IntPtr mediaType) public static unsafe void RequestAccessForMediaType(IntPtr mediaType, AVRequestAccessStatus completion) { + ValidateHandle(); RequestAccessForMediaTypeBlockFactory ??= LibObjC.BlockLiteralFactory.CreateFactory( signature: "v@?^vC", delegate (IntPtr block, byte accessGranted) @@ -247,6 +290,7 @@ public static unsafe void RequestAccessForMediaType(IntPtr mediaType, AVRequestA public sealed class AVCaptureDeviceDiscoverySession : LibObjC.NSObject { + public AVCaptureDeviceDiscoverySession(IntPtr handle, bool retain) : base(handle, retain) { } @@ -260,6 +304,7 @@ public AVCaptureDeviceDiscoverySession(IntPtr handle, bool retain) : public static AVCaptureDeviceDiscoverySession DiscoverySessionWithVideoDevices() { + ValidateHandle(); var deviceTypes = new[] { AVCaptureDeviceType.BuiltInWideAngleCamera, diff --git a/FlashCap.Core/Internal/NativeMethods.cs b/FlashCap.Core/Internal/NativeMethods.cs index dd65909..494dd70 100644 --- a/FlashCap.Core/Internal/NativeMethods.cs +++ b/FlashCap.Core/Internal/NativeMethods.cs @@ -561,6 +561,10 @@ public static bool GetCompressionAndBitCount( compression = Compression.ARGB; bitCount = 32; return true; + case PixelFormats.BGRA32: + compression = Compression.ARGB; + bitCount = 32; + return true; case PixelFormats.RGB16: compression = Compression.D3D_RGB565; bitCount = 16; diff --git a/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs b/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs index cdef71b..15b57ed 100644 --- a/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs +++ b/FlashCap.Core/Internal/NativeMethods_AVFoundation.cs @@ -25,8 +25,8 @@ internal static class NativeMethods_AVFoundation //[PixelFormats.UYVY] = LibCoreVideo.PixelFormatType_24RGB, [PixelFormats.RGB32] = 32, //[PixelFormats.ARGB32] = LibCoreVideo.PixelFormatType_32BGRA, - //[PixelFormats.BGRA32] = LibCoreVideo.PixelFormatType_32BGRA, - [PixelFormats.ARGB32] = LibCoreVideo.PixelFormatType_32BGRA, + [PixelFormats.BGRA32] = LibCoreVideo.PixelFormatType_32BGRA, + //[PixelFormats.ARGB32] = LibCoreVideo.PixelFormatType_32BGRA, [PixelFormats.RGB24] = LibCoreVideo.PixelFormatType_24RGB, [PixelFormats.UYVY] = LibCoreVideo.PixelFormatType_422YpCbCr8_yuvs, [PixelFormats.YUYV] = LibCoreVideo.PixelFormatType_422YpCbCr8, @@ -394,6 +394,9 @@ public abstract class NSObject : NativeObject { protected NSObject(IntPtr handle, bool retain) { + + if (handle == IntPtr.Zero) return; + Handle = handle; if (retain) @@ -402,12 +405,16 @@ protected NSObject(IntPtr handle, bool retain) protected override void Dispose(bool disposing) { + if (!disposing) return; + if (Handle == IntPtr.Zero) return; - LibObjC.SendNoResult( + LibCoreFoundation.CFRelease(Handle); + + /*LibObjC.SendNoResult( Handle, - LibObjC.GetSelector(LibObjC.ReleaseSelector)); + LibObjC.GetSelector(LibObjC.ReleaseSelector));*/ Handle = IntPtr.Zero; } @@ -607,16 +614,26 @@ public sealed class DispatchQueue : NativeObject { public DispatchQueue(string label) { - //Handle = LibC.DispatchQueueCreate(label, IntPtr.Zero) is var handle && handle != IntPtr.Zero - // ? handle : throw new InvalidOperationException("Cannot create a dispatch queue."); - Handle = LibSystem.dispatch_queue_create(label, IntPtr.Zero); + //Handle = LibSystem.dispatch_queue_create(label, IntPtr.Zero); + Handle = LibC.DispatchQueueCreate(label, IntPtr.Zero); + if (Handle == IntPtr.Zero) + { + throw new InvalidOperationException("Handle invalid 0H003."); + } CFRetain(Handle); } - - - protected override void Dispose(bool disposing) => - LibC.DispatchRelease(Handle); + + + protected override void Dispose(bool disposing) + { + if (Handle == IntPtr.Zero) return; + if (Handle != IntPtr.Zero) + { + LibC.DispatchRelease(Handle); + Handle = IntPtr.Zero; + } + } } } diff --git a/FlashCap.Core/VideoCharacteristics.cs b/FlashCap.Core/VideoCharacteristics.cs index a877b1c..df700e6 100644 --- a/FlashCap.Core/VideoCharacteristics.cs +++ b/FlashCap.Core/VideoCharacteristics.cs @@ -25,7 +25,8 @@ public enum PixelFormats PNG, UYVY, YUYV, - NV12 + NV12, + BGRA32 } public sealed class VideoCharacteristics : diff --git a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj index 63e5bf8..a712953 100644 --- a/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj +++ b/samples/FlashCap.Avalonia/FlashCap.Avalonia/FlashCap.Avalonia.csproj @@ -2,7 +2,7 @@ WinExe - net48;net8.0 + net9.0;net48;net8.0 true