0

I'm in the process of implementing a remote camera control app with Canons EDSDK. My camera is a Canon PowerShot SX 70 HS. So far everything seems to work, except for the functionality to save the taken picture to the host-PC.

My understanding of the needed workflow is the following:

  1. Initialize camera
  2. Rewrite object event handler and pass to EdsObjectEventHandler in order to modify the "save-image-behavior"
  3. Take photo
  4. Close streams and destroy all objects.

I have adapted my own initial version and tried out the solutions provided in these threads:

canon EDSDK saving image in my PC

Canon SDK - Downloading image to host PC

Below is the code...

...for the first thread:

#include <iostream>

#include "EDSDK.h"
#include "EDSDKTypes.h"
#include "EDSDKErrors.h"

EdsError getFirstCamera(EdsCameraRef* camera);
EdsError downloadImage(EdsDirectoryItemRef directoryItem);
EdsError EDSCALLBACK handleStateEvent(EdsStateEvent event, EdsUInt32 parameter, EdsVoid* context);
EdsError EDSCALLBACK handleObjectEvent(EdsObjectEvent event, EdsBaseRef object, EdsVoid* context);
EdsError EDSCALLBACK handlePropertyEvent(EdsPropertyEvent event, EdsPropertyID property, EdsUInt32 inParam, EdsVoid* context);


int main(int argc, char** argv)
{
    EdsError err;
    EdsCameraRef camera = NULL;
    bool isSDKLoaded = false;
    EdsCapacity capacity = { 0x7FFFFFFF, 0x1000, 1 };
    EdsInt32 saveTarget = kEdsSaveTo_Host;

    // Initialize SDK
    err = EdsInitializeSDK();
    if (err == EDS_ERR_OK)
    {
        isSDKLoaded = true;
    }

    // Get first camera
    if (err == EDS_ERR_OK)
    {
        err = getFirstCamera(&camera);
    }


    // Open session with camera
    err = EdsOpenSession(camera);

    // Set event handler
    if (err == EDS_ERR_OK)
        err = EdsSetObjectEventHandler(camera, kEdsObjectEvent_All, handleObjectEvent, NULL);
    
    if (err == EDS_ERR_OK)
        err = EdsSetPropertyEventHandler(camera, kEdsPropertyEvent_All, handlePropertyEvent, NULL);
    
    if (err == EDS_ERR_OK)
        err = EdsSetCameraStateEventHandler(camera, kEdsStateEvent_All, handleStateEvent, NULL);

    err = EdsSetPropertyData(camera, kEdsPropID_SaveTo, 0, 4, &saveTarget);

    err = EdsSetCapacity(camera, capacity);

    ///// Take picture
    err = EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);
    ////

    // Close session with camera
    if (err == EDS_ERR_OK)
    {
        err = EdsCloseSession(camera);
    }

    // Release camera
    if (camera != NULL)
    {
        EdsRelease(camera);
    }

    // Terminate SDK
    if (isSDKLoaded)
    {
        EdsTerminateSDK();
    }
}


EdsError EDSCALLBACK handleObjectEvent(EdsObjectEvent event, EdsBaseRef object, EdsVoid* context)
{
    std::cout << "I'm in the callback-function!!!\n"; // Never shows!
    EdsError err = EDS_ERR_OK;

    switch (event)
    {
    case kEdsObjectEvent_DirItemRequestTransfer:
        err = downloadImage(object);
        break;
    default:
        break;
    }

    // Object must be released
    if (object) {
        err = EdsRelease(object);
    }
    return err;
}

EdsError EDSCALLBACK handlePropertyEvent(EdsPropertyEvent event, EdsPropertyID property, EdsUInt32 inParam, EdsVoid* context)
{
    return EDS_ERR_OK;
}

EdsError EDSCALLBACK handleStateEvent(EdsStateEvent event, EdsUInt32 parameter, EdsVoid* context)
{
    return EDS_ERR_OK;
}


EdsError getFirstCamera(EdsCameraRef* camera)
{
    EdsError err;
    EdsCameraListRef cameraList = NULL;

    err = EdsGetCameraList(&cameraList);
    err = EdsGetChildAtIndex(cameraList, 0, camera);
    return err;
}

