--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mmdevicefw/mdf/src/audio/mdasoundadapter/mdasoundadapterbody.cpp Fri Oct 08 19:40:43 2010 +0100
@@ -0,0 +1,1944 @@
+// Copyright (c) 2008-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:
+//
+#include "mdasoundadapterconsts.h"
+#include "mdasoundadapterbody.h"
+#include <e32debug.h>
+
+#include "mmf/utils/rateconvert.h" // if we need to resample
+
+#include <hal.h>
+
+_LIT(KPddFileName,"SOUNDSC.PDD");
+_LIT(KLddFileName,"ESOUNDSC.LDD");
+
+
+const TInt KBytesPerSample = 2;
+const TInt KMinBufferSize = 2;
+
+/**
+This function raises a panic
+EDeviceNotOpened is raised when any of the RMdaDevSound APIs are called before opening the device.
+*/
+GLDEF_C void Panic(TSoundAdapterPanicCodes aPanicCode)
+ {
+ User::Panic(KSoundAdapterPanicCategory, aPanicCode);
+ }
+
+
+const TText8 *RMdaDevSound::CBody::TState::Name() const
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ switch(iState)
+ {
+ case ENotReady: return _S8("ENotReady");
+ case EStopped: return _S8("EStopped");
+ case ERecording: return _S8("ERecording");
+ case ERecordingPausedInHw: return _S8("ERecordingPausedInHw");
+ case ERecordingPausedInSw: return _S8("ERecordingPausedInSw");
+ case EPlaying: return _S8("EPlaying");
+ case EPlayingPausedInHw: return _S8("EPlayingPausedInHw");
+ case EPlayingPausedInSw: return _S8("EPlayingPausedInSw");
+ case EPlayingUnderrun: return _S8("EPlayingUnderrun");
+ }
+ return _S8("CorruptState");
+ #else
+ return _S8("");
+ #endif
+ }
+
+
+
+RMdaDevSound::CBody::TState &RMdaDevSound::CBody::TState::operator=(TStateEnum aNewState)
+ {
+ if(iState != aNewState)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound state %s -> %s", Name(), TState(aNewState).Name());
+ #endif
+ iState = aNewState;
+ }
+ return *this;
+ }
+
+RMdaDevSound::CBody* RMdaDevSound::CBody::NewL()
+ {
+ CBody* self = new(ELeave) CBody();
+ CleanupStack::PushL(self);
+ self->ConstructL();
+ CleanupStack::Pop();
+ return self;
+ }
+
+RMdaDevSound::CBody::~CBody()
+ {
+ for(TInt i = 0; i < KPlaySharedChunkBuffers; i++)
+ {
+ delete iPlayers[i];
+ iPlayers[i] = NULL;
+ }
+ delete iRecorder;
+ iRecorder = NULL;
+ delete iPlayFormatData.iConverter;
+ delete iRecordFormatData.iConverter;
+ iPlayChunk.Close();
+ iPlaySoundDevice.Close();
+ iRecordChunk.Close();
+ iRecordSoundDevice.Close();
+ iConvertedPlayData.Close();
+ iSavedTrailingData.Close();
+ iBufferedRecordData.Close();
+ }
+
+RMdaDevSound::CBody::CBody()
+ :iState(ENotReady), iBufferOffset(-1)
+ {
+
+ }
+
+TVersion RMdaDevSound::CBody::VersionRequired() const
+ {
+ if(iPlaySoundDevice.Handle())
+ {
+ return iPlaySoundDevice.VersionRequired();
+ }
+ else
+ {
+ return TVersion();
+ }
+ }
+
+TInt RMdaDevSound::CBody::IsMdaSound()
+ {
+ return ETrue;
+ }
+
+void RMdaDevSound::CBody::ConstructL()
+ {
+ // Try to load the audio physical driver
+ TInt err = User::LoadPhysicalDevice(KPddFileName);
+ if ((err!=KErrNone) && (err!=KErrAlreadyExists))
+ {
+ User::Leave(err);
+ }
+ // Try to load the audio logical driver
+ err = User::LoadLogicalDevice(KLddFileName);
+ if ((err!=KErrNone) && (err!=KErrAlreadyExists))
+ {
+ User::Leave(err);
+ }
+ for(TInt i=0; i<KPlaySharedChunkBuffers; i++)
+ {
+ iPlayers[i] = new(ELeave) CPlayer(CActive::EPriorityUserInput, *this, i);
+ iFreePlayers.Push(iPlayers[i]);
+ }
+
+ iRecorder = new(ELeave) CRecorder(CActive::EPriorityUserInput, *this);
+
+ TInt tmp;
+ User::LeaveIfError(HAL::Get(HAL::ENanoTickPeriod, tmp));
+ iNTickPeriodInUsec = tmp;
+ }
+
+TInt RMdaDevSound::CBody::Open(TInt /*aUnit*/)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::Open "));
+ #endif
+ TInt err = KErrNone;
+ //Default behavior of this method is to open both the play and record audio devices.
+ if(!iPlaySoundDevice.Handle() && !iRecordSoundDevice.Handle())
+ {
+ err = iPlaySoundDevice.Open(KSoundScTxUnit0);
+ if(err == KErrNone)
+ {
+ err = iRecordSoundDevice.Open(KSoundScRxUnit0);
+ }
+ }
+ if(err != KErrNone)
+ {
+ Close();
+ }
+ else
+ {
+ TSoundFormatsSupportedV02Buf capsBuf;
+ iPlaySoundDevice.Caps(capsBuf);
+ TInt minBufferSize = KMinBufferSize;
+ #ifdef SYMBIAN_FORCE_32BIT_LENGTHS
+ minBufferSize = Max(minBufferSize, 4); // force to 32-bit buffer align
+ #endif
+ iRequestMinSize = Max(capsBuf().iRequestMinSize, minBufferSize);
+ // work out mask so that x&iRequestMinMask is equiv to x/iRequestMinSize*iRequestMinSize
+ iRequestMinMask = ~(iRequestMinSize-1); // assume iRequestMinSize is power of 2
+ iSavedTrailingData.Close();
+ iSavedTrailingData.Create(iRequestMinSize);
+
+ iState = EStopped;
+ iBytesPlayed = 0;
+ }
+
+ return err;
+ }
+
+TInt RMdaDevSound::CBody::PlayVolume()
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ return iPlaySoundDevice.Volume();
+ }
+
+void RMdaDevSound::CBody::SetPlayVolume(TInt aVolume)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ if(aVolume >=0 && aVolume<=KSoundMaxVolume)
+ {
+ iPlaySoundDevice.SetVolume(KLinerToDbConstantLookup[aVolume].iDBValue);
+ }
+ }
+void RMdaDevSound::CBody::SetVolume(TInt aLogarithmicVolume)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ if(aLogarithmicVolume >= 0 && aLogarithmicVolume <= KSoundMaxVolume)
+ {
+ iPlaySoundDevice.SetVolume(aLogarithmicVolume);
+ }
+ }
+
+void RMdaDevSound::CBody::CancelPlayData()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::CancelPlayData: state %s", iState.Name());
+ #endif
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ // If there is a client request, cancel it
+ // Must do this before canceling players because otherwise they may just restart!
+ if(iClientPlayStatus)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("msp PlayCancelled complete iClientPlayStatus");
+ #endif
+ User::RequestComplete(iClientPlayStatus, KErrCancel); // Call also sets iClientPlayStatus to NULL
+ }
+
+ // Discard any buffered data
+ iClientPlayData.Set(0,0);
+ // Discard any saved trailing data (ie. data saved due driver requiring all requests to be a multiple of iRequestMinSize).
+ iSavedTrailingData.SetLength(0);
+
+ // Emulator RSoundSc PDD when running without a soundcard has a major
+ // issue with cancelling whilst paused. It will not clear the pending
+ // list (because the timer is not active) and therefore this list will
+ // later overflow causing hep corruption.
+ // This means that, for now, we MUST Resume before calling CancelPlayData
+ // to avoid kernel panics...
+
+ // The device driver will not cancel a request which is in progress...
+ // So, if we are paused in hw, we must resume before cancelling the
+ // player otherwise it will hang in CActive::Cancel
+ if(iState == EPlayingPausedInHw)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("msp Resume to avoid hang");
+ #endif
+ (void) iPlaySoundDevice.Resume();
+ }
+
+ // Update state
+ iState = EStopped;
+
+
+ // The RSoundSc driver will not cancel a request which is in progress (or paused).
+ // If we just loop across the players, cancelling each individual request and waiting for it to complete,
+ // several of them will actually play, which is both wrong and time consuming....
+ // Issue a block cancel upfront to avoid this
+ iPlaySoundDevice.CancelPlayData();
+
+ // Cancel all players
+ for (TUint playerIndex=0; playerIndex<KPlaySharedChunkBuffers; ++playerIndex)
+ {
+ // If the player is active it will call PlayRequestCompleted with aDueToCancelCommand true
+ // to update the iFreePlayers and iActivePlayRequestSizes FIFOs.
+ iPlayers[playerIndex]->Cancel();
+ }
+
+ iBufferOffset = -1;
+ iBufferLength = 0;
+
+ return;
+ }
+
+TInt RMdaDevSound::CBody::RecordLevel()
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ return iRecordSoundDevice.Volume();
+ }
+
+void RMdaDevSound::CBody::SetRecordLevel(TInt aLevel)
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ iRecordSoundDevice.SetVolume(aLevel);
+ }
+
+void RMdaDevSound::CBody::CancelRecordData()
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::CancelRecordData: state %s", iState.Name());
+ #endif
+
+ // Stop recorder object (and its request)
+ iRecorder->Cancel();
+
+ // Stop driver from recording
+ iRecordSoundDevice.CancelRecordData();
+
+ // If there is a client request, cancel it
+ if(iClientRecordStatus)
+ {
+ User::RequestComplete(iClientRecordStatus, KErrNone); // Call also sets iClientPlayStatus to NULL
+ }
+
+ iState = EStopped;
+ return;
+ }
+
+void RMdaDevSound::CBody::FlushRecordBuffer()
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::FlushRecordBuffer - implemented by calling PauseRecordBuffer"));
+ #endif
+
+ PauseRecordBuffer();
+ }
+
+TInt RMdaDevSound::CBody::BytesPlayed()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::BytesPlayed %s", iState.Name());
+ #endif
+
+ return I64LOW(BytesPlayed64());
+ }
+
+
+TUint64 RMdaDevSound::CBody::BytesPlayed64()
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ TUint64 currentBytesPlayed = KMaxTUint64;
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ currentBytesPlayed = iBytesPlayed;
+ break;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ Panic(EBadState);
+ break;
+
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ // Paused, so use pause time
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("EPlayingPausedInHw: iPausedBytes %x %x", I64HIGH(iPausedBytesPlayed), I64LOW(iPausedBytesPlayed));
+ #endif
+ currentBytesPlayed = iPausedBytesPlayed;
+ break;
+
+ case EPlayingPausedInSw: // ie. Driver not playing or paused
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("EPlayingPausedInSw: iPausedBytesPlayed %x %x", I64HIGH(iPausedBytesPlayed), I64LOW(iPausedBytesPlayed));
+ #endif
+ currentBytesPlayed = iPausedBytesPlayed;
+ break;
+ case EPlayingUnderrun:
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("EPlayingUnderrun: iBytesPlayed %x %x", I64HIGH(iBytesPlayed), I64LOW(iBytesPlayed));
+ #endif
+ currentBytesPlayed = iBytesPlayed;
+ break;
+
+ case EPlaying:
+ {
+ // Playing so calculate time since last update to iBytesPlayed
+ TUint32 curTime = CurrentTimeInMsec();
+ TUint32 curRequestSize = iActivePlayRequestSizes.Peek();
+
+ TUint32 extraPlayTime = (curTime >= iStartTime) ? (curTime-iStartTime) : (KMaxTUint32 - (iStartTime-curTime));
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("iStartTime %d curTime %d extraPlayTime %d", iStartTime, curTime, extraPlayTime);
+
+ RDebug::Printf("iPlayFormatData.iSampleRate %d KBytesPerSample %d iNTickPeriodInUsec %d",
+ iPlayFormatData.iSampleRate, KBytesPerSample, iNTickPeriodInUsec);
+ #endif
+ TUint32 extraBytesPlayed = TUint32((TUint64(extraPlayTime) * iPlayFormatData.iSampleRate * iPlayFormatData.iRequestedChannels * KBytesPerSample)/1000);
+ if(extraBytesPlayed > curRequestSize)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("caping extraBytes played from %d to %d", extraBytesPlayed, curRequestSize);
+ #endif
+ extraBytesPlayed = curRequestSize;
+ }
+
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("iBytesPlayed %d extraBytesPlayed %d (curRequestSize %d) -> currentBytesPlayed %x %x",
+ iBytesPlayed, extraBytesPlayed, curRequestSize, I64HIGH(currentBytesPlayed), I64LOW(currentBytesPlayed));
+ #endif
+
+ currentBytesPlayed = iBytesPlayed + extraBytesPlayed;
+ break;
+ }
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("iPlayFormatData.iConverter %x", iPlayFormatData.iConverter);
+ #endif
+
+ if (iPlayFormatData.iConverter)
+ {
+ // need to scale bytes played to fit with requested rate and channels, not actual
+ if (iPlayFormatData.iActualChannels != iPlayFormatData.iRequestedChannels)
+ {
+ if (iPlayFormatData.iActualChannels == 2)
+ {
+ // requested was mono, we have stereo
+ currentBytesPlayed /= 2;
+ }
+ else
+ {
+ // requested was stereo, we have mono
+ currentBytesPlayed *= 2;
+ }
+ }
+ if (iPlayFormatData.iSampleRate != iPlayFormatData.iActualRate)
+ {
+ currentBytesPlayed = TUint64(currentBytesPlayed*
+ TReal(iPlayFormatData.iSampleRate)/TReal(iPlayFormatData.iActualRate)); // don't round
+ }
+ }
+
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("currentBytesPlayed %x %x", I64HIGH(currentBytesPlayed), I64LOW(currentBytesPlayed));
+ #endif
+ return currentBytesPlayed;
+ }
+
+void RMdaDevSound::CBody::ResetBytesPlayed()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::ResetBytesPlayed %s", iState.Name());
+ #endif
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ iBytesPlayed = 0;
+ iPlaySoundDevice.ResetBytesTransferred();
+ return;
+ }
+
+void RMdaDevSound::CBody::PausePlayBuffer()
+ {
+#ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::PausePlayBuffer %s", iState.Name());
+#endif
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // Driver is not playing so pause in s/w
+ break;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ Panic(EBadState);
+ break;
+
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ case EPlayingPausedInSw: // ie. Driver not playing or paused
+ // Already paused so nothing to do.
+ break;
+ case EPlayingUnderrun:
+ iState = EPlayingPausedInSw;
+ break;
+
+ case EPlaying:
+ {
+ iPauseTime = CurrentTimeInMsec();
+ iPausedBytesPlayed = BytesPlayed64();
+ TInt res = iPlaySoundDevice.Pause();
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("iPlaySoundDevice.Pause res = %d", res);
+ #endif
+ if(res == KErrNone)
+ {
+ iState = EPlayingPausedInHw;
+ }
+ else
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("msp PausePlayBuffer hw pause unexpectedly failed, doing sw pause");
+ #endif
+ iState = EPlayingPausedInSw;
+ }
+ break;
+ }
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ return;
+ }
+
+void RMdaDevSound::CBody::ResumePlaying()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::ResumePlaying %s", iState.Name());
+ #endif
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // No change
+ break;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ Panic(EBadState);
+ break;
+
+ case EPlaying:
+ // No change
+ break;
+
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ {
+ // Re-enable reporting of KErrUnderflow (will re-raise KErrUnderflow if nothing to start playing).
+ iUnderFlowReportedSinceLastPlayOrRecordRequest = EFalse;
+
+ TInt res = iPlaySoundDevice.Resume();
+#ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("ResumePlayBuffer EPlayingPausedInHw res = %d", res);
+#endif
+ if(res == KErrNone)
+ {
+ // Resume ok so a pending request will complete
+ iState = EPlaying;
+ // Update iStartTime to allow for time spent paused
+ TUint32 curTime = CurrentTimeInMsec();
+ TUint32 timePaused = (curTime >= iPauseTime) ? (curTime-iPauseTime) : (KMaxTUint32 - (iPauseTime-curTime));
+ iStartTime += timePaused; // nb. It is harmless if this wraps.
+ }
+ else
+ {
+ // Resume failed, therefore driver is not playing
+ // No need to update iStartTime/iPauseTime because these are only used within a driver request
+ // Change state to Stopped
+ iState = EStopped;
+ // Attempt to start a new (pending) request.
+ StartPlayersAndUpdateState();
+ }
+ break;
+ }
+ case EPlayingPausedInSw: // ie. Driver not playing/paused
+ {
+ // Driver not playing
+ // Re-enable reporting of KErrUnderflow (will re-raise KErrUnderflow if nothing to start playing).
+ iUnderFlowReportedSinceLastPlayOrRecordRequest = EFalse;
+ // No need to update iStartTime/iPauseTime because these are only used within a driver request
+ // Change state to Stopped
+ iState = EStopped;
+ // Attempt to start a new (pending) request.
+ StartPlayersAndUpdateState();
+ break;
+ }
+ case EPlayingUnderrun:
+ break;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ return;
+ }
+
+void RMdaDevSound::CBody::PauseRecordBuffer()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::PauseRecordBuffer %s", iState.Name());
+ #endif
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // Driver is not recording so pause in s/w
+ // Do not pause because that will cause problems when CAudioDevice::Pause calls
+ break;
+
+ case ERecording:
+ {
+ TInt res = iRecordSoundDevice.Pause();
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("PauseRecordBuffer EPlaying res = %d", res);
+ #endif
+ if(res == KErrNone)
+ {
+ iState = ERecordingPausedInHw;
+ }
+ else
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("PauseRecordBuffer hw pause unexpectedly failed, doing sw pause");
+ #endif
+ iState = ERecordingPausedInSw;
+ }
+ break;
+ }
+
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ // Already paused so nothing to do.
+ break;
+
+ case EPlaying:
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ Panic(EBadState);
+ break;
+
+ case EPlayingPausedInSw:
+ // This is an ugly hack to maintain compatibility with CAudioDevice::Pause which
+ // calls both PausePlayBuffer and PauseRecordBuffer whilst in stopped, then later calls ResumePlaying
+ break;
+ case EPlayingUnderrun: // ie. Play request pending on h/w and paused
+ Panic(EBadState);
+ break;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ return;
+ }
+
+void RMdaDevSound::CBody::ResumeRecording()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::ResumeRecording %s", iState.Name());
+ #endif
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // No change
+ break;
+
+ case ERecording:
+ // No change
+ break;
+
+ case ERecordingPausedInHw:
+ {
+ TInt res = iRecordSoundDevice.Resume();
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("ResumeRecordBuffer ERecordingPausedInHw res = %d", res);
+ #endif
+ if(res == KErrNone)
+ {
+ // Resume ok so a pending request will complete
+ iState = ERecording;
+ }
+ else
+ {
+ iState = EStopped;
+ // Resume failed, so attempt to start a new (pending) request.
+ // If this works, it will update the state to ERecording.
+ StartRecordRequest();
+ }
+ break;
+ }
+ case ERecordingPausedInSw:
+ {
+ // Update state to stopped and attempt to start any pending request
+ iState = EStopped;
+ // If this works, it will update the state to ERecording.
+ StartRecordRequest();
+ break;
+ }
+
+ case EPlaying:
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ case EPlayingPausedInSw: // ie. Driver not playing/paused
+ case EPlayingUnderrun:
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ return;
+
+
+ }
+
+TInt RMdaDevSound::CBody::GetTimePlayed(TTimeIntervalMicroSeconds& aTimePlayed)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+
+ TUint64 bytesPlayed = BytesPlayed64();
+
+ TUint64 timePlayed = 1000 * 1000 * bytesPlayed / (iPlayFormatData.iSampleRate * iPlayFormatData.iRequestedChannels * KBytesPerSample);
+
+ aTimePlayed = TTimeIntervalMicroSeconds(timePlayed);
+
+ return KErrNone;
+ }
+
+
+void RMdaDevSound::CBody::FormatsSupported(TSoundFormatsSupportedBuf& aFormatsSupported, RSoundSc& aSoundDevice)
+ {
+ TSoundFormatsSupportedV02Buf supportedFormat;
+ aSoundDevice.Caps(supportedFormat);
+ TUint32 rates = supportedFormat().iRates;
+
+ for(TInt i = KNumSampleRates-1; i > 0 ;i--)//min to max
+ {
+ if(rates & KRateEnumLookup[i].iRateConstant)
+ {
+ aFormatsSupported().iMinRate = KRateEnumLookup[i].iRate;
+ break;
+ }
+ }
+ for(TInt i = 0; i < KNumSampleRates; i++)//max to min
+ {
+ if(rates & KRateEnumLookup[i].iRateConstant)
+ {
+ aFormatsSupported().iMaxRate = KRateEnumLookup[i].iRate;
+ break;
+ }
+ }
+ TUint32 enc = supportedFormat().iEncodings;
+
+ if (enc & KSoundEncoding16BitPCM)
+ {
+ aFormatsSupported().iEncodings = EMdaSoundEncoding16BitPCM;// Always defaults to this
+ }
+ if (enc & KSoundEncoding8BitPCM)
+ {
+ aFormatsSupported().iEncodings |= EMdaSoundEncoding8BitPCM;
+ }
+ TUint32 channels = supportedFormat().iChannels;
+
+ if (channels & KSoundStereoChannel)
+ {
+ aFormatsSupported().iChannels = 2;
+ }
+ else
+ {
+ aFormatsSupported().iChannels = 1;
+ }
+ aFormatsSupported().iMinBufferSize = supportedFormat().iRequestMinSize;
+ aFormatsSupported().iMaxBufferSize = KMaxBufferSize;
+ aFormatsSupported().iMinVolume = 0;
+ aFormatsSupported().iMaxVolume = KSoundMaxVolume;
+ }
+
+void RMdaDevSound::CBody::GetFormat(TCurrentSoundFormatBuf& aFormat,
+ RSoundSc& /*aSoundDevice*/,
+ const TFormatData &aFormatData)
+ {
+ // always return the requested, or the initial, not current device setting
+ aFormat().iChannels = aFormatData.iRequestedChannels; // never clear if this is bitmap or value, but effectively the same
+ aFormat().iRate = aFormatData.iSampleRate;
+ }
+
+void RMdaDevSound::CBody::StartPlayersAndUpdateState()
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // Allow following code to queue more driver play requests and check for stopped
+ break;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ Panic(EBadState);
+ break;
+
+ case EPlaying:
+ // Allow following code to queue more driver play requests and check for stopped
+ break;
+ case EPlayingPausedInHw: // ie. Play request pending on h/w and paused
+ // Allow following code to queue more driver play requests
+ break;
+
+ case EPlayingPausedInSw:
+ // Paused but driver not playing+paused, therefore do not queue new requests until ResumePlaying
+ return;
+ case EPlayingUnderrun:
+ break;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ // iState is now either EStopped, EPlaying or EPlayingPausedInHw
+ __ASSERT_DEBUG(((iState == EStopped) || (iState == EPlaying) || (iState == EPlayingPausedInHw) || (iState == EPlayingUnderrun)), Panic(EBadState));
+
+ while( (iClientPlayData.Length() != 0) && (! iFreePlayers.IsEmpty()))
+ {
+ // More data to play and more players, so issue another request
+
+ bool wasIdle = iFreePlayers.IsFull();
+ // Get a free player
+ CPlayer *player = iFreePlayers.Pop();
+ // Calculate length of request
+ TUint32 lengthToPlay = iClientPlayData.Length();
+ if(lengthToPlay > iDeviceBufferLength)
+ {
+ lengthToPlay = iDeviceBufferLength;
+ }
+
+ // Remember request length, so we can update bytes played when it finishes
+ iActivePlayRequestSizes.Push(lengthToPlay);
+
+ // Find offset to copy data to
+ TUint playerIndex = player->GetPlayerIndex();
+ ASSERT(playerIndex < KPlaySharedChunkBuffers);
+ TUint chunkOffset = iPlayBufferConfig.iBufferOffsetList[playerIndex];
+
+ // Copy data
+ TPtr8 destPtr(iPlayChunk.Base()+ chunkOffset, 0, iDeviceBufferLength);
+ destPtr.Copy(iClientPlayData.Mid(0, lengthToPlay));
+
+ // Update iClientPlayData to remove the data just queued
+ iClientPlayData.Set(iClientPlayData.Right(iClientPlayData.Length()-lengthToPlay));
+
+ // Start the CPlayer
+ player->PlayData(chunkOffset, lengthToPlay);
+ if(wasIdle)
+ {
+ iState = EPlaying;
+ iStartTime = CurrentTimeInMsec();
+
+ }
+
+ }
+
+ // Check if the client request is now complete
+ if(iClientPlayData.Length() == 0 && iClientPlayStatus)
+ {
+ // We have queued all the client play data to the driver so we can now complete the client request.
+ // If actual playback fails, we will notify the client via the Play Error notification mechanism.
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::StartPlayersAndUpdateState completing client request");
+ #endif
+ User::RequestComplete(iClientPlayStatus, KErrNone); // This call also sets iClientPlayStatus to NULL
+ }
+
+ //nb. iState is now either EStopped, EPlaying or EPlayingPausedInHw (see previous switch and assert)
+ if(iState != EPlayingPausedInHw)
+ {
+ if(iFreePlayers.IsFull())
+ {
+ // Free fifo is full, therefore there are no active players
+ iState = EPlayingUnderrun;
+ if(! iUnderFlowReportedSinceLastPlayOrRecordRequest)
+ {
+ // We report KErrUnderflow if we have not already reported it since the last PlayData call.
+ // Note that
+ // i) We do NOT report driver underflows.
+ // ii) We report underflow when we run out of data to pass to the driver.
+ // iii) We throttle this reporting
+ // iv) We WILL report KErrUnderflow if already stopped and asked to play a zero length buffer
+ // The last point is required because the client maps a manual stop command into a devsound play with a
+ // zero length buffer and the last buffer flag set, this in turn is mapped to a Playdata calll with an empty buffer
+ // which is expected to complete ok and cause a KErrUnderflow error to be reported...
+ iUnderFlowReportedSinceLastPlayOrRecordRequest = ETrue;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::StartPlayersAndUpdateState stopped and iUnderFlowReportedSinceLastPlayOrRecordRequest false so raising KErrUnderflow");
+ #endif
+
+ // Discard any saved trailing data (ie. data saved due driver requiring all requests to be a multiple of iRequestMinSize).
+ // This maybe because client is delibrately letting us underflow to play silence. In that case we do not want to
+ // play the trailing data at the beginning of the new data issued after the silence...
+ iSavedTrailingData.SetLength(0);
+
+ SoundDeviceError(KErrUnderflow);
+ }
+ }
+ else
+ {
+ // Free fifo not full, therefore there are active players
+ iState = EPlaying;
+ }
+ }
+ return;
+ }
+
+TInt RMdaDevSound::CBody::SetFormat(const TCurrentSoundFormatBuf& aFormat,
+ RSoundSc& aSoundDevice,
+ TFormatData &aFormatData)
+ {
+ TInt err = KErrNotFound;
+ TCurrentSoundFormatV02Buf formatBuf;
+
+ delete aFormatData.iConverter;
+ aFormatData.iConverter = NULL; // setting this to NULL indicates we are not using converter. No other flag
+ iConvertedPlayData.Close();
+
+ TInt wantedRate = aFormat().iRate;
+ for(TInt index = 0; index < KNumSampleRates; index++ )
+ {
+ if(wantedRate == KRateEnumLookup[index].iRate)
+ {
+ formatBuf().iRate = KRateEnumLookup[index].iRateEnum;
+ aFormatData.iSampleRate = wantedRate;
+ err = KErrNone;
+ break;
+ }
+ }
+
+ if(err == KErrNone)
+ {
+ // Assume, for now, we support the requested channels and rate
+ aFormatData.iActualChannels = aFormatData.iRequestedChannels;
+ aFormatData.iActualRate = aFormatData.iSampleRate;
+
+ // Attempt to configure driver
+ formatBuf().iChannels = aFormat().iChannels;
+ formatBuf().iEncoding = ESoundEncoding16BitPCM;
+ formatBuf().iDataFormat = ESoundDataFormatInterleaved;
+ err = aSoundDevice.SetAudioFormat(formatBuf);
+ #if defined(SYMBIAN_SOUNDADAPTER_FORCECDRATES) || defined (SYMBIAN_SOUNDADAPTER_FORCESTEREO)
+ err = KErrNotSupported; // force Negotiate - for debugging
+ #endif
+ if (err==KErrNotSupported)
+ {
+ // don't support directly. Perhaps can rate convert?
+ err = NegotiateFormat(aFormat, aSoundDevice, aFormatData);
+ }
+ }
+ return err;
+ }
+
+TInt RMdaDevSound::CBody::NegotiateFormat(const TCurrentSoundFormatBuf& aFormat,
+ RSoundSc& aSoundDevice,
+ TFormatData &aFormatData)
+ {
+ ASSERT(!aFormatData.iConverter); // we don't clear on fail - so assuming NULL to start with
+
+ TInt err = KErrNotFound;
+ TCurrentSoundFormatV02Buf formatBuf;
+
+ // find out first what the driver supports
+ TSoundFormatsSupportedV02Buf supportedFormat;
+ aSoundDevice.Caps(supportedFormat);
+ TUint32 supportedRates = supportedFormat().iRates;
+ #ifdef SYMBIAN_SOUNDADAPTER_FORCECDRATES
+ supportedRates &= KSoundRate11025Hz| KSoundRate22050Hz | KSoundRate44100Hz; // only use CD rates - for debugging
+ #endif
+
+ // For PlayCase:
+ // first try to find the first rate below or equal to the requested that is supported
+ // initially go down and be fussy, but if we pass the requested rate find the first that
+ // is supported
+ // For RecordCase:
+ // We want the next rate above consistently - we go down from this to the requested rate.
+ // If there is one, we don't support - we _never_ upsample.
+ // note that the table is given in descending order, so we start with the highest
+ TInt wantedRate = aFormat().iRate;
+ TInt takeTheFirst = EFalse;
+ TInt nextUpValidIndex = -1;
+ for(TInt index = 0; index < KNumSampleRates; index++ )
+ {
+ TBool lookingAtRequestedRate = wantedRate == KRateEnumLookup[index].iRate;
+ TSoundRate wantedEnum = KRateEnumLookup[index].iRateEnum;
+ TUint32 equivBitmap = KRateEnumLookup[index].iRateConstant;
+ TBool isSupported = (equivBitmap & supportedRates) != EFalse;
+ if (lookingAtRequestedRate || takeTheFirst)
+ {
+ if (isSupported)
+ {
+ // this rate is supported
+ formatBuf().iRate = wantedEnum;
+ aFormatData.iActualRate = KRateEnumLookup[index].iRate;
+ err = KErrNone;
+ break;
+ }
+ }
+ else if (!takeTheFirst)
+ {
+ // while we are still looking for the rate, want to cache any supported index
+ // at end of loop, this will be the first rate above ours that is supported
+ // use for fallback if required
+ if (isSupported)
+ {
+ nextUpValidIndex = index;
+ }
+ }
+ if (lookingAtRequestedRate)
+ {
+ // if we get this far we've gone passed the wanted rate. For play we enable
+ // "takeTheFirst". For record we just abort.
+ if (&aSoundDevice==&iPlaySoundDevice)
+ {
+ takeTheFirst = ETrue;
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ if (err)
+ {
+ // if there is one above the requested rate, use that
+ if (nextUpValidIndex>=0)
+ {
+ TSoundRate wantedEnum = KRateEnumLookup[nextUpValidIndex].iRateEnum;
+ formatBuf().iRate = wantedEnum;
+ aFormatData.iActualRate = KRateEnumLookup[nextUpValidIndex].iRate;
+ err = KErrNone;
+ }
+ }
+
+ if (err)
+ {
+ // should have something!
+ return err;
+ }
+
+ aFormatData.iSampleRate = wantedRate; // iSampleRate is our requested/apparent rate, not the device rate.
+
+ TUint32 channelsSupported = supportedFormat().iChannels;
+ #ifdef SYMBIAN_SOUNDADAPTER_FORCESTEREO
+ channelsSupported &= KSoundStereoChannel; // don't use mono - for debugging
+ #endif
+ if(KSoundAdapterForceStereo==1)
+ {
+ channelsSupported &= KSoundStereoChannel;
+#ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("Added stereo support."));
+#endif
+ }
+ if (aFormat().iChannels == 1)
+ {
+ aFormatData.iRequestedChannels = 1;
+ // want mono
+ if (channelsSupported & KSoundMonoChannel)
+ {
+ // mono is supported, as usual
+ aFormatData.iActualChannels = 1;
+ }
+ else if (channelsSupported & KSoundStereoChannel)
+ {
+ aFormatData.iActualChannels = 2;
+ }
+ else
+ {
+ return KErrNotSupported; // should not get this far for real
+ }
+ }
+ else if (aFormat().iChannels == 2)
+ {
+ aFormatData.iRequestedChannels = 2;
+ // want stereo
+ if (channelsSupported & KSoundStereoChannel)
+ {
+ // stereo is supported, as usual
+ aFormatData.iActualChannels = 2;
+ }
+ else if (channelsSupported & KSoundMonoChannel)
+ {
+ aFormatData.iActualChannels = 1;
+ }
+ else
+ {
+ return KErrNotSupported; // should not get this far for real
+ }
+ }
+ else
+ {
+ return KErrNotSupported; // unknown number of channels requested!
+ }
+
+ formatBuf().iChannels = aFormatData.iActualChannels;
+
+ formatBuf().iEncoding = ESoundEncoding16BitPCM;
+ formatBuf().iDataFormat = ESoundDataFormatInterleaved;
+ err = aSoundDevice.SetAudioFormat(formatBuf);
+
+ if (!err)
+ {
+ ASSERT(!aFormatData.iConverter); // pre-condition at top of function anyway
+ if (&aSoundDevice==&iPlaySoundDevice)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::NegotiateFormat: Convert:CreateL from %d/%d to %d/%d"),
+ aFormatData.iSampleRate, aFormatData.iRequestedChannels,
+ aFormatData.iActualRate, aFormatData.iActualChannels);
+ #endif
+ // when playing we convert from requested to actual
+ TRAP(err, aFormatData.iConverter = CChannelAndSampleRateConverter::CreateL(aFormatData.iSampleRate,
+ aFormatData.iRequestedChannels,
+ aFormatData.iActualRate,
+ aFormatData.iActualChannels));
+ }
+ else
+ {
+ // when recording we convert from actual to requested
+ TInt outputRateToUse = aFormatData.iSampleRate;
+ #ifdef SYMBIAN_SKIP_RESAMPLE_ON_RECORD
+ // with this macro just channel convert at most
+ outputRateToUse = aFormatData.iActualRate;
+ #endif
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::NegotiateFormat: Convert:CreateL from %d/%d to %d/%d"),
+ aFormatData.iActualRate, aFormatData.iActualChannels,
+ aFormatData.iSampleRate, aFormatData.iRequestedChannels);
+ #endif
+ TRAP(err, aFormatData.iConverter = CChannelAndSampleRateConverter::CreateL(aFormatData.iActualRate,
+ aFormatData.iActualChannels,
+ outputRateToUse,
+ aFormatData.iRequestedChannels));
+ }
+ }
+ if(err != KErrNone)
+ {
+ delete aFormatData.iConverter;
+ aFormatData.iConverter= NULL;
+ iConvertedPlayData.Close();
+ }
+
+ return err;
+ }
+
+void RMdaDevSound::CBody::StartRecordRequest()
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+
+ iRecorder->RecordData(iBufferLength);
+ }
+
+// Note both InRecordMode and InPlayMode return EFalse for ENotReady and EStopped
+TBool RMdaDevSound::CBody::InRecordMode()const
+ {
+ switch(iState)
+ {
+ case ENotReady:
+ case EStopped:
+ return EFalse;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ return ETrue;
+
+ case EPlaying:
+ case EPlayingPausedInHw:
+ case EPlayingPausedInSw:
+ case EPlayingUnderrun:
+ return EFalse;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+ return EFalse;
+ }
+
+TBool RMdaDevSound::CBody::InPlayMode() const
+ {
+ switch(iState)
+ {
+ case ENotReady:
+ case EStopped:
+ return EFalse;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ return EFalse;
+
+ case EPlaying:
+ case EPlayingPausedInHw:
+ case EPlayingPausedInSw:
+ case EPlayingUnderrun:
+ return ETrue;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+ return EFalse;
+ }
+
+
+TUint32 RMdaDevSound::CBody::CurrentTimeInMsec() const
+ {
+ TUint64 tmp = User::NTickCount();
+ tmp *= iNTickPeriodInUsec;
+ tmp /= 1000;
+ return TUint32(tmp);
+ }
+
+void RMdaDevSound::CBody::PlayFormatsSupported(TSoundFormatsSupportedBuf& aFormatsSupported)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ FormatsSupported(aFormatsSupported, iPlaySoundDevice);
+ }
+
+void RMdaDevSound::CBody::GetPlayFormat(TCurrentSoundFormatBuf& aFormat)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ GetFormat(aFormat, iPlaySoundDevice, iPlayFormatData);
+ }
+
+TInt RMdaDevSound::CBody::SetPlayFormat(const TCurrentSoundFormatBuf& aFormat)
+ {
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ return SetFormat(aFormat, iPlaySoundDevice, iPlayFormatData);
+ }
+
+void RMdaDevSound::CBody::RecordFormatsSupported(TSoundFormatsSupportedBuf& aFormatsSupported)
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ FormatsSupported(aFormatsSupported, iRecordSoundDevice);
+ }
+
+void RMdaDevSound::CBody::GetRecordFormat(TCurrentSoundFormatBuf& aFormat)
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ GetFormat(aFormat, iRecordSoundDevice, iRecordFormatData);
+ }
+
+TInt RMdaDevSound::CBody::SetRecordFormat(const TCurrentSoundFormatBuf& aFormat)
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ return SetFormat(aFormat, iRecordSoundDevice, iRecordFormatData);
+ }
+
+void RMdaDevSound::CBody::Close()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("void RMdaDevSound::CBody::Close() started");
+ #endif
+ iBufferOffset = -1;
+ iBufferLength = 0;
+
+ if(iPlaySoundDevice.Handle() != KNullHandle)
+ {
+ // Make sure all player objects are idle
+ CancelPlayData();
+ iPlayChunk.Close();
+ iPlaySoundDevice.Close();
+ }
+
+ if(iRecordSoundDevice.Handle() != KNullHandle)
+ {
+ CancelRecordData();
+ iRecordChunk.Close();
+ iRecordSoundDevice.Close();
+ }
+
+ iState = ENotReady;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("void RMdaDevSound::CBody::Close() ended");
+ #endif
+ }
+
+TInt RMdaDevSound::CBody::Handle()
+ {//This method is actually used to check whether the device is opened. Below logic should work
+ if(iPlaySoundDevice.Handle())
+ {
+ return iPlaySoundDevice.Handle();
+ }
+ if(iRecordSoundDevice.Handle())
+ {
+ return iRecordSoundDevice.Handle();
+ }
+ return 0;
+ }
+
+
+void RMdaDevSound::CBody::PlayData(TRequestStatus& aStatus, const TDesC8& aData)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::PlayData(0x%x,%d) State=%s Handle=%d.",&aStatus,
+ aData.Length(), iState.Name(), iPlayChunk.Handle());
+ #endif
+
+ __ASSERT_DEBUG(iPlaySoundDevice.Handle(), Panic(EDeviceNotOpened));
+ aStatus = KRequestPending;
+
+ if((iClientPlayStatus != NULL) || InRecordMode())
+ {
+ // We only support one outstanding request
+ // No support for simultaneous play and record in RMdaDevSound
+ TRequestStatus *pRequest = &aStatus;
+ User::RequestComplete(pRequest, KErrInUse);
+ return;
+ }
+ iClientPlayStatus = &aStatus;//store the status of datapath player
+
+ if(iPlayFormatData.iConverter || iSavedTrailingData.Length() != 0)
+ {
+ // Need a conversion buffer
+ // Needs to hold any trailing data truncated from the previous request (due
+ // to alignment requirements) and either the new data, or the new rate adapted data
+ TUint32 spaceRequired = iSavedTrailingData.Length();
+ if(iPlayFormatData.iConverter)
+ {
+ // Doing rate conversion so also need space for the converted data
+ spaceRequired += iPlayFormatData.iConverter->MaxConvertBufferSize(aData.Length());
+ }
+ else
+ {
+ // Not doing rate adaptation therefore only need to allow for the new incoming data
+ spaceRequired += aData.Length();
+ }
+ // Check if existing buffer exists and is big enough
+ if(iConvertedPlayData.MaxLength() < spaceRequired)
+ {
+ iConvertedPlayData.Close();
+ TInt err = iConvertedPlayData.Create(spaceRequired);
+ if(err)
+ {
+ User::RequestComplete(iClientPlayStatus, err);
+ return;
+ }
+ }
+
+ // Truncate iConvertedPlayData and copy in saved trailing data (if any)
+ iConvertedPlayData = iSavedTrailingData;
+ iSavedTrailingData.SetLength(0);
+
+ // Now append rate adapted data or incoming data
+ if (iPlayFormatData.iConverter)
+ {
+ // The convertor will panic if it fails to convert any data, therefore
+ // we avoid passing it an empty source buffer
+ if(aData.Length() != 0)
+ {
+ TPtr8 destPtr((TUint8 *)iConvertedPlayData.Ptr()+iConvertedPlayData.Length(), 0, iConvertedPlayData.MaxLength()-iConvertedPlayData.Length());
+ TInt len = iPlayFormatData.iConverter->Convert(aData, destPtr);
+ iConvertedPlayData.SetLength(iConvertedPlayData.Length() + destPtr.Length());
+ if(len != aData.Length())
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::PlayData converted %d but expected to convert %d", len, aData.Length());
+ #endif
+ }
+ }
+ }
+ else
+ {
+ iConvertedPlayData.Append(aData);
+ }
+ iClientPlayData.Set(iConvertedPlayData);
+ }
+ else
+ {
+ // Do not need a conversion buffer so just aim the descriptor at the data
+ iClientPlayData.Set(aData);
+ }
+ iUnderFlowReportedSinceLastPlayOrRecordRequest = EFalse;
+
+ // All driver requests must be an exact multiple of iRequestMinSize
+ TUint32 trailingDataLen = iClientPlayData.Length() % iRequestMinSize;
+ if(trailingDataLen)
+ {
+ // Not a multiple of iRequestMinSize, so need to truncate current request, and save trailing bytes for
+ // inclusion at the beginning of the next request
+ iSavedTrailingData = iClientPlayData.Right(trailingDataLen);
+ iClientPlayData.Set(iClientPlayData.Left(iClientPlayData.Length()-trailingDataLen));
+ }
+
+ #ifdef SYMBIAN_FORCE_32BIT_LENGTHS
+ if (iClientPlayData.Length()%4 != 0)
+ {
+ // simulate the limitation of some hardware, where -6 is generated if the
+ // buffer length is not divisible by 4.
+ TRequestStatus *pRequest = &aStatus;
+ User::RequestComplete(pRequest, KErrArgument);
+ }
+ #endif
+
+ iRecordChunk.Close();
+ if(!iPlayChunk.Handle())
+ {
+ //This is where we setup to play.
+ //Configure the shared chunk for two buffers with iBufferSize each
+ iPlayBufferConfig.iNumBuffers = KPlaySharedChunkBuffers;
+ iDeviceBufferLength = KPlaySharedChunkBufferSize;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("iDeviceBufferLength %d", iDeviceBufferLength);
+ #endif
+ iPlayBufferConfig.iFlags = 0;//data will be continuous
+ // If required, use rate converter etc
+ iPlayBufferConfig.iBufferSizeInBytes = iDeviceBufferLength;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("number of buffers: [%d]",iPlayBufferConfig.iNumBuffers);
+ RDebug::Printf("BufferSize in Bytes [%d]",iPlayBufferConfig.iBufferSizeInBytes);
+ #endif
+ TPckg<TPlaySharedChunkBufConfig> bufferConfigBuf(iPlayBufferConfig);
+ TInt error = iPlaySoundDevice.SetBufferChunkCreate(bufferConfigBuf,iPlayChunk);
+ if(error == KErrNone)
+ {
+ iPlaySoundDevice.GetBufferConfig(bufferConfigBuf);
+ }
+ if (error)
+ {
+ SoundDeviceError(error);
+ return;
+ }
+ }
+
+ StartPlayersAndUpdateState();
+
+ return;
+ }
+
+void RMdaDevSound::CBody::RecordData(TRequestStatus& aStatus, TDes8& aData)
+ {
+ __ASSERT_DEBUG(iRecordSoundDevice.Handle(), Panic(EDeviceNotOpened));
+ aStatus = KRequestPending;
+ if((iClientPlayStatus != NULL) || InPlayMode())
+ {
+ // We only support one outstanding request
+ // No support for simultaneous play and record in RMdaDevSound
+ TRequestStatus *pRequest = &aStatus;
+ User::RequestComplete(pRequest, KErrInUse);
+ return;
+ }
+ iClientRecordStatus = &aStatus;
+ iClientRecordData = &aData;
+ iUnderFlowReportedSinceLastPlayOrRecordRequest = EFalse;
+
+ iPlayChunk.Close();
+ if(!iRecordChunk.Handle())
+ {
+ //Configure the shared chunk for two buffers with iBufferSize each
+ iRecordBufferConfig.iNumBuffers = KRecordMaxSharedChunkBuffers;
+ iDeviceBufferLength = KRecordSharedChunkBufferSize; // initial size - resize if needs be
+ if (iRecordFormatData.iConverter)
+ {
+ // if number of channels used differs from request, resize buffer
+ // assume we have nice rounded values for buffer.
+ if (iRecordFormatData.iActualChannels>iRecordFormatData.iRequestedChannels)
+ {
+ iDeviceBufferLength *= 2; // will record at stereo and convert to mono
+ }
+ else if (iRecordFormatData.iActualChannels<iRecordFormatData.iRequestedChannels)
+ {
+ iDeviceBufferLength /= 2; // will record at mono and convert to stereo
+ }
+ }
+ iRecordBufferConfig.iBufferSizeInBytes = iDeviceBufferLength;
+ iRecordBufferConfig.iFlags = 0;
+ TPckg<TRecordSharedChunkBufConfig> bufferConfigBuf(iRecordBufferConfig);
+ TInt error = iRecordSoundDevice.SetBufferChunkCreate(bufferConfigBuf,iRecordChunk);
+ if(error == KErrNone)
+ {
+ iRecordSoundDevice.GetBufferConfig(bufferConfigBuf);
+ }
+ else
+ {
+ SoundDeviceError(error);
+ return;
+ }
+ iState = ERecording;
+ }
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::RecordData,iBufferOffset[%d]",iBufferOffset);
+ #endif
+
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EBadState);
+ break;
+
+ case EStopped:
+ case ERecording:
+ // Either idle or recording is in progress, therefore we can issue another request
+ StartRecordRequest();
+ break;
+
+ case ERecordingPausedInHw:
+ // Driver is paused, therefore we can issue a request which will immediately return buffered data
+ // or be aborted (in the driver) with KErrCancelled if there is no more data). nb. That KErrCancelled should not be
+ // returned to the client because the old RMdaDevSound driver would have completed with KErrNone and zero data length.
+ StartRecordRequest();
+ break;
+
+ case ERecordingPausedInSw:
+ // Paused in s/w but driver is not paused, therefore can not issue a new request to driver because
+ // it would re-start recording.
+ // This implies we were paused whilst the h/w was not recording, so there is no buffered data.
+
+ // Complete the request with KErrNone and no data.
+ iClientRecordData->SetLength(0);
+ User::RequestComplete(iClientRecordStatus, KErrNone);
+ break;
+
+ case EPlaying:
+ case EPlayingPausedInHw:
+ case EPlayingPausedInSw:
+ case EPlayingUnderrun:
+ Panic(EBadState);
+ break;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+ }
+
+/**
+ Notify client of error.
+
+ Note that we continue playing/recording if possible.
+
+ We do not maintain information which could map the error back to a particular client play/record request
+ and therefore we have to notify the client of error every time it happens.
+
+ nb. A client play/record request is completed with KErrNone if it queues ok - All errors are reported via the Notify*Error
+ mechanism.
+ */
+void RMdaDevSound::CBody::SoundDeviceError(TInt aError)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::SoundDeviceError: Error[%d] state %s", aError, iState.Name());
+ #endif
+
+ ASSERT(aError != KErrNone);
+
+ if(iClientPlayErrorStatus)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::SoundDeviceError Completing iPlayerErrorStatus");
+ #endif
+
+ User::RequestComplete(iClientPlayErrorStatus, aError); // nb call also sets iClientPlayErrorStatus to NULL
+ }
+
+ if(iClientRecordErrorStatus)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::SoundDeviceError Completing iClientRecordErrorStatus");
+ #endif
+ User::RequestComplete(iClientRecordErrorStatus, aError); // nb call also sets iClientRecordErrorStatus to NULL
+ }
+
+ return;
+ }
+
+void RMdaDevSound::CBody::NotifyRecordError(TRequestStatus& aStatus)
+ {
+ aStatus = KRequestPending;
+ iClientRecordErrorStatus = &aStatus;
+ }
+
+void RMdaDevSound::CBody::NotifyPlayError(TRequestStatus& aStatus)
+ {
+ aStatus = KRequestPending;
+ iClientPlayErrorStatus = &aStatus;
+ }
+
+void RMdaDevSound::CBody::CancelNotifyPlayError()
+ {
+ if(iClientPlayErrorStatus)
+ {
+ User::RequestComplete(iClientPlayErrorStatus, KErrCancel);
+ }
+ }
+
+void RMdaDevSound::CBody::CancelNotifyRecordError()
+ {
+ if(iClientRecordErrorStatus)
+ {
+ User::RequestComplete(iClientRecordErrorStatus, KErrCancel);
+ }
+ else
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("msp BufferEmptied but iClientPlayStatus==NULL");
+ #endif
+ }
+ }
+
+void RMdaDevSound::CBody::FlushPlayBuffer()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::FlushPlayBuffer calling CancelPlayData");
+ #endif
+ CancelPlayData();
+ }
+
+RSoundSc& RMdaDevSound::CBody::PlaySoundDevice()
+ {
+ return iPlaySoundDevice;
+ }
+
+RSoundSc& RMdaDevSound::CBody::RecordSoundDevice()
+ {
+ return iRecordSoundDevice;
+ }
+
+const RMdaDevSound::CBody::TState &RMdaDevSound::CBody::State() const
+ {
+ return iState;
+ }
+
+
+void RMdaDevSound::CBody::BufferFilled(TInt aBufferOffset)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::BufferFilled:"));
+ #endif
+
+ ASSERT(aBufferOffset>=0 || aBufferOffset==KErrCancel);
+ ASSERT(iClientRecordData); // request should not get this without
+
+ if(aBufferOffset==KErrCancel)
+ {
+ //we can get KErrCancel when we call pause and there is no more data left with the driver
+ //we send the empty buffer to the HwDevice, where this should trigger the shutdown mechanism
+ iClientRecordData->SetLength(0);
+ User::RequestComplete(iClientRecordStatus, KErrNone);
+ iClientRecordStatus = NULL;
+ return;
+ }
+
+ iBufferOffset = aBufferOffset;
+ //when last buffer is flushed, new driver sometimes gives buffer size of odd number. One of our codecs
+ //expects that the buffer size should always be even. Base suggested that we fix in multimedia
+ //as it is quite complicated to fix in overthere.
+ iBufferLength = iBufferLength & 0xfffffffe;
+ TPtr8 dataPtr(iRecordChunk.Base()+ iBufferOffset, iBufferLength, iClientRecordData->MaxLength());
+ if (iRecordFormatData.iConverter)
+ {
+ iRecordFormatData.iConverter->Convert(dataPtr, *iClientRecordData);
+ }
+ else
+ {
+ iClientRecordData->Copy(dataPtr);
+ }
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::BufferFilled: BufferOffset[%d] BufferLen[%d]"), iBufferOffset, iBufferLength);
+ #endif
+ if(iBufferOffset >= 0)
+ {
+ iRecordSoundDevice.ReleaseBuffer(iBufferOffset);
+ }
+ if(iClientRecordStatus)
+ {
+ User::RequestComplete(iClientRecordStatus, KErrNone);
+ iClientRecordStatus = NULL;
+ }
+ else
+ {
+ RDebug::Printf("msp PlayCancelled but iClientPlayStatus==NULL");
+ }
+ }
+
+/*
+ This function is called to notify us that a CPlayer's request has completed and what its status was.
+
+ It is important to note that:-
+ 1) RSoundSc driver PlayData requests are guaranteed to complete in order, oldest first
+ 2) If we are overloaded, it is possible for more than one request to complete before any CPlayer::RunL is ran. In
+ this situation the CPlayer::RunL functions, and hence this callback, maybe invoked in non-oldest first order
+
+ but
+
+ a) It is impossible for callback for the second oldest CPlayer to occur before the driver request for the oldest has
+ been complete (because of 1)
+ b) We will always get exactly one callback for every complete request.
+
+ Therefore this callback notifies us of two subtly separate things:-
+
+ i) The oldest request has been completed (so we can reduce can increase the bytes played counter by its length
+ ii) CPlayer aPlayerIndex is free for re-use
+
+ but we can not assume that aPlayerIndex is the oldest request, therefore we save the play request lengths outside of
+ the CPlayer object.
+*/
+void RMdaDevSound::CBody::PlayRequestHasCompleted(CPlayer *aPlayer, TInt aStatus, TBool aDueToCancelCommand)
+ {
+ // CPlayer is done so put it on the free queue
+ iFreePlayers.Push(aPlayer);
+
+ TUint32 bytesPlayed = iActivePlayRequestSizes.Pop();
+ // Request has finished therefore now timing the following request to simulate bytes played
+ iStartTime = CurrentTimeInMsec();
+ if(aDueToCancelCommand)
+ {
+ // Callback due to CPlayer::Cancel/DoCancel being called, therefore we
+ // do not want to update bytes played, process state, report a error or start new players
+ return;
+ }
+
+ // Update iBytesPlayed by the length of the oldest request (which might not be the one that CPlayer was
+ // handling).
+ iBytesPlayed += bytesPlayed;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("PlayRequestHasCompleted increasing iBytesPlayed by %d to %d", bytesPlayed, iBytesPlayed);
+ #endif
+
+ // Process state
+ switch(iState)
+ {
+ case ENotReady:
+ Panic(EDeviceNotOpened);
+ break;
+
+ case EStopped:
+ // Will happen if we are doing CancelPlayData processing with active players
+ break;
+
+ case ERecording:
+ case ERecordingPausedInHw:
+ case ERecordingPausedInSw:
+ Panic(EBadState);
+ break;
+
+ case EPlaying:
+ // Normal situation
+ break;
+
+ case EPlayingPausedInHw:
+ // H/W was/is paused, but there must have been an already complete request that we had not
+ // processed yet.
+ // There must be at least one more pending request on h/w, otherwise the h/w would have refused to pause
+ // I would expect this be rare, but it happens quite often...
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ ASSERT(iActivePlayRequestSizes.Length() != 0);
+ #endif
+ // Need to update the start and pause time to now because we have just updated the actual iBytesPlayed
+ // and logically the h/w is paused at the beginning of the next request
+ iStartTime = CurrentTimeInMsec();
+ iPauseTime = iStartTime;
+ break;
+
+ case EPlayingPausedInSw:
+ // This will happen if there is only a single hw request outstanding, and the hardware has finished it, but the
+ // corresponding RunL has not run yet (in which case PausePlayBuffer will have attempted to use h/w pause,
+ // but the driver call would have failed, and the state changed to EPlayingPausedInSw).
+ iStartTime = CurrentTimeInMsec();
+ iPauseTime = iStartTime;
+ return;
+ case EPlayingUnderrun:
+ break;
+
+ default:
+ Panic(EBadState);
+ break;
+ }
+
+
+ // If we have an error, report it to the client
+ // We NEVER report driver underflow, instead we report KErrUnderflow if we run out of data to pass to driver.
+ if( (aStatus != KErrNone) && (aStatus != KErrUnderflow) )
+ {
+ SoundDeviceError(aStatus);
+ }
+
+ // If appropriate start more players
+ StartPlayersAndUpdateState();
+ return;
+ }
+
+RMdaDevSound::CBody::CPlayer::CPlayer(TInt aPriority, RMdaDevSound::CBody& aParent, TInt aIndex):
+ CActive(aPriority), iParent(aParent), iIndex(aIndex), iBufferOffset(-1), iBufferLength(0)
+ {
+ CActiveScheduler::Add(this);
+ }
+
+RMdaDevSound::CBody::CPlayer::~CPlayer()
+ {
+ Cancel();
+ }
+
+
+void RMdaDevSound::CBody::CPlayer::RunL()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("****RMdaDevSound::CBody::CPlayer(%d)::RunL: Error[%d] ParentState[%s]",
+ iIndex, iStatus.Int(), iParent.State().Name());
+ RDebug::Printf("iActivePlayRequestSizes.Length() = %d iFreePlayers.Length() = %d (including this one as active)",
+ iParent.iActivePlayRequestSizes.Length(),
+ iParent.iFreePlayers.Length());
+ #endif
+ iParent.PlayRequestHasCompleted(this, iStatus.Int(), EFalse);
+ return;
+ }
+
+TInt RMdaDevSound::CBody::CPlayer::RunError(TInt aError)
+ {
+ iParent.PlayRequestHasCompleted(this, aError, EFalse);
+ return KErrNone;
+ }
+
+void RMdaDevSound::CBody::CPlayer::DoCancel()
+ {
+#ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::CPlayer(%d)::DoCancel", iIndex);
+#endif
+ if(iStatus == KRequestPending)
+ {
+ // Avoid cancelling requests which have already completed.
+ // It wastes time, and might provoke a sound driver problem
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::CPlayer::DoCancel - would have cancelled driver request");
+ #endif
+ iParent.PlaySoundDevice().Cancel(iStatus);
+ }
+ iParent.PlayRequestHasCompleted(this, KErrCancel, ETrue);
+ }
+
+void RMdaDevSound::CBody::CPlayer::PlayData(TUint aChunkOffset, TInt aLength)
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::CPlayer(%d)::PlayData : IsActive[%d]"),
+ iIndex, IsActive());
+ RDebug::Printf("iActivePlayRequestSizes.Length() = %d iFreePlayers.Length() = %d (inc this player)",
+ iParent.iActivePlayRequestSizes.Length(),
+ iParent.iFreePlayers.Length());
+ #endif
+
+ iBufferOffset = aChunkOffset;
+ iBufferLength = aLength;
+
+ //Make sure the length is a multiple of 4 to work around an h6 limitation.
+ iBufferLength = iBufferLength & 0xfffffffc;
+
+ // Issue the RSoundSc request
+ iParent.PlaySoundDevice().PlayData(iStatus, iBufferOffset, iBufferLength, EFalse);
+ SetActive();
+ return;
+ }
+
+TUint RMdaDevSound::CBody::CPlayer::GetPlayerIndex() const
+ {
+ return iIndex;
+ }
+
+RMdaDevSound::CBody::CRecorder::CRecorder(TInt aPriority, RMdaDevSound::CBody& aParent):
+ CActive(aPriority), iParent(aParent), iBufferOffset(-1), iBufferLength(0)
+ {
+ CActiveScheduler::Add(this);
+ }
+
+RMdaDevSound::CBody::CRecorder::~CRecorder()
+ {
+ Cancel();
+ }
+
+void RMdaDevSound::CBody::CRecorder::RecordData(TInt& aBufferLength)
+ {
+ if (!IsActive())
+ {
+ iStatus = KRequestPending;
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("Recording request: BufferLength[%d]", aBufferLength);
+ #endif
+ iParent.RecordSoundDevice().RecordData(iStatus, aBufferLength);
+ SetActive();
+ }
+ }
+
+void RMdaDevSound::CBody::CRecorder::RunL()
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("****RMdaDevSound::CBody::CRecorder()::RunL: Error[%d] ParentState[%s]",
+ iStatus.Int(), iParent.State().Name());
+ #endif
+
+
+ TInt error = iStatus.Int();
+
+ if((error >= 0) || (error == KErrCancel))
+ {//we can get KErrCancel when we call pause and there is no more data left with the driver
+ iParent.BufferFilled(error);
+ }
+ else
+ {
+ #ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Print(_L("RMdaDevSound::CBody::CPlayer()::RunL: Error[%d]"), error);
+ #endif
+ iParent.SoundDeviceError(error);
+ }
+ }
+
+
+TInt RMdaDevSound::CBody::CRecorder::RunError(TInt aError)
+ {
+ iParent.SoundDeviceError(aError);
+ return KErrNone;
+ }
+
+void RMdaDevSound::CBody::CRecorder::DoCancel()
+ {
+#ifdef SYMBIAN_SOUNDADAPTER_DEBUG
+ RDebug::Printf("RMdaDevSound::CBody::CRecorder()::DoCancel");
+#endif
+ iParent.RecordSoundDevice().Cancel(iStatus);
+ }
+
+
+// End of file