/// <summary> /// The used camera /// </summary> public Camera MainCamera { get; private set; } /// <summary> /// States if a session with the MainCamera is opened /// </summary> public bool CameraSessionOpen { get; private set; } /// <summary> /// States if the LiveView is on or not /// </summary> public bool IsLiveViewOn { get; private set; } /// <summary> /// States if LiveView is recorded or not /// </summary> public bool IsEvfFilming { get; private set; } /// <summary> /// Directory to where photos will be saved /// </summary> public string ImageSaveDirectory { get; set; } /// <summary> /// Handles errors that happen with the SDK /// </summary> public uint Error { get { return EDSDK.EDS_ERR_OK; } set { if (value != EDSDK.EDS_ERR_OK) throw new Exception("SDK Error: " + value); } } /// <summary> /// Frame buffer for LiveView recording /// </summary> private Queue<byte[]> FrameBuffer = new Queue<byte[]>(1000);还有一些来自SDK的以及我自己添加的一些事件:
#region SDK Events public event EDSDK.EdsCameraAddedHandler SDKCameraAddedEvent; public event EDSDK.EdsObjectEventHandler SDKObjectEvent; public event EDSDK.EdsProgressCallback SDKProgressCallbackEvent; public event EDSDK.EdsPropertyEventHandler SDKPropertyEvent; public event EDSDK.EdsStateEventHandler SDKStateEvent; #endregion #region Custom Events public delegate void CameraAddedHandler(); public delegate void ProgressHandler(int Progress); public delegate void ImageUpdate(Image img); public delegate void FloatUpdate(float Value); /// <summary> /// Fires if a camera is added /// </summary> public event CameraAddedHandler CameraAdded; /// <summary> /// Fires if any process reports progress /// </summary> public event ProgressHandler ProgressChanged; /// <summary> /// Fires if the LiveView image is updated /// </summary> public event ImageUpdate LiveViewUpdated; /// <summary> /// Fires if a new framerate is calculated /// </summary> public event FloatUpdate FrameRateUpdated; #endregion这个类的方法将在稍后讨论。
Camera类相当简单,工作起来就像一个相机指针和有关相机的一些信息的容器:
public class Camera { internal IntPtr Ref; public EDSDK.EdsDeviceInfo Info { get; private set; } public uint Error { get { return EDSDK.EDS_ERR_OK; } set { if (value != EDSDK.EDS_ERR_OK) throw new Exception("SDK Error: " + value); } } public Camera(IntPtr Reference) { this.Ref = Reference; EDSDK.EdsDeviceInfo dinfo; Error = EDSDK.EdsGetDeviceInfo(Reference, out dinfo); this.Info = dinfo; } }
/// <summary> /// Initialises the SDK and adds events /// </summary> public SDKHandler() { //this is the important part of initialisation Error = EDSDK.EdsInitializeSDK(); //here we subscribe to the CameraAddedEvent and tell the SDK we did so CameraAddedEvent += new EDSDK.EdsCameraAddedHandler(SDKHandler_CameraAddedEvent); EDSDK.EdsSetCameraAddedHandler(CameraAddedEvent, IntPtr.Zero); //here we subscribe to the rest of the camera events SDKStateEvent += new EDSDK.EdsStateEventHandler(Camera_SDKStateEvent); SDKPropertyEvent += new EDSDK.EdsPropertyEventHandler(Camera_SDKPropertyEvent); SDKProgressCallbackEvent += new EDSDK.EdsProgressCallback(Camera_SDKProgressCallbackEvent); SDKObjectEvent += new EDSDK.EdsObjectEventHandler(Camera_SDKObjectEvent); }而当你关闭程序时,就会调用:
/// <summary> /// Closes open session and terminates the SDK /// </summary> public void Dispose() { if (CameraSessionOpen) Error = EDSDK.EdsCloseSession(MainCamera.Ref); Error = EDSDK.EdsTerminateSDK(); }
要打开一个会话,你必须选择一个相机。如果要获得所有连接的像机的列表,那就调用这个:
/// <summary> /// Get a list of all connected cameras /// </summary> /// <returns>The camera list</returns> public List<Camera> GetCameraList() { IntPtr camlist; //Get cameralist Error = EDSDK.EdsGetCameraList(out camlist); //Get each camera from camlist int c; Error = EDSDK.EdsGetChildCount(camlist, out c); List<Camera> OutCamList = new List<Camera>(); for (int i = 0; i < c; i++) { IntPtr cptr; Error = EDSDK.EdsGetChildAtIndex(camlist, i, out cptr); OutCamList.Add(new Camera(cptr)); { return OutCamList; }
从以前收到的像机列表中选择一个,打开一个使用它的会话:
/// <summary> /// Opens a session with given camera /// </summary> /// <param name="NewCamera">The camera which will be used</param> public void OpenSession(Camera NewCamera) { //make sure the previous camera session is closed if (CameraSessionOpen) Error = EDSDK.EdsCloseSession(MainCamera.Ref); if (NewCamera != null) { MainCamera = NewCamera; //open a session Error = EDSDK.EdsOpenSession(MainCamera.Ref); //subscribe to the camera events (this time, in-Camera) EDSDK.EdsSetCameraStateEventHandler(MainCamera.Ref, EDSDK.StateEvent_All, SDKStateEvent, IntPtr.Zero); EDSDK.EdsSetObjectEventHandler(MainCamera.Ref, EDSDK.ObjectEvent_All, SDKObjectEvent, IntPtr.Zero); EDSDK.EdsSetPropertyEventHandler(MainCamera.Ref, EDSDK.PropertyEvent_All, SDKPropertyEvent, IntPtr.Zero); CameraSessionOpen = true; } }如果你完成了对相机的使用,就使用这个方法关闭会话:
/// <summary> /// Closes the session with the current camera /// </summary> public void CloseSession() { if (CameraSessionOpen) { Error = EDSDK.EdsCloseSession(MainCamera.Ref); CameraSessionOpen = false; } }
Set 和 Get 相机设置
通过ID去设置和获取相机的设置是非常简单的,但是一些有难度的结构值(这里还没有介绍).下面这个例子你可以在这个方法中获取到Tv,Av和ISO的设置。
/// <summary> /// Gets the current setting of given property ID /// </summary> /// <param name="PropID">The property ID</param> /// <returns>The current setting of the camera</returns> public uint GetSetting(uint PropID) { if (MainCamera.Ref != IntPtr.Zero) { unsafe { uint property = 0; EDSDK.EdsDataType dataType; int dataSize; IntPtr ptr = new IntPtr(&property); //get the size of this property Error = EDSDK.EdsGetPropertySize(MainCamera.Ref, PropID, 0, out dataType, out dataSize); //get the data for this property Error = EDSDK.EdsGetPropertyData(MainCamera.Ref, PropID, 0, dataSize, ptr); return property; } } else { throw new ArgumentNullException("Camera or camera reference is null/zero"); } }Setting方法(这里的参数一般是ID,从Camera类中获取这个string值):
/// <summary> /// Sets a value for the given property ID /// </summary> /// <param name="PropID">The property ID</param> /// <param name="Value">The value which will be set</param> public void SetSetting(uint PropID, uint Value) { if (MainCamera.Ref != IntPtr.Zero) { int propsize; EDSDK.EdsDataType proptype; //get the size of this property Error = EDSDK.EdsGetPropertySize(MainCamera.Ref, PropID, 0, out proptype, out propsize); //set the property Error = EDSDK.EdsSetPropertyData(MainCamera.Ref, PropID, 0, propsize, Value); } else { throw new ArgumentNullException("Camera or camera reference is null/zero"); } }可获取的设置值清单:
特定的相机没有特定的支持设置。这就是为什么你需要去获取所有可支持的设置值清单。这些只支持"AEModeSelect", "ISO", "Av", "Tv", "MeteringMode" 和"ExposureCompensation"。传给特定的ID你可以获取到对应的返回值。在Camera类中可以找到和Av,Tv和ISO的对应值。查看PDF格式的SDK文档可以获取其他的值。
/// <summary> /// Gets the list of possible values for the current camera to set. /// Only the PropertyIDs "AEModeSelect", "ISO", "Av", "Tv", "MeteringMode" /// and "ExposureCompensation" are allowed. /// </summary> /// <param name="PropID">The property ID</param> /// <returns>A list of available values for the given property ID</returns> public List<int> GetSettingsList(uint PropID) { if (MainCamera.Ref != IntPtr.Zero) { if (PropID == EDSDK.PropID_AEModeSelect || PropID == EDSDK.PropID_ISOSpeed || PropID == EDSDK.PropID_Av || PropID == EDSDK.PropID_Tv || PropID == EDSDK.PropID_MeteringMode || PropID == EDSDK.PropID_ExposureCompensation) { EDSDK.EdsPropertyDesc des; Error = EDSDK.EdsGetPropertyDesc(MainCamera.Ref, PropID, out des); return des.PropDesc.Take(des.NumElements).ToList(); } else throw new ArgumentException("Method cannot be used with this Property ID"); } else { throw new ArgumentNullException("Camera or camera reference is null/zero"); } }
在bulb mode(灯泡模式)下正常拍照
用当前设置拍照,调用TakePhoto方法。有三点需要特别注意:
1、新线程启动了所以主线程没有被挂起。
2、之所有这里用while 循环,是因为由相机有时不会立即就绪,需要稍后再试
3、如果你将pc作为外设,那么请到下一个章节学习如何获取图片。
/// <summary> /// Takes a photo with the current camera settings /// </summary> public void TakePhoto() { new Thread(delegate() { int BusyCount = 0; uint err = EDSDK.EDS_ERR_OK; while (BusyCount < 20) { //try to take a photo err = EDSDK.EdsSendCommand(MainCamera.Ref, EDSDK.CameraCommand_TakePicture, 0); //if the camer is currently busy, wait and try again. //If successful or an error happened, break the loop if (err == EDSDK.EDS_ERR_DEVICE_BUSY) { BusyCount++; Thread.Sleep(50); } else { break; } } Error = err; }).Start(); }在bulb 模式下拍照,调用带有时间参数的 takePhoto方法
/// <summary> /// Takes a photo in bulb mode with the current camera settings /// </summary> /// <param name="BulbTime">The time in milliseconds for how long the shutter will be open</param> public void TakePhoto(uint BulbTime) { new Thread(delegate() { if (BulbTime < 1000) { throw new ArgumentException("Bulbtime has to be bigger than 1000ms"); } int BusyCount = 0; uint err = EDSDK.EDS_ERR_OK; while (BusyCount < 20) { //open the shutter err = EDSDK.EdsSendCommand(MainCamera.Ref, EDSDK.CameraCommand_BulbStart, 0); if (err == EDSDK.EDS_ERR_DEVICE_BUSY) { BusyCount++; Thread.Sleep(50); } else { break; } } Error = err; //Wait for the specified time Thread.Sleep((int)BulbTime); //close the shutter Error = EDSDK.EdsSendCommand(MainCamera.Ref, EDSDK.CameraCommand_BulbEnd, 0); }).Start(); }
想要把拍摄的照片直接传到电脑上,代替相机存储,请调用SetSetting方法进行设置:
SetSetting(EDSDK.PropID_SaveTo, (uint)EDSDK.EdsSaveTo.Host);每拍摄一张照片,EDSDK.ObjectEvent_DirItemRequestTransfer 类型的 SDKObjectEvent 都会被触发
/// <summary> /// An Objectevent fired /// </summary> /// <param name="inEvent">The ObjectEvent id</param> /// <param name="inRef">Pointer to the object</param> /// <param name="inContext"></param> /// <returns>An EDSDK errorcode</returns> private uint Camera_SDKObjectEvent(uint inEvent, IntPtr inRef, IntPtr inContext) { if(inEvent == EDSDK.ObjectEvent_DirItemRequestTransfer) DownloadImage(inRef, @"Images\"); return EDSDK.EDS_ERR_OK; }Do wnloadIma ge方法如下 :
/// <summary> /// Downloads an image to given directory /// </summary> /// <param name="Info">Pointer to the object. /// Get it from the SDKObjectEvent.</param> /// <param name="directory"></param> public void DownloadImage(IntPtr ObjectPointer, string directory) { EDSDK.EdsDirectoryItemInfo dirInfo; IntPtr streamRef; //get information about the image Error = EDSDK.EdsGetDirectoryItemInfo(ObjectPointer, out dirInfo); string CurrentPhoto = Path.Combine(directory, dirInfo.szFileName); //create a filestream for the image Error = EDSDK.EdsCreateFileStream(CurrentPhoto, EDSDK.EdsFileCreateDisposition.CreateAlways, EDSDK.EdsAccess.ReadWrite, out streamRef); uint blockSize = 1024 * 1024; uint remainingBytes = dirInfo.Size; //download the image data in blocks do { if (remainingBytes < blockSize) { blockSize = (uint)(remainingBytes / 512) * 512; } remainingBytes -= blockSize; Error = EDSDK.EdsDownload(ObjectPointer, blockSize, streamRef); } while (remainingBytes > 512); //download the last bit of the image Error = EDSDK.EdsDownload(ObjectPointer, remainingBytes, streamRef); //tell the camera that the download is done Error = EDSDK.EdsDownloadComplete(ObjectPointer); //release image and stream Error = EDSDK.EdsRelease(ObjectPointer); Error = EDSDK.EdsRelease(streamRef); }
打开并查看视频
视频是最难处理的事情之一,尤其是要求高性能的情况下。 首先我们这样打开视频:
/// <summary> /// Starts the LiveView /// </summary> public void StartLiveView() { //make sure it's not already on if (!IsLiveViewOn) { //set the LiveView output to be the PC SetSetting(EDSDK.PropID_Evf_OutputDevice, EDSDK.EvfOutputDevice_PC); IsLiveViewOn = true; } }完成之后, SDKPropertyEvent这个事件的 inPropertyID参数就被设置成了 EDSDK.PropID_Evf_OutputDevice:
/// <summary> /// A property changed /// </summary> /// <param name="inEvent">The PropetyEvent ID</param> /// <param name="inPropertyID">The Property ID</param> /// <param name="inParameter">Event Parameter</param> /// <param name="inContext">...</param> /// <returns>An EDSDK errorcode</returns> private uint Camera_SDKPropertyEvent (uint inEvent, uint inPropertyID, uint inParameter, IntPtr inContext) { if (inPropertyID == EDSDK.PropID_Evf_OutputDevice) { if (IsEvfFilming == true) DownloadEvfFilm(); else if (IsLiveViewOn == true) DownloadEvf(); } return EDSDK.EDS_ERR_OK; }DownloadEvf方法如下:
/// <summary> /// Downloads the LiveView image /// </summary> private void DownloadEvf() { new Thread(delegate() { //To give the camera time to switch the mirror Thread.Sleep(1500); IntPtr jpgPointer; IntPtr stream = IntPtr.Zero; IntPtr EvfImageRef = IntPtr.Zero; UnmanagedMemoryStream ums; uint err; uint length; //create streams err = EDSDK.EdsCreateMemoryStream(0, out stream); err = EDSDK.EdsCreateEvfImageRef(stream, out EvfImageRef); Stopwatch watch = new Stopwatch(); //stopwatch for FPS calculation float lastfr = 24; //last actual FPS //Run LiveView while (IsLiveViewOn) { watch.Restart(); //download current LiveView image err = EDSDK.EdsDownloadEvfImage(MainCamera.Ref, EvfImageRef); unsafe { //get pointer and create stream Error = EDSDK.EdsGetPointer(stream, out jpgPointer); Error = EDSDK.EdsGetLength(stream, out length); ums = new UnmanagedMemoryStream ((byte*)jpgPointer.ToPointer(), length, length, FileAccess.Read); //fire the LiveViewUpdated event with //the LiveView image created from the stream if (LiveViewUpdated != null) LiveViewUpdated(Image.FromStream(ums)); ums.Close(); } //calculate the framerate and fire the FrameRateUpdated event lastfr = lastfr * 0.9f + (100f / watch.ElapsedMilliseconds); if (FrameRateUpdated != null) FrameRateUpdated(lastfr); } //Release and finish if (stream != IntPtr.Zero) { Error = EDSDK.EdsRelease(stream); } if (EvfImageRef != IntPtr.Zero) { Error = EDSDK.EdsRelease(EvfImageRef); } //stop the LiveView SetSetting(EDSDK.PropID_Evf_OutputDevice, EDSDK.EvfOutputDevice_TFT); }).Start(); }
虽然这样下载视频图像不是最简单的,但可以说是最快的。
调用StopLiveView方法就能停止实物取景,实质上它的目的是让DownloadEvf方法跳出while循环:
/// <summary> /// Stops the LiveView /// </summary> public void StopLiveView() { IsLiveViewOn = false; }
记录视频的工作跟播放视频的方式很像。
开始方法如下:
/// <summary> /// Starts LiveView and records it /// </summary> public void StartEvfFilming() { if (!IsLiveViewOn) { SetSetting(EDSDK.PropID_Evf_OutputDevice, EDSDK.EvfOutputDevice_PC); IsLiveViewOn = true; IsEvfFilming = true; } }捕获SDKPropertyEvent事件:
/// <summary> /// A property changed /// </summary> /// <param name="inEvent">The PropetyEvent ID</param> /// <param name="inPropertyID">The Property ID</param> /// <param name="inParameter">Event Parameter</param> /// <param name="inContext">...</param> /// <returns>An EDSDK errorcode</returns> private uint Camera_SDKPropertyEvent (uint inEvent, uint inPropertyID, uint inParameter, IntPtr inContext) { if (inPropertyID == EDSDK.PropID_Evf_OutputDevice) { if (IsEvfFilming == true) DownloadEvfFilm(); else if (IsLiveViewOn == true) DownloadEvf(); } return EDSDK.EDS_ERR_OK; }DownloadEvfFilmmethod和DownloadEvfmethod比较相似,但有以下不同:
/// <summary> /// Records the LiveView image /// </summary> private void DownloadEvfFilm() { new Thread(delegate() { //To give the camera time to switch the mirror Thread.Sleep(1500); IntPtr jpgPointer; IntPtr stream = IntPtr.Zero; IntPtr EvfImageRef = IntPtr.Zero; UnmanagedMemoryStream ums; uint err; uint length; err = EDSDK.EdsCreateMemoryStream(0, out stream); err = EDSDK.EdsCreateEvfImageRef(stream, out EvfImageRef); //Download one frame to init the video size err = EDSDK.EdsDownloadEvfImage(MainCamera.Ref, EvfImageRef); unsafe { Error = EDSDK.EdsGetPointer(stream, out jpgPointer); Error = EDSDK.EdsGetLength(stream, out length); ums = new UnmanagedMemoryStream((byte*)jpgPointer.ToPointer(), length, length, FileAccess.Read); Bitmap bmp = new Bitmap(ums); StartEvfVideoWriter(bmp.Width, bmp.Height); bmp.Dispose(); ums.Close(); } Stopwatch watch = new Stopwatch(); byte[] barr; //bitmap byte array const long ft = 41; //Frametime at 24FPS //(actually 41.66, but there is a bit of calculation overhead) float lastfr = 24; //last actual FPS int LVUpdateBreak1 = 0; //Run LiveView while (IsEvfFilming) { watch.Restart(); err = EDSDK.EdsDownloadEvfImage(MainCamera.Ref, EvfImageRef); unsafe { Error = EDSDK.EdsGetPointer(stream, out jpgPointer); Error = EDSDK.EdsGetLength(stream, out length); ums = new UnmanagedMemoryStream((byte*)jpgPointer.ToPointer(), length, length, FileAccess.Read); barr = new byte[length]; ums.Read(barr, 0, (int)length); //For better performance the LiveView is only updated with every 4th frame if (LVUpdateBreak1 == 0 && LiveViewUpdated != null) { LiveViewUpdated(Image.FromStream(ums)); LVUpdateBreak1 = 4; } LVUpdateBreak1--; FrameBuffer.Enqueue(barr); ums.Close(); } //To get a steady framerate: while (true) if (watch.ElapsedMilliseconds >= ft) break; lastfr = lastfr * 0.9f + (100f / watch.ElapsedMilliseconds); if (FrameRateUpdated != null) FrameRateUpdated(lastfr); } //Release and finish if (stream != IntPtr.Zero) { Error = EDSDK.EdsRelease(stream); } if (EvfImageRef != IntPtr.Zero) { Error = EDSDK.EdsRelease(EvfImageRef); } SetSetting(EDSDK.PropID_Evf_OutputDevice, EDSDK.EvfOutputDevice_TFT); }).Start(); }由于写硬件驱动转换图片对象很慢,并且这也不需要实时处理,所以有了下面的StartEvfVideoWriter 方法 。这个方法将边框从队列中取出来保存,直到队列为空并且电影处理已关闭。我这里没有包含实际的视频保存功能,你可以用你偏好的类库去完成。
/// <summary> /// Writes video frames from the buffer to a file /// </summary> /// <param name="Width">Width of the video</param> /// <param name="Height">Height of the video</param> private void StartEvfVideoWriter(int Width, int Height) { new Thread(delegate() { byte[] byteArray; ImageConverter ic = new ImageConverter(); Image img; while (IsEvfFilming) { while (FrameBuffer.Count > 0) { //get byte array from queue byteArray = FrameBuffer.Dequeue(); //convert it to an image object img = (Image)ic.ConvertFrom(byteArray); //Save video frame here. e.g. with the VideoFileWriter from the AForge library. } //if saving is faster than the LiveView, wait a bit for new frames and start over if (IsEvfFilming) Thread.Sleep(10); } }).Start(); }下面是如何利用 AForgelibrary 的一个例子 (请注意correct DLLs,它们没被包含在这个项目中)
private void StartVideoWriter(int Width, int Height) { new Thread(delegate() { VideoFileWriter writer = new VideoFileWriter(); writer.Open("LiveViewVideo.avi", Width, Height, 24, VideoCodec.MPEG4); byte[] byteArray; ImageConverter ic = new ImageConverter(); Image img; while (IsEvfFilming) { while (FrameBuffer.Count > 0) { byteArray = FrameBuffer.Dequeue(); img = (Image)ic.ConvertFrom(byteArray); writer.WriteVideoFrame(new Bitmap(img)); } if (IsEvfFilming) Thread.Sleep(10); } writer.Close(); }).Start(); }关闭电影功能跟关闭实时取景方法一样:
/// <summary> /// Stops LiveView and filming /// </summary> public void StopEvfFilming() { IsLiveViewOn = false; IsEvfFilming = false; }
为了避免或允许用户在相机上改变设置,你可以这样关闭或者打开相机的接口:
/// <summary> /// Locks or unlocks the cameras UI /// </summary> /// <param name="LockState">True for locked, false to unlock</param> public void UILock(bool LockState) { if (LockState == true) Error = EDSDK.EdsSendStatusCommand(MainCamera.Ref, EDSDK.CameraState_UILock, 0); else Error = EDSDK.EdsSendStatusCommand (MainCamera.Ref, EDSDK.CameraState_UIUnLock, 0); }
在图像化界面向导代码中,你可以看到如何将以上所有的代码运用到一个真实可用的软件中。你也可以设置 Av,Tv,ISO和白平衡,实时取景和拍照等模式.
插入相机,打开图形化界面就可以开始你的设置啦。
我用EOS 40D测试了以上代码:
如果你尝试了不同的方法,请告诉我,我会把它添加到这篇文章中。
如果你发现一些bug,对方法有改进或者有一些新的想法,非常希望你能告诉我。
源码下载: