Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / video

Canon EDSDK Tutorial in C#

4.95/5 (147 votes)
25 May 2016MIT14 min read 4M   35.1K  
A tutorial about the Canon SDK to remotely control DSLR cameras. From taking photos to using the LiveView.
The documentation provided for Canon EOS Digital SDK is not very complete and it is difficult to find good examples on the internet. This is a tutorial of some of the most important things I have compiled.

New Version Download

Old Version Download

Introduction

The Canon EOS Digital SDK is quite a powerful SDK to remote control Canon DSLRs. Unfortunately, it is quite difficult to find some good examples for it on the internet and the provided documentation is not very complete. Since I have found out many things already and want to make it easier for others, I thought I could compile some of the most important things together and do a tutorial.

The tutorial and the library is written in C# but it is CLS compliant and therefore can be used from any .NET language.

This tutorial includes:

Note: I'm not affiliated with or funded by Canon Inc. in any way.

I do not guarantee for this software in any way. Use it at your own risk!

Background

You have to have a copy of the Canon EDSDK to get this working. I am not allowed to include the DLLs within the project so you'll have to apply to get them, here:

Once you have the DLLs, put them beside your executable. Other places will cause problems if the main DLL makes a call to a sub-DLL.

It is also important that you use your camera in fully manual or at least half-automated mode for some methods to work.

Using the Code

The solution consists of four projects:

  • EDSDKLib: The main project where all the SDK and camera handling happens
  • WinFormsExample: An example project that consumes the EDSDKLib and uses it in a Windows Forms UI application
  • WpfExample: An example project that consumes the EDSDKLib and uses it in a WPF UI application
  • ConsoleExample: An example project that consumes the EDSDKLib and uses it in a console application

I will only concentrate on the EDSDKLib project here since this is what this whole article is about: Using the EDSDK with C#.

First, let us look at the main classes:

  • CanonAPI: The class that mainly handles the SDK lifetime, the connected Cameras and the SDK events
  • Camera: This is used to communicate with a physical camera. Set/Get properties, take photos, download data and more.
  • CanonSDK: This class contains all the native calls to the Canon SDK DLLs and some helper methods to set/get values.
  • STAThread: A helper class to create an STA thread or execute code on an STA thread
  • ErrorHandler: A static class that provides methods to check the SDK return values/error codes

Let's have a closer look into the inner workings of those classes.

Initiate and Terminate the SDK

Initializing and terminating are the easiest things to do. When you start your program, create a new instance of the CanonAPI class:

C#
public CanonAPI(bool useCallingThread)
{
    try
    {
        //Ensure that only one caller at a time can increase the counter
        lock (InitLock)
        {
            //If no instance exists yet, initialize everything
            if (RefCount == 0)
            {
                if (useCallingThread)
                {
                    if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
                        throw new ThreadStateException("Calling thread must be in STA");
                    ErrorHandler.CheckError(this, CanonSDK.EdsInitializeSDK());
                }
                else
                {
                    //Trying to trigger DllNotFoundException so it's not thrown
                    //in the event loop on a different thread:
                    CanonSDK.EdsRelease(IntPtr.Zero);

                    //Start the main thread where SDK will run on
                    MainThread = new ApiThread();
                    MainThread.Start();
                    //Initialize the SDK on the main thread
                    MainThread.Invoke(() => ErrorHandler.CheckError
                                     (this, CanonSDK.EdsInitializeSDK()));
                }

                CanonSDK.InitializeVersion();
                //Subscribe to the CameraAdded event
                CameraAddedEvent = new SDKCameraAddedHandler(CanonAPI_CameraAddedEvent);
                ErrorHandler.CheckError(this, 
                    CanonSDK.EdsSetCameraAddedHandler(CameraAddedEvent, IntPtr.Zero));
                _IsSDKInitialized = true;
            }
            RefCount++;
        }
    }
    catch
    {
        IsDisposed = true;
        if (MainThread?.IsRunning == true) MainThread.Shutdown();
        throw;
    }
}

Ensure that you keep around an instance of this class, otherwise the GC might collect it and the SDK gets terminated with it (because of the destructor of the CanonAPI class).

And when you close your program, call the Dispose method. The public Dispose method in turn calls this overload:

