From 51925aa92e812ec6fc3afd8c39e6c3dc4112eda9 Mon Sep 17 00:00:00 2001
From: Michael Fitzmayer <[EMAIL REDACTED]>
Date: Wed, 6 May 2026 18:31:37 +0200
Subject: [PATCH] [N-Gage] Resolve hang on repeated app launch.
CAudio::~CAudio() waited on MaoscBufferCopied(KErrAbort) to set
EStateDone, but that callback can never fire once the active scheduler
loop has exited, deadlocking every close.
- StopThread() before iStream->Stop() in ~CAudio()
- Force iState = EStateDone instead of waiting on a dead callback
- Add 5s timeout to AudioIsReady() poll in E32Main()
- Fix CleanupStack LIFO pop order (mainApp before gRenderer)
---
src/audio/ngage/SDL_ngageaudio.cpp | 119 ++++++++++------------------
src/main/ngage/SDL_sysmain_main.cpp | 13 ++-
2 files changed, 54 insertions(+), 78 deletions(-)
diff --git a/src/audio/ngage/SDL_ngageaudio.cpp b/src/audio/ngage/SDL_ngageaudio.cpp
index 687b2c597ce1a..246ea2dd42d12 100644
--- a/src/audio/ngage/SDL_ngageaudio.cpp
+++ b/src/audio/ngage/SDL_ngageaudio.cpp
@@ -75,13 +75,17 @@ void CAudio::ConstructL(TInt aLatency)
CAudio::~CAudio()
{
if (iStream) {
+ // Stop the processing thread before stopping the stream.
+ StopThread();
+
iStream->Stop();
- while (iState != EStateDone) {
- User::After(100000); // 100ms.
- }
+ // MaoscBufferCopied(KErrAbort) can't fire here because the active
+ // scheduler is no longer pumping; force the state to avoid deadlock.
+ iState = EStateDone;
delete iStream;
+ iStream = NULL;
}
}
@@ -98,12 +102,9 @@ void CAudio::Start()
}
}
-
-
void CAudio::RunL()
{
iTimerActive = EFalse;
-
}
void CAudio::DoCancel()
@@ -117,10 +118,8 @@ void CAudio::StartThread()
TInt heapMinSize = 8192; // 8 KB initial heap size.
TInt heapMaxSize = 1024 * 1024; // 1 MB maximum heap size.
-
TInt err = iProcess.Create(_L("ProcessThread"), ProcessThreadCB, KDefaultStackSize * 2, heapMinSize, heapMaxSize, this);
- if (err == KErrNone)
- {
+ if (err == KErrNone) {
TThreadPriority prio = EPriorityLess;
const char *prioHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY);
@@ -130,7 +129,6 @@ void CAudio::StartThread()
RThread().SetPriority(prio);
}
-
iProcess.SetPriority(prio);
iProcess.Resume();
} else {
@@ -148,42 +146,36 @@ void CAudio::StopThread()
}
/***************************************************
-* ProcessThreadCB -
-*
-* This thread calls the SDL mixer when the buffer is ready and self->iState == EStatePlaying (basically other than initial stated, when not writing)
-*
-* It only mixes, never calls WriteL
-****************************************************/
+ * ProcessThreadCB -
+ *
+ * This thread calls the SDL mixer when the buffer is ready and self->iState == EStatePlaying (basically other than initial stated, when not writing)
+ *
+ * It only mixes, never calls WriteL
+ ****************************************************/
TInt CAudio::ProcessThreadCB(TAny *aPtr)
{
CTrapCleanup *cleanup = CTrapCleanup::New();
if (!cleanup)
return KErrNoMemory;
-
+
CAudio *self = static_cast<CAudio *>(aPtr);
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
-
TInt processTick = 40000; // Default 40ms
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
- if (tickHint)
- {
+ if (tickHint) {
processTick = SDL_atoi(tickHint) * 1000;
}
- while (self->iStreamStarted)
- {
- if (self->iState == EStatePlaying && !self->iBufferReady)
- {
- /* Ask SDL to mix audio into buffer[fill_index]*/
+ while (self->iStreamStarted) {
+ if (self->iState == EStatePlaying && !self->iBufferReady) {
+ /* Ask SDL to mix audio into buffer[fill_index]*/
SDL_PlaybackAudioThreadIterate(device);
- /* Signal AudioThreadCB to write it*/
+ /* Signal AudioThreadCB to write it*/
self->iBufferReady = ETrue;
- }
- else
- {
+ } else {
/*if we are not ready to obtain the mix data we sleep a bit this thread*/
User::After(processTick);
}
@@ -195,17 +187,17 @@ TInt CAudio::ProcessThreadCB(TAny *aPtr)
static TBool gAudioRunning;
/***************************************************
-* AudioThreadCB -
-*
-* This thread owns the scheduler and calls WriteL, wich queues the assigned sound buffer to be played
-****************************************************/
+ * AudioThreadCB -
+ *
+ * This thread owns the scheduler and calls WriteL, wich queues the assigned sound buffer to be played
+ ****************************************************/
TInt AudioThreadCB(TAny *aParams)
{
CTrapCleanup *cleanup = CTrapCleanup::New();
CActiveScheduler *scheduler = new CActiveScheduler();
CActiveScheduler::Install(scheduler);
-
+
TRAPD(err, {
TInt latency = *(TInt *)aParams;
CAudio *audio = CAudio::NewL(latency);
@@ -215,7 +207,6 @@ TInt AudioThreadCB(TAny *aParams)
gAudioRunning = ETrue;
audio->Start();
-
TInt processTick = 5000; // Default 5ms
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
@@ -223,34 +214,27 @@ TInt AudioThreadCB(TAny *aParams)
processTick = SDL_atoi(tickHint) * 1000;
}
-
- while (gAudioRunning)
- {
+ while (gAudioRunning) {
TInt error;
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
-
+
/*there is some mix data sound ready*/
- if (audio->iBufferReady)
- {
+ if (audio->iBufferReady) {
audio->iBufferReady = EFalse;
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
- if (device && device->hidden)
- {
+ if (device && device->hidden) {
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
audio->iState = EStateWriting;
/*sends the chuck mixed to the queue*/
audio->iBufDes.Set(phdata->buffer[phdata->fill_index], device->buffer_size, device->buffer_size);
TRAPD(werr, audio->iStream->WriteL(audio->iBufDes));
- if (werr != KErrNone)
- {
+ if (werr != KErrNone) {
/*asks ProcessThreadCB to bring another mix chunk*/
audio->iState = EStatePlaying;
- }
- else
- {
+ } else {
/*swap buffers so while this buffer is being played we can get the mix of the next one if we can*/
phdata->fill_index = 1 - phdata->fill_index;
}
@@ -270,16 +254,15 @@ TInt AudioThreadCB(TAny *aParams)
}
/***************************************************
-* MaoscOpenComplete -
-*
-* Opens the audiostream
-*
-* *******************************************************/
+ * MaoscOpenComplete -
+ *
+ * Opens the audiostream
+ *
+ * *******************************************************/
void CAudio::MaoscOpenComplete(TInt aError)
{
- if (aError == KErrNone)
- {
+ if (aError == KErrNone) {
/*setting the volume to max, users can change the volume later of their channels individually in code*/
iStream->SetVolume(iStream->MaxVolume());
iStreamStarted = ETrue;
@@ -296,11 +279,8 @@ void CAudio::MaoscOpenComplete(TInt aError)
/* Kickstart: device is guaranteed valid now*/
this->iState = EStatePlaying;
-
- }
- else
- {
+ } else {
SDL_Log("Error: Failed to open audio stream: %d", aError);
}
}
@@ -314,17 +294,12 @@ void CAudio::MaoscOpenComplete(TInt aError)
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
{
- if (aError == KErrNone)
- {
+ if (aError == KErrNone) {
iState = EStatePlaying;
- }
- else if (aError == KErrAbort)
- {
+ } else if (aError == KErrAbort) {
/* The stream has been stopped.*/
iState = EStateDone;
- }
- else
- {
+ } else {
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
}
}
@@ -338,13 +313,12 @@ void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
void CAudio::MaoscPlayComplete(TInt aError)
{
-
+
/* If we finish due to an underflow, we'll need to restart playback.
Normally KErrUnderlow is raised at stream end, but in our case the API
should never see the stream end -- we are continuously feeding it more
data! Many underflow errors mean that the latency target is too low.*/
- if (aError == KErrUnderflow)
- {
+ if (aError == KErrUnderflow) {
/* Restart the stream hardware */
iStream->Stop();
TInt ignoredError;
@@ -355,22 +329,17 @@ void CAudio::MaoscPlayComplete(TInt aError)
return;
} else if (aError != KErrNone) {
-
}
/* We shouldn't get here.*/
SDL_Log("%s: %d", SDL_FUNCTION, aError);
}
-
-
TBool AudioIsReady()
{
return gAudioRunning;
}
-
-
RThread audioThread;
void InitAudio(TInt *aLatency)
diff --git a/src/main/ngage/SDL_sysmain_main.cpp b/src/main/ngage/SDL_sysmain_main.cpp
index 7fe7a9bf31fad..4e81c6d123e01 100644
--- a/src/main/ngage/SDL_sysmain_main.cpp
+++ b/src/main/ngage/SDL_sysmain_main.cpp
@@ -87,9 +87,15 @@ GLDEF_C TInt E32Main()
TInt targetLatency = 225;
InitAudio(&targetLatency);
- // Wait until audio is ready.
- while (!AudioIsReady()) {
+ // Wait until audio is ready (timeout after ~5 seconds).
+ TInt audioWaitMs = 0;
+ while (!AudioIsReady() && audioWaitMs < 5000) {
User::After(100000); // 100ms.
+ audioWaitMs += 100;
+ }
+ if (!AudioIsReady()) {
+ SDL_Log("Error: Audio failed to initialise within timeout");
+ User::Leave(KErrTimedOut);
}
// Create and start the rendering backend.
@@ -104,8 +110,8 @@ GLDEF_C TInt E32Main()
// Start the active scheduler to handle events.
CActiveScheduler::Start();
- CleanupStack::PopAndDestroy(gRenderer);
CleanupStack::PopAndDestroy(mainApp);
+ CleanupStack::PopAndDestroy(gRenderer);
User::SwitchHeap(oldHeap);
@@ -154,6 +160,7 @@ static bool callbacks_initialized = false;
static void ShutdownApp(SDL_AppResult result)
{
+ callbacks_initialized = false;
DeinitAudio();
SDL_AppQuit(NULL, result);
SDL_Quit();