Monday, July 25, 2011

Controlling a digital camera with Delphi (part 3). How to shoot a picture

The following Delphi code can be used to shoot a picture. The camera must support the command, otherwise nothing happens. Note that the code needs better error checking but it should work.

procedure TMainForm.ShootPicture;
var
aPortableDevManager: IPortableDeviceManager;
deviceIDs: TArrayPWideChar;
countDeviceIDs: Cardinal;
res: HResult;
aPortableDevice: IPortableDevice;
aPortableDevValues: IPortableDeviceValues;
key: _tagpropertykey;
aCmdPortableDevValues, aCmdPortableDevValuesResult: IPortableDeviceValues;
aCmdGUID: TGUID;
begin
//for info see: http://msdn.microsoft.com/en-us/library/dd319331%28v=VS.85%29.aspx
aPortableDevManager := CoPortableDeviceManager.Create;
if VarIsClear(aPortableDevManager) then exit;
//get number of devices
countDeviceIDs := 0;
res := aPortableDevManager.GetDevices(nil, countDeviceIDs);
if (res < 0) or (countDeviceIDs = 0) then exit; //failed
//get all devices:
setLength(deviceIDs, countDeviceIDs);
res := aPortableDevManager.GetDevices(@deviceIDs[0], countDeviceIDs);
if res < 0 then exit; //failed
//get properties of the first device:
//create device:
aPortableDevice := CoPortableDevice.Create;
if VarIsClear(aPortableDevice) then exit;
//create device values:
aPortableDevValues := CreateComObject(CLASS_PortableDeviceValues) as IPortableDeviceValues;
if VarIsClear(aPortableDevValues) then exit;
//open the device (assume the camera is the first device):
res := aPortableDevice.Open(deviceIDs[0], aPortableDevValues);
if res < 0 then exit; //failed
//Note: when FAILED, we should Release the interfaces. (not implemented yet)
//shoot a picture:
//assume the camera supports this command, otherwise nothing happens.
//create device values:
aCmdPortableDevValues := CreateComObject(CLASS_PortableDeviceValues) as IPortableDeviceValues;
if VarIsClear(aCmdPortableDevValues) then exit;

key.fmtid := WPD_CATEGORY_COMMON;
key.pid := WPD_PROPERTY_COMMON_COMMAND_CATEGORY;
aCmdGUID := WPD_CATEGORY_STILL_IMAGE_CAPTURE;
res := aCmdPortableDevValues.SetGuidValue(key, aCmdGUID);
if res < 0 then exit; //failed

key.fmtid := WPD_CATEGORY_COMMON;
key.pid := WPD_PROPERTY_COMMON_COMMAND_ID;
res := aCmdPortableDevValues.SetUnsignedIntegerValue(key, WPD_COMMAND_STILL_IMAGE_CAPTURE_INITIATE);
if res < 0 then exit; //failed

//Shoot:
res := aPortableDevice.SendCommand(0, aCmdPortableDevValues, aCmdPortableDevValuesResult);
if res < 0 then exit; //failed
//I do not care about result:
aCmdPortableDevValuesResult := nil;
aCmdPortableDevValues := nil;
end;

Tuesday, May 10, 2011

Controlling a digital camera with Delphi (part 2)

As described in my previous post, I started coding with the Windows Portable Device (WPD) library. To do this in Delphi, you first need to create a TLB.PAS file that contains the declarations and other info from the library. I am using Delphi 2009, so the steps for this version are (assuming you have your project open):

1. menu: ‘Component -> Import Component…’
2. ‘Import a Type Library’, then click ‘Next’
3. From the list select: ‘PortableDeviceApi 1.0 Type Library’, then click ‘Next’
4. In the ‘Unit Dir Name’ box, enter the directory where you want the unit to be created, then click ‘Next’
5. Select ‘Create Unit’, then click ‘Finish’.
You should see the new Unit in Delphi. Add the unit to your project.

The library contains most of the information you need, but there are a few things we need to modify. First of all, add the following in the const declarations at the beginning of the unit (not necessary, but useful):

CLASS_PortableDeviceValues: TGUID = '{0c15d503-d017-47ce-9016-7b3f978721cc}';

Then, change the declarations for the following functions, which are not properly defined (I wasted a few days until I figured this out):
For ‘IEnumPortableDeviceObjectIDs’, change the ‘Next’ function to:

function Next(cObjects: LongWord;
pObjIDs: Pointer;
var pcFetched: LongWord): HResult; stdcall;

For ‘IPortableDeviceManager’, change the ‘GetDevices’ and ‘GetDeviceFriendlyName’ to:

function GetDevices(pPnPDeviceIDs: Pointer;
var pcPnPDeviceIDs: LongWord): HResult; stdcall;
function GetDeviceFriendlyName(pszPnPDeviceID: PWideChar;
pDeviceFriendlyName: Pointer;
var pcchDeviceFriendlyName: LongWord): HResult; stdcall;

