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 | \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 |