Symbian
Symbian OS Library

SYMBIAN OS V9.3

[Index] [Spacer] [Previous] [Next]



How to write a controller plugin

A controller plugin is the main type of multimedia framework (MMF) plugin. A controller plugin typically supports playing or recording one or more multimedia formats, mp3 or avi for example. A controller plugin is able to read data from one or more sources, apply any required data transformations, then write the data to the appropriate sink.

Writing a controller plugin involves the following tasks:


Implementing the core controller plugin interface

The controller plugin API is defined by the controller framework element of the MMF. All controller plugin implementations must derive from the abstract class CMMFController and implement its pure virtual methods.

As well as providing the controller API, the CMMFController base class also provides functionality such as instantiation of controller plugins via the ECom plugin framework, inter-thread message decoding and parameter unpacking, an object reference counting mechanism and a few utility methods for use by controller plugins.

The following sections describe how to implement the controller API.

Sources and sinks

Sources and sinks are themselves ECom plugins and provide an interface that allows the controller to read and write raw data buffers. The type of source or sink, along with any source/sink-specific initialisation parameters (for example the file name for a file source), is specified by the client. The source or sink is then created and owned by the controller framework and passed into the controller plugin via a call to CMMFController::AddDataSourceL() or CMMFController::AddDataSinkL() for a data source or a data sink respectively.

Note that ownership of a source or sink always remains with the controller framework and not with the controller plugin, and that a controller plugin should never delete a source or sink. If the controller plugin leaves during the call to CMMFController::AddDataSourceL() or CMMFController::AddDataSinkL(), the source or sink will be destroyed by the controller framework.

There is no limit on the number of sources or sinks that can be added to a particular controller plugin, other than the limit provided by the plugin itself. For example, an audio controller would have one source and one sink. A video playing controller would have one source and two sinks (display and speaker).

Sources and sinks can also be removed from the controller plugin by the client. The reference of the particular source or sink being removed is passed into either the CMMFController::RemoveDataSourceL() or CMMFController::RemoveDataSinkL() method to remove a data source or a data sink respectively. The controller plugin should remove any reference that it has to the source or sink; the source or sink will then be deleted by the controller framework. Note that if the controller plugin leaves during these calls (for example if it does not support the removal of sources or sinks), the controller framework will not delete the source or sink.

Controller states

Controller plugin states, along with the commands to transition the plugin between the states, are:

Calling CMMFController::ResetL() on the controller plugin should cause it to revert back to the Open state. This will involve stopping playback, deleting any allocated resources, and removing any references to sources and sinks.

Position and Duration

Position and duration can be set by deriving from the pure virtual methods CMMFController::PositionL(), CMMFController::SetPositionL() and CMMFController::DurationL().

Note that position has meaning only in the Primed and Playing states. The controller plugin should leave with KErrNotReady if an attempt is made to set or get the position when the controller is in any other state. However, it should be possible to query the duration as soon as the data source or data sink has been added.

Custom Commands

Custom commands allow controller plugin writers to extend the basic controller API. They provide a way of passing raw messages from the client through the controller framework to the controller plugin.

The CMMFController::CustomCommand() method can be implemented in the controller plugin to support custom commands. The default implementation provided by the base class is to complete the message with KErrNotSupported. Note that it is imperative that the message is always completed by this method and that this method cannot leave. Any error that occurs must be passed back to the client by completing the message with the error code.

Each TMMFMessage has an associated interface UID which should be checked before handling any message to make sure that the command is supported.

The following code illustrates a typical custom command implementation.

void CMyController::CustomCommand(TMMFMessage& aMessage)
    {
    // First, check we can handle message by checking its interface id
    if (aMessage.InterfaceId() != KUidMyControllerCustomCommandInterface)
       {
        aMessage.Complete(KErrNotSupported);
        return;
       }

    // Next, dispatch the command to the appropriate method.
    TInt error = KErrNone;
    switch (aMessage.Function())
        {
    case EMyCustomCommandOne:
        error = HandleCustomCommandOne(aMessage);
        break;
    case EMyCustomCommandTwo:
        error = HandleCustomCommandTwo(aMessage);
        break;
    default:
        error = KErrNotSupported;
break;
        }
    aMessage.Complete(error);
    }

The above example shows synchronous command handling. If the plugin needs to do something which may take a long time, the aMessage parameter should be copied, stored and completed later when processing has finished.

The methods HandleCustomCommandOne and HandleCustomCommandTwo above, copy any data to and from the client. This can be done using the appropriate TMMFMessage methods.

Asynchronous Error Reporting

The CMMFController::DoSendEventToClient() utility method allows a controller plugin to send an event to its client.

The multimedia client utilities listen for specific event types and error codes. If the controller plugin is accessed by clients that are using the multimedia client utility APIs (which will usually be the case) then those event types and error codes should be used. The event types and error codes for which the multimedia client utilities listen are listed below:

