media/libcubeb/src/cubeb_opensl.c

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:f50db4e2403d
1 /*
2 * Copyright © 2012 Mozilla Foundation
3 *
4 * This program is made available under an ISC-style license. See the
5 * accompanying file LICENSE for details.
6 */
7 #undef NDEBUG
8 #include <assert.h>
9 #include <dlfcn.h>
10 #include <stdlib.h>
11 #include <SLES/OpenSLES.h>
12 #if defined(__ANDROID__)
13 #include "android/sles_definitions.h"
14 #include <SLES/OpenSLES_Android.h>
15 #include <android/log.h>
16 #define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "Cubeb_OpenSL" , ## args)
17 #endif
18 #include "cubeb/cubeb.h"
19 #include "cubeb-internal.h"
20
21 static struct cubeb_ops const opensl_ops;
22
23 struct cubeb {
24 struct cubeb_ops const * ops;
25 void * lib;
26 void * libmedia;
27 int32_t (* get_output_latency)(uint32_t * latency, int stream_type);
28 SLInterfaceID SL_IID_BUFFERQUEUE;
29 SLInterfaceID SL_IID_PLAY;
30 #if defined(__ANDROID__)
31 SLInterfaceID SL_IID_ANDROIDCONFIGURATION;
32 #endif
33 SLObjectItf engObj;
34 SLEngineItf eng;
35 SLObjectItf outmixObj;
36 };
37
38 #define NELEMS(A) (sizeof(A) / sizeof A[0])
39 #define NBUFS 4
40 #define AUDIO_STREAM_TYPE_MUSIC 3
41
42 struct cubeb_stream {
43 cubeb * context;
44 SLObjectItf playerObj;
45 SLPlayItf play;
46 SLBufferQueueItf bufq;
47 void *queuebuf[NBUFS];
48 int queuebuf_idx;
49 long queuebuf_len;
50 long bytespersec;
51 long framesize;
52 int draining;
53 cubeb_stream_type stream_type;
54
55 cubeb_data_callback data_callback;
56 cubeb_state_callback state_callback;
57 void * user_ptr;
58 };
59
60 static void
61 bufferqueue_callback(SLBufferQueueItf caller, void * user_ptr)
62 {
63 cubeb_stream * stm = user_ptr;
64 SLBufferQueueState state;
65 (*stm->bufq)->GetState(stm->bufq, &state);
66
67 if (stm->draining) {
68 if (!state.count) {
69 stm->draining = 0;
70 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
71 }
72 return;
73 }
74
75 if (state.count > 1)
76 return;
77
78 SLuint32 i;
79 for (i = state.count; i < NBUFS; i++) {
80 void *buf = stm->queuebuf[stm->queuebuf_idx];
81 long written = stm->data_callback(stm, stm->user_ptr,
82 buf, stm->queuebuf_len / stm->framesize);
83 if (written == CUBEB_ERROR) {
84 (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_STOPPED);
85 return;
86 }
87
88 if (written) {
89 (*stm->bufq)->Enqueue(stm->bufq, buf, written * stm->framesize);
90 stm->queuebuf_idx = (stm->queuebuf_idx + 1) % NBUFS;
91 } else if (!i) {
92 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
93 return;
94 }
95
96 if ((written * stm->framesize) < stm->queuebuf_len) {
97 stm->draining = 1;
98 return;
99 }
100 }
101 }
102
103 #if defined(__ANDROID__)
104 static SLuint32
105 convert_stream_type_to_sl_stream(cubeb_stream_type stream_type)
106 {
107 switch(stream_type) {
108 case CUBEB_STREAM_TYPE_SYSTEM:
109 return SL_ANDROID_STREAM_SYSTEM;
110 case CUBEB_STREAM_TYPE_MUSIC:
111 return SL_ANDROID_STREAM_MEDIA;
112 case CUBEB_STREAM_TYPE_NOTIFICATION:
113 return SL_ANDROID_STREAM_NOTIFICATION;
114 case CUBEB_STREAM_TYPE_ALARM:
115 return SL_ANDROID_STREAM_ALARM;
116 case CUBEB_STREAM_TYPE_VOICE_CALL:
117 return SL_ANDROID_STREAM_VOICE;
118 case CUBEB_STREAM_TYPE_RING:
119 return SL_ANDROID_STREAM_RING;
120 case CUBEB_STREAM_TYPE_ENFORCED_AUDIBLE:
121 default:
122 return 0xFFFFFFFF;
123 }
124 }
125 #endif
126
127 static void opensl_destroy(cubeb * ctx);
128
129 /*static*/ int
130 opensl_init(cubeb ** context, char const * context_name)
131 {
132 cubeb * ctx;
133
134 *context = NULL;
135
136 ctx = calloc(1, sizeof(*ctx));
137 assert(ctx);
138
139 ctx->ops = &opensl_ops;
140
141 ctx->lib = dlopen("libOpenSLES.so", RTLD_LAZY);
142 ctx->libmedia = dlopen("libmedia.so", RTLD_LAZY);
143 if (!ctx->lib || !ctx->libmedia) {
144 free(ctx);
145 return CUBEB_ERROR;
146 }
147
148 /* Get the latency, in ms, from AudioFlinger */
149 /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
150 * audio_stream_type_t streamType) */
151 /* First, try the most recent signature. */
152 ctx->get_output_latency =
153 dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPj19audio_stream_type_t");
154 if (!ctx->get_output_latency) {
155 /* in case of failure, try the legacy version. */
156 /* status_t AudioSystem::getOutputLatency(uint32_t* latency,
157 * int streamType) */
158 ctx->get_output_latency =
159 dlsym(ctx->libmedia, "_ZN7android11AudioSystem16getOutputLatencyEPji");
160 if (!ctx->get_output_latency) {
161 opensl_destroy(ctx);
162 return CUBEB_ERROR;
163 }
164 }
165
166 typedef SLresult (*slCreateEngine_t)(SLObjectItf *,
167 SLuint32,
168 const SLEngineOption *,
169 SLuint32,
170 const SLInterfaceID *,
171 const SLboolean *);
172 slCreateEngine_t f_slCreateEngine =
173 (slCreateEngine_t)dlsym(ctx->lib, "slCreateEngine");
174 SLInterfaceID SL_IID_ENGINE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ENGINE");
175 SLInterfaceID SL_IID_OUTPUTMIX = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_OUTPUTMIX");
176 ctx->SL_IID_BUFFERQUEUE = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_BUFFERQUEUE");
177 #if defined(__ANDROID__)
178 ctx->SL_IID_ANDROIDCONFIGURATION = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_ANDROIDCONFIGURATION");
179 #endif
180 ctx->SL_IID_PLAY = *(SLInterfaceID *)dlsym(ctx->lib, "SL_IID_PLAY");
181 if (!f_slCreateEngine ||
182 !SL_IID_ENGINE ||
183 !SL_IID_OUTPUTMIX ||
184 !ctx->SL_IID_BUFFERQUEUE ||
185 #if defined(__ANDROID__)
186 !ctx->SL_IID_ANDROIDCONFIGURATION ||
187 #endif
188 !ctx->SL_IID_PLAY) {
189 opensl_destroy(ctx);
190 return CUBEB_ERROR;
191 }
192
193 const SLEngineOption opt[] = {{SL_ENGINEOPTION_THREADSAFE, SL_BOOLEAN_TRUE}};
194
195 SLresult res;
196 res = f_slCreateEngine(&ctx->engObj, 1, opt, 0, NULL, NULL);
197 if (res != SL_RESULT_SUCCESS) {
198 opensl_destroy(ctx);
199 return CUBEB_ERROR;
200 }
201
202 res = (*ctx->engObj)->Realize(ctx->engObj, SL_BOOLEAN_FALSE);
203 if (res != SL_RESULT_SUCCESS) {
204 opensl_destroy(ctx);
205 return CUBEB_ERROR;
206 }
207
208 res = (*ctx->engObj)->GetInterface(ctx->engObj, SL_IID_ENGINE, &ctx->eng);
209 if (res != SL_RESULT_SUCCESS) {
210 opensl_destroy(ctx);
211 return CUBEB_ERROR;
212 }
213
214 const SLInterfaceID idsom[] = {SL_IID_OUTPUTMIX};
215 const SLboolean reqom[] = {SL_BOOLEAN_TRUE};
216 res = (*ctx->eng)->CreateOutputMix(ctx->eng, &ctx->outmixObj, 1, idsom, reqom);
217 if (res != SL_RESULT_SUCCESS) {
218 opensl_destroy(ctx);
219 return CUBEB_ERROR;
220 }
221
222 res = (*ctx->outmixObj)->Realize(ctx->outmixObj, SL_BOOLEAN_FALSE);
223 if (res != SL_RESULT_SUCCESS) {
224 opensl_destroy(ctx);
225 return CUBEB_ERROR;
226 }
227
228 *context = ctx;
229
230 return CUBEB_OK;
231 }
232
233 static char const *
234 opensl_get_backend_id(cubeb * ctx)
235 {
236 return "opensl";
237 }
238
239 static int
240 opensl_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
241 {
242 assert(ctx && max_channels);
243 /* The android mixer handles up to two channels, see
244 http://androidxref.com/4.2.2_r1/xref/frameworks/av/services/audioflinger/AudioFlinger.h#67 */
245 *max_channels = 2;
246
247 return CUBEB_OK;
248 }
249
250 static int
251 opensl_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
252 {
253 /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
254 * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
255 * so we just dlopen the library and get the two symbols we need. */
256 int rv;
257 void * libmedia;
258 uint32_t (*get_primary_output_samplingrate)();
259 uint32_t (*get_output_samplingrate)(int * samplingRate, int streamType);
260 uint32_t primary_sampling_rate;
261
262 libmedia = dlopen("libmedia.so", RTLD_LAZY);
263 if (!libmedia) {
264 return CUBEB_ERROR;
265 }
266
267 /* uint32_t AudioSystem::getPrimaryOutputSamplingRate(void) */
268 get_primary_output_samplingrate =
269 dlsym(libmedia, "_ZN7android11AudioSystem28getPrimaryOutputSamplingRateEv");
270 if (!get_primary_output_samplingrate) {
271 /* fallback to
272 * status_t AudioSystem::getOutputSamplingRate(int* samplingRate, int streamType)
273 * if we cannot find getPrimaryOutputSamplingRate. */
274 get_output_samplingrate =
275 dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPj19audio_stream_type_t");
276 if (!get_output_samplingrate) {
277 /* Another signature exists, with a int instead of an audio_stream_type_t */
278 get_output_samplingrate =
279 dlsym(libmedia, "_ZN7android11AudioSystem21getOutputSamplingRateEPii");
280 if (!get_output_samplingrate) {
281 dlclose(libmedia);
282 return CUBEB_ERROR;
283 }
284 }
285 }
286
287 if (get_primary_output_samplingrate) {
288 *rate = get_primary_output_samplingrate();
289 } else {
290 /* We don't really know about the type, here, so we just pass music. */
291 rv = get_output_samplingrate((int *) rate, AUDIO_STREAM_TYPE_MUSIC);
292 if (rv) {
293 dlclose(libmedia);
294 return CUBEB_ERROR;
295 }
296 }
297
298 dlclose(libmedia);
299
300 /* Depending on which method we called above, we can get a zero back, yet have
301 * a non-error return value, especially if the audio system is not
302 * ready/shutting down (i.e. when we can't get our hand on the AudioFlinger
303 * thread). */
304 if (*rate == 0) {
305 return CUBEB_ERROR;
306 }
307
308 return CUBEB_OK;
309 }
310
311 static int
312 opensl_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
313 {
314 /* https://android.googlesource.com/platform/ndk.git/+/master/docs/opensles/index.html
315 * We don't want to deal with JNI here (and we don't have Java on b2g anyways),
316 * so we just dlopen the library and get the two symbols we need. */
317
318 int rv;
319 void * libmedia;
320 size_t (*get_primary_output_frame_count)(void);
321 int (*get_output_frame_count)(size_t * frameCount, int streamType);
322 uint32_t primary_sampling_rate;
323 size_t primary_buffer_size;
324
325 rv = opensl_get_preferred_sample_rate(ctx, &primary_sampling_rate);
326
327 if (rv) {
328 return CUBEB_ERROR;
329 }
330
331 libmedia = dlopen("libmedia.so", RTLD_LAZY);
332 if (!libmedia) {
333 return CUBEB_ERROR;
334 }
335
336 /* JB variant */
337 /* size_t AudioSystem::getPrimaryOutputFrameCount(void) */
338 get_primary_output_frame_count =
339 dlsym(libmedia, "_ZN7android11AudioSystem26getPrimaryOutputFrameCountEv");
340 if (!get_primary_output_frame_count) {
341 /* ICS variant */
342 /* status_t AudioSystem::getOutputFrameCount(int* frameCount, int streamType) */
343 get_output_frame_count =
344 dlsym(libmedia, "_ZN7android11AudioSystem19getOutputFrameCountEPii");
345 if (!get_output_frame_count) {
346 dlclose(libmedia);
347 return CUBEB_ERROR;
348 }
349 }
350
351 if (get_primary_output_frame_count) {
352 primary_buffer_size = get_primary_output_frame_count();
353 } else {
354 if (get_output_frame_count(&primary_buffer_size, params.stream_type) != 0) {
355 return CUBEB_ERROR;
356 }
357 }
358
359 /* To get a fast track in Android's mixer, we need to be at the native
360 * samplerate, which is device dependant. Some devices might be able to
361 * resample when playing a fast track, but it's pretty rare. */
362 *latency_ms = NBUFS * primary_buffer_size / (primary_sampling_rate / 1000);
363
364 dlclose(libmedia);
365
366 return CUBEB_OK;
367 }
368
369 static void
370 opensl_destroy(cubeb * ctx)
371 {
372 if (ctx->outmixObj)
373 (*ctx->outmixObj)->Destroy(ctx->outmixObj);
374 if (ctx->engObj)
375 (*ctx->engObj)->Destroy(ctx->engObj);
376 dlclose(ctx->lib);
377 dlclose(ctx->libmedia);
378 free(ctx);
379 }
380
381 static void opensl_stream_destroy(cubeb_stream * stm);
382
383 static int
384 opensl_stream_init(cubeb * ctx, cubeb_stream ** stream, char const * stream_name,
385 cubeb_stream_params stream_params, unsigned int latency,
386 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
387 void * user_ptr)
388 {
389 cubeb_stream * stm;
390
391 assert(ctx);
392
393 *stream = NULL;
394
395 if (stream_params.rate < 8000 || stream_params.rate > 88200 ||
396 stream_params.channels < 1 || stream_params.channels > 32 ||
397 latency < 1 || latency > 2000) {
398 return CUBEB_ERROR_INVALID_FORMAT;
399 }
400
401 SLDataFormat_PCM format;
402
403 format.formatType = SL_DATAFORMAT_PCM;
404 format.numChannels = stream_params.channels;
405 // samplesPerSec is in milliHertz
406 format.samplesPerSec = stream_params.rate * 1000;
407 format.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
408 format.containerSize = SL_PCMSAMPLEFORMAT_FIXED_16;
409 format.channelMask = stream_params.channels == 1 ?
410 SL_SPEAKER_FRONT_CENTER :
411 SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
412
413 switch (stream_params.format) {
414 case CUBEB_SAMPLE_S16LE:
415 format.endianness = SL_BYTEORDER_LITTLEENDIAN;
416 break;
417 case CUBEB_SAMPLE_S16BE:
418 format.endianness = SL_BYTEORDER_BIGENDIAN;
419 break;
420 default:
421 return CUBEB_ERROR_INVALID_FORMAT;
422 }
423
424 stm = calloc(1, sizeof(*stm));
425 assert(stm);
426
427 stm->context = ctx;
428 stm->data_callback = data_callback;
429 stm->state_callback = state_callback;
430 stm->user_ptr = user_ptr;
431
432 stm->stream_type = stream_params.stream_type;
433 stm->framesize = stream_params.channels * sizeof(int16_t);
434 stm->bytespersec = stream_params.rate * stm->framesize;
435 stm->queuebuf_len = (stm->bytespersec * latency) / (1000 * NBUFS);
436 // round up to the next multiple of stm->framesize, if needed.
437 if (stm->queuebuf_len % stm->framesize) {
438 stm->queuebuf_len += stm->framesize - (stm->queuebuf_len % stm->framesize);
439 }
440 int i;
441 for (i = 0; i < NBUFS; i++) {
442 stm->queuebuf[i] = malloc(stm->queuebuf_len);
443 assert(stm->queuebuf[i]);
444 }
445
446 SLDataLocator_BufferQueue loc_bufq;
447 loc_bufq.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
448 loc_bufq.numBuffers = NBUFS;
449 SLDataSource source;
450 source.pLocator = &loc_bufq;
451 source.pFormat = &format;
452
453 SLDataLocator_OutputMix loc_outmix;
454 loc_outmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
455 loc_outmix.outputMix = ctx->outmixObj;
456 SLDataSink sink;
457 sink.pLocator = &loc_outmix;
458 sink.pFormat = NULL;
459
460 #if defined(__ANDROID__)
461 const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE, ctx->SL_IID_ANDROIDCONFIGURATION};
462 const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE};
463 #else
464 const SLInterfaceID ids[] = {ctx->SL_IID_BUFFERQUEUE};
465 const SLboolean req[] = {SL_BOOLEAN_TRUE};
466 #endif
467 assert(NELEMS(ids) == NELEMS(req));
468 SLresult res = (*ctx->eng)->CreateAudioPlayer(ctx->eng, &stm->playerObj,
469 &source, &sink, NELEMS(ids), ids, req);
470 if (res != SL_RESULT_SUCCESS) {
471 opensl_stream_destroy(stm);
472 return CUBEB_ERROR;
473 }
474
475 #if defined(__ANDROID__)
476 SLuint32 stream_type = convert_stream_type_to_sl_stream(stream_params.stream_type);
477 if (stream_type != 0xFFFFFFFF) {
478 SLAndroidConfigurationItf playerConfig;
479 res = (*stm->playerObj)->GetInterface(stm->playerObj,
480 ctx->SL_IID_ANDROIDCONFIGURATION, &playerConfig);
481 res = (*playerConfig)->SetConfiguration(playerConfig,
482 SL_ANDROID_KEY_STREAM_TYPE, &stream_type, sizeof(SLint32));
483 if (res != SL_RESULT_SUCCESS) {
484 opensl_stream_destroy(stm);
485 return CUBEB_ERROR;
486 }
487 }
488 #endif
489
490 res = (*stm->playerObj)->Realize(stm->playerObj, SL_BOOLEAN_FALSE);
491 if (res != SL_RESULT_SUCCESS) {
492 opensl_stream_destroy(stm);
493 return CUBEB_ERROR;
494 }
495
496 res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_PLAY, &stm->play);
497 if (res != SL_RESULT_SUCCESS) {
498 opensl_stream_destroy(stm);
499 return CUBEB_ERROR;
500 }
501
502 res = (*stm->playerObj)->GetInterface(stm->playerObj, ctx->SL_IID_BUFFERQUEUE,
503 &stm->bufq);
504 if (res != SL_RESULT_SUCCESS) {
505 opensl_stream_destroy(stm);
506 return CUBEB_ERROR;
507 }
508
509 res = (*stm->bufq)->RegisterCallback(stm->bufq, bufferqueue_callback, stm);
510 if (res != SL_RESULT_SUCCESS) {
511 opensl_stream_destroy(stm);
512 return CUBEB_ERROR;
513 }
514
515 *stream = stm;
516 return CUBEB_OK;
517 }
518
519 static void
520 opensl_stream_destroy(cubeb_stream * stm)
521 {
522 if (stm->playerObj)
523 (*stm->playerObj)->Destroy(stm->playerObj);
524 int i;
525 for (i = 0; i < NBUFS; i++) {
526 free(stm->queuebuf[i]);
527 }
528
529 free(stm);
530 }
531
532 static int
533 opensl_stream_start(cubeb_stream * stm)
534 {
535 /* To refill the queues before starting playback in order to avoid racing
536 * with refills started by SetPlayState on OpenSLES ndk threads. */
537 bufferqueue_callback(NULL, stm);
538 SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PLAYING);
539 if (res != SL_RESULT_SUCCESS)
540 return CUBEB_ERROR;
541 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STARTED);
542 return CUBEB_OK;
543 }
544
545 static int
546 opensl_stream_stop(cubeb_stream * stm)
547 {
548 SLresult res = (*stm->play)->SetPlayState(stm->play, SL_PLAYSTATE_PAUSED);
549 if (res != SL_RESULT_SUCCESS)
550 return CUBEB_ERROR;
551 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
552 return CUBEB_OK;
553 }
554
555 static int
556 opensl_stream_get_position(cubeb_stream * stm, uint64_t * position)
557 {
558 SLmillisecond msec;
559 uint64_t samplerate;
560 SLresult res;
561 int rv;
562 uint32_t mixer_latency;
563
564 res = (*stm->play)->GetPosition(stm->play, &msec);
565 if (res != SL_RESULT_SUCCESS)
566 return CUBEB_ERROR;
567
568 samplerate = stm->bytespersec / stm->framesize;
569
570 rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
571 if (rv) {
572 return CUBEB_ERROR;
573 }
574
575 if (msec > mixer_latency) {
576 *position = samplerate * (msec - mixer_latency) / 1000;
577 } else {
578 *position = 0;
579 }
580 return CUBEB_OK;
581 }
582
583 int
584 opensl_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
585 {
586 int rv;
587 uint32_t mixer_latency;
588 uint32_t samplerate;
589
590 /* The latency returned by AudioFlinger is in ms, so we have to get
591 * AudioFlinger's samplerate to convert it to frames. */
592 rv = opensl_get_preferred_sample_rate(stm->context, &samplerate);
593 if (rv) {
594 return CUBEB_ERROR;
595 }
596
597 /* audio_stream_type_t is an int, so this is okay. */
598 rv = stm->context->get_output_latency(&mixer_latency, stm->stream_type);
599 if (rv) {
600 return CUBEB_ERROR;
601 }
602
603 *latency = NBUFS * stm->queuebuf_len / stm->framesize + // OpenSL latency
604 mixer_latency * samplerate / 1000; // AudioFlinger latency
605
606 return CUBEB_OK;
607 }
608
609 static struct cubeb_ops const opensl_ops = {
610 .init = opensl_init,
611 .get_backend_id = opensl_get_backend_id,
612 .get_max_channel_count = opensl_get_max_channel_count,
613 .get_min_latency = opensl_get_min_latency,
614 .get_preferred_sample_rate = opensl_get_preferred_sample_rate,
615 .destroy = opensl_destroy,
616 .stream_init = opensl_stream_init,
617 .stream_destroy = opensl_stream_destroy,
618 .stream_start = opensl_stream_start,
619 .stream_stop = opensl_stream_stop,
620 .stream_get_position = opensl_stream_get_position,
621 .stream_get_latency = opensl_stream_get_latency
622 };

mercurial