Skip to content

Commit b82c715

Browse files
committed
Enhances error handling for camera operations
Improves exception handling when taking pictures. Adds a method to translate capture error codes to user-friendly messages, ensuring better diagnostics during failures. Refines completion logic to safely handle asynchronous tasks and simplifies resource management for event handling. Addresses potential race conditions and provides clearer error messages for timeouts and capture issues.
1 parent 77696a4 commit b82c715

File tree

1 file changed

+131
-27
lines changed

1 file changed

+131
-27
lines changed

Canon.Core/CanonCamera.cs

Lines changed: 131 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Diagnostics;
1+
using System.Diagnostics;
22
using Microsoft.Extensions.Logging;
33
using System.Runtime.InteropServices;
44

@@ -65,10 +65,51 @@ private uint OnCameraStateChanged(uint inEvent, uint inParameter, IntPtr inConte
6565
{
6666
lock (_initLock)
6767
_initTask = null;
68+
69+
// If the camera shuts down, fail any pending picture task
70+
_takePictureCompletion?.TrySetException(new InvalidOperationException("Camera was disconnected or shut down."));
71+
}
72+
else if (inEvent == EDSDK.StateEvent_CaptureError)
73+
{
74+
// Camera failed to take the shot (e.g., focus failure)
75+
// This will immediately fail the TakePicture() task with a useful message
76+
string errorMsg = GetCaptureErrorMessage(inParameter);
77+
_takePictureCompletion?.TrySetException(new Exception(errorMsg)); // Or use your custom EdsException
6878
}
6979

7080
return 0;
7181
}
82+
83+
/// <summary>
84+
/// Helper method to translate capture error codes into messages.
85+
/// Based on EDSDK_API_EN.pdf Page 96
86+
/// </summary>
87+
private string GetCaptureErrorMessage(uint errorCode)
88+
{
89+
// --- THIS IS THE FIXED FUNCTION ---
90+
// Rewritten to use a classic switch statement for older C# compatibility
91+
switch (errorCode)
92+
{
93+
case 0x00000001:
94+
return "Shooting failure (General Error)";
95+
case 0x00000002:
96+
return "Lens cover was closed";
97+
case 0x00000003:
98+
return "General shooting error (Bulb or mirror-up)";
99+
case 0x00000004:
100+
return "Camera is busy cleaning the sensor";
101+
case 0x00000005:
102+
return "Camera is set to silent operation";
103+
case 0x00000006:
104+
return "No card inserted";
105+
case 0x00000007:
106+
return "Card error (Full or other)";
107+
case 0x00000008:
108+
return "Card write-protected";
109+
default:
110+
return $"Unknown capture error: 0x{errorCode:X}";
111+
}
112+
}
72113