Client utility

Event type

Error code

Meaning

Audio Player Utility (CMdaAudioPlayerUtility)

Any

KErrOverflow

Playback complete, the end of the data was reached.

Any

KErrEof

Playback complete, the end of the data was reached.

Audio Recorder Utility (CMdaAudioRecorderUtility)

Any

KErrOverflow

Playback complete, the end of the data was reached.

Any

KErrUnderflow

Recording complete, no more source data from microphone

Any

KErrEof

Playback/recording complete, end of data reached

Audio Converter Utility (CMdaAudioConvertUtility)

Any

KErrOverflow

Conversion complete, end of data reached

Any

KErrEof

Conversion complete, end of data reached

Video Player Utility (CVideoPlayerUtility)

Any

KErrOverflow

Playback complete, end of data reached

Any

KErrEof

Playback complete, end of data reached

KMMFEventCategory VideoOpenComplete

Any

Open complete. Must be used otherwise clients will not be notified when the clip has been opened. This is used because video clips can take a long time to process on opening. The error code is passed back to the client.

KMMFEventCategory PlaybackComplete

Any

Playback complete. Can be used instead of KErrOverflow or KErrEof. The error code is passed back to the client.

KMMFEventCategory VideoRebufferStarted

Any

Re-buffering has begun. Client will be notified so it can update its UI.

KMMFEventCategory VideoRebufferComplete

Any

Re-buffering has finished. Client will be notified so it can update its UI.

MMF Objects

The controller framework contains an object referencing mechanism that allows a client to send messages to arbitrary objects in the controller thread without having to go via the controller plugin itself. In order to achieve this, the arbitrary object must be derived from CMMFObject and added to the object container. The object container can be accessed via the CMMFController::MMFObjectContainerL() method.

Sources and sinks have a CMMFObject wrapper placed around them by the controller framework, and can receive messages from the client. This mechanism is also used to reference source(s) and sink(s), so the client can specify exactly which source/sink when calling the CMMFController::RemoveDataSourceL() or CMMFController::RemoveDataSinkL() methods.

Note that objects added to the CMMFObjectContainer are owned by the CMMFObjectContainer.

Each object added to the CMMFObjectContainer is assigned a handle. This handle must be passed back to the client in order for the client to be able to send messages directly to the object. The client should use this handle to set the handle of the TMMFMessageDestination parameter in the RMMFController::CustomCommandAsync() or RMMFController::CustomCommandSync() method for asynchronous or synchronous operation respectively. The custom command will then be routed directly to the CMMFObject by the controller framework.

[Top]


Implementing the standard custom command interfaces

The core controller plugin API provides only basic support for controlling the flow of data. The application-level multimedia utility APIs (CMdaAudioPlayerUtility for example) contain much richer functionality. The application-level multimedia utility APIs provide clients with a concrete API to access extended controller functionality, and to give controller plugins a concrete mixin API to implement.

Several sets of standard custom command APIs have been defined. The following table shows which of these classes must be implemented to allow the controller plugin to be used properly from each of the application-level utility APIs.

Application-level Utility API

Required Custom Command APIs

CmdaAudioPlayerUtility

MMMFAudioPlayDeviceCustomCommandImplementor

MMMFAudioPlayControllerCustomCommandImplementor

CmdaAudioRecorderUtility

MMMFAudioPlayDeviceCustomCommandImplementor

MMMFAudioRecordDeviceCustomCommandImplementor

MMMFAudioPlayControllerCustomCommandImplementor

MMMFAudioRecordControllerCustomCommandImplementor

MMMFAudioControllerCustomCommandImplementor

CmdaAudioConvertUtility

MMMFAudioPlayControllerCustomCommandImplementor

MMMFAudioRecordControllerCustomCommandImplementor

MMMFAudioControllerCustomCommandImplementor

CvideoPlayerUtility

MMMFVideoControllerCustomCommandImplementor

MMMFVideoPlayControllerCustomCommandImplementor

MMMFAudioPlayDeviceCustomCommandImplementor

CvideoRecorderUtility

MMMFVideoControllerCustomCommandImplementor

MMMFVideoRecordControllerCustomCommandImplementor

MMMFAudioRecordDeviceCustomCommandImplementor

In order to implement the required custom command APIs, the controller plugin should derive from the mixins shown in the table above, then use the CMMFController::AddCustomCommandParserL() method to register itself as being able to handle that API.

The CMMFCustomCommandParserBase derived object decodes the custom command on behalf of the controller plugin and calls the concrete API via the mixin interface. The following table shows which CMMFCustomCommandParserBase object should be used with each mixin class.

Mixin Interface

Custom Command Parser

