diff options
Diffstat (limited to 'media/libcubeb/src/cubeb_opensl.c')
-rw-r--r-- | media/libcubeb/src/cubeb_opensl.c | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/media/libcubeb/src/cubeb_opensl.c b/media/libcubeb/src/cubeb_opensl.c new file mode 100644 index 000000000..d44a56bd7 --- /dev/null +++ b/media/libcubeb/src/cubeb_opensl.c @@ -0,0 +1,889 @@ +/* + * Copyright © 2012 Mozilla Foundation + * + * This program is made available under an ISC-style license. See the + * accompanying file LICENSE for details. + */ +#undef NDEBUG +#include <assert.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <pthread.h> +#include <SLES/OpenSLES.h> +#include <math.h> +#include <time.h> +#if defined(__ANDROID__) +#include <dlfcn.h> +#include <sys/system_properties.h> +#include "android/sles_definitions.h" +#include <SLES/OpenSLES_Android.h> +#include <android/log.h> +#include <android/api-level.h> +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) +#define ANDROID_VERSION_GINGERBREAD_MR1 10 +#define ANDROID_VERSION_LOLLIPOP 21 +#define ANDROID_VERSION_MARSHMALLOW 23 +#endif +#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; +#endif + SLInterfaceID SL_IID_VOLUME; + SLObjectItf engObj; + SLEngineItf eng; + SLObjectItf outmixObj; +}; + +#define NELEMS(A) (sizeof(A) / sizeof A[0]) +#define NBUFS 4 +#define AUDIO_STREAM_TYPE_MUSIC 3 + +struct cubeb_stream { + cubeb * context; + pthread_mutex_t mutex; + SLObjectItf playerObj; + SLPlayItf play; + SLBufferQueueItf bufq; + SLVolumeItf volume; + uint8_t *queuebuf[NBUFS]; + int queuebuf_idx; + long queuebuf_len; + long bytespersec; + long framesize; + long written; + int draining; + cubeb_stream_type stream_type; + + cubeb_data_callback data_callback; + cubeb_state_callback state_callback; + void * user_ptr; + + cubeb_resampler * resampler; + unsigned int inputrate; + unsigned int outputrate; + unsigned int latency; + int64_t lastPosition; + int64_t lastPositionTimeStamp; + int64_t lastCompensativePosition; +}; + +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: + 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); + (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); + } + break; + default: + break; + } +} + +static void +bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) +{ + cubeb_stream * stm = user_ptr; + assert(stm); + SLBufferQueueState state; + SLresult res; + + res = (*stm->bufq)->GetState(stm->bufq, &state); + assert(res == SL_RESULT_SUCCESS); + + if (state.count > 1) + return; + + 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); + int draining = stm->draining; + pthread_mutex_unlock(&stm->mutex); + + 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; + } + } +} + +#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; + } +} +#endif + +static void opensl_destroy(cubeb * ctx); + +#if defined(__ANDROID__) + +// The bionic header file on B2G contains the required +// declarations on all releases. +#ifndef MOZ_WIDGET_GONK + +#if (__ANDROID_API__ >= ANDROID_VERSION_LOLLIPOP) +typedef int (system_property_get)(const char*, char*); + +static int +__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 +#endif + +static int +get_android_version(void) +{ + char version_string[PROP_VALUE_MAX]; + + memset(version_string, 0, PROP_VALUE_MAX); + + int len = __system_property_get("ro.build.version.sdk", version_string); + if (len <= 0) { + LOG("Failed to get Android version!\n"); + return len; + } + + int version = (int)strtol(version_string, NULL, 10); + LOG("%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); + 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 *, + 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"); +#endif + ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); + if (!f_slCreateEngine || + !SL_IID_ENGINE || + !SL_IID_OUTPUTMIX || + !ctx->SL_IID_BUFFERQUEUE || +#if defined(__ANDROID__) + !ctx->SL_IID_ANDROIDCONFIGURATION || +#endif + !ctx->SL_IID_PLAY) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; + + SLresult res; + res = cubeb_get_sles_engine(&ctx->engObj, 1, opt, 0, NULL, NULL); + + if (res != SL_RESULT_SUCCESS) { + opensl_destroy(ctx); + return CUBEB_ERROR; + } + + res = cubeb_realize_sles_engine(ctx->engObj); + 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; + } + + *context = 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 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; + } + + /* 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; + } + } + } + + 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; + } + } + + 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_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_frames) +{ + /* 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; + 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; + + r = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); + + if (r) { + return CUBEB_ERROR; + } + + libmedia = dlopen("libmedia.so", RTLD_LAZY); + if (!libmedia) { + return CUBEB_ERROR; + } + + /* 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; + } + } + + 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; + } + } + + /* 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; + + dlclose(libmedia); + + 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); + dlclose(ctx->libmedia); + free(ctx); +} + +static void opensl_stream_destroy(cubeb_stream * stm); + +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); + assert(!input_stream_params && "not supported"); + if (input_device || output_device) { + /* Device selection not yet implemented. */ + return CUBEB_ERROR_DEVICE_UNAVAILABLE; + } + + *stream = NULL; + + SLDataFormat_PCM format; + + 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; + } + + 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->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; + + int r = pthread_mutex_init(&stm->mutex, NULL); + assert(r == 0); + + 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 = ctx->outmixObj; + SLDataSink sink; + sink.pLocator = &loc_outmix; + sink.pFormat = NULL; + +#if defined(__ANDROID__) + 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}; + const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; +#endif + assert(NELEMS(ids) == NELEMS(req)); + + 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 = (*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) { + if (opensl_get_preferred_sample_rate(ctx, &preferred_sampling_rate)) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + format.samplesPerSec = preferred_sampling_rate * 1000; + res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, + &source, &sink, NELEMS(ids), ids, req); + } + + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + 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); + } + + cubeb_stream_params params = *output_stream_params; + params.rate = preferred_sampling_rate; + + stm->resampler = cubeb_resampler_create(stm, NULL, ¶ms, + output_stream_params->rate, + data_callback, + user_ptr, + CUBEB_RESAMPLER_QUALITY_DEFAULT); + + if (!stm->resampler) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + 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_STREAM_TYPE, &stream_type, sizeof(SLint32)); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + } +#endif + + res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, + &stm->bufq); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_VOLUME, + &stm->volume); + + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + res = (*stm->play)->RegisterCallback(stm->play, play_callback, stm); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + 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) { + opensl_stream_destroy(stm); + return CUBEB_ERROR; + } + + res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); + if (res != SL_RESULT_SUCCESS) { + opensl_stream_destroy(stm); + 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); + } + + *stream = stm; + return CUBEB_OK; +} + +static void +opensl_stream_destroy(cubeb_stream * stm) +{ + if (stm->playerObj) + (*stm->playerObj)->Destroy(stm->playerObj); + int i; + for (i = 0; i < NBUFS; i++) { + free(stm->queuebuf[i]); + } + pthread_mutex_destroy(&stm->mutex); + + cubeb_resampler_destroy(stm->resampler); + + free(stm); +} + +static int +opensl_stream_start(cubeb_stream * stm) +{ + 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); + return CUBEB_OK; +} + +static int +opensl_stream_stop(cubeb_stream * stm) +{ + SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); + if (res != SL_RESULT_SUCCESS) + return CUBEB_ERROR; + stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); + return CUBEB_OK; +} + +static int +opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) +{ + SLmillisecond msec; + 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) + 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; + } + + 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->inputrate / stm->outputrate; + pthread_mutex_unlock(&stm->mutex); + assert(maximum_position >= 0); + + if (msec > mixer_latency) { + int64_t unadjusted_position; + if (stm->lastCompensativePosition > msec + compensation_msec) { + // Over compensation, use lastCompensativePosition. + unadjusted_position = + samplerate * (stm->lastCompensativePosition - mixer_latency) / 1000; + } else { + unadjusted_position = + samplerate * (msec - mixer_latency + compensation_msec) / 1000; + stm->lastCompensativePosition = msec + compensation_msec; + } + *position = unadjusted_position < maximum_position ? + unadjusted_position : maximum_position; + } else { + *position = 0; + } + return CUBEB_OK; +} + +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; + 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 = opensl_get_min_latency, + .get_preferred_sample_rate = opensl_get_preferred_sample_rate, + .enumerate_devices = 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_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, + .register_device_collection_changed = NULL +}; |