Creating a Source Plugin

A data source plugin needs to implement the pure virtual, and where appropriate override the virtual, MDataSource base class mixin functions. This section describes how to implement the MDataSource base class.

Note that a single plugin can be both a source and a sink, i.e. derive from both MDataSource and MDataSink.

Source Plugin Instantiation

A client application instantiates a source plugin using the RMMFController::AddDataSource() method, passing in the UID of the source as one of the method's parameters. The controller framework instantiates a source via the MDataSource::NewSourceL() method, rather than the more conventional NewL() method. This is because a plugin can be both a source and a sink of multimedia data, in which case the instantiation methods for sources and sinks need to be distinct. The MDataSource base class instantiation methods are described below, followed by information on how to write the instantiation methods in the derived class.

The derived data source needs to implement MDataSource::NewSourceL() and MDataSource::ConstructSourceL() for instantiation. The constructor of a data source plugin needs to specify what type of data source it is. This is achieved by passing a type UID into the MDataSource constructor.

If the source has additional methods, that are not part of the base MDataSource class, then a further layer of instantiation is required. For example, suppose CAcmeDataSource has some extra methods that are not part of the base class, then an additional mxin interface for this class is required. For example:

class MAcmeDataSource : public MDataSource
    {
public:
    inline static MAcmeDataSource* NewAcmeDataSourceL(TUid aImplemetationUid, const TDesC8& aInitData);
    //This allows dynamic linkage to the Class:    
    }

The NewAcmeDataSourceL should be implemented as follows:

MAcmeDataSource* retPtr = static_cast<MAcmeDataSource*>(MDataSource::NewSourceL(aImplementationUid, aInitData)); 

The class should derive from the MAcmeDataSource rather than MDataSource as follows:

