| 1 | /*******************************************************************\r |
| 2 | *\r |
| 3 | * File: Audio_mediaserver.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 | #include "audio_mediaserver.h"\r |
| 16 | #include "debug.h"\r |
| 17 | \r |
| 18 | //#define DEBUG_UNDERFLOWS\r |
| 19 | //#undef DEBUGPRINT\r |
| 20 | //#define DEBUGPRINT(x...)\r |
| 21 | \r |
| 22 | \r |
| 23 | const TInt KUpdatesPerSec = 10;\r |
| 24 | const TInt KBlockTime = 1000000 / KUpdatesPerSec;\r |
| 25 | const TInt KMaxLag = 200000; // max sound lag, lower values increase chance of underflow\r |
| 26 | const TInt KMaxUnderflows = 50; // max underflows/API errors we are going allow in a row (to prevent lockups)\r |
| 27 | \r |
| 28 | \r |
| 29 | /*******************************************\r |
| 30 | *\r |
| 31 | * CGameAudioMS\r |
| 32 | *\r |
| 33 | *******************************************/\r |
| 34 | \r |
| 35 | CGameAudioMS::CGameAudioMS(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)\r |
| 36 | : iRate(aRate), iStereo(aStereo), iWritesPerSec(aWritesPerSec), iVolume(aVolume)\r |
| 37 | {\r |
| 38 | }\r |
| 39 | \r |
| 40 | \r |
| 41 | CGameAudioMS* CGameAudioMS::NewL(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)\r |
| 42 | {\r |
| 43 | DEBUGPRINT(_L("CGameAudioMS::NewL(%i, %i, %i, %i)"), aRate, aStereo, aWritesPerSec, aVolume);\r |
| 44 | CGameAudioMS* self = new(ELeave) CGameAudioMS(aRate, aStereo, aWritesPerSec, aVolume);\r |
| 45 | CleanupStack::PushL(self);\r |
| 46 | self->ConstructL();\r |
| 47 | CleanupStack::Pop(); // self\r |
| 48 | return self;\r |
| 49 | }\r |
| 50 | \r |
| 51 | \r |
| 52 | CGameAudioMS::~CGameAudioMS()\r |
| 53 | {\r |
| 54 | DEBUGPRINT(_L("CGameAudioMS::~CGameAudioMS()"));\r |
| 55 | if(iMdaAudioOutputStream) {\r |
| 56 | iScheduler->Schedule(); // let it finish it's stuff\r |
| 57 | iMdaAudioOutputStream->Stop();\r |
| 58 | delete iMdaAudioOutputStream;\r |
| 59 | }\r |
| 60 | if(iServer) delete iServer;\r |
| 61 | \r |
| 62 | for (TInt i=0; i<KSoundBuffers; i++)\r |
| 63 | delete iSoundBuffers[i];\r |
| 64 | \r |
| 65 | // Polled AS\r |
| 66 | //if(iScheduler) delete iScheduler;\r |
| 67 | }\r |
| 68 | \r |
| 69 | \r |
| 70 | void CGameAudioMS::ConstructL()\r |
| 71 | {\r |
| 72 | iServer = CMdaServer::NewL();\r |
| 73 | \r |
| 74 | // iScheduler = CPolledActiveScheduler::NewL();\r |
| 75 | iScheduler = CPolledActiveScheduler::Instance();\r |
| 76 | \r |
| 77 | switch(iRate) {\r |
| 78 | case 11025: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate11025Hz; break;\r |
| 79 | case 16000: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate16000Hz; break;\r |
| 80 | case 22050: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate22050Hz; break;\r |
| 81 | case 44100: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate44100Hz; break;\r |
| 82 | default: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz; break;\r |
| 83 | }\r |
| 84 | \r |
| 85 | iMdaAudioDataSettings.iChannels = (iStereo) ? TMdaAudioDataSettings::EChannelsStereo : TMdaAudioDataSettings::EChannelsMono;\r |
| 86 | iMdaAudioDataSettings.iCaps = TMdaAudioDataSettings::ESampleRateFixed | iMdaAudioDataSettings.iSampleRate;\r |
| 87 | iMdaAudioDataSettings.iFlags = TMdaAudioDataSettings::ENoNetworkRouting;\r |
| 88 | \r |
| 89 | iMaxWriteSamples = iRate / iWritesPerSec;\r |
| 90 | if (iRate % iWritesPerSec)\r |
| 91 | iMaxWriteSamples++;\r |
| 92 | int bufferedFrames = iWritesPerSec / KUpdatesPerSec;\r |
| 93 | \r |
| 94 | iBufferSize = iMaxWriteSamples * (iStereo ? 4 : 2);\r |
| 95 | iBufferSize *= bufferedFrames;\r |
| 96 | for (TInt i=0 ; i<KSoundBuffers ; i++)\r |
| 97 | {\r |
| 98 | // it seems .SetLength(max) throws USER:23 panic,\r |
| 99 | // so make them a bit larger\r |
| 100 | iSoundBuffers[i] = HBufC8::NewL(iBufferSize+4);\r |
| 101 | iSoundBuffers[i]->Des().FillZ (iBufferSize+4);\r |
| 102 | }\r |
| 103 | \r |
| 104 | iCurrentBuffer = 0;\r |
| 105 | iCurrentBufferSize = 0;\r |
| 106 | \r |
| 107 | DEBUGPRINT(_L("sound: iMaxWriteSamples: %i, iBufferSize: %i"), iMaxWriteSamples, iBufferSize);\r |
| 108 | \r |
| 109 | // here we actually test if we can create and open CMdaAudioOutputStream at all, but really create and use it later.\r |
| 110 | iMdaAudioOutputStream = CMdaAudioOutputStream::NewL(iListener, iServer);\r |
| 111 | if (iMdaAudioOutputStream) {\r |
| 112 | if (iVolume < 0 || iVolume > iMdaAudioOutputStream->MaxVolume())\r |
| 113 | iVolume = iMdaAudioOutputStream->MaxVolume();\r |
| 114 | delete iMdaAudioOutputStream;\r |
| 115 | iMdaAudioOutputStream = 0;\r |
| 116 | }\r |
| 117 | }\r |
| 118 | \r |
| 119 | // returns a pointer to buffer for next frame,\r |
| 120 | // to be used when iSoundBuffers are used directly\r |
| 121 | TInt16 *CGameAudioMS::NextFrameL(TInt aPcmFrames)\r |
| 122 | {\r |
| 123 | TInt mul = iStereo ? 4 : 2;\r |
| 124 | TInt bytes = aPcmFrames * mul;\r |
| 125 | iCurrentPosition += bytes / 2;\r |
| 126 | iCurrentBufferSize += bytes;\r |
| 127 | \r |
| 128 | if (aPcmFrames > iMaxWriteSamples) {\r |
| 129 | DEBUGPRINT(_L("too many samples: %i > %i"), aPcmFrames, iMaxWriteSamples);\r |
| 130 | }\r |
| 131 | \r |
| 132 | if (iCurrentBufferSize + iMaxWriteSamples * mul > iBufferSize)\r |
| 133 | {\r |
| 134 | //DEBUGPRINT(_L("write on iCurrentBufferSize %i"), iCurrentBufferSize);\r |
| 135 | WriteBlockL();\r |
| 136 | }\r |
| 137 | \r |
| 138 | iScheduler->Schedule();\r |
| 139 | \r |
| 140 | if(iListener.iUnderflowed) {\r |
| 141 | if(iListener.iUnderflowed > KMaxUnderflows) {\r |
| 142 | delete iMdaAudioOutputStream;\r |
| 143 | iMdaAudioOutputStream = 0;\r |
| 144 | return 0;\r |
| 145 | }\r |
| 146 | UnderflowedL(); // not again!\r |
| 147 | }\r |
| 148 | \r |
| 149 | return iCurrentPosition;\r |
| 150 | }\r |
| 151 | \r |
| 152 | void CGameAudioMS::WriteBlockL()\r |
| 153 | {\r |
| 154 | iScheduler->Schedule();\r |
| 155 | // do not write until stream is open\r |
| 156 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
| 157 | //if(!iListener.iHasCopied) WaitForCopyToCompleteL(); // almost never happens anyway and sometimes even deadlocks?\r |
| 158 | //iListener.iHasCopied = EFalse;\r |
| 159 | \r |
| 160 | \r |
| 161 | if(!iListener.iUnderflowed) {\r |
| 162 | TInt64 delta;\r |
| 163 | // don't write if sound is lagging too much\r |
| 164 | delta = iTime - iMdaAudioOutputStream->Position().Int64();\r |
| 165 | if (delta > MAKE_TINT64(0, KMaxLag))\r |
| 166 | // another query sometimes returns very different result\r |
| 167 | delta = iTime - iMdaAudioOutputStream->Position().Int64();\r |
| 168 | \r |
| 169 | if(delta <= MAKE_TINT64(0, KMaxLag)) {\r |
| 170 | //RDebug::Print(_L("delta: %i"), iTime.Low() - iMdaAudioOutputStream->Position().Int64().Low());\r |
| 171 | iSoundBuffers[iCurrentBuffer]->Des().SetLength(iCurrentBufferSize);\r |
| 172 | iMdaAudioOutputStream->WriteL(*iSoundBuffers[iCurrentBuffer]);\r |
| 173 | iTime += KBlockTime;\r |
| 174 | } else {\r |
| 175 | DEBUGPRINT(_L("lag: %i"), I64LOW(delta));\r |
| 176 | }\r |
| 177 | }\r |
| 178 | \r |
| 179 | if (++iCurrentBuffer == KSoundBuffers)\r |
| 180 | iCurrentBuffer = 0;\r |
| 181 | iSoundBuffers[iCurrentBuffer]->Des().SetMax();\r |
| 182 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
| 183 | iCurrentBufferSize = 0;\r |
| 184 | }\r |
| 185 | \r |
| 186 | void CGameAudioMS::Pause()\r |
| 187 | {\r |
| 188 | if(!iMdaAudioOutputStream) return;\r |
| 189 | \r |
| 190 | iScheduler->Schedule(); // let it finish it's stuff\r |
| 191 | iMdaAudioOutputStream->Stop();\r |
| 192 | delete iMdaAudioOutputStream;\r |
| 193 | iMdaAudioOutputStream = 0;\r |
| 194 | }\r |
| 195 | \r |
| 196 | // call this before doing any playback!\r |
| 197 | TInt16 *CGameAudioMS::ResumeL()\r |
| 198 | {\r |
| 199 | DEBUGPRINT(_L("CGameAudioMS::Resume()"));\r |
| 200 | iScheduler->Schedule();\r |
| 201 | \r |
| 202 | // we act a bit strange here: simulate buffer underflow, which actually starts audio\r |
| 203 | iListener.iIsOpen = ETrue;\r |
| 204 | iListener.iUnderflowed = 1;\r |
| 205 | iListener.iLastError = 0;\r |
| 206 | iCurrentBufferSize = 0;\r |
| 207 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
| 208 | return iCurrentPosition;\r |
| 209 | }\r |
| 210 | \r |
| 211 | // handles underflow condition\r |
| 212 | void CGameAudioMS::UnderflowedL()\r |
| 213 | {\r |
| 214 | #ifdef DEBUG_UNDERFLOWS\r |
| 215 | DEBUGPRINT(_L("UnderflowedL()"));\r |
| 216 | #endif\r |
| 217 | \r |
| 218 | if (iListener.iLastError != KErrUnderflow)\r |
| 219 | {\r |
| 220 | // recreate the stream\r |
| 221 | //iMdaAudioOutputStream->Stop();\r |
| 222 | if(iMdaAudioOutputStream) delete iMdaAudioOutputStream;\r |
| 223 | iMdaAudioOutputStream = CMdaAudioOutputStream::NewL(iListener, iServer);\r |
| 224 | iMdaAudioOutputStream->Open(&iMdaAudioDataSettings);\r |
| 225 | iMdaAudioOutputStream->SetAudioPropertiesL(iMdaAudioDataSettings.iSampleRate, iMdaAudioDataSettings.iChannels);\r |
| 226 | iMdaAudioOutputStream->SetVolume(iVolume); // new in UIQ3\r |
| 227 | \r |
| 228 | iListener.iIsOpen = EFalse; // wait for it to open\r |
| 229 | //iListener.iHasCopied = ETrue; // but don't wait for last copy to complete\r |
| 230 | // let it open and feed some stuff to make it happy\r |
| 231 | User::After(0);\r |
| 232 | iScheduler->Schedule();\r |
| 233 | iListener.iLastError = 0;\r |
| 234 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
| 235 | } else {\r |
| 236 | iListener.iLastError = iListener.iUnderflowed = 0;\r |
| 237 | }\r |
| 238 | iTime = iMdaAudioOutputStream->Position().Int64();\r |
| 239 | }\r |
| 240 | \r |
| 241 | void CGameAudioMS::WaitForOpenToCompleteL()\r |
| 242 | {\r |
| 243 | DEBUGPRINT(_L("CGameAudioMS::WaitForOpenToCompleteL"));\r |
| 244 | TInt count = 20; // 2 seconds\r |
| 245 | TInt waitPeriod = 100 * 1000;\r |
| 246 | \r |
| 247 | if(!iListener.iIsOpen) {\r |
| 248 | // it is often enough to do this\r |
| 249 | User::After(0);\r |
| 250 | iScheduler->Schedule();\r |
| 251 | }\r |
| 252 | while (!iListener.iIsOpen && --count)\r |
| 253 | {\r |
| 254 | User::After(waitPeriod);\r |
| 255 | iScheduler->Schedule();\r |
| 256 | }\r |
| 257 | if (!iListener.iIsOpen)\r |
| 258 | User::LeaveIfError(KErrNotSupported);\r |
| 259 | }\r |
| 260 | \r |
| 261 | TInt CGameAudioMS::ChangeVolume(TInt aUp)\r |
| 262 | {\r |
| 263 | //DEBUGPRINT(_L("CGameAudioMS::ChangeVolume(%i)"), aUp);\r |
| 264 | \r |
| 265 | if (iMdaAudioOutputStream) {\r |
| 266 | if (aUp) {\r |
| 267 | iVolume += 5;\r |
| 268 | if (iVolume > iMdaAudioOutputStream->MaxVolume())\r |
| 269 | iVolume = iMdaAudioOutputStream->MaxVolume();\r |
| 270 | } else {\r |
| 271 | iVolume -= 5;\r |
| 272 | if (iVolume < 0) iVolume = 0;\r |
| 273 | }\r |
| 274 | iMdaAudioOutputStream->SetVolume(iVolume);\r |
| 275 | }\r |
| 276 | \r |
| 277 | return iVolume;\r |
| 278 | }\r |
| 279 | \r |
| 280 | void TGameAudioEventListener::MaoscOpenComplete(TInt aError)\r |
| 281 | {\r |
| 282 | #ifdef DEBUG_UNDERFLOWS\r |
| 283 | DEBUGPRINT(_L("CGameAudioMS::MaoscOpenComplete, error=%d"), aError);\r |
| 284 | #endif\r |
| 285 | \r |
| 286 | iIsOpen = ETrue;\r |
| 287 | if(aError) {\r |
| 288 | iLastError = aError;\r |
| 289 | iUnderflowed++;\r |
| 290 | }\r |
| 291 | else iUnderflowed = 0;\r |
| 292 | }\r |
| 293 | \r |
| 294 | void TGameAudioEventListener::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer)\r |
| 295 | {\r |
| 296 | if (aError)\r |
| 297 | DEBUGPRINT(_L("CGameAudioMS::MaoscBufferCopied, error=%d"), aError);\r |
| 298 | \r |
| 299 | // iHasCopied = ETrue;\r |
| 300 | \r |
| 301 | if(aError) { // shit!\r |
| 302 | iLastError = aError;\r |
| 303 | iUnderflowed++;\r |
| 304 | }\r |
| 305 | }\r |
| 306 | \r |
| 307 | void TGameAudioEventListener::MaoscPlayComplete(TInt aError)\r |
| 308 | {\r |
| 309 | #ifdef DEBUG_UNDERFLOWS\r |
| 310 | DEBUGPRINT(_L("CGameAudioMS::MaoscPlayComplete: %i"), aError);\r |
| 311 | #endif\r |
| 312 | if(aError) {\r |
| 313 | iLastError = aError;\r |
| 314 | iUnderflowed++; // never happened to me while testing, but just in case\r |
| 315 | }\r |
| 316 | }\r |
| 317 | \r |