1- using System . Diagnostics ;
1+ using System . Diagnostics ;
22using Microsoft . Extensions . Logging ;
33using 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