EdsError downloadImage(EdsDirectoryItemRef directoryItem)
{
    EdsError err = EDS_ERR_OK;
    EdsStreamRef stream = NULL;
    // Get directory item information
    EdsDirectoryItemInfo dirItemInfo;
    err = EdsGetDirectoryItemInfo(directoryItem, &dirItemInfo);

    // Create file stream for transfer destination
    if (err == EDS_ERR_OK)
    {
        err = EdsCreateFileStream(dirItemInfo.szFileName, kEdsFileCreateDisposition_CreateAlways, kEdsAccess_ReadWrite, &stream);
    }
    // Download image
    if (err == EDS_ERR_OK)
    {
        err = EdsDownload(directoryItem, dirItemInfo.size, stream);
    }
    // Issue notification that download is complete
    if (err == EDS_ERR_OK)
    {
        err = EdsDownloadComplete(directoryItem);
    }
    // Release stream
    if (stream != NULL)
    {
        EdsRelease(stream);
        stream = NULL;
    }
    return err;
}

and for the second thread:

#include <iostream>

#include "EDSDK.h"
#include "EDSDKTypes.h"
#include "EDSDKErrors.h"

static EdsError EDSCALLBACK handleObjectEvent(EdsObjectEvent event, EdsBaseRef object, EdsVoid* context)
{
    std::cout << "I'm in the callback-function!!!\n";  // NEVER PRINTS!
    EdsError err = EDS_ERR_OK;
    if (event == kEdsObjectEvent_DirItemRequestTransfer)
    {
        EdsStreamRef stream = NULL;
        EdsDirectoryItemInfo dirItemInfo;
        err = EdsGetDirectoryItemInfo(object, &dirItemInfo);
        err = EdsCreateFileStream(dirItemInfo.szFileName, kEdsFileCreateDisposition_CreateAlways, kEdsAccess_ReadWrite, &stream);
        err = EdsDownload(object, dirItemInfo.size, stream);
        err = EdsDownloadComplete(object);
        EdsRelease(stream);
        stream = NULL;
    }
    if (object)
        EdsRelease(object);

    return err;
}

void TakePhoto(EdsCameraRef camera)
{
    EdsError err = EDS_ERR_OK;
    EdsCameraListRef cameraList = NULL;
    EdsUInt32 count = 0;

    err = EdsInitializeSDK();
    err = EdsGetCameraList(&cameraList);
    err = EdsGetChildCount(cameraList, &count);
    if (count > 0)
    {
        err = EdsGetChildAtIndex(cameraList, 0, &camera);
        cameraList = NULL;
        err = EdsSetObjectEventHandler(camera, kEdsObjectEvent_All, handleObjectEvent, NULL);
        err = EdsOpenSession(camera);
        err = EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);
    }
}

void Close(EdsCameraRef camera)
{
    EdsError err = EdsCloseSession(camera);
    EdsRelease(camera);
    EdsTerminateSDK();
}

int main() {
    EdsCameraRef camera = NULL;
    TakePhoto(camera);
    Close(camera);
    return 0;
}

However, as mentioned before: The images are nowhere to find on my host PC. This doesn't change, if i provide a full path to EdsCreateFileStream. The default behavior of saving the image in the directory in which the binary resides (using dirItemInfo.szFileName as the first argument to EdsCreateFileStream also doesn't work.

It could be, that that's because the newest solution i tried is from 2014 and the API changed in the meantime. I also looked for implementations on GitHub and looked in the official manual, but both were very opaque to me.

What really puzzles me is, that when i tried out a printf-style debugging (adding an output inside the handleObjectEvent, the print never showed up! Is this because of the callback-nature of the function? I must confess, that i haven't worked with Callbacks before.

All the versions provided below build and execute. Also i can here the camera clicking, but as stated before: I can't find the image on disk!

Are the callbacks even executed? Why does the print never show then? How can i ultimately modify the programs to save images to disk?

Tim Hilt
  • 605
  • 5
  • 23

1 Answers1

1

I can think of two reasons it doesn't work:

  1. this is a console application so it doesn't have a message pump. This means events aren't fired "automatically". You'll have to call EdsGetEvent repeatedly until you have received the event you need. In your case the ideal point would be after sending the TakePicture command.

  2. After sending the TakePicture command you immediately close down everything so there is no time for the event to fire. The TakePicture command returns pretty much immediately and doesn't wait until the whole photo-taking procedure is done (it does wait until focusing is done though). Luckily, this is also fixed with the EdsGetEvent loop I described above.

So esentially:

    err = EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);

    // eventHasFired is a boolean variable you can
    // set to true after you have downloaded the image.
    // or use a more sophisticated synchronization approach if necessary
    while (!eventHasFired)
    {
        EdsGetEvent();
        // you could add a little pause here to reduce CPU usage
    }

    if (err == EDS_ERR_OK)
    {
        err = EdsCloseSession(camera);
    }

EDIT

