diff --git a/Android/jni/android_globals.h b/Android/jni/android_globals.h index c07fe796..e3360834 100644 --- a/Android/jni/android_globals.h +++ b/Android/jni/android_globals.h @@ -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; diff --git a/Android/jni/apple2ix.mk b/Android/jni/apple2ix.mk index bf842173..a5488e97 100644 --- a/Android/jni/apple2ix.mk +++ b/Android/jni/apple2ix.mk @@ -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) diff --git a/Android/jni/jnihooks.c b/Android/jni/jnihooks.c index 6795b542..72055d42 100644 --- a/Android/jni/jnihooks.c +++ b/Android/jni/jnihooks.c @@ -12,11 +12,14 @@ #include "common.h" #include "androidkeys.h" +#include #include 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); diff --git a/Android/jni/sources.mk b/Android/jni/sources.mk index 272f742b..43252646 100644 --- a/Android/jni/sources.mk +++ b/Android/jni/sources.mk @@ -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 + diff --git a/src/audio/mockingboard.c b/src/audio/mockingboard.c index b1f9e953..5c7cae91 100644 --- a/src/audio/mockingboard.c +++ b/src/audio/mockingboard.c @@ -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 //=========================================================================== diff --git a/src/audio/mockingboard.h b/src/audio/mockingboard.h index aebffc6a..042b582f 100644 --- a/src/audio/mockingboard.h +++ b/src/audio/mockingboard.h @@ -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); diff --git a/src/audio/soundcore-opensles.c b/src/audio/soundcore-opensles.c index 3f0b1ec2..890b99b3 100644 --- a/src/audio/soundcore-opensles.c +++ b/src/audio/soundcore-opensles.c @@ -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; iringBuffer+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; } diff --git a/src/audio/soundcore.c b/src/audio/soundcore.c index 44c04515..e08712d9 100644 --- a/src/audio/soundcore.c +++ b/src/audio/soundcore.c @@ -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); } diff --git a/src/audio/soundcore.h b/src/audio/soundcore.h index 6bbd55fc..12961dd6 100644 --- a/src/audio/soundcore.h +++ b/src/audio/soundcore.h @@ -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; diff --git a/src/audio/speaker.c b/src/audio/speaker.c index 3a1c5d29..321f7e9d 100644 --- a/src/audio/speaker.c +++ b/src/audio/speaker.c @@ -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; } diff --git a/src/audio/speaker.h b/src/audio/speaker.h index e9b5d063..95e5ce7e 100644 --- a/src/audio/speaker.h +++ b/src/audio/speaker.h @@ -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); /* diff --git a/src/common.h b/src/common.h index e7c3d721..2902913d 100644 --- a/src/common.h +++ b/src/common.h @@ -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 diff --git a/src/misc.c b/src/misc.c index e57f2eab..35774564 100644 --- a/src/misc.c +++ b/src/misc.c @@ -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++) {