Restore support for the Nokia N-Gage (#12148)

This commit is contained in:
Michael Fitzmayer
2025-05-22 20:07:22 +02:00
committed by GitHub
parent 26f9940f82
commit 7ae64592c9
51 changed files with 4184 additions and 44 deletions

View File

@@ -77,6 +77,9 @@ static const AudioBootStrap *const bootstrap[] = {
#ifdef SDL_AUDIO_DRIVER_N3DS
&N3DSAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_NGAGE
&NGAGEAUDIO_bootstrap,
#endif
#ifdef SDL_AUDIO_DRIVER_EMSCRIPTEN
&EMSCRIPTENAUDIO_bootstrap,
#endif

View File

@@ -393,6 +393,7 @@ extern AudioBootStrap PS2AUDIO_bootstrap;
extern AudioBootStrap PSPAUDIO_bootstrap;
extern AudioBootStrap VITAAUD_bootstrap;
extern AudioBootStrap N3DSAUDIO_bootstrap;
extern AudioBootStrap NGAGEAUDIO_bootstrap;
extern AudioBootStrap EMSCRIPTENAUDIO_bootstrap;
extern AudioBootStrap QSAAUDIO_bootstrap;

View File

@@ -0,0 +1,103 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifdef SDL_AUDIO_DRIVER_NGAGE
#include "../SDL_sysaudio.h"
#include "SDL_ngageaudio.h"
static SDL_AudioDevice *devptr = NULL;
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr()
{
return devptr;
}
static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
{
SDL_PrivateAudioData *phdata = SDL_calloc(1, sizeof(SDL_PrivateAudioData));
if (!phdata) {
SDL_OutOfMemory();
return false;
}
device->hidden = phdata;
phdata->buffer = SDL_calloc(1, device->buffer_size);
if (!phdata->buffer) {
SDL_OutOfMemory();
SDL_free(phdata);
return false;
}
devptr = device;
// Since the phone can change the sample rate during a phone call,
// we set the sample rate to 8KHz to be safe. Even though it
// might be possible to adjust the sample rate dynamically, it's
// not supported by the current implementation.
device->spec.format = SDL_AUDIO_S16LE;
device->spec.channels = 1;
device->spec.freq = 8000;
SDL_UpdatedAudioDeviceFormat(device);
return true;
}
static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
{
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
if (!phdata) {
*buffer_size = 0;
return 0;
}
*buffer_size = device->buffer_size;
return phdata->buffer;
}
static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
{
if (device->hidden) {
SDL_free(device->hidden->buffer);
SDL_free(device->hidden);
}
return;
}
static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)
{
impl->OpenDevice = NGAGEAUDIO_OpenDevice;
impl->GetDeviceBuf = NGAGEAUDIO_GetDeviceBuf;
impl->CloseDevice = NGAGEAUDIO_CloseDevice;
impl->ProvidesOwnCallbackThread = true;
impl->OnlyHasDefaultPlaybackDevice = true;
return true;
}
AudioBootStrap NGAGEAUDIO_bootstrap = { "N-Gage", "N-Gage audio driver", NGAGEAUDIO_Init, false };
#endif // SDL_AUDIO_DRIVER_NGAGE

View File

@@ -0,0 +1,368 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include "SDL_ngageaudio.h"
#include "../SDL_sysaudio.h"
#include "SDL_internal.h"
#ifdef __cplusplus
}
#endif
#ifdef SDL_AUDIO_DRIVER_NGAGE
#include "SDL_ngageaudio.hpp"
CAudio::CAudio() : CActive(EPriorityStandard), iBufDes(NULL, 0) {}
CAudio *CAudio::NewL(TInt aLatency)
{
CAudio *self = new (ELeave) CAudio();
CleanupStack::PushL(self);
self->ConstructL(aLatency);
CleanupStack::Pop(self);
return self;
}
void CAudio::ConstructL(TInt aLatency)
{
CActiveScheduler::Add(this);
User::LeaveIfError(iTimer.CreateLocal());
iTimerCreated = ETrue;
iStream = CMdaAudioOutputStream::NewL(*this);
if (!iStream) {
SDL_Log("Error: Failed to create audio stream");
User::Leave(KErrNoMemory);
}
iLatency = aLatency;
iLatencySamples = aLatency * 8; // 8kHz.
// Determine minimum and maximum number of samples to write with one
// WriteL request.
iMinWrite = iLatencySamples / 8;
iMaxWrite = iLatencySamples / 2;
// Set defaults.
iState = EStateNone;
iTimerCreated = EFalse;
iTimerActive = EFalse;
}
CAudio::~CAudio()
{
if (iStream) {
iStream->Stop();
while (iState != EStateDone) {
User::After(100000); // 100ms.
}
delete iStream;
}
}
void CAudio::Start()
{
if (iStream) {
// Set to 8kHz mono audio.
iStreamSettings.iChannels = TMdaAudioDataSettings::EChannelsMono;
iStreamSettings.iSampleRate = TMdaAudioDataSettings::ESampleRate8000Hz;
iStream->Open(&iStreamSettings);
iState = EStateOpening;
} else {
SDL_Log("Error: Failed to open audio stream");
}
}
// Feeds more processed data to the audio stream.
void CAudio::Feed()
{
// If a WriteL is already in progress, or we aren't even playing;
// do nothing!
if ((iState != EStateWriting) && (iState != EStatePlaying)) {
return;
}
// Figure out the number of samples that really have been played
// through the output.
TTimeIntervalMicroSeconds pos = iStream->Position();
TInt played = 8 * (pos.Int64() / TInt64(1000)).GetTInt(); // 8kHz.
played += iBaseSamplesPlayed;
// Determine the difference between the number of samples written to
// CMdaAudioOutputStream and the number of samples it has played.
// The difference is the amount of data in the buffers.
if (played < 0) {
played = 0;
}
TInt buffered = iSamplesWritten - played;
if (buffered < 0) {
buffered = 0;
}
if (iState == EStateWriting) {
return;
}
// The trick for low latency: Do not let the buffers fill up beyond the
// latency desired! We write as many samples as the difference between
// the latency target (in samples) and the amount of data buffered.
TInt samplesToWrite = iLatencySamples - buffered;
// Do not write very small blocks. This should improve efficiency, since
// writes to the streaming API are likely to be expensive.
if (samplesToWrite < iMinWrite) {
// Not enough data to write, set up a timer to fire after a while.
// Try againwhen it expired.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
return;
}
// Do not write more than the set number of samples at once.
int numSamples = samplesToWrite;
if (numSamples > iMaxWrite) {
numSamples = iMaxWrite;
}
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
iBufDes.Set(phdata->buffer, 2 * numSamples, 2 * numSamples);
iStream->WriteL(iBufDes);
iState = EStateWriting;
// Keep track of the number of samples written (for latency calculations).
iSamplesWritten += numSamples;
} else {
// Output device not ready yet. Let's go for another round.
if (iTimerActive) {
return;
}
iTimerActive = ETrue;
SetActive();
iTimer.After(iStatus, (1000 * iLatency) / 8);
}
}
void CAudio::RunL()
{
iTimerActive = EFalse;
Feed();
}
void CAudio::DoCancel()
{
iTimerActive = EFalse;
iTimer.Cancel();
}
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) {
iProcess.SetPriority(EPriorityLess);
iProcess.Resume();
} else {
SDL_Log("Error: Failed to create audio processing thread: %d", err);
}
}
void CAudio::StopThread()
{
if (iStreamStarted) {
iProcess.Kill(KErrNone);
iProcess.Close();
iStreamStarted = EFalse;
}
}
TInt CAudio::ProcessThreadCB(TAny *aPtr)
{
CAudio *self = static_cast<CAudio *>(aPtr);
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
while (self->iStreamStarted) {
if (device) {
SDL_PlaybackAudioThreadIterate(device);
} else {
device = NGAGE_GetAudioDeviceAddr();
}
User::After(100000); // 100ms.
}
return KErrNone;
}
void CAudio::MaoscOpenComplete(TInt aError)
{
if (aError == KErrNone) {
iStream->SetVolume(1);
iStreamStarted = ETrue;
StartThread();
} else {
SDL_Log("Error: Failed to open audio stream: %d", aError);
}
}
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
{
if (aError == KErrNone) {
iState = EStatePlaying;
Feed();
} else if (aError == KErrAbort) {
// The stream has been stopped.
iState = EStateDone;
} else {
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
}
}
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) {
// The number of samples played gets resetted to zero when we restart
// playback after underflow.
iBaseSamplesPlayed = iSamplesWritten;
iStream->Stop();
Cancel();
iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono);
iState = EStatePlaying;
Feed();
return;
} else if (aError != KErrNone) {
// Handle error.
}
// We shouldn't get here.
SDL_Log("%s: %d", __FUNCTION__, aError);
}
static TBool gAudioRunning;
TBool AudioIsReady()
{
return gAudioRunning;
}
TInt AudioThreadCB(TAny *aParams)
{
CTrapCleanup *cleanup = CTrapCleanup::New();
if (!cleanup) {
return KErrNoMemory;
}
CActiveScheduler *scheduler = new CActiveScheduler();
if (!scheduler) {
delete cleanup;
return KErrNoMemory;
}
CActiveScheduler::Install(scheduler);
TRAPD(err,
{
TInt latency = *(TInt *)aParams;
CAudio *audio = CAudio::NewL(latency);
CleanupStack::PushL(audio);
gAudioRunning = ETrue;
audio->Start();
TBool once = EFalse;
while (gAudioRunning) {
// Allow active scheduler to process any events.
TInt error;
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
if (!once) {
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
if (device) {
// Stream ready; start feeding audio data.
// After feeding it once, the callbacks will take over.
audio->iState = CAudio::EStatePlaying;
audio->Feed();
once = ETrue;
}
}
User::After(100000); // 100ms.
}
CleanupStack::PopAndDestroy(audio);
});
delete scheduler;
delete cleanup;
return err;
}
RThread audioThread;
void InitAudio(TInt *aLatency)
{
_LIT(KAudioThreadName, "AudioThread");
TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
if (err != KErrNone) {
User::Leave(err);
}
audioThread.Resume();
}
void DeinitAudio()
{
gAudioRunning = EFalse;
TRequestStatus status;
audioThread.Logon(status);
User::WaitForRequest(status);
audioThread.Close();
}
#endif // SDL_AUDIO_DRIVER_NGAGE