C#
protected virtual void Dispose(bool managed)
{
    //Ensure that only one caller at a time can decrease the counter
    lock (InitLock)
    {
        if (!IsDisposed)
        {
            //If it's the last instance, release everything
            if (RefCount == 1)
            {
                _IsSDKInitialized = false;//Set beforehand because if an error happens, 
                                          //the SDK will be in an unstable state anyway

                //Remove event handler for the CameraAdded event
                ErrorCode err = CanonSDK.EdsSetCameraAddedHandler(null, IntPtr.Zero);
                if (managed)
                {
                    ErrorHandler.CheckError(this, err);
                    //Dispose all the connected cameras
                    CurrentCameras.ForEach(t => t.Dispose());
                }
                //Terminate the SDK
                if (MainThread?.IsRunning == true) 
                    err = MainThread.Invoke(() => { return CanonSDK.EdsTerminateSDK(); });
                //Close the main thread
                if (MainThread?.IsRunning == true) MainThread.Shutdown();
                if (managed) ErrorHandler.CheckError(this, err);
            }
            RefCount--;
            IsDisposed = true;
        }
    }
}

Getting Connected Cameras

Now that the SDK is initialized, we can get a list of currently connected cameras:

C#
public List<Camera> GetCameraList()
{
    if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));

    //Ensure that only one caller at a time can access the camera list
    lock (CameraLock)
    {
        //Get a list of camera pointers
        IEnumerable<IntPtr> ptrList = GetCameraPointerList();
        List<Camera> camList = new List<Camera>();

        //Find cameras that were connected before and add new ones
        foreach (var ptr in ptrList)
        {
            var oldCam = CurrentCameras.FirstOrDefault(t => t.Reference == ptr);
            if (oldCam != null && !oldCam.IsDisposed) 
                camList.Add(oldCam);           //Pointer exists already so we reuse it
            else camList.Add(new Camera(ptr)); //Pointer does not exists yet, so we add it
        }

        //Ensure that cameras not connected anymore are disposed properly
        var oldCameras = CurrentCameras.Where(t => !ptrList.Any(u => u == t.Reference));
        foreach (var cam in oldCameras) { if (!cam.IsDisposed) cam.Dispose(); }

        CurrentCameras.Clear();
        CurrentCameras.AddRange(camList);
        return camList;
    }
}

The CanonAPI class keeps a list of connected cameras to ensure that each SDK camera pointer is associated with only one Camera class instance. Having two or more Camera class instances that use the same SDK pointer could potentially cause problems if they access the camera at the same time.

The subroutine GetCameraPointerList that is used above gets a list of pointers that point to the SDKs camera objects:

C#
protected IEnumerable<IntPtr> GetCameraPointerList()
{
    if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));

    IntPtr camlist;
    //Get camera list
    ErrorHandler.CheckError(this, CanonSDK.EdsGetCameraList(out camlist));

    //Get number of connected cameras
    int camCount;
    ErrorHandler.CheckError(this, CanonSDK.EdsGetChildCount(camlist, out camCount));
    List<IntPtr> ptrList = new List<IntPtr>();
    for (int i = 0; i < camCount; i++)
    {
        //Get camera pointer
        IntPtr cptr;
        ErrorHandler.CheckError(this, CanonSDK.EdsGetChildAtIndex(camlist, i, out cptr));
        ptrList.Add(cptr);
    }
    //Release the list
    ErrorHandler.CheckError(this, CanonSDK.EdsRelease(camlist));
    return ptrList;
}

Open and Close a Session with a Camera

For now, we are done with the CanonAPI class and can start using the Camera and open a session with it:

C#
public void OpenSession()
{
    CheckState(false);

    if (!SessionOpen)
    {
        MainThread.Invoke(() =>
        {
            ErrorHandler.CheckError(this, CanonSDK.EdsOpenSession(CamRef));
                    
            //Check if Record is available
            uint property;
            _IsRecordAvailable = CanonSDK.GetPropertyData
                (CamRef, PropertyID.Record, 0, out property) == ErrorCode.OK;

            //Subscribe to events
            SDKStateEvent += new SDKStateEventHandler(Camera_SDKStateEvent);
            SDKPropertyEvent += new SDKPropertyEventHandler(Camera_SDKPropertyEvent);
            SDKProgressCallbackEvent += new SDKProgressCallback
                                        (Camera_SDKProgressCallbackEvent);
            SDKObjectEvent += new SDKObjectEventHandler(Camera_SDKObjectEvent);
            ErrorHandler.CheckError(this, 
            CanonSDK.EdsSetCameraStateEventHandler
                     (CamRef, StateEventID.All, SDKStateEvent, CamRef));
            ErrorHandler.CheckError(this, 
            CanonSDK.EdsSetObjectEventHandler
                     (CamRef, ObjectEventID.All, SDKObjectEvent, CamRef));
            ErrorHandler.CheckError(this, 
            CanonSDK.EdsSetPropertyEventHandler
                     (CamRef, PropertyEventID.All, SDKPropertyEvent, CamRef));

            SessionOpen = true;
        });
    }
}