73114
/// <summary>
74115
/// Initializes the Canon SDK and connects to the first detected camera.
@@ -259,11 +300,16 @@ await _thread.InvokeAsync(() =>
259300

260301
try
261302
{
303+
// Wait for OnCameraObject or OnCameraStateChanged to complete the task
262304
return await _takePictureCompletion.Task.WaitAsync(TimeSpan.FromSeconds(10));
263305
}
264306
catch (TimeoutException)
265307
{
266-
throw new TimeoutException("The camera didn't produce a JPG image within specified timeout. Check focus and camera settings");
308+
// Invalidate the TCS so the late event doesn't complete a *future* request
309+
_takePictureCompletion = null;
310+
311+
// We can also add a better message, as you wanted
312+
throw new TimeoutException("Picture taking operation timed out. The camera did not respond. Check focus, lens cap, or if it's set to RAW.");
267313
}
268314
}
269315
finally
@@ -279,47 +325,95 @@ await _thread.InvokeAsync(() =>
279325
{
280326
await InitializeAsync();
281327

282-
return await _thread.InvokeAsync(() =>
328+
// Try to acquire the semaphore with a 0ms timeout.
329+
if (!await _takePictureSemaphore.WaitAsync(0))
283330
{
284-
var evfImage = nint.Zero;
285-
var stream = nint.Zero;
331+
// If this fails, TakePicture is busy. Skip this frame.
332+
return null;
333+
}
286334

287-
try
335+
try
336+
{
337+
return await _thread.InvokeAsync(() =>
288338
{
289-
EDSDK.EdsCreateMemoryStream(0, out stream).ThrowIfEdSdkError("Could not create memory stream for EVF image");
290-
EDSDK.EdsCreateEvfImageRef(stream, out evfImage).ThrowIfEdSdkError("Could not create EVF image reference");
339+
var evfImage = nint.Zero;
340+
var stream = nint.Zero;
291341

292-
if (EDSDK.EdsDownloadEvfImage(_cameraRef, evfImage) != EDSDK.EDS_ERR_OK)
293-
return null;
342+
try
343+
{
344+
// 1. Create Stream
345+
EDSDK.EdsCreateMemoryStream(0, out stream).ThrowIfEdSdkError("Could not create memory stream for EVF image");
346+
if (stream == nint.Zero) throw new EdsException(0, "EdsCreateMemoryStream returned OK but stream was null.", null);
294347

295-
EDSDK.EdsGetPointer(stream, out var imagePtr).ThrowIfEdSdkError("Could not get stream pointer");
296-
EDSDK.EdsGetLength(stream, out var length).ThrowIfEdSdkError("Could not get stream length");
348+
// 2. Create Image Ref
349+
EDSDK.EdsCreateEvfImageRef(stream, out evfImage).ThrowIfEdSdkError("Could not create EVF image reference");
350+
if (evfImage == nint.Zero) throw new EdsException(0, "EdsCreateEvfImageRef returned OK but image ref was null.", null);
351+
352+
// 3. Download Image
353+
var err = EDSDK.EdsDownloadEvfImage(_cameraRef, evfImage);
354+
if (err == EDSDK.EDS_ERR_OBJECT_NOTREADY)
355+
{
356+
return null; // Not an error, just not ready for a frame
357+
}
358+
err.ThrowIfEdSdkError("Could not download EVF image"); // Throws on other errors
297359

298-
var bytes = new byte[length];
299-
Marshal.Copy(imagePtr, bytes, 0, (int)length);
300-
return bytes;
301-
}
302-
finally
303-
{
304-
if (stream != nint.Zero) EDSDK.EdsRelease(stream);
305-
if (evfImage != nint.Zero) EDSDK.EdsRelease(evfImage);
306-
}
307-
});
360+
// 4. Get Pointer (Defensive Check 1)
361+
// This is the area where the NullReferenceException was happening
362+
var ptrErr = EDSDK.EdsGetPointer(stream, out var imagePtr);
363+
if (ptrErr != EDSDK.EDS_ERR_OK || imagePtr == nint.Zero)
364+
{
365+
// Camera said download was OK, but the stream pointer is bad.
366+
// This happens when the camera is in a bad state. Skip the frame.
367+
return null;
368+
}
369+
370+
// 5. Get Length (Defensive Check 2)
371+
var lenErr = EDSDK.EdsGetLength(stream, out var length);
372+
if (lenErr != EDSDK.EDS_ERR_OK || length == 0)
373+
{
374+
// Stream is bad or empty, skip frame
375+
return null;
376+
}
377+
378+
// 6. Copy bytes
379+
var bytes = new byte[length];
380+
Marshal.Copy(imagePtr, bytes, 0, (int)length);
381+
return bytes;
382+
}
383+
finally
384+
{
385+
// This release code is correct and essential
386+
if (stream != nint.Zero) EDSDK.EdsRelease(stream);
387+
if (evfImage != nint.Zero) EDSDK.EdsRelease(evfImage);
388+
}
389+
});
390+
}
391+
finally
392+
{
393+
// Always release the semaphore so the next operation can run
394+
_takePictureSemaphore.Release();
395+
}
308396
}
309397

310398
/// <summary>
311399
/// Handles camera object events, such as file transfers.
312400
/// </summary>
313401
private uint OnCameraObject(uint inEvent, nint inRef, nint inContext)
314402
{
403+
// This event is triggered when an image is ready to be downloaded
315404
if (inEvent == EDSDK.ObjectEvent_DirItemRequestTransfer)
316405
{
317406
try
318407
{
319408
EDSDK.EdsGetDirectoryItemInfo(inRef, out var dirItemInfo).ThrowIfEdSdkError("Failed to get file info");
320409

410+
// We only care about JPGs for the TakePicture preview
321411
if (Path.GetExtension(dirItemInfo.szFileName).ToLower() is not (".jpg" or ".jpeg"))
412+
{
413+
// This wasn't the image we wanted, just complete the download and ignore
414+
EDSDK.EdsDownloadComplete(inRef).ThrowIfEdSdkError("Failed to complete non-JPG download");
322415
return 0;
416+
}
323417

324418
var tempFileName = $"{Path.GetTempFileName()}.jpg";
325419
EDSDK.EdsCreateFileStream(tempFileName, EDSDK.EdsFileCreateDisposition.CreateAlways, EDSDK.EdsAccess.ReadWrite, out var stream).ThrowIfEdSdkError("Failed to create download stream");
@@ -349,22 +443,32 @@ private uint OnCameraObject(uint inEvent, nint inRef, nint inContext)
349443
}
350444

351445
_latestImageBytes = bytes;
352-
_takePictureCompletion?.SetResult(bytes);
446+
447+
// Use TrySetResult for race-condition safety
448+
_takePictureCompletion?.TrySetResult(bytes);
353449
}
354450
catch (Exception exception)
355451
{
356-
_takePictureCompletion?.SetException(exception);
452+
_takePictureCompletion?.TrySetException(exception);
453+
}
454+
finally
455+
{
456+
// Clear the TCS now that this transfer is handled (success or fail)
457+
_takePictureCompletion = null;
357458
}
358459
}
359460
finally
360461
{
361-
EDSDK.EdsRelease(inRef);
362-
_takePictureCompletion = null;
462+
// Always release the event reference
463+
if (inRef != nint.Zero)
464+
EDSDK.EdsRelease(inRef);
363465
}
364466
}
365-
366467
else if (inRef != nint.Zero)
468+
{
469+
// Release other events we don't care about
367470
EDSDK.EdsRelease(inRef);
471+
}
368472

369473
return 0;
370474
}

0 commit comments

Comments
 (0)