Преглед на файлове

sokol_audio - opensles backend: initial version

septag преди 6 години
родител
ревизия
cba6a42319
променени са 1 файла, в които са добавени 285 реда и са изтрити 1 реда
  1. 285 1
      sokol_audio.h

+ 285 - 1
sokol_audio.h

@@ -38,6 +38,7 @@
     - Linux: ALSA (link with asound)
     - macOS/iOS: CoreAudio (link with AudioToolbox)
     - emscripten: WebAudio with ScriptProcessorNode
+    - Android: OpenSLES (link with OpenSLES)
 
     Sokol Audio will not do any buffer mixing or volume control, if you have
     multiple independent input streams of sample data you need to perform the
@@ -63,7 +64,7 @@
 
     SOKOL AUDIO AND SOLOUD
     ======================
-    The WASAPI, ALSA and CoreAudio backend code has been taken from the
+    The WASAPI, ALSA, OpenSLES and CoreAudio backend code has been taken from the
     SoLoud library (with some modifications, so any bugs in there are most
     likely my fault). If you need a more fully-featured audio solution, check
     out SoLoud, it's excellent:
@@ -474,6 +475,8 @@ SOKOL_API_DECL int saudio_push(const float* frames, int num_frames);
 #elif (defined(__linux__) || defined(__unix__)) && !defined(__EMSCRIPTEN__) && !defined(__ANDROID__)
     #define ALSA_PCM_NEW_HW_PARAMS_API
     #include <alsa/asoundlib.h>
+#elif defined(__ANDROID__)
+    #include "SLES/OpenSLES_Android.h"
 #elif defined(_WIN32)
     #ifndef CINTERFACE
     #define CINTERFACE
@@ -559,6 +562,38 @@ typedef struct {
     bool thread_stop;
 } _saudio_backend_t;
 
+/*=== OpenSLES BACKEND DECLARATIONS ==============================================*/
+#elif defined(__ANDROID__)
+
+#define SAUDIO_NUM_BUFFERS 2
+
+typedef struct {
+    pthread_mutex_t mutex;
+    pthread_cond_t cond;  
+    int count;  
+} _saudio_semaphore_t;
+
+typedef struct {
+    SLObjectItf engine_obj;
+    SLEngineItf engine;
+    SLObjectItf output_mix_obj;
+    SLVolumeItf output_mix_vol;
+    SLDataLocator_OutputMix out_locator;
+    SLDataSink dst_data_sink;
+    SLObjectItf player_obj;
+    SLPlayItf player;
+    SLVolumeItf player_vol;
+    SLAndroidSimpleBufferQueueItf player_buffer_queue;
+
+    int16_t* output_buffers[SAUDIO_NUM_BUFFERS];
+    float* src_buffer;
+    int active_buffer;
+    _saudio_semaphore_t buffer_sem;
+    pthread_t thread;
+    volatile int thread_stop;
+    SLDataLocator_AndroidSimpleBufferQueue in_locator;
+} _saudio_backend_t;
+
 /*=== WASAPI BACKEND DECLARATIONS ============================================*/
 #elif defined(_WIN32)
 
@@ -1349,6 +1384,255 @@ _SOKOL_PRIVATE void _saudio_backend_shutdown(void) {
     */
 }
 