If you are done working with this camera, close the session this way:

C#
public void CloseSession()
{
    CheckState(false);

    if (SessionOpen)
    {
        //Unsubscribe from all events
        UnsubscribeEvents();

        //If the live view is on, stop it
        if (IsLiveViewOn)
        {
            KeepLVAlive = false;
            LVThread.Join(5000);
        }

        MainThread.Invoke(() =>
        {
            //Close the session with the camera
            ErrorHandler.CheckError(this, CanonSDK.EdsCloseSession(CamRef));
            SessionOpen = false;
        });
    }
}

You can safely open and close the session multiple times while the camera is connected to the computer.

If you are completely done with this camera, you can dispose it by calling the Dispose method (which, as before, internally calls this overload):

C#
protected virtual void Dispose(bool managed)
{
    if (!IsDisposed)
    {
        //Unsubscribe from all events
        UnsubscribeEvents();

        //If the live view is on, stop it
        if (IsLiveViewOn)
        {
            KeepLVAlive = false;
            LVThread.Join();
        }
        IsLiveViewOn = false;

        MainThread.Invoke(() =>
        {
            if (CanonAPI.IsSDKInitialized)
            {
                //If it's open, close the session
                if (SessionOpen) CanonSDK.EdsCloseSession(CamRef);
                //Release the camera
                CanonSDK.EdsRelease(CamRef);
            }
            _IsDisposed = true;
        });
        //Shutdown the main camera thread
        MainThread.Shutdown();
    }
}

UnsubscribeEvents is just a little helper method:

C#
private void UnsubscribeEvents()
{
    SDKStateEvent -= Camera_SDKStateEvent;
    SDKPropertyEvent -= Camera_SDKPropertyEvent;
    SDKProgressCallbackEvent -= Camera_SDKProgressCallbackEvent;
    SDKObjectEvent -= Camera_SDKObjectEvent;

    if (CanonAPI.IsSDKInitialized)
    {
        MainThread.Invoke(() =>
        {
            //Clear callbacks from Canon SDK
            CanonSDK.EdsSetCameraStateEventHandler(CamRef, StateEventID.All, null, CamRef);
            CanonSDK.EdsSetObjectEventHandler(CamRef, ObjectEventID.All, null, CamRef);
            CanonSDK.EdsSetPropertyEventHandler(CamRef, PropertyEventID.All, null, CamRef);
        });
    }
}

Get Camera Settings

Setting and getting camera settings can be very easy for values with an ID, but more difficult for struct values.

Here is the getting method for normal int values (like Tv, Av or ISO):

C#
public int GetInt32Setting(PropertyID propID, int inParam = 0)
{
    CheckState();

    return MainThread.Invoke(() =>
    {
        int property;
        ErrorHandler.CheckError(this, CanonSDK.GetPropertyData
                               (CamRef, propID, inParam, out property));
        return property;
    });
}

There are multiple methods for getting values of different types but they all look basically the same. The important part is that all of them call the CanonSDK.GetPropertyData method that handles all the details. If you are interested in how to get values from managed to unmanaged, have a look at the mentioned method and its overloads in the CanonSDK class.

Set Camera Settings

Setting a camera setting is similar to getting it but there are only two methods because we can handle it in a more generic way with the object type.

Any value other than a string can be set with this method (e.g., integers for Tv, Av or ISO):

C#
public void SetSetting(PropertyID propID, object value, int inParam = 0)
{
    CheckState();

    MainThread.Invoke(() =>
    {
        int propsize;
        DataType proptype;
        ErrorHandler.CheckError(this, 
        CanonSDK.EdsGetPropertySize(CamRef, propID, inParam, out proptype, out propsize));
        ErrorHandler.CheckError(this, 
        CanonSDK.EdsSetPropertyData(CamRef, propID, inParam, propsize, value));
    });
}

