From 33ade008b5befa3a56871e61078d5a5d923bcbf8 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Thu, 17 Aug 2023 17:09:14 +0200 Subject: OpenSLES: compile as C++ --- src/android/cubeb-output-latency.h | 6 +- src/android/cubeb_media_library.h | 22 +- src/cubeb-jni.h | 8 + src/cubeb_opensl.c | 1802 ----------------------------------- src/cubeb_opensl.cpp | 1805 ++++++++++++++++++++++++++++++++++++ 5 files changed, 1832 insertions(+), 1811 deletions(-) delete mode 100644 src/cubeb_opensl.c create mode 100644 src/cubeb_opensl.cpp (limited to 'src') diff --git a/src/android/cubeb-output-latency.h b/src/android/cubeb-output-latency.h index 870a884..f8ee0e2 100644 --- a/src/android/cubeb-output-latency.h +++ b/src/android/cubeb-output-latency.h @@ -19,7 +19,7 @@ output_latency_function * cubeb_output_latency_load_method(int version) { output_latency_function * ol = NULL; - ol = calloc(1, sizeof(output_latency_function)); + ol = (output_latency_function*) calloc(1, sizeof(output_latency_function)); ol->version = version; @@ -61,6 +61,8 @@ cubeb_output_latency_unload_method(output_latency_function * ol) free(ol); } +extern "C" { + uint32_t cubeb_get_output_latency(output_latency_function * ol) { @@ -73,4 +75,6 @@ cubeb_get_output_latency(output_latency_function * ol) return cubeb_get_output_latency_from_media_library(ol->from_lib); } +} + #endif // _CUBEB_OUTPUT_LATENCY_H_ diff --git a/src/android/cubeb_media_library.h b/src/android/cubeb_media_library.h index 27fbc86..c7aecc5 100644 --- a/src/android/cubeb_media_library.h +++ b/src/android/cubeb_media_library.h @@ -1,9 +1,15 @@ +#include +#include +#include +#include #ifndef _CUBEB_MEDIA_LIBRARY_H_ #define _CUBEB_MEDIA_LIBRARY_H_ +typedef int32_t (*get_output_latency_ptr)(uint32_t * latency, int stream_type); + struct media_lib { void * libmedia; - int32_t (*get_output_latency)(uint32_t * latency, int stream_type); + get_output_latency_ptr get_output_latency; }; typedef struct media_lib media_lib; @@ -11,30 +17,30 @@ typedef struct media_lib media_lib; media_lib * cubeb_load_media_library() { - media_lib ml = {0}; + media_lib ml = {}; ml.libmedia = dlopen("libmedia.so", RTLD_LAZY); if (!ml.libmedia) { - return NULL; + return nullptr; } // Get the latency, in ms, from AudioFlinger. First, try the most recent // signature. status_t AudioSystem::getOutputLatency(uint32_t* latency, // audio_stream_type_t streamType) - ml.get_output_latency = dlsym( + ml.get_output_latency = (get_output_latency_ptr)dlsym( ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); if (!ml.get_output_latency) { // In case of failure, try the signature from legacy version. // status_t AudioSystem::getOutputLatency(uint32_t* latency, int streamType) ml.get_output_latency = - dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); + (get_output_latency_ptr)dlsym(ml.libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); if (!ml.get_output_latency) { - return NULL; + return nullptr; } } - media_lib * rv = NULL; - rv = calloc(1, sizeof(media_lib)); + media_lib * rv = nullptr; + rv = (media_lib*) calloc(1, sizeof(media_lib)); assert(rv); *rv = ml; return rv; diff --git a/src/cubeb-jni.h b/src/cubeb-jni.h index c4a712a..d63629f 100644 --- a/src/cubeb-jni.h +++ b/src/cubeb-jni.h @@ -3,6 +3,10 @@ typedef struct cubeb_jni cubeb_jni; +#ifdef __cplusplus +extern "C" { +#endif + cubeb_jni * cubeb_jni_init(); int @@ -10,4 +14,8 @@ cubeb_get_output_latency_from_jni(cubeb_jni * cubeb_jni_ptr); void cubeb_jni_destroy(cubeb_jni * cubeb_jni_ptr); +#ifdef __cplusplus +}; +#endif + #endif // _CUBEB_JNI_H_ diff --git a/src/cubeb_opensl.c b/src/cubeb_opensl.c deleted file mode 100644 index 7165446..0000000 --- a/src/cubeb_opensl.c +++ /dev/null @@ -1,1802 +0,0 @@ -/* - * Copyright © 2012 Mozilla Foundation - * - * This program is made available under an ISC-style license. See the - * accompanying file LICENSE for details. - */ -#undef NDEBUG -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(__ANDROID__) -#include "android/sles_definitions.h" -#include -#include -#include -#include -#include -#endif -#include "android/cubeb-output-latency.h" -#include "cubeb-internal.h" -#include "cubeb/cubeb.h" -#include "cubeb_android.h" -#include "cubeb_array_queue.h" -#include "cubeb_resampler.h" - -#if defined(__ANDROID__) -#ifdef LOG -#undef LOG -#endif -// #define LOGGING_ENABLED -#ifdef LOGGING_ENABLED -#define LOG(args...) \ - __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args) -#else -#define LOG(...) -#endif - -// #define TIMESTAMP_ENABLED -#ifdef TIMESTAMP_ENABLED -#define FILENAME \ - (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) -#define LOG_TS(args...) \ - __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \ - ##args) -#define TIMESTAMP(msg) \ - do { \ - struct timeval timestamp; \ - int ts_ret = gettimeofday(×tamp, NULL); \ - if (ts_ret == 0) { \ - LOG_TS("%lld: %s (%s %s:%d)", \ - timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \ - __FUNCTION__, FILENAME, __LINE__); \ - } else { \ - LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \ - __LINE__); \ - } \ - } while (0) -#else -#define TIMESTAMP(...) -#endif - -#define ANDROID_VERSION_GINGERBREAD_MR1 10 -#define ANDROID_VERSION_JELLY_BEAN 18 -#define ANDROID_VERSION_LOLLIPOP 21 -#define ANDROID_VERSION_MARSHMALLOW 23 -#define ANDROID_VERSION_N_MR1 25 -#endif - -#define DEFAULT_SAMPLE_RATE 48000 -#define DEFAULT_NUM_OF_FRAMES 480 - -static struct cubeb_ops const opensl_ops; - -struct cubeb { - struct cubeb_ops const * ops; - void * lib; - SLInterfaceID SL_IID_BUFFERQUEUE; - SLInterfaceID SL_IID_PLAY; -#if defined(__ANDROID__) - SLInterfaceID SL_IID_ANDROIDCONFIGURATION; - SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; -#endif - SLInterfaceID SL_IID_VOLUME; - SLInterfaceID SL_IID_RECORD; - SLObjectItf engObj; - SLEngineItf eng; - SLObjectItf outmixObj; - output_latency_function * p_output_latency_function; -}; - -#define NELEMS(A) (sizeof(A) / sizeof A[0]) -#define NBUFS 2 - -struct cubeb_stream { - /* Note: Must match cubeb_stream layout in cubeb.c. */ - cubeb * context; - void * user_ptr; - /**/ - pthread_mutex_t mutex; - SLObjectItf playerObj; - SLPlayItf play; - SLBufferQueueItf bufq; - SLVolumeItf volume; - void ** queuebuf; - uint32_t queuebuf_capacity; - int queuebuf_idx; - long queuebuf_len; - long bytespersec; - long framesize; - /* Total number of played frames. - * Synchronized by stream::mutex lock. */ - long written; - /* Flag indicating draining. Synchronized - * by stream::mutex lock. */ - int draining; - /* Flags to determine in/out.*/ - uint32_t input_enabled; - uint32_t output_enabled; - /* Recorder abstract object. */ - SLObjectItf recorderObj; - /* Recorder Itf for input capture. */ - SLRecordItf recorderItf; - /* Buffer queue for input capture. */ - SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; - /* Store input buffers. */ - void ** input_buffer_array; - /* The capacity of the array. - * On capture only can be small (4). - * On full duplex is calculated to - * store 1 sec of data buffers. */ - uint32_t input_array_capacity; - /* Current filled index of input buffer array. - * It is initiated to -1 indicating buffering - * have not started yet. */ - int input_buffer_index; - /* Length of input buffer.*/ - uint32_t input_buffer_length; - /* Input frame size */ - uint32_t input_frame_size; - /* Device sampling rate. If user rate is not - * accepted an compatible rate is set. If it is - * accepted this is equal to params.rate. */ - uint32_t input_device_rate; - /* Exchange input buffers between input - * and full duplex threads. */ - array_queue * input_queue; - /* Silent input buffer used on full duplex. */ - void * input_silent_buffer; - /* Number of input frames from the start of the stream*/ - uint32_t input_total_frames; - /* Flag to stop the execution of user callback and - * close all working threads. Synchronized by - * stream::mutex lock. */ - uint32_t shutdown; - /* Store user callback. */ - cubeb_data_callback data_callback; - /* Store state callback. */ - cubeb_state_callback state_callback; - - cubeb_resampler * resampler; - unsigned int user_output_rate; - unsigned int output_configured_rate; - unsigned int buffer_size_frames; - // Audio output latency used in cubeb_stream_get_position(). - unsigned int output_latency_ms; - int64_t lastPosition; - int64_t lastPositionTimeStamp; - int64_t lastCompensativePosition; - int voice_input; - int voice_output; -}; - -/* Forward declaration. */ -static int -opensl_stop_player(cubeb_stream * stm); -static int -opensl_stop_recorder(cubeb_stream * stm); - -static int -opensl_get_draining(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && - "get_draining: mutex should be locked but it's not."); -#endif - return stm->draining; -} - -static void -opensl_set_draining(cubeb_stream * stm, int value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set draining try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && - "set_draining: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->draining = value; -} - -static void -opensl_notify_drained(cubeb_stream * stm) -{ - assert(stm); - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->play) { - LOG("stop player in play_callback"); - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - } -} - -static uint32_t -opensl_get_shutdown(cubeb_stream * stm) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - assert((r == EDEADLK || r == EBUSY) && - "get_shutdown: mutex should be locked but it's not."); -#endif - return stm->shutdown; -} - -static void -opensl_set_shutdown(cubeb_stream * stm, uint32_t value) -{ -#ifdef DEBUG - int r = pthread_mutex_trylock(&stm->mutex); - LOG("set shutdown try r = %d", r); - assert((r == EDEADLK || r == EBUSY) && - "set_shutdown: mutex should be locked but it's not."); -#endif - assert(value == 0 || value == 1); - stm->shutdown = value; -} - -static void -play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) -{ - cubeb_stream * stm = user_ptr; - assert(stm); - switch (event) { - case SL_PLAYEVENT_HEADATMARKER: - opensl_notify_drained(stm); - break; - default: - break; - } -} - -static void -recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event) -{ - cubeb_stream * stm = pContext; - assert(stm); - - if (event == SL_RECORDEVENT_HEADATMARKER) { - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (draining) { - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); - if (stm->recorderItf) { - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - } - if (stm->play) { - r = opensl_stop_player(stm); - assert(r == CUBEB_OK); - } - } - } -} - -static void -bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) -{ - cubeb_stream * stm = user_ptr; - assert(stm); - SLBufferQueueState state; - SLresult res; - long written = 0; - - res = (*stm->bufq)->GetState(stm->bufq, &state); - assert(res == SL_RESULT_SUCCESS); - - if (state.count > 1) { - return; - } - - uint8_t * buf = stm->queuebuf[stm->queuebuf_idx]; - written = 0; - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - if (!draining && !shutdown) { - written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf, - stm->queuebuf_len / stm->framesize); - LOG("bufferqueue_callback: resampler fill returned %ld frames", written); - if (written < 0 || written * stm->framesize > stm->queuebuf_len) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - return; - } - } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - assert(stm->queuebuf_len >= written * stm->framesize); - memset(buf + written * stm->framesize, 0, - stm->queuebuf_len - written * stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - - if (written > 0) { - pthread_mutex_lock(&stm->mutex); - stm->written += written; - pthread_mutex_unlock(&stm->mutex); - } - - if (!draining && written * stm->framesize < stm->queuebuf_len) { - LOG("bufferqueue_callback draining"); - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = - INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (written_duration == 0) { - // since we didn't write any sample, it's not possible to reach the marker - // time and trigger the callback. We should initiative notify drained. - opensl_notify_drained(stm); - } else { - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play) - ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); - } - return; - } -} - -static int -opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer) -{ - assert(stm); - - int current_index = stm->input_buffer_index; - void * last_buffer = NULL; - - if (current_index < 0) { - // This is the first enqueue - current_index = 0; - } else { - // The current index hold the last filled buffer get it before advance - // index. - last_buffer = stm->input_buffer_array[current_index]; - // Advance to get next available buffer - current_index = (current_index + 1) % stm->input_array_capacity; - } - // enqueue next empty buffer to be filled by the recorder - SLresult res = (*stm->recorderBufferQueueItf) - ->Enqueue(stm->recorderBufferQueueItf, - stm->input_buffer_array[current_index], - stm->input_buffer_length); - if (res != SL_RESULT_SUCCESS) { - LOG("Enqueue recorder failed. Error code: %lu", res); - return CUBEB_ERROR; - } - // All good, update buffer and index. - stm->input_buffer_index = current_index; - if (last_filled_buffer) { - *last_filled_buffer = last_buffer; - } - return CUBEB_OK; -} - -// input data callback -void -recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - uint32_t shutdown = opensl_get_shutdown(stm); - int draining = opensl_get_draining(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf - // page 184, on transition to the SL_RECORDSTATE_STOPPED state, - // the application should continue to enqueue buffers onto the queue - // to retrieve the residual recorded data in the system. - r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - // Fill resampler with last input - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long got = cubeb_resampler_fill(stm->resampler, input_buffer, - &input_frame_count, NULL, 0); - // Error case - if (got < 0 || got > input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - r = opensl_stop_recorder(stm); - assert(r == CUBEB_OK); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - } - - // Advance total stream frames - stm->input_total_frames += got; - - if (got < input_frame_count) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - int64_t duration = - INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; - (*stm->recorderItf) - ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); - return; - } -} - -void -recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) -{ - assert(context); - cubeb_stream * stm = context; - assert(stm->recorderBufferQueueItf); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - /* On draining and shutdown the recorder should have been stoped from - * the one set the flags. Accordint to the doc, on transition to - * the SL_RECORDSTATE_STOPPED state, the application should - * continue to enqueue buffers onto the queue to retrieve the residual - * recorded data in the system. */ - LOG("Input shutdown %d or drain %d", shutdown, draining); - int r = opensl_enqueue_recorder(stm, NULL); - assert(r == CUBEB_OK); - return; - } - - // Enqueue next available buffer and get the last filled buffer. - void * input_buffer = NULL; - r = opensl_enqueue_recorder(stm, &input_buffer); - assert(r == CUBEB_OK); - assert(input_buffer); - - assert(stm->input_queue); - r = array_queue_push(stm->input_queue, input_buffer); - if (r == -1) { - LOG("Input queue is full, drop input ..."); - return; - } - - LOG("Input pushed in the queue, input array %zu", - array_queue_get_size(stm->input_queue)); -} - -static void -player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) -{ - TIMESTAMP("ENTER"); - cubeb_stream * stm = user_ptr; - assert(stm); - SLresult res; - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int draining = opensl_get_draining(stm); - uint32_t shutdown = opensl_get_shutdown(stm); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Get output - void * output_buffer = NULL; - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - output_buffer = stm->queuebuf[stm->queuebuf_idx]; - // Advance the output buffer queue index - stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (shutdown || draining) { - LOG("Shutdown/draining, send silent"); - // Set silent on buffer - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Get input. - void * input_buffer = array_queue_pop(stm->input_queue); - long input_frame_count = stm->input_buffer_length / stm->input_frame_size; - long frames_needed = stm->queuebuf_len / stm->framesize; - if (!input_buffer) { - LOG("Input hole set silent input buffer"); - input_buffer = stm->input_silent_buffer; - } - - long written = 0; - // Trigger user callback through resampler - written = - cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count, - output_buffer, frames_needed); - - LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written, - frames_needed, array_queue_get_size(stm->input_queue)); - - if (written < 0 || written > frames_needed) { - // Error case - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - opensl_stop_player(stm); - opensl_stop_recorder(stm); - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); - memset(output_buffer, 0, stm->queuebuf_len); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - return; - } - - // Advance total out written frames counter - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - stm->written += written; - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (written < frames_needed) { - r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - int64_t written_duration = - INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; - opensl_set_draining(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf - // to make sure all the data has been processed. - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); - } - - // Keep sending silent data even in draining mode to prevent the audio - // back-end from being stopped automatically by OpenSL/ES. - memset((uint8_t *)output_buffer + written * stm->framesize, 0, - stm->queuebuf_len - written * stm->framesize); - - // Enqueue data in player buffer queue - res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); - assert(res == SL_RESULT_SUCCESS); - TIMESTAMP("EXIT"); -} - -static void -opensl_destroy(cubeb * ctx); - -#if defined(__ANDROID__) -#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) -typedef int(system_property_get)(const char *, char *); - -static int -wrap_system_property_get(const char * name, char * value) -{ - void * libc = dlopen("libc.so", RTLD_LAZY); - if (!libc) { - LOG("Failed to open libc.so"); - return -1; - } - system_property_get * func = - (system_property_get *)dlsym(libc, "__system_property_get"); - int ret = -1; - if (func) { - ret = func(name, value); - } - dlclose(libc); - return ret; -} -#endif - -static int -get_android_version(void) -{ - char version_string[PROP_VALUE_MAX]; - - memset(version_string, 0, PROP_VALUE_MAX); - -#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) - int len = wrap_system_property_get("ro.build.version.sdk", version_string); -#else - int len = __system_property_get("ro.build.version.sdk", version_string); -#endif - if (len <= 0) { - LOG("Failed to get Android version!\n"); - return len; - } - - int version = (int)strtol(version_string, NULL, 10); - LOG("Android version %d", version); - return version; -} -#endif - -/*static*/ int -opensl_init(cubeb ** context, char const * context_name) -{ - cubeb * ctx; - -#if defined(__ANDROID__) - int android_version = get_android_version(); - if (android_version > 0 && - android_version <= ANDROID_VERSION_GINGERBREAD_MR1) { - // Don't even attempt to run on Gingerbread and lower - return CUBEB_ERROR; - } -#endif - - *context = NULL; - - ctx = calloc(1, sizeof(*ctx)); - assert(ctx); - - ctx->ops = &opensl_ops; - - ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); - if (!ctx->lib) { - free(ctx); - return CUBEB_ERROR; - } - - typedef SLresult (*slCreateEngine_t)( - SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32, - const SLInterfaceID *, const SLboolean *); - slCreateEngine_t f_slCreateEngine = - (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); - SLInterfaceID SL_IID_ENGINE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); - SLInterfaceID SL_IID_OUTPUTMIX = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); - ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME"); - ctx->SL_IID_BUFFERQUEUE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); -#if defined(__ANDROID__) - ctx->SL_IID_ANDROIDCONFIGURATION = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); - ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = - *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); -#endif - ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); - ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); - - if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX || - !ctx->SL_IID_BUFFERQUEUE || -#if defined(__ANDROID__) - !ctx->SL_IID_ANDROIDCONFIGURATION || - !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || -#endif - !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; - - SLresult res; - res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, NULL, NULL); - - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; - const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; - res = - (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - opensl_destroy(ctx); - return CUBEB_ERROR; - } - - ctx->p_output_latency_function = - cubeb_output_latency_load_method(android_version); - if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) { - LOG("Warning: output latency is not available, cubeb_stream_get_position() " - "is not supported"); - } - - *context = ctx; - - LOG("Cubeb init (%p) success", ctx); - return CUBEB_OK; -} - -static char const * -opensl_get_backend_id(cubeb * ctx) -{ - return "opensl"; -} - -static int -opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) -{ - assert(ctx && max_channels); - /* The android mixer handles up to two channels, see - http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 - */ - *max_channels = 2; - - return CUBEB_OK; -} - -static void -opensl_destroy(cubeb * ctx) -{ - if (ctx->outmixObj) { - (*ctx->outmixObj)->Destroy(ctx->outmixObj); - } - if (ctx->engObj) { - (*ctx->engObj)->Destroy(ctx->engObj); - } - dlclose(ctx->lib); - if (ctx->p_output_latency_function) - cubeb_output_latency_unload_method(ctx->p_output_latency_function); - free(ctx); -} - -static void -opensl_stream_destroy(cubeb_stream * stm); - -#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) -static int -opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, - cubeb_stream_params * params) -{ - assert(format); - assert(params); - - format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; - format->numChannels = params->channels; - // sampleRate is in milliHertz - format->sampleRate = params->rate * 1000; - format->channelMask = params->channels == 1 - ? SL_SPEAKER_FRONT_CENTER - : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - case CUBEB_SAMPLE_FLOAT32LE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_FLOAT32BE: - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; - format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - return CUBEB_OK; -} -#endif - -static int -opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) -{ - assert(format); - assert(params); - - format->formatType = SL_DATAFORMAT_PCM; - format->numChannels = params->channels; - // samplesPerSec is in milliHertz - format->samplesPerSec = params->rate * 1000; - format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; - format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; - format->channelMask = params->channels == 1 - ? SL_SPEAKER_FRONT_CENTER - : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; - - switch (params->format) { - case CUBEB_SAMPLE_S16LE: - format->endianness = SL_BYTEORDER_LITTLEENDIAN; - break; - case CUBEB_SAMPLE_S16BE: - format->endianness = SL_BYTEORDER_BIGENDIAN; - break; - default: - return CUBEB_ERROR_INVALID_FORMAT; - } - return CUBEB_OK; -} - -static int -opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) -{ - assert(stm); - assert(params); - - SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; - lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - lDataLocatorOut.numBuffers = NBUFS; - - SLDataFormat_PCM lDataFormat; - int r = opensl_set_format(&lDataFormat, params); - if (r != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - - /* For now set device rate to params rate. */ - stm->input_device_rate = params->rate; - - SLDataSink lDataSink; - lDataSink.pLocator = &lDataLocatorOut; - lDataSink.pFormat = &lDataFormat; - - SLDataLocator_IODevice lDataLocatorIn; - lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE; - lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT; - lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; - lDataLocatorIn.device = NULL; - - SLDataSource lDataSource; - lDataSource.pLocator = &lDataLocatorIn; - lDataSource.pFormat = NULL; - - const SLInterfaceID lSoundRecorderIIDs[] = { - stm->context->SL_IID_RECORD, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - stm->context->SL_IID_ANDROIDCONFIGURATION}; - - const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, - SL_BOOLEAN_TRUE}; - // create the audio recorder abstract object - SLresult res = (*stm->context->eng) - ->CreateAudioRecorder( - stm->context->eng, &stm->recorderObj, &lDataSource, - &lDataSink, NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, lSoundRecorderReqs); - // Sample rate not supported. Try again with default sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED) { - if (stm->output_enabled && stm->output_configured_rate != 0) { - // Set the same with the player. Since there is no - // api for input device this is a safe choice. - stm->input_device_rate = stm->output_configured_rate; - } else { - // The output preferred rate is used for an input only scenario. - // The default rate expected to be supported from all android devices. - stm->input_device_rate = DEFAULT_SAMPLE_RATE; - } - lDataFormat.samplesPerSec = stm->input_device_rate * 1000; - res = (*stm->context->eng) - ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj, - &lDataSource, &lDataSink, - NELEMS(lSoundRecorderIIDs), - lSoundRecorderIIDs, lSoundRecorderReqs); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - } - - if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { - SLAndroidConfigurationItf recorderConfig; - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &recorderConfig); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get the android configuration interface for recorder. " - "Error " - "code: %lu", - res); - return CUBEB_ERROR; - } - - // Voice recognition is the lowest latency, according to the docs. Camcorder - // uses a microphone that is in the same direction as the camera. - SLint32 streamType = stm->voice_input - ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION - : SL_ANDROID_RECORDING_PRESET_CAMCORDER; - - res = - (*recorderConfig) - ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, - &streamType, sizeof(SLint32)); - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set the android configuration to VOICE for the recorder. " - "Error code: %lu", - res); - return CUBEB_ERROR; - } - } - // realize the audio recorder - res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the record interface - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD, - &stm->recorderItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->recorderItf) - ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder marker callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); - - res = (*stm->recorderItf) - ->SetCallbackEventsMask(stm->recorderItf, - (SLuint32)SL_RECORDEVENT_HEADATMARKER); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); - return CUBEB_ERROR; - } - // get the simple android buffer queue interface - res = (*stm->recorderObj) - ->GetInterface(stm->recorderObj, - stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, - &stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get recorder (android) buffer queue interface. Error code: " - "%lu", - res); - return CUBEB_ERROR; - } - - // register callback on record (input) buffer queue - slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback; - if (stm->output_enabled) { - // Register full duplex callback instead. - rec_callback = recorder_fullduplex_callback; - } - res = (*stm->recorderBufferQueueItf) - ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register recorder buffer queue callback. Error code: %lu", - res); - return CUBEB_ERROR; - } - - // Calculate length of input buffer according to requested latency - stm->input_frame_size = params->channels * sizeof(int16_t); - stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames); - - // Calculate the capacity of input array - stm->input_array_capacity = NBUFS; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->input_array_capacity = - 1 * stm->input_device_rate / stm->input_buffer_length; - } - // Allocate input array - stm->input_buffer_array = - (void **)calloc(1, sizeof(void *) * stm->input_array_capacity); - // Buffering has not started yet. - stm->input_buffer_index = -1; - // Prepare input buffers - for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { - stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length); - } - - // On full duplex allocate input queue and silent buffer - if (stm->output_enabled) { - stm->input_queue = array_queue_create(stm->input_array_capacity); - assert(stm->input_queue); - stm->input_silent_buffer = calloc(1, stm->input_buffer_length); - assert(stm->input_silent_buffer); - } - - // Enqueue buffer to start rolling once recorder started - r = opensl_enqueue_recorder(stm, NULL); - if (r != CUBEB_OK) { - return r; - } - - LOG("Cubeb stream init recorder success"); - - return CUBEB_OK; -} - -static int -opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) -{ - assert(stm); - assert(params); - - stm->user_output_rate = params->rate; - if (params->format == CUBEB_SAMPLE_S16NE || - params->format == CUBEB_SAMPLE_S16BE) { - stm->framesize = params->channels * sizeof(int16_t); - } else if (params->format == CUBEB_SAMPLE_FLOAT32NE || - params->format == CUBEB_SAMPLE_FLOAT32BE) { - stm->framesize = params->channels * sizeof(float); - } - stm->lastPosition = -1; - stm->lastPositionTimeStamp = 0; - stm->lastCompensativePosition = -1; - - void * format = NULL; - SLuint32 * format_sample_rate = NULL; - -#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) - SLAndroidDataFormat_PCM_EX pcm_ext_format; - if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) { - if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - format = &pcm_ext_format; - format_sample_rate = &pcm_ext_format.sampleRate; - } -#endif - - SLDataFormat_PCM pcm_format; - if (!format) { - if (opensl_set_format(&pcm_format, params) != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } - format = &pcm_format; - format_sample_rate = &pcm_format.samplesPerSec; - } - - SLDataLocator_BufferQueue loc_bufq; - loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; - loc_bufq.numBuffers = NBUFS; - SLDataSource source; - source.pLocator = &loc_bufq; - source.pFormat = format; - - SLDataLocator_OutputMix loc_outmix; - loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = stm->context->outmixObj; - SLDataSink sink; - sink.pLocator = &loc_outmix; - sink.pFormat = NULL; - -#if defined(__ANDROID__) - const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE, - stm->context->SL_IID_VOLUME, - stm->context->SL_IID_ANDROIDCONFIGURATION}; - const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; -#else - const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; - const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; -#endif - assert(NELEMS(ids) == NELEMS(req)); - - uint32_t preferred_sampling_rate = stm->user_output_rate; - SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; - if (preferred_sampling_rate) { - res = (*stm->context->eng) - ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, - &sink, NELEMS(ids), ids, req); - } - - // Sample rate not supported? Try again with primary sample rate! - if (res == SL_RESULT_CONTENT_UNSUPPORTED && - preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { - preferred_sampling_rate = DEFAULT_SAMPLE_RATE; - *format_sample_rate = preferred_sampling_rate * 1000; - res = (*stm->context->eng) - ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, - &sink, NELEMS(ids), ids, req); - } - - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create audio player. Error code: %lu", res); - return CUBEB_ERROR; - } - - stm->output_configured_rate = preferred_sampling_rate; - stm->bytespersec = stm->output_configured_rate * stm->framesize; - stm->queuebuf_len = stm->framesize * stm->buffer_size_frames; - - // Calculate the capacity of input array - stm->queuebuf_capacity = NBUFS; - // Allocate input arrays - stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity); - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - stm->queuebuf[i] = calloc(1, stm->queuebuf_len); - assert(stm->queuebuf[i]); - } - - SLAndroidConfigurationItf playerConfig = NULL; - - if (get_android_version() >= ANDROID_VERSION_N_MR1) { - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, - stm->context->SL_IID_ANDROIDCONFIGURATION, - &playerConfig); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get Android configuration interface. Error code: %lu", - res); - return CUBEB_ERROR; - } - - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - if (stm->voice_output) { - streamType = SL_ANDROID_STREAM_VOICE; - } - res = (*playerConfig) - ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, - &streamType, sizeof(streamType)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android configuration to %d Error code: %lu", - streamType, res); - } - - SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; - if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { - performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; - } - - res = (*playerConfig) - ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE, - &performanceMode, sizeof(performanceMode)); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android performance mode to %d Error code: %lu. This " - "is" - " not fatal", - performanceMode, res); - } - } - - res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize player object. Error code: %lu", res); - return CUBEB_ERROR; - } - - // There are two ways of getting the audio output latency: - // - a configuration value, only available on some devices (notably devices - // running FireOS) - // - A Java method, that we call using JNI. - // - // The first method is prefered, if available, because it can account for more - // latency causes, and is more precise. - - // Latency has to be queried after the realization of the interface, when - // using SL_IID_ANDROIDCONFIGURATION. - SLuint32 audioLatency = 0; - SLuint32 paramSize = sizeof(SLuint32); - // The reported latency is in milliseconds. - if (playerConfig) { - res = (*playerConfig) - ->GetConfiguration(playerConfig, - (const SLchar *)"androidGetAudioLatency", - ¶mSize, &audioLatency); - if (res == SL_RESULT_SUCCESS) { - LOG("Got playback latency using android configuration extension"); - stm->output_latency_ms = audioLatency; - } - } - // `playerConfig` is available, but the above failed, or `playerConfig` is not - // available. In both cases, we need to acquire the output latency by an other - // mean. - if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) { - if (cubeb_output_latency_method_is_loaded( - stm->context->p_output_latency_function)) { - LOG("Got playback latency using JNI"); - stm->output_latency_ms = - cubeb_get_output_latency(stm->context->p_output_latency_function); - } else { - LOG("No alternate latency querying method loaded, A/V sync will be off."); - stm->output_latency_ms = 0; - } - } - - LOG("Audio output latency: %dms", stm->output_latency_ms); - - res = - (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get play interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE, - &stm->bufq); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get bufferqueue interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->playerObj) - ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME, - &stm->volume); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get volume interface. Error code: %lu", res); - return CUBEB_ERROR; - } - - res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register play callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - // Work around wilhelm/AudioTrack badness, bug 1221228 - (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0); - - res = (*stm->play) - ->SetCallbackEventsMask(stm->play, - (SLuint32)SL_PLAYEVENT_HEADATMARKER); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set headatmarker event mask. Error code: %lu", res); - return CUBEB_ERROR; - } - - slBufferQueueCallback player_callback = bufferqueue_callback; - if (stm->input_enabled) { - player_callback = player_fullduplex_callback; - } - res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register bufferqueue callback. Error code: %lu", res); - return CUBEB_ERROR; - } - - { - // Enqueue a silent frame so once the player becomes playing, the frame - // will be consumed and kick off the buffer queue callback. - // Note the duration of a single frame is less than 1ms. We don't bother - // adjusting the playback position. - uint8_t * buf = stm->queuebuf[stm->queuebuf_idx++]; - memset(buf, 0, stm->framesize); - res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize); - assert(res == SL_RESULT_SUCCESS); - } - - LOG("Cubeb stream init playback success"); - return CUBEB_OK; -} - -static int -opensl_validate_stream_param(cubeb_stream_params * stream_params) -{ - if ((stream_params && - (stream_params->channels < 1 || stream_params->channels > 32))) { - return CUBEB_ERROR_INVALID_FORMAT; - } - if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { - LOG("Loopback is not supported"); - return CUBEB_ERROR_NOT_SUPPORTED; - } - return CUBEB_OK; -} - -int -has_pref_set(cubeb_stream_params * input_params, - cubeb_stream_params * output_params, cubeb_stream_prefs pref) -{ - return (input_params && input_params->prefs & pref) || - (output_params && output_params->prefs & pref); -} - -static int -opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, - char const * stream_name, cubeb_devid input_device, - cubeb_stream_params * input_stream_params, - cubeb_devid output_device, - cubeb_stream_params * output_stream_params, - unsigned int latency_frames, - cubeb_data_callback data_callback, - cubeb_state_callback state_callback, void * user_ptr) -{ - cubeb_stream * stm; - - assert(ctx); - if (input_device || output_device) { - LOG("Device selection is not supported in Android. The default will be " - "used"); - } - - *stream = NULL; - - int r = opensl_validate_stream_param(output_stream_params); - if (r != CUBEB_OK) { - LOG("Output stream params not valid"); - return r; - } - r = opensl_validate_stream_param(input_stream_params); - if (r != CUBEB_OK) { - LOG("Input stream params not valid"); - return r; - } - - stm = calloc(1, sizeof(*stm)); - assert(stm); - - stm->context = ctx; - stm->data_callback = data_callback; - stm->state_callback = state_callback; - stm->user_ptr = user_ptr; - stm->buffer_size_frames = - latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; - stm->input_enabled = (input_stream_params) ? 1 : 0; - stm->output_enabled = (output_stream_params) ? 1 : 0; - stm->shutdown = 1; - stm->voice_input = - has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE); - stm->voice_output = - has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE); - - LOG("cubeb stream prefs: voice_input: %s voice_output: %s", - stm->voice_input ? "true" : "false", - stm->voice_output ? "true" : "false"); - -#ifdef DEBUG - pthread_mutexattr_t attr; - pthread_mutexattr_init(&attr); - pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); - r = pthread_mutex_init(&stm->mutex, &attr); -#else - r = pthread_mutex_init(&stm->mutex, NULL); -#endif - assert(r == 0); - - if (output_stream_params) { - LOG("Playback params: Rate %d, channels %d, format %d, latency in frames " - "%d.", - output_stream_params->rate, output_stream_params->channels, - output_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_playback(stm, output_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - if (input_stream_params) { - LOG("Capture params: Rate %d, channels %d, format %d, latency in frames " - "%d.", - input_stream_params->rate, input_stream_params->channels, - input_stream_params->format, stm->buffer_size_frames); - r = opensl_configure_capture(stm, input_stream_params); - if (r != CUBEB_OK) { - opensl_stream_destroy(stm); - return r; - } - } - - /* Configure resampler*/ - uint32_t target_sample_rate; - if (input_stream_params) { - target_sample_rate = input_stream_params->rate; - } else { - assert(output_stream_params); - target_sample_rate = output_stream_params->rate; - } - - // Use the actual configured rates for input - // and output. - cubeb_stream_params input_params; - if (input_stream_params) { - input_params = *input_stream_params; - input_params.rate = stm->input_device_rate; - } - cubeb_stream_params output_params; - if (output_stream_params) { - output_params = *output_stream_params; - output_params.rate = stm->output_configured_rate; - } - - stm->resampler = cubeb_resampler_create( - stm, input_stream_params ? &input_params : NULL, - output_stream_params ? &output_params : NULL, target_sample_rate, - data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, - CUBEB_RESAMPLER_RECLOCK_NONE); - if (!stm->resampler) { - LOG("Failed to create resampler"); - opensl_stream_destroy(stm); - return CUBEB_ERROR; - } - - *stream = stm; - LOG("Cubeb stream (%p) init success", stm); - return CUBEB_OK; -} - -static int -opensl_start_player(cubeb_stream * stm) -{ - assert(stm->playerObj); - SLuint32 playerState; - (*stm->playerObj)->GetState(stm->playerObj, &playerState); - if (playerState == SL_OBJECT_STATE_REALIZED) { - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to start player. Error code: %lu", res); - return CUBEB_ERROR; - } - } - return CUBEB_OK; -} - -static int -opensl_start_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - SLuint32 recorderState; - (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); - if (recorderState == SL_OBJECT_STATE_REALIZED) { - SLresult res = - (*stm->recorderItf) - ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to start recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - } - return CUBEB_OK; -} - -static int -opensl_stream_start(cubeb_stream * stm) -{ - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 0); - opensl_set_draining(stm, 0); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_start_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_start_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); - LOG("Cubeb stream (%p) started", stm); - return CUBEB_OK; -} - -static int -opensl_stop_player(cubeb_stream * stm) -{ - assert(stm->playerObj); - assert(stm->shutdown || stm->draining); - - SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop player. Error code: %lu", res); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stop_recorder(cubeb_stream * stm) -{ - assert(stm->recorderObj); - assert(stm->shutdown || stm->draining); - - SLresult res = (*stm->recorderItf) - ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to stop recorder. Error code: %lu", res); - return CUBEB_ERROR; - } - - return CUBEB_OK; -} - -static int -opensl_stream_stop(cubeb_stream * stm) -{ - assert(stm); - - int r = pthread_mutex_lock(&stm->mutex); - assert(r == 0); - opensl_set_shutdown(stm, 1); - r = pthread_mutex_unlock(&stm->mutex); - assert(r == 0); - - if (stm->playerObj) { - r = opensl_stop_player(stm); - if (r != CUBEB_OK) { - return r; - } - } - - if (stm->recorderObj) { - int r = opensl_stop_recorder(stm); - if (r != CUBEB_OK) { - return r; - } - } - - stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); - LOG("Cubeb stream (%p) stopped", stm); - return CUBEB_OK; -} - -static int -opensl_destroy_recorder(cubeb_stream * stm) -{ - assert(stm); - assert(stm->recorderObj); - - if (stm->recorderBufferQueueItf) { - SLresult res = - (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); - if (res != SL_RESULT_SUCCESS) { - LOG("Failed to clear recorder buffer queue. Error code: %lu", res); - return CUBEB_ERROR; - } - stm->recorderBufferQueueItf = NULL; - for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { - free(stm->input_buffer_array[i]); - } - } - - (*stm->recorderObj)->Destroy(stm->recorderObj); - stm->recorderObj = NULL; - stm->recorderItf = NULL; - - if (stm->input_queue) { - array_queue_destroy(stm->input_queue); - } - free(stm->input_silent_buffer); - - return CUBEB_OK; -} - -static void -opensl_stream_destroy(cubeb_stream * stm) -{ - assert(stm->draining || stm->shutdown); - - // If we're still draining at stream destroy time, pause the streams now so we - // can destroy them safely. - if (stm->draining) { - opensl_stream_stop(stm); - } - // Sleep for 10ms to give active streams time to pause so that no further - // buffer callbacks occur. Inspired by the same workaround (sleepBeforeClose) - // in liboboe. - usleep(10 * 1000); - - if (stm->playerObj) { - (*stm->playerObj)->Destroy(stm->playerObj); - stm->playerObj = NULL; - stm->play = NULL; - stm->bufq = NULL; - for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { - free(stm->queuebuf[i]); - } - } - - if (stm->recorderObj) { - int r = opensl_destroy_recorder(stm); - assert(r == CUBEB_OK); - } - - if (stm->resampler) { - cubeb_resampler_destroy(stm->resampler); - } - - pthread_mutex_destroy(&stm->mutex); - - LOG("Cubeb stream (%p) destroyed", stm); - free(stm); -} - -static int -opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) -{ - SLmillisecond msec; - uint32_t compensation_msec = 0; - SLresult res; - - res = (*stm->play)->GetPosition(stm->play, &msec); - if (res != SL_RESULT_SUCCESS) - return CUBEB_ERROR; - - struct timespec t; - clock_gettime(CLOCK_MONOTONIC, &t); - if (stm->lastPosition == msec) { - compensation_msec = - (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / - 1000000; - } else { - stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec; - stm->lastPosition = msec; - } - - uint64_t samplerate = stm->user_output_rate; - uint32_t output_latency = stm->output_latency_ms; - - pthread_mutex_lock(&stm->mutex); - int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / - stm->output_configured_rate; - pthread_mutex_unlock(&stm->mutex); - assert(maximum_position >= 0); - - if (msec > output_latency) { - int64_t unadjusted_position; - if (stm->lastCompensativePosition > msec + compensation_msec) { - // Over compensation, use lastCompensativePosition. - unadjusted_position = - samplerate * (stm->lastCompensativePosition - output_latency) / 1000; - } else { - unadjusted_position = - samplerate * (msec - output_latency + compensation_msec) / 1000; - stm->lastCompensativePosition = msec + compensation_msec; - } - *position = unadjusted_position < maximum_position ? unadjusted_position - : maximum_position; - } else { - *position = 0; - } - return CUBEB_OK; -} - -static int -opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) -{ - assert(stm); - assert(latency); - - uint32_t stream_latency_frames = - stm->user_output_rate * stm->output_latency_ms / 1000; - - return stream_latency_frames + cubeb_resampler_latency(stm->resampler); -} - -int -opensl_stream_set_volume(cubeb_stream * stm, float volume) -{ - SLresult res; - SLmillibel max_level, millibels; - float unclamped_millibels; - - res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level); - - if (res != SL_RESULT_SUCCESS) { - return CUBEB_ERROR; - } - - /* millibels are 100*dB, so the conversion from the volume's linear amplitude - * is 100 * 20 * log(volume). However we clamp the resulting value before - * passing it to lroundf() in order to prevent it from silently returning an - * erroneous value when the unclamped value exceeds the size of a long. */ - unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f)); - unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN); - unclamped_millibels = fminf(unclamped_millibels, max_level); - - millibels = lroundf(unclamped_millibels); - - res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels); - - if (res != SL_RESULT_SUCCESS) { - return CUBEB_ERROR; - } - return CUBEB_OK; -} - -static struct cubeb_ops const opensl_ops = { - .init = opensl_init, - .get_backend_id = opensl_get_backend_id, - .get_max_channel_count = opensl_get_max_channel_count, - .get_min_latency = NULL, - .get_preferred_sample_rate = NULL, - .enumerate_devices = NULL, - .device_collection_destroy = NULL, - .destroy = opensl_destroy, - .stream_init = opensl_stream_init, - .stream_destroy = opensl_stream_destroy, - .stream_start = opensl_stream_start, - .stream_stop = opensl_stream_stop, - .stream_get_position = opensl_stream_get_position, - .stream_get_latency = opensl_stream_get_latency, - .stream_get_input_latency = NULL, - .stream_set_volume = opensl_stream_set_volume, - .stream_set_name = NULL, - .stream_get_current_device = NULL, - .stream_device_destroy = NULL, - .stream_register_device_changed_callback = NULL, - .register_device_collection_changed = NULL}; diff --git a/src/cubeb_opensl.cpp b/src/cubeb_opensl.cpp new file mode 100644 index 0000000..d44f86c --- /dev/null +++ b/src/cubeb_opensl.cpp @@ -0,0 +1,1805 @@ +/* + * Copyright © 2012 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__ANDROID__) +#include "android/sles_definitions.h" +#include +#include +#include +#include +#include +#endif +#include "android/cubeb-output-latency.h" +#include "cubeb-internal.h" +#include "cubeb/cubeb.h" +#include "cubeb_android.h" +#include "cubeb_array_queue.h" +#include "cubeb_resampler.h" + +#if defined(__ANDROID__) +#ifdef LOG +#undef LOG +#endif +// #define LOGGING_ENABLED +#ifdef LOGGING_ENABLED +#define LOG(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL", ##args) +#else +#define LOG(...) +#endif + +// #define TIMESTAMP_ENABLED +#ifdef TIMESTAMP_ENABLED +#define FILENAME \ + (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) +#define LOG_TS(args...) \ + __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL ES: Timestamp(usec)", \ + ##args) +#define TIMESTAMP(msg) \ + do { \ + struct timeval timestamp; \ + int ts_ret = gettimeofday(×tamp, NULL); \ + if (ts_ret == 0) { \ + LOG_TS("%lld: %s (%s %s:%d)", \ + timestamp.tv_sec * 1000000LL + timestamp.tv_usec, msg, \ + __FUNCTION__, FILENAME, __LINE__); \ + } else { \ + LOG_TS("Error: %s (%s %s:%d) - %s", msg, __FUNCTION__, FILENAME, \ + __LINE__); \ + } \ + } while (0) +#else +#define TIMESTAMP(...) +#endif + +#define ANDROID_VERSION_GINGERBREAD_MR1 10 +#define ANDROID_VERSION_JELLY_BEAN 18 +#define ANDROID_VERSION_LOLLIPOP 21 +#define ANDROID_VERSION_MARSHMALLOW 23 +#define ANDROID_VERSION_N_MR1 25 +#endif + +#define DEFAULT_SAMPLE_RATE 48000 +#define DEFAULT_NUM_OF_FRAMES 480 + +extern cubeb_ops const opensl_ops; + +struct cubeb { + struct cubeb_ops const * ops; + void * lib; + SLInterfaceID SL_IID_BUFFERQUEUE; + SLInterfaceID SL_IID_PLAY; +#if defined(__ANDROID__) + SLInterfaceID SL_IID_ANDROIDCONFIGURATION; + SLInterfaceID SL_IID_ANDROIDSIMPLEBUFFERQUEUE; +#endif + SLInterfaceID SL_IID_VOLUME; + SLInterfaceID SL_IID_RECORD; + SLObjectItf engObj; + SLEngineItf eng; + SLObjectItf outmixObj; + output_latency_function * p_output_latency_function; +}; + +#define NELEMS(A) (sizeof(A) / sizeof A[0]) +#define NBUFS 2 + +struct cubeb_stream { + /* Note: Must match cubeb_stream layout in cubeb.c. */ + cubeb * context; + void * user_ptr; + /**/ + pthread_mutex_t mutex; + SLObjectItf playerObj; + SLPlayItf play; + SLBufferQueueItf bufq; + SLVolumeItf volume; + void ** queuebuf; + uint32_t queuebuf_capacity; + int queuebuf_idx; + long queuebuf_len; + long bytespersec; + long framesize; + /* Total number of played frames. + * Synchronized by stream::mutex lock. */ + long written; + /* Flag indicating draining. Synchronized + * by stream::mutex lock. */ + int draining; + /* Flags to determine in/out.*/ + uint32_t input_enabled; + uint32_t output_enabled; + /* Recorder abstract object. */ + SLObjectItf recorderObj; + /* Recorder Itf for input capture. */ + SLRecordItf recorderItf; + /* Buffer queue for input capture. */ + SLAndroidSimpleBufferQueueItf recorderBufferQueueItf; + /* Store input buffers. */ + void ** input_buffer_array; + /* The capacity of the array. + * On capture only can be small (4). + * On full duplex is calculated to + * store 1 sec of data buffers. */ + uint32_t input_array_capacity; + /* Current filled index of input buffer array. + * It is initiated to -1 indicating buffering + * have not started yet. */ + int input_buffer_index; + /* Length of input buffer.*/ + uint32_t input_buffer_length; + /* Input frame size */ + uint32_t input_frame_size; + /* Device sampling rate. If user rate is not + * accepted an compatible rate is set. If it is + * accepted this is equal to params.rate. */ + uint32_t input_device_rate; + /* Exchange input buffers between input + * and full duplex threads. */ + array_queue * input_queue; + /* Silent input buffer used on full duplex. */ + void * input_silent_buffer; + /* Number of input frames from the start of the stream*/ + uint32_t input_total_frames; + /* Flag to stop the execution of user callback and + * close all working threads. Synchronized by + * stream::mutex lock. */ + uint32_t shutdown; + /* Store user callback. */ + cubeb_data_callback data_callback; + /* Store state callback. */ + cubeb_state_callback state_callback; + + cubeb_resampler * resampler; + unsigned int user_output_rate; + unsigned int output_configured_rate; + unsigned int buffer_size_frames; + // Audio output latency used in cubeb_stream_get_position(). + unsigned int output_latency_ms; + int64_t lastPosition; + int64_t lastPositionTimeStamp; + int64_t lastCompensativePosition; + int voice_input; + int voice_output; +}; + +/* Forward declaration. */ +static int +opensl_stop_player(cubeb_stream * stm); +static int +opensl_stop_recorder(cubeb_stream * stm); + +static int +opensl_get_draining(cubeb_stream * stm) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + assert((r == EDEADLK || r == EBUSY) && + "get_draining: mutex should be locked but it's not."); +#endif + return stm->draining; +} + +static void +opensl_set_draining(cubeb_stream * stm, int value) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + LOG("set draining try r = %d", r); + assert((r == EDEADLK || r == EBUSY) && + "set_draining: mutex should be locked but it's not."); +#endif + assert(value == 0 || value == 1); + stm->draining = value; +} + +static void +opensl_notify_drained(cubeb_stream * stm) +{ + assert(stm); + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + if (stm->play) { + LOG("stop player in play_callback"); + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + } +} + +static uint32_t +opensl_get_shutdown(cubeb_stream * stm) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + assert((r == EDEADLK || r == EBUSY) && + "get_shutdown: mutex should be locked but it's not."); +#endif + return stm->shutdown; +} + +static void +opensl_set_shutdown(cubeb_stream * stm, uint32_t value) +{ +#ifdef DEBUG + int r = pthread_mutex_trylock(&stm->mutex); + LOG("set shutdown try r = %d", r); + assert((r == EDEADLK || r == EBUSY) && + "set_shutdown: mutex should be locked but it's not."); +#endif + assert(value == 0 || value == 1); + stm->shutdown = value; +} + +static void +play_callback(SLPlayItf caller, void * user_ptr, SLuint32 event) +{ + cubeb_stream * stm = static_cast(user_ptr); + assert(stm); + switch (event) { + case SL_PLAYEVENT_HEADATMARKER: + opensl_notify_drained(stm); + break; + default: + break; + } +} + +static void +recorder_marker_callback(SLRecordItf caller, void * pContext, SLuint32 event) +{ + cubeb_stream * stm = static_cast(pContext); + assert(stm); + + if (event == SL_RECORDEVENT_HEADATMARKER) { + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (draining) { + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); + if (stm->recorderItf) { + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + } + if (stm->play) { + r = opensl_stop_player(stm); + assert(r == CUBEB_OK); + } + } + } +} + +static void +bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) +{ + cubeb_stream * stm = static_cast(user_ptr); + assert(stm); + SLBufferQueueState state; + SLresult res; + long written = 0; + + res = (*stm->bufq)->GetState(stm->bufq, &state); + assert(res == SL_RESULT_SUCCESS); + + if (state.count > 1) { + return; + } + + uint8_t * buf = reinterpret_cast(stm->queuebuf[stm->queuebuf_idx]); + written = 0; + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + if (!draining && !shutdown) { + written = cubeb_resampler_fill(stm->resampler, NULL, NULL, buf, + stm->queuebuf_len / stm->framesize); + + LOG("bufferqueue_callback: resampler fill returned %ld frames", written); + if (written < 0 || written * stm->framesize > stm->queuebuf_len) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + opensl_stop_player(stm); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + return; + } + } + + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + assert(stm->queuebuf_len >= written * stm->framesize); + memset(buf + written * stm->framesize, 0, + stm->queuebuf_len - written * stm->framesize); + res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; + + if (written > 0) { + pthread_mutex_lock(&stm->mutex); + stm->written += written; + pthread_mutex_unlock(&stm->mutex); + } + + if (!draining && written * stm->framesize < stm->queuebuf_len) { + LOG("bufferqueue_callback draining"); + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int64_t written_duration = + INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (written_duration == 0) { + // since we didn't write any sample, it's not possible to reach the marker + // time and trigger the callback. We should initiative notify drained. + opensl_notify_drained(stm); + } else { + // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf + // to make sure all the data has been processed. + (*stm->play) + ->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + } + return; + } +} + +static int +opensl_enqueue_recorder(cubeb_stream * stm, void ** last_filled_buffer) +{ + assert(stm); + + int current_index = stm->input_buffer_index; + void * last_buffer = NULL; + + if (current_index < 0) { + // This is the first enqueue + current_index = 0; + } else { + // The current index hold the last filled buffer get it before advance + // index. + last_buffer = stm->input_buffer_array[current_index]; + // Advance to get next available buffer + current_index = (current_index + 1) % stm->input_array_capacity; + } + // enqueue next empty buffer to be filled by the recorder + SLresult res = (*stm->recorderBufferQueueItf) + ->Enqueue(stm->recorderBufferQueueItf, + stm->input_buffer_array[current_index], + stm->input_buffer_length); + if (res != SL_RESULT_SUCCESS) { + LOG("Enqueue recorder failed. Error code: %lu", res); + return CUBEB_ERROR; + } + // All good, update buffer and index. + stm->input_buffer_index = current_index; + if (last_filled_buffer) { + *last_filled_buffer = last_buffer; + } + return CUBEB_OK; +} + +// input data callback +void +recorder_callback(SLAndroidSimpleBufferQueueItf bq, void * context) +{ + assert(context); + cubeb_stream * stm = static_cast(context); + assert(stm->recorderBufferQueueItf); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + uint32_t shutdown = opensl_get_shutdown(stm); + int draining = opensl_get_draining(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + // According to the OpenSL ES 1.1 Specification, 8.14 SLBufferQueueItf + // page 184, on transition to the SL_RECORDSTATE_STOPPED state, + // the application should continue to enqueue buffers onto the queue + // to retrieve the residual recorded data in the system. + r = opensl_enqueue_recorder(stm, NULL); + assert(r == CUBEB_OK); + return; + } + + // Enqueue next available buffer and get the last filled buffer. + void * input_buffer = NULL; + r = opensl_enqueue_recorder(stm, &input_buffer); + assert(r == CUBEB_OK); + assert(input_buffer); + // Fill resampler with last input + long input_frame_count = stm->input_buffer_length / stm->input_frame_size; + long got = cubeb_resampler_fill(stm->resampler, input_buffer, + &input_frame_count, NULL, 0); + // Error case + if (got < 0 || got > input_frame_count) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + r = opensl_stop_recorder(stm); + assert(r == CUBEB_OK); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + } + + // Advance total stream frames + stm->input_total_frames += got; + + if (got < input_frame_count) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + int64_t duration = + INT64_C(1000) * stm->input_total_frames / stm->input_device_rate; + (*stm->recorderItf) + ->SetMarkerPosition(stm->recorderItf, (SLmillisecond)duration); + return; + } +} + +void +recorder_fullduplex_callback(SLAndroidSimpleBufferQueueItf bq, void * context) +{ + assert(context); + cubeb_stream * stm = static_cast(context); + assert(stm->recorderBufferQueueItf); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + /* On draining and shutdown the recorder should have been stoped from + * the one set the flags. Accordint to the doc, on transition to + * the SL_RECORDSTATE_STOPPED state, the application should + * continue to enqueue buffers onto the queue to retrieve the residual + * recorded data in the system. */ + LOG("Input shutdown %d or drain %d", shutdown, draining); + int r = opensl_enqueue_recorder(stm, NULL); + assert(r == CUBEB_OK); + return; + } + + // Enqueue next available buffer and get the last filled buffer. + void * input_buffer = NULL; + r = opensl_enqueue_recorder(stm, &input_buffer); + assert(r == CUBEB_OK); + assert(input_buffer); + + assert(stm->input_queue); + r = array_queue_push(stm->input_queue, input_buffer); + if (r == -1) { + LOG("Input queue is full, drop input ..."); + return; + } + + LOG("Input pushed in the queue, input array %zu", + array_queue_get_size(stm->input_queue)); +} + +static void +player_fullduplex_callback(SLBufferQueueItf caller, void * user_ptr) +{ + TIMESTAMP("ENTER"); + cubeb_stream * stm = static_cast(user_ptr); + assert(stm); + SLresult res; + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int draining = opensl_get_draining(stm); + uint32_t shutdown = opensl_get_shutdown(stm); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + // Get output + void * output_buffer = NULL; + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + output_buffer = stm->queuebuf[stm->queuebuf_idx]; + // Advance the output buffer queue index + stm->queuebuf_idx = (stm->queuebuf_idx + 1) % stm->queuebuf_capacity; + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (shutdown || draining) { + LOG("Shutdown/draining, send silent"); + // Set silent on buffer + memset(output_buffer, 0, stm->queuebuf_len); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + return; + } + + // Get input. + void * input_buffer = array_queue_pop(stm->input_queue); + long input_frame_count = stm->input_buffer_length / stm->input_frame_size; + long frames_needed = stm->queuebuf_len / stm->framesize; + if (!input_buffer) { + LOG("Input hole set silent input buffer"); + input_buffer = stm->input_silent_buffer; + } + + long written = 0; + // Trigger user callback through resampler + written = + cubeb_resampler_fill(stm->resampler, input_buffer, &input_frame_count, + output_buffer, frames_needed); + + LOG("Fill: written %ld, frames_needed %ld, input array size %zu", written, + frames_needed, array_queue_get_size(stm->input_queue)); + + if (written < 0 || written > frames_needed) { + // Error case + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + opensl_stop_player(stm); + opensl_stop_recorder(stm); + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR); + memset(output_buffer, 0, stm->queuebuf_len); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + return; + } + + // Advance total out written frames counter + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + stm->written += written; + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (written < frames_needed) { + r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + int64_t written_duration = + INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + opensl_set_draining(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + // Use SL_PLAYEVENT_HEADATMARKER event from slPlayCallback of SLPlayItf + // to make sure all the data has been processed. + (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)written_duration); + } + + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + memset((uint8_t *)output_buffer + written * stm->framesize, 0, + stm->queuebuf_len - written * stm->framesize); + + // Enqueue data in player buffer queue + res = (*stm->bufq)->Enqueue(stm->bufq, output_buffer, stm->queuebuf_len); + assert(res == SL_RESULT_SUCCESS); + TIMESTAMP("EXIT"); +} + +static void +opensl_destroy(cubeb * ctx); + +#if defined(__ANDROID__) +#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) +typedef int(system_property_get)(const char *, char *); + +static int +wrap_system_property_get(const char * name, char * value) +{ + void * libc = dlopen("libc.so", RTLD_LAZY); + if (!libc) { + LOG("Failed to open libc.so"); + return -1; + } + system_property_get * func = + (system_property_get *)dlsym(libc, "__system_property_get"); + int ret = -1; + if (func) { + ret = func(name, value); + } + dlclose(libc); + return ret; +} +#endif + +static int +get_android_version(void) +{ + char version_string[PROP_VALUE_MAX]; + + memset(version_string, 0, PROP_VALUE_MAX); + +#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) + int len = wrap_system_property_get("ro.build.version.sdk", version_string); +#else + int len = __system_property_get("ro.build.version.sdk", version_string); +#endif + if (len <= 0) { + LOG("Failed to get Android version!\n"); + return len; + } + + int version = (int)strtol(version_string, NULL, 10); + LOG("Android version %d", version); + return version; +} +#endif + +extern "C" { +int +opensl_init(cubeb ** context, char const * context_name) +{ + cubeb * ctx; + +#if defined(__ANDROID__) + int android_version = get_android_version(); + if (android_version > 0 && + android_version <= ANDROID_VERSION_GINGERBREAD_MR1) { + // Don't even attempt to run on Gingerbread and lower + return CUBEB_ERROR; + } +#endif + + *context = NULL; + + ctx = static_cast(calloc(1, sizeof(*ctx))); + assert(ctx); + + ctx->ops = &opensl_ops; + + ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); + if (!ctx->lib) { + free(ctx); + return CUBEB_ERROR; + } + + typedef SLresult (*slCreateEngine_t)( + SLObjectItf *, SLuint32, const SLEngineOption *, SLuint32, + const SLInterfaceID *, const SLboolean *); + slCreateEngine_t f_slCreateEngine = + (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); + SLInterfaceID SL_IID_ENGINE = + *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); + SLInterfaceID SL_IID_OUTPUTMIX = + *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); + ctx->SL_IID_VOLUME = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_VOLUME"); + ctx->SL_IID_BUFFERQUEUE = + *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); +#if defined(__ANDROID__) + ctx->SL_IID_ANDROIDCONFIGURATION = + *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); + ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE = + *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDSIMPLEBUFFERQUEUE"); +#endif + ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); + ctx->SL_IID_RECORD = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_RECORD"); + + if (!f_slCreateEngine || !SL_IID_ENGINE || !SL_IID_OUTPUTMIX || + !ctx->SL_IID_BUFFERQUEUE || +#if defined(__ANDROID__) + !ctx->SL_IID_ANDROIDCONFIGURATION || + !ctx->SL_IID_ANDROIDSIMPLEBUFFERQUEUE || +#endif + !ctx->SL_IID_PLAY || !ctx->SL_IID_RECORD) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; + + SLresult res; + res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, NULL, NULL); + + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng); + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; + const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; + res = + (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + ctx->p_output_latency_function = + cubeb_output_latency_load_method(android_version); + if (!cubeb_output_latency_method_is_loaded(ctx->p_output_latency_function)) { + LOG("Warning: output latency is not available, cubeb_stream_get_position() " + "is not supported"); + } + + *context = ctx; + + LOG("Cubeb init (%p) success", ctx); + return CUBEB_OK; +} +} + +static char const * +opensl_get_backend_id(cubeb * ctx) +{ + return "opensl"; +} + +static int +opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) +{ + assert(ctx && max_channels); + /* The android mixer handles up to two channels, see + http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 + */ + *max_channels = 2; + + return CUBEB_OK; +} + +static void +opensl_destroy(cubeb * ctx) +{ + if (ctx->outmixObj) { + (*ctx->outmixObj)->Destroy(ctx->outmixObj); + } + if (ctx->engObj) { + (*ctx->engObj)->Destroy(ctx->engObj); + } + dlclose(ctx->lib); + if (ctx->p_output_latency_function) + cubeb_output_latency_unload_method(ctx->p_output_latency_function); + free(ctx); +} + +static void +opensl_stream_destroy(cubeb_stream * stm); + +#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) +static int +opensl_set_format_ext(SLAndroidDataFormat_PCM_EX * format, + cubeb_stream_params * params) +{ + assert(format); + assert(params); + + format->formatType = SL_ANDROID_DATAFORMAT_PCM_EX; + format->numChannels = params->channels; + // sampleRate is in milliHertz + format->sampleRate = params->rate * 1000; + format->channelMask = params->channels == 1 + ? SL_SPEAKER_FRONT_CENTER + : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; + format->endianness = SL_BYTEORDER_LITTLEENDIAN; + break; + case CUBEB_SAMPLE_S16BE: + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format->representation = SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT; + format->endianness = SL_BYTEORDER_BIGENDIAN; + break; + case CUBEB_SAMPLE_FLOAT32LE: + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; + format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + format->endianness = SL_BYTEORDER_LITTLEENDIAN; + break; + case CUBEB_SAMPLE_FLOAT32BE: + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_32; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_32; + format->representation = SL_ANDROID_PCM_REPRESENTATION_FLOAT; + format->endianness = SL_BYTEORDER_BIGENDIAN; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + return CUBEB_OK; +} +#endif + +static int +opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) +{ + assert(format); + assert(params); + + format->formatType = SL_DATAFORMAT_PCM; + format->numChannels = params->channels; + // samplesPerSec is in milliHertz + format->samplesPerSec = params->rate * 1000; + format->bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format->containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format->channelMask = params->channels == 1 + ? SL_SPEAKER_FRONT_CENTER + : SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + + switch (params->format) { + case CUBEB_SAMPLE_S16LE: + format->endianness = SL_BYTEORDER_LITTLEENDIAN; + break; + case CUBEB_SAMPLE_S16BE: + format->endianness = SL_BYTEORDER_BIGENDIAN; + break; + default: + return CUBEB_ERROR_INVALID_FORMAT; + } + return CUBEB_OK; +} + +static int +opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) +{ + assert(stm); + assert(params); + + SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; + lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; + lDataLocatorOut.numBuffers = NBUFS; + + SLDataFormat_PCM lDataFormat; + int r = opensl_set_format(&lDataFormat, params); + if (r != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; + } + + /* For now set device rate to params rate. */ + stm->input_device_rate = params->rate; + + SLDataSink lDataSink; + lDataSink.pLocator = &lDataLocatorOut; + lDataSink.pFormat = &lDataFormat; + + SLDataLocator_IODevice lDataLocatorIn; + lDataLocatorIn.locatorType = SL_DATALOCATOR_IODEVICE; + lDataLocatorIn.deviceType = SL_IODEVICE_AUDIOINPUT; + lDataLocatorIn.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT; + lDataLocatorIn.device = NULL; + + SLDataSource lDataSource; + lDataSource.pLocator = &lDataLocatorIn; + lDataSource.pFormat = NULL; + + const SLInterfaceID lSoundRecorderIIDs[] = { + stm->context->SL_IID_RECORD, + stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + stm->context->SL_IID_ANDROIDCONFIGURATION}; + + const SLboolean lSoundRecorderReqs[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE}; + // create the audio recorder abstract object + SLresult res = (*stm->context->eng) + ->CreateAudioRecorder( + stm->context->eng, &stm->recorderObj, &lDataSource, + &lDataSink, NELEMS(lSoundRecorderIIDs), + lSoundRecorderIIDs, lSoundRecorderReqs); + // Sample rate not supported. Try again with default sample rate! + if (res == SL_RESULT_CONTENT_UNSUPPORTED) { + if (stm->output_enabled && stm->output_configured_rate != 0) { + // Set the same with the player. Since there is no + // api for input device this is a safe choice. + stm->input_device_rate = stm->output_configured_rate; + } else { + // The output preferred rate is used for an input only scenario. + // The default rate expected to be supported from all android devices. + stm->input_device_rate = DEFAULT_SAMPLE_RATE; + } + lDataFormat.samplesPerSec = stm->input_device_rate * 1000; + res = (*stm->context->eng) + ->CreateAudioRecorder(stm->context->eng, &stm->recorderObj, + &lDataSource, &lDataSink, + NELEMS(lSoundRecorderIIDs), + lSoundRecorderIIDs, lSoundRecorderReqs); + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to create recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + } + + if (get_android_version() > ANDROID_VERSION_JELLY_BEAN) { + SLAndroidConfigurationItf recorderConfig; + res = (*stm->recorderObj) + ->GetInterface(stm->recorderObj, + stm->context->SL_IID_ANDROIDCONFIGURATION, + &recorderConfig); + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get the android configuration interface for recorder. " + "Error " + "code: %lu", + res); + return CUBEB_ERROR; + } + + // Voice recognition is the lowest latency, according to the docs. Camcorder + // uses a microphone that is in the same direction as the camera. + SLint32 streamType = stm->voice_input + ? SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION + : SL_ANDROID_RECORDING_PRESET_CAMCORDER; + + res = + (*recorderConfig) + ->SetConfiguration(recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, + &streamType, sizeof(SLint32)); + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set the android configuration to VOICE for the recorder. " + "Error code: %lu", + res); + return CUBEB_ERROR; + } + } + // realize the audio recorder + res = (*stm->recorderObj)->Realize(stm->recorderObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to realize recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + // get the record interface + res = (*stm->recorderObj) + ->GetInterface(stm->recorderObj, stm->context->SL_IID_RECORD, + &stm->recorderItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get recorder interface. Error code: %lu", res); + return CUBEB_ERROR; + } + + res = (*stm->recorderItf) + ->RegisterCallback(stm->recorderItf, recorder_marker_callback, stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register recorder marker callback. Error code: %lu", res); + return CUBEB_ERROR; + } + + (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); + + res = (*stm->recorderItf) + ->SetCallbackEventsMask(stm->recorderItf, + (SLuint32)SL_RECORDEVENT_HEADATMARKER); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set headatmarker event mask. Error code: %lu", res); + return CUBEB_ERROR; + } + // get the simple android buffer queue interface + res = (*stm->recorderObj) + ->GetInterface(stm->recorderObj, + stm->context->SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + &stm->recorderBufferQueueItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get recorder (android) buffer queue interface. Error code: " + "%lu", + res); + return CUBEB_ERROR; + } + + // register callback on record (input) buffer queue + slAndroidSimpleBufferQueueCallback rec_callback = recorder_callback; + if (stm->output_enabled) { + // Register full duplex callback instead. + rec_callback = recorder_fullduplex_callback; + } + res = (*stm->recorderBufferQueueItf) + ->RegisterCallback(stm->recorderBufferQueueItf, rec_callback, stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register recorder buffer queue callback. Error code: %lu", + res); + return CUBEB_ERROR; + } + + // Calculate length of input buffer according to requested latency + stm->input_frame_size = params->channels * sizeof(int16_t); + stm->input_buffer_length = (stm->input_frame_size * stm->buffer_size_frames); + + // Calculate the capacity of input array + stm->input_array_capacity = NBUFS; + if (stm->output_enabled) { + // Full duplex, update capacity to hold 1 sec of data + stm->input_array_capacity = + 1 * stm->input_device_rate / stm->input_buffer_length; + } + // Allocate input array + stm->input_buffer_array = + (void **)calloc(1, sizeof(void *) * stm->input_array_capacity); + // Buffering has not started yet. + stm->input_buffer_index = -1; + // Prepare input buffers + for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { + stm->input_buffer_array[i] = calloc(1, stm->input_buffer_length); + } + + // On full duplex allocate input queue and silent buffer + if (stm->output_enabled) { + stm->input_queue = array_queue_create(stm->input_array_capacity); + assert(stm->input_queue); + stm->input_silent_buffer = calloc(1, stm->input_buffer_length); + assert(stm->input_silent_buffer); + } + + // Enqueue buffer to start rolling once recorder started + r = opensl_enqueue_recorder(stm, NULL); + if (r != CUBEB_OK) { + return r; + } + + LOG("Cubeb stream init recorder success"); + + return CUBEB_OK; +} + +static int +opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) +{ + assert(stm); + assert(params); + + stm->user_output_rate = params->rate; + if (params->format == CUBEB_SAMPLE_S16NE || + params->format == CUBEB_SAMPLE_S16BE) { + stm->framesize = params->channels * sizeof(int16_t); + } else if (params->format == CUBEB_SAMPLE_FLOAT32NE || + params->format == CUBEB_SAMPLE_FLOAT32BE) { + stm->framesize = params->channels * sizeof(float); + } + stm->lastPosition = -1; + stm->lastPositionTimeStamp = 0; + stm->lastCompensativePosition = -1; + + void * format = NULL; + SLuint32 * format_sample_rate = NULL; + +#if defined(__ANDROID__) && (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) + SLAndroidDataFormat_PCM_EX pcm_ext_format; + if (get_android_version() >= ANDROID_VERSION_LOLLIPOP) { + if (opensl_set_format_ext(&pcm_ext_format, params) != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; + } + format = &pcm_ext_format; + format_sample_rate = &pcm_ext_format.sampleRate; + } +#endif + + SLDataFormat_PCM pcm_format; + if (!format) { + if (opensl_set_format(&pcm_format, params) != CUBEB_OK) { + return CUBEB_ERROR_INVALID_FORMAT; + } + format = &pcm_format; + format_sample_rate = &pcm_format.samplesPerSec; + } + + SLDataLocator_BufferQueue loc_bufq; + loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; + loc_bufq.numBuffers = NBUFS; + SLDataSource source; + source.pLocator = &loc_bufq; + source.pFormat = format; + + SLDataLocator_OutputMix loc_outmix; + loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; + loc_outmix.outputMix = stm->context->outmixObj; + SLDataSink sink; + sink.pLocator = &loc_outmix; + sink.pFormat = NULL; + +#if defined(__ANDROID__) + const SLInterfaceID ids[] = {stm->context->SL_IID_BUFFERQUEUE, + stm->context->SL_IID_VOLUME, + stm->context->SL_IID_ANDROIDCONFIGURATION}; + const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; +#else + const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_VOLUME}; + const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; +#endif + assert(NELEMS(ids) == NELEMS(req)); + + uint32_t preferred_sampling_rate = stm->user_output_rate; + SLresult res = SL_RESULT_CONTENT_UNSUPPORTED; + if (preferred_sampling_rate) { + res = (*stm->context->eng) + ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, + &sink, NELEMS(ids), ids, req); + } + + // Sample rate not supported? Try again with primary sample rate! + if (res == SL_RESULT_CONTENT_UNSUPPORTED && + preferred_sampling_rate != DEFAULT_SAMPLE_RATE) { + preferred_sampling_rate = DEFAULT_SAMPLE_RATE; + *format_sample_rate = preferred_sampling_rate * 1000; + res = (*stm->context->eng) + ->CreateAudioPlayer(stm->context->eng, &stm->playerObj, &source, + &sink, NELEMS(ids), ids, req); + } + + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to create audio player. Error code: %lu", res); + return CUBEB_ERROR; + } + + stm->output_configured_rate = preferred_sampling_rate; + stm->bytespersec = stm->output_configured_rate * stm->framesize; + stm->queuebuf_len = stm->framesize * stm->buffer_size_frames; + + // Calculate the capacity of input array + stm->queuebuf_capacity = NBUFS; + // Allocate input arrays + stm->queuebuf = (void **)calloc(1, sizeof(void *) * stm->queuebuf_capacity); + for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { + stm->queuebuf[i] = calloc(1, stm->queuebuf_len); + assert(stm->queuebuf[i]); + } + + SLAndroidConfigurationItf playerConfig = NULL; + + if (get_android_version() >= ANDROID_VERSION_N_MR1) { + res = (*stm->playerObj) + ->GetInterface(stm->playerObj, + stm->context->SL_IID_ANDROIDCONFIGURATION, + &playerConfig); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get Android configuration interface. Error code: %lu", + res); + return CUBEB_ERROR; + } + + SLint32 streamType = SL_ANDROID_STREAM_MEDIA; + if (stm->voice_output) { + streamType = SL_ANDROID_STREAM_VOICE; + } + res = (*playerConfig) + ->SetConfiguration(playerConfig, SL_ANDROID_KEY_STREAM_TYPE, + &streamType, sizeof(streamType)); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set Android configuration to %d Error code: %lu", + streamType, res); + } + + SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; + if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { + performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; + } + + res = (*playerConfig) + ->SetConfiguration(playerConfig, SL_ANDROID_KEY_PERFORMANCE_MODE, + &performanceMode, sizeof(performanceMode)); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set Android performance mode to %d Error code: %lu. This " + "is" + " not fatal", + performanceMode, res); + } + } + + res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to realize player object. Error code: %lu", res); + return CUBEB_ERROR; + } + + // There are two ways of getting the audio output latency: + // - a configuration value, only available on some devices (notably devices + // running FireOS) + // - A Java method, that we call using JNI. + // + // The first method is prefered, if available, because it can account for more + // latency causes, and is more precise. + + // Latency has to be queried after the realization of the interface, when + // using SL_IID_ANDROIDCONFIGURATION. + SLuint32 audioLatency = 0; + SLuint32 paramSize = sizeof(SLuint32); + // The reported latency is in milliseconds. + if (playerConfig) { + res = (*playerConfig) + ->GetConfiguration(playerConfig, + (const SLchar *)"androidGetAudioLatency", + ¶mSize, &audioLatency); + if (res == SL_RESULT_SUCCESS) { + LOG("Got playback latency using android configuration extension"); + stm->output_latency_ms = audioLatency; + } + } + // `playerConfig` is available, but the above failed, or `playerConfig` is not + // available. In both cases, we need to acquire the output latency by an other + // mean. + if ((playerConfig && res != SL_RESULT_SUCCESS) || !playerConfig) { + if (cubeb_output_latency_method_is_loaded( + stm->context->p_output_latency_function)) { + LOG("Got playback latency using JNI"); + stm->output_latency_ms = + cubeb_get_output_latency(stm->context->p_output_latency_function); + } else { + LOG("No alternate latency querying method loaded, A/V sync will be off."); + stm->output_latency_ms = 0; + } + } + + LOG("Audio output latency: %dms", stm->output_latency_ms); + + res = + (*stm->playerObj) + ->GetInterface(stm->playerObj, stm->context->SL_IID_PLAY, &stm->play); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get play interface. Error code: %lu", res); + return CUBEB_ERROR; + } + + res = (*stm->playerObj) + ->GetInterface(stm->playerObj, stm->context->SL_IID_BUFFERQUEUE, + &stm->bufq); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get bufferqueue interface. Error code: %lu", res); + return CUBEB_ERROR; + } + + res = (*stm->playerObj) + ->GetInterface(stm->playerObj, stm->context->SL_IID_VOLUME, + &stm->volume); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to get volume interface. Error code: %lu", res); + return CUBEB_ERROR; + } + + res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register play callback. Error code: %lu", res); + return CUBEB_ERROR; + } + + // Work around wilhelm/AudioTrack badness, bug 1221228 + (*stm->play)->SetMarkerPosition(stm->play, (SLmillisecond)0); + + res = (*stm->play) + ->SetCallbackEventsMask(stm->play, + (SLuint32)SL_PLAYEVENT_HEADATMARKER); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to set headatmarker event mask. Error code: %lu", res); + return CUBEB_ERROR; + } + + slBufferQueueCallback player_callback = bufferqueue_callback; + if (stm->input_enabled) { + player_callback = player_fullduplex_callback; + } + res = (*stm->bufq)->RegisterCallback(stm->bufq, player_callback, stm); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to register bufferqueue callback. Error code: %lu", res); + return CUBEB_ERROR; + } + + { + // Enqueue a silent frame so once the player becomes playing, the frame + // will be consumed and kick off the buffer queue callback. + // Note the duration of a single frame is less than 1ms. We don't bother + // adjusting the playback position. + uint8_t * buf = reinterpret_cast(stm->queuebuf[stm->queuebuf_idx++]); + memset(buf, 0, stm->framesize); + res = (*stm->bufq)->Enqueue(stm->bufq, buf, stm->framesize); + assert(res == SL_RESULT_SUCCESS); + } + + LOG("Cubeb stream init playback success"); + return CUBEB_OK; +} + +static int +opensl_validate_stream_param(cubeb_stream_params * stream_params) +{ + if ((stream_params && + (stream_params->channels < 1 || stream_params->channels > 32))) { + return CUBEB_ERROR_INVALID_FORMAT; + } + if ((stream_params && (stream_params->prefs & CUBEB_STREAM_PREF_LOOPBACK))) { + LOG("Loopback is not supported"); + return CUBEB_ERROR_NOT_SUPPORTED; + } + return CUBEB_OK; +} + +int +has_pref_set(cubeb_stream_params * input_params, + cubeb_stream_params * output_params, cubeb_stream_prefs pref) +{ + return (input_params && input_params->prefs & pref) || + (output_params && output_params->prefs & pref); +} + +static int +opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, + char const * stream_name, cubeb_devid input_device, + cubeb_stream_params * input_stream_params, + cubeb_devid output_device, + cubeb_stream_params * output_stream_params, + unsigned int latency_frames, + cubeb_data_callback data_callback, + cubeb_state_callback state_callback, void * user_ptr) +{ + cubeb_stream * stm = nullptr; + + assert(ctx); + if (input_device || output_device) { + LOG("Device selection is not supported in Android. The default will be " + "used"); + } + + *stream = NULL; + + int r = opensl_validate_stream_param(output_stream_params); + if (r != CUBEB_OK) { + LOG("Output stream params not valid"); + return r; + } + r = opensl_validate_stream_param(input_stream_params); + if (r != CUBEB_OK) { + LOG("Input stream params not valid"); + return r; + } + + stm = reinterpret_cast(calloc(1, sizeof(*stm))); + assert(stm); + + stm->context = ctx; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + stm->buffer_size_frames = + latency_frames ? latency_frames : DEFAULT_NUM_OF_FRAMES; + stm->input_enabled = (input_stream_params) ? 1 : 0; + stm->output_enabled = (output_stream_params) ? 1 : 0; + stm->shutdown = 1; + stm->voice_input = + has_pref_set(input_stream_params, NULL, CUBEB_STREAM_PREF_VOICE); + stm->voice_output = + has_pref_set(NULL, output_stream_params, CUBEB_STREAM_PREF_VOICE); + + LOG("cubeb stream prefs: voice_input: %s voice_output: %s", + stm->voice_input ? "true" : "false", + stm->voice_output ? "true" : "false"); + +#ifdef DEBUG + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_ERRORCHECK); + r = pthread_mutex_init(&stm->mutex, &attr); +#else + r = pthread_mutex_init(&stm->mutex, NULL); +#endif + assert(r == 0); + + if (output_stream_params) { + LOG("Playback params: Rate %d, channels %d, format %d, latency in frames " + "%d.", + output_stream_params->rate, output_stream_params->channels, + output_stream_params->format, stm->buffer_size_frames); + r = opensl_configure_playback(stm, output_stream_params); + if (r != CUBEB_OK) { + opensl_stream_destroy(stm); + return r; + } + } + + if (input_stream_params) { + LOG("Capture params: Rate %d, channels %d, format %d, latency in frames " + "%d.", + input_stream_params->rate, input_stream_params->channels, + input_stream_params->format, stm->buffer_size_frames); + r = opensl_configure_capture(stm, input_stream_params); + if (r != CUBEB_OK) { + opensl_stream_destroy(stm); + return r; + } + } + + /* Configure resampler*/ + uint32_t target_sample_rate; + if (input_stream_params) { + target_sample_rate = input_stream_params->rate; + } else { + assert(output_stream_params); + target_sample_rate = output_stream_params->rate; + } + + // Use the actual configured rates for input + // and output. + cubeb_stream_params input_params; + if (input_stream_params) { + input_params = *input_stream_params; + input_params.rate = stm->input_device_rate; + } + cubeb_stream_params output_params; + if (output_stream_params) { + output_params = *output_stream_params; + output_params.rate = stm->output_configured_rate; + } + + stm->resampler = cubeb_resampler_create( + stm, input_stream_params ? &input_params : NULL, + output_stream_params ? &output_params : NULL, target_sample_rate, + data_callback, user_ptr, CUBEB_RESAMPLER_QUALITY_DEFAULT, + CUBEB_RESAMPLER_RECLOCK_NONE); + if (!stm->resampler) { + LOG("Failed to create resampler"); + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + *stream = stm; + LOG("Cubeb stream (%p) init success", stm); + return CUBEB_OK; +} + +static int +opensl_start_player(cubeb_stream * stm) +{ + assert(stm->playerObj); + SLuint32 playerState; + (*stm->playerObj)->GetState(stm->playerObj, &playerState); + if (playerState == SL_OBJECT_STATE_REALIZED) { + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to start player. Error code: %lu", res); + return CUBEB_ERROR; + } + } + return CUBEB_OK; +} + +static int +opensl_start_recorder(cubeb_stream * stm) +{ + assert(stm->recorderObj); + SLuint32 recorderState; + (*stm->recorderObj)->GetState(stm->recorderObj, &recorderState); + if (recorderState == SL_OBJECT_STATE_REALIZED) { + SLresult res = + (*stm->recorderItf) + ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_RECORDING); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to start recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + } + return CUBEB_OK; +} + +static int +opensl_stream_start(cubeb_stream * stm) +{ + assert(stm); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 0); + opensl_set_draining(stm, 0); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (stm->playerObj) { + r = opensl_start_player(stm); + if (r != CUBEB_OK) { + return r; + } + } + + if (stm->recorderObj) { + int r = opensl_start_recorder(stm); + if (r != CUBEB_OK) { + return r; + } + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); + LOG("Cubeb stream (%p) started", stm); + return CUBEB_OK; +} + +static int +opensl_stop_player(cubeb_stream * stm) +{ + assert(stm->playerObj); + assert(stm->shutdown || stm->draining); + + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to stop player. Error code: %lu", res); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +opensl_stop_recorder(cubeb_stream * stm) +{ + assert(stm->recorderObj); + assert(stm->shutdown || stm->draining); + + SLresult res = (*stm->recorderItf) + ->SetRecordState(stm->recorderItf, SL_RECORDSTATE_PAUSED); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to stop recorder. Error code: %lu", res); + return CUBEB_ERROR; + } + + return CUBEB_OK; +} + +static int +opensl_stream_stop(cubeb_stream * stm) +{ + assert(stm); + + int r = pthread_mutex_lock(&stm->mutex); + assert(r == 0); + opensl_set_shutdown(stm, 1); + r = pthread_mutex_unlock(&stm->mutex); + assert(r == 0); + + if (stm->playerObj) { + r = opensl_stop_player(stm); + if (r != CUBEB_OK) { + return r; + } + } + + if (stm->recorderObj) { + int r = opensl_stop_recorder(stm); + if (r != CUBEB_OK) { + return r; + } + } + + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + LOG("Cubeb stream (%p) stopped", stm); + return CUBEB_OK; +} + +static int +opensl_destroy_recorder(cubeb_stream * stm) +{ + assert(stm); + assert(stm->recorderObj); + + if (stm->recorderBufferQueueItf) { + SLresult res = + (*stm->recorderBufferQueueItf)->Clear(stm->recorderBufferQueueItf); + if (res != SL_RESULT_SUCCESS) { + LOG("Failed to clear recorder buffer queue. Error code: %lu", res); + return CUBEB_ERROR; + } + stm->recorderBufferQueueItf = NULL; + for (uint32_t i = 0; i < stm->input_array_capacity; ++i) { + free(stm->input_buffer_array[i]); + } + } + + (*stm->recorderObj)->Destroy(stm->recorderObj); + stm->recorderObj = NULL; + stm->recorderItf = NULL; + + if (stm->input_queue) { + array_queue_destroy(stm->input_queue); + } + free(stm->input_silent_buffer); + + return CUBEB_OK; +} + +static void +opensl_stream_destroy(cubeb_stream * stm) +{ + assert(stm->draining || stm->shutdown); + + // If we're still draining at stream destroy time, pause the streams now so we + // can destroy them safely. + if (stm->draining) { + opensl_stream_stop(stm); + } + // Sleep for 10ms to give active streams time to pause so that no further + // buffer callbacks occur. Inspired by the same workaround (sleepBeforeClose) + // in liboboe. + usleep(10 * 1000); + + if (stm->playerObj) { + (*stm->playerObj)->Destroy(stm->playerObj); + stm->playerObj = NULL; + stm->play = NULL; + stm->bufq = NULL; + for (uint32_t i = 0; i < stm->queuebuf_capacity; ++i) { + free(stm->queuebuf[i]); + } + } + + if (stm->recorderObj) { + int r = opensl_destroy_recorder(stm); + assert(r == CUBEB_OK); + } + + if (stm->resampler) { + cubeb_resampler_destroy(stm->resampler); + } + + pthread_mutex_destroy(&stm->mutex); + + LOG("Cubeb stream (%p) destroyed", stm); + free(stm); +} + +static int +opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + SLmillisecond msec; + uint32_t compensation_msec = 0; + SLresult res; + + res = (*stm->play)->GetPosition(stm->play, &msec); + if (res != SL_RESULT_SUCCESS) + return CUBEB_ERROR; + + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + if (stm->lastPosition == msec) { + compensation_msec = + (t.tv_sec * 1000000000LL + t.tv_nsec - stm->lastPositionTimeStamp) / + 1000000; + } else { + stm->lastPositionTimeStamp = t.tv_sec * 1000000000LL + t.tv_nsec; + stm->lastPosition = msec; + } + + uint64_t samplerate = stm->user_output_rate; + uint32_t output_latency = stm->output_latency_ms; + + pthread_mutex_lock(&stm->mutex); + int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / + stm->output_configured_rate; + pthread_mutex_unlock(&stm->mutex); + assert(maximum_position >= 0); + + if (msec > output_latency) { + int64_t unadjusted_position; + if (stm->lastCompensativePosition > msec + compensation_msec) { + // Over compensation, use lastCompensativePosition. + unadjusted_position = + samplerate * (stm->lastCompensativePosition - output_latency) / 1000; + } else { + unadjusted_position = + samplerate * (msec - output_latency + compensation_msec) / 1000; + stm->lastCompensativePosition = msec + compensation_msec; + } + *position = unadjusted_position < maximum_position ? unadjusted_position + : maximum_position; + } else { + *position = 0; + } + return CUBEB_OK; +} + +static int +opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + assert(stm); + assert(latency); + + uint32_t stream_latency_frames = + stm->user_output_rate * stm->output_latency_ms / 1000; + + return stream_latency_frames + cubeb_resampler_latency(stm->resampler); +} + +int +opensl_stream_set_volume(cubeb_stream * stm, float volume) +{ + SLresult res; + SLmillibel max_level, millibels; + float unclamped_millibels; + + res = (*stm->volume)->GetMaxVolumeLevel(stm->volume, &max_level); + + if (res != SL_RESULT_SUCCESS) { + return CUBEB_ERROR; + } + + /* millibels are 100*dB, so the conversion from the volume's linear amplitude + * is 100 * 20 * log(volume). However we clamp the resulting value before + * passing it to lroundf() in order to prevent it from silently returning an + * erroneous value when the unclamped value exceeds the size of a long. */ + unclamped_millibels = 100.0f * 20.0f * log10f(fmaxf(volume, 0.0f)); + unclamped_millibels = fmaxf(unclamped_millibels, SL_MILLIBEL_MIN); + unclamped_millibels = fminf(unclamped_millibels, max_level); + + millibels = lroundf(unclamped_millibels); + + res = (*stm->volume)->SetVolumeLevel(stm->volume, millibels); + + if (res != SL_RESULT_SUCCESS) { + return CUBEB_ERROR; + } + return CUBEB_OK; +} + +struct cubeb_ops const opensl_ops = { + .init = opensl_init, + .get_backend_id = opensl_get_backend_id, + .get_max_channel_count = opensl_get_max_channel_count, + .get_min_latency = NULL, + .get_preferred_sample_rate = NULL, + .enumerate_devices = NULL, + .device_collection_destroy = NULL, + .destroy = opensl_destroy, + .stream_init = opensl_stream_init, + .stream_destroy = opensl_stream_destroy, + .stream_start = opensl_stream_start, + .stream_stop = opensl_stream_stop, + .stream_get_position = opensl_stream_get_position, + .stream_get_latency = opensl_stream_get_latency, + .stream_get_input_latency = NULL, + .stream_set_volume = opensl_stream_set_volume, + .stream_set_name = NULL, + .stream_get_current_device = NULL, + .stream_device_destroy = NULL, + .stream_register_device_changed_callback = NULL, + .register_device_collection_changed = NULL}; -- cgit v1.2.3