diff -r 000000000000 -r 79dd3e2336a0 devsound/devsoundrefplugin/src/swcodecwrapper/mmfSwCodecPlayDataPath.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/devsound/devsoundrefplugin/src/swcodecwrapper/mmfSwCodecPlayDataPath.cpp Fri Oct 08 19:40:43 2010 +0100 @@ -0,0 +1,752 @@ +// Copyright (c) 2003-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\mmfswcodecplaydatapath.cpp +// +// + +#include "mmfSwCodecPlayDataPath.h" +#include +#include +#include +#include "mmfSwCodecUtility.h" + +const TInt KBytesPerSample = 2; +const TInt KMaxBytesInSec = 192000; //considering maximum samplerate 96KHz +CMMFSwCodecPlayDataPath* CMMFSwCodecPlayDataPath::NewL() + { + CMMFSwCodecPlayDataPath* self = new(ELeave) CMMFSwCodecPlayDataPath; + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(); + return self; + } + + +void CMMFSwCodecPlayDataPath::ConstructL() + { + iAudioPlayer = new (ELeave) CDataPathPlayer(*this,CActive::EPriorityUserInput); + iSoundDeviceErrorReceiver = new (ELeave) CSoundDevPlayErrorReceiver(*this, CActive::EPriorityUserInput); + iUtility = CMMFSwCodecUtility::NewL(); + iVbrFlag = EFalse; + } + + +CMMFSwCodecPlayDataPath::~CMMFSwCodecPlayDataPath() + { + delete iAudioPlayer; + delete iSoundDeviceErrorReceiver; + delete iUtility; + + iSoundDevice.Close(); + + if (iCodec) + { + delete iSourceBuffer; + if (!iCodec->IsNullCodec()) + { + delete iSoundDeviceBuffer; + } + } + +#ifdef __USE_MMF_TRANSFERBUFFERS__ + delete iTransferWindow; + + if(iTransferBuffer) + { + iTransferBuffer->Close(); + delete iTransferBuffer; + } +#endif + +#ifdef __USE_MMF_PTRBUFFERS__ + delete iPtrBufferMemoryBlock; +#endif + } + + +TInt CMMFSwCodecPlayDataPath::SetObserver(MMMFHwDeviceObserver& aObserver) + { + TInt error; + if (iHwDeviceObserver) + { + error = KErrAlreadyExists; + } + else + { + iHwDeviceObserver = &aObserver; + error = KErrNone; + } + return error; + } + + +TInt CMMFSwCodecPlayDataPath::AddCodec(CMMFSwCodec& aCodec) + { + if (iCodec) + return KErrNotSupported; //doesn't support multiple codecs + + TInt err = KErrNone; + + iCodec = &aCodec; + + // Allocate data buffer + iSourceBufferSize = iCodec->SourceBufferSize(); + iSoundDevBufferSize = iCodec->SinkBufferSize(); + + if ((!iSourceBufferSize)||(!iSoundDevBufferSize)) + err = KErrArgument; //codec plugin has not specified buffer size + + if (err == KErrNone) + { +#ifdef __USE_MMF_TRANSFERBUFFERS__ + TRAP(err,iSourceBuffer = CreateTransferBufferL(iSourceBufferSize, static_cast(iSourceBuffer))); +#endif +#ifdef __USE_MMF_PTRBUFFERS__ + TRAP(err,iSourceBuffer = CreatePtrBufferL(iSourceBufferSize)); +#else + TRAP(err,iSourceBuffer = CMMFDataBuffer::NewL(iSourceBufferSize)); +#endif + } + + if (err == KErrNone) + { + if (iCodec->IsNullCodec()) + {//use source buffer for sound device buffer + iSoundDeviceBuffer = NULL; + } + else + {//codec needs separate source and sound device buffers + TRAP(err,iSoundDeviceBuffer = CMMFDataBuffer::NewL(iSoundDevBufferSize)); + } + } + return err; + } + + +TInt CMMFSwCodecPlayDataPath::Start() + { + TInt startError = KErrNone; + + if (!iCodec) + {//check that a codec has been added + startError = KErrNotReady; + } + if ((!iSoundDevice.Handle())&&(!startError)) + {//check that the sound drivers can be opened + startError = iSoundDevice.Open(); + } + + if (iState == EPaused) + {//we are paused so need to resume play + if (!startError) + { +#ifdef _SCW_DEBUG + RDebug::Print(_L("CMMFSwCodecPlayDataPath::Start-Resume")); +#endif + iAudioPlayer->ResumePlaying(); + iState = EPlaying; + } + } + else if (!startError) + { +#ifdef _SCW_DEBUG + RDebug::Print(_L("CMMFSwCodecPlayDataPath::Start-Normal")); +#endif + // get sample rate and channels from RMdaDevSound + RMdaDevSound::TCurrentSoundFormatBuf format; + iSoundDevice.GetPlayFormat(format); + iSampleRate = format().iRate; + iChannels = format().iChannels; + + iNoMoreSourceData = EFalse; + iNoMoreSoundDeviceData = EFalse; + iSourceBuffer->SetLastBuffer(EFalse); + iBytesPlayed = 0; + iState = EPlaying; + iSoundDeviceErrorReceiver->Start(); + TRAP(startError,FillSourceBufferL()); //get initial buffer of audio data + if (startError == KErrNone) + { + // Start the player objects + iAudioPlayer->Start(); + } + else + {//failed to start up correctly go back to stopped state + iState = EStopped; + iSoundDeviceErrorReceiver->Stop(); + } + } + return startError; + } + + +// *** Main Play Loop *** + +void CMMFSwCodecPlayDataPath::FillSourceBufferL() + {//asks observer to fill the source buffer + // Ask immediately for data from the observer +#ifdef __CYCLE_MMF_DATABUFFERS__ + // Create a new buffer to replicate INC021405 Play-EOF-Play on HwAccelerated solution Panics + // If the creation fails, we carry on regardless as the original buffer will not have been + // destroyed. Must do this as alloc fail tests will not run. + if(iSourceBuffer) + { + iSourceBuffer = CycleAudioBuffer(iSourceBuffer); + } +#endif // __CYCLE_MMF_DATABUFFERS__ + User::LeaveIfError(iHwDeviceObserver->FillThisHwBuffer(*iSourceBuffer)); + + } + + +void CMMFSwCodecPlayDataPath::BufferFilledL(CMMFDataBuffer& aBuffer) + {//call back from observer to indicate buffer has been filled + if (iState == EStopped) + User::Leave(KErrNotReady);//ok if paused? + + iSourceBuffer = &aBuffer; + iSourceBuffer->SetStatus(EFull); +#ifdef _SCW_DEBUG + RDebug::Print(_L("CMMFSwCodecPlayDataPath::BufferFilledL")); +#endif + + //need to check that the buffer size is not 0 - if so assume we've reached the end of the data + if (!iSourceBuffer->BufferSize()) + {//no buffer - could be end of source or could be that the source has no data?? + iNoMoreSourceData = ETrue; +#ifdef _SCW_DEBUG + RDebug::Print(_L("CMMFSwCodecPlayDataPath::BufferFilledL-NoMoreSourceData")); +#endif + } + //even if the buffer size is 0 we still + //need to perform the following to get the sound device callback + FillSoundDeviceBufferL(); //get buffer in pcm16 format for sound device + + /* iVbrFlag is added to datapath to avail the alternative dataflow wherein datapath makes sure that + destinationbuffer is filled to its maximum length before sending it to the sound driver. + Sending the buffer directly to the device causes underflow incase of Vorbis codecs.*/ + if (iVbrFlag) + { + /*There are two cases we need to deal here + 1. When the output of the codec is 0 for header data. + in that case, repeat till actual decoding of ogg packets and pages. + 2. When destination buffer is not filled even to its half length, get next source buffer + and decode it. This is to avoid underflows when ever we receive little pcm for a + a given source buffer. + */ + if (iSoundDeviceBuffer->Data().Length() < iSoundDeviceBuffer->Data().MaxLength()/2 && !(iSoundDeviceBuffer->LastBuffer())) + { + iSourceBuffer->SetStatus(EAvailable); //source buffer is now available + iSoundDeviceBuffer->SetPosition(iSoundDeviceBuffer->Data().Length());//this indicates the available space in the buffer to the codec + FillSourceBufferL(); + return; + } + else //data is sufficient to avoid underflows + { + iSoundDeviceBuffer->SetPosition(0); + if(iSoundDeviceBuffer->Data().Length()==0 && iSoundDeviceBuffer->LastBuffer()) + { + iNoMoreSoundDeviceData = ETrue; + } + } + } + + // attenuate the amplitude of the samples if volume ramping has been changed + // and is non-zero + if (iCustomInterface) + { + TTimeIntervalMicroSeconds volumeRamp = iCustomInterface->VolumeRamp(); + if (volumeRamp != iVolumeRamp) + { + iVolumeRamp = volumeRamp; + if (iVolumeRamp.Int64() != 0) + { + iUtility->ConfigAudioRamper( + iVolumeRamp.Int64(), + iSampleRate, + iChannels); + iRampAudioSample = ETrue; + } + else + { + iRampAudioSample = EFalse; + } + + } + if (iRampAudioSample) + iRampAudioSample = iUtility->RampAudio(iSoundDeviceBuffer); + } + + iAudioPlayer->PlayData(*iSoundDeviceBuffer); //play data to sound drivers + + if (iSourceBuffer->LastBuffer())//check last buffer flag + { + iNoMoreSourceData = ETrue; +#ifdef _SCW_DEBUG + RDebug::Print(_L("CMMFSwCodecPlayDataPath::BufferFilledL-LBNoMoreSourceData")); +#endif + } + } + + +void CMMFSwCodecPlayDataPath::FillSoundDeviceBufferL() + {//use CMMFSwCodec to fill the sound device buffer + + CMMFSwCodec::TCodecProcessResult codecProcessResult; + + if (iCodec->IsNullCodec()) + {//no codec so data can be sent direct to sink + iSoundDeviceBuffer = iSourceBuffer; + iSoundDeviceBuffer->SetStatus(EFull); //sink buffer is full + } + else + { + //pass buffer to codec for processing + codecProcessResult = iCodec->ProcessL(*iSourceBuffer, *iSoundDeviceBuffer); + + if (iSourceBuffer->LastBuffer()) //if source is last buffer so is sound dev + iSoundDeviceBuffer->SetLastBuffer(ETrue); + if ((!iSoundDeviceBuffer->BufferSize())&&(codecProcessResult.iDstBytesAdded)) + {//the codec has added data but not set the buffer length + iSoundDeviceBuffer->Data().SetLength(codecProcessResult.iDstBytesAdded); + } + //only supports EProcessComplete + switch (codecProcessResult.iCodecProcessStatus) + { + case CMMFSwCodec::TCodecProcessResult::EProcessComplete: + //finished procesing source data - all data in sink buffer + { + iSoundDeviceBuffer->SetStatus(EFull); //sink buffer is full + } + break; +#ifdef SYMBIAN_VARIABLE_BITRATE_CODEC + case CMMFSwCodec::TCodecProcessResult::EProcessIncomplete: + //finished procesing source data - all data in sink buffer + { + iSoundDeviceBuffer->SetStatus(EFull); //sink buffer is full + } + break; +#endif + case CMMFSwCodec::TCodecProcessResult::EDstNotFilled: + //could be the last buffer in which case dst might not get filled + { + iSoundDeviceBuffer->SetStatus(EFull); //sink buffer is full + } + break; + case CMMFSwCodec::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. + { + iSoundDeviceBuffer->SetStatus(EFull);//sink buffer may not really be 'full' but its as full as it going to get + iNoMoreSourceData = ETrue; + //doesn't matter if sink buffer is not full + } + break; + default: + Panic(EMMFSwCodecWrapperBadCodec); //should never get here - bad codec + } + } + } + + +void CMMFSwCodecPlayDataPath::BufferEmptiedL(const CMMFDataBuffer& aBuffer) + {//call back from CDataPathPlayer when the sound device buffer has been emptied + if (&aBuffer != iSoundDeviceBuffer) + Panic(EMMFSwCodecWrapperBadBuffer); + if(iVbrFlag && (iSourceBuffer->Status() == EUnAvailable || iNoMoreSourceData)) + {//No more source data. Play rest of the decoded data.Inform codec not to consider the source buffer + if(iSourceBuffer->Status()!=EUnAvailable) + { + iSourceBuffer->SetStatus(EUnAvailable); + } + FillSoundDeviceBufferL(); + if(iSoundDeviceBuffer->BufferSize() > 0) + { + // attenuate the amplitude of the samples if volume ramping has been changed + // and is non-zero + if (iCustomInterface) + { + TTimeIntervalMicroSeconds volumeRamp = iCustomInterface->VolumeRamp(); + if (volumeRamp != iVolumeRamp) + { + iVolumeRamp = volumeRamp; + if (iVolumeRamp.Int64() != 0) + { + iUtility->ConfigAudioRamper(iVolumeRamp.Int64(), iSampleRate, iChannels); + iRampAudioSample = ETrue; + } + else + { + iRampAudioSample = EFalse; + } + + } + if (iRampAudioSample) + { + iRampAudioSample = iUtility->RampAudio(iSoundDeviceBuffer); + } + + } + iAudioPlayer->PlayData(*iSoundDeviceBuffer); //play data to sound drivers + return; + } + else + { + if(iNoMoreSourceData) + { + iNoMoreSoundDeviceData = ETrue; + } + iSourceBuffer->SetStatus(EAvailable); + } + } + if (!iNoMoreSourceData) + FillSourceBufferL(); + } + +//*** End of Main Play Loop *** + + +void CMMFSwCodecPlayDataPath::Stop() + { + iAudioPlayer->Cancel(); + iSoundDeviceErrorReceiver->Cancel(); + iSoundDevice.Close(); + +#ifdef __CYCLE_MMF_DATABUFFERS__ + // Create a new buffer to replicate INC021405 Play-EOF-Play on HwAccelerated solution Panics + // If the creation fails, we carry on regardless as the original buffer will not have been + // destroyed. Must do this as alloc fail tests will not run. + if(iSourceBuffer) + { + iSourceBuffer = CycleAudioBuffer(iSourceBuffer); + } +#endif // __CYCLE_MMF_DATABUFFERS__ + + iState = EStopped; + } + + +void CMMFSwCodecPlayDataPath::Pause() + { + //since a pause can happen anyway in the datatransfer -need to set to a known + //state so that when play is resumed the behaviour is predictable + if (iSoundDevice.Handle()) + { + iSoundDevice.PausePlayBuffer(); //needs new LDD + iState = EPaused; +#ifdef _SCW_DEBUG + RDebug::Print(_L("Pause")); +#endif + } + else + {//an error must have occured + iState = EStopped; + } + } + + +TInt CMMFSwCodecPlayDataPath::EmptyBuffers() + { + TInt error = KErrNone; + if (iSoundDevice.Handle() == 0) + { + error = KErrNotReady; + } + else + { + iAudioPlayer->Cancel(); + iSoundDevice.CancelPlayData(); + iSoundDeviceErrorReceiver->Stop(); + iState = EStopped; + } + return error; + } + + +RMdaDevSound& CMMFSwCodecPlayDataPath::Device() + { + return iSoundDevice; + } + + +void CMMFSwCodecPlayDataPath::SoundDeviceException(TInt aError) + { + if(iIgnoreUnderflow) + { + if(!iVbrFlag && aError==KErrUnderflow && !iNoMoreSourceData) + { + //ignore underflow + return; + } + //for VBR codec data,no more source does not mean that no more sounddevice data + //so ignore underflows till the last buffer is played from the codec + else if(iVbrFlag && aError==KErrUnderflow && !iNoMoreSoundDeviceData) + { + //ignore underflow + return; + } + } + + //this sends a request to the hw device observer usually Devsound + //to update the bytes played + //it is done here so that the sound driver can be closed prior to + //updating the plicy and sending the error back + TUid uidUpdateBytesPlayed; + uidUpdateBytesPlayed.iUid = KMmfHwDeviceObserverUpdateBytesPlayed; + TPtrC8 dummy(0,0); + iHwDeviceObserver->MsgFromHwDevice(uidUpdateBytesPlayed,dummy); + + //this closes RMdaDevSound. + Stop(); + + //inform devsound so it can update policy + iHwDeviceObserver->Stopped(); + + // Inform the observer of the exception condition + // We inform the hw device observer after the policy has been + // updated incase the observer relied on the error to assume + // the policy has been updated + iHwDeviceObserver->Error(aError); + } + + +void CMMFSwCodecPlayDataPath::SetPlayCustomInterface(MPlayCustomInterface& aCustomInterface) + { + iCustomInterface = &aCustomInterface; + } + +/** +Retrieves a custom interface to the device. +The reference CMMFSwCodecWrapper supports two custom interfaces, +MEmptyBuffersCustomInterface and MSetVbrFlagCustomInterface + +@param aInterface + Interface UID, defined with the custom interface. + aInterface = KMmfUidEmptyBuffersCustomInterface for MEmptyBuffersCustomInterface, + KSetVbrFlagCustomInterfaceTypeUid for MSetVbrFlagCustomInterface + +@return A pointer to the interface implementation, or NULL if the device can not + implement the interface requested. The return value must be cast to the + correct type by the user. +*/ +TAny* CMMFSwCodecPlayDataPath::CustomInterface(TUid aInterface) + { + TAny* ret = NULL; + if (aInterface.iUid == KMmfUidEmptyBuffersCustomInterface) + { + MEmptyBuffersCustomInterface* result = static_cast (this); + ret = static_cast(result); + } + else if(aInterface.iUid == KSetVbrFlagCustomInterfaceTypeUid) + { + SetVbrFlag(); + } + if (aInterface == KTimePlayedCustomInterfaceTypeUid) + { + MTimePlayedCustomInterface* result = static_cast (this); + ret = static_cast(result); + } + if (aInterface == KIgnoreUnderflowCustomInterfaceTypeUid) + { + MIgnoreUnderflowEventsCustomInterface* result = static_cast (this); + ret = static_cast(result); + } + return ret; + } + +/** +Used to set iVbrFlag on the datapath. + +This method is used to set the iVbrFlag in datapath. This flag is added to datapath to avail the +alternative dataflow wherein datapath makes sure that destinationbuffer is filled to its maximum length +before sending it to the sound driver. Sending the buffer directly to the device causes underflow incase of VBR codecs. +*/ +void CMMFSwCodecPlayDataPath::SetVbrFlag() + { + iVbrFlag = ETrue; + } + +TInt CMMFSwCodecPlayDataPath::GetTimePlayed(TTimeIntervalMicroSeconds& aTime) + { + if(iSoundDevice.Handle()) + { + TInt bytes = iSoundDevice.BytesPlayed(); + //Work around for overflow of bytes played from driver. + //This code will be removed when Base provides the TimePlayed() API which returns the play time + //Assuming GetTimePlayed() gets called in an interval not more than 3 secs, reset driver's bytes played when it is near KMaxInt + if(bytes > (KMaxTInt - 3*KMaxBytesInSec)) + { + iBytesPlayed = iBytesPlayed+bytes; + iSoundDevice.ResetBytesPlayed(); + bytes = 0; + } + TInt64 samplesPlayed = (iBytesPlayed+bytes)/(KBytesPerSample*iChannels); + aTime = (samplesPlayed*1000000)/iSampleRate; + } + else + { + aTime = 0; + } + + return KErrNone; + } + +void CMMFSwCodecPlayDataPath::IgnoreUnderflowEvents() + { + iIgnoreUnderflow = ETrue; + } +/************************************************************************ + * CDataPathPlayer * + ************************************************************************/ + +CDataPathPlayer::CDataPathPlayer(CMMFSwCodecPlayDataPath& aParent, TInt aPriority) +: CActive(aPriority), iParent(aParent) + { + CActiveScheduler::Add(this); + } + + +CDataPathPlayer::~CDataPathPlayer() + { + Cancel(); + } + + +void CDataPathPlayer::Start() + { + // No implementation + } + + +void CDataPathPlayer::ResumePlaying() + { + if (iParent.Device().Handle()) + { + //should be ok to call this even if we are active + iParent.Device().ResumePlaying(); + iResumePlaying = ETrue; + } +#ifdef _SCW_DEBUG + RDebug::Print(_L("Playing Resumed")); +#endif + } + + +void CDataPathPlayer::PlayData(const CMMFDataBuffer& aData) + { + iDataFromSource = &aData; + if (!IsActive()) + { +#ifdef _SCW_DEBUG + RDebug::Print(_L("CDataPathPlayer::PlayData")); +#endif + iParent.Device().PlayData(iStatus,(STATIC_CAST(const CMMFDataBuffer*, iDataFromSource))->Data()); + SetActive(); + } + } + + +void CDataPathPlayer::Stop() + { + if (!IsActive()) + iParent.Device().FlushPlayBuffer(); // Otherwise won't be flushed + Cancel(); + iParent.SoundDeviceException(KErrCancel); + } + + +void CDataPathPlayer::RunL() + { +#ifdef _SCW_DEBUG + RDebug::Print(_L("CDataPathPlayer::RunL error[%d]"), iStatus.Int()); +#endif + if (iStatus.Int()!=KErrNone) + { + iParent.SoundDeviceException(iStatus.Int()); + } + else + { + iParent.BufferEmptiedL(static_cast(*iDataFromSource)); + iResumePlaying = EFalse; + } + } + + +TInt CDataPathPlayer::RunError(TInt aError) + { + Error(aError); + return KErrNone; + } + + +void CDataPathPlayer::DoCancel() + { + if (iParent.Device().Handle()) + { + iParent.Device().CancelPlayData(); + iParent.Device().FlushPlayBuffer(); + } + } + + +void CDataPathPlayer::Error(TInt aError) + { + iParent.SoundDeviceException(aError); + } + + +/************************************************************************ + * CSoundDevPlayErrorReceiver * + ************************************************************************/ + +CSoundDevPlayErrorReceiver::CSoundDevPlayErrorReceiver(CMMFSwCodecPlayDataPath& aParent, TInt aPriority) +: CActive(aPriority), iParent(aParent) + { + CActiveScheduler::Add(this); + } + +CSoundDevPlayErrorReceiver::~CSoundDevPlayErrorReceiver() + { + Cancel(); + } + +void CSoundDevPlayErrorReceiver::Start() + { + iParent.Device().NotifyPlayError(iStatus); + SetActive(); + } + +void CSoundDevPlayErrorReceiver::Stop() + { + Cancel(); + } + +void CSoundDevPlayErrorReceiver::RunL() + { + TInt reason = iStatus.Int(); + Start(); + // An error has been returned +#ifdef _SCW_DEBUG + RDebug::Print(_L("CSoundDevPlayErrorReceiver::RunL[%d]"), reason); +#endif + iParent.SoundDeviceException(reason); + } + +void CSoundDevPlayErrorReceiver::DoCancel() + { + iParent.Device().CancelNotifyPlayError(); + } + +