Setting a string value:

C#
public void SetSetting(PropertyID propID, string value, int inParam = 0, int MAX = 32)
{
    CheckState();

    if (value == null) value = string.Empty;
    if (value.Length > MAX - 1) value = value.Substring(0, MAX - 1);

    byte[] propBytes = System.Text.Encoding.ASCII.GetBytes(value + '\0');
    MainThread.Invoke(() =>
    {
        ErrorHandler.CheckError(this, CanonSDK.EdsSetPropertyData(CamRef,
        propID, inParam, propBytes.Length, propBytes));
    });
}

String length can be limited with the MAX parameter because some camera properties can only have a specific length. Most of them have a length of 32 characters though.

Get a List of Available Settings

Different cameras have different settings available and lenses only have a certain range of Av values. That's why you need to get a list of all supported settings. This only works with "AEModeSelect", "ISO", "Av", "Tv", "MeteringMode" and "ExposureCompensation". This method returns an array of CameraValues. Each CameraValue has an integer value (that's the ID used by the SDK), a string value (that can be used as a label, e.g., "ISO 100" or "1/100") and, if applicable, a double value (that can be used for calculations, e.g., ISO 100 has a value of 100 and a Tv value of 1/100 has the value 0.01):

C#
public CameraValue[] GetSettingsList(PropertyID propId)
{
    CheckState();

    if (propId == PropertyID.AEModeSelect || propId == PropertyID.ISO || 
    propId == PropertyID.Av || propId == PropertyID.Tv
        || propId == PropertyID.MeteringMode || 
        propId == PropertyID.ExposureCompensation)
    {
        CameraValue[] vals = null;
        PropertyDesc des = default(PropertyDesc);
        MainThread.Invoke(() => ErrorHandler.CheckError
        (this, CanonSDK.EdsGetPropertyDesc(CamRef, propId, out des)));
        vals = new CameraValue[des.NumElements];
        for (int i = 0; i < vals.Length; i++) 
            vals[i] = new CameraValue(des.PropDesc[i], propId);
        return vals;
    }
    else throw new ArgumentException($"Method cannot be used with Property ID {propId}");
}

In the library, there are also five corresponding classes for each type that supports this method. These classes contain a list of all possible values and some regularly used fields (like Auto or Bulb). These classes are called AvValues, TvValues, ISOValues, ExpCompValues, AEModeValues and MeteringModeValues. All methods, properties and fields of those classes are static.

Send a Command to the Camera

With camera commands, you can control several things, for example, take a picture, press the shutter button or drive the focus motor of your lens.

