Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cb0ea9d
Merge remote-tracking branch 'kekyo/develop' into develop
ffquintella Mar 31, 2025
096e2c8
Merge remote-tracking branch 'kekyo/develop' into develop
ffquintella May 22, 2025
50eff5d
Several improvements on resource management.
ffquintella May 22, 2025
c0b0906
INC
ffquintella May 22, 2025
f66bf60
Handle checks
ffquintella May 22, 2025
b1ffa61
More checks
ffquintella May 22, 2025
93c4c05
Improve error messages
ffquintella May 23, 2025
dfc81c1
Fix bug in object creation
ffquintella May 23, 2025
3653c2a
Handle dispose fixes
ffquintella May 23, 2025
0e90da9
Fix Handle checks
ffquintella May 23, 2025
76b8fed
Checking sync context
ffquintella May 23, 2025
ac52dfb
context fix
ffquintella May 23, 2025
8aa79ef
Fix Capture device stop
ffquintella May 23, 2025
7c1937f
Test stop running
ffquintella May 26, 2025
40f6b94
inc
ffquintella May 26, 2025
58aa57e
debug build
ffquintella May 26, 2025
97c672b
mode debug builds.
ffquintella May 26, 2025
ca517cf
small change on stop running
ffquintella May 26, 2025
6deb07d
debug build
ffquintella May 26, 2025
5dff3c4
inc
ffquintella May 26, 2025
5e41bcd
inc
ffquintella May 26, 2025
e6fa275
Adding error handlers
ffquintella May 26, 2025
c956f3b
device dispose
ffquintella May 26, 2025
1c2bb3e
Input and output handling
ffquintella May 26, 2025
8b98aeb
fix handle clean
ffquintella May 26, 2025
d7aa64a
change release order
ffquintella May 27, 2025
b0a14b9
fix dispose
ffquintella May 27, 2025
4570da5
Improve memory dispose
ffquintella May 27, 2025
172ac7c
error checks
ffquintella May 27, 2025
5bd8e45
more changes
ffquintella May 27, 2025
2f148a6
INC
ffquintella May 27, 2025
2dc8342
GC protection
ffquintella May 27, 2025
b0ff04f
inc
ffquintella May 27, 2025
af8753b
More changes
ffquintella May 27, 2025
9993a89
INC
ffquintella May 27, 2025
6a60aed
More code fixes
ffquintella Jul 29, 2025
57052aa
Fix configure await
ffquintella Jul 29, 2025
4b803a3
Merge branch 'config_await' into develop
ffquintella Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions FlashCap.Core/CaptureDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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() =>
Expand All @@ -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() =>
Expand Down Expand Up @@ -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
Expand Down
128 changes: 106 additions & 22 deletions FlashCap.Core/Devices/AVFoundationDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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)
{
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions FlashCap.Core/FlashCap.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<NoWarn>$(NoWarn);CS0649</NoWarn>
<IsPackable>true</IsPackable>
<Version>1.0.0</Version>
</PropertyGroup>

<ItemGroup>
Expand Down
Loading