mirror of
https://github.com/mauiaaron/apple2.git
synced 2024-06-26 00:29:27 +00:00
Use single buffer queue callback for OpenSLES/Android
- OpenSLES Callback now performs simple mix of 2 sources (speaker and mockingboard) - Avoids multiple callbacks which was a performance penalty especially for low-end droids
This commit is contained in:
parent
2381868c63
commit
5f4bf6b1a7
|
@ -12,4 +12,6 @@
|
|||
extern unsigned long android_deviceSampleRateHz;
|
||||
extern unsigned long android_monoBufferSubmitSizeSamples;
|
||||
extern unsigned long android_stereoBufferSubmitSizeSamples;
|
||||
extern bool android_armNeonEnabled;
|
||||
extern bool android_x86SSSE3Enabled;
|
||||
|
||||
|
|
|
@ -30,3 +30,4 @@ include $(BUILD_SHARED_LIBRARY)
|
|||
# --OR-- Build an executable so native can drive this show
|
||||
#include $(BUILD_EXECUTABLE)
|
||||
|
||||
$(call import-module, android/cpufeatures)
|
||||
|
|
|
@ -12,11 +12,14 @@
|
|||
#include "common.h"
|
||||
#include "androidkeys.h"
|
||||
|
||||
#include <cpu-features.h>
|
||||
#include <jni.h>
|
||||
|
||||
unsigned long android_deviceSampleRateHz = 0;
|
||||
unsigned long android_monoBufferSubmitSizeSamples = 0;
|
||||
unsigned long android_stereoBufferSubmitSizeSamples = 0;
|
||||
bool android_armNeonEnabled = false;
|
||||
bool android_x86SSSE3Enabled = false;
|
||||
|
||||
enum {
|
||||
ANDROID_ACTION_DOWN = 0x0,
|
||||
|
@ -94,6 +97,35 @@ void Java_org_deadc0de_apple2ix_Apple2Activity_nativeOnCreate(JNIEnv *env, jobje
|
|||
return;
|
||||
}
|
||||
|
||||
AndroidCpuFamily family = android_getCpuFamily();
|
||||
uint64_t features = android_getCpuFeatures();
|
||||
if (family == ANDROID_CPU_FAMILY_X86) {
|
||||
if (features & ANDROID_CPU_X86_FEATURE_SSSE3) {
|
||||
LOG("nANDROID_CPU_X86_FEATURE_SSSE3");
|
||||
android_x86SSSE3Enabled = true;
|
||||
}
|
||||
if (features & ANDROID_CPU_X86_FEATURE_MOVBE) {
|
||||
LOG("ANDROID_CPU_X86_FEATURE_MOVBE");
|
||||
}
|
||||
if (features & ANDROID_CPU_X86_FEATURE_POPCNT) {
|
||||
LOG("ANDROID_CPU_X86_FEATURE_POPCNT");
|
||||
}
|
||||
} else if (family == ANDROID_CPU_FAMILY_ARM) {
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_ARMv7) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_ARMv7");
|
||||
}
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_VFPv3) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_VFPv3");
|
||||
}
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_NEON) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_NEON");
|
||||
android_armNeonEnabled = true;
|
||||
}
|
||||
if (features & ANDROID_CPU_ARM_FEATURE_LDREX_STREX) {
|
||||
LOG("ANDROID_CPU_ARM_FEATURE_LDREX_STREX");
|
||||
}
|
||||
}
|
||||
|
||||
data_dir = strdup(dataDir);
|
||||
(*env)->ReleaseStringUTFChars(env, j_dataDir, dataDir);
|
||||
LOG("nativeOnCreate(%s)...", data_dir);
|
||||
|
|
|
@ -37,3 +37,5 @@ APPLE2_MAIN_SRC = \
|
|||
|
||||
APPLE2_BASE_CFLAGS := -DAPPLE2IX=1 -DINTERFACE_TOUCH=1 -DMOBILE_DEVICE=1 -DVIDEO_OPENGL=1 -DDEBUGGER=1 -DAUDIO_ENABLED=1 -std=gnu11 -I$(APPLE2_SRC_PATH)
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES += cpufeatures
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ static bool g_bVotraxPhoneme = false;
|
|||
|
||||
#ifdef APPLE2IX
|
||||
static unsigned long SAMPLE_RATE = 0;
|
||||
static float samplesScale = 1.f;
|
||||
#else
|
||||
static const unsigned long SAMPLE_RATE = 44100; // Use a base freq so that DirectX (or sound h/w) doesn't have to up/down-sample
|
||||
#endif
|
||||
|
@ -896,6 +897,12 @@ static void MB_Update()
|
|||
}
|
||||
|
||||
static int nNumSamplesError = 0;
|
||||
if (!MockingboardVoice)
|
||||
{
|
||||
nNumSamplesError = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!MockingboardVoice->bActive || !g_bMB_Active)
|
||||
{
|
||||
nNumSamplesError = 0;
|
||||
|
@ -1086,8 +1093,13 @@ static void MB_Update()
|
|||
else if(nDataR > nWaveDataMax)
|
||||
nDataR = nWaveDataMax;
|
||||
|
||||
#ifdef APPLE2IX
|
||||
g_nMixBuffer[i*g_nMB_NumChannels+0] = (short)nDataL * samplesScale;
|
||||
g_nMixBuffer[i*g_nMB_NumChannels+1] = (short)nDataR * samplesScale;
|
||||
#else
|
||||
g_nMixBuffer[i*g_nMB_NumChannels+0] = (short)nDataL; // L
|
||||
g_nMixBuffer[i*g_nMB_NumChannels+1] = (short)nDataR; // R
|
||||
#endif
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -1106,7 +1118,11 @@ static void MB_Update()
|
|||
if (MockingboardVoice->Lock(MockingboardVoice, requestedBufSize, &pDSLockedBuffer0, &dwDSLockedBufferSize0)) {
|
||||
return;
|
||||
}
|
||||
assert(dwDSLockedBufferSize0 % 2 == 0);
|
||||
|
||||
{
|
||||
unsigned long modTwo = (dwDSLockedBufferSize0 % 2);
|
||||
assert(modTwo == 0);
|
||||
}
|
||||
memcpy(pDSLockedBuffer0, &g_nMixBuffer[bufIdx/sizeof(short)], dwDSLockedBufferSize0);
|
||||
MockingboardVoice->Unlock(MockingboardVoice, dwDSLockedBufferSize0);
|
||||
bufIdx += dwDSLockedBufferSize0;
|
||||
|
@ -1253,6 +1269,7 @@ static unsigned long SSI263Thread(void *lpParameter)
|
|||
static void SSI263_Play(unsigned int nPhoneme)
|
||||
{
|
||||
return; // SSI263 voices are currently deadc0de
|
||||
#if 0
|
||||
#if 1
|
||||
int hr;
|
||||
|
||||
|
@ -1348,6 +1365,7 @@ static void SSI263_Play(unsigned int nPhoneme)
|
|||
|
||||
SSI263Voice.bActive = true;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -1375,7 +1393,7 @@ static bool MB_DSInit()
|
|||
if(!audio_isAvailable)
|
||||
return false;
|
||||
|
||||
int hr = audio_createSoundBuffer(&MockingboardVoice, 2);
|
||||
int hr = audio_createSoundBuffer(&MockingboardVoice);
|
||||
LOG("MB_DSInit: DSGetSoundBuffer(), hr=0x%08X\n", (unsigned int)hr);
|
||||
if(FAILED(hr))
|
||||
{
|
||||
|
@ -1421,6 +1439,7 @@ static bool MB_DSInit()
|
|||
#endif
|
||||
|
||||
return true;
|
||||
#if 0
|
||||
|
||||
#ifdef APPLE2IX
|
||||
int err = 0;
|
||||
|
@ -1576,6 +1595,7 @@ static bool MB_DSInit()
|
|||
|
||||
return true;
|
||||
|
||||
#endif
|
||||
#endif // NO_DIRECT_X
|
||||
}
|
||||
|
||||
|
@ -2210,7 +2230,11 @@ bool MB_IsActive()
|
|||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#if defined(APPLE2IX)
|
||||
void MB_SetVolumeZeroToTen(unsigned long goesToTen) {
|
||||
samplesScale = goesToTen/10.f;
|
||||
}
|
||||
#else
|
||||
static long NewVolume(unsigned long dwVolume, unsigned long dwVolumeMax)
|
||||
{
|
||||
float fVol = (float) dwVolume / (float) dwVolumeMax; // 0.0=Max, 1.0=Min
|
||||
|
@ -2220,12 +2244,6 @@ static long NewVolume(unsigned long dwVolume, unsigned long dwVolumeMax)
|
|||
|
||||
void MB_SetVolume(unsigned long dwVolume, unsigned long dwVolumeMax)
|
||||
{
|
||||
#ifdef APPLE2IX
|
||||
#warning TODO FIXME ... why is OpenAL on my Linux box so damn loud?!
|
||||
dwVolume >>= 2;
|
||||
dwVolumeMax >>= 2;
|
||||
#endif
|
||||
|
||||
MockingboardVoice->nVolume = NewVolume(dwVolume, dwVolumeMax);
|
||||
|
||||
#if !defined(APPLE2IX)
|
||||
|
@ -2233,6 +2251,7 @@ void MB_SetVolume(unsigned long dwVolume, unsigned long dwVolumeMax)
|
|||
MockingboardVoice->SetVolume(MockingboardVoice->nVolume);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
//===========================================================================
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ void MB_SetSoundcardType(SS_CARDTYPE NewSoundcardType);
|
|||
double MB_GetFramePeriod();
|
||||
bool MB_IsActive();
|
||||
unsigned long MB_GetVolume();
|
||||
void MB_SetVolumeZeroToTen(unsigned long goesToTen);
|
||||
void MB_SetVolume(unsigned long dwVolume, unsigned long dwVolumeMax);
|
||||
unsigned long MB_GetSnapshot(SS_CARD_MOCKINGBOARD* pSS, unsigned long dwSlot);
|
||||
unsigned long MB_SetSnapshot(SS_CARD_MOCKINGBOARD* pSS, unsigned long dwSlot);
|
||||
|
|
|
@ -20,8 +20,6 @@
|
|||
# error FIXME TODO this currently uses Android BufferQueue extensions...
|
||||
#endif
|
||||
|
||||
#include "uthash.h"
|
||||
|
||||
#define DEBUG_OPENSL 0
|
||||
#if DEBUG_OPENSL
|
||||
# define OPENSL_LOG(...) LOG(__VA_ARGS__)
|
||||
|
@ -29,49 +27,42 @@
|
|||
# define OPENSL_LOG(...)
|
||||
#endif
|
||||
|
||||
#define OPENSL_NUM_BUFFERS 4
|
||||
#define NUM_CHANNELS 2
|
||||
|
||||
typedef struct SLVoice {
|
||||
unsigned long voiceId;
|
||||
|
||||
SLObjectItf bqPlayerObject;
|
||||
SLPlayItf bqPlayerPlay;
|
||||
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||||
SLMuteSoloItf bqPlayerMuteSolo;
|
||||
SLVolumeItf bqPlayerVolume;
|
||||
void *ctx; // EngineContext_s
|
||||
|
||||
// working data buffer
|
||||
uint8_t *ringBuffer; // ringBuffer of total size : bufferSize+submitSize
|
||||
uint8_t *submitBuf; // submitBuffer
|
||||
unsigned long bufferSize; // ringBuffer non-overflow size
|
||||
unsigned long submitSize; // buffer size OpenSLES expects/wants
|
||||
unsigned long writeHead; // head of the writer of ringBuffer (speaker, mockingboard)
|
||||
ptrdiff_t writeHead; // head of the writer of ringBuffer (speaker, mockingboard)
|
||||
unsigned long writeWrapCount; // count of buffer wraps for the writer
|
||||
|
||||
unsigned long spinLock; // spinlock around reader variables
|
||||
unsigned long readHead; // head of the reader of ringBuffer (OpenSLES callback)
|
||||
ptrdiff_t readHead; // head of the reader of ringBuffer (OpenSLES callback)
|
||||
unsigned long readWrapCount; // count of buffer wraps for the reader
|
||||
|
||||
unsigned long replay_index;
|
||||
|
||||
// misc
|
||||
unsigned long numChannels;
|
||||
bool backfillQuiet
|
||||
// next voice
|
||||
struct SLVoice *next;
|
||||
} SLVoice;
|
||||
|
||||
typedef struct SLVoices {
|
||||
unsigned long voiceId;
|
||||
SLVoice *voice;
|
||||
UT_hash_handle hh;
|
||||
} SLVoices;
|
||||
|
||||
typedef struct EngineContext_s {
|
||||
SLObjectItf engineObject;
|
||||
SLEngineItf engineEngine;
|
||||
SLObjectItf outputMixObject;
|
||||
|
||||
SLObjectItf bqPlayerObject;
|
||||
SLPlayItf bqPlayerPlay;
|
||||
SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue;
|
||||
|
||||
uint8_t *mixBuf; // mix buffer submitted to OpenSLES
|
||||
unsigned long submitSize; // buffer size OpenSLES expects/wants
|
||||
|
||||
SLVoice *voices;
|
||||
SLVoice *recycledVoices;
|
||||
|
||||
} EngineContext_s;
|
||||
|
||||
static SLVoices *voices = NULL;
|
||||
static AudioBackend_s opensles_audio_backend = { 0 };
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@ -93,19 +84,9 @@ static inline bool _underrun_check_and_manage(SLVoice *voice, OUTPARM unsigned l
|
|||
((readHead >= voice->writeHead) && (readWrapCount == voice->writeWrapCount)) )
|
||||
{
|
||||
isUnder = true;
|
||||
LOG("Buffer underrun ... queuing quiet samples ...");
|
||||
|
||||
LOG("Buffer underrun ...");
|
||||
voice->writeHead = readHead;
|
||||
voice->writeWrapCount = readWrapCount;
|
||||
memset(voice->ringBuffer+voice->writeHead, 0x0, voice->submitSize);
|
||||
voice->writeHead += voice->submitSize;
|
||||
|
||||
if (voice->writeHead >= voice->bufferSize) {
|
||||
voice->writeHead = voice->writeHead - voice->bufferSize;
|
||||
memset(voice->ringBuffer+voice->bufferSize, 0x0, voice->submitSize);
|
||||
memset(voice->ringBuffer, 0x0, voice->writeHead);
|
||||
++voice->writeWrapCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (readHead <= voice->writeHead) {
|
||||
|
@ -121,55 +102,107 @@ static inline bool _underrun_check_and_manage(SLVoice *voice, OUTPARM unsigned l
|
|||
// system needs moar data (of the correct buffer size)
|
||||
static void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
|
||||
|
||||
SLVoice *voice = (SLVoice *)context;
|
||||
// invariant : can always read submitSize from position of readHead (bufferSize+submitSize)
|
||||
|
||||
// enqueue next buffer of correct size to OpenSLES
|
||||
// invariant : we can always read submitSize amount from the position of readHead
|
||||
EngineContext_s *ctx = (EngineContext_s *)context;
|
||||
|
||||
SLresult result = SL_RESULT_SUCCESS;
|
||||
if (voice->backfillQuiet) {
|
||||
memcpy(voice->submitBuf, voice->ringBuffer+voice->readHead, voice->submitSize);
|
||||
result = (*bq)->Enqueue(bq, voice->submitBuf, voice->submitSize);
|
||||
memset(voice->ringBuffer+voice->readHead, 0x0, voice->submitSize);
|
||||
} else {
|
||||
result = (*bq)->Enqueue(bq, voice->ringBuffer+voice->readHead, voice->submitSize);
|
||||
}
|
||||
|
||||
// now manage overflow/wrapping ... (it's easier to ask for buffer overflow forgiveness than permission ;-)
|
||||
|
||||
unsigned long newReadHead = voice->readHead + voice->submitSize;
|
||||
unsigned long newReadWrapCount = voice->readWrapCount;
|
||||
|
||||
if (newReadHead >= voice->bufferSize) {
|
||||
newReadHead = newReadHead - voice->bufferSize;
|
||||
if (voice->backfillQuiet) {
|
||||
memset(voice->ringBuffer+voice->bufferSize, 0x0, voice->submitSize);
|
||||
memset(voice->ringBuffer, 0x0, newReadHead);
|
||||
do {
|
||||
// This is a very simple inline mixer so that we only need one BufferQueue (which works best on low-end Android
|
||||
// devices
|
||||
//
|
||||
// HACK ASSUMPTIONS :
|
||||
// * max of 2 voices/buffers
|
||||
// * both buffers contain stereo signed 16bit samples with zero as mid point
|
||||
// * absolute value of maximum amplitude is less than one half SHRT_MAX (to avoid clipping)
|
||||
SLVoice *voice0 = ctx->voices;
|
||||
if (!voice0) {
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
++newReadWrapCount;
|
||||
}
|
||||
|
||||
SPINLOCK_ACQUIRE(&voice->spinLock);
|
||||
voice->readHead = newReadHead;
|
||||
voice->readWrapCount = newReadWrapCount;
|
||||
SPINLOCK_RELINQUISH(&voice->spinLock);
|
||||
// copy/mix data
|
||||
|
||||
memcpy(ctx->mixBuf, voice0->ringBuffer+voice0->readHead, ctx->submitSize);
|
||||
|
||||
SLVoice *voice1 = voice0->next;
|
||||
if (voice1) {
|
||||
|
||||
// add second waveform into mix buffer
|
||||
////if (SIMD_IS_AVAILABLE()) {
|
||||
#if USE_SIMD
|
||||
#warning FIXME TODO vectorial code here
|
||||
#endif
|
||||
////} else {
|
||||
uint16_t *mixBuf = (uint16_t *)ctx->mixBuf;
|
||||
unsigned long submitSize = ctx->submitSize>>1;
|
||||
for (unsigned long i=0; i<submitSize; i++) {
|
||||
mixBuf[i] += ((uint16_t *)(voice1->ringBuffer+voice1->readHead))[i];
|
||||
}
|
||||
////}
|
||||
}
|
||||
|
||||
// submit data to OpenSLES
|
||||
|
||||
result = (*bq)->Enqueue(bq, ctx->mixBuf, ctx->submitSize);
|
||||
|
||||
// now manage quiet backfilling and overflow/wrapping ...
|
||||
|
||||
memset(voice0->ringBuffer+voice0->readHead, 0x0, ctx->submitSize); // backfill quiet samples
|
||||
|
||||
unsigned long newReadHead0 = voice0->readHead + ctx->submitSize;
|
||||
unsigned long newReadWrapCount0 = voice0->readWrapCount;
|
||||
|
||||
if (newReadHead0 >= voice0->bufferSize) {
|
||||
newReadHead0 = newReadHead0 - voice0->bufferSize;
|
||||
memset(voice0->ringBuffer+voice0->bufferSize, 0x0, ctx->submitSize); // backfill quiet samples
|
||||
memset(voice0->ringBuffer, 0x0, newReadHead0);
|
||||
++newReadWrapCount0;
|
||||
}
|
||||
|
||||
SPINLOCK_ACQUIRE(&voice0->spinLock);
|
||||
voice0->readHead = newReadHead0;
|
||||
voice0->readWrapCount = newReadWrapCount0;
|
||||
SPINLOCK_RELINQUISH(&voice0->spinLock);
|
||||
|
||||
if (voice1) {
|
||||
memset(voice1->ringBuffer+voice1->readHead, 0x0, ctx->submitSize); // backfill quiet samples
|
||||
|
||||
unsigned long newReadHead1 = voice1->readHead + ctx->submitSize;
|
||||
unsigned long newReadWrapCount1 = voice1->readWrapCount;
|
||||
|
||||
if (newReadHead1 >= voice1->bufferSize) {
|
||||
newReadHead1 = newReadHead1 - voice1->bufferSize;
|
||||
memset(voice1->ringBuffer+voice1->bufferSize, 0x0, ctx->submitSize); // backfill quiet samples
|
||||
memset(voice1->ringBuffer, 0x0, newReadHead1);
|
||||
++newReadWrapCount1;
|
||||
}
|
||||
|
||||
SPINLOCK_ACQUIRE(&voice1->spinLock);
|
||||
voice1->readHead = newReadHead1;
|
||||
voice1->readWrapCount = newReadWrapCount1;
|
||||
SPINLOCK_RELINQUISH(&voice1->spinLock);
|
||||
}
|
||||
|
||||
} while (0);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
LOG("WARNING: could not enqueue data to OpenSLES!");
|
||||
(*(voice->bqPlayerPlay))->SetPlayState(voice->bqPlayerPlay, SL_PLAYSTATE_STOPPED);
|
||||
(*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_STOPPED);
|
||||
}
|
||||
}
|
||||
|
||||
static long _SLMaybeSubmitAndStart(SLVoice *voice) {
|
||||
SLuint32 state = 0;
|
||||
SLresult result = (*(voice->bqPlayerPlay))->GetPlayState(voice->bqPlayerPlay, &state);
|
||||
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
|
||||
SLresult result = (*(ctx->bqPlayerPlay))->GetPlayState(ctx->bqPlayerPlay, &state);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get source state : %lu", result);
|
||||
} else {
|
||||
if ((state != SL_PLAYSTATE_PLAYING) && (state != SL_PLAYSTATE_PAUSED)) {
|
||||
LOG("FORCING restart audio buffer queue playback ...");
|
||||
result = (*(voice->bqPlayerPlay))->SetPlayState(voice->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
bqPlayerCallback(voice->bqPlayerBufferQueue, voice);
|
||||
result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
bqPlayerCallback(ctx->bqPlayerBufferQueue, ctx);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -192,7 +225,7 @@ static long SLGetPosition(AudioBuffer_s *_this, OUTPARM unsigned long *bytes_que
|
|||
|
||||
unsigned long queuedBytes = 0;
|
||||
if (!underrun) {
|
||||
//queuedBytes = voice->submitSize; // assume that there are always about this much actually queued
|
||||
//queuedBytes = ctx->submitSize; // assume that there are always about this much actually queued
|
||||
}
|
||||
|
||||
assert(workingBytes <= voice->bufferSize);
|
||||
|
@ -211,6 +244,7 @@ static long SLLockBuffer(AudioBuffer_s *_this, unsigned long write_bytes, INOUT
|
|||
|
||||
do {
|
||||
SLVoice *voice = (SLVoice*)_this->_internal;
|
||||
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
|
||||
|
||||
if (write_bytes == 0) {
|
||||
LOG("HMMM ... writing full buffer!");
|
||||
|
@ -226,7 +260,7 @@ static long SLLockBuffer(AudioBuffer_s *_this, unsigned long write_bytes, INOUT
|
|||
|
||||
// TODO FIXME : maybe need to resurrect the 2 inner pointers and foist the responsibility onto the
|
||||
// speaker/mockingboard modules so we can actually write moar here?
|
||||
unsigned long writableBytes = MIN( availableBytes, ((voice->bufferSize+voice->submitSize) - voice->writeHead) );
|
||||
unsigned long writableBytes = MIN( availableBytes, ((voice->bufferSize+ctx->submitSize) - voice->writeHead) );
|
||||
|
||||
if (write_bytes > writableBytes) {
|
||||
OPENSL_LOG("NOTE truncating audio buffer (call again to write complete requested buffer) ...");
|
||||
|
@ -245,21 +279,22 @@ static long SLUnlockBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
|
|||
|
||||
do {
|
||||
SLVoice *voice = (SLVoice*)_this->_internal;
|
||||
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
|
||||
|
||||
unsigned long previousWriteHead = voice->writeHead;
|
||||
|
||||
voice->writeHead += audio_bytes;
|
||||
|
||||
assert((voice->writeHead <= (voice->bufferSize + voice->submitSize)) && "OOPS, real overflow in queued sound data!");
|
||||
assert((voice->writeHead <= (voice->bufferSize + ctx->submitSize)) && "OOPS, real overflow in queued sound data!");
|
||||
|
||||
if (voice->writeHead >= voice->bufferSize) {
|
||||
// copy data from overflow into beginning of buffer
|
||||
voice->writeHead = voice->writeHead - voice->bufferSize;
|
||||
++voice->writeWrapCount;
|
||||
memcpy(voice->ringBuffer, voice->ringBuffer+voice->bufferSize, voice->writeHead);
|
||||
} else if (previousWriteHead < voice->submitSize) {
|
||||
} else if (previousWriteHead < ctx->submitSize) {
|
||||
// copy data in beginning of buffer into overflow position
|
||||
unsigned long copyNumBytes = MIN(audio_bytes, voice->submitSize-previousWriteHead);
|
||||
unsigned long copyNumBytes = MIN(audio_bytes, ctx->submitSize-previousWriteHead);
|
||||
memcpy(voice->ringBuffer+voice->bufferSize+previousWriteHead, voice->ringBuffer+previousWriteHead, copyNumBytes);
|
||||
}
|
||||
|
||||
|
@ -269,6 +304,7 @@ static long SLUnlockBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
|
|||
return err;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// HACK Part I : done once for mockingboard that has semiauto repeating phonemes ...
|
||||
static long SLUnlockStaticBuffer(AudioBuffer_s *_this, unsigned long audio_bytes) {
|
||||
SLVoice *voice = (SLVoice*)_this->_internal;
|
||||
|
@ -289,6 +325,7 @@ static long SLReplay(AudioBuffer_s *_this) {
|
|||
#warning FIXME TODO ... how do we handle mockingboard for new OpenSLES buffer queue codepath?
|
||||
return err;
|
||||
}
|
||||
#endif
|
||||
|
||||
static long SLGetStatus(AudioBuffer_s *_this, OUTPARM unsigned long *status) {
|
||||
*status = -1;
|
||||
|
@ -296,9 +333,10 @@ static long SLGetStatus(AudioBuffer_s *_this, OUTPARM unsigned long *status) {
|
|||
|
||||
do {
|
||||
SLVoice* voice = (SLVoice*)_this->_internal;
|
||||
EngineContext_s *ctx = (EngineContext_s *)voice->ctx;
|
||||
|
||||
SLuint32 state = 0;
|
||||
result = (*(voice->bqPlayerPlay))->GetPlayState(voice->bqPlayerPlay, &state);
|
||||
result = (*(ctx->bqPlayerPlay))->GetPlayState(ctx->bqPlayerPlay, &state);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get source state : %lu", result);
|
||||
break;
|
||||
|
@ -317,212 +355,55 @@ static long SLGetStatus(AudioBuffer_s *_this, OUTPARM unsigned long *status) {
|
|||
// ----------------------------------------------------------------------------
|
||||
// SLVoice is the AudioBuffer_s->_internal
|
||||
|
||||
static void _opensl_destroyVoice(SLVoice *voice) {
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (voice->bqPlayerObject != NULL) {
|
||||
(*(voice->bqPlayerObject))->Destroy(voice->bqPlayerObject);
|
||||
voice->bqPlayerObject = NULL;
|
||||
voice->bqPlayerPlay = NULL;
|
||||
voice->bqPlayerBufferQueue = NULL;
|
||||
voice->bqPlayerMuteSolo = NULL;
|
||||
voice->bqPlayerVolume = NULL;
|
||||
}
|
||||
|
||||
static inline void _opensl_destroyVoice(SLVoice *voice) {
|
||||
if (voice->ringBuffer) {
|
||||
FREE(voice->ringBuffer);
|
||||
}
|
||||
|
||||
memset(voice, 0, sizeof(*voice));
|
||||
FREE(voice);
|
||||
}
|
||||
|
||||
static SLVoice *_opensl_createVoice(unsigned long numChannels, const EngineContext_s *ctx) {
|
||||
SLVoice *voice = NULL;
|
||||
|
||||
do {
|
||||
|
||||
//
|
||||
// General buffer memory management
|
||||
//
|
||||
|
||||
voice = calloc(1, sizeof(*voice));
|
||||
if (voice == NULL) {
|
||||
ERRLOG("OOPS, Out of memory!");
|
||||
break;
|
||||
}
|
||||
|
||||
assert(numChannels == 1 || numChannels == 2);
|
||||
voice->numChannels = numChannels;
|
||||
|
||||
SLuint32 channelMask = 0;
|
||||
if (numChannels == 2) {
|
||||
channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
|
||||
voice->submitSize = android_stereoBufferSubmitSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * numChannels;
|
||||
voice->bufferSize = opensles_audio_backend.systemSettings.stereoBufferSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * numChannels;
|
||||
voice->backfillQuiet = true;
|
||||
LOG("ideal stereo submission bufsize is %lu (bytes:%lu)", (unsigned long)android_stereoBufferSubmitSizeSamples, (unsigned long)voice->submitSize);
|
||||
} else {
|
||||
channelMask = SL_SPEAKER_FRONT_CENTER;
|
||||
voice->submitSize = android_monoBufferSubmitSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample;
|
||||
voice->bufferSize = opensles_audio_backend.systemSettings.monoBufferSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample;
|
||||
LOG("ideal mono submission bufsize is %lu (bytes:%lu)", (unsigned long)android_monoBufferSubmitSizeSamples, (unsigned long)voice->submitSize);
|
||||
}
|
||||
|
||||
// Allocate enough space for the temp buffer (including a maximum allowed overflow)
|
||||
voice->ringBuffer = malloc(voice->bufferSize + voice->submitSize/*max overflow*/);
|
||||
if (voice->ringBuffer == NULL) {
|
||||
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)voice->bufferSize+voice->submitSize);
|
||||
break;
|
||||
}
|
||||
|
||||
voice->submitBuf = malloc(voice->submitSize);
|
||||
if (voice->submitBuf == NULL) {
|
||||
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)voice->submitSize);
|
||||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// OpenSLES buffer queue player setup
|
||||
//
|
||||
|
||||
SLresult result = SL_RESULT_UNKNOWN_ERROR;
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
|
||||
.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
.numBuffers = 2,
|
||||
#warning FIXME TODO ... verify 2 numBuffers is best
|
||||
};
|
||||
SLDataFormat_PCM format_pcm = {
|
||||
.formatType = SL_DATAFORMAT_PCM,
|
||||
.numChannels = numChannels,
|
||||
.samplesPerSec = opensles_audio_backend.systemSettings.sampleRateHz * 1000,
|
||||
.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
.channelMask = channelMask,
|
||||
.endianness = SL_BYTEORDER_LITTLEENDIAN,
|
||||
};
|
||||
SLDataSource audioSrc = {
|
||||
.pLocator = &loc_bufq,
|
||||
.pFormat = &format_pcm,
|
||||
};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {
|
||||
.locatorType = SL_DATALOCATOR_OUTPUTMIX,
|
||||
.outputMix = ctx->outputMixObject,
|
||||
};
|
||||
SLDataSink audioSnk = {
|
||||
.pLocator = &loc_outmix,
|
||||
.pFormat = NULL,
|
||||
};
|
||||
|
||||
// create audio player
|
||||
#define _NUM_INTERFACES 3
|
||||
const SLInterfaceID ids[_NUM_INTERFACES] = {
|
||||
SL_IID_BUFFERQUEUE,
|
||||
SL_IID_EFFECTSEND,
|
||||
//SL_IID_MUTESOLO,
|
||||
SL_IID_VOLUME,
|
||||
};
|
||||
const SLboolean req[_NUM_INTERFACES] = {
|
||||
SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
//numChannels == 1 ? SL_BOOLEAN_FALSE : SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
};
|
||||
|
||||
result = (*(ctx->engineEngine))->CreateAudioPlayer(ctx->engineEngine, &(voice->bqPlayerObject), &audioSrc, &audioSnk, _NUM_INTERFACES, ids, req);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not create the BufferQueue player object : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// realize the player
|
||||
result = (*(voice->bqPlayerObject))->Realize(voice->bqPlayerObject, /*asynchronous_realization:*/SL_BOOLEAN_FALSE);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not realize the BufferQueue player object : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// get the play interface
|
||||
result = (*(voice->bqPlayerObject))->GetInterface(voice->bqPlayerObject, SL_IID_PLAY, &(voice->bqPlayerPlay));
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get the play interface : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*(voice->bqPlayerObject))->GetInterface(voice->bqPlayerObject, SL_IID_BUFFERQUEUE, &(voice->bqPlayerBufferQueue));
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get the BufferQueue play interface : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*(voice->bqPlayerBufferQueue))->RegisterCallback(voice->bqPlayerBufferQueue, bqPlayerCallback, voice);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not register BufferQueue callback : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
#if 0 // mute/solo is not supported for sources that are known to be mono, as this is
|
||||
// get the mute/solo interface
|
||||
result = (*(voice->bqPlayerObject))->GetInterface(voice->bqPlayerObject, SL_IID_MUTESOLO, &(voice->bqPlayerMuteSolo));
|
||||
assert(SL_RESULT_SUCCESS == result);
|
||||
(void)result;
|
||||
#endif
|
||||
|
||||
// get the volume interface
|
||||
result = (*(voice->bqPlayerObject))->GetInterface(voice->bqPlayerObject, SL_IID_VOLUME, &(voice->bqPlayerVolume));
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get the BufferQueue volume interface : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// Force OpenSLES to start playback
|
||||
unsigned long workingBytes;
|
||||
_underrun_check_and_manage(voice, &workingBytes);
|
||||
_SLMaybeSubmitAndStart(voice);
|
||||
|
||||
return voice;
|
||||
|
||||
} while(0);
|
||||
|
||||
// ERR
|
||||
if (voice) {
|
||||
_opensl_destroyVoice(voice);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
static long opensl_destroySoundBuffer(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **soundbuf_struct) {
|
||||
static long opensl_destroySoundBuffer(const struct AudioContext_s *audio_context, INOUT AudioBuffer_s **soundbuf_struct) {
|
||||
if (!*soundbuf_struct) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG("opensl_destroySoundBuffer ...");
|
||||
SLVoice *voice = (SLVoice *)((*soundbuf_struct)->_internal);
|
||||
unsigned long voiceId = voice->voiceId;
|
||||
|
||||
_opensl_destroyVoice(voice);
|
||||
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
|
||||
|
||||
SLVoices *vnode = NULL;
|
||||
HASH_FIND_INT(voices, &voiceId, vnode);
|
||||
if (vnode) {
|
||||
HASH_DEL(voices, vnode);
|
||||
FREE(vnode);
|
||||
SLVoice *v = (SLVoice *)((*soundbuf_struct)->_internal);
|
||||
|
||||
SLVoice *vprev = NULL;
|
||||
SLVoice *voice = ctx->voices;
|
||||
while (voice) {
|
||||
if (voice == v) {
|
||||
if (vprev) {
|
||||
vprev->next = voice->next;
|
||||
} else {
|
||||
ctx->voices = voice->next;
|
||||
}
|
||||
break;
|
||||
}
|
||||
vprev = voice;
|
||||
voice = voice->next;
|
||||
}
|
||||
|
||||
assert(voice && "voice should exist, or speaker, mockingboard, etc are not using this internal API correctly!");
|
||||
|
||||
// Do not actually destory the voice here since we could race with the buffer queue. purge these on complete sound
|
||||
// system shutdown
|
||||
|
||||
voice->next = ctx->recycledVoices;
|
||||
ctx->recycledVoices = voice;
|
||||
|
||||
memset(*soundbuf_struct, 0x0, sizeof(soundbuf_struct));
|
||||
FREE(*soundbuf_struct);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long opensl_createSoundBuffer(const AudioContext_s *audio_context, unsigned long numChannels, INOUT AudioBuffer_s **soundbuf_struct) {
|
||||
static long opensl_createSoundBuffer(const AudioContext_s *audio_context, INOUT AudioBuffer_s **soundbuf_struct) {
|
||||
LOG("opensl_createSoundBuffer ...");
|
||||
assert(*soundbuf_struct == NULL);
|
||||
|
||||
|
@ -533,24 +414,35 @@ static long opensl_createSoundBuffer(const AudioContext_s *audio_context, unsign
|
|||
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
|
||||
assert(ctx != NULL);
|
||||
|
||||
if ((voice = _opensl_createVoice(numChannels, ctx)) == NULL)
|
||||
{
|
||||
ERRLOG("OOPS, Cannot create new voice");
|
||||
break;
|
||||
unsigned long bufferSize = opensles_audio_backend.systemSettings.stereoBufferSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * NUM_CHANNELS;
|
||||
|
||||
if (ctx->recycledVoices) {
|
||||
LOG("Recycling previously SLVoice ...");
|
||||
voice = ctx->recycledVoices;
|
||||
ctx->recycledVoices = voice->next;
|
||||
uint8_t *prevBuffer = voice->ringBuffer;
|
||||
memset(voice, 0x0, sizeof(SLVoice *));
|
||||
voice->bufferSize = bufferSize;
|
||||
voice->ringBuffer = prevBuffer;
|
||||
} else {
|
||||
LOG("Creating new SLVoice ...");
|
||||
voice = calloc(1, sizeof(*voice));
|
||||
if (voice == NULL) {
|
||||
ERRLOG("OOPS, Out of memory!");
|
||||
break;
|
||||
}
|
||||
voice->bufferSize = bufferSize;
|
||||
// Allocate enough space for the temp buffer (including a maximum allowed overflow)
|
||||
voice->ringBuffer = malloc(voice->bufferSize + ctx->submitSize/*max overflow*/);
|
||||
if (voice->ringBuffer == NULL) {
|
||||
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)voice->bufferSize+ctx->submitSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SLVoices *vnode = calloc(1, sizeof(SLVoices));
|
||||
if (!vnode) {
|
||||
ERRLOG("OOPS, Not enough memory");
|
||||
break;
|
||||
}
|
||||
LOG("ideal stereo submission bufsize is %lu (bytes:%lu)", (unsigned long)android_stereoBufferSubmitSizeSamples, (unsigned long)ctx->submitSize);
|
||||
|
||||
static unsigned long counter = 0;
|
||||
vnode->voiceId = __sync_add_and_fetch(&counter, 1);
|
||||
voice->voiceId = vnode->voiceId;
|
||||
|
||||
vnode->voice = voice;
|
||||
HASH_ADD_INT(voices, voice->voiceId, vnode);
|
||||
voice->ctx = ctx;
|
||||
|
||||
if ((*soundbuf_struct = malloc(sizeof(AudioBuffer_s))) == NULL) {
|
||||
ERRLOG("OOPS, Not enough memory");
|
||||
|
@ -562,9 +454,14 @@ static long opensl_createSoundBuffer(const AudioContext_s *audio_context, unsign
|
|||
(*soundbuf_struct)->Lock = &SLLockBuffer;
|
||||
(*soundbuf_struct)->Unlock = &SLUnlockBuffer;
|
||||
(*soundbuf_struct)->GetStatus = &SLGetStatus;
|
||||
// mockingboard-specific hacks
|
||||
(*soundbuf_struct)->UnlockStaticBuffer = &SLUnlockStaticBuffer;
|
||||
(*soundbuf_struct)->Replay = &SLReplay;
|
||||
// mockingboard-specific (SSI263) hacks
|
||||
//(*soundbuf_struct)->UnlockStaticBuffer = &SLUnlockStaticBuffer;
|
||||
//(*soundbuf_struct)->Replay = &SLReplay;
|
||||
|
||||
voice->next = ctx->voices;
|
||||
ctx->voices = voice;
|
||||
|
||||
LOG("Successfully created SLVoice");
|
||||
|
||||
return 0;
|
||||
} while(0);
|
||||
|
@ -586,6 +483,14 @@ static long opensles_systemShutdown(AudioContext_s **audio_context) {
|
|||
EngineContext_s *ctx = (EngineContext_s *)((*audio_context)->_internal);
|
||||
assert(ctx != NULL);
|
||||
|
||||
// destroy buffer queue audio player object, and invalidate all associated interfaces
|
||||
if (ctx->bqPlayerObject != NULL) {
|
||||
(*(ctx->bqPlayerObject))->Destroy(ctx->bqPlayerObject);
|
||||
ctx->bqPlayerObject = NULL;
|
||||
ctx->bqPlayerPlay = NULL;
|
||||
ctx->bqPlayerBufferQueue = NULL;
|
||||
}
|
||||
|
||||
// destroy output mix object, and invalidate all associated interfaces
|
||||
if (ctx->outputMixObject != NULL) {
|
||||
(*(ctx->outputMixObject))->Destroy(ctx->outputMixObject);
|
||||
|
@ -599,7 +504,23 @@ static long opensles_systemShutdown(AudioContext_s **audio_context) {
|
|||
ctx->engineEngine = NULL;
|
||||
}
|
||||
|
||||
if (ctx->mixBuf) {
|
||||
FREE(ctx->mixBuf);
|
||||
}
|
||||
|
||||
assert(ctx->voices == NULL && "incorrect API usage");
|
||||
|
||||
SLVoice *voice = ctx->recycledVoices;
|
||||
while (voice) {
|
||||
SLVoice *vkill = voice;
|
||||
voice = voice->next;
|
||||
_opensl_destroyVoice(vkill);
|
||||
}
|
||||
|
||||
memset(ctx, 0x0, sizeof(EngineContext_s));
|
||||
FREE(ctx);
|
||||
|
||||
memset(*audio_context, 0x0, sizeof(AudioContext_s));
|
||||
FREE(*audio_context);
|
||||
|
||||
return 0;
|
||||
|
@ -607,7 +528,6 @@ static long opensles_systemShutdown(AudioContext_s **audio_context) {
|
|||
|
||||
static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
|
||||
assert(*audio_context == NULL);
|
||||
assert(voices == NULL);
|
||||
|
||||
EngineContext_s *ctx = NULL;
|
||||
SLresult result = -1;
|
||||
|
@ -616,16 +536,23 @@ static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
|
|||
opensles_audio_backend.systemSettings.bytesPerSample = 2;
|
||||
|
||||
if (android_deviceSampleRateHz <= 22050/*sentinel in DevicePropertyCalculator.java*/) {
|
||||
// HACK NOTE : assuming this is a low-end Gingerbread device ... try to push for a lower submit size to improve
|
||||
// latency ... this is less aggressive than calculations made in DevicePropertyCalculator.java
|
||||
android_monoBufferSubmitSizeSamples >>= 1;
|
||||
android_stereoBufferSubmitSizeSamples >>= 1;
|
||||
android_monoBufferSubmitSizeSamples;
|
||||
android_stereoBufferSubmitSizeSamples >>= 1; // value from Android/Java seems to be pre-multiplied by channel size?
|
||||
opensles_audio_backend.systemSettings.monoBufferSizeSamples = android_deviceSampleRateHz * 0.3/*sec*/;
|
||||
opensles_audio_backend.systemSettings.stereoBufferSizeSamples = android_deviceSampleRateHz * 0.3/*sec*/;
|
||||
} else {
|
||||
opensles_audio_backend.systemSettings.monoBufferSizeSamples = android_deviceSampleRateHz * 0.125/*sec*/;
|
||||
opensles_audio_backend.systemSettings.stereoBufferSizeSamples = android_deviceSampleRateHz * 0.125/*sec*/;
|
||||
}
|
||||
|
||||
if (android_stereoBufferSubmitSizeSamples<<2 > opensles_audio_backend.systemSettings.stereoBufferSizeSamples) {
|
||||
opensles_audio_backend.systemSettings.stereoBufferSizeSamples = android_stereoBufferSubmitSizeSamples<<2;
|
||||
LOG("Changing stereo buffer size to be %lu samples", opensles_audio_backend.systemSettings.stereoBufferSizeSamples);
|
||||
}
|
||||
if (android_monoBufferSubmitSizeSamples<<2 > opensles_audio_backend.systemSettings.monoBufferSizeSamples) {
|
||||
opensles_audio_backend.systemSettings.monoBufferSizeSamples = android_monoBufferSubmitSizeSamples<<2;
|
||||
LOG("Changing mono buffer size to be %lu samples", opensles_audio_backend.systemSettings.monoBufferSizeSamples);
|
||||
}
|
||||
#warning TODO FIXME ^^^^^ need a dynamic bufferSize calculation/calibration routine to determine optimal buffer size for device ... may also need a user-initiated calibration too
|
||||
|
||||
do {
|
||||
|
@ -638,6 +565,13 @@ static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
|
|||
break;
|
||||
}
|
||||
|
||||
ctx->submitSize = android_stereoBufferSubmitSizeSamples * opensles_audio_backend.systemSettings.bytesPerSample * NUM_CHANNELS;
|
||||
ctx->mixBuf = malloc(ctx->submitSize);
|
||||
if (ctx->mixBuf == NULL) {
|
||||
ERRLOG("OOPS, Error allocating %lu bytes", (unsigned long)ctx->submitSize);
|
||||
break;
|
||||
}
|
||||
|
||||
// create basic engine
|
||||
result = slCreateEngine(&(ctx->engineObject), 0, NULL, /*engineMixIIDCount:*/0, /*engineMixIIDs:*/NULL, /*engineMixReqs:*/NULL);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
|
@ -683,10 +617,95 @@ static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
|
|||
break;
|
||||
}
|
||||
|
||||
//
|
||||
// OpenSLES buffer queue player setup
|
||||
//
|
||||
|
||||
// configure audio source
|
||||
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {
|
||||
.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
|
||||
.numBuffers = 2,
|
||||
#warning FIXME TODO ... verify 2 numBuffers is best
|
||||
};
|
||||
SLDataFormat_PCM format_pcm = {
|
||||
.formatType = SL_DATAFORMAT_PCM,
|
||||
.numChannels = 2,
|
||||
.samplesPerSec = opensles_audio_backend.systemSettings.sampleRateHz * 1000,
|
||||
.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16,
|
||||
.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
|
||||
.endianness = SL_BYTEORDER_LITTLEENDIAN,
|
||||
};
|
||||
SLDataSource audioSrc = {
|
||||
.pLocator = &loc_bufq,
|
||||
.pFormat = &format_pcm,
|
||||
};
|
||||
|
||||
// configure audio sink
|
||||
SLDataLocator_OutputMix loc_outmix = {
|
||||
.locatorType = SL_DATALOCATOR_OUTPUTMIX,
|
||||
.outputMix = ctx->outputMixObject,
|
||||
};
|
||||
SLDataSink audioSnk = {
|
||||
.pLocator = &loc_outmix,
|
||||
.pFormat = NULL,
|
||||
};
|
||||
|
||||
// create audio player
|
||||
#define _NUM_INTERFACES 3
|
||||
const SLInterfaceID ids[_NUM_INTERFACES] = {
|
||||
SL_IID_BUFFERQUEUE,
|
||||
SL_IID_EFFECTSEND,
|
||||
//SL_IID_MUTESOLO,
|
||||
SL_IID_VOLUME,
|
||||
};
|
||||
const SLboolean req[_NUM_INTERFACES] = {
|
||||
SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
//numChannels == 1 ? SL_BOOLEAN_FALSE : SL_BOOLEAN_TRUE,
|
||||
SL_BOOLEAN_TRUE,
|
||||
};
|
||||
|
||||
result = (*(ctx->engineEngine))->CreateAudioPlayer(ctx->engineEngine, &(ctx->bqPlayerObject), &audioSrc, &audioSnk, _NUM_INTERFACES, ids, req);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not create the BufferQueue player object : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// realize the player
|
||||
result = (*(ctx->bqPlayerObject))->Realize(ctx->bqPlayerObject, /*asynchronous_realization:*/SL_BOOLEAN_FALSE);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not realize the BufferQueue player object : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// get the play interface
|
||||
result = (*(ctx->bqPlayerObject))->GetInterface(ctx->bqPlayerObject, SL_IID_PLAY, &(ctx->bqPlayerPlay));
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get the play interface : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// get the buffer queue interface
|
||||
result = (*(ctx->bqPlayerObject))->GetInterface(ctx->bqPlayerObject, SL_IID_BUFFERQUEUE, &(ctx->bqPlayerBufferQueue));
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not get the BufferQueue play interface : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
// register callback on the buffer queue
|
||||
result = (*(ctx->bqPlayerBufferQueue))->RegisterCallback(ctx->bqPlayerBufferQueue, bqPlayerCallback, ctx);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, could not register BufferQueue callback : %lu", result);
|
||||
break;
|
||||
}
|
||||
|
||||
(*audio_context)->_internal = ctx;
|
||||
(*audio_context)->CreateSoundBuffer = &opensl_createSoundBuffer;
|
||||
(*audio_context)->DestroySoundBuffer = &opensl_destroySoundBuffer;
|
||||
|
||||
LOG("Successfully created OpenSLES engine and buffer queue");
|
||||
|
||||
} while (0);
|
||||
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
|
@ -703,37 +722,20 @@ static long opensles_systemSetup(INOUT AudioContext_s **audio_context) {
|
|||
}
|
||||
|
||||
// pause all audio
|
||||
static long opensles_systemPause(void) {
|
||||
static long opensles_systemPause(AudioContext_s *audio_context) {
|
||||
LOG("OpenSLES pausing play");
|
||||
|
||||
SLVoices *vnode = NULL;
|
||||
SLVoices *tmp = NULL;
|
||||
|
||||
HASH_ITER(hh, voices, vnode, tmp) {
|
||||
SLVoice *voice = vnode->voice;
|
||||
SLresult result = (*(voice->bqPlayerPlay))->SetPlayState(voice->bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, Failed to pause source : %lu", result);
|
||||
}
|
||||
}
|
||||
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
|
||||
SLresult result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PAUSED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long opensles_systemResume(void) {
|
||||
static long opensles_systemResume(AudioContext_s *audio_context) {
|
||||
LOG("OpenSLES resuming play");
|
||||
|
||||
SLVoices *vnode = NULL;
|
||||
SLVoices *tmp = NULL;
|
||||
int err = 0;
|
||||
|
||||
HASH_ITER(hh, voices, vnode, tmp) {
|
||||
SLVoice *voice = vnode->voice;
|
||||
SLresult result = (*(voice->bqPlayerPlay))->SetPlayState(voice->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
if (result != SL_RESULT_SUCCESS) {
|
||||
ERRLOG("OOPS, Failed to resume source : %lu", result);
|
||||
}
|
||||
}
|
||||
EngineContext_s *ctx = (EngineContext_s *)(audio_context->_internal);
|
||||
SLresult result = (*(ctx->bqPlayerPlay))->SetPlayState(ctx->bqPlayerPlay, SL_PLAYSTATE_PLAYING);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ AudioBackend_s *audio_backend = NULL;
|
|||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer, unsigned long numChannels) {
|
||||
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer) {
|
||||
*audioBuffer = NULL;
|
||||
|
||||
if (!audio_isAvailable) {
|
||||
|
@ -44,7 +44,7 @@ long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer, unsigned long nu
|
|||
err = -1;
|
||||
break;
|
||||
}
|
||||
err = audioContext->CreateSoundBuffer(audioContext, numChannels, audioBuffer);
|
||||
err = audioContext->CreateSoundBuffer(audioContext, audioBuffer);
|
||||
if (err) {
|
||||
break;
|
||||
}
|
||||
|
@ -98,13 +98,13 @@ void audio_pause(void) {
|
|||
if (!audio_isAvailable) {
|
||||
return;
|
||||
}
|
||||
audio_backend->pause();
|
||||
audio_backend->pause(audioContext);
|
||||
}
|
||||
|
||||
void audio_resume(void) {
|
||||
if (!audio_isAvailable) {
|
||||
return;
|
||||
}
|
||||
audio_backend->resume();
|
||||
audio_backend->resume(audioContext);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,15 +43,15 @@ typedef struct AudioBuffer_s {
|
|||
long (*GetStatus)(struct AudioBuffer_s *_this, OUTPARM unsigned long *status);
|
||||
|
||||
// Mockingboard-specific buffer replay
|
||||
long (*UnlockStaticBuffer)(struct AudioBuffer_s *_this, unsigned long audio_bytes);
|
||||
long (*Replay)(struct AudioBuffer_s *_this);
|
||||
//long (*UnlockStaticBuffer)(struct AudioBuffer_s *_this, unsigned long audio_bytes);
|
||||
//long (*Replay)(struct AudioBuffer_s *_this);
|
||||
|
||||
} AudioBuffer_s;
|
||||
|
||||
/*
|
||||
* Creates a sound buffer object.
|
||||
*/
|
||||
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer, unsigned long numChannels);
|
||||
long audio_createSoundBuffer(INOUT AudioBuffer_s **audioBuffer);
|
||||
|
||||
/*
|
||||
* Destroy and nullify sound buffer object.
|
||||
|
@ -111,7 +111,7 @@ typedef struct AudioSettings_s {
|
|||
|
||||
typedef struct AudioContext_s {
|
||||
PRIVATE void *_internal;
|
||||
PRIVATE long (*CreateSoundBuffer)(const struct AudioContext_s *sound_system, unsigned long numChannels, INOUT AudioBuffer_s **buffer);
|
||||
PRIVATE long (*CreateSoundBuffer)(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **buffer);
|
||||
PRIVATE long (*DestroySoundBuffer)(const struct AudioContext_s *sound_system, INOUT AudioBuffer_s **buffer);
|
||||
} AudioContext_s;
|
||||
|
||||
|
@ -123,8 +123,8 @@ typedef struct AudioBackend_s {
|
|||
PRIVATE long (*setup)(INOUT AudioContext_s **audio_context);
|
||||
PRIVATE long (*shutdown)(INOUT AudioContext_s **audio_context);
|
||||
|
||||
PRIVATE long (*pause)(void);
|
||||
PRIVATE long (*resume)(void);
|
||||
PRIVATE long (*pause)(AudioContext_s *audio_context);
|
||||
PRIVATE long (*resume)(AudioContext_s *audio_context);
|
||||
|
||||
} AudioBackend_s;
|
||||
|
||||
|
|
|
@ -31,10 +31,18 @@
|
|||
|
||||
#define SPKR_SILENT_STEP 1
|
||||
|
||||
#if defined(NUM_CHANNELS)
|
||||
#error FIXME
|
||||
#if defined(ANDROID)
|
||||
// Note to future self:
|
||||
//
|
||||
// This is a leaky backend implementation detail : we are optimizing to use only one OpenSLES buffer queue callback that
|
||||
// mixes samples from both mockingboard and speaker (thus the speaker needs to be stereo to match mockingboard).
|
||||
//
|
||||
// This is different than the OpenAL backend implementation for desktop (Linux/Mac/...) which is a push-based local
|
||||
// queue.
|
||||
# define NUM_CHANNELS 2
|
||||
#else
|
||||
#define NUM_CHANNELS 2
|
||||
// this can still be mono for other systems ...
|
||||
# define NUM_CHANNELS 1
|
||||
#endif
|
||||
|
||||
static unsigned long bufferTotalSize = 0;
|
||||
|
@ -342,7 +350,7 @@ void speaker_init(void) {
|
|||
long err = 0;
|
||||
speaker_isAvailable = false;
|
||||
do {
|
||||
err = audio_createSoundBuffer(&speakerBuffer, NUM_CHANNELS);
|
||||
err = audio_createSoundBuffer(&speakerBuffer);
|
||||
if (err) {
|
||||
break;
|
||||
}
|
||||
|
@ -425,6 +433,8 @@ void speaker_flush(void) {
|
|||
// OpenSLES seems to be able to pause output without the nasty pops that I hear with OpenAL on Linux
|
||||
// desktop. So this speaker_going_silent hack is not needed. There is also a noticeable glitch in
|
||||
// OpenSLES when this codepath is enabled.
|
||||
//
|
||||
// Furthermore, the simple mixer in soundcore-opensles.c now requires signed 16bit samples
|
||||
#else
|
||||
// After 0.1sec of //e cycles time start reducing samples to zero (if they aren't there already). This
|
||||
// process attempts to mask the extraneous clicks when freezing/restarting emulation (GUI access) and
|
||||
|
@ -460,7 +470,7 @@ bool speaker_is_active(void) {
|
|||
}
|
||||
|
||||
void speaker_set_volume(int16_t amplitude) {
|
||||
speaker_amplitude = (amplitude/2);
|
||||
speaker_amplitude = amplitude;
|
||||
}
|
||||
|
||||
double speaker_cycles_per_sample(void) {
|
||||
|
@ -494,7 +504,11 @@ GLUE_C_READ(speaker_toggle)
|
|||
|
||||
if (!is_fullspeed) {
|
||||
if (speaker_data == speaker_amplitude) {
|
||||
#ifdef ANDROID
|
||||
speaker_data = -speaker_amplitude;
|
||||
#else
|
||||
speaker_data = 0;
|
||||
#endif
|
||||
} else {
|
||||
speaker_data = speaker_amplitude;
|
||||
}
|
||||
|
|
|
@ -12,13 +12,15 @@
|
|||
#ifndef _SPEAKER_H_
|
||||
#define _SPEAKER_H_
|
||||
|
||||
#define SPKR_DATA_INIT 0x4000
|
||||
// leaky detail : max amplitude should be <= SHRT_MAX/2 to not overflow/clip 16bit samples when simple additive mixing
|
||||
// between speaker and mockingboard
|
||||
#define SPKR_DATA_INIT (SHRT_MAX>>3) // 0x0FFF
|
||||
|
||||
void speaker_init(void);
|
||||
void speaker_destroy(void);
|
||||
void speaker_reset(void);
|
||||
void speaker_flush(void);
|
||||
void speaker_set_volume(int16_t amplitude);
|
||||
void speaker_setVolumeZeroToTen(unsigned long goesToTen);
|
||||
bool speaker_is_active(void);
|
||||
|
||||
/*
|
||||
|
|
|
@ -98,7 +98,11 @@ static inline GLenum safeGLGetError(void) {
|
|||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "../Android/jni/android_globals.h"
|
||||
# include "../Android/jni/android_globals.h"
|
||||
# define USE_SIMD 1
|
||||
# define SIMD_IS_AVAILABLE() (android_armNeonEnabled/* || android_x86SSSE3Enabled*/)
|
||||
#else
|
||||
# define SIMD_IS_AVAILABLE()
|
||||
#endif
|
||||
|
||||
#define PATH_SEPARATOR "/" // =P
|
||||
|
|
|
@ -508,7 +508,8 @@ void c_initialize_apple_ii_memory()
|
|||
void c_initialize_sound_hooks()
|
||||
{
|
||||
#ifdef AUDIO_ENABLED
|
||||
speaker_set_volume(sound_volume * (SPKR_DATA_INIT/10));
|
||||
speaker_setVolumeZeroToTen(sound_volume);
|
||||
MB_SetVolumeZeroToTen(sound_volume);
|
||||
#endif
|
||||
for (int i = 0xC030; i < 0xC040; i++)
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue
Block a user