class CAcmeDataSource: public CBase, public MAcmeDataSource
    {
public: 
    MDataSource* NewSourceL();

Source Plugin Buffer Creation

Buffers are required to transfer data between a data source and a data sink. These buffers can be created by the source and/or sink. The methods below are for source buffer creation.

The MDataSource::CanCreateSourceBuffer() method must be implemented by the data source plugin. Most sources should be capable of creating their own buffer and so would return ETrue. Note that just because a source can create a source buffer, this does not guarantee that the framework will use the buffer created by the source. The buffer that is used depends on factors such as whether a null codec is used and whether the sink is the reference buffer, in either of these cases the source buffer will not be used.

The MDataSource::CreateSourceBufferL() method is called by the framework to create a buffer from the source. This method should create a buffer of length zero bytes and a maximum length of an appropriate size for the source. The appropriate size is determined by source specifics, such as whether the source data ultimately comes from hardware that supplies buffers of a certain size. Generally a larger buffer size means a smaller number data transfers between the source and sink are required. The returned buffer must derive from CMMFBuffer but will be a derived buffer, for example CMMFDataBuffer or a video frame buffer.

The overloaded MDataSource::CreateSourceBufferL() method, which has an additional aSinkBuffer parameter, is optional. The default implementation is identical to the standard CreateSourceBufferL above. This version is used where the nature of the sink buffer may impact the created source buffer. This method should only be overridden if the size and/or type of the sink buffer can influence the size and/or type of the created source buffer.

Data Transfer

Data transfer methods are used by a datapath to perform the transfer of data between the source and the sink.

The MDataSource::FillBufferL() method is used to obtain data from the data source. It is a passive method in that an external component such as a datapath must ask the source to fill the buffer with data from the source. The MDataSource::FillBufferL() method may operate either synchronous and asynchronously:

  • A synchronous source is one in which the mechanism for filling the source buffer is synchronous. That is, the source fills the buffer and calls the BufferFilledL() method on the consumer of the dat, which is itself derived from MDataSink to indicate that the buffer has now been filled.

  • An asynchronous source is one in which the mechanism for filling the source buffer operates asynchronously. The MDataSource::FillBufferL() method will typically make an asynchronous request, for example via an active object, such that when the FillBufferL method has returned, the buffer has not yet been filled. The BufferFilledL() call back will occur some time later when the asynchronous request has been processed.

The MDataSink::BufferFilledL() method is the callback on a sink when the source has filled a buffer with source data. The source normally operates in a passive mode in that a sink of data will ask the source to fill a buffer via a call to MDataSource::FillBufferL() above. However, the sink that makes the FillBufferL() call on the source needs this callback to know when the buffer has been filled. This is applicable to a datapath which is both a sink and source of data. The actual sink, as seen by the controller, would normally operate passively and return KErrNotSupported for this procedure.

The MDataSink::EmptyBufferL() method is used to transfer data to the data sink. This is a passive method in that an external component, such as a datapath, must send the buffer with data to the sink. This method may operate either synchronously or asynchronously:

  • A synchronous sink is one in which the emptying of the sink buffer is synchronous. That is, the sink finishes processing the buffer and then calls the BufferEmptiedL() method on the data supplier.

  • An asynchronous sink is one in which the mechanism for emptying the sink buffer operates asynchronously. The MDataSink::EmptyBufferL() method will typically make an asynchronous request, for example via an active object, such that when the EmptyBufferL() method has returned, the buffer is not yet available for reuse. The BufferEmptiedL() call back should occur some time later when the asynchronous request has been processed.

Note that the 'Empty' in the method name does not imply that the sink really has to empty the buffer. The returned buffer does not have to contain no data and have a length of 0. The buffer passed back to the supplier by the BufferEmptiedL() could be the same buffer that was passed in with the EmptyBufferL() method, although it does not have to be. The buffer passed back is intended to be used as the next buffer passed into the subsequent call to EmptyBufferL().

The MDataSource::BufferEmptiedL() method is the callback on the source when the source asks a sink to empty a buffer that originated from the sink. The sink normally operates in a passive mode (will not ask the source to fill a buffer with data) in that a source of data will ask the sink to empty a buffer via a call to EmptyBufferL() above, but a source can ask a sink to empty a buffer by calling EmptyBufferL() on the sink. In which case the sink informs the source that it has finished with the buffer by calling the source's BufferEmptiedL() method. Sources which only support the passive mode of operation should return KErrNotSupported.

Source State Methods

The MDataSource mixin provides a number of state-related functions used to inform a source/sink that the data path (via the controller) has made a transition to a particular state. These state transition methods are usually called on the source/sink from the datapath and so will be called on the data source/sink plugin. These methods are not pure virtual and so it is not compulsory to implement them.

The MDataSource::SourceThreadLogon() method indicates to the source that it can perform any thread specific initialisation. This is so that the thread in which the data source is instantiated is not necessarily the same thread in which the actual transfer of data between the source and the sink takes place. It is only necessary to provide an implementation of MDataSource::SourceThreadLogon() if the source has thread specific initialisation and/or can generate events during data transfer.

The MAsyncEventHandler must also be passed into the source in the same thread in which the source is to transfer data. If the source can generate events during a data transfer, then it must keep the reference to event handler.

Implementation of the MDataSource::SourcePrimeL(), MDataSource::SourcePlayL(), MDataSource::SourcePauseL() and MDataSource::SourceStopL() methods is optional. They are called when a controller performs a transition to the corresponding state. For example, if the controller starts, or resumes, playing then MDataSource::SourcePlayL() is called.

Implementation of the MDataSource::SouceThreadLogoff() method is optional This method is called when the controller has finished with the data source and is called in the same thread as the data transfer. This allows the data source to perform any thread specific destruction such as the closure of handles.

Source Data Types

The MDataSource::SourceDataTypeCode() method must be implemented by the data source. It should return the data type of the source for the specified media ID. Some data sources may need their data type to be explicitly set, via the SetSourceDataTypeCode method or via negotiation with a data sink, in which case this function should either return a default FourCC code, or a NULL code, to indicate that the data type is not yet known.

Implementation of the MDataSource::SetSourceDataTypeCode() method is optional. It should be implemented where the source can support multiple data types.

Source Sink Negotiation

In many cases a data source will need to adjust its settings and data type according to the settings of the data sink. The opposite is also true in that a data sink may need to adjust its settings and data type according to the data source. This process is known as negotiation. An example of negotiation is where the source is an audio input recording to a clip of a certain data type. The source audio input (such as a microphone) attempts to match its settings to that required by the clip. For example, if the audio input supports the same data type as that required by the clip to be recorded to, then the negotiation should set the source audio input to the same data type and settings as the clip sink.

Implementation of the MDataSource::NegotiateSourceL() method is optional. It needs to be implemented by a data source only if the source needs to configure itself in accordance with the sink.

Implementation of the MDataSink::NegotiateL() method is optional. It only needs to be implemented by a data sink if the data sink needs to configure itself in accordance with the source.

Note that it is not always necessary to call both the source and sink negotiate functions. It is up to the controller to determine whether one, or both, are the most appropriate. The controller is also responsible for determining the sequence of the negotiate functions. For example, if an audio input data source is negotiating with a format sink such that the audio input needs to adjust it's settings to that of the sink, then there is need to perform this negotiation until the sink has been configured.

Source Custom Commands

The MDataSource::SourceCustomCommand() method facilitates the use of custom commands. An example implementation is shown below:

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

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

Use of the custom command mechanism is preferable to adding extra methods as it avoids extra casting as described is required for source/sink instantiation.

Source Priority Settings

The MDataSource::SetSourcePrioritySettings() method is optional. It is used to provide a mechanism to determine which source should have priority in cases where more than one client wishes to use the same physical source. An example might be an audio output, although several audio output sinks can be created, the actual hardware may only have one physical speaker. Therefore, if one audio output is being used to play music, and another is being used to play a ring tone due to an incoming call, then the latter needs to take precedence. TMMFPrioritySettings contains an iPriority TInt data member, where 100 is maximum priority, 0 is normal and -100 is minimum priority. The TMMFPrioritySettings::TMdaPriorityPreference and TMMFPrioritySettings::TMMFStateA data members provide further information which may be used if required. These specify whether the priority applies to recording or playing.