1 /*
2 * Copyright © 2013 Mozilla Foundation
3 *
4 * This program is made available under an ISC-style license. See the
5 * accompanying file LICENSE for details.
6 */
7
8 #if !defined(NDEBUG)
9 #define NDEBUG
10 #endif
11 #include <android/log.h>
12 #include <assert.h>
13 #include <dlfcn.h>
14 #include <pthread.h>
15 #include <stdlib.h>
16 #include <time.h>
17
18 #include "android/audiotrack_definitions.h"
19 #include "cubeb-internal.h"
20 #include "cubeb/cubeb.h"
21
22 #ifndef ALOG
23 #if defined(DEBUG) || defined(FORCE_ALOG)
24 #define ALOG(args...) \
25 __android_log_print(ANDROID_LOG_INFO, "Gecko - Cubeb", ##args)
26 #else
27 #define ALOG(args...)
28 #endif
29 #endif
30
31 /**
32 * A lot of bytes for safety. It should be possible to bring this down a bit. */
33 #define SIZE_AUDIOTRACK_INSTANCE 256
34
35 /**
36 * call dlsym to get the symbol |mangled_name|, handle the error and store the
37 * pointer in |pointer|. Because depending on Android version, we want different
38 * symbols, not finding a symbol is not an error. */
39 #define DLSYM_DLERROR(mangled_name, pointer, lib) \
40 do { \
41 pointer = dlsym(lib, mangled_name); \
42 if (!pointer) { \
43 ALOG("error while loading %stm: %stm\n", mangled_name, dlerror()); \
44 } else { \
45 ALOG("%stm: OK", mangled_name); \
46 } \
47 } while (0);
48
49 static struct cubeb_ops const audiotrack_ops;
50 void
51 audiotrack_destroy(cubeb * context);
52 void
53 audiotrack_stream_destroy(cubeb_stream * stream);
54
55 struct AudioTrack {
56 /* only available on ICS and later. The second int paramter is in fact of type
57 * audio_stream_type_t. */
58 /* static */ status_t (*get_min_frame_count)(int * frame_count,
59 int stream_type, uint32_t rate);
60 /* if we have a recent ctor, but can't find the above symbol, we
61 * can get the minimum frame count with this signature, and we are
62 * running gingerbread. */
63 /* static */ status_t (*get_min_frame_count_gingerbread)(int * frame_count,
64 int stream_type,
65 uint32_t rate);
66 void * (*ctor)(void * instance, int, unsigned int, int, int, int,
67 unsigned int, void (*)(int, void *, void *), void *, int, int);
68 void * (*dtor)(void * instance);
69 void (*start)(void * instance);
70 void (*pause)(void * instance);
71 uint32_t (*latency)(void * instance);
72 status_t (*check)(void * instance);
73 status_t (*get_position)(void * instance, uint32_t * position);
74 /* static */ int (*get_output_samplingrate)(int * samplerate, int stream);
75 status_t (*set_marker_position)(void * instance, unsigned int);
76 status_t (*set_volume)(void * instance, float left, float right);
77 };
78
79 struct cubeb {
80 struct cubeb_ops const * ops;
81 void * library;
82 struct AudioTrack klass;
83 };
84
85 struct cubeb_stream {
86 /* Note: Must match cubeb_stream layout in cubeb.c. */
87 cubeb * context;
88 void * user_ptr;
89 /**/
90 cubeb_stream_params params;
91 cubeb_data_callback data_callback;
92 cubeb_state_callback state_callback;
93 void * instance;
94 /* Number of frames that have been passed to the AudioTrack callback */
95 long unsigned written;
96 int draining;
97 };
98
99 static void
audiotrack_refill(int event,void * user,void * info)100 audiotrack_refill(int event, void * user, void * info)
101 {
102 cubeb_stream * stream = user;
103 switch (event) {
104 case EVENT_MORE_DATA: {
105 long got = 0;
106 struct Buffer * b = (struct Buffer *)info;
107
108 if (stream->draining) {
109 return;
110 }
111
112 got = stream->data_callback(stream, stream->user_ptr, NULL, b->raw,
113 b->frameCount);
114
115 stream->written += got;
116
117 if (got != (long)b->frameCount) {
118 stream->draining = 1;
119 /* set a marker so we are notified when the are done draining, that is,
120 * when every frame has been played by android. */
121 stream->context->klass.set_marker_position(stream->instance,
122 stream->written);
123 }
124
125 break;
126 }
127 case EVENT_UNDERRUN:
128 ALOG("underrun in cubeb backend.");
129 break;
130 case EVENT_LOOP_END:
131 assert(0 && "We don't support the loop feature of audiotrack.");
132 break;
133 case EVENT_MARKER:
134 assert(stream->draining);
135 stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_DRAINED);
136 break;
137 case EVENT_NEW_POS:
138 assert(
139 0 &&
140 "We don't support the setPositionUpdatePeriod feature of audiotrack.");
141 break;
142 case EVENT_BUFFER_END:
143 assert(0 && "Should not happen.");
144 break;
145 }
146 }
147
148 /* We are running on gingerbread if we found the gingerbread signature for
149 * getMinFrameCount */
150 static int
audiotrack_version_is_gingerbread(cubeb * ctx)151 audiotrack_version_is_gingerbread(cubeb * ctx)
152 {
153 return ctx->klass.get_min_frame_count_gingerbread != NULL;
154 }
155
156 int
audiotrack_get_min_frame_count(cubeb * ctx,cubeb_stream_params * params,int * min_frame_count)157 audiotrack_get_min_frame_count(cubeb * ctx, cubeb_stream_params * params,
158 int * min_frame_count)
159 {
160 status_t status;
161 /* Recent Android have a getMinFrameCount method. */
162 if (!audiotrack_version_is_gingerbread(ctx)) {
163 status = ctx->klass.get_min_frame_count(
164 min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
165 } else {
166 status = ctx->klass.get_min_frame_count_gingerbread(
167 min_frame_count, AUDIO_STREAM_TYPE_MUSIC, params->rate);
168 }
169 if (status != 0) {
170 ALOG("error getting the min frame count");
171 return CUBEB_ERROR;
172 }
173 return CUBEB_OK;
174 }
175
176 int
audiotrack_init(cubeb ** context,char const * context_name)177 audiotrack_init(cubeb ** context, char const * context_name)
178 {
179 cubeb * ctx;
180 struct AudioTrack * c;
181
182 assert(context);
183 *context = NULL;
184
185 ctx = calloc(1, sizeof(*ctx));
186 assert(ctx);
187
188 /* If we use an absolute path here ("/system/lib/libmedia.so"), and on Android
189 * 2.2, the dlopen succeeds, all the dlsym succeed, but a segfault happens on
190 * the first call to a dlsym'ed function. Somehow this does not happen when
191 * using only the name of the library. */
192 ctx->library = dlopen("libmedia.so", RTLD_LAZY);
193 if (!ctx->library) {
194 ALOG("dlopen error: %s.", dlerror());
195 free(ctx);
196 return CUBEB_ERROR;
197 }
198
199 /* Recent Android first, then Gingerbread. */
200 DLSYM_DLERROR("_ZN7android10AudioTrackC1EijiiijPFviPvS1_ES1_ii",
201 ctx->klass.ctor, ctx->library);
202 DLSYM_DLERROR("_ZN7android10AudioTrackD1Ev", ctx->klass.dtor, ctx->library);
203
204 DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency,
205 ctx->library);
206 DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check,
207 ctx->library);
208
209 DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii",
210 ctx->klass.get_output_samplingrate, ctx->library);
211
212 /* |getMinFrameCount| is available on gingerbread and ICS with different
213 * signatures. */
214 DLSYM_DLERROR(
215 "_ZN7android10AudioTrack16getMinFrameCountEPi19audio_stream_type_tj",
216 ctx->klass.get_min_frame_count, ctx->library);
217 if (!ctx->klass.get_min_frame_count) {
218 DLSYM_DLERROR("_ZN7android10AudioTrack16getMinFrameCountEPiij",
219 ctx->klass.get_min_frame_count_gingerbread, ctx->library);
220 }
221
222 DLSYM_DLERROR("_ZN7android10AudioTrack5startEv", ctx->klass.start,
223 ctx->library);
224 DLSYM_DLERROR("_ZN7android10AudioTrack5pauseEv", ctx->klass.pause,
225 ctx->library);
226 DLSYM_DLERROR("_ZN7android10AudioTrack11getPositionEPj",
227 ctx->klass.get_position, ctx->library);
228 DLSYM_DLERROR("_ZN7android10AudioTrack17setMarkerPositionEj",
229 ctx->klass.set_marker_position, ctx->library);
230 DLSYM_DLERROR("_ZN7android10AudioTrack9setVolumeEff", ctx->klass.set_volume,
231 ctx->library);
232
233 /* check that we have a combination of symbol that makes sense */
234 c = &ctx->klass;
235 if (!(c->ctor && c->dtor && c->latency && c->check &&
236 /* at least one way to get the minimum frame count to request. */
237 (c->get_min_frame_count || c->get_min_frame_count_gingerbread) &&
238 c->start && c->pause && c->get_position && c->set_marker_position)) {
239 ALOG("Could not find all the symbols we need.");
240 audiotrack_destroy(ctx);
241 return CUBEB_ERROR;
242 }
243
244 ctx->ops = &audiotrack_ops;
245
246 *context = ctx;
247
248 return CUBEB_OK;
249 }
250
251 char const *
audiotrack_get_backend_id(cubeb * context)252 audiotrack_get_backend_id(cubeb * context)
253 {
254 return "audiotrack";
255 }
256
257 static int
audiotrack_get_max_channel_count(cubeb * ctx,uint32_t * max_channels)258 audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
259 {
260 assert(ctx && max_channels);
261
262 /* The android mixer handles up to two channels, see
263 http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67
264 */
265 *max_channels = 2;
266
267 return CUBEB_OK;
268 }
269
270 static int
audiotrack_get_min_latency(cubeb * ctx,cubeb_stream_params params,uint32_t * latency_ms)271 audiotrack_get_min_latency(cubeb * ctx, cubeb_stream_params params,
272 uint32_t * latency_ms)
273 {
274 /* We always use the lowest latency possible when using this backend (see
275 * audiotrack_stream_init), so this value is not going to be used. */
276 int r;
277
278 r = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms);
279 if (r != CUBEB_OK) {
280 return CUBEB_ERROR;
281 }
282
283 return CUBEB_OK;
284 }
285
286 static int
audiotrack_get_preferred_sample_rate(cubeb * ctx,uint32_t * rate)287 audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
288 {
289 status_t r;
290
291 r = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
292
293 return r == 0 ? CUBEB_OK : CUBEB_ERROR;
294 }
295
296 void
audiotrack_destroy(cubeb * context)297 audiotrack_destroy(cubeb * context)
298 {
299 assert(context);
300
301 dlclose(context->library);
302
303 free(context);
304 }
305
306 int
audiotrack_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,cubeb_data_callback data_callback,cubeb_state_callback state_callback,void * user_ptr)307 audiotrack_stream_init(cubeb * ctx, cubeb_stream ** stream,
308 char const * stream_name, cubeb_devid input_device,
309 cubeb_stream_params * input_stream_params,
310 cubeb_devid output_device,
311 cubeb_stream_params * output_stream_params,
312 unsigned int latency, cubeb_data_callback data_callback,
313 cubeb_state_callback state_callback, void * user_ptr)
314 {
315 cubeb_stream * stm;
316 int32_t channels;
317 uint32_t min_frame_count;
318
319 assert(ctx && stream);
320
321 assert(!input_stream_params && "not supported");
322 if (input_device || output_device) {
323 /* Device selection not yet implemented. */
324 return CUBEB_ERROR_DEVICE_UNAVAILABLE;
325 }
326
327 if (output_stream_params->format == CUBEB_SAMPLE_FLOAT32LE ||
328 output_stream_params->format == CUBEB_SAMPLE_FLOAT32BE) {
329 return CUBEB_ERROR_INVALID_FORMAT;
330 }
331
332 if (audiotrack_get_min_frame_count(ctx, output_stream_params,
333 (int *)&min_frame_count)) {
334 return CUBEB_ERROR;
335 }
336
337 stm = calloc(1, sizeof(*stm));
338 assert(stm);
339
340 stm->context = ctx;
341 stm->data_callback = data_callback;
342 stm->state_callback = state_callback;
343 stm->user_ptr = user_ptr;
344 stm->params = *output_stream_params;
345
346 stm->instance = calloc(SIZE_AUDIOTRACK_INSTANCE, 1);
347 (*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) =
348 0xbaadbaad;
349 assert(stm->instance && "cubeb: EOM");
350
351 /* gingerbread uses old channel layout enum */
352 if (audiotrack_version_is_gingerbread(ctx)) {
353 channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_Legacy
354 : AUDIO_CHANNEL_OUT_MONO_Legacy;
355 } else {
356 channels = stm->params.channels == 2 ? AUDIO_CHANNEL_OUT_STEREO_ICS
357 : AUDIO_CHANNEL_OUT_MONO_ICS;
358 }
359
360 ctx->klass.ctor(stm->instance, AUDIO_STREAM_TYPE_MUSIC, stm->params.rate,
361 AUDIO_FORMAT_PCM_16_BIT, channels, min_frame_count, 0,
362 audiotrack_refill, stm, 0, 0);
363
364 assert((*(uint32_t *)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE -
365 4)) == 0xbaadbaad);
366
367 if (ctx->klass.check(stm->instance)) {
368 ALOG("stream not initialized properly.");
369 audiotrack_stream_destroy(stm);
370 return CUBEB_ERROR;
371 }
372
373 *stream = stm;
374
375 return CUBEB_OK;
376 }
377
378 void
audiotrack_stream_destroy(cubeb_stream * stream)379 audiotrack_stream_destroy(cubeb_stream * stream)
380 {
381 assert(stream->context);
382
383 stream->context->klass.dtor(stream->instance);
384
385 free(stream->instance);
386 stream->instance = NULL;
387 free(stream);
388 }
389
390 int
audiotrack_stream_start(cubeb_stream * stream)391 audiotrack_stream_start(cubeb_stream * stream)
392 {
393 assert(stream->instance);
394
395 stream->context->klass.start(stream->instance);
396 stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
397
398 return CUBEB_OK;
399 }
400
401 int
audiotrack_stream_stop(cubeb_stream * stream)402 audiotrack_stream_stop(cubeb_stream * stream)
403 {
404 assert(stream->instance);
405
406 stream->context->klass.pause(stream->instance);
407 stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
408
409 return CUBEB_OK;
410 }
411
412 int
audiotrack_stream_get_position(cubeb_stream * stream,uint64_t * position)413 audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
414 {
415 uint32_t p;
416
417 assert(stream->instance && position);
418 stream->context->klass.get_position(stream->instance, &p);
419 *position = p;
420
421 return CUBEB_OK;
422 }
423
424 int
audiotrack_stream_get_latency(cubeb_stream * stream,uint32_t * latency)425 audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
426 {
427 assert(stream->instance && latency);
428
429 /* Android returns the latency in ms, we want it in frames. */
430 *latency = stream->context->klass.latency(stream->instance);
431 /* with rate <= 96000, we won't overflow until 44.739 seconds of latency */
432 *latency = (*latency * stream->params.rate) / 1000;
433
434 return 0;
435 }
436
437 int
audiotrack_stream_set_volume(cubeb_stream * stream,float volume)438 audiotrack_stream_set_volume(cubeb_stream * stream, float volume)
439 {
440 status_t status;
441
442 status = stream->context->klass.set_volume(stream->instance, volume, volume);
443
444 if (status) {
445 return CUBEB_ERROR;
446 }
447
448 return CUBEB_OK;
449 }
450
451 static struct cubeb_ops const audiotrack_ops = {
452 .init = audiotrack_init,
453 .get_backend_id = audiotrack_get_backend_id,
454 .get_max_channel_count = audiotrack_get_max_channel_count,
455 .get_min_latency = audiotrack_get_min_latency,
456 .get_preferred_sample_rate = audiotrack_get_preferred_sample_rate,
457 .enumerate_devices = NULL,
458 .device_collection_destroy = NULL,
459 .destroy = audiotrack_destroy,
460 .stream_init = audiotrack_stream_init,
461 .stream_destroy = audiotrack_stream_destroy,
462 .stream_start = audiotrack_stream_start,
463 .stream_stop = audiotrack_stream_stop,
464 .stream_get_position = audiotrack_stream_get_position,
465 .stream_get_latency = audiotrack_stream_get_latency,
466 .stream_get_input_latency = NULL,
467 .stream_set_volume = audiotrack_stream_set_volume,
468 .stream_set_name = NULL,
469 .stream_get_current_device = NULL,
470 .stream_device_destroy = NULL,
471 .stream_register_device_changed_callback = NULL,
472 .register_device_collection_changed = NULL};
473