+/*=== ANDROID BACKEND IMPLEMENTATION ======================================*/
+#elif defined(__ANDROID__)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+_SOKOL_PRIVATE void _saudio_semaphore_init(_saudio_semaphore_t* sem) {
+    sem->count = 0;
+    int r = pthread_mutex_init(&sem->mutex, NULL);
+    SOKOL_ASSERT(r == 0);
+
+    r = pthread_cond_init(&sem->cond, NULL);
+    SOKOL_ASSERT(r == 0);
+
+    (void)(r);
+}
+
+_SOKOL_PRIVATE void _saudio_semaphore_destroy(_saudio_semaphore_t* sem)
+{
+    pthread_cond_destroy(&sem->cond);
+    pthread_mutex_destroy(&sem->mutex);
+}
+
+_SOKOL_PRIVATE void _saudio_semaphore_post(_saudio_semaphore_t* sem, int count)
+{
+    int r = pthread_mutex_lock(&sem->mutex);
+    SOKOL_ASSERT(r == 0);
+
+    for (int ii = 0; ii < count; ii++) {
+        r = pthread_cond_signal(&sem->cond);
+        SOKOL_ASSERT(r == 0);
+    }
+
+    sem->count += count;
+    r = pthread_mutex_unlock(&sem->mutex);
+    SOKOL_ASSERT(r == 0);
+
+    (void)(r);
+}
+
+_SOKOL_PRIVATE bool _saudio_semaphore_wait(_saudio_semaphore_t* sem)
+{
+    int r = pthread_mutex_lock(&sem->mutex);
+    SOKOL_ASSERT(r == 0);
+
+    while (r == 0 && sem->count <= 0) {
+        r = pthread_cond_wait(&sem->cond, &sem->mutex);
+    }
+
+    bool ok = (r == 0);
+    if (ok) {
+        --sem->count;
+    }
+    r = pthread_mutex_unlock(&sem->mutex);
+    (void)(r);
+    return ok;
+}
+
+/* fill intermediate buffer with new data and reset buffer_pos */
+_SOKOL_PRIVATE void _saudio_opensles_fill_buffer(void) {
+    int src_buffer_frames = _saudio.buffer_frames;
+    if (_saudio_has_callback()) {
+        _saudio_stream_callback(_saudio.backend.src_buffer, src_buffer_frames, _saudio.num_channels);
+    }
+    else {
+        const int src_buffer_byte_size = src_buffer_frames * _saudio.num_channels * sizeof(float);
+        if (0 == _saudio_fifo_read(&_saudio.fifo, (uint8_t*)_saudio.backend.src_buffer, src_buffer_byte_size)) {
+            /* not enough read data available, fill the entire buffer with silence */
+            memset(_saudio.backend.src_buffer, 0x0, src_buffer_byte_size);
+        }
+    }
+}
+
+_SOKOL_PRIVATE void SLAPIENTRY _saudio_opensles_play_cb(SLPlayItf player, void *context, SLuint32 event) {
+    (void)(context);
+    (void)(player);
+
+    if (event & SL_PLAYEVENT_HEADATEND) {
+        _saudio_semaphore_post(&_saudio.backend.buffer_sem, 1);
+    }
+}
+
+_SOKOL_PRIVATE void* _saudio_opensles_thread_fn(void* param) {
+    while (!_saudio.backend.thread_stop)  {
+        /* get next output buffer, advance, next buffer. */
+        int16_t* out_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];
+        _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
+        int16_t* next_buffer = _saudio.backend.output_buffers[_saudio.backend.active_buffer];     
+
+        /* queue this buffer */
+        const int buffer_size_bytes = _saudio.buffer_frames * _saudio.num_channels * sizeof(short);
+        (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, out_buffer, buffer_size_bytes);
+        
+        /* fill the next buffer */
+        _saudio_opensles_fill_buffer();
+        const int num_samples = _saudio.num_channels * _saudio.buffer_frames;
+        for (int i = 0; i < num_samples; ++i) {
+            next_buffer[i] = (int16_t) (_saudio.backend.src_buffer[i] * 0x7FFF);
+        }
+
+        _saudio_semaphore_wait(&_saudio.backend.buffer_sem);
+    }
+
+    return 0;
+}
+
+_SOKOL_PRIVATE void _saudio_backend_shutdown(void) { 
+    _saudio.backend.thread_stop = 1;
+    pthread_join(_saudio.backend.thread, 0);
+
+    if (_saudio.backend.player_obj) {
+        (*_saudio.backend.player_obj)->Destroy(_saudio.backend.player_obj);
+    }
+
+    if (_saudio.backend.output_mix_obj) {
+        (*_saudio.backend.output_mix_obj)->Destroy(_saudio.backend.output_mix_obj);
+    }
+
+    if (_saudio.backend.engine_obj) {
+        (*_saudio.backend.engine_obj)->Destroy(_saudio.backend.engine_obj);
+    }
+
+    for (int i = 0; i < SAUDIO_NUM_BUFFERS; i++) {
+        SOKOL_FREE(_saudio.backend.output_buffers[i]);
+    }    
+    SOKOL_FREE(_saudio.backend.src_buffer);
+}
+
+_SOKOL_PRIVATE bool _saudio_backend_init(void) { 
+    _saudio.bytes_per_frame = sizeof(float) * _saudio.num_channels;
+
+    for (int i = 0; i < SAUDIO_NUM_BUFFERS; ++i) {
+        const int buffer_size_bytes = sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
+        _saudio.backend.output_buffers[i] = SOKOL_MALLOC(buffer_size_bytes);
+        SOKOL_ASSERT(_saudio.backend.output_buffers[i]);
+        memset(_saudio.backend.output_buffers[i], 0x0, buffer_size_bytes);
+    }
+
+    {
+        const int buffer_size_bytes = _saudio.bytes_per_frame * _saudio.buffer_frames;
+        _saudio.backend.src_buffer = SOKOL_MALLOC(buffer_size_bytes);
+        SOKOL_ASSERT(_saudio.backend.src_buffer);
+        memset(_saudio.backend.src_buffer, 0x0, buffer_size_bytes);
+    }
+
+
+    /* Create engine */
+    const SLEngineOption opts[] = { SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE };
+    if (slCreateEngine(&_saudio.backend.engine_obj, 1, opts, 0, NULL, NULL ) != SL_RESULT_SUCCESS) {
+        SOKOL_LOG("sokol_audio opensles: slCreateEngine failed");
+        _saudio_backend_shutdown();
+        return false;
+    }
+
+    (*_saudio.backend.engine_obj)->Realize(_saudio.backend.engine_obj, SL_BOOLEAN_FALSE);
+    if ((*_saudio.backend.engine_obj)->GetInterface(_saudio.backend.engine_obj, SL_IID_ENGINE, &_saudio.backend.engine) != SL_RESULT_SUCCESS) {
+        SOKOL_LOG("sokol_audio opensles: GetInterface->Engine failed");
+        _saudio_backend_shutdown();
+        return false;
+    }
+
+    /* Create output mix. */
+    {
+        const SLInterfaceID ids[] = { SL_IID_VOLUME };
+        const SLboolean req[] = { SL_BOOLEAN_FALSE };
+
+        if( (*_saudio.backend.engine)->CreateOutputMix(_saudio.backend.engine, &_saudio.backend.output_mix_obj, 1, ids, req) != SL_RESULT_SUCCESS)
+        {
+            SOKOL_LOG("sokol_audio opensles: CreateOutputMix failed");
+            _saudio_backend_shutdown();
+            return false;
+        }
+        (*_saudio.backend.output_mix_obj)->Realize(_saudio.backend.output_mix_obj, SL_BOOLEAN_FALSE);
+
+        if((*_saudio.backend.output_mix_obj)->GetInterface(_saudio.backend.output_mix_obj, SL_IID_VOLUME, &_saudio.backend.output_mix_vol) != SL_RESULT_SUCCESS) {
+            SOKOL_LOG("sokol_audio opensles: GetInterface->OutputMixVol failed");
+        }
+    }
+
+    /* android buffer queue */
+    _saudio.backend.in_locator.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE;
+    _saudio.backend.in_locator.numBuffers = SAUDIO_NUM_BUFFERS;
+
+    /* data format */
+    SLDataFormat_PCM format;
+    format.formatType = SL_DATAFORMAT_PCM;
+    format.numChannels = _saudio.num_channels;
+    format.samplesPerSec = _saudio.sample_rate * 1000;
+    format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+    format.containerSize = 16;
+    format.endianness = SL_BYTEORDER_LITTLEENDIAN;
+
+    if (_saudio.num_channels == 2) {
+        format.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+    } else {
+        format.channelMask = SL_SPEAKER_FRONT_CENTER;
+    }
+
+    SLDataSource src;
+    src.pLocator = &_saudio.backend.in_locator;
+    src.pFormat = &format;
+
+    /* Output mix. */
+    _saudio.backend.out_locator.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+    _saudio.backend.out_locator.outputMix = _saudio.backend.output_mix_obj;
+        
+    _saudio.backend.dst_data_sink.pLocator = &_saudio.backend.out_locator;
+    _saudio.backend.dst_data_sink.pFormat = NULL;    
+
+    /* setup player */
+    {
+        const SLInterfaceID ids[] = { SL_IID_VOLUME, SL_IID_ANDROIDSIMPLEBUFFERQUEUE };
+        const SLboolean req[] = { SL_BOOLEAN_FALSE, SL_BOOLEAN_TRUE };
+
+        (*_saudio.backend.engine)->CreateAudioPlayer(_saudio.backend.engine, &_saudio.backend.player_obj, &src, &_saudio.backend.dst_data_sink, sizeof(ids) / sizeof(ids[0]), ids, req);
+        
+        (*_saudio.backend.player_obj)->Realize(_saudio.backend.player_obj, SL_BOOLEAN_FALSE);
+    
+        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_PLAY, &_saudio.backend.player);
+        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_VOLUME, &_saudio.backend.player_vol);
+
+        (*_saudio.backend.player_obj)->GetInterface(_saudio.backend.player_obj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, &_saudio.backend.player_buffer_queue);
+    }
+
+    /* begin */
+    {
+        const int buffer_size_bytes = sizeof(int16_t) * _saudio.num_channels * _saudio.buffer_frames;
+        (*_saudio.backend.player_buffer_queue)->Enqueue(_saudio.backend.player_buffer_queue, _saudio.backend.output_buffers[0], buffer_size_bytes);
+        _saudio.backend.active_buffer = (_saudio.backend.active_buffer + 1) % SAUDIO_NUM_BUFFERS;
+
+        (*_saudio.backend.player)->RegisterCallback(_saudio.backend.player, _saudio_opensles_play_cb, NULL);
+        (*_saudio.backend.player)->SetCallbackEventsMask(_saudio.backend.player, SL_PLAYEVENT_HEADATEND);
+        (*_saudio.backend.player)->SetPlayState(_saudio.backend.player, SL_PLAYSTATE_PLAYING);
+    }
+
+    /* create the buffer-streaming start thread */
+    if (0 != pthread_create(&_saudio.backend.thread, 0, _saudio_opensles_thread_fn, 0)) {
+        _saudio_backend_shutdown();
+        return false;
+    }
+
+    return true;
+}
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
 #else /* dummy backend */
 _SOKOL_PRIVATE bool _saudio_backend_init(void) { return false; };
 _SOKOL_PRIVATE void _saudio_backend_shutdown(void) { };