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, &params, (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