1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/media/libcubeb/src/cubeb_audiotrack.c Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,484 @@ 1.4 +/* 1.5 + * Copyright © 2013 Mozilla Foundation 1.6 + * 1.7 + * This program is made available under an ISC-style license. See the 1.8 + * accompanying file LICENSE for details. 1.9 + */ 1.10 + 1.11 +#if !defined(NDEBUG) 1.12 +#define NDEBUG 1.13 +#endif 1.14 +#include <assert.h> 1.15 +#include <pthread.h> 1.16 +#include <stdlib.h> 1.17 +#include <time.h> 1.18 +#include <dlfcn.h> 1.19 +#include "android/log.h" 1.20 + 1.21 +#include "cubeb/cubeb.h" 1.22 +#include "cubeb-internal.h" 1.23 +#include "android/audiotrack_definitions.h" 1.24 + 1.25 +#ifndef ALOG 1.26 +#if defined(DEBUG) || defined(FORCE_ALOG) 1.27 +#define ALOG(args...) __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb" , ## args) 1.28 +#else 1.29 +#define ALOG(args...) 1.30 +#endif 1.31 +#endif 1.32 + 1.33 +/** 1.34 + * A lot of bytes for safety. It should be possible to bring this down a bit. */ 1.35 +#define SIZE_AUDIOTRACK_INSTANCE 256 1.36 + 1.37 +/** 1.38 + * call dlsym to get the symbol |mangled_name|, handle the error and store the 1.39 + * pointer in |pointer|. Because depending on Android version, we want different 1.40 + * symbols, not finding a symbol is not an error. */ 1.41 +#define DLSYM_DLERROR(mangled_name, pointer, lib) \ 1.42 + do { \ 1.43 + pointer = dlsym(lib, mangled_name); \ 1.44 + if (!pointer) { \ 1.45 + ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \ 1.46 + } else { \ 1.47 + ALOG("%stm: OK", mangled_name); \ 1.48 + } \ 1.49 + } while(0); 1.50 + 1.51 +static struct cubeb_ops const audiotrack_ops; 1.52 +void audiotrack_destroy(cubeb * context); 1.53 +void audiotrack_stream_destroy(cubeb_stream * stream); 1.54 + 1.55 +struct AudioTrack { 1.56 + /* only available on ICS and later. The second int paramter is in fact of type audio_stream_type_t. */ 1.57 + /* static */ status_t (*get_min_frame_count)(int* frame_count, int stream_type, uint32_t rate); 1.58 + /* if we have a recent ctor, but can't find the above symbol, we 1.59 + * can get the minimum frame count with this signature, and we are 1.60 + * running gingerbread. */ 1.61 + /* static */ status_t (*get_min_frame_count_gingerbread)(int* frame_count, int stream_type, uint32_t rate); 1.62 + /* if this symbol is not availble, and the next one is, we know 1.63 + * we are on a Froyo (Android 2.2) device. */ 1.64 + void* (*ctor)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int, int); 1.65 + void* (*ctor_froyo)(void* instance, int, unsigned int, int, int, int, unsigned int, void (*)(int, void*, void*), void*, int); 1.66 + void* (*dtor)(void* instance); 1.67 + void (*start)(void* instance); 1.68 + void (*pause)(void* instance); 1.69 + uint32_t (*latency)(void* instance); 1.70 + status_t (*check)(void* instance); 1.71 + status_t (*get_position)(void* instance, uint32_t* position); 1.72 + /* only used on froyo. */ 1.73 + /* static */ int (*get_output_frame_count)(int* frame_count, int stream); 1.74 + /* static */ int (*get_output_latency)(uint32_t* latency, int stream); 1.75 + /* static */ int (*get_output_samplingrate)(int* samplerate, int stream); 1.76 + status_t (*set_marker_position)(void* instance, unsigned int); 1.77 + 1.78 +}; 1.79 + 1.80 +struct cubeb { 1.81 + struct cubeb_ops const * ops; 1.82 + void * library; 1.83 + struct AudioTrack klass; 1.84 +}; 1.85 + 1.86 +struct cubeb_stream { 1.87 + cubeb * context; 1.88 + cubeb_stream_params params; 1.89 + cubeb_data_callback data_callback; 1.90 + cubeb_state_callback state_callback; 1.91 + void * instance; 1.92 + void * user_ptr; 1.93 + /* Number of frames that have been passed to the AudioTrack callback */ 1.94 + long unsigned written; 1.95 + int draining; 1.96 +}; 1.97 + 1.98 +static void 1.99 +audiotrack_refill(int event, void* user, void* info) 1.100 +{ 1.101 + cubeb_stream * stream = user; 1.102 + switch (event) { 1.103 + case EVENT_MORE_DATA: { 1.104 + long got = 0; 1.105 + struct Buffer * b = (struct Buffer*)info; 1.106 + 1.107 + if (stream->draining) { 1.108 + return; 1.109 + } 1.110 + 1.111 + got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount); 1.112 + 1.113 + stream->written += got; 1.114 + 1.115 + if (got != (long)b->frameCount) { 1.116 + uint32_t p; 1.117 + stream->draining = 1; 1.118 + /* set a marker so we are notified when the are done draining, that is, 1.119 + * when every frame has been played by android. */ 1.120 + stream->context->klass.set_marker_position(stream->instance, stream->written); 1.121 + } 1.122 + 1.123 + break; 1.124 + } 1.125 + case EVENT_UNDERRUN: 1.126 + ALOG("underrun in cubeb backend."); 1.127 + break; 1.128 + case EVENT_LOOP_END: 1.129 + assert(0 && "We don't support the loop feature of audiotrack."); 1.130 + break; 1.131 + case EVENT_MARKER: 1.132 + assert(stream->draining); 1.133 + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED); 1.134 + break; 1.135 + case EVENT_NEW_POS: 1.136 + assert(0 && "We don't support the setPositionUpdatePeriod feature of audiotrack."); 1.137 + break; 1.138 + case EVENT_BUFFER_END: 1.139 + assert(0 && "Should not happen."); 1.140 + break; 1.141 + } 1.142 +} 1.143 + 1.144 +/* We are running on froyo if we found the right AudioTrack constructor */ 1.145 +static int 1.146 +audiotrack_version_is_froyo(cubeb * ctx) 1.147 +{ 1.148 + return ctx->klass.ctor_froyo != NULL; 1.149 +} 1.150 + 1.151 +/* We are running on gingerbread if we found the gingerbread signature for 1.152 + * getMinFrameCount */ 1.153 +static int 1.154 +audiotrack_version_is_gingerbread(cubeb * ctx) 1.155 +{ 1.156 + return ctx->klass.get_min_frame_count_gingerbread != NULL; 1.157 +} 1.158 + 1.159 +int 1.160 +audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params, int * min_frame_count) 1.161 +{ 1.162 + status_t status; 1.163 + /* Recent Android have a getMinFrameCount method. On Froyo, we have to compute it by hand. */ 1.164 + if (audiotrack_version_is_froyo(ctx)) { 1.165 + int samplerate, frame_count, latency, min_buffer_count; 1.166 + status = ctx->klass.get_output_frame_count(&frame_count, params->stream_type); 1.167 + if (status) { 1.168 + ALOG("error getting the output frame count."); 1.169 + return CUBEB_ERROR; 1.170 + } 1.171 + status = ctx->klass.get_output_latency((uint32_t*)&latency, params->stream_type); 1.172 + if (status) { 1.173 + ALOG("error getting the output frame count."); 1.174 + return CUBEB_ERROR; 1.175 + } 1.176 + status = ctx->klass.get_output_samplingrate(&samplerate, params->stream_type); 1.177 + if (status) { 1.178 + ALOG("error getting the output frame count."); 1.179 + return CUBEB_ERROR; 1.180 + } 1.181 + 1.182 + /* Those numbers were found reading the Android source. It is the minimum 1.183 + * numbers that will be accepted by the AudioTrack class, hence yielding the 1.184 + * best latency possible. 1.185 + * See https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/media/libmedia/AudioTrack.cpp 1.186 + * around line 181 for Android 2.2 */ 1.187 + min_buffer_count = latency / ((1000 * frame_count) / samplerate); 1.188 + min_buffer_count = min_buffer_count < 2 ? min_buffer_count : 2; 1.189 + *min_frame_count = (frame_count * params->rate * min_buffer_count) / samplerate; 1.190 + return CUBEB_OK; 1.191 + } 1.192 + /* Recent Android have a getMinFrameCount method. */ 1.193 + if (!audiotrack_version_is_gingerbread(ctx)) { 1.194 + status = ctx->klass.get_min_frame_count(min_frame_count, params->stream_type, params->rate); 1.195 + } else { 1.196 + status = ctx->klass.get_min_frame_count_gingerbread(min_frame_count, params->stream_type, params->rate); 1.197 + } 1.198 + if (status != 0) { 1.199 + ALOG("error getting the min frame count"); 1.200 + return CUBEB_ERROR; 1.201 + } 1.202 + return CUBEB_OK; 1.203 +} 1.204 + 1.205 +int 1.206 +audiotrack_init(cubeb ** context, char const * context_name) 1.207 +{ 1.208 + cubeb * ctx; 1.209 + struct AudioTrack* c; 1.210 + 1.211 + assert(context); 1.212 + *context = NULL; 1.213 + 1.214 + ctx = calloc(1, sizeof(*ctx)); 1.215 + assert(ctx); 1.216 + 1.217 + /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android 1.218 + * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on 1.219 + * the first call to a dlsym'ed function. Somehow this does not happen when 1.220 + * using only the name of the library. */ 1.221 + ctx->library = dlopen("libmedia.so", RTLD_LAZY); 1.222 + if (!ctx->library) { 1.223 + ALOG("dlopen error: %s.", dlerror()); 1.224 + free(ctx); 1.225 + return CUBEB_ERROR; 1.226 + } 1.227 + 1.228 + /* Recent Android first, then Froyo. */ 1.229 + DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii", ctx->klass.ctor, ctx->library); 1.230 + if (!ctx->klass.ctor) { 1.231 + DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_i", ctx->klass.ctor_froyo, ctx->library); 1.232 + assert(ctx->klass.ctor_froyo); 1.233 + } 1.234 + DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library); 1.235 + 1.236 + DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library); 1.237 + DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library); 1.238 + 1.239 + DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library); 1.240 + 1.241 + /* |getMinFrameCount| is not available on Froyo, and is available on 1.242 + * gingerbread and ICS with a different signature. */ 1.243 + if (audiotrack_version_is_froyo(ctx)) { 1.244 + DLSYM_DLERROR("_ZN7android11AudioSystem19getOutputFrameCountEPii", ctx->klass.get_output_frame_count, ctx->library); 1.245 + DLSYM_DLERROR("_ZN7android11AudioSystem16getOutputLatencyEPji", ctx->klass.get_output_latency, ctx->library); 1.246 + } else { 1.247 + DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj", ctx->klass.get_min_frame_count, ctx->library); 1.248 + if (!ctx->klass.get_min_frame_count) { 1.249 + DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij", ctx->klass.get_min_frame_count_gingerbread, ctx->library); 1.250 + } 1.251 + } 1.252 + 1.253 + DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start, ctx->library); 1.254 + DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause, ctx->library); 1.255 + DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj", ctx->klass.get_position, ctx->library); 1.256 + DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj", ctx->klass.set_marker_position, ctx->library); 1.257 + 1.258 + /* check that we have a combination of symbol that makes sense */ 1.259 + c = &ctx->klass; 1.260 + if(!((c->ctor || c->ctor_froyo) && /* at least on ctor. */ 1.261 + c->dtor && c->latency && c->check && 1.262 + /* at least one way to get the minimum frame count to request. */ 1.263 + ((c->get_output_frame_count && c->get_output_latency && c->get_output_samplingrate) || 1.264 + c->get_min_frame_count || 1.265 + c->get_min_frame_count_gingerbread) && 1.266 + c->start && c->pause && c->get_position && c->set_marker_position)) { 1.267 + ALOG("Could not find all the symbols we need."); 1.268 + audiotrack_destroy(ctx); 1.269 + return CUBEB_ERROR; 1.270 + } 1.271 + 1.272 + ctx->ops = &audiotrack_ops; 1.273 + 1.274 + *context = ctx; 1.275 + 1.276 + return CUBEB_OK; 1.277 +} 1.278 + 1.279 +char const * 1.280 +audiotrack_get_backend_id(cubeb * context) 1.281 +{ 1.282 + return "audiotrack"; 1.283 +} 1.284 + 1.285 +static int 1.286 +audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels) 1.287 +{ 1.288 + assert(ctx && max_channels); 1.289 + 1.290 + /* The android mixer handles up to two channels, see 1.291 + http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */ 1.292 + *max_channels = 2; 1.293 + 1.294 + return CUBEB_OK; 1.295 +} 1.296 + 1.297 +static int 1.298 +audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms) 1.299 +{ 1.300 + /* We always use the lowest latency possible when using this backend (see 1.301 + * audiotrack_stream_init), so this value is not going to be used. */ 1.302 + int rv; 1.303 + 1.304 + rv = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms); 1.305 + if (rv != CUBEB_OK) { 1.306 + return CUBEB_ERROR; 1.307 + } 1.308 + 1.309 + /* Convert to milliseconds. */ 1.310 + *latency_ms = *latency_ms * 1000 / params.rate; 1.311 + 1.312 + return CUBEB_OK; 1.313 +} 1.314 + 1.315 +static int 1.316 +audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate) 1.317 +{ 1.318 + status_t rv; 1.319 + 1.320 + rv = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */); 1.321 + 1.322 + return rv == 0 ? CUBEB_OK : CUBEB_ERROR; 1.323 +} 1.324 + 1.325 +void 1.326 +audiotrack_destroy(cubeb * context) 1.327 +{ 1.328 + assert(context); 1.329 + 1.330 + dlclose(context->library); 1.331 + 1.332 + free(context); 1.333 +} 1.334 + 1.335 +int 1.336 +audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name, 1.337 + cubeb_stream_params stream_params, unsigned int latency, 1.338 + cubeb_data_callback data_callback, 1.339 + cubeb_state_callback state_callback, 1.340 + void * user_ptr) 1.341 +{ 1.342 + cubeb_stream * stm; 1.343 + int32_t channels; 1.344 + uint32_t min_frame_count; 1.345 + 1.346 + assert(ctx && stream); 1.347 + 1.348 + if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE || 1.349 + stream_params.format == CUBEB_SAMPLE_FLOAT32BE) { 1.350 + return CUBEB_ERROR_INVALID_FORMAT; 1.351 + } 1.352 + 1.353 + if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) { 1.354 + return CUBEB_ERROR; 1.355 + } 1.356 + 1.357 + stm = calloc(1, sizeof(*stm)); 1.358 + assert(stm); 1.359 + 1.360 + stm->context = ctx; 1.361 + stm->data_callback = data_callback; 1.362 + stm->state_callback = state_callback; 1.363 + stm->user_ptr = user_ptr; 1.364 + stm->params = stream_params; 1.365 + 1.366 + stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1); 1.367 + (*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) = 0xbaadbaad; 1.368 + assert(stm->instance && "cubeb: EOM"); 1.369 + 1.370 + /* gingerbread uses old channel layout enum */ 1.371 + if (audiotrack_version_is_froyo(ctx) || audiotrack_version_is_gingerbread(ctx)) { 1.372 + channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy : AUDIO_CHANNEL_OUT_MONO_Legacy; 1.373 + } else { 1.374 + channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS : AUDIO_CHANNEL_OUT_MONO_ICS; 1.375 + } 1.376 + 1.377 + if (audiotrack_version_is_froyo(ctx)) { 1.378 + ctx->klass.ctor_froyo(stm->instance, 1.379 + stm->params.stream_type, 1.380 + stm->params.rate, 1.381 + AUDIO_FORMAT_PCM_16_BIT, 1.382 + channels, 1.383 + min_frame_count, 1.384 + 0, 1.385 + audiotrack_refill, 1.386 + stm, 1.387 + 0); 1.388 + } else { 1.389 + ctx->klass.ctor(stm->instance, 1.390 + stm->params.stream_type, 1.391 + stm->params.rate, 1.392 + AUDIO_FORMAT_PCM_16_BIT, 1.393 + channels, 1.394 + min_frame_count, 1.395 + 0, 1.396 + audiotrack_refill, 1.397 + stm, 1.398 + 0, 1.399 + 0); 1.400 + } 1.401 + 1.402 + assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad); 1.403 + 1.404 + if (ctx->klass.check(stm->instance)) { 1.405 + ALOG("stream not initialized properly."); 1.406 + audiotrack_stream_destroy(stm); 1.407 + return CUBEB_ERROR; 1.408 + } 1.409 + 1.410 + *stream = stm; 1.411 + 1.412 + return CUBEB_OK; 1.413 +} 1.414 + 1.415 +void 1.416 +audiotrack_stream_destroy(cubeb_stream * stream) 1.417 +{ 1.418 + assert(stream->context); 1.419 + 1.420 + stream->context->klass.dtor(stream->instance); 1.421 + 1.422 + free(stream->instance); 1.423 + stream->instance = NULL; 1.424 + free(stream); 1.425 +} 1.426 + 1.427 +int 1.428 +audiotrack_stream_start(cubeb_stream * stream) 1.429 +{ 1.430 + assert(stream->instance); 1.431 + 1.432 + stream->context->klass.start(stream->instance); 1.433 + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED); 1.434 + 1.435 + return CUBEB_OK; 1.436 +} 1.437 + 1.438 +int 1.439 +audiotrack_stream_stop(cubeb_stream * stream) 1.440 +{ 1.441 + assert(stream->instance); 1.442 + 1.443 + stream->context->klass.pause(stream->instance); 1.444 + stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED); 1.445 + 1.446 + return CUBEB_OK; 1.447 +} 1.448 + 1.449 +int 1.450 +audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position) 1.451 +{ 1.452 + uint32_t p; 1.453 + 1.454 + assert(stream->instance && position); 1.455 + stream->context->klass.get_position(stream->instance, &p); 1.456 + *position = p; 1.457 + 1.458 + return CUBEB_OK; 1.459 +} 1.460 + 1.461 +int 1.462 +audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency) 1.463 +{ 1.464 + assert(stream->instance && latency); 1.465 + 1.466 + /* Android returns the latency in ms, we want it in frames. */ 1.467 + *latency = stream->context->klass.latency(stream->instance); 1.468 + /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */ 1.469 + *latency = (*latency * stream->params.rate) / 1000; 1.470 + 1.471 + return 0; 1.472 +} 1.473 + 1.474 +static struct cubeb_ops const audiotrack_ops = { 1.475 + .init = audiotrack_init, 1.476 + .get_backend_id = audiotrack_get_backend_id, 1.477 + .get_max_channel_count = audiotrack_get_max_channel_count, 1.478 + .get_min_latency = audiotrack_get_min_latency, 1.479 + .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate, 1.480 + .destroy = audiotrack_destroy, 1.481 + .stream_init = audiotrack_stream_init, 1.482 + .stream_destroy = audiotrack_stream_destroy, 1.483 + .stream_start = audiotrack_stream_start, 1.484 + .stream_stop = audiotrack_stream_stop, 1.485 + .stream_get_position = audiotrack_stream_get_position, 1.486 + .stream_get_latency = audiotrack_stream_get_latency 1.487 +};