// Copyright (c) 2002-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
// source\server\mmfdatapath.cpp
//
//
#include <e32math.h>
#include <mmf/common/mmffourcc.h>
#include <mmf/common/mmfpaniccodes.h>
#include <mmf/server/mmfaudiooutput.h>
#include <mmf/server/mmfaudioinput.h>
#include <mmf/server/mmfdatapath.h>
#include "mmfclientaudiostreamutils.h"
#include <mmf/common/mmfaudio.h>
#include <mmf/plugin/mmfcodecimplementationuids.hrh> // KUidMmfCodecAudioSettings
#include <mmf/server/devsoundstandardcustominterfaces.h>
const TUid KUidCodecAudioConfig = {KUidMmfCodecAudioSettings};
void Panic(TMMFDataPathPanicCode aPanicCode, TInt aSourceLineNumber)
{
_LIT(KMMFDataPathPanicCategory, "MMFDataPath");
User::Panic(KMMFDataPathPanicCategory, STATIC_CAST(TInt,aPanicCode) + aSourceLineNumber);
}
//all functions are exported form the DLL and are virtual to allow plugins to define there own CMMFDataPaths
/**
Allocates and constructs a data path.
Use this function if the codec UID is not already known by CMMFController
and there is no data path ambiguity - ie only one data path is possible.
Will create codec via fourCC.
@param aEventHandler
Installs an event handler to provide message passing between clients and sources/sinks.
@return Newly constructed data path object.
*/
EXPORT_C CMMFDataPath* CMMFDataPath::NewL(MAsyncEventHandler& aEventHandler)
{
CMMFDataPath* self = new(ELeave) CMMFDataPath(TMediaId(), aEventHandler);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop();
return self;
}
/**
Allocates and constructs a data path according to the specified media ID.
Use this function if the codec UID is not already known by CMMFController
and there is ambiguity with the data path ie. there is more than one possible data path.
@param aMediaId
Optional media ID parameter when there are multiple media types.
@param aEventHandler
Installs an event handler to provide message passing between clients and sources/sinks.
@return A newly constructed data path object.
*/
EXPORT_C CMMFDataPath* CMMFDataPath::NewL(TMediaId aMediaId, MAsyncEventHandler& aEventHandler)
{
CMMFDataPath* self = new(ELeave) CMMFDataPath(aMediaId, aEventHandler);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop();
return self;
}
/**
Allocates and constructs a data path according to the specified codec UID.
Use this function if the codec UID is already known by CMMFController
and there is no data path ambiguity ie. only one data path is possible
will create codec explicitly using the supplied codec Uid
@param aCodecUid
Optional mediaID parameter when there are multiple media types
@param aEventHandler
Installs an event handler to provide message passing between clients and sources/sinks.
@return A newly constructed data path object.
*/
EXPORT_C CMMFDataPath* CMMFDataPath::NewL(TUid aCodecUid, MAsyncEventHandler& aEventHandler)
{
CMMFDataPath* self = new(ELeave) CMMFDataPath(TMediaId(), aEventHandler);
CleanupStack::PushL(self);
self->ConstructL(aCodecUid);
CleanupStack::Pop();
return self;
}
/**
Allocates and constructs a data path according to the specified codec UID.
Use this function if the codec UID is already known by CMMFController
and there is ambiguity ie. more than one possible data path.
TMediaId used to select the path.
@param aCodecUid
The codec UID.
@param aMediaId
Optional mediaID parameter when there are multiple media types.
@param aEventHandler
Installs an event handler to provide message passing between clients and sources/sinks.
@return A newly constructed data path object.
*/
EXPORT_C CMMFDataPath* CMMFDataPath::NewL(TUid aCodecUid, TMediaId aMediaId, MAsyncEventHandler& aEventHandler)
{
CMMFDataPath* self = new(ELeave) CMMFDataPath(aMediaId, aEventHandler);
CleanupStack::PushL(self);
self->ConstructL(aCodecUid);
CleanupStack::Pop();
return self;
}
/**
Standard destructor.
*/
EXPORT_C CMMFDataPath::~CMMFDataPath()
{
Cancel();
delete iCodec;
DoCleanupBuffers();
//log off the source and sink
if (iDataSource)
iDataSource->SourceThreadLogoff();
if (iDataSink)
iDataSink->SinkThreadLogoff();
if (iCompleteCallback)
{
iCompleteCallback->Cancel();
delete iCompleteCallback;
}
}
/**
Deletes buffers if this datapath's sources and sinks own the buffers returned by PrimeL().
Typically if buffers are created asychronously, the datapath doesn't own the buffer
so leaves cleanup handling to the owner sources/sinks.
Called when source and sink needs to be de-referenced. Sets iDataPathCreated, iSinkCanReceive,
iSnkBufRef and iSrcBufRef to EFalse; sets iState to EStopped.
*/
EXPORT_C void CMMFDataPath::ResetL()
{
delete iCodec;
iCodec = NULL;
DoCleanupBuffers(); // Delete buffers
//logoff and dereference source and sink
if (iDataSource)
{ iDataSource->SourceThreadLogoff(); iDataSource = NULL; }
if (iDataSink)
{ iDataSink->SinkThreadLogoff(); iDataSink = NULL; }
// Reset states
iDataPathCreated = EFalse;
iState = EStopped;
iSrcBufRef = EFalse;
iSnkBufRef = EFalse;
iPauseCalled = EFalse;
delete iCompleteCallback; iCompleteCallback = NULL;
}
/**
Delete source and/or sink buffers that are owned by DataPath.
Ownership indicated by iSrcBufRef and iSnkBufRef.
Ownership is assigned during buffer allocation within the datapath PrimeL().
*/
void CMMFDataPath::DoCleanupBuffers()
{
// delete source and/or sink buffer that is owned by DataPath
if ( !iSrcBufRef && iSourceBuffer )
{
delete iSourceBuffer;
}
iSourceBuffer = NULL;
if ( !iSnkBufRef && iSinkBuffer )
{
delete iSinkBuffer;
}
iSinkBuffer = NULL;
}
/**
Obtain source and/or sink buffer using the synchronous API CreateSourceBufferL() and CreateSinkBufferL().
*/
void CMMFDataPath::ObtainSyncBuffersL()
{
//Try to create source and sink buffers. If we can't create them synchronously via
//CreateSourceBufferL and CreateSinkBufferL we will need to obtain them by
//asynchronous buffer creation when playing starts.
if (iBuffersToUse & ENeedSourceBuffer)
{
if (!iSourceBuffer) //we may already have a buffer from a previous initialization
{
TRAPD(err, iSourceBuffer = iDataSource->CreateSourceBufferL(iMediaId,*iSinkBuffer, iSrcBufRef));
if(err != KErrNone && err != KErrNotSupported)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::ObtainSyncBuffersL - Leaving %d (this 0x%x)\n"),err, this);
#endif
User::Leave(err);
}
}
}
if (iBuffersToUse & ENeedSinkBuffer)
{
if (!iSinkBuffer) //we may already have a buffer from a previous initialization
{
TRAPD(err, iSinkBuffer = iDataSink->CreateSinkBufferL(iMediaId, iSnkBufRef));
if(err != KErrNone && err != KErrNotSupported)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::ObtainSyncBuffersL - Leaving %d (this 0x%x)\n"),err, this);
#endif
User::Leave(err);
}
}
}
if (iSourceBuffer && !(iBuffersToUse & ENeedSinkBuffer))
{//only need one buffer, use source
iSinkBuffer =iSourceBuffer;
iSnkBufRef = ETrue; //the sink buffer is not to be deleted
}
else if (iSinkBuffer && !(iBuffersToUse & ENeedSourceBuffer))
{//only need one buffer, use sink
iSourceBuffer =iSinkBuffer;
iSrcBufRef = ETrue; //the sink buffer is not to be deleted
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::ObtainSyncBuffersL - DONE iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
}
/**
Constructs a source.
The default implementation leaves with KErrNotSupported.
@param aInitData
The initialisation data.
*/
EXPORT_C void CMMFDataPath::ConstructSourceL( const TDesC8& /*aInitData*/ )
{
User::Leave(KErrNotSupported);
}
/**
Constructs a sink.
Overridable constuction specific to this datasource.
The default implementation leaves with KErrNotSupported.
@param aInitData
The initialisation data.
*/
EXPORT_C void CMMFDataPath::ConstructSinkL( const TDesC8& /*aInitData*/ )
{
User::Leave(KErrNotSupported);
}
/**
Takes UID of codec on construction, and if not an NULL codec sets the datapath up for codec instantiation.
@param aCodecUid
The UID of the codec.
*/
EXPORT_C void CMMFDataPath::ConstructL(TUid aCodecUid)
{
iUseSuppliedCodecUid = EFalse; //initially assume no supplied codec uid
if (aCodecUid != KNullUid)
{//the data path NewL has specified a specific codec
//create CMMFCodec here
iCodec = CMMFCodec::NewL(aCodecUid);
if (iCodec)
iUseSuppliedCodecUid = ETrue;
}
iSrcBufRef = EFalse;
iSnkBufRef = EFalse;
iObtainingAsyncSourceBuffer = EFalse;
iObtainingAsyncSinkBuffer = EFalse;
iSourceBufferWithSource = EFalse;
iSinkBufferWithSink = EFalse;
}
/**
Adds a data source to the datapath and, if the sink already exists, tries to establish a connection
between the source and sink.
@param aSource
The data source to add to the data path.
*/
EXPORT_C void CMMFDataPath::AddDataSourceL(MDataSource* aSource)
{
if (!iDataSink) iDataSource=aSource; //can't create a data path without the MDataSink as well
else if (!iUseSuppliedCodecUid) //no supplied uid need to see if we can create codec to establish a data path
{//we have a data sink as well so check a data path can be established between source&sink
CreateDataPathL(aSource, iDataSink);
iDataSource = aSource;
}
else //the CMMFController specified the codec uid so must use existing codec
{//note we are assuming here that the CMMFController knows what it is doing ie the supplied codec uid
//can make the appropriate data conversion
iDataPathCreated = ETrue;
iDataSource = aSource;
}
ClearPlayWindowL() ;
User::LeaveIfError(iDataSource->SourceThreadLogon(*this));
}
/**
Adds a data sink to the datapath and, if the source already exists, tries to establish a connection
between the source and sink.
@param aSink
The data sink to add to the data path.
*/
EXPORT_C void CMMFDataPath::AddDataSinkL(MDataSink* aSink)
{
if (!iDataSource) iDataSink=aSink; //can't create a data path without the MDataSource as well
else if (!iUseSuppliedCodecUid) //no supplied uid need to see if we can create codec to establish a data path
{//we have a data source as well so check a media path can be established between source&sink
CreateDataPathL(iDataSource, aSink);
iDataSink = aSink;
}
else //the CMMFController specified the codec uid so must use existing codec
{//note we are assuming here that the CMMFController knows what it is doing ie the supplied codec uid
//can make the appropriate data conversion
iDataPathCreated = ETrue;
iDataSink = aSink;
//set 4CCs
iSourceFourCC = iDataSink->SinkDataTypeCode(iMediaId);//sink because CMMFDataPath is an MDataSink to its MDataSource!
iSinkFourCC = iDataSource->SourceDataTypeCode(iMediaId);//source because CMMFDataPath is an MDataSource to its MDataSink!
}
User::LeaveIfError(iDataSink->SinkThreadLogon(*this));
}
/*
* CreateDataPathL
*
* internal function to establish a datapath between the source and sink
* the data supplied by the sink adn expected by the source are checked and
* a codec is instantiated if necessary
*
* @param aSource
* @param aSink
*/
void CMMFDataPath::CreateDataPathL(MDataSource* aSource, MDataSink* aSink)
{ //procedure to attempt to match the source to the sink creating a codec if necessary
// returns ETrue if the datapath could be constructed else false
//sets iCodec to the appropriate codec.& sets its own iSink/iSource FourCC datatype codes
iDataPathCreated = EFalse;
if (aSource && aSink) //have a source and sink
{ //we have a data source & sink but no codec so try and find one - if required
TFourCC sourceFourCCCode = aSource->SourceDataTypeCode(iMediaId); //get MDataSource data type fourCC code
TFourCC sinkFourCCCode = aSink->SinkDataTypeCode(iMediaId); //get MDataSink data type fourCC code
if ((sourceFourCCCode != sinkFourCCCode) && //MDataSource & MDataSink datatypes are not compatible
(sourceFourCCCode != KMMFFourCCCodeNULL) && (sinkFourCCCode != KMMFFourCCCodeNULL))
{//we need a codec to make the conversion between the source and the sink
CMMFCodec* codec = CMMFCodec::NewL(sourceFourCCCode, sinkFourCCCode);
if (codec)
{
delete iCodec;
iCodec = codec;
//data path created ie have source/sink and can match their datatypes
iDataPathCreated = ETrue;
//now we have an source attached we need to configure the codec for sample rate
//and number of channels
//prepare a package to send to a codec
TMMFAudioConfig audioSettings;
//test for decoder
if (aSource->DataSourceType() == KUidMmfFormatDecode)
{
audioSettings.iSampleRate = static_cast<CMMFFormatDecode*>(aSource)->SampleRate();
audioSettings.iChannels = static_cast<CMMFFormatDecode*>(aSource)->NumChannels();
}
//package up to send to codec
TPckgBuf<TMMFAudioConfig> configPackage(audioSettings);
//we need to catch User::Leave(KErrNotSupported) as by default most codecs
//do not support the ConfigureL method.
TRAPD(err,iCodec->ConfigureL(KUidCodecAudioConfig, configPackage));
// need to check other error here
if (err != KErrNone && err != KErrNotSupported)
{
User::Leave(err);
}
}
else
{
User::Leave( KErrNotSupported ) ; //couldn't find suitable codec
}
} //if (sourceFourCCCode != sinkFourCCCode)
else
{ //source & sink fourCC datatypes are the same so no codec is required
__ASSERT_DEBUG(iCodec == NULL, Panic(EMMFDataPathPanicProgrammingError,__LINE__));
iDataPathCreated = ETrue;
}
//can assign FourCC codes for the CMMFDataPath
iSinkFourCC = sourceFourCCCode; //sink because CMMFDataPath is an MDataSink to its MDataSource!
iSourceFourCC = sinkFourCCCode; //source because CMMFDataPath is an MDataSource to its MDataSink!
//If sink & source need its own Prime() done in controller
}
}
/**
Clears the specified buffer.
Pure virtual dummy implementation, not needed by datapath
comes from MDataSink - CMMFData path is a sink to its MDataSource.
This is only required for an active push MDataSource requesting a buffer empty.
@param aBuffer
The buffer to empty.
@param aSupplier
The MDataSource supplying this buffer.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving of different media types.
*/
EXPORT_C void CMMFDataPath::EmptyBufferL(CMMFBuffer* /* aBuffer */, MDataSource* /*aSupplier*/, TMediaId /*aMediaId*/)
{
//not implemented
}
/*
* FillSourceBufferL
*
* Function to get data from the datapath's iDataSource
*/
void CMMFDataPath::FillSourceBufferL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSourceBufferL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording), Panic(EMMFDataPathPanicBadState,__LINE__));
// clear the no-more-source flag here (as well as in PlayL()) because
// there may have been a re-position since the last call to BufferFilledL()
iNoMoreSourceData = EFalse;
if(!iObtainingAsyncSourceBuffer)
{//this is a normal request for data.
//If we are getting asynchronous buffers, then can't do this as iSourceBuffer == NULL
iSourceBuffer->SetFrameNumber(++iCurrentSourceFrameNumber); //so source knows which data to load buffer with
iSourceBuffer->SetStatus(EBeingFilled);
iSourceBuffer->SetLastBuffer(EFalse);
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP asking for buffer %d - ptr=0x%x (this 0x%x)\n"), iCurrentSourceFrameNumber, iSourceBuffer,this);
#endif
iSourceBufferWithSource = ETrue;
// wait for BufferFilled callback from source. Do this here as some sources cause
//re-entrancy into data path via BufferFilledL
ChangeDataPathTransferState(EWaitSource);
iDataSource->FillBufferL(iSourceBuffer, this, iMediaId);
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSourceBufferL - DONE tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Indicates the data source has filled the specified buffer.
Called by the CMMFDataPath's MDataSource when it has filled the buffer.
@param aBuffer
A pointer to the filled buffer.
*/
EXPORT_C void CMMFDataPath::BufferFilledL(CMMFBuffer* aBuffer)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL src has filled buffer %d (ptr=0x%x) with %d bytes EoF = %d tick-%d (this 0x%x)\n"),aBuffer->FrameNumber(),aBuffer, aBuffer->BufferSize(),aBuffer->LastBuffer(), User::TickCount(),this);
#endif
//This assertion is commented because of PDEF117405
//state only used if we are passing data
//__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording), Panic(EMMFDataPathPanicBadState,__LINE__));
__ASSERT_DEBUG((!iNoMoreSourceData), Panic(EMMFDataPathPanicBadState,__LINE__));
iSourceBufferWithSource = EFalse;
//Has the datapath stopped running, if so were not interested in any callbacks.
if(iState == EStopped || iState == EPrimed || (iPauseCalled && iState != ERecording))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL called while not expecting callback iState=%d iPauseCalled=%d (this 0x%x)\n"),iState, iPauseCalled,this);
#endif
return;
}
#ifdef REPOSITION_SPEEDUP
// if the source has been re-positioned, then go & get some more source data now
if (!iObtainingAsyncSourceBuffer && iSourceBuffer->FrameNumber() != iCurrentSourceFrameNumber)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL source was re-positioned re-requesting source data (this 0x%x)\n"),this);
#endif
ChangeDataPathTransferState(ENeedSourceData);
return;
}
#endif //REPOSITION_SPEEDUP
//bufer is NULL, indicating no more source data.
if (!aBuffer)
{
//If we only hold a reference to the source buffer, set that to NULL
if(iSnkBufRef)
iSourceBuffer = NULL;
iNoMoreSourceData = ETrue;
if(!iCodec || //there's only one buffer and that has been returned as NULL, so must be end of data
iSinkBufferWithSink) //buffer is with sink, we don't have any more data to put in it, so must be end of data
{
ChangeDataPathTransferState(EEndOfData);
}
else //sink buffer is with datapath, see if there is anything to send to sink
ChangeDataPathTransferState(ENeedToMatchSourceToSink);
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL DONE aBuffer==NULL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
return;
}
//We were waiting for a response from the source to get an asynchronous buffer.
//We now have it, and we proceed to transfer this data to the sink.
if (iObtainingAsyncSourceBuffer)
{
iObtainingAsyncSourceBuffer = EFalse;
}
aBuffer->SetStatus(EFull);
if(iSourceBuffer != aBuffer)
{//buffer has been changed by the source
iSourceBuffer = aBuffer;
if (!(iBuffersToUse & ENeedSinkBuffer))
{//we only need one buffer and use source
iSinkBuffer = iSourceBuffer;
iSnkBufRef = ETrue;
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL - iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
}
//Is this the last buffer from the source (0 length or LastBuffer flag set)
//or have reached the end of the play window; we only look at the play window here
//if we are converting. For conversion we look at the data we have read. This is then passed onto
//the source
if (!iSourceBuffer->BufferSize() || iSourceBuffer->LastBuffer() ||
(((iState == EConverting) || (iState == EPlaying)) && (iPlayWindowEndPosition < iCachedSourceDuration) && ( InputPosition() >= iPlayWindowEndPosition )))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL end of input data tick-%d (this 0x%x)\n"),User::TickCount(),this);
RDebug::Print(_L("iSourceBuffer->BufferSize()=%d\n"),iSourceBuffer->BufferSize());
RDebug::Print(_L("iSourceBuffer->LastBuffer()=%d\n"),iSourceBuffer->LastBuffer());
RDebug::Print(_L("InputPosition()=%d >= iPlayWindowEndPosition=%d\n"),I64INT(InputPosition().Int64()),I64INT(iPlayWindowEndPosition.Int64()));
#endif
iNoMoreSourceData = ETrue;
iSourceBuffer->SetLastBuffer(ETrue); //just in-case we are terminating on BufferSize == 0 or play window
}
if (!iCodec)
ChangeDataPathTransferState(ESendDataToSink);
else if(!iSinkBufferWithSink) //sink buffer is with data path, can try to fill it
ChangeDataPathTransferState(ENeedToMatchSourceToSink);
//else wait for sink to return buffer BufferEmptied will send us into ENeedToMatchSourceToSink state
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferFilledL - DONE tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/*
* FillSinkBufferL
*
* Function to take the data from an already full source buffer and by using
* a codec if necessary fills the sink buffer
*/
void CMMFDataPath::FillSinkBufferL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
//This state is only used if we are passing data
__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording), Panic(EMMFDataPathPanicBadState,__LINE__));
//This state is only used if we have a real codec
__ASSERT_DEBUG(iCodec, Panic(EMMFDataPathPanicBadState,__LINE__));
//The sink buffer is with the sink so we can't fill it.
//When it has been emptied, this state will be re-entered from BufferEmptiedL
if(iSinkBufferWithSink)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL buffer is in use by SINK - DONE (this 0x%x)\n"),this);
#endif
ChangeDataPathTransferState(EWaitSink); // wait for BufferEmptied callback from sink
return;
}
//The source buffer is with the source so we can't take data from it.
//When it has been filled, this state will be re-entered from BufferFilledL
if(iSourceBufferWithSource)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL buffer is in use by SOURCE - DONE (this 0x%x)\n"),this);
#endif
ChangeDataPathTransferState(EWaitSource); // wait for BufferFilled callback from source
return;
}
//source buffer is NULL, can't be any more data to send.
//iNoMoreSourceData is set and the source buffer is empty, can't be any more data to send.
if(!iSourceBuffer || (iNoMoreSourceData && !iSourceBuffer->BufferSize()))
{
if(iSinkBuffer->Status() == EBeingFilled)
{//if we have data in sink buffer, mark it as last buffer and send
iSinkBuffer->SetLastBuffer(ETrue);
ChangeDataPathTransferState(ESendDataToSink);
}
else //the sink buffer can't have anything in it
ChangeDataPathTransferState(EEndOfData);
}
#ifdef REPOSITION_SPEEDUP
// if the source has been re-positioned,
// speed things up by getting some more source data now
if(iSourceBuffer && iSourceBuffer->FrameNumber() != iCurrentSourceFrameNumber)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL() source was re-positioned re-requesting source data (this 0x%x)\n"),this);
#endif
ChangeDataPathTransferState(ENeedSourceData);
return;
}
#endif //REPOSITION_SPEEDUP
iSinkBuffer->SetStatus(EBeingFilled);
iSinkBuffer->SetLastBuffer(EFalse);
//pass buffer to codec for processing
iCodecProcessResult = iCodec->ProcessL(*iSourceBuffer, *iSinkBuffer);
//the codec tries to fill the sink buffer to its max length
//TCodecProcessResult returns the status of the codec Process -
//this can result in result conditions such as:
//EProcessComplete - the codec processed all the source data into the sink buffer
//EProcessIncomplete - the codec filled sink buffer before all the source buffer was processed
//EDstNotFilled - the codec processed the source buffer but the sink buffer was not filled
//EEndOfData - the codec detected the end data - all source data in processed but sink may not be full
//EProcessError - the codec process error condition
switch (iCodecProcessResult.iStatus)
{
case TCodecProcessResult::EProcessComplete:
//finished procesing source data - all data in sink buffer
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::FillSinkBufferL codec EProcessComplete (this 0x%x)\n"),this);
#endif
iSourceBuffer->SetStatus(EAvailable); //source buffer is now avaialble
iSinkBuffer->SetStatus(EFull); //sink buffer is full
if (iNoMoreSourceData)
iSinkBuffer->SetLastBuffer(ETrue);
ChangeDataPathTransferState(ESendDataToSink);// the full sink buffer needs to be sent to the sink
}
break;
case TCodecProcessResult::EProcessIncomplete:
// the sink was filled before all the src was processed
// therefore still send everything to sink
//but datapath needs to carry on processing the source buffer before it gets more source data
//when sink has emptied data path needs to send rest of data
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::FillSinkBufferL codec EProcessIncomplete (this 0x%x)\n"),this);
#endif
TUint sourceBufferPosition = iCodecProcessResult.iSrcBytesProcessed + iSourceBuffer->Position();
iSourceBuffer->SetPosition(sourceBufferPosition);//update source buffer position
iSinkBuffer->SetStatus(EFull); //sink & source buffers are both full
ChangeDataPathTransferState(ESendDataToSink); // the full sink buffer needs to be sent to the sink
}
break;
case TCodecProcessResult::EDstNotFilled:
// the destination is not full
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::FillSinkBufferL codec EDstNotFilled (this 0x%x)\n"),this);
#endif
iSourceBuffer->SetStatus(EAvailable); //source buffer is now available
TUint sinkBufferPosition = iCodecProcessResult.iDstBytesAdded + iSinkBuffer->Position();
iSinkBuffer->SetPosition(sinkBufferPosition);//update sink buffer position (still EBeingFilled)
// if this was the last source buffer, send what we've got (if anything)
// to the sink... EmptySinkBuffer() should then enter EEndOfData state
if (iNoMoreSourceData)
{
iSinkBuffer->SetLastBuffer(ETrue);
ChangeDataPathTransferState(ESendDataToSink);//send what we've got to the sink -
}
else
{
ChangeDataPathTransferState(ENeedSourceData); //need to get more source data to fill sink buffer
}
}
break;
case TCodecProcessResult::EEndOfData:
//no more data - send what we've got to the sink
//note we can't always rely on this - in many cases the codec will not know when
//it has reached the end of data.
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::FillSinkBufferL codec EEndOfData (this 0x%x)\n"),this);
#endif
iSourceBuffer->SetStatus(EAvailable); //source buffer is now avaialble
iSinkBuffer->SetStatus(EFull);//sink buffer may not really be 'full' but its as full as it going to get
//This only occurs where the codec can detect the end of data, but the source can't
iNoMoreSourceData=ETrue;
iSinkBuffer->SetLastBuffer(ETrue);
ChangeDataPathTransferState(ESendDataToSink);//send what we've got to the sink -
//doesn't matter if sink buffer is not full
}
break;
case TCodecProcessResult::EProcessError:
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL tick-%d DONE %d (this 0x%x)\n"),User::TickCount(), __LINE__,this);
#endif
User::Leave(KErrCorrupt); //codec process error
break;
default:
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL tick-%d DONE %d (this 0x%x)\n"),User::TickCount(), __LINE__,this);
#endif
User::Leave(KErrCorrupt); //should never get here
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::FillSinkBufferL - done tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Tests whether the data path can create a sink buffer.
The default implementation returns false.
@return ETrue if the data path can create a sink buffer. EFalse if the data path cannot create a sink buffer.
*/
EXPORT_C TBool CMMFDataPath::CanCreateSinkBuffer()
{
return NULL; //CMMFDataPath cannot create buffer
}
/**
Creates a sink buffer according to the specifed media ID.
Intended for synchronous usage (buffers supplied by datapath for an MDataSink).
This method is essentially a dummy implementation of an MDataSink pure virtual.
The default implementation returns NULL.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving of different media types.
@return Returns NULL in this instance as datapath can't create sink buffers
*/
EXPORT_C CMMFBuffer* CMMFDataPath::CreateSinkBufferL(TMediaId /*aMediaId*/)
{//CMMFDataPath can't create buffers
return NULL;
}
/**
Creates a sink buffer according to the specifed media ID and reference.
Intended for asynchronous usage (buffers supplied by Devsound device).
This method is essentially a dummy implementation of an MDataSink pure virtual.
The default implementation returns NULL.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving for different media types.
@param aReference
A boolean indicating buffer ownership.
@return Returns NULL in this instance as datapath can't create sink buffers.
*/
EXPORT_C CMMFBuffer* CMMFDataPath::CreateSinkBufferL(TMediaId /*aMediaId*/, TBool& /*aReference*/)
{//CMMFDataPath can't create buffers
return NULL;
}
/**
Gets the sink's data type for the specified media ID.
@param aMediaId
An optional parameter to specifiy the specific stream when datasource contains more than one stream of data
@return The sink's data type.
*/
EXPORT_C TFourCC CMMFDataPath::SinkDataTypeCode(TMediaId /*aMediaId*/)
{
return(iSinkFourCC);
}
/**
Fills the specified buffer.
Pure virtual dummy implementation, not needed by datapath
comes from MDataSink - CMMFData path is a source to its MDataSink
Only required for an active pull MDataSink requesting a buffer fill. The default implementation is empty.
@param aBuffer
The buffer to fill.
@param aConsumer
The MDataSink supplying this buffer.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving of different media types
*/
EXPORT_C void CMMFDataPath::FillBufferL(CMMFBuffer* /*aBuffer*/, MDataSink* /*aConsumer*/, TMediaId /*aMediaId*/)
{
//not implementated
}
void CMMFDataPath::SetBuffersAvailable()
{
// set source buffer to be available
if (iSourceBuffer)
iSourceBuffer->SetStatus(EAvailable);
// set sink buffer to be available
if (iSinkBuffer)
iSinkBuffer->SetStatus(EAvailable);
}
void CMMFDataPath::ResetRefBuffers()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::ResetRefBuffers iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
// Reset the buffer pointers to NULL if they are supplied by DevSound
// We do this because buffers that are not owned by the datapath may not be valid any more.
if (iSrcBufRef)
{
iSourceBuffer = NULL;
}
if (iSnkBufRef)
{
iSinkBuffer = NULL;
}
}
TInt CMMFDataPath::DetermineBuffersToUseL(void) const
{
TInt buffs = ENoBuffers;
if(iCodec)
{//Using a real Codec, need both sets of buffers
if(!iDataSink->CanCreateSinkBuffer() || ! iDataSource->CanCreateSourceBuffer())
User::Leave(KErrNotSupported);
buffs = CMMFDataPath::ENeedSinkBuffer | CMMFDataPath::ENeedSourceBuffer;
}
else //we are using a Null Codec, only need one buffer, but which one?
{//use buffer from DevSound, if no DevSound (ie, clip to clip), prefer source buffer.
//If preferring source but it can't create buffers, use sink.
if ((iDataSink->DataSinkType() == KUidMmfAudioOutput) && (iDataSink->CanCreateSinkBuffer()))
buffs = ENeedSinkBuffer;
else if(iDataSource->CanCreateSourceBuffer())
buffs = ENeedSourceBuffer;
else if(iDataSink->CanCreateSinkBuffer())
buffs = ENeedSinkBuffer;
else
User::Leave(KErrNotSupported);
}
return buffs;
}
/*
* InitializeSinkL
*
* Function to initialize iDataSink before it can start sending data
* This is a one time prime. This will synchronize DataPath's data driving
* mechanism with HwDevice implementation.
*
* This initialisation method detects its attached sources and sinks and makes adjustments to the state machine
* This avoid the possibility of NULL buffers (not yet returned by an asychronous sink or source) being passed around the MMF
*/
void CMMFDataPath::InitializeSinkL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InitializeSinkL iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
//state only used if we are passing data
__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording), Panic(EMMFDataPathPanicBadState,__LINE__));
iObtainingAsyncSinkBuffer = EFalse;
if (iBuffersToUse & ENeedSinkBuffer)
{
//Buffers are initially created in the Prime method. But following a pause, we must re-create
//any referenced buffers, so try direct creation.
//NB: this does mean we are trying this twice, Prime and here
if (!iSinkBuffer) //we may already have a buffer from a previous initialization
{
TRAPD(err, iSinkBuffer = iDataSink->CreateSinkBufferL(iMediaId, iSnkBufRef));
if(err != KErrNone && err != KErrNotSupported)
User::Leave(err);
}
//If buffer has not been supplied via CreateSinkBufferL,
//must use asynchronous buffer creation
if (!iSinkBuffer)
{
iObtainingAsyncSinkBuffer = ETrue;
ChangeDataPathTransferState(EWaitSink); // wait for BufferEmptied callback from sink
iDataSink->EmptyBufferL(iSinkBuffer, this, iMediaId);
}
else
{
//we have a sink buffer from CreateSinkBufferL
iSinkBuffer->SetStatus(EAvailable);
if (iBuffersToUse & ENeedSourceBuffer)
{//need a source buffer, go get it
ChangeDataPathTransferState(EInitializeSource);
}
else
{//only need one buffer, use sink
iSourceBuffer = iSinkBuffer;
iSrcBufRef = ETrue; //the src buffer is not to be deleted
ChangeDataPathTransferState(ENeedSourceData); //got all buffers, start getting data
}
}
}
else
{//don't need a sink buffer, but we need a source one
ChangeDataPathTransferState(EInitializeSource);
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InitializeSinkL - DONE iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
}
/*
* InitializeSourceL
*
* Function to initialize iDataSource before it can start sending data
* This is a one time prime. This will synchronize DataPath's data driving
* mechanism with HwDevice implementation.
*
* This initialisation method detects its attached sources and sinks and makes adjustments to the state machine
* This avoid the possibility of NULL buffers (not yet returned by an asychronous sink or source) being passed around the MMF
*/
void CMMFDataPath::InitializeSourceL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InitializeSourceL - iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
//state only used if we are passing data
__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording), Panic(EMMFDataPathPanicBadState,__LINE__));
iObtainingAsyncSourceBuffer = EFalse;
if (iBuffersToUse & ENeedSourceBuffer)
{
//Buffers are initially created in the Prime method. But following a pause, we must re-create
//any referenced buffers, so try direct creation.
//NB: this does mean we are trying this twice, Prime and here.
if (!iSourceBuffer) //we may already have a buffer from a previous initialization
{
TRAPD(err, iSourceBuffer = iDataSource->CreateSourceBufferL(iMediaId,*iSinkBuffer, iSrcBufRef));
if(err != KErrNone && err != KErrNotSupported)
User::Leave(err);
}
//If buffer has not been supplied via CreateSourceBufferL
//must use asynchronous buffer creation
if (!iSourceBuffer)
{
iObtainingAsyncSourceBuffer = ETrue;
ChangeDataPathTransferState(ENeedSourceData);
}
else
{//we have a source buffer from CreateSourceBufferL
iSourceBuffer->SetStatus(EAvailable);
if (!(iBuffersToUse & ENeedSinkBuffer))
{//only need one buffer, use sink
iSinkBuffer = iSourceBuffer;
iSnkBufRef = ETrue;
}
ChangeDataPathTransferState(ENeedSourceData); //got all buffers, start getting data
}
}
else
{//don't need a source buffer, use sinks
if(iSinkBuffer)
{
iSourceBuffer = iSinkBuffer;
iSrcBufRef = iSnkBufRef;
SetBuffersAvailable();
}
#ifdef _DP_DEBUG
else
Panic(EMMFDataPathPanicProgrammingError,__LINE__);
#endif
ChangeDataPathTransferState(ENeedSourceData); //got all buffers, start getting data
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InitializeSourceL - iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
}
}
/*
* EmptySinkBufferL
*
* Function to pass a full databuffer to the iDataSink
*/
void CMMFDataPath::EmptySinkBufferL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::EmptySinkBufferL pass data to sink tick-%d (this 0x%x)\n"),User::TickCount(),this);
if(iSinkBuffer)
RDebug::Print(_L("iSinkBuffer %d contains %d bytes eof = %d line %d (this 0x%x)\n"),iSinkBuffer->FrameNumber(), iSinkBuffer->BufferSize(),iSinkBuffer->LastBuffer(),__LINE__,this);
#endif
//Before emptying the sink buffer we need to check it has data to empty - this
//may not be the case if there is no more data ie iNoMoreSourceData is true.
//In this case we need to check to see if there is any data left in the sink
//buffer, ie the sink buffer is either full or being filled. If there is not any
//data in the sink buffer, ie it is not in the state of EBeingFilled or EFull
//then there is nothing to empty so the datapath state is set to EEndOfData and
//we return from the procedure.
//state only used if we are passing data
__ASSERT_DEBUG((iState == EPlaying || iState == EConverting || iState == ERecording || (iState == EPrimed && iPauseCalled)), Panic(EMMFDataPathPanicBadState,__LINE__));
__ASSERT_DEBUG(iSinkBuffer &&
((iSinkBuffer->Status()==EBeingFilled) || (iSinkBuffer->Status()==EFull)),
Panic(EMMFDataPathPanicProgrammingError,__LINE__));
__ASSERT_DEBUG(iSinkBufferWithSink == EFalse, Panic(EMMFDataPathPanicBadState,__LINE__));
//Due to sinks that may call BuferEmptied directly (ie. re-entrancy onto DataPath) we
//must work out next state here. If re-entrancy, the next state may validly get overwritten
// in BuferEmptied.
if(iObtainingAsyncSinkBuffer) //wait for buffer to be returned in BufferEmptied
{
ChangeDataPathTransferState(EWaitSink); // wait for BufferEmptied callback from sink
}
#ifdef REPOSITION_SPEEDUP
// if the source has been re-positioned,
// speed things up by getting some more source data now
if(iSourceBuffer && iSourceBuffer->FrameNumber() != iCurrentSourceFrameNumber)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::EmptySinkBufferL() source was re-positioned re-requesting source data (this 0x%x)\n"),this);
#endif
ChangeDataPathTransferState(ENeedSourceData);
return;
}
#endif //REPOSITION_SPEEDUP
//We have sent data to sink, if we are using a real Codec, we can now get more data from source
//if there is any more to get and the codec has emptied it.
//NB: No need to check we own the source buffer as we will no be in this state
//if we have asked for more source data and haven't received it.
else if (iCodec && !iNoMoreSourceData && (iSourceBuffer->Status() == EAvailable))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("ASKING for more source data iCodec = 0x%x iNoMoreSourceData=%d iSourceBufferWithSource = %d (this 0x%x)\n"), iCodec, iNoMoreSourceData, iSourceBufferWithSource,this);
#endif
ChangeDataPathTransferState(ENeedSourceData);
}
else
{
#ifdef _DP_DEBUG
RDebug::Print(_L("Not asking for any more source data iCodec = 0x%x iNoMoreSourceData=%d iSourceBufferWithSource = %d iSourceBuffer->Status=%d (this 0x%x)\n"), iCodec, iNoMoreSourceData, iSourceBufferWithSource ,iSourceBuffer->Status(), this);
#endif
//if this is the last buffer, set this flag so we can deal with KErrUnderflow
//as a valid termination of playing
if(iSinkBuffer->LastBuffer())
iAllDataSentToSink=ETrue;
ChangeDataPathTransferState(EWaitSink); // wait for BufferEmptied callback from sink
}
if(!iObtainingAsyncSinkBuffer) //normal data transfer
iSinkBuffer->SetFrameNumber(++iCurrentSinkFrameNumber);
#ifdef _DP_DEBUG
RDebug::Print(_L("DP sending buffer %d ptr=0x%x of %d bytes to sink (this 0x%x)\n"), iSinkBuffer->FrameNumber(),iSinkBuffer,iSinkBuffer->BufferSize(),this);
#endif
iSinkBufferWithSink = ETrue;
TRAPD(error, iDataSink->EmptyBufferL(iSinkBuffer, this, iMediaId));
// Check that we haven't exceeded the maximum clip length - if so, go to the EndOfData state
// so we perform necessary cleanup.
if (error == KErrEof || error == KErrOverflow || error == KErrUnderflow)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::EmptySinkBufferL DONE %d error = %d tick-%d (this 0x%x)\n"),__LINE__, error, User::TickCount(),this);
#endif
iDataPathCompletedErrorCode = error;
ChangeDataPathTransferState(EEndOfData);
return;
}
User::LeaveIfError(error);
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::EmptySinkBufferL - done tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Indicates the data sink has emptied the buffer.
Called by the CMMFDataPath's MDataSink when it has emptied the buffer
@param aBuffer
The emptied buffer.
*/
EXPORT_C void CMMFDataPath::BufferEmptiedL(CMMFBuffer* aBuffer)
{
#ifdef _DP_DEBUG
TInt bufNum = 9999;
if(aBuffer)
bufNum = aBuffer->FrameNumber();
else
RDebug::Print(_L("DP::BufferEmptiedL returned NULL (this 0x%x)\n"),this);
RDebug::Print(_L("DP::BufferEmptiedL sink has taken buffer %d (ptr=0x%x) bytes %d eof= %d iNoMoreSourceData = %d tick-%d (this 0x%x)\n"),
bufNum, aBuffer, aBuffer->BufferSize(),aBuffer->LastBuffer(), iNoMoreSourceData, User::TickCount(),this);
#endif
iSinkBufferWithSink = EFalse;
//Has the datapath stopped running, if so were not interested in any callbacks.
if(iState == EStopped || (iState == EPrimed && !iPauseCalled))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferEmptiedL called while not expecting callback iState=%d iPauseCalled=%d (this 0x%x)\n"),iState, iPauseCalled,this);
#endif
return;
}
// This will allow MDataSink to send dynamic buffer to DataPath with each BufferEmptiedL request.
if (iSinkBuffer != aBuffer) //buffer has been updated
{
iSinkBuffer = aBuffer;
if (!(iBuffersToUse & ENeedSourceBuffer))
{ //can use a single buffer
iSourceBuffer = iSinkBuffer;
iSrcBufRef = iSnkBufRef;
}
}
iSinkBuffer->SetStatus(EAvailable);
if (iObtainingAsyncSinkBuffer) //we are creating an asynchronous sink buffer
{
iObtainingAsyncSinkBuffer = EFalse;
//we have a sink buffer, should this also be used by the source
if (!(iBuffersToUse & ENeedSourceBuffer))
{//using a single buffer, so start getting data
iSourceBuffer = iSinkBuffer;
iSrcBufRef = iSnkBufRef;
ChangeDataPathTransferState(ENeedSourceData);
}
else //obtain a separate source buffer
ChangeDataPathTransferState(EInitializeSource);
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferEmptiedL - DONE iSourceBuffer=0x%x ref=%d iSinkBuffer=0x%x ref=%d (this 0x%x)\n"),iSourceBuffer,iSrcBufRef,iSinkBuffer,iSnkBufRef, this);
#endif
return;
}
if(!iCodec) //No Codec in use
{
if(iNoMoreSourceData)
ChangeDataPathTransferState(EEndOfData);//final buffer returned from sink
else
ChangeDataPathTransferState(ENeedSourceData);//get more data from source
}
else //real codecs
{
//There is more source data and src buffer is being filled or we are about to go into
// ENeedSourceData state to fill it, so wait for source data to arrive.
//NB:if there was more source data and we are using a real codec and source buffer was empty,
//more source data would have been requested in EmptySinkBuffer
if(!iNoMoreSourceData && (iSourceBufferWithSource || iTransferState == ENeedSourceData))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferEmptiedL - waiting for more source data - DONE tick-%d line %d (this 0x%x)\n"),User::TickCount(),__LINE__,this);
#endif
return;
}
//source has supplied a NULL buffer or it has been emptied; no more data to send.
if(!iSourceBuffer || (iSourceBuffer->Status() == EAvailable))
ChangeDataPathTransferState(EEndOfData);
else if(iSourceBuffer->Status() == EFull) //there is data in the source buffer, go and get it
ChangeDataPathTransferState(ENeedToMatchSourceToSink);
#ifdef _DP_DEBUG
else
Panic(EMMFDataPathPanicProgrammingError,__LINE__);
#endif
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::BufferEmptiedL - DONE tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Tests whether the data path can create a source buffer.
Would expect datapath to always return NULL, so this is a default implementation of a pure virtual from MDataSink.
The default implementation returns EFalse.
@return ETrue if the data path can create a source buffer. EFalse if the data path cannot create a source buffer.
*/
EXPORT_C TBool CMMFDataPath::CanCreateSourceBuffer() //from both MDataSource & MDataSink?
{
return EFalse; //CMMFDataPath cannot create buffer
}
/**
Creates a source buffer.
Intended for synchronous usage (buffers supplied by datapath for a MDataSource)
This method is essentially a dummy implementation of an MDataSource pure virtual.
The default implementation leaves with KErrNotSupported and returns NULL.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving of different media types.
@return A pointer to a newly created buffer. Returns NULL in this instance as datapath can't create source buffers
*/
EXPORT_C CMMFBuffer* CMMFDataPath::CreateSourceBufferL(TMediaId /*aMediaId*/) //CMMFDataPath can't create buffers
{
User::Leave(KErrNotSupported);
return NULL;
}
/**
Creates a source buffer according to the specifed media ID and reference.
Intended for asynchronous usage (buffers supplied by datapath for a MDataSource)
This method is essentially a dummy implementation of an MDataSource pure virtual.
The default implementation leaves with KErrNotSupported and returns NULL.
@param aMediaId
An optional mediaID parameter when there are multiple buffers arriving of different media types.
@param aReference
A boolean indicating buffer ownership. ETrue if the MDataSource owns the buffer, EFalse if the caller owns the buffer.
@return A pointer to a newly created buffer. Returns NULL in this instance as datapath can't create source buffers.
*/
EXPORT_C CMMFBuffer* CMMFDataPath::CreateSourceBufferL(TMediaId /*aMediaId*/, TBool& /*aReference*/) //CMMFDataPath can't create buffers
{
User::Leave(KErrNotSupported);
return NULL;
}
/**
Gets the source data type for the specified media ID.
@param aMediaId
An optional parameter to specifiy specific stream when datasource contains more than one stream of data.
@return The source data type.
*/
EXPORT_C TFourCC CMMFDataPath::SourceDataTypeCode(TMediaId /*aMediaId*/)
{
return(iSourceFourCC);
}
/**
Allocates buffers in preparation to play.
Must be called before calling PlayL().
iSnkBufRef and iSrcBufRef contain ETrue if these buffers are created and owned by a MDataSource or MDataSink
For clean-up purposes, datapath only cleans up buffers allocated directly by PrimeL().
*/
EXPORT_C void CMMFDataPath::PrimeL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::PrimeL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
//allocate resources ie buffers & prepare to play
if (iDataPathCreated && (iState == EStopped))
//luckily the client utility does this.
{//can only prime from the stopped state
//This will determine what buffers we need to run the datapath.
//Can leave KERRNotSupported
iBuffersToUse = DetermineBuffersToUseL();
//Try to create source and sink buffers. If we can't create them in the Prime,
//we will need to obtain them by asynchronous buffer creation when playing starts.
ObtainSyncBuffersL();
iDataSource->SourcePrimeL(); //propogate state change to source
iDataSink->SinkPrimeL(); //propogate state change to sink
//If Client has set these, they will be set following the prime
iPlayWindowStartPosition = 0;
iPlayWindowEndPosition = Duration();
iState = EPrimed;
iPauseCalled = EFalse;
if (iCompleteCallback)
{
delete iCompleteCallback;
iCompleteCallback = NULL;
}
TBool waitForSink = (iDataSink->DataSinkType() == KUidMmfAudioOutput)?ETrue:EFalse;
iCompleteCallback = new (ELeave) CCompleteCallback(*this,waitForSink);
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::PrimeL Done tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Starts an active scheduler 'play' loop.
Can only play from the primed state.
*/
EXPORT_C void CMMFDataPath::PlayL()
{
#if defined(__PROFILING)
RDebug::ProfileEnd(1);
#endif // defined(__PROFILING)
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::PlayL, on src buff %d sink buf %d (this 0x%x)\n"), iCurrentSourceFrameNumber, iCurrentSinkFrameNumber,this);
RDebug::Print(_L("iStartPosition = %d\n"), I64INT(iStartPosition.Int64()));
#endif
if ((iDataPathCreated) && (iState == EPrimed))
{
//can only play from the primed state
TBool savedPauseCalled=EFalse;
if(iPauseCalled) //sink and source will have been stopped, and we will not have been re-primed
{
savedPauseCalled=ETrue;
iDataSink->SinkPrimeL(); //propagate change down to sink
iPauseCalled = EFalse;
}
iCurrentSourceFrameNumber = 0; //reset to beginning
iCurrentSinkFrameNumber = 0; //reset to beginning
iSourceBufferWithSource = EFalse;
iSinkBufferWithSink = EFalse;
iNoMoreSourceData = EFalse;
iAllDataSentToSink=EFalse;
iDataPathCompletedErrorCode=KErrNone;
SetPositionL( iStartPosition ) ;
iReferenceAudioSamplesPlayed = 0;
iReferenceAudioSamplesRecorded = 0;
//complete a request on iStatus to invoke play code
iDataSource->SourcePlayL(); //propagate state change to source
if (!(savedPauseCalled && (iTransferState==EWaitSink || iTransferState==EInitializeSink)))
{
iDataSink->SinkPlayL(); //propogate state change to sink
}
if (iDataSink->DataSinkType() == KUidMmfAudioOutput)
iState = EPlaying;
else if(iDataSource->DataSourceType() == KUidMmfAudioInput)
iState = ERecording;
else
iState = EConverting;
//need to re-initialize any buffer(s) that we only own references to
ChangeDataPathTransferState(EInitializeSink);
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::Play - DONE\n"));
#endif
}
/**
Pauses playing.
Sends KMMFErrorCategoryDataPathGeneralError to the client if an error occurs.
*/
EXPORT_C void CMMFDataPath::Pause()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::Pause, on src buff %d sink buf %d (this 0x%x)\n"), iCurrentSourceFrameNumber, iCurrentSinkFrameNumber,this);
RDebug::Print(_L("DP::Pause current state=%d tick-%d (this 0x%x)\n"),iTransferState, User::TickCount(),this);
#endif
TRAPD(err, DoPauseL());
if (err)
{
DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::Pause - DONE tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Stops playing.
Resets datapath position - currently does not clean up buffers. Sends KMMFErrorCategoryDataPathGeneralError
to the client if an error occurs.
*/
EXPORT_C void CMMFDataPath::Stop()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::Stop current state=%d tick-%d (this 0x%x)\n"), iTransferState, User::TickCount(),this);
#endif
if ((iDataPathCreated) && (iState != EStopped))
{
TRAPD(err, DoStopL());
if (err)
DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
}
}
/**
Forces and end of data state on the datapath
*/
EXPORT_C void CMMFDataPath::EndOfData()
{
TRAPD(err, DoEndOfDataL());
if (err)
{
DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, err);
}
}
/**
*/
TInt CMMFDataPath::AudioSamplesPlayed() const
{
if (iDataSink->DataSinkType() != KUidMmfAudioOutput)
return 0;
CMMFAudioOutput* audioOutput = STATIC_CAST(CMMFAudioOutput*,iDataSink);
TInt samples = 0;
if(iState == EPlaying)
samples = audioOutput->SoundDevice().SamplesPlayed();
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::AudioSamplesPlayed = %d\n"),samples);
#endif
return samples;
}
/**
*/
TInt CMMFDataPath::AudioSamplesRecorded() const
{
if (iDataSource->DataSourceType() != KUidMmfAudioInput)
return 0;
CMMFAudioInput* audioInput = STATIC_CAST(CMMFAudioInput*,iDataSource);
TInt samples = 0;
if(iState == ERecording)
samples = audioInput->SoundDevice().SamplesRecorded();
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::AudioSamplesRecorded = %d\n"),samples);
#endif
return samples;
}
/**
*/
TTimeIntervalMicroSeconds CMMFDataPath::CalculateAudioOutputPosition() const
{
//This operation can only be carried out on an Audio Output
__ASSERT_ALWAYS(iDataSink->DataSinkType() == KUidMmfAudioOutput, Panic(EMMFDataPathPanicProgrammingError,__LINE__));
//If we are not playing, simply return where we will play from
if(iState != EPlaying || iCurrentSinkFrameNumber == 0)
return iStartPosition;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::CalculateAudioOutputDuration from %d\n"),iReferenceAudioSamplesPlayed);
#endif
TReal samplesPlayed = AudioSamplesPlayed() - iReferenceAudioSamplesPlayed;
CMMFAudioOutput* audioOutput = STATIC_CAST(CMMFAudioOutput*,iDataSink);
CMMFDevSound& devSound = audioOutput->SoundDevice();
TMMFCapabilities devSoundConfig = devSound.Config();
TInt samplingFreq = StreamUtils::SampleRateAsValue(devSoundConfig);
#ifdef _DP_DEBUG
RDebug::Print(_L("samplingFreq %d\n"),samplingFreq);
#endif
TReal timePlayedSeconds = 0;
if(samplesPlayed)
timePlayedSeconds = samplesPlayed/samplingFreq;
TInt64 timePlayed(I64DOUBLECAST(timePlayedSeconds * 1000000));
#ifdef _DP_DEBUG
RDebug::Print(_L("timePlayed %d\n"), I64LOW(timePlayed));
#endif
return TTimeIntervalMicroSeconds(timePlayed + iStartPosition.Int64());
}
/**
*/
TTimeIntervalMicroSeconds CMMFDataPath::CalculateAudioInputPosition() const
{
//This operation can only be carried out on an Audio Input
__ASSERT_ALWAYS(iDataSource->DataSourceType() == KUidMmfAudioInput, Panic(EMMFDataPathPanicProgrammingError,__LINE__));
//If we are not playing, simply return where we will play from
if(iState != ERecording)
return iStartPosition;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::CalculateAudioInputPosition from %d\n"),iReferenceAudioSamplesRecorded);
#endif
TReal samplesRecorded = AudioSamplesRecorded() - iReferenceAudioSamplesRecorded;
CMMFAudioInput* audioInput = STATIC_CAST(CMMFAudioInput*,iDataSource);
CMMFDevSound& devSound = audioInput->SoundDevice();
TMMFCapabilities devSoundConfig = devSound.Config();
TInt samplingFreq = StreamUtils::SampleRateAsValue(devSoundConfig);
#ifdef _DP_DEBUG
RDebug::Print(_L("samplingFreq %d\n"),samplingFreq);
#endif
TReal timeRecordedSeconds = 0;
if(samplesRecorded)
timeRecordedSeconds = samplesRecorded/samplingFreq;
TInt64 timeRecorded(I64DOUBLECAST(timeRecordedSeconds * 1000000));
#ifdef _DP_DEBUG
RDebug::Print(_L("timeRecorded %d\n"), I64LOW(timeRecorded));
#endif
return TTimeIntervalMicroSeconds(timeRecorded);
}
TTimeIntervalMicroSeconds CMMFDataPath::OutputPosition() const
{
TTimeIntervalMicroSeconds interval;
TTimeIntervalMicroSeconds position;
if (iDataSink->DataSinkType() == KUidMmfAudioOutput)
{
position = CalculateAudioOutputPosition();
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::OutputPosition from audio output= %d\n"),I64INT(position.Int64()));
#endif
}
else if (iDataSink->DataSinkType() == KUidMmfFormatEncode)
{
//note Encode format position takes priority if both source & sink are formats?
// try to get the position directly from the format. If that fails, work it out here
TRAPD(error, position = ((CMMFFormatEncode*)iDataSink)->PositionL());
if (error)//getting the position from the format didn't work so calculate it here
{
interval = ((CMMFFormatEncode*)iDataSink)->FrameTimeInterval(iMediaId);
TInt64 position64 = interval.Int64() * iCurrentSinkFrameNumber;
position = position64;
}
TTimeIntervalMicroSeconds duration = CONST_CAST(CMMFDataPath*,this)->Duration(); //need this to check position doesn't exceed duration
if (position > duration)//this can happen on last buffer
position = duration;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::OutputPosition from format= %d\n"),I64INT(position.Int64()));
#endif
}
else
{//can only read output position if sink is a format or an audio output
return TTimeIntervalMicroSeconds(0);
}
return position;
}
TTimeIntervalMicroSeconds CMMFDataPath::InputPosition() const
{
TTimeIntervalMicroSeconds interval;
TTimeIntervalMicroSeconds position;
if (iDataSource->DataSourceType() == KUidMmfAudioInput)
{
position = CalculateAudioInputPosition();
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InputPosition from audio input= %d\n"),I64INT(position.Int64()));
#endif
}
else if (iDataSource->DataSourceType() == KUidMmfFormatDecode)
{//note Decode format position takes priority if both source & sink are formats?
// try to get the position directly from the format. If that fails, work it out here
TRAPD(error, position = ((CMMFFormatDecode*)iDataSource)->PositionL());
if (error)//getting the position from the format didn't work so calculate it here
{
interval = ((CMMFFormatDecode*)iDataSource)->FrameTimeInterval(iMediaId);
TInt64 position64 = interval.Int64() * iCurrentSourceFrameNumber;
position = position64;
}
TTimeIntervalMicroSeconds duration = CONST_CAST(CMMFDataPath*,this)->Duration(); //need this to check position doesn't exceed duration
if (position > duration)//this can happen on last buffer
position = duration;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::InputPosition from format = %d\n"),I64INT(position.Int64()));
#endif
}
else
{//can only read input position if source is a format or an audio input
return TTimeIntervalMicroSeconds(0);
}
return position;
}
/**
Gets the data path position.
@return The data path position.
*/
EXPORT_C TTimeIntervalMicroSeconds CMMFDataPath::Position() const
{
if ((iState == ERecording) || (iState == EConverting))
return InputPosition();
else if(iState == EPlaying)
return OutputPosition();
else
{
return iStartPosition;
}
}
/**
Sets the data path position.
@param aPosition
The data path position.
*/
EXPORT_C void CMMFDataPath::SetPositionL(const TTimeIntervalMicroSeconds& aPosition)
{//need to map to source position to frame position
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::SetPositionL = %d ticks-%d (this 0x%x)\n"),I64INT(aPosition.Int64()), User::TickCount(),this);
#endif
if (iState == EStopped)
User::Leave(KErrNotReady); //can only set position if primed
//As this will affect the position, we need to know how many bytes were
//played when position was updated. Future Position() requests will
//then use this refernce to determine the current position.
iReferenceAudioSamplesPlayed = AudioSamplesPlayed();
// Force the new position to be inside the play window (also within the file duration)
if ( aPosition < iPlayWindowStartPosition )
iStartPosition = iPlayWindowStartPosition;
else if ( aPosition > iPlayWindowEndPosition )
iStartPosition = iPlayWindowEndPosition; //clearly this will cause nothing to be played
else
iStartPosition = aPosition;
TTimeIntervalMicroSeconds interval;
//can only set the position on an MDataSource that is a format object
//Note: position defaults to source if both source & sink are clips
if (iDataSource->DataSourceType() == KUidMmfFormatDecode)
{
//position is not beyond the end of file
interval = ((CMMFFormatDecode*)iDataSource)->FrameTimeInterval(iMediaId);
// for some reason this code won't compile without these intermediate steps
TInt64 position = iStartPosition.Int64();
TInt64 interval64 = interval.Int64();
if (interval64 == 0)
User::Leave(KErrDivideByZero);
TInt64 datapos64 = position/interval64;
iCurrentSourceFrameNumber = I64LOW(datapos64);
// Try to set the position directly on the format
TRAP_IGNORE(((CMMFFormatDecode*)iDataSource)->SetPositionL(iStartPosition));
//NB: don't worry about error, since we'll reposition anyway when we get the next buffer
}
else if (iDataSink->DataSinkType() == KUidMmfFormatEncode)
{
//position is not beyond the end of file
interval = ((CMMFFormatEncode*)iDataSink)->FrameTimeInterval(iMediaId);
//convert to TUint - for some reason it won't compile without these intermediate steps
TInt64 position = iStartPosition.Int64();
TInt64 interval64 = interval.Int64();
if (interval64 == 0)
User::Leave(KErrDivideByZero);
TInt64 datapos64 = position/interval64;
iCurrentSinkFrameNumber = I64LOW(datapos64);
// Try to set the position directly on the format
TRAP_IGNORE(((CMMFFormatEncode*)iDataSink)->SetPositionL(iStartPosition));
//NB: don't worry about error, since we'll reposition anyway when we get the next buffer
}
else
{//can only set position if source or sink is a format
//If both source and sink are formats position is relative to the source
User::Leave(KErrNotSupported); //can't set position if neither source nor sink are clips
}
if(iCodec) //we have a real codec, must reset it
iCodec->ResetL(); // Need to preserve sync when resuming play
// Once we've sent the last buffer to the sink it's too late to start
// changing the state since we may get a RunError(KErrUnderflow) at any time.
// Once this happens, the sound driver may have unloaded etc..and recovery
// would be complicated.
if (iAllDataSentToSink)
return;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::SetPosition - Done iCurrentSourceFrameNumber=%d iStartPosition=%d ticks-%d (this 0x%x)\n"),iCurrentSourceFrameNumber, I64INT(iStartPosition.Int64()), User::TickCount(),this);
#endif
}
/**
Sets the play window absolutely (i.e. the parameters are relative to the start of the entire clip).
@param aStart
The offset from the start of the Clip
@param aEnd
The offset from the end of the clip (if this is less than aStart, then the two will be inverted).
*/
EXPORT_C void CMMFDataPath::SetPlayWindowL( const TTimeIntervalMicroSeconds& aStart, const TTimeIntervalMicroSeconds& aEnd )
{
// Clear the existing Play window
ClearPlayWindowL() ;
// Check that the parameters are legitimate. 0 <= startpos < endpos <= duration & update member variables
TTimeIntervalMicroSeconds duration = Duration();
if ( aStart < TTimeIntervalMicroSeconds(0) )
iPlayWindowStartPosition = TTimeIntervalMicroSeconds(0);
else if ( aStart > duration )
iPlayWindowStartPosition = duration;
else iPlayWindowStartPosition = aStart;
if ( aEnd < TTimeIntervalMicroSeconds(0) )
iPlayWindowEndPosition = TTimeIntervalMicroSeconds(0);
else if ( aEnd > duration )
iPlayWindowEndPosition = duration;
else iPlayWindowEndPosition = aEnd;
// ensure that the current position is inside the new play window
if ( iPlayWindowEndPosition != TTimeIntervalMicroSeconds(0) )
{
TTimeIntervalMicroSeconds currentPosition = Position() ;
if ( currentPosition < iPlayWindowStartPosition )
SetPositionL( iPlayWindowStartPosition ) ;
else if ( currentPosition > iPlayWindowEndPosition )
SetPositionL( iPlayWindowEndPosition ) ;
}
else
ClearPlayWindowL() ;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::SetPlayWindowL iPlayWindowStartPosition=%d iPlayWindowEndPosition=%d\n"),I64INT(iPlayWindowStartPosition.Int64()),I64INT(iPlayWindowEndPosition.Int64()));
#endif
}
/**
Sets the play window to the full length of clip.
*/
EXPORT_C void CMMFDataPath::ClearPlayWindowL()
{
iPlayWindowStartPosition = TTimeIntervalMicroSeconds(0) ;
iPlayWindowEndPosition = Duration();
if(iState == EStopped)
iStartPosition = iPlayWindowStartPosition;
}
/**
Returns the current data path state.
*/
EXPORT_C TInt CMMFDataPath::State()
{
return iState ;
}
/**
Uses the AO mechanism to drive state changes between sources and sinks.
RunL() moves and assigns buffers between its attached MDataSource/MDataSinks.
*/
void CMMFDataPath::ChangeDataPathTransferState(TTransferState aNewDataPathTransferState)
{
#ifdef _DP_DEBUG
switch (aNewDataPathTransferState)
{
case EWaitSink:
RDebug::Print(_L("Next State EWaitSink ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case EWaitSource:
RDebug::Print(_L("Next State EWaitSource ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case EInitializeSink:
RDebug::Print(_L("Next State EInitializeSink ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case EInitializeSource:
RDebug::Print(_L("Next State EInitializeSource ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case ENeedSourceData:
RDebug::Print(_L("Next State ENeedSourceData ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case ENeedSinkData:
RDebug::Print(_L("Next State ENeedSinkData ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case ENeedToMatchSourceToSink:
RDebug::Print(_L("Next State ENeedToMatchSourceToSink ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case ESendDataToSink:
RDebug::Print(_L("Next State ESendDataToSink ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
case EEndOfData:
RDebug::Print(_L("Next State EEndOfData ticks-%d (this 0x%x)\n"),User::TickCount(),this);
break;
}
#endif
TRequestStatus* stat = &iStatus;
//change state
iTransferState = aNewDataPathTransferState;
if ((iTransferState != EWaitSink) && (iTransferState != EWaitSource) &&
(iState == EPlaying || iState == ERecording || iState == EConverting || (iState == EPrimed && iPauseCalled)))
{//can go ahead with transfer
if (!IsActive())
{
User::RequestComplete(stat, KErrNone);
SetActive();
}
}
#ifdef _DP_DEBUG
else
RDebug::Print(_L("Datapath is no longer active, not going to new state (this 0x%x)\n"),this);
#endif
}
/**
Runs the clip depending on the current data path and transfer state.
For example, fills the sink buffer if TDataPathState is EPlaying and TTransferState is ENeedSinkData.
*/
EXPORT_C void CMMFDataPath::RunL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::RunL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
switch (iState)
{
case EStopped:
break;
case EPrimed:
//paused with stored position
break;
case EPlaying:
case ERecording:
case EConverting:
switch (iTransferState)
{
case EWaitSink:
case EWaitSource:
break;
case EInitializeSink:
InitializeSinkL();
break;
case EInitializeSource:
InitializeSourceL();
break;
case ENeedSourceData:
FillSourceBufferL();
break;
case ENeedSinkData:
FillSinkBufferL();
break;
case ENeedToMatchSourceToSink:
FillSinkBufferL();
break;
case ESendDataToSink:
EmptySinkBufferL();
break;
case EEndOfData:
EndOfData();
break;
}
break;
default:
break;
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::RunL DONE\n"));
#endif
}
/**
Cancels the clip.
The default implementation is empty.
*/
EXPORT_C void CMMFDataPath::DoCancel()
{
//don't need to do anything as we don't have any async requests to other objects
}
/**
Handles errors coming from attached sources and passes them to the clients.
@param aError
Standard error code (KErrNone = No Error).
@return The event code returned to the data path. KErrNone if end of file is encountered.
*/
EXPORT_C TInt CMMFDataPath::RunError(TInt aError)
{
return DoSendEventToClient(KMMFErrorCategoryDataPathGeneralError, aError);
}
/**
Returns the duration of the clip.
@return The length of clip in TTimeIntervalMicroSeconds.
*/
TTimeIntervalMicroSeconds CMMFDataPath::Duration() const
{
TTimeIntervalMicroSeconds duration(0);
if ( iDataSource && ( iDataSource->DataSourceType() == KUidMmfFormatDecode ) )
{
//this updated version of datapath caches the duration of the
//source clip for efficiency - since this meathod is const
//we need to cast away the constness to set iCachedSourceDuration
CMMFDataPath* thisNonConst = const_cast<CMMFDataPath*>(this);
duration = STATIC_CAST(CMMFFormatDecode*, thisNonConst->iDataSource )->Duration( iMediaId ) ;
thisNonConst->iCachedSourceDuration = duration;
}
else if ( iDataSink && ( iDataSink->DataSinkType() == KUidMmfFormatEncode ) )
duration = STATIC_CAST(CMMFFormatEncode*, iDataSink )->Duration( iMediaId ) ;
return duration ;
}
/**
This is a virtual function datapath (or derivations off) that can be implemented but may be left blank for default behaviour.
Additional Stop() method specific to this datapath.
*/
void CMMFDataPath::DoStopL()
{
// Note that the datapath needs to be paused first
// before it can be stopped - this is important for audio play
// for instance to effect an immediate stop rather than playing out
// the current buffer.
// Stop data source and sink
iDataSource->SourceStopL(); // propagate state change to source
iDataSink->SinkStopL(); // propagate change down to sink
iSinkBufferWithSink = EFalse;
iSourceBufferWithSource = EFalse;
//Contains the completion code if the datapath completes as a result of an error
iDataPathCompletedErrorCode = KErrNone;
ResetRefBuffers(); // buffer references may not be valid any more
SetPositionL(iPlayWindowStartPosition); // reset position
iState = EStopped; // stop succeeded, set state to stopped
}
/**
This is a virtual function datapath (or derivations off) that can be implemented but may be left blank for default behaviour.
Additional Pause method specific to this datapath.
The DataPath implements pause by recording the current position and stopping the source and sink.
When Play is called the DataPath uses the stored position as the starting point and resumes
playing. The reason for this (rather than using the PauseL() API on sources and sinks) is that
some implementations do not support a suitable pause, therefore the DataPath is implemented to
support the lowest common denominator.
Note:
A suitable pause implementation will retain any buffers in use. There will be no
need to call PrimeL() prior to PlayL().
*/
void CMMFDataPath::DoPauseL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::DoPauseL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
if ((iDataPathCreated) && ((iState == EPlaying) || (iState == EConverting)))
{
// try to pause source and sink
iDataSource->SourcePauseL(); // propagate state change to source
SetPositionL(Position());
iDataSink->SinkStopL();
iPauseCalled = ETrue; // indicate pause is called
iState = EPrimed; // go back to primed state (we're not playing)
iSinkBufferWithSink = EFalse;
iSourceBufferWithSource = EFalse;
ResetRefBuffers(); // buffer references may not be valid any more
Cancel(); //Stop the state machine
}
else if(iState == ERecording)
{
iPauseCalled = ETrue;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::DoPauseL Recording, pause datasource\n"));
#endif
iDataSource->SourcePauseL();
}
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::DoPauseL - Done iReferenceAudioSamplesPlayed = %d\n"),iReferenceAudioSamplesPlayed);
RDebug::Print(_L("DP::DoPauseL - Done restart at %d tick-%d (this 0x%x)\n"),I64INT(iStartPosition.Int64()), User::TickCount(),this);
#endif
}
/**
This is a virtual function datapath (or derivations off) that can be implemented or may be left blank for default behaviour.
Additional Pause method specific to this datapath.
*/
void CMMFDataPath::DoEndOfDataL()
{
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::DoEndOfDataL tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
SetPositionL(iPlayWindowStartPosition); // reset position
ASSERT(iCompleteCallback);
iCompleteCallback->SignalDataPathComplete(iDataPathCompletedErrorCode);
ResetRefBuffers();
iState = EStopped;
#ifdef _DP_DEBUG
RDebug::Print(_L("DP::DoEndOfDataL - Done tick-%d (this 0x%x)\n"),User::TickCount(),this);
#endif
}
/**
Passes error handling and general messages up to clients.
@param aEventType
Category code for the event. Category codes can be used as unique identifers.
@param aErrorCode
Standard error code.
@return The event code sent to client.
*/
//error handling
EXPORT_C TInt CMMFDataPath::DoSendEventToClient(TUid aEventType, TInt aErrorCode)
{
TMMFEvent event(aEventType, aErrorCode);
return iEventHandler.SendEventToClient(event);
}
/**
Passes error handling and general messages to clients.
@param aEvent
TMMFEvent supplied by callee (typically DoSendEventToClient)
@return The Event Message sent to the datapath event handler
*/
EXPORT_C TInt CMMFDataPath::SendEventToClient(const TMMFEvent& aEvent)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::SendEventToClient CODE = %d ticks=%d (this 0x%x)\n"),aEvent.iErrorCode,User::TickCount(),this);
RDebug::Print(_L("CMMFDataPath::SendEventToClient iEventType = %d == %d\n"),aEvent.iEventType, KMMFEventCategoryPlaybackComplete);
#endif
//If we have sent all the data to the sink, it is legal for it to send KErrUnderFlow
//to us and we can go through a clean shutdown, otherwise we pass the error to the
//controller and it is responsible for determining what to do
if(iAllDataSentToSink &&
(aEvent.iEventType == KMMFEventCategoryPlaybackComplete) &&
(aEvent.iErrorCode == KErrUnderflow))
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::SendEventToClient Clean complete\n"));
#endif
//sink may not return the final buffer once it has finished with it
//force ourselves into the EndOfData state
if(iTransferState != EEndOfData)
{
iDataPathCompletedErrorCode = KErrNone;
TRAP_IGNORE(DoEndOfDataL());
}
iCompleteCallback->SignalSinkComplete(KErrNone);
return KErrNone;
}
return iEventHandler.SendEventToClient(aEvent);
}
CMMFDataPath::CCompleteCallback::CCompleteCallback(CMMFDataPath& aDataPath, TBool aWaitForSink)
: CActive(EPriorityStandard),
iDataPath(aDataPath),
iWaitForSink(aWaitForSink)
{
CActiveScheduler::Add(this);
}
CMMFDataPath::CCompleteCallback::~CCompleteCallback()
{
Cancel();
}
void CMMFDataPath::CCompleteCallback::SignalDataPathComplete(TInt aDataPathError)
{
iDataPathComplete = ETrue;
iDataPathError = aDataPathError;
if (!IsActive())
{
// Signal ourselves to run with the given completion code
TRequestStatus* status = &ActiveStatus();
User::RequestComplete(status, KErrNone);
}
}
void CMMFDataPath::CCompleteCallback::SignalSinkComplete(TInt aSinkError)
{
iSinkComplete = ETrue;
iSinkError = aSinkError;
if (!IsActive())
{
// Signal ourselves to run with the given completion code
TRequestStatus* status = &ActiveStatus();
User::RequestComplete(status, KErrNone);
}
}
TRequestStatus& CMMFDataPath::CCompleteCallback::ActiveStatus()
{
SetActive();
return iStatus;
}
void CMMFDataPath::CCompleteCallback::DoCancel()
{
}
void CMMFDataPath::CCompleteCallback::RunL()
{
if (iWaitForSink)
{
if (iDataPathComplete && iSinkComplete)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::CCompleteCallback::RunL STOPPING src & sink ticks=%d (this 0x%x)\n"),User::TickCount(),this);
#endif
TRAP_IGNORE(iDataPath.DoStopL())
iDataPathComplete = EFalse;
iSinkComplete = EFalse;
// if we have to wait for the sink to complete, always use the sink error
iDataPath.DoSendEventToClient(KMMFEventCategoryPlaybackComplete, iSinkError);
}
}
else if (iDataPathComplete)
{
#ifdef _DP_DEBUG
RDebug::Print(_L("CMMFDataPath::CCompleteCallback::RunL STOPPING src & sink ticks=%d (this 0x%x)\n"),User::TickCount(),this);
#endif
TRAP_IGNORE(iDataPath.DoStopL())
iDataPathComplete = EFalse;
iSinkComplete = EFalse;
iDataPath.DoSendEventToClient(KMMFEventCategoryPlaybackComplete, iDataPathError);
}
}
EXPORT_C TInt CMMFDataPath::SetBlockLength(TUint aBlockLength)
{
MMMFDevSoundCustomInterfaceFileBlockLength* fileBlockLengthCI = NULL;
TInt err = KErrNotSupported;
if (iCodec)
{
err = iCodec->ExtensionInterface(KUidCustomInterfaceDevSoundFileBlockLength.iUid, (TAny*&)fileBlockLengthCI);
}
if (err == KErrNone)
{
fileBlockLengthCI->SetFileBlockLength(aBlockLength);
}
return err;
}