C#
public void SendCommand(CameraCommand command, int inParam = 0)
{
    CheckState();
    MainThread.Invoke(() => ErrorHandler.CheckError
    (this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}

Some of the commands are used in the following topics.

To change the lock status of your camera or to enter/exit direct transfer mode, you can use the camera status commands:

C#
public void SendCommand(CameraCommand command, int inParam = 0)
{
    CheckState();
    MainThread.Invoke(() => ErrorHandler.CheckError
               (this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}

Taking a Photo (Normal and Bulb)

To take a photo with the current settings, call the TakePhoto method:

C#
public void TakePhoto()
{
    CheckState();
    SendCommand(CameraCommand.TakePicture);
}

Alternatively, you can use the TakePhotoAsync method that executes the command asynchronously (on a Threadpool Thread, not on a .NET 4.5 Task).

Or if your camera supports it, you can use the TakePhotoShutter method that uses the PressShutterButton command instead of the TakePicture command:

C#
public void TakePhotoShutter()
{
    CheckState();
    SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Completely);
    SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF);
}

Same as before, you can also use the TakePhotoShutterAsync method for asynchronous execution.

To take a photo in bulb mode, call the TakePhotoBulb method:

C#
public void TakePhotoBulb(int bulbTime)
{
    CheckState();

    SendCommand(CameraCommand.BulbStart);
    Thread.Sleep(bulbTime);
    SendCommand(CameraCommand.BulbEnd);
}

Note that you have to have the Tv value on Bulb before calling this method, otherwise you'll get an error. The opposite applies to the TakePhoto methods, there, Tv must not be set to Bulb.

If you would like to download the taken image directly to your computer, have a look at the next topic.

Download a Taken Photo to the Computer

To save taken photos directly onto the computer instead of the camera-memory, set it by calling the SetSetting method:

C#
SetSetting(PropertyID.SaveTo, (int)SaveTo.Host); //SaveTo.Both would save the image 
                                                 //to the camera AND the computer

Most cameras require that you set the remaining disk space on your computer before taking a picture. If you do not do this, most cameras assume that there is no disk space left and will return an error.

You can call the SetCapacity method to set the remaining disk space (if you want, you can just set an arbitrarily high value):

C#
public void SetCapacity(int bytesPerSector, int numberOfFreeClusters)
{
    CheckState();
    MainThread.Invoke(() =>
    {
        Capacity capacity = new Capacity(numberOfFreeClusters, bytesPerSector, true);
        ErrorHandler.CheckError(this, CanonSDK.EdsSetCapacity(CamRef, capacity));
    });
}

Once you have taken a photo, the SDKObjectEvent will fire with the inEvent variable being ObjectEventID.DirItemRequestTransfer. For convenience, the Camera class has a DownloadReady event that fires with all the info you need.

The DownloadReady event has two parameters, one is the Camera object from which the event was fired and the second being a DownloadInfo object. The DownloadInfo object provides some information about the file and can be passed to three methods:

The DownloadFile method downloads the image into the provided directory. To get or set the file name, you can use the FileName property of the DownloadInfo object.

C#
public void DownloadFile(DownloadInfo Info, string directory)
{
    CheckState();
    if (Info == null) throw new ArgumentNullException(nameof(Info));
    if (directory == null || string.IsNullOrEmpty(directory.Trim())) directory = ".";

    string currentFile = Path.Combine(directory, Info.FileName);
    if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
    DownloadToFile(Info, currentFile);
}

The DownloadFile method without the directory parameter downloads the image into a System.IO.Stream instead of saving it to the file system.

C#
public Stream DownloadFile(DownloadInfo Info)
{
    CheckState();
    if (Info == null) throw new ArgumentNullException(nameof(Info));
    return DownloadToStream(Info);
}

With the CancelDownload method, you can cancel and discard the taken image:

C#
public void CancelDownload(DownloadInfo Info)
{
    CheckState();
    if (Info == null) throw new ArgumentNullException(nameof(Info));

    MainThread.Invoke(() =>
    {
        ErrorHandler.CheckError(this, CanonSDK.EdsDownloadCancel(Info.Reference));
        ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
    });
}

The two DownloadFile methods used these subroutines to do the actual work:

C#
protected void DownloadToFile(DownloadInfo Info, string filepath)
{
    using (var stream = new SDKStream
          (filepath, FileCreateDisposition.CreateAlways, FileAccess.ReadWrite))
    {
        DownloadData(Info, stream.Reference);
    }
}

and:

C#
protected Stream DownloadToStream(DownloadInfo Info)
{
    SDKStream stream = new SDKStream(Info.Size64);
    DownloadData(Info, stream.Reference);
    stream.Position = 0;
    return stream;
}

The SDKStream class is a wrapper around SDK streams so it can be used like a normal System.IO.Stream.

And this is the real download:

C#
protected void DownloadData(DownloadInfo Info, IntPtr stream)
{
    MainThread.Invoke(() =>
    {
        try
        {
            //Set the progress callback
            ErrorHandler.CheckError(this, CanonSDK.EdsSetProgressCallback
            (stream, SDKProgressCallbackEvent, ProgressOption.Periodically, Info.Reference));
            //Check which SDK version is used and download the data with the correct method
            if (CanonSDK.IsVerGE34) ErrorHandler.CheckError
            (this, CanonSDK.EdsDownload(Info.Reference, Info.Size64, stream));
            else ErrorHandler.CheckError
            (this, CanonSDK.EdsDownload(Info.Reference, Info.Size, stream));
        }
        finally
        {
            //Release all data
            ErrorHandler.CheckError(this, CanonSDK.EdsDownloadComplete(Info.Reference));
            ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
        }
    });
}

Start and View the Live View

The live view is one of the more difficult things to do, especially if it should be high-performance. First, we start the live view like this:

C#
public void StartLiveView()
{
    CheckState();
    if (!IsLiveViewOn) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.PC);
}

Once this is done, the SDKPropertyEvent will fire with the inPropertyID variable being PropertyID.Evf_OutputDevice:

C#
private ErrorCode Camera_SDKPropertyEvent
(PropertyEventID inEvent, PropertyID inPropertyID, int inParameter, IntPtr inContext)
{
    ThreadPool.QueueUserWorkItem((state) =>
    {
        try
        {
            if (inPropertyID == PropertyID.Evf_OutputDevice || 
                inPropertyID == PropertyID.Record)
            {
                lock (lvThreadLockObj)
                {
                    EvfOutputDevice outDevice = GetEvf_OutputDevice();
                    Recording recordState = IsRecordAvailable ? 
                    ((Recording)GetInt32Setting(PropertyID.Record)) : Recording.Off;

                    if (outDevice == EvfOutputDevice.PC || 
                    (recordState == Recording.Ready && 
                                    outDevice == EvfOutputDevice.Filming) ||
                        (useFilmingPcLv && recordState == Recording.On && 
                        (outDevice == EvfOutputDevice.Filming || 
                         outDevice == EvfOutputDevice.Camera)))
                    {
                        if (!KeepLVAlive)
                        {
                            KeepLVAlive = true;
                            LVThread = STAThread.CreateThread(DownloadEvf);
                            LVThread.Start();
                        }
                    }
                    else if (KeepLVAlive) { KeepLVAlive = false; }
                }
            }
        }
        catch (Exception ex) 
        { if (!IsDisposed && !ErrorHandler.ReportError(this, ex)) throw; }

        PropertyChanged?.Invoke(this, inEvent, inPropertyID, inParameter);
    });
    return ErrorCode.OK;
}

There are a few more checks in there to ensure that the live view also works while filming.

And the DownloadEvf method being:

C#
private void DownloadEvf()
{
    if (IsLiveViewOn) return;

    try
    {
        //Create variables
        IntPtr evfImageRef = IntPtr.Zero;
        ErrorCode err;

        //Create stream
        using (var stream = new SDKStream(0))
        {
            IsLiveViewOn = true;
                    
            //Run live view
            while (KeepLVAlive)
            {
                //Get live view image
                lock (STAThread.ExecLock)
                {
                    err = CanonSDK.EdsCreateEvfImageRef(stream.Reference, out evfImageRef);
                    if (err == ErrorCode.OK) err = 
                        CanonSDK.EdsDownloadEvfImage(CamRef, evfImageRef);
                }

                //Check for errors
                if (err == ErrorCode.OBJECT_NOTREADY) { continue; }
                else if (err != ErrorCode.OK) { ErrorHandler.CheckError(err); continue; }

                //Release current evf image
                CanonSDK.EdsRelease(evfImageRef);
                        
                //Set stream position back to zero
                stream.Position = 0;

                //Update live view
                LiveViewUpdated?.Invoke(this, stream);
            }
        }
    }
    catch (Exception ex) { if (ex is ThreadAbortException || 
                               !ErrorHandler.ReportError(this, ex)) throw; }
    finally
    {
        IsLiveViewOn = false;
        ThreadPool.QueueUserWorkItem((state) => LiveViewStopped?.Invoke(this));
    }
}

To stop the live view, simply call the StopLiveView method. This will trigger the SDKPropertyEvent again where the KeepLVAlive variable is set to false and the live view loop will stop. With the LVOff parameter, you can set if the live view should be shut down completely or should be shown on the camera. (Some older cameras might always shut down.)

C#
public void StopLiveView(bool LVOff = true)
{
    CheckState();
    if (LVOff) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Off);
    else SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Camera);
}

