michael@0: /* michael@0: * Copyright © 2013 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: michael@0: #if !defined(NDEBUG) michael@0: #define NDEBUG michael@0: #endif michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include "android/log.h" michael@0: michael@0: #include "cubeb/cubeb.h" michael@0: #include "cubeb-internal.h" michael@0: #include "android/audiotrack_definitions.h" michael@0: michael@0: #ifndef ALOG michael@0: #if defined(DEBUG) || defined(FORCE_ALOG) michael@0: #define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args) michael@0: #else michael@0: #define ALOG(args...) michael@0: #endif michael@0: #endif michael@0: michael@0: /** michael@0: * A lot of bytes for safety. It should be possible to bring this down a bit. */ michael@0: #define SIZE_AUDIOTRACK_INSTANCE 256 michael@0: michael@0: /** michael@0: * call dlsym to get the symbol |mangled_name|, handle the error and store the michael@0: * pointer in |pointer|. Because depending on Android version, we want different michael@0: * symbols, not finding a symbol is not an error. */ michael@0: #define DLSYM_DLERROR(mangled_name, pointer, lib) \ michael@0: do { \ michael@0: pointer = dlsym(lib, mangled_name); \ michael@0: if (!pointer) { \ michael@0: ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \ michael@0: } else { \ michael@0: ALOG("%stm: OK", mangled_name); \ michael@0: } \ michael@0: } while(0); michael@0: michael@0: static struct cubeb_ops const audiotrack_ops; michael@0: void audiotrack_destroy(cubeb * context); michael@0: void audiotrack_stream_destroy(cubeb_stream * stream); michael@0: michael@0: struct AudioTrack { michael@0: /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */ michael@0: /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate); michael@0: /* if we have a recent ctor, but can't find the above symbol, we michael@0: * can get the minimum frame count with this signature, and we are michael@0: * running gingerbread. */ michael@0: /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate); michael@0: /* if this symbol is not availble, and the next one is, we know michael@0: * we are on a Froyo (Android 2.2) device. */ michael@0: void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int); michael@0: void* (*ctor_froyo)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int); michael@0: void* (*dtor)(void* instance); michael@0: void (*start)(void* instance); michael@0: void (*pause)(void* instance); michael@0: uint32_t (*latency)(void* instance); michael@0: status_t (*check)(void* instance); michael@0: status_t (*get_position)(void* instance, uint32_t* position); michael@0: /* only used on froyo. */ michael@0: /* static */ int (*get_output_frame_count)(int* frame_count, int stream); michael@0: /* static */ int (*get_output_latency)(uint32_t* latency, int stream); michael@0: /* static */ int (*get_output_samplingrate)(int* samplerate, int stream); michael@0: status_t (*set_marker_position)(void* instance, unsigned int); michael@0: michael@0: }; michael@0: michael@0: struct cubeb { michael@0: struct cubeb_ops const * ops; michael@0: void * library; michael@0: struct AudioTrack klass; michael@0: }; michael@0: michael@0: struct cubeb_stream { michael@0: cubeb * context; michael@0: cubeb_stream_params params; michael@0: cubeb_data_callback data_callback; michael@0: cubeb_state_callback state_callback; michael@0: void * instance; michael@0: void * user_ptr; michael@0: /* Number of frames that have been passed to the AudioTrack callback */ michael@0: long unsigned written; michael@0: int draining; michael@0: }; michael@0: michael@0: static void michael@0: audiotrack_refill(int event, void* user, void* info) michael@0: { michael@0: cubeb_stream * stream = user; michael@0: switch (event) { michael@0: case EVENT_MORE_DATA: { michael@0: long got = 0; michael@0: struct Buffer * b = (struct Buffer*)info; michael@0: michael@0: if (stream->draining) { michael@0: return; michael@0: } michael@0: michael@0: got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount); michael@0: michael@0: stream->written += got; michael@0: michael@0: if (got != (long)b->frameCount) { michael@0: uint32_t p; michael@0: stream->draining = 1; michael@0: /* set a marker so we are notified when the are done draining, that is, michael@0: * when every frame has been played by android. */ michael@0: stream->context->klass.set_marker_position(stream->instance, stream->written); michael@0: } michael@0: michael@0: break; michael@0: } michael@0: case EVENT_UNDERRUN: michael@0: ALOG("underrun in cubeb backend."); michael@0: break; michael@0: case EVENT_LOOP_END: michael@0: assert(0 && "We don't support the loop feature of audiotrack."); michael@0: break; michael@0: case EVENT_MARKER: michael@0: assert(stream->draining); michael@0: stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); michael@0: break; michael@0: case EVENT_NEW_POS: michael@0: assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack."); michael@0: break; michael@0: case EVENT_BUFFER_END: michael@0: assert(0 && "Should not happen."); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* We are running on froyo if we found the right AudioTrack constructor */ michael@0: static int michael@0: audiotrack_version_is_froyo(cubeb * ctx) michael@0: { michael@0: return ctx->klass.ctor_froyo != NULL; michael@0: } michael@0: michael@0: /* We are running on gingerbread if we found the gingerbread signature for michael@0: * getMinFrameCount */ michael@0: static int michael@0: audiotrack_version_is_gingerbread(cubeb * ctx) michael@0: { michael@0: return ctx->klass.get_min_frame_count_gingerbread != NULL; michael@0: } michael@0: michael@0: int michael@0: audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count) michael@0: { michael@0: status_t status; michael@0: /* Recent Android have a getMinFrameCount method. On Froyo, we have to compute it by hand. */ michael@0: if (audiotrack_version_is_froyo(ctx)) { michael@0: int samplerate, frame_count, latency, min_buffer_count; michael@0: status = ctx->klass.get_output_frame_count(&frame_count, params->stream_type); michael@0: if (status) { michael@0: ALOG("error getting the output frame count."); michael@0: return CUBEB_ERROR; michael@0: } michael@0: status = ctx->klass.get_output_latency((uint32_t*)&latency, params->stream_type); michael@0: if (status) { michael@0: ALOG("error getting the output frame count."); michael@0: return CUBEB_ERROR; michael@0: } michael@0: status = ctx->klass.get_output_samplingrate(&samplerate, params->stream_type); michael@0: if (status) { michael@0: ALOG("error getting the output frame count."); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* Those numbers were found reading the Android source. It is the minimum michael@0: * numbers that will be accepted by the AudioTrack class, hence yielding the michael@0: * best latency possible. michael@0: * See https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/media/libmedia/AudioTrack.cpp michael@0: * around line 181 for Android 2.2 */ michael@0: min_buffer_count = latency / ((1000 * frame_count) / samplerate); michael@0: min_buffer_count = min_buffer_count < 2 ? min_buffer_count : 2; michael@0: *min_frame_count = (frame_count * params->rate * min_buffer_count) / samplerate; michael@0: return CUBEB_OK; michael@0: } michael@0: /* Recent Android have a getMinFrameCount method. */ michael@0: if (!audiotrack_version_is_gingerbread(ctx)) { michael@0: status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); michael@0: } else { michael@0: status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate); michael@0: } michael@0: if (status != 0) { michael@0: ALOG("error getting the min frame count"); michael@0: return CUBEB_ERROR; michael@0: } michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiotrack_init(cubeb ** context, char const * context_name) michael@0: { michael@0: cubeb * ctx; michael@0: struct AudioTrack* c; michael@0: michael@0: assert(context); michael@0: *context = NULL; michael@0: michael@0: ctx = calloc(1, sizeof(*ctx)); michael@0: assert(ctx); michael@0: michael@0: /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android michael@0: * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on michael@0: * the first call to a dlsym'ed function. Somehow this does not happen when michael@0: * using only the name of the library. */ michael@0: ctx->library = dlopen("libmedia.so", RTLD_LAZY); michael@0: if (!ctx->library) { michael@0: ALOG("dlopen error: %s.", dlerror()); michael@0: free(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* Recent Android first, then Froyo. */ michael@0: DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library); michael@0: if (!ctx->klass.ctor) { michael@0: DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i", ctx->klass.ctor_froyo, ctx->library); michael@0: assert(ctx->klass.ctor_froyo); michael@0: } michael@0: DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library); michael@0: michael@0: DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library); michael@0: DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library); michael@0: michael@0: DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library); michael@0: michael@0: /* |getMinFrameCount| is not available on Froyo, and is available on michael@0: * gingerbread and ICS with a different signature. */ michael@0: if (audiotrack_version_is_froyo(ctx)) { michael@0: DLSYM_DLERROR("_ZN7android11AudioSystem19getOutputFrameCountEPii", ctx->klass.get_output_frame_count, ctx->library); michael@0: DLSYM_DLERROR("_ZN7android11AudioSystem16getOutputLatencyEPji", ctx->klass.get_output_latency, ctx->library); michael@0: } else { michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library); michael@0: if (!ctx->klass.get_min_frame_count) { michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library); michael@0: } michael@0: } michael@0: michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library); michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library); michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library); michael@0: DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library); michael@0: michael@0: /* check that we have a combination of symbol that makes sense */ michael@0: c = &ctx->klass; michael@0: if(!((c->ctor || c->ctor_froyo) && /* at least on ctor. */ michael@0: c->dtor && c->latency && c->check && michael@0: /* at least one way to get the minimum frame count to request. */ michael@0: ((c->get_output_frame_count && c->get_output_latency && c->get_output_samplingrate) || michael@0: c->get_min_frame_count || michael@0: c->get_min_frame_count_gingerbread) && michael@0: c->start && c->pause && c->get_position && c->set_marker_position)) { michael@0: ALOG("Could not find all the symbols we need."); michael@0: audiotrack_destroy(ctx); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: ctx->ops = &audiotrack_ops; michael@0: michael@0: *context = ctx; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: char const * michael@0: audiotrack_get_backend_id(cubeb * context) michael@0: { michael@0: return "audiotrack"; michael@0: } michael@0: michael@0: static int michael@0: audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) michael@0: { michael@0: assert(ctx && max_channels); michael@0: 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: audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) michael@0: { michael@0: /* We always use the lowest latency possible when using this backend (see michael@0: * audiotrack_stream_init), so this value is not going to be used. */ michael@0: int rv; michael@0: michael@0: rv = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms); michael@0: if (rv != CUBEB_OK) { michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: /* Convert to milliseconds. */ michael@0: *latency_ms = *latency_ms * 1000 / params.rate; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: static int michael@0: audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) michael@0: { michael@0: status_t rv; michael@0: michael@0: rv = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */); michael@0: michael@0: return rv == 0 ? CUBEB_OK : CUBEB_ERROR; michael@0: } michael@0: michael@0: void michael@0: audiotrack_destroy(cubeb * context) michael@0: { michael@0: assert(context); michael@0: michael@0: dlclose(context->library); michael@0: michael@0: free(context); michael@0: } michael@0: michael@0: int michael@0: audiotrack_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, michael@0: cubeb_state_callback state_callback, michael@0: void * user_ptr) michael@0: { michael@0: cubeb_stream * stm; michael@0: int32_t channels; michael@0: uint32_t min_frame_count; michael@0: michael@0: assert(ctx && stream); michael@0: michael@0: if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE || michael@0: stream_params.format == CUBEB_SAMPLE_FLOAT32BE) { michael@0: return CUBEB_ERROR_INVALID_FORMAT; michael@0: } michael@0: michael@0: if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) { michael@0: return CUBEB_ERROR; 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: stm->params = stream_params; michael@0: michael@0: stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1); michael@0: (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad; michael@0: assert(stm->instance && "cubeb: EOM"); michael@0: michael@0: /* gingerbread uses old channel layout enum */ michael@0: if (audiotrack_version_is_froyo(ctx) || audiotrack_version_is_gingerbread(ctx)) { michael@0: channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy; michael@0: } else { michael@0: channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; michael@0: } michael@0: michael@0: if (audiotrack_version_is_froyo(ctx)) { michael@0: ctx->klass.ctor_froyo(stm->instance, michael@0: stm->params.stream_type, michael@0: stm->params.rate, michael@0: AUDIO_FORMAT_PCM_16_BIT, michael@0: channels, michael@0: min_frame_count, michael@0: 0, michael@0: audiotrack_refill, michael@0: stm, michael@0: 0); michael@0: } else { michael@0: ctx->klass.ctor(stm->instance, michael@0: stm->params.stream_type, michael@0: stm->params.rate, michael@0: AUDIO_FORMAT_PCM_16_BIT, michael@0: channels, michael@0: min_frame_count, michael@0: 0, michael@0: audiotrack_refill, michael@0: stm, michael@0: 0, michael@0: 0); michael@0: } michael@0: michael@0: assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad); michael@0: michael@0: if (ctx->klass.check(stm->instance)) { michael@0: ALOG("stream not initialized properly."); michael@0: audiotrack_stream_destroy(stm); michael@0: return CUBEB_ERROR; michael@0: } michael@0: michael@0: *stream = stm; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: void michael@0: audiotrack_stream_destroy(cubeb_stream * stream) michael@0: { michael@0: assert(stream->context); michael@0: michael@0: stream->context->klass.dtor(stream->instance); michael@0: michael@0: free(stream->instance); michael@0: stream->instance = NULL; michael@0: free(stream); michael@0: } michael@0: michael@0: int michael@0: audiotrack_stream_start(cubeb_stream * stream) michael@0: { michael@0: assert(stream->instance); michael@0: michael@0: stream->context->klass.start(stream->instance); michael@0: stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiotrack_stream_stop(cubeb_stream * stream) michael@0: { michael@0: assert(stream->instance); michael@0: michael@0: stream->context->klass.pause(stream->instance); michael@0: stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position) michael@0: { michael@0: uint32_t p; michael@0: michael@0: assert(stream->instance && position); michael@0: stream->context->klass.get_position(stream->instance, &p); michael@0: *position = p; michael@0: michael@0: return CUBEB_OK; michael@0: } michael@0: michael@0: int michael@0: audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency) michael@0: { michael@0: assert(stream->instance && latency); michael@0: michael@0: /* Android returns the latency in ms, we want it in frames. */ michael@0: *latency = stream->context->klass.latency(stream->instance); michael@0: /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */ michael@0: *latency = (*latency * stream->params.rate) / 1000; michael@0: michael@0: return 0; michael@0: } michael@0: michael@0: static struct cubeb_ops const audiotrack_ops = { michael@0: .init = audiotrack_init, michael@0: .get_backend_id = audiotrack_get_backend_id, michael@0: .get_max_channel_count = audiotrack_get_max_channel_count, michael@0: .get_min_latency = audiotrack_get_min_latency, michael@0: .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, michael@0: .destroy = audiotrack_destroy, michael@0: .stream_init = audiotrack_stream_init, michael@0: .stream_destroy = audiotrack_stream_destroy, michael@0: .stream_start = audiotrack_stream_start, michael@0: .stream_stop = audiotrack_stream_stop, michael@0: .stream_get_position = audiotrack_stream_get_position, michael@0: .stream_get_latency = audiotrack_stream_get_latency michael@0: };