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