michael@0: /* michael@0: * Copyright © 2012 Mozilla Foundation michael@0: * michael@0: * This program is made available under an ISC-style license. See the michael@0: * accompanying file LICENSE for details. michael@0: */ michael@0: #undef NDEBUG michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #if defined(__ANDROID__) michael@0: #include "android/sles_definitions.h" michael@0: #include michael@0: #include michael@0: #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args) michael@0: #endif michael@0: #include "cubeb/cubeb.h" michael@0: #include "cubeb-internal.h" michael@0: michael@0: static struct cubeb_ops const opensl_ops; michael@0: michael@0: struct cubeb { michael@0: struct cubeb_ops const * ops; michael@0: void * lib; michael@0: void * libmedia; michael@0: int32_t (* get_output_latency)(uint32_t * latency, int stream_type); michael@0: SLInterfaceID SL_IID_BUFFERQUEUE; michael@0: SLInterfaceID SL_IID_PLAY; michael@0: #if defined(__ANDROID__) michael@0: SLInterfaceID SL_IID_ANDROIDCONFIGURATION; michael@0: #endif michael@0: SLObjectItf engObj; michael@0: SLEngineItf eng; michael@0: SLObjectItf outmixObj; michael@0: }; michael@0: michael@0: #define NELEMS(A) (sizeof(A) / sizeof A[0]) michael@0: #define NBUFS 4 michael@0: #define AUDIO_STREAM_TYPE_MUSIC 3 michael@0: michael@0: struct cubeb_stream { michael@0: cubeb * context; michael@0: SLObjectItf playerObj; michael@0: SLPlayItf play; michael@0: SLBufferQueueItf bufq; michael@0: void *queuebuf[NBUFS]; michael@0: int queuebuf_idx; michael@0: long queuebuf_len; michael@0: long bytespersec; michael@0: long framesize; michael@0: int draining; michael@0: cubeb_stream_type stream_type; michael@0: michael@0: cubeb_data_callback data_callback; michael@0: cubeb_state_callback state_callback; michael@0: void * user_ptr; michael@0: }; michael@0: michael@0: static void michael@0: bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr) michael@0: { michael@0: cubeb_stream * stm = user_ptr; michael@0: SLBufferQueueState state; michael@0: (*stm->bufq)->GetState(stm->bufq, &state); michael@0: michael@0: if (stm->draining) { michael@0: if (!state.count) { michael@0: stm->draining = 0; michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (state.count > 1) michael@0: return; michael@0: michael@0: SLuint32 i; michael@0: for (i = state.count; i < NBUFS; i++) { michael@0: void *buf = stm->queuebuf[stm->queuebuf_idx]; michael@0: long written = stm->data_callback(stm, stm->user_ptr, michael@0: buf, stm->queuebuf_len / stm->framesize); michael@0: if (written == CUBEB_ERROR) { michael@0: (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED); michael@0: return; michael@0: } michael@0: michael@0: if (written) { michael@0: (*stm->bufq)->Enqueue(stm->bufq, buf, written * stm->framesize); michael@0: stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS; michael@0: } else if (!i) { michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED); michael@0: return; michael@0: } michael@0: michael@0: if ((written * stm->framesize) < stm->queuebuf_len) { michael@0: stm->draining = 1; michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: #if defined(__ANDROID__) michael@0: static SLuint32 michael@0: convert_stream_type_to_sl_stream(cubeb_stream_type stream_type) michael@0: { michael@0: switch(stream_type) { michael@0: case CUBEB_STREAM_TYPE_SYSTEM: michael@0: return SL_ANDROID_STREAM_SYSTEM; michael@0: case CUBEB_STREAM_TYPE_MUSIC: michael@0: return SL_ANDROID_STREAM_MEDIA; michael@0: case CUBEB_STREAM_TYPE_NOTIFICATION: michael@0: return SL_ANDROID_STREAM_NOTIFICATION; michael@0: case CUBEB_STREAM_TYPE_ALARM: michael@0: return SL_ANDROID_STREAM_ALARM; michael@0: case CUBEB_STREAM_TYPE_VOICE_CALL: michael@0: return SL_ANDROID_STREAM_VOICE; michael@0: case CUBEB_STREAM_TYPE_RING: michael@0: return SL_ANDROID_STREAM_RING; michael@0: case CUBEB_STREAM_TYPE_ENFORCED_AUDIBLE: michael@0: default: michael@0: return 0xFFFFFFFF; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: static void opensl_destroy(cubeb * ctx); michael@0: michael@0: /*static*/ int michael@0: opensl_init(cubeb ** context, char const * context_name) michael@0: { michael@0: cubeb * ctx; michael@0: michael@0: *context = NULL; michael@0: michael@0: ctx = calloc(1, sizeof(*ctx)); michael@0: assert(ctx); michael@0: michael@0: ctx->ops = &opensl_ops; michael@0: michael@0: ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY); michael@0: ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY); michael@0: if (!ctx->lib || !ctx->libmedia) { michael@0: free(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* Get the latency, in ms, from AudioFlinger */ michael@0: /* status_t AudioSystem::getOutputLatency(uint32_t* latency, michael@0: * audio_stream_type_t streamType) */ michael@0: /* First, try the most recent signature. */ michael@0: ctx->get_output_latency = michael@0: dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t"); michael@0: if (!ctx->get_output_latency) { michael@0: /* in case of failure, try the legacy version. */ michael@0: /* status_t AudioSystem::getOutputLatency(uint32_t* latency, michael@0: * int streamType) */ michael@0: ctx->get_output_latency = michael@0: dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji"); michael@0: if (!ctx->get_output_latency) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: michael@0: typedef SLresult (*slCreateEngine_t)(SLObjectItf *, michael@0: SLuint32, michael@0: const SLEngineOption *, michael@0: SLuint32, michael@0: const SLInterfaceID *, michael@0: const SLboolean *); michael@0: slCreateEngine_t f_slCreateEngine = michael@0: (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine"); michael@0: SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE"); michael@0: SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX"); michael@0: ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE"); michael@0: #if defined(__ANDROID__) michael@0: ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION"); michael@0: #endif michael@0: ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY"); michael@0: if (!f_slCreateEngine || michael@0: !SL_IID_ENGINE || michael@0: !SL_IID_OUTPUTMIX || michael@0: !ctx->SL_IID_BUFFERQUEUE || michael@0: #if defined(__ANDROID__) michael@0: !ctx->SL_IID_ANDROIDCONFIGURATION || michael@0: #endif michael@0: !ctx->SL_IID_PLAY) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}}; michael@0: michael@0: SLresult res; michael@0: res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, NULL, NULL); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX}; michael@0: const SLboolean reqom[] = {SL_BOOLEAN_TRUE}; michael@0: res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *context = ctx; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static char const * michael@0: opensl_get_backend_id(cubeb * ctx) michael@0: { michael@0: return "opensl"; michael@0: } michael@0: michael@0: static int michael@0: opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) michael@0: { michael@0: assert(ctx && max_channels); michael@0: /* The android mixer handles up to two channels, see michael@0: http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */ michael@0: *max_channels = 2; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) michael@0: { michael@0: /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html michael@0: * We don't want to deal with JNI here (and we don't have Java on b2g anyways), michael@0: * so we just dlopen the library and get the two symbols we need. */ michael@0: int rv; michael@0: void * libmedia; michael@0: uint32_t (*get_primary_output_samplingrate)(); michael@0: uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType); michael@0: uint32_t primary_sampling_rate; michael@0: michael@0: libmedia = dlopen("libmedia.so", RTLD_LAZY); michael@0: if (!libmedia) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */ michael@0: get_primary_output_samplingrate = michael@0: dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv"); michael@0: if (!get_primary_output_samplingrate) { michael@0: /* fallback to michael@0: * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType) michael@0: * if we cannot find getPrimaryOutputSamplingRate. */ michael@0: get_output_samplingrate = michael@0: dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t"); michael@0: if (!get_output_samplingrate) { michael@0: /* Another signature exists, with a int instead of an audio_stream_type_t */ michael@0: get_output_samplingrate = michael@0: dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii"); michael@0: if (!get_output_samplingrate) { michael@0: dlclose(libmedia); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (get_primary_output_samplingrate) { michael@0: *rate = get_primary_output_samplingrate(); michael@0: } else { michael@0: /* We don't really know about the type, here, so we just pass music. */ michael@0: rv = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC); michael@0: if (rv) { michael@0: dlclose(libmedia); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: michael@0: dlclose(libmedia); michael@0: michael@0: /* Depending on which method we called above, we can get a zero back, yet have michael@0: * a non-error return value, especially if the audio system is not michael@0: * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger michael@0: * thread). */ michael@0: if (*rate == 0) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) michael@0: { michael@0: /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html michael@0: * We don't want to deal with JNI here (and we don't have Java on b2g anyways), michael@0: * so we just dlopen the library and get the two symbols we need. */ michael@0: michael@0: int rv; michael@0: void * libmedia; michael@0: size_t (*get_primary_output_frame_count)(void); michael@0: int (*get_output_frame_count)(size_t * frameCount, int streamType); michael@0: uint32_t primary_sampling_rate; michael@0: size_t primary_buffer_size; michael@0: michael@0: rv = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate); michael@0: michael@0: if (rv) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: libmedia = dlopen("libmedia.so", RTLD_LAZY); michael@0: if (!libmedia) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* JB variant */ michael@0: /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */ michael@0: get_primary_output_frame_count = michael@0: dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv"); michael@0: if (!get_primary_output_frame_count) { michael@0: /* ICS variant */ michael@0: /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */ michael@0: get_output_frame_count = michael@0: dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii"); michael@0: if (!get_output_frame_count) { michael@0: dlclose(libmedia); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: michael@0: if (get_primary_output_frame_count) { michael@0: primary_buffer_size = get_primary_output_frame_count(); michael@0: } else { michael@0: if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: michael@0: /* To get a fast track in Android's mixer, we need to be at the native michael@0: * samplerate, which is device dependant. Some devices might be able to michael@0: * resample when playing a fast track, but it's pretty rare. */ michael@0: *latency_ms = NBUFS * primary_buffer_size / (primary_sampling_rate / 1000); michael@0: michael@0: dlclose(libmedia); michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static void michael@0: opensl_destroy(cubeb * ctx) michael@0: { michael@0: if (ctx->outmixObj) michael@0: (*ctx->outmixObj)->Destroy(ctx->outmixObj); michael@0: if (ctx->engObj) michael@0: (*ctx->engObj)->Destroy(ctx->engObj); michael@0: dlclose(ctx->lib); michael@0: dlclose(ctx->libmedia); michael@0: free(ctx); michael@0: } michael@0: michael@0: static void opensl_stream_destroy(cubeb_stream * stm); michael@0: michael@0: static int michael@0: opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, michael@0: cubeb_stream_params stream_params, unsigned int latency, michael@0: cubeb_data_callback data_callback, cubeb_state_callback state_callback, michael@0: void * user_ptr) michael@0: { michael@0: cubeb_stream * stm; michael@0: michael@0: assert(ctx); michael@0: michael@0: *stream = NULL; michael@0: michael@0: if (stream_params.rate < 8000 || stream_params.rate > 88200 || michael@0: stream_params.channels < 1 || stream_params.channels > 32 || michael@0: latency < 1 || latency > 2000) { michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: michael@0: SLDataFormat_PCM format; michael@0: michael@0: format.formatType = SL_DATAFORMAT_PCM; michael@0: format.numChannels = stream_params.channels; michael@0: // samplesPerSec is in milliHertz michael@0: format.samplesPerSec = stream_params.rate * 1000; michael@0: format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16; michael@0: format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16; michael@0: format.channelMask = stream_params.channels == 1 ? michael@0: SL_SPEAKER_FRONT_CENTER : michael@0: SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; michael@0: michael@0: switch (stream_params.format) { michael@0: case CUBEB_SAMPLE_S16LE: michael@0: format.endianness = SL_BYTEORDER_LITTLEENDIAN; michael@0: break; michael@0: case CUBEB_SAMPLE_S16BE: michael@0: format.endianness = SL_BYTEORDER_BIGENDIAN; michael@0: break; michael@0: default: michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: michael@0: stm = calloc(1, sizeof(*stm)); michael@0: assert(stm); michael@0: michael@0: stm->context = ctx; michael@0: stm->data_callback = data_callback; michael@0: stm->state_callback = state_callback; michael@0: stm->user_ptr = user_ptr; michael@0: michael@0: stm->stream_type = stream_params.stream_type; michael@0: stm->framesize = stream_params.channels * sizeof(int16_t); michael@0: stm->bytespersec = stream_params.rate * stm->framesize; michael@0: stm->queuebuf_len = (stm->bytespersec * latency) / (1000 * NBUFS); michael@0: // round up to the next multiple of stm->framesize, if needed. michael@0: if (stm->queuebuf_len % stm->framesize) { michael@0: stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize); michael@0: } michael@0: int i; michael@0: for (i = 0; i < NBUFS; i++) { michael@0: stm->queuebuf[i] = malloc(stm->queuebuf_len); michael@0: assert(stm->queuebuf[i]); michael@0: } michael@0: michael@0: SLDataLocator_BufferQueue loc_bufq; michael@0: loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE; michael@0: loc_bufq.numBuffers = NBUFS; michael@0: SLDataSource source; michael@0: source.pLocator = &loc_bufq; michael@0: source.pFormat = &format; michael@0: michael@0: SLDataLocator_OutputMix loc_outmix; michael@0: loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX; michael@0: loc_outmix.outputMix = ctx->outmixObj; michael@0: SLDataSink sink; michael@0: sink.pLocator = &loc_outmix; michael@0: sink.pFormat = NULL; michael@0: michael@0: #if defined(__ANDROID__) michael@0: const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_ANDROIDCONFIGURATION}; michael@0: const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; michael@0: #else michael@0: const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE}; michael@0: const SLboolean req[] = {SL_BOOLEAN_TRUE}; michael@0: #endif michael@0: assert(NELEMS(ids) == NELEMS(req)); michael@0: SLresult res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj, michael@0: &source, &sink, NELEMS(ids), ids, req); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: #if defined(__ANDROID__) michael@0: SLuint32 stream_type = convert_stream_type_to_sl_stream(stream_params.stream_type); michael@0: if (stream_type != 0xFFFFFFFF) { michael@0: SLAndroidConfigurationItf playerConfig; michael@0: res = (*stm->playerObj)->GetInterface(stm->playerObj, michael@0: ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig); michael@0: res = (*playerConfig)->SetConfiguration(playerConfig, michael@0: SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32)); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE, michael@0: &stm->bufq); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm); michael@0: if (res != SL_RESULT_SUCCESS) { michael@0: opensl_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *stream = stm; michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static void michael@0: opensl_stream_destroy(cubeb_stream * stm) michael@0: { michael@0: if (stm->playerObj) michael@0: (*stm->playerObj)->Destroy(stm->playerObj); michael@0: int i; michael@0: for (i = 0; i < NBUFS; i++) { michael@0: free(stm->queuebuf[i]); michael@0: } michael@0: michael@0: free(stm); michael@0: } michael@0: michael@0: static int michael@0: opensl_stream_start(cubeb_stream * stm) michael@0: { michael@0: /* To refill the queues before starting playback in order to avoid racing michael@0: * with refills started by SetPlayState on OpenSLES ndk threads. */ michael@0: bufferqueue_callback(NULL, stm); michael@0: SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING); michael@0: if (res != SL_RESULT_SUCCESS) michael@0: return CUBEB_ERROR; michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: opensl_stream_stop(cubeb_stream * stm) michael@0: { michael@0: SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED); michael@0: if (res != SL_RESULT_SUCCESS) michael@0: return CUBEB_ERROR; michael@0: stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED); michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: opensl_stream_get_position(cubeb_stream * stm, uint64_t * position) michael@0: { michael@0: SLmillisecond msec; michael@0: uint64_t samplerate; michael@0: SLresult res; michael@0: int rv; michael@0: uint32_t mixer_latency; michael@0: michael@0: res = (*stm->play)->GetPosition(stm->play, &msec); michael@0: if (res != SL_RESULT_SUCCESS) michael@0: return CUBEB_ERROR; michael@0: michael@0: samplerate = stm->bytespersec / stm->framesize; michael@0: michael@0: rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type); michael@0: if (rv) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: if (msec > mixer_latency) { michael@0: *position = samplerate * (msec - mixer_latency) / 1000; michael@0: } else { michael@0: *position = 0; michael@0: } michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency) michael@0: { michael@0: int rv; michael@0: uint32_t mixer_latency; michael@0: uint32_t samplerate; michael@0: michael@0: /* The latency returned by AudioFlinger is in ms, so we have to get michael@0: * AudioFlinger's samplerate to convert it to frames. */ michael@0: rv = opensl_get_preferred_sample_rate(stm->context, &samplerate); michael@0: if (rv) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* audio_stream_type_t is an int, so this is okay. */ michael@0: rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type); michael@0: if (rv) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *latency = NBUFS * stm->queuebuf_len / stm->framesize + // OpenSL latency michael@0: mixer_latency * samplerate / 1000; // AudioFlinger latency michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static struct cubeb_ops const opensl_ops = { michael@0: .init = opensl_init, michael@0: .get_backend_id = opensl_get_backend_id, michael@0: .get_max_channel_count = opensl_get_max_channel_count, michael@0: .get_min_latency = opensl_get_min_latency, michael@0: .get_preferred_sample_rate = opensl_get_preferred_sample_rate, michael@0: .destroy = opensl_destroy, michael@0: .stream_init = opensl_stream_init, michael@0: .stream_destroy = opensl_stream_destroy, michael@0: .stream_start = opensl_stream_start, michael@0: .stream_stop = opensl_stream_stop, michael@0: .stream_get_position = opensl_stream_get_position, michael@0: .stream_get_latency = opensl_stream_get_latency michael@0: };