mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-05-08 14:52:00 +00:00
Update for N-Gage - Audio is now double buffered (#15516)
[N-Gage] Audio is now double buffered to avoid stuttering and glitches. Some audio platform specific variables were exposed through SDL_Hints. Same method was used to display FPS. N-gage functions to obtain the current buffer and screen pitch were added to the render. Adds hints: SDL_AUDIO_NGAGE_LATENCY SDL_AUDIO_NGAGE_SCHEDULER_TICK SDL_AUDIO_NGAGE_PROCESS_TICK SDL_AUDIO_NGAGE_PROCESS_PRIORITY SDL_RENDER_SHOW_FPS Adds functions to get current buffer address and pitch: void *NGAGE_GetBackbufferAddress(void); int NGAGE_GetBackbufferPitch(void); --------- Co-authored-by: Michael Fitzmayer <mail@michael-fitzmayer.de> Co-authored-by: Eddy Jansson <eloj@users.noreply.github.com>
This commit is contained in:
@@ -42,18 +42,20 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
}
|
||||
device->hidden = phdata;
|
||||
|
||||
phdata->buffer = SDL_calloc(1, device->buffer_size);
|
||||
if (!phdata->buffer) {
|
||||
SDL_OutOfMemory();
|
||||
phdata->buffer[0] = SDL_calloc(1, device->buffer_size);
|
||||
phdata->buffer[1] = SDL_calloc(1, device->buffer_size);
|
||||
if (!phdata->buffer[0] || !phdata->buffer[1])
|
||||
{
|
||||
SDL_Log("Error: Failed to allocate audio buffers");
|
||||
SDL_free(phdata->buffer[0]);
|
||||
SDL_free(phdata->buffer[1]);
|
||||
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.
|
||||
phdata->fill_index = 0;
|
||||
|
||||
devptr = device;
|
||||
|
||||
device->spec.format = SDL_AUDIO_S16LE;
|
||||
device->spec.channels = 1;
|
||||
@@ -64,6 +66,13 @@ static bool NGAGEAUDIO_OpenDevice(SDL_AudioDevice *device)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*********************************************
|
||||
|
||||
NGAGEAUDIO_GetDeviceBuf -
|
||||
|
||||
Return the buffer that is currently being filled by SDL
|
||||
|
||||
**********************************************/
|
||||
static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
{
|
||||
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
|
||||
@@ -71,19 +80,24 @@ static Uint8 *NGAGEAUDIO_GetDeviceBuf(SDL_AudioDevice *device, int *buffer_size)
|
||||
*buffer_size = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
*buffer_size = device->buffer_size;
|
||||
return phdata->buffer;
|
||||
|
||||
return phdata->buffer[phdata->fill_index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void NGAGEAUDIO_CloseDevice(SDL_AudioDevice *device)
|
||||
{
|
||||
if (device->hidden) {
|
||||
SDL_free(device->hidden->buffer);
|
||||
SDL_free(device->hidden);
|
||||
}
|
||||
SDL_PrivateAudioData *phdata = (SDL_PrivateAudioData *)device->hidden;
|
||||
|
||||
return;
|
||||
SDL_free(phdata->buffer[0]);
|
||||
SDL_free(phdata->buffer[1]);
|
||||
SDL_free(phdata);
|
||||
device->hidden = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool NGAGEAUDIO_Init(SDL_AudioDriverImpl *impl)
|
||||
|
||||
@@ -98,89 +98,12 @@ void CAudio::Start()
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
@@ -194,9 +117,21 @@ 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);
|
||||
if (err == KErrNone)
|
||||
{
|
||||
TThreadPriority prio = EPriorityLess;
|
||||
|
||||
const char *prioHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY);
|
||||
if (prioHint) {
|
||||
// Symbian priorities: 10 (MuchLess), 20 (Less), 30 (Normal), 40 (More)
|
||||
prio = (TThreadPriority)SDL_atoi(prioHint);
|
||||
RThread().SetPriority(prio);
|
||||
}
|
||||
|
||||
|
||||
iProcess.SetPriority(prio);
|
||||
iProcess.Resume();
|
||||
} else {
|
||||
SDL_Log("Error: Failed to create audio processing thread: %d", err);
|
||||
@@ -212,138 +147,240 @@ 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
|
||||
****************************************************/
|
||||
|
||||
TInt CAudio::ProcessThreadCB(TAny *aPtr)
|
||||
{
|
||||
CTrapCleanup *cleanup = CTrapCleanup::New();
|
||||
if (!cleanup)
|
||||
return KErrNoMemory;
|
||||
|
||||
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.
|
||||
|
||||
TInt processTick = 40000; // Default 40ms
|
||||
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
|
||||
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]*/
|
||||
SDL_PlaybackAudioThreadIterate(device);
|
||||
|
||||
/* Signal AudioThreadCB to write it*/
|
||||
self->iBufferReady = ETrue;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*if we are not ready to obtain the mix data we sleep a bit this thread*/
|
||||
User::After(processTick);
|
||||
}
|
||||
}
|
||||
|
||||
delete cleanup;
|
||||
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 reset 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", SDL_FUNCTION, aError);
|
||||
}
|
||||
|
||||
static TBool gAudioRunning;
|
||||
|
||||
TBool AudioIsReady()
|
||||
{
|
||||
return gAudioRunning;
|
||||
}
|
||||
/***************************************************
|
||||
* 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();
|
||||
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);
|
||||
|
||||
TRAPD(err,
|
||||
{
|
||||
TInt latency = *(TInt *)aParams;
|
||||
CAudio *audio = CAudio::NewL(latency);
|
||||
CleanupStack::PushL(audio);
|
||||
audio->iBufferReady = EFalse;
|
||||
|
||||
gAudioRunning = ETrue;
|
||||
audio->Start();
|
||||
TBool once = EFalse;
|
||||
gAudioRunning = ETrue;
|
||||
audio->Start();
|
||||
|
||||
|
||||
while (gAudioRunning) {
|
||||
// Allow active scheduler to process any events.
|
||||
TInt error;
|
||||
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
|
||||
TInt processTick = 5000; // Default 5ms
|
||||
const char *tickHint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_PROCESS_TICK);
|
||||
if (tickHint) {
|
||||
processTick = SDL_atoi(tickHint) * 1000;
|
||||
}
|
||||
|
||||
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.
|
||||
}
|
||||
while (gAudioRunning)
|
||||
{
|
||||
TInt error;
|
||||
CActiveScheduler::RunIfReady(error, CActive::EPriorityIdle);
|
||||
|
||||
/*there is some mix data sound ready*/
|
||||
if (audio->iBufferReady)
|
||||
{
|
||||
audio->iBufferReady = EFalse;
|
||||
|
||||
CleanupStack::PopAndDestroy(audio);
|
||||
});
|
||||
SDL_AudioDevice *device = NGAGE_GetAudioDeviceAddr();
|
||||
|
||||
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)
|
||||
{
|
||||
/*asks ProcessThreadCB to bring another mix chunk*/
|
||||
audio->iState = EStatePlaying;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*sleep a bit this thread not to hog the CPU*/
|
||||
User::After(processTick);
|
||||
}
|
||||
|
||||
CleanupStack::PopAndDestroy(audio);
|
||||
});
|
||||
|
||||
delete scheduler;
|
||||
delete cleanup;
|
||||
return err;
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscOpenComplete -
|
||||
*
|
||||
* Opens the audiostream
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
void CAudio::MaoscOpenComplete(TInt aError)
|
||||
{
|
||||
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;
|
||||
|
||||
/* Wait until SDL has set devptr and hidden data*/
|
||||
SDL_AudioDevice *device = NULL;
|
||||
while (!device || !device->hidden) {
|
||||
User::After(10000); // 10ms poll
|
||||
device = NGAGE_GetAudioDeviceAddr();
|
||||
}
|
||||
|
||||
/* Now start the ProcessThreadCB thread*/
|
||||
StartThread();
|
||||
|
||||
/* Kickstart: device is guaranteed valid now*/
|
||||
this->iState = EStatePlaying;
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_Log("Error: Failed to open audio stream: %d", aError);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscOpenComplete -
|
||||
*
|
||||
* This signals the mixed data has been finally copied to the designated audio buffer
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
void CAudio::MaoscBufferCopied(TInt aError, const TDesC8 & /*aBuffer*/)
|
||||
{
|
||||
if (aError == KErrNone)
|
||||
{
|
||||
iState = EStatePlaying;
|
||||
}
|
||||
else if (aError == KErrAbort)
|
||||
{
|
||||
/* The stream has been stopped.*/
|
||||
iState = EStateDone;
|
||||
}
|
||||
else
|
||||
{
|
||||
SDL_Log("Error: Failed to copy audio buffer: %d", aError);
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************
|
||||
* MaoscPlayComplete -
|
||||
*
|
||||
* The result after playing the mixed chunk
|
||||
*
|
||||
* *******************************************************/
|
||||
|
||||
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)
|
||||
{
|
||||
/* Restart the stream hardware */
|
||||
iStream->Stop();
|
||||
TInt ignoredError;
|
||||
TRAP(ignoredError, iStream->SetAudioPropertiesL(TMdaAudioDataSettings::ESampleRate8000Hz, TMdaAudioDataSettings::EChannelsMono));
|
||||
|
||||
/* This wakes up ProcessThreadCB so it can call SDL_PlaybackAudioThreadIterate*/
|
||||
iState = EStatePlaying;
|
||||
|
||||
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)
|
||||
{
|
||||
// Check if the user has provided a custom latency value via a hint
|
||||
const char *hint = SDL_GetHint(SDL_HINT_AUDIO_NGAGE_LATENCY);
|
||||
if (hint) {
|
||||
*aLatency = (TInt)SDL_atoi(hint);
|
||||
}
|
||||
|
||||
_LIT(KAudioThreadName, "AudioThread");
|
||||
|
||||
TInt err = audioThread.Create(KAudioThreadName, AudioThreadCB, KDefaultStackSize, 0, aLatency);
|
||||
|
||||
@@ -23,9 +23,27 @@
|
||||
#ifndef SDL_ngageaudio_h
|
||||
#define SDL_ngageaudio_h
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_LATENCY
|
||||
#define SDL_HINT_AUDIO_NGAGE_LATENCY "SDL_AUDIO_NGAGE_LATENCY"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK
|
||||
#define SDL_HINT_AUDIO_NGAGE_SCHEDULER_TICK "SDL_AUDIO_NGAGE_SCHEDULER_TICK"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_TICK
|
||||
#define SDL_HINT_AUDIO_NGAGE_PROCESS_TICK "SDL_AUDIO_NGAGE_PROCESS_TICK"
|
||||
#endif
|
||||
|
||||
#ifndef SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY
|
||||
#define SDL_HINT_AUDIO_NGAGE_PROCESS_PRIORITY "SDL_AUDIO_NGAGE_PROCESS_PRIORITY"
|
||||
#endif
|
||||
typedef struct SDL_PrivateAudioData
|
||||
{
|
||||
Uint8 *buffer;
|
||||
Uint8 *buffer[2];
|
||||
int fill_index; /* Which buffer SDL is currently filling */
|
||||
int play_index; /* Which buffer the hardware is currently using*/
|
||||
int buffer_size;
|
||||
|
||||
} SDL_PrivateAudioData;
|
||||
|
||||
|
||||
@@ -42,6 +42,16 @@ TBool AudioIsReady();
|
||||
void InitAudio(TInt *aLatency);
|
||||
void DeinitAudio();
|
||||
|
||||
enum TAudioState
|
||||
{
|
||||
EStateNone = 0,
|
||||
EStateOpening,
|
||||
EStatePlaying,
|
||||
EStateWriting,
|
||||
EStateDone
|
||||
};
|
||||
|
||||
|
||||
class CAudio : public CActive, public MMdaAudioOutputStreamCallback
|
||||
{
|
||||
public:
|
||||
@@ -50,49 +60,42 @@ class CAudio : public CActive, public MMdaAudioOutputStreamCallback
|
||||
|
||||
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;
|
||||
|
||||
TAudioState iState;
|
||||
CMdaAudioOutputStream *iStream; /*CMdaAudioOutputStream handler*/
|
||||
TPtr8 iBufDes; /* Descriptor for the buffer.*/
|
||||
TBool iStreamStarted; /* have we initialized the audio stream?*/
|
||||
RThread iProcess; /* thread handler */
|
||||
TBool iBufferReady; /* Signal AudioThreadCB the buffer is ready*/
|
||||
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
|
||||
@@ -149,6 +149,23 @@ void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target)
|
||||
}
|
||||
}
|
||||
|
||||
static void SDLCALL NGAGE_ShowFPSChanged(void *userdata, const char *name, const char *oldValue, const char *newValue)
|
||||
{
|
||||
CRenderer *renderer = (CRenderer *)userdata;
|
||||
renderer->SetShowFPS(SDL_GetStringBoolean(newValue, false));
|
||||
}
|
||||
|
||||
|
||||
void *NGAGE_GetBackbufferAddress(void)
|
||||
{
|
||||
return gRenderer->GetCurrentBitmap()->DataAddress();
|
||||
}
|
||||
|
||||
int NGAGE_GetBackbufferPitch(void)
|
||||
{
|
||||
return CFbsBitmap::ScanLineLength(NGAGE_SCREEN_WIDTH, EColor4K) / 2;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -166,6 +183,8 @@ CRenderer::CRenderer() : iRenderer(0), iDirectScreen(0), iScreenGc(0), iWsSessio
|
||||
|
||||
CRenderer::~CRenderer()
|
||||
{
|
||||
SDL_RemoveHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this);
|
||||
|
||||
delete iRenderer;
|
||||
iRenderer = 0;
|
||||
|
||||
@@ -266,6 +285,8 @@ void CRenderer::ConstructL()
|
||||
}
|
||||
iDirectScreen->ScreenDevice()->SetAutoUpdate(ETrue);
|
||||
}
|
||||
|
||||
SDL_AddHintCallback(SDL_HINT_RENDER_NGAGE_SHOW_FPS, NGAGE_ShowFPSChanged, this);
|
||||
}
|
||||
|
||||
void CRenderer::Restart(RDirectScreenAccess::TTerminationReasons aReason)
|
||||
@@ -336,6 +357,8 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
NGAGE_TextureData *phdata = (NGAGE_TextureData *)texture->internal;
|
||||
if (!phdata || !phdata->bitmap) {
|
||||
return false;
|
||||
@@ -346,7 +369,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
|
||||
int sw = srcrect->w;
|
||||
int sh = srcrect->h;
|
||||
|
||||
|
||||
// Fast path: render target texture with no color mod.
|
||||
// BitBlt directly from its bitmap — DataAddress() is unreliable
|
||||
// for bitmaps that have been drawn into via a CFbsBitGc.
|
||||
@@ -359,7 +382,8 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
SDL_GetTextureBlendMode(texture, &blend);
|
||||
bool no_color_key = (blend != SDL_BLENDMODE_BLEND);
|
||||
|
||||
if (phdata->gc && no_color_mod && no_scale && no_color_key) {
|
||||
if (phdata->gc && no_color_mod && no_scale && no_color_key)
|
||||
{
|
||||
CFbsBitGc *gc = GetCurrentGc();
|
||||
if (gc) {
|
||||
TRect aSource(TPoint(srcrect->x, srcrect->y), TSize(sw, sh));
|
||||
@@ -369,6 +393,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Fast path: color-key with no color mod and no scale.
|
||||
// Blit directly from the source bitmap into the destination, skipping transparent pixels.
|
||||
if (no_color_mod && no_scale && !no_color_key && phdata->has_color_key) {
|
||||
@@ -414,6 +439,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
TSize scratch_size = iScratchBitmap->SizeInPixels();
|
||||
if (scratch_size.iWidth < sw || scratch_size.iHeight < sh) {
|
||||
iScratchBitmap->Reset();
|
||||
@@ -438,6 +464,7 @@ bool CRenderer::Copy(SDL_Renderer *renderer, SDL_Texture *texture, const SDL_Rec
|
||||
void *source = iPixelBufferA;
|
||||
void *dest = iPixelBufferB;
|
||||
|
||||
|
||||
if (!no_color_mod) {
|
||||
ApplyColorMod(dest, source, src_pitch, sw, sh, texture->color);
|
||||
void *tmp = source;
|
||||
@@ -934,13 +961,15 @@ void CRenderer::HandleEvent(const TWsEvent &aWsEvent)
|
||||
timestamp = SDL_GetPerformanceCounter();
|
||||
SDL_SendKeyboardKey(timestamp, 1, aWsEvent.Key()->iCode, ConvertScancode(aWsEvent.Key()->iScanCode), true);
|
||||
|
||||
/*
|
||||
commented out so it works with hints
|
||||
if (aWsEvent.Key()->iScanCode == EStdKeyHash) {
|
||||
if (iShowFPS) {
|
||||
iShowFPS = EFalse;
|
||||
} else {
|
||||
iShowFPS = ETrue;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
break;
|
||||
case EEventKeyUp: /* Key events */
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
#define NGAGE_SCREEN_WIDTH 176
|
||||
#define NGAGE_SCREEN_HEIGHT 208
|
||||
|
||||
#ifndef SDL_HINT_RENDER_NGAGE_SHOW_FPS
|
||||
#define SDL_HINT_RENDER_NGAGE_SHOW_FPS "SDL_RENDER_NGAGE_SHOW_FPS"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -108,6 +112,8 @@ void NGAGE_SetDrawColor(const Uint32 color);
|
||||
void NGAGE_PumpEventsInternal(void);
|
||||
void NGAGE_SuspendScreenSaverInternal(bool suspend);
|
||||
void NGAGE_SetRenderTargetInternal(NGAGE_TextureData *target);
|
||||
void *NGAGE_GetBackbufferAddress(void);
|
||||
int NGAGE_GetBackbufferPitch(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ class CRenderer : public MDirectScreenAccess
|
||||
void SetClipRect(TInt aX, TInt aY, TInt aWidth, TInt aHeight);
|
||||
void UpdateFPS();
|
||||
void SuspendScreenSaver(TBool aSuspend);
|
||||
void SetShowFPS(TBool aShow) { iShowFPS = aShow; }
|
||||
|
||||
// Render target management.
|
||||
void SetRenderTarget(NGAGE_TextureData *aTarget);
|
||||
|
||||
Reference in New Issue
Block a user