Skip to content

Commit ca7454e

Browse files
Carti-itffquintella
andcommitted
New Video Format - Squashed & memory management
Co-authored-by: Felipe Quintella <felipe@quintella.com>
1 parent 60fa896 commit ca7454e

File tree

9 files changed

+497
-161
lines changed

9 files changed

+497
-161
lines changed

FlashCap.Core/CaptureDevice.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ protected CaptureDevice(object identity, string name)
3333
[MethodImpl(MethodImplOptions.AggressiveInlining)]
3434
#endif
3535
public void Dispose() =>
36-
_ = this.DisposeAsync().ConfigureAwait(false);
36+
_ = this.DisposeAsync().ConfigureAwait(true);
3737

3838
#if NETCOREAPP3_0_OR_GREATER || NETSTANDARD2_1
3939
ValueTask IAsyncDisposable.DisposeAsync() =>
@@ -43,10 +43,10 @@ ValueTask IAsyncDisposable.DisposeAsync() =>
4343
public async Task DisposeAsync()
4444
{
4545
using var _ = await locker.LockAsync(default).
46-
ConfigureAwait(false);
46+
ConfigureAwait(true);
4747

4848
await this.OnDisposeAsync().
49-
ConfigureAwait(false);
49+
ConfigureAwait(true);
5050
}
5151

5252
protected virtual Task OnDisposeAsync() =>

FlashCap.Core/Devices/AVFoundationDevice.cs

Lines changed: 106 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ public sealed class AVFoundationDevice : CaptureDevice
3434
private AVCaptureSession? session;
3535
private FrameProcessor? frameProcessor;
3636
private IntPtr bitmapHeader;
37+
private VideoBufferHandler? videoBufferHandler;
38+
39+
private GCHandle? videoBufferHandlerHandle;
3740

3841
public AVFoundationDevice(string uniqueID, string modelID) :
3942
base(uniqueID, modelID)
@@ -42,39 +45,50 @@ public AVFoundationDevice(string uniqueID, string modelID) :
4245
}
4346

4447
protected override async Task OnDisposeAsync()
45-
{
46-
// Ensure that we stop the session if it's running
47-
if (this.session is not null && IsRunning)
48-
{
49-
this.session.StopRunning();
50-
IsRunning = false;
51-
}
48+
{
49+
try
50+
{
51+
// Ensure that we stop the session if it's running
52+
if (session != null && IsRunning)
53+
{
54+
session.StopRunning();
55+
IsRunning = false;
56+
}
57+
58+
// Clean up the video buffer handler if it exists
59+
if (videoBufferHandlerHandle.HasValue)
60+
{
61+
videoBufferHandlerHandle.Value.Free();
62+
videoBufferHandlerHandle = null;
63+
}
64+
65+
// Now dispose of the session and other resources
66+
if (session != null)
67+
{
68+
session.Dispose();
69+
session = null;
70+
}
71+
device?.Dispose(); device = null;
72+
deviceInput?.Dispose(); deviceInput = null;
73+
deviceOutput?.Dispose(); deviceOutput = null;
74+
queue?.Dispose(); queue = null;
5275

53-
this.session?.Dispose();
54-
this.deviceInput?.Dispose();
55-
this.deviceOutput?.Dispose();
56-
this.device?.Dispose();
57-
this.queue?.Dispose();
58-
59-
this.session = null;
60-
this.deviceInput = null;
61-
this.deviceOutput = null;
62-
this.device = null;
63-
this.queue = null;
64-
65-
if (this.bitmapHeader != IntPtr.Zero)
66-
{
67-
Marshal.FreeHGlobal(this.bitmapHeader);
68-
this.bitmapHeader = IntPtr.Zero;
69-
}
76+
if (bitmapHeader != IntPtr.Zero)
77+
{
78+
NativeMethods.FreeMemory(bitmapHeader);
79+
bitmapHeader = IntPtr.Zero;
80+
}
7081

71-
if (this.frameProcessor is not null)
82+
if (frameProcessor != null)
83+
{
84+
await frameProcessor.DisposeAsync().ConfigureAwait(false);
85+
frameProcessor = null;
86+
}
87+
}
88+
finally
7289
{
73-
await this.frameProcessor.DisposeAsync().ConfigureAwait(false);
74-
this.frameProcessor = null;
90+
await base.OnDisposeAsync().ConfigureAwait(false);
7591
}
76-
77-
await base.OnDisposeAsync().ConfigureAwait(false);
7892
}
7993

8094
protected override Task OnInitializeAsync(VideoCharacteristics characteristics, TranscodeFormats transcodeFormat,
@@ -142,14 +156,21 @@ format.FormatDescription.Dimensions is var dimensions &&
142156
{
143157
var validPixelFormat = this.deviceOutput.AvailableVideoCVPixelFormatTypes.FirstOrDefault(p => p == pixelFormatType);
144158
this.deviceOutput.SetPixelFormatType(validPixelFormat);
159+
this.deviceOutput.SetVideoOutputSize(characteristics.Width, characteristics.Height, validPixelFormat);
145160
}
146161
else
147162
{
148163
// Fallback to the mapped pixel format if no available list is provided
149164
this.deviceOutput.SetPixelFormatType(pixelFormatType);
150165
}
151-
152-
this.deviceOutput.SetSampleBufferDelegate(new VideoBufferHandler(this), this.queue);
166+
167+
videoBufferHandler = new VideoBufferHandler(this);
168+
169+
this.deviceOutput.SetSampleBufferDelegate(videoBufferHandler, this.queue);
170+
171+
// Protect against GC moving the delegate
172+
videoBufferHandlerHandle = GCHandle.Alloc(videoBufferHandler);
173+
153174
this.deviceOutput.AlwaysDiscardsLateVideoFrames = true;
154175
}
155176
finally
@@ -174,31 +195,80 @@ format.FormatDescription.Dimensions is var dimensions &&
174195
catch
175196
{
176197
NativeMethods.FreeMemory(this.bitmapHeader);
198+
this.bitmapHeader = IntPtr.Zero;
199+
200+
this.queue?.Dispose();
201+
this.queue = null;
202+
this.device?.Dispose();
203+
this.device = null;
204+
this.deviceInput?.Dispose();
205+
this.deviceInput = null;
206+
this.deviceOutput?.Dispose();
207+
this.deviceOutput = null;
208+
177209
throw;
178210
}
179211
}
180212

