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:
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 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 plugin states, along with the commands to transition the plugin between the states, are:
Open
, this is the state in which the controller is
in just 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/recording position should also be stored when in
the primed state. Controllers should only be moved into this state just before
being told to 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 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 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.
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:
|
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.
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.
|
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.
|
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 that 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 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 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
.
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.
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.
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.
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:
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 following information 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:
|
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
.