MMMFAudioControllerCustomCommandImplementor

CMMFAudioControllerCustomCommandParser

MMMFAudioPlayControllerCustomCommandImplementor

CMMFAudioPlayControllerCustomCommandParser

MMMFAudioRecordControllerCustomCommandImplementor

CMMFAudioRecordControllerCustomCommandParser

MMMFVideoControllerCustomCommandImplementor

CMMFVideoControllerCustomCommandParser

MMMFVideoPlayControllerCustomCommandImplementor

CMMFVideoPlayControllerCustomCommandParser

MMMFVideoRecordControllerCustomCommandImplementor

CMMFVideoRecordControllerCustomCommandParser

MMMFAudioPlayDeviceCustomCommandImplementor

CMMFAudioPlayDeviceCustomCommandParser

MMMFAudioRecordDeviceCustomCommandImplementor

CMMFAudioRecordDeviceCustomCommandParser

The following example code shows how the controller should register itself with the controller framework to receive standard custom commands.

class CMyControllerPlugin : public    CMMFController,
                                      MMMFAudioControllerCustomCommandImplementor,
                                      MMMFAudioPlayDeviceCustomCommandImplementor
    {
...
private:
    void ConstructL();
    };

void CMyControllerPlugin::ConstructL()
    {
...
    // Construct custom command parsers

    CMMFAudioControllerCustomCommandParser* audConParser =
                                     CMMFAudioControllerCustomCommandParser::NewL(*this);
    CleanupStack::PushL(audParser);
    AddCustomCommandParserL(*audConParser); //parser now owned by controller framework
    CleanupStack::Pop(audConParser);

    CMMFAudioPlayDeviceCustomCommandParser* audPlayDevParser = 
                                     CMMFAudioPlayDeviceCustomCommandParser::NewL(*this);
    CleanupStack::PushL(audPlayDevParser);
    AddCustomCommandParserL(*audPlayDevParser); //parser now owned by controller framework
    CleanupStack::Pop();//audPlayDevParser


    }

It is also possible for controller plugins to define their own standard custom command classes. This might be useful if a group of plugins have the same API (for example a group of MIDI controller plugins). Clients would then be able to access equivalent functionality in each plugin using the same API.

[Top]


Interaction with MMF Base Classes and other Plugins

The MMF provides a set of utility classes and other types of plugins to aid with writing controller plugins. All utility classes are provided in the library MMFServerBaseClasses.DLL. A brief description of each of the classes follows. Note that the use of data sources, data sinks and buffers is mandatory. The use of the other classes is optional.

Data Sources and Sinks

Data sources and sinks are ECom plugins, and are derived from the base class MDataSource or MDataSink respectively. The currently available data sources and sinks are CMMFFile, CMMFDescriptor, CMMFAudioInput and CMMFAudioOutput. They are created by the controller framework and passed into the controller plugin by reference using the CMMFController::AddDataSourceL() or CMMFController::AddDataSinkL() method.

Some sources and sinks have extended APIs that allow the controller plugin to perform actions, such as setting the volume. The type of the source or sink can be checked by the controller plugin using the methods MDataSource::DataSourceType() and MDataSink::DataSinkType(). These methods return a UID which can be checked against known UIDs to identify the extended API of the source or sink. For example, the following code would be used to set the volume of a speaker sink.

MMMFAudioOutput* audioOutput = STATIC_CAST(MMMFAudioOutput*, iDataSink);
audioOutput->SoundDevice().SetVolume(14);

It is possible dynamically to add new data sources and sinks to the system, see How to write a Source/Sink plugin for information.

Buffers

Buffers are used to contain data as it is being transferred from a source to a sink. There are several buffer types all derived from a common base class CMMFBuffer: CMMFDataBuffer, CMMFDescriptorBuffer and CMMFTransferBuffer.

Datapath

The datapath is a utility class that transfers data from a single source to a single sink, via a codec plugin if the source and sink data types differ. The API of the datapath is very similar to that of the basic controller plugin API, and much of the complexity of controller plugins can be avoided by using the datapath.

The datapath can be used either through:

If the source and sink data types are the same then no codec is required and the datapath will use a null codec and the buffers will be transferred straight from the source to the sink without being copied in between.

Codecs

Codec plugins are derived from CMMFCodec and can be used by controller plugins to convert data from one data type to another. Codec plugins are designed to be used by different controller plugins, for example both an audio controller and a video controller might want to make use of a PCM8 to PCM16 codec.

See How to write a codec plugin for details of how to implement codec plugins.

Formats

Controller plugin writers may wish to implement their format support by writing format plugins. While format plugins can only be used by one controller plugin, this does make it much easier to dynamically extend the formats supported by the controller plugin without providing a whole new controller plugin. See How to write a format plugin for details of how to implement format plugins.