Recording Videos

Newer cameras have the possibility to record videos built in. To use this, you have to set your camera to video recording mode first (it will not work otherwise!). Usually, there is some button, knob or dial you have to switch. If that is done, you can call the starting method:

C#
public void StartFilming(bool PCLiveview)
{
    CheckState();

    Recording state = (Recording)GetInt32Setting(PropertyID.Record);
    if (state != Recording.On)
    {
        if (state != Recording.Ready) throw new InvalidOperationException
        ("The camera is not ready to film. The Record property has to be Recording.Ready");
        useFilmingPcLv = PCLiveview;
        //When recording videos, it has to be saved on the camera internal memory
        SetSetting(PropertyID.SaveTo, (int)SaveTo.Camera);
        //Start the video recording
        SetSetting(PropertyID.Record, (int)Recording.On);
    }
}

Note that the SaveTo property is set to Camera. This is a requirement of the SDK, presumably because the data transfer to the computer would be too slow.

Still, you can download the finished film after you stopped filming just by setting the saveFilm parameter of the StopFilming method to true:

C#
public void StopFilming(bool saveFilm, bool stopLiveView)
{
    CheckState();

    Recording state = (Recording)GetInt32Setting(PropertyID.Record);
    if (state == Recording.On)
    {
        this.saveFilm = saveFilm;
        //Stop video recording
        SetSetting(PropertyID.Record, (int)Recording.Off);
        useFilmingPcLv = false;
        if (IsLiveViewOn && stopLiveView) StopLiveView(false);
    }
}

