Writing a controller plugin

This document explains how to write a controller plugin.

Purpose

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 and write the data to the appropriate sink.

Required Background

You need to be familiar with the Symbian platform Plugin Framework.

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- specific or 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. It is then passed into the controller plugin via a call to CMMFController::AddDataSourceL() or CMMFController::AddDataSinkL() for a data source or a data sink respectively.

Note : The 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: 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

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

  • Open , this is the current state of the controller, after it has been created. Once the controller has been configured, it moves to the Stopped state. This transition typically involves adding the source(s) and sink(s) and configuring any utility objects or other plugins owned by the controller.

  • Stopped , in this state the controller should not have any resources or buffers allocated, and the position should be zero.

  • Primed , in moving to the primed state, the controller should allocate any buffers and resources that will be required to perform playback. The playback or recording position should also be stored when the controllere is in the primed state. Controllers should only be moved into this state just before play, otherwise unnecessary memory and resources will be used.

  • Playing , when in this state, the controller plugin should be transferring data between source(s) and sink(s).

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: The 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 sends 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: 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 some task that requires 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 or 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 the source or sink when calling the CMMFController::RemoveDataSourceL() or CMMFController::RemoveDataSinkL() methods.

Note: The 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.

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 (for example, CMdaAudioPlayerUtility ) 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.

In order to implement the required custom command APIs, the controller plugin should derive from the mixins shown in the table above, and 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.

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.

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: The use of data sources, data sinks and buffers is mandatory. The use of the other classes is optional.

  • Data Sources and Sinks

  • Buffers

  • Datapath

  • Codecs

  • Formats

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 listed below:

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 writing 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:

  • CMMFDataPath , in which case the data will be transferred from the source to the sink in the main thread of the controller plugin.

  • RMMFDataPath , in which case a new thread will be launched to transfer the data from the source to the sink. This is useful if a controller has multiple sources and sinks (a video controller for example). The multiple datapaths can each be executed in their own thread to improve performance.

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 writing 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 writing a format plugin for details of how to implement format plugins.

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 decides 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:

  • The display name and supplier of the controller plugin.

  • The media types supported by the controller plugin (e.g. audio or video).

  • The formats the controller plugin can play and record.

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

  • Display Name of the format.

  • Supplier of the format (in case support for this format was provided by a different party to the controller plugin).

  • The Media Types supported by the format.

  • The MIME types applicable to the format.

  • The file extensions that identify files that can be handled by the format.

  • Any header data segments that could be matched to the first few bytes of multimedia data to identify that the data could be handled using this format.

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:

  • Internally: In this case, the controller is able to read or write the format by itself. Controller plugins can specify the formats they support internally with extra entries in their plugin resource file. They define two new ECom plugin interface uids (one for play formats, the other for record formats) in their opaque_data field using the tags <p> and <r> . The play formats they support are then listed as ECom plugin implementations of the play format interface UID, and likewise with the record formats. These interface UIDs and implementations do not correspond to any real plugins. They are simply a way of letting the controller framework know exactly which formats the controller supports in a scalable manner. The implementation UIDs of each format should be known to the controller so that a client can specify the format that a controller should use by using this UID.

  • By using format plugins: The MMFServerBaseClasses component defines base classes for both encoding and decoding format plugins. By using format plugins, the formats supported by a controller plugin can be extended dynamically without having to change the controller plugin itself. The ECom plugin resource file of each format plugin contains the UID of the controller plugin that it extends, allowing the controller framework to build up an accurate picture of the formats supported by each controller.

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 .

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.