There may be other functions that need editing but these are the ones I have discovered so far.
In addition, it is a good idea to define some constants, to make your life easier, especially when translating from c code. I created a new unit (named WPD) and added a long list of constants and some useful functions. I will post a link to the file once I clean it up a bit, so be patient for now. Most of the info can be found in the h files that are included in the Microsoft Windows Software Development Kit for Windows 7. This can be freely downloaded from the Microsoft web site. Also, be sure to visit and carefully study the Microsoft site for portable devices which contains examples of c code, upon which I based my implementation.

To end today’s post, here is code to look for wpd devices connected to your computer and list their names and device Ids to a Memo component:

procedure TMainForm.ListDevices;
var
aPortableDevManager: IPortableDeviceManager;
countDeviceIDs: Cardinal;
res: HResult;
deviceIDs: TArrayPWideChar;
i: integer;
pcchDeviceFriendlyName: Cardinal;
DeviceFriendlyName: PWideChar;
begin
aPortableDevManager := CoPortableDeviceManager.Create;
if VarIsClear(aPortableDevManager) then exit;
//get number of devices
countDeviceIDs := 0;
res := aPortableDevManager.GetDevices(nil, countDeviceIDs);
Memo1.Lines.Add('Devices found: ' + inttostr(countDeviceIDs));
if (res < 0) or (countDeviceIDs = 0) then exit;
//get all devices:
setLength(deviceIDs, countDeviceIDs);
res := aPortableDevManager.GetDevices(@deviceIDs[0], countDeviceIDs);
if res < 0 then exit; //failed
for i := 0 to countDeviceIDs - 1 do //enumerate the devices:
begin
Memo1.Lines.Add(deviceIDs[i]);
//get length of friendly name:
pcchDeviceFriendlyName := 0;
aPortableDevManager.GetDeviceFriendlyName(deviceIDs[i],
nil,
pcchDeviceFriendlyName);
DeviceFriendlyName := PWideChar(StringOfChar(' ', pcchDeviceFriendlyName));
//get name:
aPortableDevManager.GetDeviceFriendlyName(deviceIDs[i],
@DeviceFriendlyName[0],
pcchDeviceFriendlyName);
Memo1.Lines.Add(DeviceFriendlyName);
end;
end;

You will need these in your ‘uses’ list: Windows, ComObj, ActiveX, PortableDeviceApiLib_TLB, Variants, Classes
TArrayPWideChar was defined as:

type
pArrayPWideChar = ^TArrayPWideChar;
TArrayPWideChar = array of PWideChar;

In my next post I hope to show you how to shoot a picture.

Wednesday, May 04, 2011

Controlling a digital camera with Delphi

I want to create an application that will control a digital camera using Delphi, so that I can take pictures by issuing commands from the computer and then transfer them to my hard disk, all through the USB connection. I thought this would be relatively easy, but I managed to get lost pretty quickly.
First of all, I tried to implement the Picture Transfer Protocol (PTP), which is a relatively low level interface. I tried code by Miguel Lucero (http://www.delphi3000.com/articles/article_4077.asp) and Mike Heydon (http://www.delphi3000.com/articles/article_4841.asp) and managed to get the device name of the camera from the registry. Then I tried to communicate with it through the USB interface using the CreateFile and WriteFile/ReadFile functions. The CreateFile function seemed to work fine, returning what appeared as a valid handle:

For an example see http://members.fortunecity.com/sanya_k/oly/registry.htm, and the same code in Delphi:

hcamIN := CreateFile(PWideChar(pipeIn),
GENERIC_READ,
FILE_SHARE_READ,
nil, // no SECURITY_ATTRIBUTES structure
OPEN_EXISTING, // No special create flags
0, // No special attributes
0); // No template file

The problem was with the ReadFile and WriteFile functions, which always returned a “Request is not supported” error (error 50), no matter what I tried. Then I tried the DeviceIoControl function, but this also failed. I do not know what the problem is. I am running Delphi 2009 on Windows 7. I tried running the application in administrator mode, but that did not work any better. I thought that perhaps the camera (a Nikon D70) does not support PTP or USB commands, but this is not true, because the Nikon Camera Control Pro software works perfectly and I could see the USB commands going through, with the help of a USB sniffer program (I tried SnoopyPro and USBlyzer). Finally, I gave up, after almost a week of trying. Perhaps it is Delphi’s fault. I would appreciate any suggestions. If anyone has had a similar experience, please let me know.

After abandoning the USB low-level path, I decided to use the Windows Image Acquisition library (WIA), but soon realized that it is deprecated and that Windows Portable Devices interface (WPD) has taken its place. So, I started to code some basic stuff using WPD. I have made some progress and will report on it soon (I hope).