After discussing some possibilities in the comments and looking at the error-codes from the EDSDK we figured out, that the camera isn't able to autofocus when sending TakePicture the first time. Sending the command repeatedly in a while-loop provides a workaround:

while (err != EDS_ERR_OK)
    err = EdsSendCommand(camera, kEdsCameraCommand_TakePicture, 0);
Tim Hilt
  • 605
  • 5
  • 23
Johannes Bildstein
  • 1,069
  • 2
  • 8
  • 20
  • Hi Johannes and thanks for the answer. Can you expand a little bit on the binary variable? For the while-condition to change i would have to modify `eventHasFired` after `EdsGetEvent()` and the pause. How can i check whether the event has actually fired? – Tim Hilt Nov 02 '20 at 14:49
  • @TimHilt you're welcome and sure: you'd set it inside your `handleObjectEvent` callback, after you have downloaded the image and before you call `return err`. The `EdsGetEvent` function will call the `handleObjectEvent` internally. So essentially the `eventHasFired` variable will be set inside the `while` loop. You'll know that the event has fired because you should see your printout and, ideally, the image will be saved. – Johannes Bildstein Nov 02 '20 at 15:03
  • I edited my solution using the 2nd codesnippet above to include a global `event`-variable, as suggested. I pasted the edited code here: https://pastebin.com/H0teptGq I didn't add a pause to call `EdsGetEvent` as fast and as often as possible, however i never see any printouts and the function got stuck in the while-loop! It seems as if the callback is never even triggered! – Tim Hilt Nov 02 '20 at 15:14
  • @TimHilt then the only thing I can think of now is that you may have to call `CoInitializeEx` with `COINIT_APARTMENTTHREADED` at the very start of `main()`. I'm not a C++ dev so I'm not totally sure if that'll work but I do know that for the SDK to work properly, the thread it runs on has to be in single threaded apartment mode. – Johannes Bildstein Nov 02 '20 at 20:52
  • Ok so interesting news from my part: I tried again with the other code-snippet and now the event-callback is triggered, but with the wrong event: 0x201 (`kEdsObjectEvent_VolumeInfoChanged`) instead of 0x208 (`kEdsObjectEvent_DirItemRequestTransfer`)! If i call `EdsGetEvent()` repeatedly i only get 0x201 and only once; seems to be the only event. Here is my changed code: https://pastebin.com/5rzZCAKu – Tim Hilt Nov 03 '20 at 09:33
  • also sorry for the spam, but i feel completely lost atm and you seem to have a lot more experience with EDSDK. Thank you for your help!!! – Tim Hilt Nov 03 '20 at 09:34
  • @TimHilt no worries, we'll figure this out. I think the problem is that one of the calls may return an error. Could you please add an `if (err != ok) { print error; return; }` statement after each SDK call. I also noticed that you didn't set the capacity and SaveTo in your previous pastebin, perhaps you'll get the same behavior than now if you add those. Maybe also add a printout in the state and property events just to see if they are fired. – Johannes Bildstein Nov 03 '20 at 13:23
  • we're close to a complete solution!!! I got it to take pictures and store them on disk. However right now i have to send the `TakePicture`-command twice in a row. The error-code is always 0x8d01 (EDS_ERR_TAKE_PICTURE_AF_NG) which tells me, that the Focus failed. The error-code is 0 on 2nd send. If i don't close the session with the camera (terminate while caught in the while loop) it works the first time. Why does the focus fail after the first send? Here is my (hopefully last) changed pastebin: https://pastebin.com/xeQxSxfr – Tim Hilt Nov 04 '20 at 10:18
  • @TimHilt great! I'm not entirely sure why the focus would work the second time but have you tried to make sure that the camera points to somewhere it would be able to focus well? or just turn off AF on the lens if that'd be ok for your use case. – Johannes Bildstein Nov 04 '20 at 10:59
  • I ended up calling `SendCommand` in a while-loop, until `err == EDS_ERR_OK`. I don't know what causes this, but using the AutoFocus programatically doesn't work. I might be able to disable it altogether in the final application, but for the time being, the workaround is ok for me. – Tim Hilt Nov 05 '20 at 14:03
  • i edited the question so that i can accept it! Many thanks for your help! – Tim Hilt Nov 05 '20 at 14:15
  • @TimHilt wonderful, glad to hear it. Focusing can be a bit tricky and you might be able to make it a bit more sophisticated with the press shutter button command. e.g. press it half, listen for focus info in the property change event and react on that. It's not easy though. – Johannes Bildstein Nov 05 '20 at 15:42