Camera Application Development

sunnuntai 14. maaliskuuta 2010

The conclusion of the previous post was that we want to create a single application binary, which can use the latest features in the camera API, but also works on older devices. In this post, we'll look at instantiating CCamera in a way that allows this.

There are two different observer interfaces for CCamera. MCameraObserver is the older one and works on all devices – MCameraObserver2 provides more information and enables use of CCameraAdvancedSettings, but is only supported on S60 3rd edition FP2 and newer devices. The observer interface to be used is selected calling either CCamera::NewL and CCamera::New2L to instantiate CCamera. If the selected interface type (or use of camera in general) is not supported, the function will leave with KErrNotSupported.

The most obvious idea, that first comes to the mind after we have a class that implements both of the observer interfaces, is first calling New2L, and if it didn't work, falling back to NewL. The code might look something like this:

TRAPD(err, iCamera = CCamera::New2L(*this, KCameraIndex, KPriority));

if( KErrNone != err )
{
// MCameraObserver2 not available, use MCameraObserver
iCamera = CCamera::NewL(*this, KCameraIndex);
}

However, an application, which contains this piece of code, will not start on older devices - instead, the error note "Menu: Feature not supported (-5)" is displayed. It turns out that the function ordinal for New2L does not exist in ecam.dll on S60 3rd edition FP1 devices and earlier, and this causes application loading to fail.

As calling New2L function normally causes problems, we'll have to load the ECam DLL dynamically, resolve the function by its ordinal, and then call it through the resulting function pointer. If any of these steps fails, we know that the MCameraObserver2 interface is not supported. The code to do this can be written as follows:

// New2L is the 17th exported function in ecam.dll (see ecam.lib)
_LIT(KECamDll, "ecam.dll");
const TInt KNew2LOrdinal = 17;

// New2L returns CCamera ptr, params are MCameraObserver2 reference and 2 TInts
typedef CCamera*(* New2LFunction)(MCameraObserver2&, TInt, TInt);

void CCameraHandler::TryNew2L(MCameraObserver2& aObserver,TInt aCameraIndex,TInt aPriority)
{
RLibrary ecamLib;
CleanupClosePushL(ecamLib);

// Load library, lookup function
User::LeaveIfError(ecamLib.Load(KECamDll));
New2LFunction CCamera_New2L((New2LFunction)ecamLib.Lookup(KNew2LOrdinal));

if( NULL == CCamera_New2L )
{
// Function not available in DLL
User::Leave(KErrNotSupported);
}

// Call CCamera::New2L
iCamera = CCamera_New2L(aObserver, aCameraIndex, aPriority);

CleanupStack::PopAndDestroy(); // ecamLib
}


New2L is then replaced with a call to this function. The final instantiation code:

TRAPD(err, TryNew2L(*this, KCameraIndex, KPriority));

if( KErrNone != err )
{
// MCameraObserver2 not available, use MCameraObserver
iCamera = CCamera::NewL(*this, KCameraIndex);
}

MCameraObserver2 is now used where it's available, MCameraObserver is used on older devices, and the same application binary can be launched on any S60 3rd or 5th edition device. Next post will take a look at using CCameraAdvancedSettings via dynamic DLL loading and a wrapper class.

lauantai 6. maaliskuuta 2010

Old And New Symbian Camera API

A bit of an introduction first. While developing a simple camera application in my free time, I have at times been quite frustrated about the level of documentation and even secrecy around the Symbian camera API (also known as ECAM) and its implementation on Nokia Series 60 devices. I'm hoping that at least some things that will be written on this blog will make life easier for somebody else.

The CCamera class was originally created during times when phone cameras weren't quite as advanced as they are today, so its API is quite limited and some of the features missing from the API are nowadays considered quite basic. In Series 60 3.1 and earlier, the missing features could only officially be taken into use by 3rd party developers by using extensions provided by the device manufacturer. Autofocus was and is the only extension publicly available for Nokia S60 3.0 and 3.1 devices – for other missing things, trying to reverse engineer the non-public APIs is the only way.

