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