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