Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
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 */
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"
18 #include "cubeb/cubeb.h"
19 #include "cubeb-internal.h"
20 #include "android/audiotrack_definitions.h"
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
30 /**
31 * A lot of bytes for safety. It should be possible to bring this down a bit. */
32 #define SIZE_AUDIOTRACK_INSTANCE 256
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);
48 static struct cubeb_ops const audiotrack_ops;
49 void audiotrack_destroy(cubeb * context);
50 void audiotrack_stream_destroy(cubeb_stream * stream);
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);
75 };
77 struct cubeb {
78 struct cubeb_ops const * ops;
79 void * library;
80 struct AudioTrack klass;
81 };
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 };
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;
104 if (stream->draining) {
105 return;
106 }
108 got = stream->data_callback(stream, stream->user_ptr, b->raw, b->frameCount);
110 stream->written += got;
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 }
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 }
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 }
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 }
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 }
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 }
202 int
203 audiotrack_init(cubeb ** context, char const * context_name)
204 {
205 cubeb * ctx;
206 struct AudioTrack* c;
208 assert(context);
209 *context = NULL;
211 ctx = calloc(1, sizeof(*ctx));
212 assert(ctx);
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 }
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);
233 DLSYM_DLERROR("_ZNK7android10AudioTrack7latencyEv", ctx->klass.latency, ctx->library);
234 DLSYM_DLERROR("_ZNK7android10AudioTrack9initCheckEv", ctx->klass.check, ctx->library);
236 DLSYM_DLERROR("_ZN7android11AudioSystem21getOutputSamplingRateEPii", ctx->klass.get_output_samplingrate, ctx->library);
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 }
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);
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 }
269 ctx->ops = &audiotrack_ops;
271 *context = ctx;
273 return CUBEB_OK;
274 }
276 char const *
277 audiotrack_get_backend_id(cubeb * context)
278 {
279 return "audiotrack";
280 }
282 static int
283 audiotrack_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
284 {
285 assert(ctx && max_channels);
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;
291 return CUBEB_OK;
292 }
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;
301 rv = audiotrack_get_min_frame_count(ctx, ¶ms, (int *)latency_ms);
302 if (rv != CUBEB_OK) {
303 return CUBEB_ERROR;
304 }
306 /* Convert to milliseconds. */
307 *latency_ms = *latency_ms * 1000 / params.rate;
309 return CUBEB_OK;
310 }
312 static int
313 audiotrack_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
314 {
315 status_t rv;
317 rv = ctx->klass.get_output_samplingrate((int32_t *)rate, 3 /* MUSIC */);
319 return rv == 0 ? CUBEB_OK : CUBEB_ERROR;
320 }
322 void
323 audiotrack_destroy(cubeb * context)
324 {
325 assert(context);
327 dlclose(context->library);
329 free(context);
330 }
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;
343 assert(ctx && stream);
345 if (stream_params.format == CUBEB_SAMPLE_FLOAT32LE ||
346 stream_params.format == CUBEB_SAMPLE_FLOAT32BE) {
347 return CUBEB_ERROR_INVALID_FORMAT;
348 }
350 if (audiotrack_get_min_frame_count(ctx, &stream_params, (int *)&min_frame_count)) {
351 return CUBEB_ERROR;
352 }
354 stm = calloc(1, sizeof(*stm));
355 assert(stm);
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;
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");
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 }
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 }
399 assert((*(uint32_t*)((intptr_t)stm->instance + SIZE_AUDIOTRACK_INSTANCE - 4)) == 0xbaadbaad);
401 if (ctx->klass.check(stm->instance)) {
402 ALOG("stream not initialized properly.");
403 audiotrack_stream_destroy(stm);
404 return CUBEB_ERROR;
405 }
407 *stream = stm;
409 return CUBEB_OK;
410 }
412 void
413 audiotrack_stream_destroy(cubeb_stream * stream)
414 {
415 assert(stream->context);
417 stream->context->klass.dtor(stream->instance);
419 free(stream->instance);
420 stream->instance = NULL;
421 free(stream);
422 }
424 int
425 audiotrack_stream_start(cubeb_stream * stream)
426 {
427 assert(stream->instance);
429 stream->context->klass.start(stream->instance);
430 stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STARTED);
432 return CUBEB_OK;
433 }
435 int
436 audiotrack_stream_stop(cubeb_stream * stream)
437 {
438 assert(stream->instance);
440 stream->context->klass.pause(stream->instance);
441 stream->state_callback(stream, stream->user_ptr, CUBEB_STATE_STOPPED);
443 return CUBEB_OK;
444 }
446 int
447 audiotrack_stream_get_position(cubeb_stream * stream, uint64_t * position)
448 {
449 uint32_t p;
451 assert(stream->instance && position);
452 stream->context->klass.get_position(stream->instance, &p);
453 *position = p;
455 return CUBEB_OK;
456 }
458 int
459 audiotrack_stream_get_latency(cubeb_stream * stream, uint32_t * latency)
460 {
461 assert(stream->instance && latency);
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;
468 return 0;
469 }
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 };