cc68a136 |
1 | /*******************************************************************\r |
2 | *\r |
3 | * File: Audio_motorola.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 | // if only I had Motorola to test this on..\r |
16 | \r |
17 | \r |
18 | #include "audio_motorola.h"\r |
19 | \r |
20 | #ifdef __DEBUG_PRINT_SND\r |
21 | #include <e32svr.h> // RDebug\r |
22 | #define DEBUGPRINT(x...) RDebug::Print(x)\r |
23 | #else\r |
24 | #define DEBUGPRINT(x...)\r |
25 | #endif\r |
26 | \r |
27 | \r |
28 | GLDEF_C TInt E32Dll(TDllReason)\r |
29 | {\r |
30 | return KErrNone;\r |
31 | }\r |
32 | \r |
33 | \r |
34 | /*******************************************\r |
35 | *\r |
36 | * CGameAudioMot\r |
37 | *\r |
38 | *******************************************/\r |
39 | \r |
40 | CGameAudioMot::CGameAudioMot(TInt aRate, TBool aStereo, TInt aPcmFrames, TInt aBufferedFrames)\r |
41 | : iRate(aRate), iStereo(aStereo), iBufferedFrames(aBufferedFrames), iPcmFrames(aPcmFrames)\r |
42 | {\r |
43 | DEBUGPRINT(_L("CGameAudioMot::CGameAudioMot"));\r |
44 | }\r |
45 | \r |
46 | \r |
47 | CGameAudioMot* CGameAudioMot::NewL(TInt aRate, TBool aStereo, TInt aPcmFrames, TInt aBufferedFrames)\r |
48 | {\r |
49 | DEBUGPRINT(_L("CGameAudioMot::NewL(%i, %i, %i, %i)"),aRate, aStereo, aPcmFrames, aBufferedFrames);\r |
50 | CGameAudioMot* self = new(ELeave) CGameAudioMot(aRate, aStereo, aPcmFrames, aBufferedFrames);\r |
51 | CleanupStack::PushL(self);\r |
52 | self->ConstructL();\r |
53 | CleanupStack::Pop(); // self\r |
54 | return self;\r |
55 | }\r |
56 | \r |
57 | \r |
58 | CGameAudioMot::~CGameAudioMot()\r |
59 | {\r |
60 | DEBUGPRINT(_L("CGameAudioMot::~CGameAudioMot()"));\r |
61 | if(iAudioOutputStream) {\r |
62 | iScheduler->Schedule(); // let it finish it's stuff\r |
63 | //iAudioOutputStream->Stop();\r |
64 | delete iAudioOutputStream;\r |
65 | }\r |
66 | \r |
67 | if(iAudioControl) delete iAudioControl;\r |
68 | \r |
69 | for (TInt i=0 ; i < KSoundBuffers+1; i++) {\r |
70 | delete iSoundBufferPtrs[i];\r |
71 | delete iSoundBuffers[i];\r |
72 | }\r |
73 | \r |
74 | // Polled AS\r |
75 | if(iScheduler) delete iScheduler;\r |
76 | }\r |
77 | \r |
78 | \r |
79 | void CGameAudioMot::ConstructL()\r |
80 | {\r |
81 | iScheduler = CPolledActiveScheduler::NewL();\r |
82 | \r |
83 | iSettings.iPCMSettings.iSamplingFreq = (TMSampleRate) iRate;\r |
84 | iSettings.iPCMSettings.iStereo = iStereo;\r |
85 | \r |
86 | TInt bytesPerFrame = iStereo ? iPcmFrames << 2 : iPcmFrames << 1;\r |
87 | for (TInt i=0 ; i<KSoundBuffers ; i++)\r |
88 | {\r |
89 | iSoundBuffers[i] = HBufC8::NewL(bytesPerFrame * iBufferedFrames);\r |
90 | iSoundBuffers[i]->Des().FillZ (bytesPerFrame * iBufferedFrames);\r |
91 | iSoundBufferPtrs[i] = new TPtr8( iSoundBuffers[i]->Des() );\r |
92 | }\r |
93 | // because feeding 2 buffers after an underflow is a little too much, but feeding 1 may be not enough,\r |
94 | // prepare this ~50ms empty buffer to additionaly feed after every underflow.\r |
95 | iSoundBuffers[KSoundBuffers] = HBufC8::NewL(bytesPerFrame * (iBufferedFrames / 4));\r |
96 | iSoundBuffers[KSoundBuffers]->Des().FillZ (bytesPerFrame * (iBufferedFrames / 4));\r |
97 | iSoundBufferPtrs[KSoundBuffers] = new TPtr8( iSoundBuffers[KSoundBuffers]->Des() );\r |
98 | \r |
99 | iCurrentBuffer = 0;\r |
100 | iListener.iFatalError = iListener.iIsOpen = iListener.iIsCtrlOpen = EFalse;\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 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
104 | if(iAudioOutputStream) {\r |
105 | delete iAudioOutputStream;\r |
106 | iAudioOutputStream = 0;\r |
107 | }\r |
108 | \r |
109 | // ceate audio control object\r |
110 | iAudioControl = CMAudioAC::NewL(iListener);\r |
111 | }\r |
112 | \r |
113 | \r |
114 | // returns a pointer to buffer for next frame,\r |
115 | // to be used when iSoundBuffers are used directly\r |
116 | TInt16 *CGameAudioMot::NextFrameL()\r |
117 | {\r |
118 | iCurrentPosition += iPcmFrames << (iStereo?1:0);\r |
119 | \r |
120 | if (++iFrameCount == iBufferedFrames)\r |
121 | {\r |
122 | WriteBlockL();\r |
123 | }\r |
124 | \r |
125 | iScheduler->Schedule();\r |
126 | \r |
127 | if(iListener.iFatalError || iListener.iUnderflowed > KMaxUnderflows) {\r |
128 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
129 | iAudioOutputStream = 0;\r |
130 | return 0;\r |
131 | }\r |
132 | else if(iListener.iUnderflowed) UnderflowedL();\r |
133 | \r |
134 | return iCurrentPosition;\r |
135 | }\r |
136 | \r |
137 | TInt16 *CGameAudioMot::DupeFrameL(TInt &aUnderflowed)\r |
138 | {\r |
139 | TInt shorts = iStereo ? (iPcmFrames << 1) : iPcmFrames;\r |
140 | if(iFrameCount)\r |
141 | Mem::Copy(iCurrentPosition, iCurrentPosition-shorts, shorts<<1);\r |
142 | else {\r |
143 | TInt lastBuffer = iCurrentBuffer;\r |
144 | if(--lastBuffer < 0) lastBuffer = KSoundBuffers - 1;\r |
145 | Mem::Copy(iCurrentPosition, ((TInt16*) (iSoundBuffers[lastBuffer]->Ptr()))+shorts*(iBufferedFrames-1), shorts<<1);\r |
146 | } \r |
147 | iCurrentPosition += shorts;\r |
148 | \r |
149 | if (++iFrameCount == iBufferedFrames)\r |
150 | {\r |
151 | WriteBlockL();\r |
152 | }\r |
153 | \r |
154 | iScheduler->Schedule();\r |
155 | \r |
156 | if(iListener.iFatalError || iListener.iUnderflowed > KMaxUnderflows) {\r |
157 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
158 | iAudioOutputStream = 0;\r |
159 | return 0;\r |
160 | }\r |
161 | else if((aUnderflowed = iListener.iUnderflowed)) UnderflowedL(); // not again!\r |
162 | \r |
163 | return iCurrentPosition;\r |
164 | }\r |
165 | \r |
166 | void CGameAudioMot::WriteBlockL()\r |
167 | {\r |
168 | iScheduler->Schedule();\r |
169 | \r |
170 | // do not write until stream is open\r |
171 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
172 | //if(!iListener.iHasCopied) WaitForCopyToCompleteL(); // almost never happens anyway and sometimes even deadlocks?\r |
173 | //iListener.iHasCopied = EFalse;\r |
174 | \r |
175 | \r |
176 | if(!iListener.iUnderflowed) {\r |
177 | iAudioOutputStream->QueueBufferL(iSoundBufferPtrs[iCurrentBuffer]);\r |
178 | // it is certain we already Queued at least 2 buffers (one just after underflow, another above)\r |
179 | if(!iDecoding) {\r |
180 | iAudioOutputStream->DecodeL();\r |
181 | iDecoding = ETrue;\r |
182 | }\r |
183 | }\r |
184 | \r |
185 | iFrameCount = 0;\r |
186 | if (++iCurrentBuffer == KSoundBuffers)\r |
187 | iCurrentBuffer = 0;\r |
188 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
189 | }\r |
190 | \r |
191 | void CGameAudioMot::Pause()\r |
192 | {\r |
193 | if(!iAudioOutputStream) return;\r |
194 | \r |
195 | iScheduler->Schedule();\r |
196 | // iAudioOutputStream->Stop(); // may be this breaks everything in A925?\r |
197 | delete iAudioOutputStream;\r |
198 | iAudioOutputStream = 0;\r |
199 | }\r |
200 | \r |
201 | // call this before doing any playback!\r |
202 | TInt16 *CGameAudioMot::ResumeL()\r |
203 | {\r |
204 | DEBUGPRINT(_L("CGameAudioMot::Resume()"));\r |
205 | iScheduler->Schedule();\r |
206 | \r |
207 | // we act a bit strange here: simulate buffer underflow, which actually starts audio\r |
208 | iListener.iIsOpen = ETrue;\r |
209 | iListener.iUnderflowed = 1;\r |
210 | iListener.iFatalError = EFalse;\r |
211 | iFrameCount = 0;\r |
212 | iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();\r |
213 | return iCurrentPosition;\r |
214 | }\r |
215 | \r |
216 | // handles underflow condition\r |
217 | void CGameAudioMot::UnderflowedL()\r |
218 | {\r |
219 | // recreate the stream\r |
220 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
221 | if(iListener.iUnderflowed > 4) {\r |
222 | // HACK: A925 user said sound works for the first time, but fails after pause/resume, etc.\r |
223 | // at the very beginning we create and delete CMAudioFB object, maybe we should do this every time?\r |
224 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
225 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
226 | }\r |
227 | \r |
228 | iAudioOutputStream = CMAudioFB::NewL(EMAudioFBRequestTypeDecode, EMAudioFBFormatPCM, iSettings, iListener);\r |
229 | iListener.iIsOpen = EFalse; // wait for it to open\r |
230 | iDecoding = EFalse;\r |
231 | //iListener.iHasCopied = ETrue; // but don't wait for last copy to complete\r |
232 | // let it open and feed some stuff to make it happy\r |
233 | User::After(0);\r |
234 | //TInt lastBuffer = iCurrentBuffer;\r |
235 | //if(--lastBuffer < 0) lastBuffer = KSoundBuffers - 1;\r |
236 | iScheduler->Schedule();\r |
237 | if(!iListener.iIsOpen) WaitForOpenToCompleteL();\r |
238 | if(iListener.iUnderflowed) {\r |
239 | // something went wrong again. May be it needs time? Trying to fix something without ability to test is hell.\r |
240 | if(iAudioOutputStream) delete iAudioOutputStream;\r |
241 | iAudioOutputStream = 0;\r |
242 | User::After(50*000);\r |
243 | iScheduler->Schedule();\r |
244 | return;\r |
245 | }\r |
246 | \r |
247 | iAudioOutputStream->QueueBufferL(iSoundBufferPtrs[KSoundBuffers]); // try a short buffer with hope to reduce lag\r |
248 | }\r |
249 | \r |
250 | \r |
251 | void CGameAudioMot::ChangeVolume(TInt aUp)\r |
252 | {\r |
253 | if(iAudioControl && iListener.iIsCtrlOpen)\r |
254 | {\r |
255 | TInt vol = iAudioControl->GetMasterVolume();\r |
256 | TInt max = iAudioControl->GetMaxMasterVolume();\r |
257 | \r |
258 | if(aUp) vol++; // adjust volume\r |
259 | else vol--;\r |
260 | \r |
261 | if(vol >= 0 && vol <= max)\r |
262 | {\r |
263 | iAudioControl->SetMasterVolume(vol);\r |
264 | }\r |
265 | }\r |
266 | }\r |
267 | \r |
268 | \r |
269 | void CGameAudioMot::WaitForOpenToCompleteL()\r |
270 | {\r |
271 | DEBUGPRINT(_L("CGameAudioMot::WaitForOpenToCompleteL"));\r |
272 | TInt count = 20; // 2 seconds\r |
273 | TInt waitPeriod = 100 * 1000;\r |
274 | \r |
275 | if(!iListener.iIsOpen) {\r |
276 | // it is often enough to do this\r |
277 | User::After(0);\r |
278 | iScheduler->Schedule();\r |
279 | }\r |
280 | while (!iListener.iIsOpen && --count)\r |
281 | {\r |
282 | User::After(waitPeriod);\r |
283 | iScheduler->Schedule();\r |
284 | }\r |
285 | if (!iListener.iIsOpen)\r |
286 | User::LeaveIfError(KErrNotSupported);\r |
287 | }\r |
288 | \r |
289 | \r |
290 | \r |
291 | void TGameAudioEventListener::OnEvent(TMAudioFBCallbackState aState, TInt aError)\r |
292 | {\r |
293 | switch ( aState )\r |
294 | {\r |
295 | case EMAudioFBCallbackStateReady:\r |
296 | iIsOpen = ETrue;\r |
297 | iUnderflowed = 0;\r |
298 | break;\r |
299 | \r |
300 | case EMAudioFBCallbackStateDecodeCompleteStopped:\r |
301 | break;\r |
302 | \r |
303 | //case EMAudioFBCallbackStateDecodeFileSystemError:\r |
304 | case EMAudioFBCallbackStateDecodeError:\r |
305 | switch( aError )\r |
306 | {\r |
307 | case EMAudioFBCallbackErrorBufferFull:\r |
308 | case EMAudioFBCallbackErrorForcedStop:\r |
309 | case EMAudioFBCallbackErrorForcedClose:\r |
310 | //case EMAudioFBCallbackErrorForcedPause:\r |
311 | case EMAudioFBCallbackErrorPriorityRejection:\r |
312 | case EMAudioFBCallbackErrorAlertModeRejection:\r |
313 | case EMAudioFBCallbackErrorResourceRejection:\r |
314 | case EMAudioFBCallbackErrorUnknown:\r |
315 | iUnderflowed++;\r |
316 | break;\r |
317 | \r |
318 | // these look like really bad errors\r |
319 | case EMAudioFBCallbackErrorInvalidParameter:\r |
320 | case EMAudioFBCallbackErrorWrongState:\r |
321 | case EMAudioFBCallbackErrorFormatNotSupported:\r |
322 | case EMAudioFBCallbackErrorFunctionNotSupported:\r |
323 | case EMAudioFBCallbackErrorNoBuffer:\r |
324 | case EMAudioFBCallbackErrorSampleOrBitRateNotSupported:\r |
325 | //case EMAudioFBCallbackErrorPriorityOrPreferenceNotSupported:\r |
326 | //case EMAudioFBCallbackErrorFileSystemFull:\r |
327 | //iFatalError = ETrue;\r |
328 | // who cares, just keep retrying\r |
329 | iUnderflowed++;\r |
330 | break;\r |
331 | \r |
332 | default:\r |
333 | iUnderflowed++;\r |
334 | break;\r |
335 | }\r |
336 | // in error condition we also set to open, so that the\r |
337 | // framework would not leave, catch the error and retry\r |
338 | iIsOpen = ETrue;\r |
339 | break;\r |
340 | \r |
341 | default:\r |
342 | break;\r |
343 | }\r |
344 | }\r |
345 | \r |
346 | void TGameAudioEventListener::OnEvent(TMAudioFBCallbackState aState, TInt aError, TDes8* aBuffer)\r |
347 | {\r |
348 | switch( aState )\r |
349 | {\r |
350 | case EMAudioFBCallbackStateDecodeBufferDecoded:\r |
351 | break;\r |
352 | \r |
353 | default:\r |
354 | OnEvent( aState, aError );\r |
355 | break;\r |
356 | }\r |
357 | }\r |
358 | \r |
359 | void TGameAudioEventListener::OnEvent(TMAudioACCallbackState aState, TInt aError)\r |
360 | {\r |
361 | if(aState == EMAudioACCallbackStateReady) iIsCtrlOpen = ETrue;\r |
362 | }\r |
363 | \r |