diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_opensl.c')
-rw-r--r-- | media/libcubeb/src/cubeb_opensl.c | 1562 |
1 files changed, 383 insertions, 1179 deletions
diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c index 96374ec07..dd5416228 100644 --- a/media/libcubeb/src/cubeb_opensl.c +++ b/media/libcubeb/src/cubeb_opensl.c @@ -9,7 +9,6 @@ #include <dlfcn.h> #include <stdlib.h> #include <pthread.h> -#include <errno.h> #include <SLES/OpenSLES.h> #include <math.h> #include <time.h> @@ -20,265 +19,85 @@ #include <SLES/OpenSLES_Android.h> #include <android/log.h> #include <android/api-level.h> -#endif -#include "cubeb/cubeb.h" -#include "cubeb-internal.h" -#include "cubeb_resampler.h" -#include "cubeb-sles.h" -#include "cubeb_array_queue.h" -#include "android/cubeb-output-latency.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 -// If the latency requested is above this threshold, this stream is considered -// intended for playback (vs. real-time). Tell Android it should favor saving -// power over performance or latency. -// This is around 100ms at 44100 or 48000 -#define POWERSAVE_LATENCY_FRAMES_THRESHOLD 4000 +#include "cubeb/cubeb.h" +#include "cubeb-internal.h" +#include "cubeb_resampler.h" +#include "cubeb-sles.h" static struct cubeb_ops const opensl_ops; struct cubeb { struct cubeb_ops const * ops; void * lib; + void * libmedia; + int32_t (* get_output_latency)(uint32_t * latency, int stream_type); 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 +#define NBUFS 4 +#define AUDIO_STREAM_TYPE_MUSIC 3 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; + uint8_t *queuebuf[NBUFS]; 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_stream_type stream_type; + cubeb_data_callback data_callback; - /* Store state callback. */ cubeb_state_callback state_callback; + void * user_ptr; 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; + unsigned int inputrate; + unsigned int outputrate; + unsigned int latency; int64_t lastPosition; int64_t lastPositionTimeStamp; int64_t lastCompensativePosition; - int voice; }; -/* 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; + int draining; 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); + case SL_PLAYEVENT_HEADATMARKER: + pthread_mutex_lock(&stm->mutex); + draining = stm->draining; + pthread_mutex_unlock(&stm->mutex); 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); - } + (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); } + break; + default: + break; } } @@ -289,333 +108,90 @@ bufferqueue_callback(SLBufferQueueItf caller, void * 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) { + 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) { + SLuint32 i; + for (i = state.count; i < NBUFS; i++) { + uint8_t *buf = stm->queuebuf[stm->queuebuf_idx]; + long written = 0; pthread_mutex_lock(&stm->mutex); - stm->written += written; + int draining = stm->draining; 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 { + if (!draining) { + written = cubeb_resampler_fill(stm->resampler, + NULL, NULL, + buf, stm->queuebuf_len / stm->framesize); + if (written < 0 || written * stm->framesize > stm->queuebuf_len) { + (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); + return; + } + } + + // Keep sending silent data even in draining mode to prevent the audio + // back-end from being stopped automatically by OpenSL/ES. + 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) % NBUFS; + if (written > 0) { + pthread_mutex_lock(&stm->mutex); + stm->written += written; + pthread_mutex_unlock(&stm->mutex); + } + + if (!draining && written * stm->framesize < stm->queuebuf_len) { + pthread_mutex_lock(&stm->mutex); + int64_t written_duration = INT64_C(1000) * stm->written * stm->framesize / stm->bytespersec; + stm->draining = 1; + pthread_mutex_unlock(&stm->mutex); // 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; } - 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); +#if defined(__ANDROID__) +static SLuint32 +convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) +{ + switch(stream_type) { + case CUBEB_STREAM_TYPE_SYSTEM: + return SL_ANDROID_STREAM_SYSTEM; + case CUBEB_STREAM_TYPE_MUSIC: + return SL_ANDROID_STREAM_MEDIA; + case CUBEB_STREAM_TYPE_NOTIFICATION: + return SL_ANDROID_STREAM_NOTIFICATION; + case CUBEB_STREAM_TYPE_ALARM: + return SL_ANDROID_STREAM_ALARM; + case CUBEB_STREAM_TYPE_VOICE_CALL: + return SL_ANDROID_STREAM_VOICE; + case CUBEB_STREAM_TYPE_RING: + return SL_ANDROID_STREAM_RING; + case CUBEB_STREAM_TYPE_SYSTEM_ENFORCED: + return SL_ANDROID_STREAM_SYSTEM_ENFORCED; + default: + return 0xFFFFFFFF; } - - // 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"); } +#endif 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) +__system_property_get(const char* name, char* value) { void* libc = dlopen("libc.so", RTLD_LAZY); if (!libc) { @@ -640,18 +216,14 @@ get_android_version(void) 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); + LOG("%d", version); return version; } #endif @@ -677,11 +249,30 @@ opensl_init(cubeb ** context, char const * context_name) ctx->ops = &opensl_ops; ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); - if (!ctx->lib) { + ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!ctx->lib || !ctx->libmedia) { free(ctx); return CUBEB_ERROR; } + /* Get the latency, in ms, from AudioFlinger */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * audio_stream_type_t streamType) */ + /* First, try the most recent signature. */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); + if (!ctx->get_output_latency) { + /* in case of failure, try the legacy version. */ + /* status_t AudioSystem::getOutputLatency(uint32_t* latency, + * int streamType) */ + ctx->get_output_latency = + dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); + if (!ctx->get_output_latency) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + } + typedef SLresult (*slCreateEngine_t)(SLObjectItf *, SLuint32, const SLEngineOption *, @@ -696,21 +287,16 @@ opensl_init(cubeb ** context, char const * context_name) 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) { + !ctx->SL_IID_PLAY) { opensl_destroy(ctx); return CUBEB_ERROR; } @@ -751,14 +337,8 @@ opensl_init(cubeb ** context, char const * context_name) return CUBEB_ERROR; } - ctx->p_output_latency_function = cubeb_output_latency_load_method(android_version); - if (!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; } @@ -779,257 +359,200 @@ opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) return CUBEB_OK; } -static void -opensl_destroy(cubeb * ctx) -{ - if (ctx->outmixObj) - (*ctx->outmixObj)->Destroy(ctx->outmixObj); - if (ctx->engObj) - cubeb_destroy_sles_engine(&ctx->engObj); - dlclose(ctx->lib); - if (ctx->p_output_latency_function) - cubeb_output_latency_unload_method(ctx->p_output_latency_function); - free(ctx); -} +static int +opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) +{ + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ + int r; + void * libmedia; + uint32_t (*get_primary_output_samplingrate)(); + uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); + + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + return CUBEB_ERROR; + } -static void opensl_stream_destroy(cubeb_stream * stm); + /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ + get_primary_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); + if (!get_primary_output_samplingrate) { + /* fallback to + * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) + * if we cannot find getPrimaryOutputSamplingRate. */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); + if (!get_output_samplingrate) { + /* Another signature exists, with a int instead of an audio_stream_type_t */ + get_output_samplingrate = + dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); + if (!get_output_samplingrate) { + dlclose(libmedia); + return CUBEB_ERROR; + } + } + } -static int -opensl_set_format(SLDataFormat_PCM * format, cubeb_stream_params * params) -{ - assert(format); - assert(params); + if (get_primary_output_samplingrate) { + *rate = get_primary_output_samplingrate(); + } else { + /* We don't really know about the type, here, so we just pass music. */ + r = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); + if (r) { + dlclose(libmedia); + return CUBEB_ERROR; + } + } - 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; + dlclose(libmedia); + + /* Depending on which method we called above, we can get a zero back, yet have + * a non-error return value, especially if the audio system is not + * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger + * thread). */ + if (*rate == 0) { + return CUBEB_ERROR; } + return CUBEB_OK; } static int -opensl_configure_capture(cubeb_stream * stm, cubeb_stream_params * params) +opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) { - assert(stm); - assert(params); + /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html + * We don't want to deal with JNI here (and we don't have Java on b2g anyways), + * so we just dlopen the library and get the two symbols we need. */ - SLDataLocator_AndroidSimpleBufferQueue lDataLocatorOut; - lDataLocatorOut.locatorType = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE; - lDataLocatorOut.numBuffers = NBUFS; + int r; + void * libmedia; + size_t (*get_primary_output_frame_count)(void); + int (*get_output_frame_count)(size_t * frameCount, int streamType); + uint32_t primary_sampling_rate; + size_t primary_buffer_size; - SLDataFormat_PCM lDataFormat; - int r = opensl_set_format(&lDataFormat, params); - if (r != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } + r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); - /* 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 (r) { + return CUBEB_ERROR; } + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + 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); + /* JB variant */ + /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ + get_primary_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); + if (!get_primary_output_frame_count) { + /* ICS variant */ + /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ + get_output_frame_count = + dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); + if (!get_output_frame_count) { + dlclose(libmedia); 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 ? 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); + if (get_primary_output_frame_count) { + primary_buffer_size = get_primary_output_frame_count(); + } else { + if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { 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; - } + /* To get a fast track in Android's mixer, we need to be at the native + * samplerate, which is device dependant. Some devices might be able to + * resample when playing a fast track, but it's pretty rare. */ + *latency_frames = NBUFS * primary_buffer_size; - (*stm->recorderItf)->SetMarkerPosition(stm->recorderItf, (SLmillisecond)0); + dlclose(libmedia); - 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; - } + return CUBEB_OK; +} - // 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; - } +static void +opensl_destroy(cubeb * ctx) +{ + if (ctx->outmixObj) + (*ctx->outmixObj)->Destroy(ctx->outmixObj); + if (ctx->engObj) + cubeb_destroy_sles_engine(&ctx->engObj); + dlclose(ctx->lib); + dlclose(ctx->libmedia); + free(ctx); +} - // 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); +static void opensl_stream_destroy(cubeb_stream * stm); - // 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); - } +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; - // 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); + assert(ctx); + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; } - // Enqueue buffer to start rolling once recorder started - r = opensl_enqueue_recorder(stm, NULL); - if (r != CUBEB_OK) { - return r; - } + *stream = NULL; - LOG("Cubeb stream init recorder success"); + SLDataFormat_PCM format; - return CUBEB_OK; -} + format.formatType = SL_DATAFORMAT_PCM; + format.numChannels = output_stream_params->channels; + // samplesPerSec is in milliHertz + format.samplesPerSec = output_stream_params->rate * 1000; + format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; + format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; + format.channelMask = output_stream_params->channels == 1 ? + SL_SPEAKER_FRONT_CENTER : + SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; + + switch (output_stream_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; + } -static int -opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { + stm = calloc(1, sizeof(*stm)); assert(stm); - assert(params); - stm->user_output_rate = params->rate; - stm->framesize = params->channels * sizeof(int16_t); + stm->context = ctx; + stm->data_callback = data_callback; + stm->state_callback = state_callback; + stm->user_ptr = user_ptr; + + stm->inputrate = output_stream_params->rate; + stm->latency = latency_frames; + stm->stream_type = output_stream_params->stream_type; + stm->framesize = output_stream_params->channels * sizeof(int16_t); stm->lastPosition = -1; stm->lastPositionTimeStamp = 0; stm->lastCompensativePosition = -1; - SLDataFormat_PCM format; - int r = opensl_set_format(&format, params); - if (r != CUBEB_OK) { - return CUBEB_ERROR_INVALID_FORMAT; - } + int r = pthread_mutex_init(&stm->mutex, NULL); + assert(r == 0); SLDataLocator_BufferQueue loc_bufq; loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; @@ -1040,15 +563,15 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { SLDataLocator_OutputMix loc_outmix; loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; - loc_outmix.outputMix = stm->context->outmixObj; + loc_outmix.outputMix = ctx->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 SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, + ctx->SL_IID_VOLUME, + ctx->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}; @@ -1056,166 +579,115 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { #endif assert(NELEMS(ids) == NELEMS(req)); - uint32_t preferred_sampling_rate = stm->user_output_rate; + uint32_t preferred_sampling_rate = stm->inputrate; +#if defined(__ANDROID__) + if (get_android_version() >= ANDROID_VERSION_MARSHMALLOW) { + // Reset preferred samping rate to trigger fallback to native sampling rate. + preferred_sampling_rate = 0; + if (opensl_get_min_latency(ctx, *output_stream_params, &latency_frames) != CUBEB_OK) { + // Default to AudioFlinger's advertised fast track latency of 10ms. + latency_frames = 440; + } + stm->latency = latency_frames; + } +#endif + 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); + res = (*ctx->eng)->CreateAudioPlayer(ctx->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; + if (res == SL_RESULT_CONTENT_UNSUPPORTED) { + if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + format.samplesPerSec = preferred_sampling_rate * 1000; - res = (*stm->context->eng)->CreateAudioPlayer(stm->context->eng, - &stm->playerObj, - &source, - &sink, - NELEMS(ids), - ids, - req); + res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, + &source, &sink, NELEMS(ids), ids, req); } if (res != SL_RESULT_SUCCESS) { - LOG("Failed to create audio player. Error code: %lu", res); + opensl_stream_destroy(stm); 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; - if (stm->output_enabled) { - // Full duplex, update capacity to hold 1 sec of data - stm->queuebuf_capacity = 1 * stm->output_configured_rate / stm->queuebuf_len; - } - // Allocate input array - 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]); + stm->outputrate = preferred_sampling_rate; + stm->bytespersec = stm->outputrate * stm->framesize; + stm->queuebuf_len = stm->framesize * latency_frames / NBUFS; + // round up to the next multiple of stm->framesize, if needed. + if (stm->queuebuf_len % stm->framesize) { + stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); } - SLAndroidConfigurationItf playerConfig = NULL; + cubeb_stream_params params = *output_stream_params; + params.rate = preferred_sampling_rate; - 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; - } + stm->resampler = cubeb_resampler_create(stm, NULL, ¶ms, + output_stream_params->rate, + data_callback, + user_ptr, + CUBEB_RESAMPLER_QUALITY_DEFAULT); - SLint32 streamType = SL_ANDROID_STREAM_MEDIA; - if (stm->voice) { - 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); - } + if (!stm->resampler) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } - SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_LATENCY; - if (stm->buffer_size_frames > POWERSAVE_LATENCY_FRAMES_THRESHOLD) { - performanceMode = SL_ANDROID_PERFORMANCE_POWER_SAVING; - } + int i; + for (i = 0; i < NBUFS; i++) { + stm->queuebuf[i] = malloc(stm->queuebuf_len); + assert(stm->queuebuf[i]); + } +#if defined(__ANDROID__) + SLuint32 stream_type = convert_stream_type_to_sl_stream(output_stream_params->stream_type); + if (stream_type != 0xFFFFFFFF) { + SLAndroidConfigurationItf playerConfig; + res = (*stm->playerObj)->GetInterface(stm->playerObj, + ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); res = (*playerConfig)->SetConfiguration(playerConfig, - SL_ANDROID_KEY_PERFORMANCE_MODE, - &performanceMode, - sizeof(performanceMode)); + SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to set Android performance mode to %d Error code: %lu. This is" - " not fatal", performanceMode, res); + opensl_stream_destroy(stm); + return CUBEB_ERROR; } } +#endif res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to realize player object. Error code: %lu", res); + opensl_stream_destroy(stm); 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); + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get play interface. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_BUFFERQUEUE, + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, &stm->bufq); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get bufferqueue interface. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } - res = (*stm->playerObj)->GetInterface(stm->playerObj, - stm->context->SL_IID_VOLUME, + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME, &stm->volume); + if (res != SL_RESULT_SUCCESS) { - LOG("Failed to get volume interface. Error code: %lu", res); + opensl_stream_destroy(stm); 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); + opensl_stream_destroy(stm); return CUBEB_ERROR; } @@ -1224,17 +696,13 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { 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); + opensl_stream_destroy(stm); 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); + res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); if (res != SL_RESULT_SUCCESS) { - LOG("Failed to register bufferqueue callback. Error code: %lu", res); + opensl_stream_destroy(stm); return CUBEB_ERROR; } @@ -1249,340 +717,55 @@ opensl_configure_playback(cubeb_stream * stm, cubeb_stream_params * params) { 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 = has_pref_set(input_stream_params, output_stream_params, CUBEB_STREAM_PREF_VOICE); - - LOG("cubeb stream prefs: voice: %s", stm->voice ? "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); - 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) +static void +opensl_stream_destroy(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; - } + if (stm->playerObj) + (*stm->playerObj)->Destroy(stm->playerObj); + int i; + for (i = 0; i < NBUFS; i++) { + free(stm->queuebuf[i]); } - return CUBEB_OK; -} + pthread_mutex_destroy(&stm->mutex); -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; + cubeb_resampler_destroy(stm->resampler); + + free(stm); } 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; - } - } - + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); + if (res != SL_RESULT_SUCCESS) + return CUBEB_ERROR; 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) +opensl_stream_stop(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); + if (res != SL_RESULT_SUCCESS) 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 (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; + uint64_t samplerate; SLresult res; + int r; + uint32_t mixer_latency; + uint32_t compensation_msec = 0; res = (*stm->play)->GetPosition(stm->play, &msec); if (res != SL_RESULT_SUCCESS) @@ -1598,23 +781,27 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) stm->lastPosition = msec; } - uint64_t samplerate = stm->user_output_rate; - uint32_t output_latency = stm->output_latency_ms; + samplerate = stm->inputrate; + + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } pthread_mutex_lock(&stm->mutex); - int64_t maximum_position = stm->written * (int64_t)stm->user_output_rate / stm->output_configured_rate; + int64_t maximum_position = stm->written * (int64_t)stm->inputrate / stm->outputrate; pthread_mutex_unlock(&stm->mutex); assert(maximum_position >= 0); - if (msec > output_latency) { + if (msec > mixer_latency) { int64_t unadjusted_position; if (stm->lastCompensativePosition > msec + compensation_msec) { // Over compensation, use lastCompensativePosition. unadjusted_position = - samplerate * (stm->lastCompensativePosition - output_latency) / 1000; + samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; } else { unadjusted_position = - samplerate * (msec - output_latency + compensation_msec) / 1000; + samplerate * (msec - mixer_latency + compensation_msec) / 1000; stm->lastCompensativePosition = msec + compensation_msec; } *position = unadjusted_position < maximum_position ? @@ -1626,6 +813,24 @@ opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) } int +opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) +{ + int r; + uint32_t mixer_latency; // The latency returned by AudioFlinger is in ms. + + /* audio_stream_type_t is an int, so this is okay. */ + r = stm->context->get_output_latency(&mixer_latency, stm->stream_type); + if (r) { + return CUBEB_ERROR; + } + + *latency = stm->latency * stm->inputrate / 1000 + // OpenSL latency + mixer_latency * stm->inputrate / 1000; // AudioFlinger latency + + return CUBEB_OK; +} + +int opensl_stream_set_volume(cubeb_stream * stm, float volume) { SLresult res; @@ -1660,19 +865,18 @@ 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, + .get_min_latency = opensl_get_min_latency, + .get_preferred_sample_rate = opensl_get_preferred_sample_rate, .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_reset_default_device = NULL, .stream_get_position = opensl_stream_get_position, - .stream_get_latency = NULL, + .stream_get_latency = opensl_stream_get_latency, .stream_set_volume = opensl_stream_set_volume, + .stream_set_panning = NULL, .stream_get_current_device = NULL, .stream_device_destroy = NULL, .stream_register_device_changed_callback = NULL, |