181213
protected override Task OnStartAsync(CancellationToken ct)
182214
{
183-
this.session?.StartRunning();
184-
return TaskCompat.CompletedTask;
215+
try
216+
{
217+
if(session== null)
218+
throw new InvalidOperationException("Session is null");
219+
this.session?.StartRunning();
220+
this.IsRunning = true;
221+
return TaskCompat.CompletedTask;
222+
}catch (Exception ex)
223+
{
224+
Debug.WriteLine($"Error starting session: {ex.Message}");
225+
throw new InvalidOperationException("Failed to start the capture session.", ex);
226+
}
227+
185228
}
186229

187230
protected override Task OnStopAsync(CancellationToken ct)
188231
{
189-
this.session?.StopRunning();
232+
try
233+
{
234+
if(session== null)
235+
throw new InvalidOperationException("Session is null");
236+
if (this.IsRunning)
237+
{
238+
this.session?.StopRunning();
239+
this.IsRunning = false;
240+
241+
}
242+
243+
}catch (Exception ex)
244+
{
245+
Debug.WriteLine($"Error stopping session: {ex.Message}");
246+
throw new InvalidOperationException("Failed to stop the capture session.", ex);
247+
}
190248
return TaskCompat.CompletedTask;
191249
}
192250

193251
protected override void OnCapture(IntPtr pData, int size, long timestampMicroseconds, long frameIndex, PixelBuffer buffer)
194252
{
195-
buffer.CopyIn(this.bitmapHeader, pData, size, timestampMicroseconds, frameIndex, TranscodeFormats.Auto);
253+
try
254+
{
255+
if (this.bitmapHeader == IntPtr.Zero) return;
256+
if (pData == IntPtr.Zero || size <= 0)
257+
{
258+
throw new ArgumentException("Invalid pixel data or size.");
259+
}
260+
buffer.CopyIn(this.bitmapHeader, pData, size, timestampMicroseconds, frameIndex, TranscodeFormats.Auto);
261+
}catch (Exception ex)
262+
{
263+
Debug.WriteLine($"Error capturing frame: {ex.Message}");
264+
throw new InvalidOperationException("Failed to capture frame.", ex);
265+
}
196266
}
197267

198268
internal sealed class VideoBufferHandler : AVCaptureVideoDataOutputSampleBuffer
199269
{
200270
private readonly AVFoundationDevice device;
201-
private int frameIndex;
271+
private int frameIndex = 0;
202272

203273
public VideoBufferHandler(AVFoundationDevice device)
204274
{

FlashCap.Core/FlashCap.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
66
<NoWarn>$(NoWarn);CS0649</NoWarn>
77
<IsPackable>true</IsPackable>
8+
<Version>1.0.0</Version>
89
</PropertyGroup>
910

1011
<ItemGroup>

0 commit comments

Comments
 (0)