switch Cyclone to submodule on it's own git repo
[picodrive.git] / platform / uiq3 / engine / audio_mediaserver.cpp
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