diff -r 000000000000 -r 79dd3e2336a0 devsound/devsoundrefplugin/src/swcodecwrapper/mmfswaudioinput.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/devsound/devsoundrefplugin/src/swcodecwrapper/mmfswaudioinput.cpp Fri Oct 08 19:40:43 2010 +0100 @@ -0,0 +1,1430 @@ +// 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: +// mmfswaudioinput.cpp +// +// +#include "mmfswaudioinput.h" +#include "mmfswaudioinputpriv.h" +#include +#include +#include "mmf/utils/rateconvert.h" // if we need to resample +_LIT(KPddFileName,"SOUNDSC.PDD"); +_LIT(KLddFileName,"ESOUNDSC.LDD"); + +#ifdef SYMBIAN_SWCODEC_LOGGING + +const TText* const KStateNames[] = // must agree with TState + { + _S("EStateCreated2"), + _S("EStateInitialized2"), + _S("EStateRecordWait2"), + _S("EStateRecordWaitAck2"), + }; + +static const TText* StateName(TInt aState) + { + return KStateNames[aState]; + } + +const TText* const KRStateNames[] = // must agree with TRunningState + { + _S("ERStateRunning"), + _S("ERStatePaused"), + _S("ERStateFinishing"), + _S("ERStateFinished"), + _S("ERStateFailed"), + }; + +static const TText* RStateName(TInt aState) + { + return KRStateNames[aState]; + } + +#endif // SYMBIAN_SWCODEC_LOGGING + +#ifdef _DEBUG + +static void Panic(TInt aPanic) + { + _LIT(KPanicString, "SwAudioInput"); + User::Panic(KPanicString, aPanic); + } + +void CAudioInput::CheckFullInvariant() + { + CheckInvariant(); + CheckActiveRecorders(); + } + +void CAudioInput::CheckInvariant(TBool aKnownConstructed) + { + // full check would be that each recorder is in one, and only one, queue. + // However, since the queues share the same infrastructure, checking the overall length of queues + // is correct should suffice. During construction or deletion this may obviously vary + TInt totalLength = QLength(iIdleQueue) + QLength(iRecordingQueue) + + QLength(iPendingQueue) + QLength(iBusyQueue); + if (aKnownConstructed) + { + __ASSERT_DEBUG(totalLength==KNumRecorders, Panic(KPanicBadTotalQueueLength)); + } + else + { + __ASSERT_DEBUG(totalLength<=KNumRecorders, Panic(KPanicBadTotalQueueLength2)); + } + __ASSERT_DEBUG(QLength(iBusyQueue)<=1, Panic(KPanicBadTotalQueueLength)); + } + +#else // _DEBUG + +// inline versions that do nothing... + +inline void CAudioInput::CheckFullInvariant() + { + } + +inline void CAudioInput::CheckInvariant(TBool /*aKnownConstructed*/) + { + } + +#endif // _DEBUG + +const TInt KMinBufferSize = 4; // assume a good default? +//Shared chunk driver does not support max. buffer size. 16K is given in order to simulate the old driver behavior. +const TInt KMaxBufferSize = 0x4000; + +//Table that maps given linear value of volume to the corresponding decibel value. +const TUint8 KLinearToDbConstantLookup[] = + { + 0, // 0 + 158, + 170, + 177, + 182, + 186, + 189, + 192, + 194, + 196, + 198, // 10 + 200, + 201, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 210, // 20 + 211, + 212, + 213, + 213, + 214, + 215, + 215, + 216, + 217, + 217, // 30 + 218, + 218, + 219, + 219, + 220, + 220, + 221, + 221, + 222, + 222, // 40 + 223, + 223, + 224, + 224, + 224, + 225, + 225, + 225, + 226, + 226, // 50 + 226, + 227, + 227, + 227, + 228, + 228, + 228, + 229, + 229, + 229, // 60 + 230, + 230, + 230, + 230, + 231, + 231, + 231, + 231, + 232, + 232, // 70 + 232, + 232, + 233, + 233, + 233, + 233, + 234, + 234, + 234, + 234, // 80 + 235, + 235, + 235, + 235, + 235, + 236, + 236, + 236, + 236, + 236, // 90 + 237, + 237, + 237, + 237, + 237, + 237, + 238, + 238, + 238, + 238, // 100 + 238, + 239, + 239, + 239, + 239, + 239, + 239, + 240, + 240, + 240, // 110 + 240, + 240, + 240, + 240, + 241, + 241, + 241, + 241, + 241, + 241, // 120 + 241, + 242, + 242, + 242, + 242, + 242, + 242, + 242, + 243, + 243, // 130 + 243, + 243, + 243, + 243, + 243, + 244, + 244, + 244, + 244, + 244, // 140 + 244, + 244, + 244, + 245, + 245, + 245, + 245, + 245, + 245, + 245, // 150 + 245, + 245, + 246, + 246, + 246, + 246, + 246, + 246, + 246, + 246, // 160 + 246, + 247, + 247, + 247, + 247, + 247, + 247, + 247, + 247, + 247, // 170 + 247, + 248, + 248, + 248, + 248, + 248, + 248, + 248, + 248, + 248, // 180 + 248, + 249, + 249, + 249, + 249, + 249, + 249, + 249, + 249, + 249, // 190 + 249, + 250, + 250, + 250, + 250, + 250, + 250, + 250, + 250, + 250, // 200 + 250, + 250, + 250, + 251, + 251, + 251, + 251, + 251, + 251, + 251, // 210 + 251, + 251, + 251, + 251, + 251, + 252, + 252, + 252, + 252, + 252, // 220 + 252, + 252, + 252, + 252, + 252, + 252, + 252, + 252, + 253, + 253, // 230 + 253, + 253, + 253, + 253, + 253, + 253, + 253, + 253, + 253, + 253, // 240 + 253, + 254, + 254, + 254, + 254, + 254, + 254, + 254, + 254, + 254, // 250 + 254, + 254, + 254, + 254, + 254 + }; + +// rate lookup table + +const TInt KNumSampleRates = 9; + +struct TSampleRateEnumTable + { + TInt iRate; + TSoundRate iRateEnum; + TUint iRateConstant; + }; +//Table that maps given samples per second to the corresponding enums in RSoundSc +const TSampleRateEnumTable KRateEnumLookup[] = + { + {48000,ESoundRate48000Hz,KSoundRate48000Hz}, + {44100,ESoundRate44100Hz,KSoundRate44100Hz}, + {32000,ESoundRate32000Hz,KSoundRate32000Hz}, + {24000,ESoundRate24000Hz,KSoundRate24000Hz}, + {22050,ESoundRate22050Hz,KSoundRate22050Hz}, + {16000,ESoundRate16000Hz,KSoundRate16000Hz}, + {12000,ESoundRate12000Hz,KSoundRate12000Hz}, + {11025,ESoundRate11025Hz,KSoundRate11025Hz}, + {8000, ESoundRate8000Hz, KSoundRate8000Hz} + }; + +// TAudioInputParams + +EXPORT_C TAudioInputParams::TAudioInputParams() : + iSampleRate(0), iNominalBufferSize(0) + { + // none + } + +// CAudioInput + +EXPORT_C MAudioInput* MAudioInput::CreateL(MAudioInputObserver& aObserver) + { + MAudioInput* result = CAudioInput::NewL(aObserver); + return result; + } + +CAudioInput* CAudioInput::NewL(MAudioInputObserver& aObserver) + { + CAudioInput* result = new CAudioInput(aObserver); + CleanupStack::PushL(result); + result->ConstructL(); + CleanupStack::Pop(result); + return result; + } + +CAudioInput::CAudioInput(MAudioInputObserver& aObserver) : + iObserver(aObserver), + iIdleQueue(_FOFF(CRecorder,iLink)), + iRecordingQueue(_FOFF(CRecorder,iLink)), + iPendingQueue(_FOFF(CRecorder,iLink)), + iBusyQueue(_FOFF(CRecorder,iLink)) + { + ASSERT(iState == EStateCreated2); // assume zero'ing initialises correctly + } + +void CAudioInput::Release() +// effective destructor call + { + delete this; + } + +TAny* CAudioInput::Interface(TUid aInterfaceUid) + { + if (aInterfaceUid == KUidAIParamInterface) + { + MAIParamInterface* self = this; + return self; + } + return NULL; + } + +RSoundSc& CAudioInput::RecordSoundDevice() + { + ASSERT(iRecordSoundDevice.Handle()!=0); // should be open + return iRecordSoundDevice; + } + +CAudioInput::~CAudioInput() + { + CheckInvariant(EFalse); // may not be constructed + Cancel(); + for (TInt i = 0; i < KNumRecorders; i++) + { + // just in case, call cancel directly from this point too + // Cancel depends on the active queue, and might not be quite the same. + CRecorder* recorder = iRecorders[i]; + if (recorder) + { + recorder->Cancel(); + } + delete recorder; + } + delete iAsyncCallBack; + iConvBuff.Close(); + iRecordSoundDevice.Close(); + iChunk.Close(); + } + +void CAudioInput::Cancel() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Cancel()")); +#endif + CancelRecorders(); + if (iAsyncCallBack) + { + iAsyncCallBack->Cancel(); + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Cancel()")); +#endif + } + +void CAudioInput::CancelRecorders() +// if a recorder is active, then cancel it. Also move the list if required. + { + CheckInvariant(); // semi-invariant check - this is called from destructor + + CRecorder* recorder; + while (QPop(recorder, iRecordingQueue)) + { + recorder->Cancel(); + iIdleQueue.AddLast(*recorder); + } + CheckFullInvariant(); + } + +void CAudioInput::CancelPendingRecorders() +// take any recorder in the pending queue. ack the buffer and send to idle + { + CheckFullInvariant(); + + CRecorder* recorder; + while (QPop(recorder, iPendingQueue)) + { + recorder->ReleaseBuffer(); + iIdleQueue.AddLast(*recorder); + } + CheckFullInvariant(); + } + +void CAudioInput::CancelBusyRecorder() +// take busy recorder. ack the buffer and send to idle + { + CheckFullInvariant(); + + CRecorder* recorder; + if (QPop(recorder, iBusyQueue)) + { + recorder->ReleaseBuffer(); + iIdleQueue.AddLast(*recorder); + } + CheckFullInvariant(); + } + +void CAudioInput::RecordAllIdle() +// take any recorder in idle queue and set recording + { + CheckFullInvariant(); + + CRecorder* recorder; + while (QPop(recorder, iIdleQueue)) + { + recorder->RecordData(); + iRecordingQueue.AddLast(*recorder); + } + CheckFullInvariant(); + } + +void CAudioInput::ConstructL() + { + for (TInt i = 0; i < KNumRecorders; i++) + { + iRecorders[i] = new (ELeave) CRecorder(*this, i); + iIdleQueue.AddLast(*(iRecorders[i])); + } + iAsyncCallBack = new (ELeave) CAsyncCallBack(CActive::EPriorityStandard); + TCallBack callback(Callback, this); + iAsyncCallBack->Set(callback); + User::LoadPhysicalDevice(KPddFileName); + User::LoadLogicalDevice(KLddFileName); + CheckFullInvariant(); + } + +TInt CAudioInput::Initialize(const TAudioInputParams& aParams) + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Initialize() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + TInt error = KErrNone; + if (iState == EStateCreated2) + { + if (!iRecordSoundDevice.Handle()) + { + error = iRecordSoundDevice.Open(KSoundScRxUnit0); + if (error) + { + Close(); // TODO Close() required? + } + } + if (!error) + { + iBufferLength = aParams.iNominalBufferSize; // will be updated by SetFormat() if required + error = SetFormat(aParams); + if (!error) + { + iRecordBufferConfig.iNumBuffers = KNumRecorders*2; // for each AO we create two buffers + iRecordBufferConfig.iFlags = 0; + iRecordBufferConfig.iBufferSizeInBytes = iBufferLength; + ASSERT(iChunk.Handle()==0); // should not be already open + TPckg bufferConfigBuf( + iRecordBufferConfig); + error = iRecordSoundDevice.SetBufferChunkCreate( + bufferConfigBuf, iChunk); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print( + _L("iRecordBufferConfig.iNumBuffers = [%d]"),iRecordBufferConfig.iNumBuffers); + RDebug::Print( + _L("iRecordBufferConfig.iFlags = [%d]"),iRecordBufferConfig.iFlags); + RDebug::Print( + _L("iRecordBufferConfig.iBufferSizeInBytes = [%d]"),iRecordBufferConfig.iBufferSizeInBytes); +#endif + if (error == KErrNone) + { + ASSERT(iChunk.Handle()); // should now be open + iRecordSoundDevice.GetBufferConfig(bufferConfigBuf); // overwrite iRecordBufferConfig + SetGain(aParams.iInitialGain); + iState = EStateInitialized2; + } + } + } + } + else + { + error = KErrNotReady; + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Initialize(%d) state=%s rstate=%s"), error, StateName( + iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + return error; + } + +void CAudioInput::Close() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Close() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + InternalStop(); // Technically this should not be required, as client should Stop() first, but just in case + if (iState == EStateInitialized2) + { + iRecordSoundDevice.Close(); + iChunk.Close(); + iConvBuff.Close(); + iState = EStateCreated2; + } + ASSERT(iState==EStateCreated2); + ASSERT(QLength(iIdleQueue)==KNumRecorders); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Close() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + } + +TInt CAudioInput::Start() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Start() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + TInt error = KErrNone; + if (iState == EStateInitialized2) + { + RecordAllIdle(); + iState = EStateRecordWait2; + iRState = ERStateRunning; + } + else + { + error = KErrNotReady; + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Start(%d) state=%s rstate=%s"), + error, StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + return error; + } + +void CAudioInput::BufferAck() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::BufferAck() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + ASSERT(iState==EStateRecordWaitAck2); + HandleBufferAck(); + iState = EStateRecordWait2; + RequestCallback(); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::BufferAck() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + } + +void CAudioInput::HandleBufferAck() + { + CRecorder* recorder = QPop(iBusyQueue); + recorder->ReleaseBuffer(); + if (iRState == ERStateRunning) + { + recorder->RecordData(); + iRecordingQueue.AddLast(*recorder); + } + else + { + iIdleQueue.AddLast(*recorder); + if (iRState == ERStatePaused && (QLength(iRecordingQueue)+QLength(iPendingQueue) == 0)) + { + iRState = ERStateFinishing; + } + } + } + +TInt CAudioInput::Pause() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Pause() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + TInt err = KErrNone; // note we are silent if called in wrong state + +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print( + _L("***!pause irecordingquelength %d pendingquelength %d ibusyquelength=%d"), QLength(iRecordingQueue), + QLength(iPendingQueue), QLength(iBusyQueue)); +#endif + if ((iState == EStateRecordWait2 || iState == EStateRecordWaitAck2) && iRState==ERStateRunning) + { + iRecordSoundDevice.Pause(); + + iRState = ERStatePaused; + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Pause(%d) state=%s err=%d"), err, StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + return err; + } + +TInt CAudioInput::Resume() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Resume() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + TInt err = KErrNone; // note we are silent if called in the wrong state + if ((iState == EStateRecordWait2 || iState == EStateRecordWaitAck2) && + ((iRState==ERStatePaused || iRState==ERStateFinishing || iRState==ERStateFinished))) + { + err = RecordSoundDevice().Resume(); + + RecordAllIdle(); + + iRState = ERStateRunning; + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Resume(%d) state=%s rstate=%s"), err, StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + return err; + } + +TInt CAudioInput::Flush() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Flush() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + TInt error = KErrNotReady; + if (iRState==ERStatePaused) + { + if (iState == EStateRecordWait2) + { + InternalFlush(); + ASSERT(iState == EStateRecordWait2); // stay put + error = KErrNone; + } + else if (iState == EStateRecordWaitAck2) + { + InternalFlush(); + iState = EStateRecordWait2; + error = KErrNone; + } + } + #ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Flush(%d) state=%s rstate=%s"), error, StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + return error; + } + +void CAudioInput::Stop() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::Stop() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + InternalStop(); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::Stop() state=%s rstate=%s"), StateName(iState), RStateName(iRState)); +#endif + } + +void CAudioInput::InternalStop() +// This stops all recording and returns pending and busy buffers to idle. Must be called when +// client knows the buffer has been grabbed (e.g. _not_ before error callback) + { + CheckInvariant(); // Can be called from buffer error, so can't check full invariant. + if (iState != EStateInitialized2 && iState != EStateCreated2) + { + InternalFlush(); + iState = EStateInitialized2; + } + CheckFullInvariant(); + ASSERT((QLength(iRecordingQueue) + QLength(iPendingQueue) + + QLength(iBusyQueue))==0); // everything is stopped + } + +void CAudioInput::InternalFlush() + { + CancelRecorders(); + CancelPendingRecorders(); + CancelBusyRecorder(); + } + +void CAudioInput::BufferArrives(CRecorder* aRecorder) + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print( + _L("--->CAudioInput::BufferArrives(%d,%d) state=%s rstate=%s"), aRecorder->Index(), + aRecorder->StatusOrOffset(), StateName(iState), RStateName(iRState)); +#endif + CheckInvariant(); // Can't use CheckFullInvariant() from RunL + ASSERT(iState==EStateRecordWait2 || iState==EStateRecordWaitAck2); + ASSERT(aRecorder->Offset()>=0); // assert we're not here due to an error + iRecordingQueue.Remove(*aRecorder); + iPendingQueue.AddLast(*aRecorder); + if (iState==EStateRecordWait2) + { + RequestCallback(); + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::BufferArrives() state=%s rstate=%s"), + StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + } + +void CAudioInput::UseBuffer(CRecorder* aRecorder) +// incomming buffer is pointed to by iBufPtr. Either directly or via convert, use for callback + { + iBufPtr.Set(iChunk.Base() + aRecorder->Offset(), aRecorder->Length()); + if (iConverter) + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("iBufPtr length [%d] iconvbuff length [%d,%d]"), + iBufPtr.Length(), iConvBuff.Length(), iConvBuff.MaxLength()); +#endif + __DEBUG_ONLY(TInt converted =) iConverter->Convert(iBufPtr, iConvBuff); + // the following assert should check we convert the log. + // Actually we sometimes fail at the end of the operation with what is effectively + // the last buffer. Arguably a driver fault, but there we are + // ASSERT(converted==iBufPtr.Length()); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("iBufPtr length [%d] iconvbuff length after [%d,%d]"), + iBufPtr.Length(), iConvBuff.Length(), iConvBuff.MaxLength()); +#endif + iObserver.InputBufferAvailable(iConvBuff); + } + else + { + iObserver.InputBufferAvailable(iBufPtr); + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("12345 ibufptr = [0x%x]"),iBufPtr.Ptr()); +#endif + } + +void CAudioInput::BufferError(CRecorder* aRecorder, TInt aError) + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print( + _L("--->CAudioInput::BufferError(%d,%d) state=%s rstate=%s"), aRecorder->Index(), + aError, StateName(iState), RStateName(iRState)); +#endif + CheckInvariant(); // Can't use CheckFullInvariant() from RunL + if (aError==KErrCancel || aError==KErrOverflow) + { + // Cancel: sign of a Pause operation. If paused etc, then merely add to idle list. potentially generate finished signal + // if not paused, then not clear but just in case request record again + // Overflow: basically try again, but if paused merely add to idle. Check for last buffer just in case + if (iRState!=ERStateRunning) + { + iRecordingQueue.Remove(*aRecorder); + iIdleQueue.AddLast(*aRecorder); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print( + _L("***! irecordingquelength %d pendingquelength %d ibusyquelength=%d"), QLength(iRecordingQueue), + QLength(iPendingQueue), QLength(iBusyQueue)); +#endif + if (iRState == ERStatePaused && (QLength(iRecordingQueue)+QLength(iPendingQueue)+QLength(iBusyQueue) == 0)) + { + iRState = ERStateFinishing; + RequestCallback(); + } + } + else + { + aRecorder->RecordData(); + } + } + else + { + iRecordingQueue.Remove(*aRecorder); + iIdleQueue.AddLast(*aRecorder); + iRState = ERStateFailed; + iObserver.InputError(aError); + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::BufferError() state=%s rstate=%s"), + StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + } + +TInt CAudioInput::Callback(TAny* aPtr) + { + CAudioInput* self = static_cast (aPtr); + TRAPD(error,self->AsyncCallbackL()); + return error; // TODO really have to handle error + } + +void CAudioInput::RequestCallback() + { + // ensure iAsyncCallBack is active + if (!iAsyncCallBack->IsActive()) + { + iAsyncCallBack->Call(); + } + } + +void CAudioInput::AsyncCallbackL() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::AsyncCallbackL() state=%s rstate=%s"), + StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + ASSERT(iState==EStateRecordWait2 || iState==EStateRecordWaitAck2); // should not occur in other states. Actually ignore in 2nd + if (iState==EStateRecordWait2) + { + if (QLength(iPendingQueue)>0) + { + ASSERT(QLength(iBusyQueue)==0); + iState = EStateRecordWaitAck2; // change state prior to callback, in case sync call from callback + CRecorder* recorder = QPop(iPendingQueue); + iBusyQueue.AddLast(*recorder); + UseBuffer(recorder); + } + else + { + if (iRState == ERStateFinishing) + { + ASSERT(QLength(iRecordingQueue)+QLength(iPendingQueue)+QLength(iBusyQueue) == 0); // should be true + iRState = ERStateFinished; + iObserver.InputFinished(); + } + } + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::AsyncCallbackL() state=%s rstate=%s"), + StateName(iState), RStateName(iRState)); +#endif + CheckFullInvariant(); + } + +TInt CAudioInput::GetBufferSizes(TInt& aMinSize, TInt& aMaxSize) + { + aMinSize = KMinBufferSize; + aMaxSize = KMaxBufferSize; + return KErrNone; + } + +TInt CAudioInput::SetGain(TInt aGain) + { + TInt error = KErrNone; // note: silent if in wrong state + if (iRecordSoundDevice.Handle()) + { + // we have to switch from level to dB value + if(aGain >=0 && aGain<=KSoundMaxVolume) + { + error = iRecordSoundDevice.SetVolume(KLinearToDbConstantLookup[aGain]); + } + else + { + error = KErrArgument; + } + } + return error; + } + +TInt CAudioInput::SetFormat(const TAudioInputParams& aFormat) + { + TInt err = KErrNotFound; + TCurrentSoundFormatV02Buf formatBuf; + TFormatData formatData; + + delete iConverter; + iConverter = NULL; // setting this to NULL indicates we are not using converter. No other flag + + TInt wantedRate = aFormat.iSampleRate; + for (TInt index = 0; index < KNumSampleRates; index++) + { + if (wantedRate == KRateEnumLookup[index].iRate) + { + formatBuf().iRate = KRateEnumLookup[index].iRateEnum; + formatData.iSampleRate = wantedRate; + err = KErrNone; + break; + } + } + + if (err == KErrNone) + { + formatBuf().iChannels = aFormat.iNumChannels; + formatBuf().iEncoding = ESoundEncoding16BitPCM; + formatBuf().iDataFormat = ESoundDataFormatInterleaved; + err = iRecordSoundDevice.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, formatData); + } + } + return err; + } + +TInt CAudioInput::NegotiateFormat(const TAudioInputParams& aFormat, TFormatData &aFormatData) + { + TInt err = KErrNotFound; + TCurrentSoundFormatV02Buf formatBuf; + + TInt origBufferLength = iBufferLength; // cache in case we change + + // find out first what the driver supports + TSoundFormatsSupportedV02Buf supportedFormat; + iRecordSoundDevice.Caps(supportedFormat); + TUint32 supportedRates = supportedFormat().iRates; +#ifdef SYMBIAN_SOUNDADAPTER_FORCECDRATES + supportedRates &= KSoundRate11025Hz | KSoundRate22050Hz + | KSoundRate44100Hz; // only use CD rates - for debugging +#endif + + // 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.iSampleRate; + 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) + { + // For record we just abort. + 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 (aFormat.iNumChannels == 1) + { + aFormatData.iRequestedChannels = 1; + // want mono + if (channelsSupported & KSoundMonoChannel) + { + // mono is supported, as usual + aFormatData.iActualChannels = 1; + } + else if (channelsSupported & KSoundStereoChannel) + { + aFormatData.iActualChannels = 2; + iBufferLength *= 2; // double size, will do stereo->mono + } + else + { + return KErrNotSupported; // should not get this far for real + } + } + else if (aFormat.iNumChannels == 2) + { + aFormatData.iRequestedChannels = 2; + // want stereo + if (channelsSupported & KSoundStereoChannel) + { + // stereo is supported, as usual + aFormatData.iActualChannels = 2; + } + else if (channelsSupported & KSoundMonoChannel) + { + aFormatData.iActualChannels = 1; + iBufferLength /= 2; // halve size, will do mono->stereo + } + 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 = iRecordSoundDevice.SetAudioFormat(formatBuf); + + if (!err) + { + ASSERT(!iConverter); // pre-condition at top of function anyway + // 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, iConverter = CChannelAndSampleRateConverter::CreateL(aFormatData.iActualRate, + aFormatData.iActualChannels, + outputRateToUse, + aFormatData.iRequestedChannels)); + } + if (!err && iConverter) + { + err = iConvBuff.Create(origBufferLength); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("iBufferLength length [%d] iconvbuff length [%d,%d]"), + iBufferLength, iConvBuff.Length(), iConvBuff.MaxLength()); +#endif + } + + return err; + } + +TInt CAudioInput::GetSupportedSampleRates(RArray& aSupportedSampleRates) + { + TInt err = KErrNone; + + if (iRecordSoundDevice.Handle()) + { + GetSupportedSampleRates(aSupportedSampleRates, iRecordSoundDevice); + } + else + {//temporarily open the device if we can + RSoundSc tempsound; + err = tempsound.Open(KSoundScRxUnit0); + if (!err) + { + err = GetSupportedSampleRates(aSupportedSampleRates, tempsound); + tempsound.Close(); + } + } + return err; + } + +TInt CAudioInput::GetSupportedSampleRates( + RArray& aSupportedSampleRates, RSoundSc& aSoundDevice) + { + ASSERT(aSoundDevice.Handle()); // parent to ensure this is open + + TInt err = KErrNone; + + 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) + { + err = aSupportedSampleRates.Append(KRateEnumLookup[i].iRate); + if (err) + { + break; + } + } + } + return err; + } + +TInt CAudioInput::QLength(TSglQue& aQueue) +// count elements in List/Q. Have to use iterator to do this - it seems. + { + TSglQueIter iter(aQueue); + TInt count=0; + while (iter++) + { + // like old-fashioned C string manipulations. iterate through all members + count++; + } + return count; + } + +CAudioInput::CRecorder* CAudioInput::QPop(TSglQue& aQueue) + { + CRecorder* recorder = NULL; + if (! aQueue.IsEmpty()) + { + recorder = aQueue.First(); + aQueue.Remove(*recorder); + } + return recorder; + } + +#ifdef _DEBUG + +// these functions are used in invariant checking only + +void CAudioInput::CheckActiveRecorders(TSglQue& aQueue, TBool aExpected, TInt aPanicCode) +// check that all the elements in the given Q are IsActive() or vice-versa + { + TSglQueIter iter(aQueue); + + CRecorder* recorder; + while ((recorder=iter++)!=NULL) + { + TBool expected = aExpected != EFalse; // ensure these are either true or false + TBool active = recorder->IsActive() != EFalse; + __ASSERT_DEBUG(expected == active, Panic(aPanicCode)); + } + } + +void CAudioInput::CheckActiveRecorders() +// check that all the elements in the recordingQueue are IsActive() etc +// can't be used as CRecorder::RunL() pre-condition + { + CheckActiveRecorders(iRecordingQueue, ETrue, EPanicBusyRecorderNotActive); + CheckActiveRecorders(iIdleQueue, EFalse, EPanicNonBusyRecorderActive); + CheckActiveRecorders(iPendingQueue, EFalse, EPanicNonBusyRecorderActive); + CheckActiveRecorders(iBusyQueue, EFalse, EPanicNonBusyRecorderActive); + } + +#endif // _DEBUG + +// +// CRecorder +// + + +CAudioInput::CRecorder::CRecorder(CAudioInput& aParent, TInt aIndex) : + CActive(EPriorityStandard), iParent(aParent), iIndex(aIndex) + { + CActiveScheduler::Add(this); + } + +CAudioInput::CRecorder::~CRecorder() + { + Cancel(); + } + +void CAudioInput::CRecorder::Cancel() + { + // this override takes into account that ReleaseBuffer must be called - this is not the + // normal pattern where following Cancel() we're not concerned with the results + if (IsActive()) + { + ASSERT(!BufferHeld()); // if active then buffer held should be clear. don't reset then + CActive::Cancel(); + ReleaseBuffer(ETrue); // release - might have been a successful run! + } + else + { + ReleaseBuffer(); // this will release buffer if still outstanding + } + } + +void CAudioInput::CRecorder::RunL() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::CRecorder::RunL(%d, %d)"), Index(), + iStatus.Int()); +#endif + TInt errorOrOffset = iStatus.Int(); // negative -> error. non-negative is offset in chunk + + if (errorOrOffset < 0) + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("errorOrOffset = [%d]"),errorOrOffset); +#endif + // ReleaseBuffer(ETrue); // calls ReleaseBuffer() on error code. Driver requires this, even though seems wrong + iParent.BufferError(this, errorOrOffset); + } + else + { + ASSERT(!iBufferHeld); + iBufferHeld = ETrue; + +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("errorOrOffset = [%d]"),errorOrOffset); +#endif + // If a buffer larger than expected arrives truncate it. + iLength = Min(iLength,iParent.iBufferLength); + iParent.BufferArrives(this); + } +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::CRecorder::RunL(%d)"), Index()); +#endif + } + +void CAudioInput::CRecorder::RecordData() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::CRecorder::RecordData(%d)"), Index()); +#endif + ASSERT(!iBufferHeld); + Deque(); // ensure we append to the AO queue, so if it comes to it we process oldest request first + CActiveScheduler::Add(this); + iLength = iParent.BufferLength(); // TODO do we have to set this first or is it an OUT param purely + iParent.RecordSoundDevice().RecordData(iStatus, iLength); + SetActive(); + +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("###****#####!!!! Buffer length [%d], status [%d] "), iLength, + iStatus.Int()); +#endif +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::CRecorder::RecordData(%d)"), Index()); +#endif + } + +void CAudioInput::CRecorder::DoCancel() + { +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("--->CAudioInput::CRecorder::DoCancel(%d)"), Index()); +#endif + iParent.RecordSoundDevice().Cancel(iStatus); +#ifdef SYMBIAN_SWCODEC_LOGGING + RDebug::Print(_L("<---CAudioInput::CRecorder::DoCancel(%d)"), Index()); +#endif + } + +void CAudioInput::CRecorder::ReleaseBuffer(TBool aDoAnyway) + { + if (iBufferHeld || aDoAnyway) + { + iParent.RecordSoundDevice().ReleaseBuffer(iStatus.Int()); + iBufferHeld = EFalse; + } + } + +TInt CAudioInput::CRecorder::Index() const + { + return iIndex; + } + +TInt CAudioInput::CRecorder::Length() const + { + return iLength; + } + +TBool CAudioInput::CRecorder::IsBusy() const + { + return IsActive() || BufferHeld(); + } + +TBool CAudioInput::CRecorder::BufferHeld() const +// BufferHeld() means we're in control of a passed buffer + { + return iBufferHeld; + } + +TInt CAudioInput::CRecorder::Offset() const +// If we call this, we've discounted errors so can assert non-negative + { + TInt result = StatusOrOffset(); + ASSERT(result>=0); + return result; + } + +TInt CAudioInput::CRecorder::StatusOrOffset() const +// The iStatus assuming is valid + { + ASSERT(!IsActive()); // or would not be valid + TInt result = iStatus.Int(); + return result; + }