Internally, it checks the SDKObjectEvent that will fire with inEvent being ObjectEventID.DirItemCreated. We can safely assume that this created item is the film. Same as with downloading a photo, the DownloadReady event will fire and you can download the film with one of the DownloadFile methods.

Lock/Unlock the camera UI

To keep the user or from changing settings on the physical camera, or allowing to do so, you can lock or unlock the camera UI like this:

C#
public void UILock(bool lockState)
{
    if (lockState) SendStatusCommand(CameraStatusCommand.UILock);
    else SendStatusCommand(CameraStatusCommand.UIUnLock);
}

Control the Focus of a Lens

A question often asked all over the internet is how to control the focus of the camera. It's actually quite easy if you know how. Most importantly, the camera has to be in live view and the lens has to be on AF. Then, you can simply call SendCommand with DriveLensEvf as the command type:

C#
SendCommand(CameraCommand.DriveLensEvf, (int)DriveLens.Near2);

To control distance and direction, you have to set the DriveLens enum appropriately. Near is to focus closer, Far is to focus further away and 1 is a small, 2 is a medium and 3 is a big step.

Files and Folders on a Camera

There are several methods for handling the file system of the camera:

  • Use GetAllVolumes to get all camera volumes (e.g., CF or SD cards)
  • Use GetAllEntries to get all volumes, files and folders on the camera
  • Use GetAllImages to get all images on the camera
  • Use FormatVolume to format a volume
  • Use DownloadFiles to download one or more files into a directory
  • Use DeleteFiles to delete one or more files on the camera

I won't go into detail here but feel free to look at the source code of these methods to find out how exactly they work.

Get the Thumbnail of an Image

Sometimes, it is useful to get a thumbnail from an image, regardless if RAW or jpg. To do that, you can call the GetFileThumb method of the CanonAPI class.

C#
public Bitmap GetFileThumb(string filepath)
{
    //create a file stream to given file
    using (var stream = new SDKStream
          (filepath, FileCreateDisposition.OpenExisting, FileAccess.Read))
    {
        //Create a thumbnail Bitmap from the stream
        return GetImage(stream.Reference, ImageSource.Thumbnail);
    }
}

whereas the more generic method GetImage is defined like this:

C#
protected Bitmap GetImage(IntPtr imgStream, ImageSource imageSource)
{
    IntPtr imgRef = IntPtr.Zero;
    IntPtr streamPointer = IntPtr.Zero;
    ImageInfo imageInfo;

    try
    {
        //create reference and get image info
        ErrorHandler.CheckError(this, 
            CanonSDK.EdsCreateImageRef(imgStream, out imgRef));
        ErrorHandler.CheckError(this, 
            CanonSDK.EdsGetImageInfo(imgRef, imageSource, out imageInfo));

        Size outputSize = new Size();
        outputSize.Width = imageInfo.EffectiveRect.Width;
        outputSize.Height = imageInfo.EffectiveRect.Height;
        //calculate amount of data
        int datalength = outputSize.Height * outputSize.Width * 3;
        //create buffer that stores the image
        byte[] buffer = new byte[datalength];
        //create a stream to the buffer
        using (var stream = new SDKStream(buffer))
        {
            //load image into the buffer
            ErrorHandler.CheckError(this, CanonSDK.EdsGetImage
            (imgRef, imageSource, TargetImageType.RGB, 
             imageInfo.EffectiveRect, outputSize, stream.Reference));

            //make BGR from RGB (System.Drawing (i.e. GDI+) uses BGR)
            unsafe
            {
                byte tmp;
                fixed (byte* pix = buffer)
                {
                    for (long i = 0; i < datalength; i += 3)
                    {
                        tmp = pix[i];        //Save B value
                        pix[i] = pix[i + 2]; //Set B value with R value
                        pix[i + 2] = tmp;    //Set R value with B value
                    }
                }
            }

            //Get pointer to stream data
            ErrorHandler.CheckError(this, 
                CanonSDK.EdsGetPointer(stream.Reference, out streamPointer));
            //Create bitmap with the data in the buffer
            return new Bitmap(outputSize.Width, outputSize.Height, 
            datalength, PixelFormat.Format24bppRgb, streamPointer);
        }
    }
    finally
    {
        //Release all data
        if (imgStream != IntPtr.Zero) ErrorHandler.CheckError
           (this, CanonSDK.EdsRelease(imgStream));
        if (imgRef != IntPtr.Zero) ErrorHandler.CheckError
           (this, CanonSDK.EdsRelease(imgRef));
    }
}