[Top]


Integrating the Controller into the ECom Plugin Framework

This section describes how to write the ECom plugin registry file. See ECom for generic instructions on how to write an ECom plugin.

The controller plugin resolver is responsible for deciding which controller plugin should be used to provide support for a particular format. Controller plugins provide information in their ECom resource file which allows the controller framework (and ultimately the client application) to determine:

For each format supported, for both playing and recording, the following information is provided:

Most of the information outlined above is provided by the plugin in the opaque_data field of the ECom resource file. This field takes an 8-bit string and is limited to 127 characters in length. A tagged data scheme is used to separate the different types of data. The tags are all three characters in length, and the scheme only uses opening tags (i.e. no end tags) to reduce overhead and complexity. The tags available are:

Tag

Usage

Description

<s>

Controller, Format

The supplier of the plugin.

<i>

Controller, Format

A media ID supported by this plugin. Multiple entries can be included.

<p>

Controller

The UID of the play format collection for this controller (see below).

<r>

Controller

The UID of the record format collection for this controller (see below).

<m>

Format

A mime type that describes the format. Multiple entries with this tag can be included.

<e>

Format

A file extension supported by this format. Multiple entries with this tag can be included.

<h>

Format

A segment of header data that can be matched against the first few bytes of a clip to check whether this format is capable of handling the clip. Multiple entries with this tag can be included.

Formats can be supported by controller plugins either:

The following is an example of a resource file for an audio controller plugin that supports playing WAV and AU, and recording AU.

RESOURCE REGISTRY_INFO theInfo
    {
    dll_uid = 0x101F1234;
    interfaces = 
        {
        INTERFACE_INFO // Controller Plugin Description
           {
           interface_uid = KMmfUidPluginInterfaceController ;
           implementations = 
               {
               IMPLEMENTATION_INFO
                   {
                   implementation_uid = 0x101F1235 ;
                   version_no = 1;
                   display_name = "Symbian Audio controller";
                   default_data = "?";
                   opaque_data = 
                          “<s>Symbian<i>0x101F5D07<p>0x101F0001<r>0x101F0002";
                           // SUPPLIER = Symbian
                           // MEDIA ID = uid for audio media type
                           // PLAY FORMATS = look at interface uid 0x101f0001
                           // RECORD FORMATS = look at interface uid 0x101f0002
                   }
               };
            },
        INTERFACE_INFO // Play Formats Description
            {
            interface_uid = 0x101F0001 ;
            implementations = 
               {
               IMPLEMENTATION_INFO
                   {
                   implementation_uid = 0x101F1236 ;
                   version_no = 1;
                   display_name = "WAV Play Format";
                   default_data = "?";
                   opaque_data = 
                   “<s>Symbian<i>0x101f5d07<e>.wav<h>RIFF????WAVE<m>Audio/Wave";
                   // SUPPLIER = Symbian
                   // MEDIA ID = uid for audio media type
                   // FILE EXTENSION = .wav
                   // HEADER DATA = look for RIFF????WAVE in header data. The’?’s
                   // indicate a single character wildcard.
                   // MIME TYPE = Audio/Wave
                   },
               IMPLEMENTATION_INFO
                   {
                   implementation_uid = 0x101F1237 ;
                   version_no = 1;
                   display_name = "AU Play Format";
                   default_data = "?";
                   opaque_data = 
                   “<s>Symbian<i>0x101f5d07<e>.au<h>.snd";
                   // SUPPLIER = Symbian
                   // MEDIA ID = uid for audio media type
                   // FILE EXTENSION = .au
                   // HEADER DATA = look for .snd in header data.
                   // MIME TYPE = No mime type
                   }
               };
            },
        INTERFACE_INFO // Record Formats Description
            {
            interface_uid = 0x101F0002 ;
            implementations = 
               {
               IMPLEMENTATION_INFO
                   {
                   implementation_uid = 0x101F1238 ;
                   version_no = 1;
                   display_name = "WAV Record Format";
                   default_data = "?";
                   opaque_data = 
                   “<s>Symbian<i>0x101f5d07<e>.wav<h>RIFF????WAVE<m>Audio/Wave";
                   // SUPPLIER = Symbian
                   // MEDIA ID = uid for audio media type
                   // FILE EXTENSION = .wav
                   // HEADER DATA = look for RIFF????WAVE in header data. The’?’s
                   // indicate a single character wildcard.
                   // MIME TYPE = Audio/Wave
                   }
                };
            }
        };
    }

Note the default_data field is not used by the controller framework. A UTF8 to unicode conversion is performed on the Supplier. All other data is left in ascii.

[Top]


Testing

The controller plugin should be tested by exercising any application-level utility APIs that are meant to be supported by the plugin. For example, a video player controller would be tested using the CVideoPlayer API.