In Series 60 3.2, CCamera's new observer interface MCameraObserver2, CCameraAdvancedSettings and CCameraImageProcessing APIs changed everything. Now all of the settings and most of the functionality used by the phone's camera application were also in reach of a normal 3rd party developer. This comes with a price though, an application using the new features via build time DLL linking will not even start on older devices.

One might think that it would make sense to simply develop using the older functionality supported everywhere to make applications more compatible. However, there are a lot of benefits in using the new APIs where available:

  • We get additional camera related events, the most notable one being event of another application having taken ownership of the camera hardware. When using the older API there's no way to know this has happened, viewfinder frames simply stop coming.

  • There are more settings available.

  • Settings work in a predictable way. When using advanced settings, every setting has an UID and after requersting a new value for a setting we get a callback with that UID when the new value has been taken into use. Old CCamera::SetXXX functions appear synchronous, but for example changing brightness values at key repeat speed will cause viewfinder to stop updating until the value updates stop (at least on Nokia 6220 Classic).

  • It's also worth mentioning, that the old autofocus extension does not work in newer devices, so making an application that works well everywhere using just either of the API versions is simply not possible. In this blog, we will discuss what is needed to use the new functionality where available, while retaining binary compatibility for the older devices. Three different ways of using new functionality with backwards compatibility are presented below.

    Supporting Both API Versions In One Application Binary

    1. Using CameraWrapper

    The official Forum Nokia solution for the camera API binary break is a separate ”CameraWrapper” library, which handles instantiation of CCamera and implements some functionality, including autofocus. CameraWrapper also provides pointers to CCamera and CCameraAdvancedSettings, so it's possible to even use settings not directly supported by the wrapper. Sounds good so far.

    However, when using CameraWrapper in your application, you need to embed another SIS file in the installation package. This SIS file installs ecamadvancedsettings.dll to the C drive, in case it doesn't already exist in device ROM. Even though the dll makes it possible running an application linked against advanced settings on older Nokia devices, the embedded SIS causes the application installation fail on some (all?) non-Nokia S60 devices. This means CameraWrapper is not the generic solution for the binary break problem.

    2. Moving camera handling to a DLL

    Another solution, which has been proposed in Forum Nokia discussion boards, is doing all camera handling in a separate DLL. There would be two different versions of the DLL, one using the old camera API observer, another one using the new observer and advanced settings. Version of the DLL would be decided during installation, based on whether ecamadvancedsettings.dll exists in the ROM or not.

    This method may work in most cases on Nokia phones, but does it work always? It's possible that there are devices, which have a stub ecamadvancedsettings.dll in the ROM, even though the ecam.dll of the device does not export the New2L function. In this case, the application would fail to start, due to the DLL linking against unsupported function. I wouldn't take this risk, at least with a commercial application that's supposed to run everywhere.

    3. Dynamically loading DLLs runtime

    A third solution, which will be discussed in more detail in folloing blog posts, does not require installing additional SIS files, or isolating functionality in a separate DLL. Instead, we will divide classes and functions between base functionality (available on all devices) and additional functionality (not available everywhere). Base functionality we can use normally, linking against .lib file at build time and calling functions directly from the code, but to use the additional functionality, we will load DLLs dynamically and resolve functions by their ordinals.

    This means some additional work for library loading, but after loading is done, the only difference is that the functions are called through a function pointer, and this-pointer of the object needs to be implicitly given as the first parameter for non-static functions – and even this difficulty can be taken out of sight by using a ”wrapper class”, which hides DLL loading and has similar API as the original class. A few additional lines of code is not too bad compared to the benefit of app running on all devices, whether or not all of the APIs possibly used exist.

    With dynamic loading, it would also be possible to have the camera functionality in two different DLLs, both of which are installed and one of which is dynamically loaded during runtime. This would allow the DLLs to use camera functionality linking normally and without wrapping code, and so reduce need of dynamic loading. However, in this blog we'll look into doing everything in one EXE.

    Lukijat