This document explains 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 and write the
data to the appropriate sink.
Required Background
You need to be familiar with the Symbian platform Plugin Framework.
The following tasks will be covered in this tutorial:
Basic Procedure
The basic steps for writing a controller plugin are shown here:
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 ( |
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 ( |
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 ( |
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 |
|
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.
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.
Application-level Utility API |
Required Custom Command APIs |
|
|
|
|
CVideoPlayerUtility |
|
CVideoRecorderUtility |
|
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.
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
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.
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
.
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.