Canon EDSDK Tutorial in C#






4.95/5 (146 votes)
A tutorial about the Canon SDK to remotely control DSLR cameras. From taking photos to using the LiveView.
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:
- Initiate and terminate the SDK
- Get connected cameras
- Open and close a session with a camera
- Get camera settings
- Set camera settings
- Get a list of available settings
- Send a Command to the Camera
- Take photos (normally and in Bulb mode)
- Record videos
- Download data (images, films) from the camera
- Start and view the live view
- Control the focus
- Lock and unlock the camera UI
- Folders and files on the camera
- Get an image thumbnail
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:
- Europe, African and Middle East
- North, Central and South America
- Australia and New Zealand
- India, Indonesia, Malaysia, Pakistan, the Philippines, Singapore and Thailand
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 happensWinFormsExample
: An example project that consumes theEDSDKLib
and uses it in a Windows Forms UI applicationWpfExample
: An example project that consumes theEDSDKLib
and uses it in a WPF UI applicationConsoleExample
: An example project that consumes theEDSDKLib
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 eventsCamera
: 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 toset
/get
values.STAThread:
A helper class to create an STA thread or execute code on an STA threadErrorHandler
: Astatic
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:
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:
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:
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:
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:
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:
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):
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:
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):
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):
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:
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 CameraValue
s. 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):
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.
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:
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:
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:
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:
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:
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):
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.
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.
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:
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:
protected void DownloadToFile(DownloadInfo Info, string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.CreateAlways, FileAccess.ReadWrite))
{
DownloadData(Info, stream.Reference);
}
}
and:
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:
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:
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
:
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:
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.)
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:
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
:
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:
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:
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.
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:
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).
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
andGetStructProperty
and added video recording - January 2014 - Added
SetStringProperty
andSetStructProperty
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