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