/* * 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 #if defined(__ANDROID__) #include #include #include "android/sles_definitions.h" #include #include #include #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__) #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 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 };