View File

@@ -0,0 +1,44 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
#ifndef SDL_ngageaudio_h
#define SDL_ngageaudio_h
typedef struct SDL_PrivateAudioData
{
Uint8 *buffer;
} SDL_PrivateAudioData;
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
SDL_AudioDevice *NGAGE_GetAudioDeviceAddr();
#ifdef __cplusplus
}
#endif
#endif // SDL_ngageaudio_h

View File

@@ -0,0 +1,98 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#ifndef SDL_ngageaudio_hpp
#define SDL_ngageaudio_hpp
#include <e32base.h>
#include <e32std.h>
#include <mda/common/audio.h>
#include <mdaaudiooutputstream.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "../SDL_sysaudio.h"
#include "SDL_ngageaudio.h"
#ifdef __cplusplus
}
#endif
TBool AudioIsReady();
void InitAudio(TInt *aLatency);
void DeinitAudio();
class CAudio : public CActive, public MMdaAudioOutputStreamCallback
{
public:
static CAudio *NewL(TInt aLatency);
~CAudio();
void ConstructL(TInt aLatency);
void Start();
void Feed();
void RunL();
void DoCancel();
static TInt ProcessThreadCB(TAny * /*aPtr*/);
// From MMdaAudioOutputStreamCallback
void MaoscOpenComplete(TInt aError);
void MaoscBufferCopied(TInt aError, const TDesC8 &aBuffer);
void MaoscPlayComplete(TInt aError);
enum
{
EStateNone = 0,
EStateOpening,
EStatePlaying,
EStateWriting,
EStateDone
} iState;
private:
CAudio();
void StartThread();
void StopThread();
CMdaAudioOutputStream *iStream;
TMdaAudioDataSettings iStreamSettings;
TBool iStreamStarted;
TPtr8 iBufDes; // Descriptor for the buffer.
TInt iLatency; // Latency target in ms
TInt iLatencySamples; // Latency target in samples.
TInt iMinWrite; // Min number of samples to write per turn.
TInt iMaxWrite; // Max number of samples to write per turn.
TInt iBaseSamplesPlayed; // amples played before last restart.
TInt iSamplesWritten; // Number of samples written so far.
RTimer iTimer;
TBool iTimerCreated;
TBool iTimerActive;
RThread iProcess;
};
#endif // SDL_ngageaudio_hpp