media/libcubeb/src/cubeb_audiotrack.c

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial