cc68a136 |
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 |
ca482e5d |
18 | //#define DEBUG_UNDERFLOWS\r |
cc68a136 |
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 |
ca482e5d |
35 | CGameAudioMS::CGameAudioMS(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)\r |
36 | : iRate(aRate), iStereo(aStereo), iWritesPerSec(aWritesPerSec), iVolume(aVolume)\r |
cc68a136 |
37 | {\r |
38 | }\r |
39 | \r |
40 | \r |
ca482e5d |
41 | CGameAudioMS* CGameAudioMS::NewL(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)\r |
cc68a136 |
42 | {\r |
ca482e5d |
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 |
cc68a136 |
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 |
ca482e5d |
89 | iMaxWriteSamples = iRate / iWritesPerSec;\r |
90 | if (iRate % iWritesPerSec)\r |
91 | iMaxWriteSamples++;\r |
92 | int bufferedFrames = iWritesPerSec / KUpdatesPerSec;\r |
cc68a136 |
93 | \r |
ca482e5d |
94 | iBufferSize = iMaxWriteSamples * (iStereo ? 4 : 2);\r |
95 | iBufferSize *= bufferedFrames;\r |
cc68a136 |
96 | for (TInt i=0 ; i<KSoundBuffers ; i++)\r |
97 | {\r |
ca482e5d |
98 | iSoundBuffers[i] = HBufC8::NewL(iBufferSize);\r |
99 | iSoundBuffers[i]->Des().FillZ (iBufferSize);\r |
cc68a136 |
100 | }\r |
101 | \r |
102 | iCurrentBuffer = 0;\r |
103 | iCurrentBufferSize = 0;\r |
104 | \r |
ca482e5d |
105 | DEBUGPRINT(_L("sound: iMaxWriteSamples: %i, iBufferSize: %i"), iMaxWriteSamples, iBufferSize);\r |
106 | \r |
cc68a136 |
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 |
ca482e5d |
109 | if (iMdaAudioOutputStream) {\r |
110 | if (iVolume < 0 || iVolume > iMdaAudioOutputStream->MaxVolume())\r |
111 | iVolume = iMdaAudioOutputStream->MaxVolume();\r |
cc68a136 |
112 | delete iMdaAudioOutputStream;\r |
113 | iMdaAudioOutputStream = 0;\r |
114 | }\r |
115 | }\r |
116 | \r |
117 | // returns a pointer to buffer for next frame,\r |
118 | // to be used when iSoundBuffers are used directly\r |
119 | TInt16 *CGameAudioMS::NextFrameL(TInt aPcmFrames)\r |
120 | {\r |
ca482e5d |
121 | TInt mul = iStereo ? 4 : 2;\r |
122 | TInt bytes = aPcmFrames * mul;\r |
123 | iCurrentPosition += bytes / 2;\r |
124 | iCurrentBufferSize += bytes;\r |
cc68a136 |
125 | \r |
ca482e5d |
126 | if (aPcmFrames > iMaxWriteSamples) {\r |
127 | DEBUGPRINT(_L("too many samples: %i > %i"), aPcmFrames, iMaxWriteSamples);\r |
128 | }\r |
129 | \r |
130 | if (iCurrentBufferSize + iMaxWriteSamples * mul > iBufferSize)\r |
cc68a136 |
131 | {\r |
ca482e5d |
132 | //DEBUGPRINT(_L("write on iCurrentBufferSize %i"), iCurrentBufferSize);\r |
cc68a136 |
133 | WriteBlockL();\r |
134 | }\r |
135 | \r |
136 | iScheduler->Schedule();\r |
137 | \r |
138 | if(iListener.iUnderflowed) {\r |
139 | if(iListener.iUnderflowed > KMaxUnderflows) {\r |
140 | delete iMdaAudioOutputStream;\r |
141 | iMdaAudioOutputStream = 0;\r |
142 | return 0;\r |
143 | }\r |
144 | UnderflowedL(); // not again!\r |
145 | }\r |
146 | \r |
147 | return iCurrentPosition;\r |
148 | }\r |
149 | \r |
150 | void CGameAudioMS::WriteBlockL()\r |
151 | {\r |
152 | iScheduler->Schedule();\r |
153 | // do not write until stream is open\r |
154 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
155 | //if(!iListener.iHasCopied) WaitForCopyToCompleteL(); // almost never happens anyway and sometimes even deadlocks?\r |
156 | //iListener.iHasCopied = EFalse;\r |
157 | \r |
158 | \r |
159 | if(!iListener.iUnderflowed) {\r |
160 | TInt64 delta;\r |
161 | // don't write if sound is lagging too much\r |
162 | delta = iTime - iMdaAudioOutputStream->Position().Int64();\r |
163 | if (delta > MAKE_TINT64(0, KMaxLag))\r |
164 | // another query sometimes returns very different result\r |
165 | delta = iTime - iMdaAudioOutputStream->Position().Int64();\r |
166 | \r |
167 | if(delta <= MAKE_TINT64(0, KMaxLag)) {\r |
168 | //RDebug::Print(_L("delta: %i"), iTime.Low() - iMdaAudioOutputStream->Position().Int64().Low());\r |
169 | iSoundBuffers[iCurrentBuffer]->Des().SetLength(iCurrentBufferSize);\r |
170 | iMdaAudioOutputStream->WriteL(*iSoundBuffers[iCurrentBuffer]);\r |
171 | iTime += KBlockTime;\r |
172 | } else {\r |
173 | DEBUGPRINT(_L("lag: %i"), I64LOW(delta));\r |
174 | }\r |
175 | }\r |
176 | \r |
cc68a136 |
177 | if (++iCurrentBuffer == KSoundBuffers)\r |
178 | iCurrentBuffer = 0;\r |
ca482e5d |
179 | iSoundBuffers[iCurrentBuffer]->Des().SetMax();\r |
cc68a136 |
180 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
181 | iCurrentBufferSize = 0;\r |
182 | }\r |
183 | \r |
184 | void CGameAudioMS::Pause()\r |
185 | {\r |
186 | if(!iMdaAudioOutputStream) return;\r |
187 | \r |
188 | iScheduler->Schedule(); // let it finish it's stuff\r |
189 | iMdaAudioOutputStream->Stop();\r |
190 | delete iMdaAudioOutputStream;\r |
191 | iMdaAudioOutputStream = 0;\r |
192 | }\r |
193 | \r |
194 | // call this before doing any playback!\r |
195 | TInt16 *CGameAudioMS::ResumeL()\r |
196 | {\r |
197 | DEBUGPRINT(_L("CGameAudioMS::Resume()"));\r |
198 | iScheduler->Schedule();\r |
199 | \r |
200 | // we act a bit strange here: simulate buffer underflow, which actually starts audio\r |
201 | iListener.iIsOpen = ETrue;\r |
202 | iListener.iUnderflowed = 1;\r |
203 | iListener.iLastError = 0;\r |
cc68a136 |
204 | iCurrentBufferSize = 0;\r |
205 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
206 | return iCurrentPosition;\r |
207 | }\r |
208 | \r |
209 | // handles underflow condition\r |
210 | void CGameAudioMS::UnderflowedL()\r |
211 | {\r |
ca482e5d |
212 | #ifdef DEBUG_UNDERFLOWS\r |
cc68a136 |
213 | DEBUGPRINT(_L("UnderflowedL()"));\r |
ca482e5d |
214 | #endif\r |
cc68a136 |
215 | \r |
216 | if (iListener.iLastError != KErrUnderflow)\r |
217 | {\r |
218 | // recreate the stream\r |
219 | //iMdaAudioOutputStream->Stop();\r |
220 | if(iMdaAudioOutputStream) delete iMdaAudioOutputStream;\r |
221 | iMdaAudioOutputStream = CMdaAudioOutputStream::NewL(iListener, iServer);\r |
222 | iMdaAudioOutputStream->Open(&iMdaAudioDataSettings);\r |
223 | iMdaAudioOutputStream->SetAudioPropertiesL(iMdaAudioDataSettings.iSampleRate, iMdaAudioDataSettings.iChannels);\r |
224 | iMdaAudioOutputStream->SetVolume(iVolume); // new in UIQ3\r |
225 | \r |
226 | iListener.iIsOpen = EFalse; // wait for it to open\r |
227 | //iListener.iHasCopied = ETrue; // but don't wait for last copy to complete\r |
228 | // let it open and feed some stuff to make it happy\r |
229 | User::After(0);\r |
230 | iScheduler->Schedule();\r |
231 | iListener.iLastError = 0;\r |
232 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
233 | } else {\r |
234 | iListener.iLastError = iListener.iUnderflowed = 0;\r |
235 | }\r |
236 | iTime = iMdaAudioOutputStream->Position().Int64();\r |
237 | }\r |
238 | \r |
239 | void CGameAudioMS::WaitForOpenToCompleteL()\r |
240 | {\r |
241 | DEBUGPRINT(_L("CGameAudioMS::WaitForOpenToCompleteL"));\r |
242 | TInt count = 20; // 2 seconds\r |
243 | TInt waitPeriod = 100 * 1000;\r |
244 | \r |
245 | if(!iListener.iIsOpen) {\r |
246 | // it is often enough to do this\r |
247 | User::After(0);\r |
248 | iScheduler->Schedule();\r |
249 | }\r |
250 | while (!iListener.iIsOpen && --count)\r |
251 | {\r |
252 | User::After(waitPeriod);\r |
253 | iScheduler->Schedule();\r |
254 | }\r |
255 | if (!iListener.iIsOpen)\r |
256 | User::LeaveIfError(KErrNotSupported);\r |
257 | }\r |
258 | \r |
ca482e5d |
259 | TInt CGameAudioMS::ChangeVolume(TInt aUp)\r |
cc68a136 |
260 | {\r |
261 | //DEBUGPRINT(_L("CGameAudioMS::ChangeVolume(%i)"), aUp);\r |
262 | \r |
263 | if (iMdaAudioOutputStream) {\r |
264 | if (aUp) {\r |
ca482e5d |
265 | iVolume += 5;\r |
266 | if (iVolume > iMdaAudioOutputStream->MaxVolume())\r |
267 | iVolume = iMdaAudioOutputStream->MaxVolume();\r |
cc68a136 |
268 | } else {\r |
ca482e5d |
269 | iVolume -= 5;\r |
270 | if (iVolume < 0) iVolume = 0;\r |
cc68a136 |
271 | }\r |
272 | iMdaAudioOutputStream->SetVolume(iVolume);\r |
273 | }\r |
ca482e5d |
274 | \r |
275 | return iVolume;\r |
cc68a136 |
276 | }\r |
277 | \r |
278 | void TGameAudioEventListener::MaoscOpenComplete(TInt aError)\r |
279 | {\r |
ca482e5d |
280 | #ifdef DEBUG_UNDERFLOWS\r |
cc68a136 |
281 | DEBUGPRINT(_L("CGameAudioMS::MaoscOpenComplete, error=%d"), aError);\r |
ca482e5d |
282 | #endif\r |
cc68a136 |
283 | \r |
284 | iIsOpen = ETrue;\r |
285 | if(aError) {\r |
286 | iLastError = aError;\r |
287 | iUnderflowed++;\r |
288 | }\r |
289 | else iUnderflowed = 0;\r |
290 | }\r |
291 | \r |
292 | void TGameAudioEventListener::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer)\r |
293 | {\r |
294 | if (aError)\r |
295 | DEBUGPRINT(_L("CGameAudioMS::MaoscBufferCopied, error=%d"), aError);\r |
296 | \r |
297 | // iHasCopied = ETrue;\r |
298 | \r |
299 | if(aError) { // shit!\r |
300 | iLastError = aError;\r |
301 | iUnderflowed++;\r |
302 | }\r |
303 | }\r |
304 | \r |
305 | void TGameAudioEventListener::MaoscPlayComplete(TInt aError)\r |
306 | {\r |
ca482e5d |
307 | #ifdef DEBUG_UNDERFLOWS\r |
cc68a136 |
308 | DEBUGPRINT(_L("CGameAudioMS::MaoscPlayComplete: %i"), aError);\r |
ca482e5d |
309 | #endif\r |
cc68a136 |
310 | if(aError) {\r |
311 | iLastError = aError;\r |
312 | iUnderflowed++; // never happened to me while testing, but just in case\r |
313 | }\r |
314 | }\r |
315 | \r |