picodrive/platform/uiq3/engine/audio_mediaserver.cpp
notaz 6a13ef3f56 final fixes for UIQ3
git-svn-id: file:///home/notaz/opt/svn/PicoDrive@573 be3aeb3a-fb24-0410-a615-afba39da0efa
2008-08-24 13:26:57 +00:00

317 lines
9.3 KiB
C++

/*******************************************************************
*
* File: Audio_mediaserver.cpp
*
* Author: Peter van Sebille (peter@yipton.net)
*
* Modified/adapted for picodriveN by notaz, 2006
*
* (c) Copyright 2006, notaz
* (c) Copyright 2001, Peter van Sebille
* All Rights Reserved
*
*******************************************************************/
#include "audio_mediaserver.h"
#include "debug.h"
//#define DEBUG_UNDERFLOWS
//#undef DEBUGPRINT
//#define DEBUGPRINT(x...)
const TInt KUpdatesPerSec = 10;
const TInt KBlockTime = 1000000 / KUpdatesPerSec;
const TInt KMaxLag = 200000; // max sound lag, lower values increase chance of underflow
const TInt KMaxUnderflows = 50; // max underflows/API errors we are going allow in a row (to prevent lockups)
/*******************************************
*
* CGameAudioMS
*
*******************************************/
CGameAudioMS::CGameAudioMS(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)
: iRate(aRate), iStereo(aStereo), iWritesPerSec(aWritesPerSec), iVolume(aVolume)
{
}
CGameAudioMS* CGameAudioMS::NewL(TInt aRate, TBool aStereo, TInt aWritesPerSec, TInt aVolume)
{
DEBUGPRINT(_L("CGameAudioMS::NewL(%i, %i, %i, %i)"), aRate, aStereo, aWritesPerSec, aVolume);
CGameAudioMS* self = new(ELeave) CGameAudioMS(aRate, aStereo, aWritesPerSec, aVolume);
CleanupStack::PushL(self);
self->ConstructL();
CleanupStack::Pop(); // self
return self;
}
CGameAudioMS::~CGameAudioMS()
{
DEBUGPRINT(_L("CGameAudioMS::~CGameAudioMS()"));
if(iMdaAudioOutputStream) {
iScheduler->Schedule(); // let it finish it's stuff
iMdaAudioOutputStream->Stop();
delete iMdaAudioOutputStream;
}
if(iServer) delete iServer;
for (TInt i=0; i<KSoundBuffers; i++)
delete iSoundBuffers[i];
// Polled AS
//if(iScheduler) delete iScheduler;
}
void CGameAudioMS::ConstructL()
{
iServer = CMdaServer::NewL();
// iScheduler = CPolledActiveScheduler::NewL();
iScheduler = CPolledActiveScheduler::Instance();
switch(iRate) {
case 11025: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate11025Hz; break;
case 16000: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate16000Hz; break;
case 22050: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate22050Hz; break;
case 44100: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate44100Hz; break;
default: iMdaAudioDataSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz; break;
}
iMdaAudioDataSettings.iChannels = (iStereo) ? TMdaAudioDataSettings::EChannelsStereo : TMdaAudioDataSettings::EChannelsMono;
iMdaAudioDataSettings.iCaps = TMdaAudioDataSettings::ESampleRateFixed | iMdaAudioDataSettings.iSampleRate;
iMdaAudioDataSettings.iFlags = TMdaAudioDataSettings::ENoNetworkRouting;
iMaxWriteSamples = iRate / iWritesPerSec;
if (iRate % iWritesPerSec)
iMaxWriteSamples++;
int bufferedFrames = iWritesPerSec / KUpdatesPerSec;
iBufferSize = iMaxWriteSamples * (iStereo ? 4 : 2);
iBufferSize *= bufferedFrames;
for (TInt i=0 ; i<KSoundBuffers ; i++)
{
// it seems .SetLength(max) throws USER:23 panic,
// so make them a bit larger
iSoundBuffers[i] = HBufC8::NewL(iBufferSize+4);
iSoundBuffers[i]->Des().FillZ (iBufferSize+4);
}
iCurrentBuffer = 0;
iCurrentBufferSize = 0;
DEBUGPRINT(_L("sound: iMaxWriteSamples: %i, iBufferSize: %i"), iMaxWriteSamples, iBufferSize);
// here we actually test if we can create and open CMdaAudioOutputStream at all, but really create and use it later.
iMdaAudioOutputStream = CMdaAudioOutputStream::NewL(iListener, iServer);
if (iMdaAudioOutputStream) {
if (iVolume < 0 || iVolume > iMdaAudioOutputStream->MaxVolume())
iVolume = iMdaAudioOutputStream->MaxVolume();
delete iMdaAudioOutputStream;
iMdaAudioOutputStream = 0;
}
}
// returns a pointer to buffer for next frame,
// to be used when iSoundBuffers are used directly
TInt16 *CGameAudioMS::NextFrameL(TInt aPcmFrames)
{
TInt mul = iStereo ? 4 : 2;
TInt bytes = aPcmFrames * mul;
iCurrentPosition += bytes / 2;
iCurrentBufferSize += bytes;
if (aPcmFrames > iMaxWriteSamples) {
DEBUGPRINT(_L("too many samples: %i > %i"), aPcmFrames, iMaxWriteSamples);
}
if (iCurrentBufferSize + iMaxWriteSamples * mul > iBufferSize)
{
//DEBUGPRINT(_L("write on iCurrentBufferSize %i"), iCurrentBufferSize);
WriteBlockL();
}
iScheduler->Schedule();
if(iListener.iUnderflowed) {
if(iListener.iUnderflowed > KMaxUnderflows) {
delete iMdaAudioOutputStream;
iMdaAudioOutputStream = 0;
return 0;
}
UnderflowedL(); // not again!
}
return iCurrentPosition;
}
void CGameAudioMS::WriteBlockL()
{
iScheduler->Schedule();
// do not write until stream is open
if(!iListener.iIsOpen) WaitForOpenToCompleteL();
//if(!iListener.iHasCopied) WaitForCopyToCompleteL(); // almost never happens anyway and sometimes even deadlocks?
//iListener.iHasCopied = EFalse;
if(!iListener.iUnderflowed) {
TInt64 delta;
// don't write if sound is lagging too much
delta = iTime - iMdaAudioOutputStream->Position().Int64();
if (delta > MAKE_TINT64(0, KMaxLag))
// another query sometimes returns very different result
delta = iTime - iMdaAudioOutputStream->Position().Int64();
if(delta <= MAKE_TINT64(0, KMaxLag)) {
//RDebug::Print(_L("delta: %i"), iTime.Low() - iMdaAudioOutputStream->Position().Int64().Low());
iSoundBuffers[iCurrentBuffer]->Des().SetLength(iCurrentBufferSize);
iMdaAudioOutputStream->WriteL(*iSoundBuffers[iCurrentBuffer]);
iTime += KBlockTime;
} else {
DEBUGPRINT(_L("lag: %i"), I64LOW(delta));
}
}
if (++iCurrentBuffer == KSoundBuffers)
iCurrentBuffer = 0;
iSoundBuffers[iCurrentBuffer]->Des().SetMax();
iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();
iCurrentBufferSize = 0;
}
void CGameAudioMS::Pause()
{
if(!iMdaAudioOutputStream) return;
iScheduler->Schedule(); // let it finish it's stuff
iMdaAudioOutputStream->Stop();
delete iMdaAudioOutputStream;
iMdaAudioOutputStream = 0;
}
// call this before doing any playback!
TInt16 *CGameAudioMS::ResumeL()
{
DEBUGPRINT(_L("CGameAudioMS::Resume()"));
iScheduler->Schedule();
// we act a bit strange here: simulate buffer underflow, which actually starts audio
iListener.iIsOpen = ETrue;
iListener.iUnderflowed = 1;
iListener.iLastError = 0;
iCurrentBufferSize = 0;
iCurrentPosition = (TInt16*) iSoundBuffers[iCurrentBuffer]->Ptr();
return iCurrentPosition;
}
// handles underflow condition
void CGameAudioMS::UnderflowedL()
{
#ifdef DEBUG_UNDERFLOWS
DEBUGPRINT(_L("UnderflowedL()"));
#endif
if (iListener.iLastError != KErrUnderflow)
{
// recreate the stream
//iMdaAudioOutputStream->Stop();
if(iMdaAudioOutputStream) delete iMdaAudioOutputStream;
iMdaAudioOutputStream = CMdaAudioOutputStream::NewL(iListener, iServer);
iMdaAudioOutputStream->Open(&iMdaAudioDataSettings);
iMdaAudioOutputStream->SetAudioPropertiesL(iMdaAudioDataSettings.iSampleRate, iMdaAudioDataSettings.iChannels);
iMdaAudioOutputStream->SetVolume(iVolume); // new in UIQ3
iListener.iIsOpen = EFalse; // wait for it to open
//iListener.iHasCopied = ETrue; // but don't wait for last copy to complete
// let it open and feed some stuff to make it happy
User::After(0);
iScheduler->Schedule();
iListener.iLastError = 0;
if(!iListener.iIsOpen) WaitForOpenToCompleteL();
} else {
iListener.iLastError = iListener.iUnderflowed = 0;
}
iTime = iMdaAudioOutputStream->Position().Int64();
}
void CGameAudioMS::WaitForOpenToCompleteL()
{
DEBUGPRINT(_L("CGameAudioMS::WaitForOpenToCompleteL"));
TInt count = 20; // 2 seconds
TInt waitPeriod = 100 * 1000;
if(!iListener.iIsOpen) {
// it is often enough to do this
User::After(0);
iScheduler->Schedule();
}
while (!iListener.iIsOpen && --count)
{
User::After(waitPeriod);
iScheduler->Schedule();
}
if (!iListener.iIsOpen)
User::LeaveIfError(KErrNotSupported);
}
TInt CGameAudioMS::ChangeVolume(TInt aUp)
{
//DEBUGPRINT(_L("CGameAudioMS::ChangeVolume(%i)"), aUp);
if (iMdaAudioOutputStream) {
if (aUp) {
iVolume += 5;
if (iVolume > iMdaAudioOutputStream->MaxVolume())
iVolume = iMdaAudioOutputStream->MaxVolume();
} else {
iVolume -= 5;
if (iVolume < 0) iVolume = 0;
}
iMdaAudioOutputStream->SetVolume(iVolume);
}
return iVolume;
}
void TGameAudioEventListener::MaoscOpenComplete(TInt aError)
{
#ifdef DEBUG_UNDERFLOWS
DEBUGPRINT(_L("CGameAudioMS::MaoscOpenComplete, error=%d"), aError);
#endif
iIsOpen = ETrue;
if(aError) {
iLastError = aError;
iUnderflowed++;
}
else iUnderflowed = 0;
}
void TGameAudioEventListener::MaoscBufferCopied(TInt aError, const TDesC8& aBuffer)
{
if (aError)
DEBUGPRINT(_L("CGameAudioMS::MaoscBufferCopied, error=%d"), aError);
// iHasCopied = ETrue;
if(aError) { // shit!
iLastError = aError;
iUnderflowed++;
}
}
void TGameAudioEventListener::MaoscPlayComplete(TInt aError)
{
#ifdef DEBUG_UNDERFLOWS
DEBUGPRINT(_L("CGameAudioMS::MaoscPlayComplete: %i"), aError);
#endif
if(aError) {
iLastError = aError;
iUnderflowed++; // never happened to me while testing, but just in case
}
}