| 1 | /*******************************************************************\r |
| 2 | *\r |
| 3 | * File: Audio_motorola.cpp\r |
| 4 | *\r |
| 5 | * Author: Peter van Sebille (peter@yipton.net)\r |
| 6 | *\r |
| 7 | * Modified/adapted for picodriveN by notaz, 2006\r |
| 8 | *\r |
| 9 | * (c) Copyright 2006, notaz\r |
| 10 | * (c) Copyright 2001, Peter van Sebille\r |
| 11 | * All Rights Reserved\r |
| 12 | *\r |
| 13 | *******************************************************************/\r |
| 14 | \r |
| 15 | // if only I had Motorola to test this on..\r |
| 16 | \r |
| 17 | \r |
| 18 | #include "audio_motorola.h"\r |
| 19 | \r |
| 20 | #ifdef __DEBUG_PRINT_SND\r |
| 21 | #include <e32svr.h> // RDebug\r |
| 22 | #define DEBUGPRINT(x...) RDebug::Print(x)\r |
| 23 | #else\r |
| 24 | #define DEBUGPRINT(x...)\r |
| 25 | #endif\r |
| 26 | \r |
| 27 | \r |
| 28 | GLDEF_C TInt E32Dll(TDllReason)\r |
| 29 | {\r |
| 30 | return KErrNone;\r |
| 31 | }\r |
| 32 | \r |
| 33 | \r |
| 34 | /*******************************************\r |
| 35 | *\r |
| 36 | * CGameAudioMot\r |
| 37 | *\r |
| 38 | *******************************************/\r |
| 39 | \r |
| 40 | CGameAudioMot::CGameAudioMot(TInt aRate, TBool aStereo, TInt aPcmFrames, TInt aBufferedFrames)\r |
| 41 | : iRate(aRate), iStereo(aStereo), iBufferedFrames(aBufferedFrames), iPcmFrames(aPcmFrames)\r |
| 42 | {\r |
| 43 | DEBUGPRINT(_L("CGameAudioMot::CGameAudioMot"));\r |
| 44 | }\r |
| 45 | \r |
| 46 | \r |
| 47 | CGameAudioMot* CGameAudioMot::NewL(TInt aRate, TBool aStereo, TInt aPcmFrames, TInt aBufferedFrames)\r |
| 48 | {\r |
| 49 | DEBUGPRINT(_L("CGameAudioMot::NewL(%i, %i, %i, %i)"),aRate, aStereo, aPcmFrames, aBufferedFrames);\r |
| 50 | CGameAudioMot* self = new(ELeave) CGameAudioMot(aRate, aStereo, aPcmFrames, aBufferedFrames);\r |
| 51 | CleanupStack::PushL(self);\r |
| 52 | self->ConstructL();\r |
| 53 | CleanupStack::Pop(); // self\r |
| 54 | return self;\r |
| 55 | }\r |
| 56 | \r |
| 57 | \r |
| 58 | CGameAudioMot::~CGameAudioMot()\r |
| 59 | {\r |
| 60 | DEBUGPRINT(_L("CGameAudioMot::~CGameAudioMot()"));\r |
| 61 | if(iAudioOutputStream) {\r |
| 62 | iScheduler->Schedule(); // let it finish it's stuff\r |
| 63 | //iAudioOutputStream->Stop();\r |
| 64 | delete iAudioOutputStream;\r |
| 65 | }\r |
| 66 | \r |
| 67 | if(iAudioControl) delete iAudioControl;\r |
| 68 | \r |
| 69 | for (TInt i=0 ; i < KSoundBuffers+1; i++) {\r |
| 70 | delete iSoundBufferPtrs[i];\r |
| 71 | delete iSoundBuffers[i];\r |
| 72 | }\r |
| 73 | \r |
| 74 | // Polled AS\r |
| 75 | if(iScheduler) delete iScheduler;\r |
| 76 | }\r |
| 77 | \r |
| 78 | \r |
| 79 | void CGameAudioMot::ConstructL()\r |
| 80 | {\r |
| 81 | iScheduler = CPolledActiveScheduler::NewL();\r |
| 82 | \r |
| 83 | iSettings.iPCMSettings.iSamplingFreq = (TMSampleRate) iRate;\r |
| 84 | iSettings.iPCMSettings.iStereo = iStereo;\r |
| 85 | \r |
| 86 | TInt bytesPerFrame = iStereo ? iPcmFrames << 2 : iPcmFrames << 1;\r |
| 87 | for (TInt i=0 ; i<KSoundBuffers ; i++)\r |
| 88 | {\r |
| 89 | iSoundBuffers[i] = HBufC8::NewL(bytesPerFrame * iBufferedFrames);\r |
| 90 | iSoundBuffers[i]->Des().FillZ (bytesPerFrame * iBufferedFrames);\r |
| 91 | iSoundBufferPtrs[i] = new TPtr8( iSoundBuffers[i]->Des() );\r |
| 92 | }\r |
| 93 | // because feeding 2 buffers after an underflow is a little too much, but feeding 1 may be not enough,\r |
| 94 | // prepare this ~50ms empty buffer to additionaly feed after every underflow.\r |
| 95 | iSoundBuffers[KSoundBuffers] = HBufC8::NewL(bytesPerFrame * (iBufferedFrames / 4));\r |
| 96 | iSoundBuffers[KSoundBuffers]->Des().FillZ (bytesPerFrame * (iBufferedFrames / 4));\r |
| 97 | iSoundBufferPtrs[KSoundBuffers] = new TPtr8( iSoundBuffers[KSoundBuffers]->Des() );\r |
| 98 | \r |
| 99 | iCurrentBuffer = 0;\r |
| 100 | iListener.iFatalError = iListener.iIsOpen = iListener.iIsCtrlOpen = EFalse;\r |
| 101 | \r |
| 102 | // here we actually test if we can create and open CMdaAudioOutputStream at all, but really create and use it later.\r |
| 103 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
| 104 | if(iAudioOutputStream) {\r |
| 105 | delete iAudioOutputStream;\r |
| 106 | iAudioOutputStream = 0;\r |
| 107 | }\r |
| 108 | \r |
| 109 | // ceate audio control object\r |
| 110 | iAudioControl = CMAudioAC::NewL(iListener);\r |
| 111 | }\r |
| 112 | \r |
| 113 | \r |
| 114 | // returns a pointer to buffer for next frame,\r |
| 115 | // to be used when iSoundBuffers are used directly\r |
| 116 | TInt16 *CGameAudioMot::NextFrameL()\r |
| 117 | {\r |
| 118 | iCurrentPosition += iPcmFrames << (iStereo?1:0);\r |
| 119 | \r |
| 120 | if (++iFrameCount == iBufferedFrames)\r |
| 121 | {\r |
| 122 | WriteBlockL();\r |
| 123 | }\r |
| 124 | \r |
| 125 | iScheduler->Schedule();\r |
| 126 | \r |
| 127 | if(iListener.iFatalError || iListener.iUnderflowed > KMaxUnderflows) {\r |
| 128 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
| 129 | iAudioOutputStream = 0;\r |
| 130 | return 0;\r |
| 131 | }\r |
| 132 | else if(iListener.iUnderflowed) UnderflowedL();\r |
| 133 | \r |
| 134 | return iCurrentPosition;\r |
| 135 | }\r |
| 136 | \r |
| 137 | TInt16 *CGameAudioMot::DupeFrameL(TInt &aUnderflowed)\r |
| 138 | {\r |
| 139 | TInt shorts = iStereo ? (iPcmFrames << 1) : iPcmFrames;\r |
| 140 | if(iFrameCount)\r |
| 141 | Mem::Copy(iCurrentPosition, iCurrentPosition-shorts, shorts<<1);\r |
| 142 | else {\r |
| 143 | TInt lastBuffer = iCurrentBuffer;\r |
| 144 | if(--lastBuffer < 0) lastBuffer = KSoundBuffers - 1;\r |
| 145 | Mem::Copy(iCurrentPosition, ((TInt16*) (iSoundBuffers[lastBuffer]->Ptr()))+shorts*(iBufferedFrames-1), shorts<<1);\r |
| 146 | } \r |
| 147 | iCurrentPosition += shorts;\r |
| 148 | \r |
| 149 | if (++iFrameCount == iBufferedFrames)\r |
| 150 | {\r |
| 151 | WriteBlockL();\r |
| 152 | }\r |
| 153 | \r |
| 154 | iScheduler->Schedule();\r |
| 155 | \r |
| 156 | if(iListener.iFatalError || iListener.iUnderflowed > KMaxUnderflows) {\r |
| 157 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
| 158 | iAudioOutputStream = 0;\r |
| 159 | return 0;\r |
| 160 | }\r |
| 161 | else if((aUnderflowed = iListener.iUnderflowed)) UnderflowedL(); // not again!\r |
| 162 | \r |
| 163 | return iCurrentPosition;\r |
| 164 | }\r |
| 165 | \r |
| 166 | void CGameAudioMot::WriteBlockL()\r |
| 167 | {\r |
| 168 | iScheduler->Schedule();\r |
| 169 | \r |
| 170 | // do not write until stream is open\r |
| 171 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
| 172 | //if(!iListener.iHasCopied) WaitForCopyToCompleteL(); // almost never happens anyway and sometimes even deadlocks?\r |
| 173 | //iListener.iHasCopied = EFalse;\r |
| 174 | \r |
| 175 | \r |
| 176 | if(!iListener.iUnderflowed) {\r |
| 177 | iAudioOutputStream->QueueBufferL(iSoundBufferPtrs[iCurrentBuffer]);\r |
| 178 | // it is certain we already Queued at least 2 buffers (one just after underflow, another above)\r |
| 179 | if(!iDecoding) {\r |
| 180 | iAudioOutputStream->DecodeL();\r |
| 181 | iDecoding = ETrue;\r |
| 182 | }\r |
| 183 | }\r |
| 184 | \r |
| 185 | iFrameCount = 0;\r |
| 186 | if (++iCurrentBuffer == KSoundBuffers)\r |
| 187 | iCurrentBuffer = 0;\r |
| 188 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
| 189 | }\r |
| 190 | \r |
| 191 | void CGameAudioMot::Pause()\r |
| 192 | {\r |
| 193 | if(!iAudioOutputStream) return;\r |
| 194 | \r |
| 195 | iScheduler->Schedule();\r |
| 196 | // iAudioOutputStream->Stop(); // may be this breaks everything in A925?\r |
| 197 | delete iAudioOutputStream;\r |
| 198 | iAudioOutputStream = 0;\r |
| 199 | }\r |
| 200 | \r |
| 201 | // call this before doing any playback!\r |
| 202 | TInt16 *CGameAudioMot::ResumeL()\r |
| 203 | {\r |
| 204 | DEBUGPRINT(_L("CGameAudioMot::Resume()"));\r |
| 205 | iScheduler->Schedule();\r |
| 206 | \r |
| 207 | // we act a bit strange here: simulate buffer underflow, which actually starts audio\r |
| 208 | iListener.iIsOpen = ETrue;\r |
| 209 | iListener.iUnderflowed = 1;\r |
| 210 | iListener.iFatalError = EFalse;\r |
| 211 | iFrameCount = 0;\r |
| 212 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
| 213 | return iCurrentPosition;\r |
| 214 | }\r |
| 215 | \r |
| 216 | // handles underflow condition\r |
| 217 | void CGameAudioMot::UnderflowedL()\r |
| 218 | {\r |
| 219 | // recreate the stream\r |
| 220 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
| 221 | if(iListener.iUnderflowed > 4) {\r |
| 222 | // HACK: A925 user said sound works for the first time, but fails after pause/resume, etc.\r |
| 223 | // at the very beginning we create and delete CMAudioFB object, maybe we should do this every time?\r |
| 224 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
| 225 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
| 226 | }\r |
| 227 | \r |
| 228 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
| 229 | iListener.iIsOpen = EFalse; // wait for it to open\r |
| 230 | iDecoding = EFalse;\r |
| 231 | //iListener.iHasCopied = ETrue; // but don't wait for last copy to complete\r |
| 232 | // let it open and feed some stuff to make it happy\r |
| 233 | User::After(0);\r |
| 234 | //TInt lastBuffer = iCurrentBuffer;\r |
| 235 | //if(--lastBuffer < 0) lastBuffer = KSoundBuffers - 1;\r |
| 236 | iScheduler->Schedule();\r |
| 237 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
| 238 | if(iListener.iUnderflowed) {\r |
| 239 | // something went wrong again. May be it needs time? Trying to fix something without ability to test is hell.\r |
| 240 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
| 241 | iAudioOutputStream = 0;\r |
| 242 | User::After(50*000);\r |
| 243 | iScheduler->Schedule();\r |
| 244 | return;\r |
| 245 | }\r |
| 246 | \r |
| 247 | iAudioOutputStream->QueueBufferL(iSoundBufferPtrs[KSoundBuffers]); // try a short buffer with hope to reduce lag\r |
| 248 | }\r |
| 249 | \r |
| 250 | \r |
| 251 | void CGameAudioMot::ChangeVolume(TInt aUp)\r |
| 252 | {\r |
| 253 | if(iAudioControl && iListener.iIsCtrlOpen)\r |
| 254 | {\r |
| 255 | TInt vol = iAudioControl->GetMasterVolume();\r |
| 256 | TInt max = iAudioControl->GetMaxMasterVolume();\r |
| 257 | \r |
| 258 | if(aUp) vol++; // adjust volume\r |
| 259 | else vol--;\r |
| 260 | \r |
| 261 | if(vol >= 0 && vol <= max)\r |
| 262 | {\r |
| 263 | iAudioControl->SetMasterVolume(vol);\r |
| 264 | }\r |
| 265 | }\r |
| 266 | }\r |
| 267 | \r |
| 268 | \r |
| 269 | void CGameAudioMot::WaitForOpenToCompleteL()\r |
| 270 | {\r |
| 271 | DEBUGPRINT(_L("CGameAudioMot::WaitForOpenToCompleteL"));\r |
| 272 | TInt count = 20; // 2 seconds\r |
| 273 | TInt waitPeriod = 100 * 1000;\r |
| 274 | \r |
| 275 | if(!iListener.iIsOpen) {\r |
| 276 | // it is often enough to do this\r |
| 277 | User::After(0);\r |
| 278 | iScheduler->Schedule();\r |
| 279 | }\r |
| 280 | while (!iListener.iIsOpen && --count)\r |
| 281 | {\r |
| 282 | User::After(waitPeriod);\r |
| 283 | iScheduler->Schedule();\r |
| 284 | }\r |
| 285 | if (!iListener.iIsOpen)\r |
| 286 | User::LeaveIfError(KErrNotSupported);\r |
| 287 | }\r |
| 288 | \r |
| 289 | \r |
| 290 | \r |
| 291 | void TGameAudioEventListener::OnEvent(TMAudioFBCallbackState aState, TInt aError)\r |
| 292 | {\r |
| 293 | switch ( aState )\r |
| 294 | {\r |
| 295 | case EMAudioFBCallbackStateReady:\r |
| 296 | iIsOpen = ETrue;\r |
| 297 | iUnderflowed = 0;\r |
| 298 | break;\r |
| 299 | \r |
| 300 | case EMAudioFBCallbackStateDecodeCompleteStopped:\r |
| 301 | break;\r |
| 302 | \r |
| 303 | //case EMAudioFBCallbackStateDecodeFileSystemError:\r |
| 304 | case EMAudioFBCallbackStateDecodeError:\r |
| 305 | switch( aError )\r |
| 306 | {\r |
| 307 | case EMAudioFBCallbackErrorBufferFull:\r |
| 308 | case EMAudioFBCallbackErrorForcedStop:\r |
| 309 | case EMAudioFBCallbackErrorForcedClose:\r |
| 310 | //case EMAudioFBCallbackErrorForcedPause:\r |
| 311 | case EMAudioFBCallbackErrorPriorityRejection:\r |
| 312 | case EMAudioFBCallbackErrorAlertModeRejection:\r |
| 313 | case EMAudioFBCallbackErrorResourceRejection:\r |
| 314 | case EMAudioFBCallbackErrorUnknown:\r |
| 315 | iUnderflowed++;\r |
| 316 | break;\r |
| 317 | \r |
| 318 | // these look like really bad errors\r |
| 319 | case EMAudioFBCallbackErrorInvalidParameter:\r |
| 320 | case EMAudioFBCallbackErrorWrongState:\r |
| 321 | case EMAudioFBCallbackErrorFormatNotSupported:\r |
| 322 | case EMAudioFBCallbackErrorFunctionNotSupported:\r |
| 323 | case EMAudioFBCallbackErrorNoBuffer:\r |
| 324 | case EMAudioFBCallbackErrorSampleOrBitRateNotSupported:\r |
| 325 | //case EMAudioFBCallbackErrorPriorityOrPreferenceNotSupported:\r |
| 326 | //case EMAudioFBCallbackErrorFileSystemFull:\r |
| 327 | //iFatalError = ETrue;\r |
| 328 | // who cares, just keep retrying\r |
| 329 | iUnderflowed++;\r |
| 330 | break;\r |
| 331 | \r |
| 332 | default:\r |
| 333 | iUnderflowed++;\r |
| 334 | break;\r |
| 335 | }\r |
| 336 | // in error condition we also set to open, so that the\r |
| 337 | // framework would not leave, catch the error and retry\r |
| 338 | iIsOpen = ETrue;\r |
| 339 | break;\r |
| 340 | \r |
| 341 | default:\r |
| 342 | break;\r |
| 343 | }\r |
| 344 | }\r |
| 345 | \r |
| 346 | void TGameAudioEventListener::OnEvent(TMAudioFBCallbackState aState, TInt aError, TDes8* aBuffer)\r |
| 347 | {\r |
| 348 | switch( aState )\r |
| 349 | {\r |
| 350 | case EMAudioFBCallbackStateDecodeBufferDecoded:\r |
| 351 | break;\r |
| 352 | \r |
| 353 | default:\r |
| 354 | OnEvent( aState, aError );\r |
| 355 | break;\r |
| 356 | }\r |
| 357 | }\r |
| 358 | \r |
| 359 | void TGameAudioEventListener::OnEvent(TMAudioACCallbackState aState, TInt aError)\r |
| 360 | {\r |
| 361 | if(aState == EMAudioACCallbackStateReady) iIsCtrlOpen = ETrue;\r |
| 362 | }\r |
| 363 | \r |