1 /*******************************************************************
\r
3 * File: Audio_mediaserver.cpp
\r
5 * Author: Peter van Sebille (peter@yipton.net)
\r
7 * Modified/adapted for picodriveN by notaz, 2006
\r
9 * (c) Copyright 2006, notaz
\r
10 * (c) Copyright 2001, Peter van Sebille
\r
11 * All Rights Reserved
\r
13 *******************************************************************/
\r
15 #include "audio_mediaserver.h"
\r
18 //#define DEBUG_UNDERFLOWS
\r
20 //#define DEBUGPRINT(x...)
\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
29 /*******************************************
\r
33 *******************************************/
\r
35 CGameAudioMS::CGameAudioMS(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)
\r
36 : iRate(aRate), iStereo(aStereo), iWritesPerSec(aWritesPerSec), iVolume(aVolume)
\r
41 CGameAudioMS* CGameAudioMS::NewL(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)
\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
47 CleanupStack::Pop(); // self
\r
52 CGameAudioMS::~CGameAudioMS()
\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
60 if(iServer) delete iServer;
\r
62 for (TInt i=0; i<KSoundBuffers; i++)
\r
63 delete iSoundBuffers[i];
\r
66 //if(iScheduler) delete iScheduler;
\r
70 void CGameAudioMS::ConstructL()
\r
72 iServer = CMdaServer::NewL();
\r
74 // iScheduler = CPolledActiveScheduler::NewL();
\r
75 iScheduler = CPolledActiveScheduler::Instance();
\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
85 iMdaAudioDataSettings.iChannels = (iStereo) ? TMdaAudioDataSettings::EChannelsStereo : TMdaAudioDataSettings::EChannelsMono;
\r
86 iMdaAudioDataSettings.iCaps = TMdaAudioDataSettings::ESampleRateFixed | iMdaAudioDataSettings.iSampleRate;
\r
87 iMdaAudioDataSettings.iFlags = TMdaAudioDataSettings::ENoNetworkRouting;
\r
89 iMaxWriteSamples = iRate / iWritesPerSec;
\r
90 if (iRate % iWritesPerSec)
\r
92 int bufferedFrames = iWritesPerSec / KUpdatesPerSec;
\r
94 iBufferSize = iMaxWriteSamples * (iStereo ? 4 : 2);
\r
95 iBufferSize *= bufferedFrames;
\r
96 for (TInt i=0 ; i<KSoundBuffers ; i++)
\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
104 iCurrentBuffer = 0;
\r
105 iCurrentBufferSize = 0;
\r
107 DEBUGPRINT(_L("sound: iMaxWriteSamples: %i, iBufferSize: %i"), iMaxWriteSamples, iBufferSize);
\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
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
123 TInt mul = iStereo ? 4 : 2;
\r
124 TInt bytes = aPcmFrames * mul;
\r
125 iCurrentPosition += bytes / 2;
\r
126 iCurrentBufferSize += bytes;
\r
128 if (aPcmFrames > iMaxWriteSamples) {
\r
129 DEBUGPRINT(_L("too many samples: %i > %i"), aPcmFrames, iMaxWriteSamples);
\r
132 if (iCurrentBufferSize + iMaxWriteSamples * mul > iBufferSize)
\r
134 //DEBUGPRINT(_L("write on iCurrentBufferSize %i"), iCurrentBufferSize);
\r
138 iScheduler->Schedule();
\r
140 if(iListener.iUnderflowed) {
\r
141 if(iListener.iUnderflowed > KMaxUnderflows) {
\r
142 delete iMdaAudioOutputStream;
\r
143 iMdaAudioOutputStream = 0;
\r
146 UnderflowedL(); // not again!
\r
149 return iCurrentPosition;
\r
152 void CGameAudioMS::WriteBlockL()
\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
161 if(!iListener.iUnderflowed) {
\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
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
175 DEBUGPRINT(_L("lag: %i"), I64LOW(delta));
\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
186 void CGameAudioMS::Pause()
\r
188 if(!iMdaAudioOutputStream) return;
\r
190 iScheduler->Schedule(); // let it finish it's stuff
\r
191 iMdaAudioOutputStream->Stop();
\r
192 delete iMdaAudioOutputStream;
\r
193 iMdaAudioOutputStream = 0;
\r
196 // call this before doing any playback!
\r
197 TInt16 *CGameAudioMS::ResumeL()
\r
199 DEBUGPRINT(_L("CGameAudioMS::Resume()"));
\r
200 iScheduler->Schedule();
\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
211 // handles underflow condition
\r
212 void CGameAudioMS::UnderflowedL()
\r
214 #ifdef DEBUG_UNDERFLOWS
\r
215 DEBUGPRINT(_L("UnderflowedL()"));
\r
218 if (iListener.iLastError != KErrUnderflow)
\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
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
232 iScheduler->Schedule();
\r
233 iListener.iLastError = 0;
\r
234 if(!iListener.iIsOpen) WaitForOpenToCompleteL();
\r
236 iListener.iLastError = iListener.iUnderflowed = 0;
\r
238 iTime = iMdaAudioOutputStream->Position().Int64();
\r
241 void CGameAudioMS::WaitForOpenToCompleteL()
\r
243 DEBUGPRINT(_L("CGameAudioMS::WaitForOpenToCompleteL"));
\r
244 TInt count = 20; // 2 seconds
\r
245 TInt waitPeriod = 100 * 1000;
\r
247 if(!iListener.iIsOpen) {
\r
248 // it is often enough to do this
\r
250 iScheduler->Schedule();
\r
252 while (!iListener.iIsOpen && --count)
\r
254 User::After(waitPeriod);
\r
255 iScheduler->Schedule();
\r
257 if (!iListener.iIsOpen)
\r
258 User::LeaveIfError(KErrNotSupported);
\r
261 TInt CGameAudioMS::ChangeVolume(TInt aUp)
\r
263 //DEBUGPRINT(_L("CGameAudioMS::ChangeVolume(%i)"), aUp);
\r
265 if (iMdaAudioOutputStream) {
\r
268 if (iVolume > iMdaAudioOutputStream->MaxVolume())
\r
269 iVolume = iMdaAudioOutputStream->MaxVolume();
\r
272 if (iVolume < 0) iVolume = 0;
\r
274 iMdaAudioOutputStream->SetVolume(iVolume);
\r
280 void TGameAudioEventListener::MaoscOpenComplete(TInt aError)
\r
282 #ifdef DEBUG_UNDERFLOWS
\r
283 DEBUGPRINT(_L("CGameAudioMS::MaoscOpenComplete, error=%d"), aError);
\r
288 iLastError = aError;
\r
291 else iUnderflowed = 0;
\r
294 void TGameAudioEventListener::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer)
\r
297 DEBUGPRINT(_L("CGameAudioMS::MaoscBufferCopied, error=%d"), aError);
\r
299 // iHasCopied = ETrue;
\r
301 if(aError) { // shit!
\r
302 iLastError = aError;
\r
307 void TGameAudioEventListener::MaoscPlayComplete(TInt aError)
\r
309 #ifdef DEBUG_UNDERFLOWS
\r
310 DEBUGPRINT(_L("CGameAudioMS::MaoscPlayComplete: %i"), aError);
\r
313 iLastError = aError;
\r
314 iUnderflowed++; // never happened to me while testing, but just in case
\r