A Note to the Methods

I did not add every single method that is used in the code here. Just download the source if something is not completely clear. And if you still have a question after that, simply ask me. :)

Note to Threading

The Canon SDK requires the use of threads in a single threaded apartment (=STA) which makes things a bit difficult. Lucky for you, I added the STAThread class that handles the setup and issues that arise with this threading model. The CanonAPI class has one thread that is used for initializing and terminating the SDK and to get all SDK events. Each Camera instance has one thread that is used to execute all commands on. To ensure that nothing executes at the same time, all commands are surrounded by a lock (with the ExecLock field as lock object). Should two commands execute at the same time, the SDK will hang completely in most cases. All methods provided by the CanonAPI and the Camera are safe to execute but if you want to call methods from the CanonSDK class, you have to make sure to execute it on the right thread (by calling the Invoke method of the STAThread class). If you have any issues with the SDK hanging, please let me know.

Using the GUI

This project includes a Window Forms UI and a WPF UI. They both work the same way and are here to show you how to use the above code in an actual program.

Note: These GUIs are not meant for production use and are merely here as an example to get you started!

Plug in your camera and select in the list. Click on "Open Session" and start to use the camera.

Select the values in the dropdown menus to set them in the camera.

Use the "StartLV" button to start the live view and the buttons with the arrows on it can be used to control the focus (only works if the lens is in AF mode and live view is running).

Image 1

Image 2

Points of Interest

This code was tested with:

  • EOS 5D Mark III
  • EOS 7D
  • EOS 40D
  • EOS 60D
  • EOS 700D
  • EOS 600D
  • EOS 550D
  • EOS 500D
  • EOS 450D
  • EOS 100D/Rebel SL1
  • EOS 1200D/Rebel T5
  • EOS 1100D/Rebel T3
  • and several others

If you tried it with a different model, please let me know so I can add it to this list.

And if you have found a bug, have an improvement or a new snippet, I'm happy to hear from you!

History

  • November 2013 - Initial version
  • November 2013 - Bugfix for viewing LiveView and taking photos at the same time
  • December 2013 - Added GetStringProperty and GetStructProperty and added video recording
  • January 2014 - Added SetStringProperty and SetStructProperty and minor changes in the UI
  • January 2014 - Added several new methods and made the code much more secure
  • June 2014 - Changed a few methods to be more secure and revised the code and UI a bit
  • October 2014 - Fixed a few bugs, revised the Winforms UI and added a WPF UI
  • November 2014 - Fixed deadlock issue when the live view is running and camera commands are called
  • April 2015 - Fixed a bug where the camera would hang before or after filming. Fixed a broken link
  • May 2015 - Camera commands are now properly executed on an STA thread. Other smaller fixes
  • August 2015 - UI: fixed CameraAdded+Shutdown bugs; Added basic error handling. Lib: smaller fixes

  • March 2016 (1.0.0) - Complete revision of the project. It now uses a similar architecture to the commercial library.
  • March 2016 (1.0.1) - Fixed wrong check in StopFilming method
  • April 2016 (1.1.0) - Several bugfixes, improvements and support for Canon SDK 3.4
  • April 2016 (1.1.1) - Fixed incorrect lock in STAThread.Invoke method (thanks Dave)
  • May 2016 (1.1.2) - Various smaller fixes (thanks elgarf) and added LiveViewStopped event

License

This article, along with any associated source code and files, is licensed under The MIT License