media/libcubeb/src/cubeb_pulse.c

branch
TOR_BUG_9701
changeset 15
b8a032363ba2
equal deleted inserted replaced
-1:000000000000 0:33e41c9d6478
1 /*
2 * Copyright © 2011 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 <pulse/pulseaudio.h>
12 #include <string.h>
13 #include "cubeb/cubeb.h"
14 #include "cubeb-internal.h"
15
16 #ifdef DISABLE_LIBPULSE_DLOPEN
17 #define WRAP(x) x
18 #else
19 #define WRAP(x) cubeb_##x
20 #define MAKE_TYPEDEF(x) static typeof(x) * cubeb_##x
21 MAKE_TYPEDEF(pa_channel_map_init_auto);
22 MAKE_TYPEDEF(pa_context_connect);
23 MAKE_TYPEDEF(pa_context_disconnect);
24 MAKE_TYPEDEF(pa_context_drain);
25 MAKE_TYPEDEF(pa_context_get_state);
26 MAKE_TYPEDEF(pa_context_new);
27 MAKE_TYPEDEF(pa_context_rttime_new);
28 MAKE_TYPEDEF(pa_context_set_state_callback);
29 MAKE_TYPEDEF(pa_context_unref);
30 MAKE_TYPEDEF(pa_context_get_sink_info_by_name);
31 MAKE_TYPEDEF(pa_context_get_server_info);
32 MAKE_TYPEDEF(pa_frame_size);
33 MAKE_TYPEDEF(pa_operation_get_state);
34 MAKE_TYPEDEF(pa_operation_unref);
35 MAKE_TYPEDEF(pa_rtclock_now);
36 MAKE_TYPEDEF(pa_stream_begin_write);
37 MAKE_TYPEDEF(pa_stream_cancel_write);
38 MAKE_TYPEDEF(pa_stream_connect_playback);
39 MAKE_TYPEDEF(pa_stream_cork);
40 MAKE_TYPEDEF(pa_stream_disconnect);
41 MAKE_TYPEDEF(pa_stream_get_latency);
42 MAKE_TYPEDEF(pa_stream_get_state);
43 MAKE_TYPEDEF(pa_stream_get_time);
44 MAKE_TYPEDEF(pa_stream_new);
45 MAKE_TYPEDEF(pa_stream_set_state_callback);
46 MAKE_TYPEDEF(pa_stream_set_write_callback);
47 MAKE_TYPEDEF(pa_stream_unref);
48 MAKE_TYPEDEF(pa_stream_update_timing_info);
49 MAKE_TYPEDEF(pa_stream_write);
50 MAKE_TYPEDEF(pa_threaded_mainloop_free);
51 MAKE_TYPEDEF(pa_threaded_mainloop_get_api);
52 MAKE_TYPEDEF(pa_threaded_mainloop_lock);
53 MAKE_TYPEDEF(pa_threaded_mainloop_in_thread);
54 MAKE_TYPEDEF(pa_threaded_mainloop_new);
55 MAKE_TYPEDEF(pa_threaded_mainloop_signal);
56 MAKE_TYPEDEF(pa_threaded_mainloop_start);
57 MAKE_TYPEDEF(pa_threaded_mainloop_stop);
58 MAKE_TYPEDEF(pa_threaded_mainloop_unlock);
59 MAKE_TYPEDEF(pa_threaded_mainloop_wait);
60 MAKE_TYPEDEF(pa_usec_to_bytes);
61 #undef MAKE_TYPEDEF
62 #endif
63
64 static struct cubeb_ops const pulse_ops;
65
66 struct cubeb {
67 struct cubeb_ops const * ops;
68 void * libpulse;
69 pa_threaded_mainloop * mainloop;
70 pa_context * context;
71 pa_sink_info * default_sink_info;
72 char * context_name;
73 int error;
74 };
75
76 struct cubeb_stream {
77 cubeb * context;
78 pa_stream * stream;
79 cubeb_data_callback data_callback;
80 cubeb_state_callback state_callback;
81 void * user_ptr;
82 pa_time_event * drain_timer;
83 pa_sample_spec sample_spec;
84 int shutdown;
85 };
86
87 enum cork_state {
88 UNCORK = 0,
89 CORK = 1 << 0,
90 NOTIFY = 1 << 1
91 };
92
93 static void
94 sink_info_callback(pa_context * context, const pa_sink_info * info, int eol, void * u)
95 {
96 cubeb * ctx = u;
97 if (!eol) {
98 ctx->default_sink_info = malloc(sizeof(pa_sink_info));
99 memcpy(ctx->default_sink_info, info, sizeof(pa_sink_info));
100 }
101 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
102 }
103
104 static void
105 server_info_callback(pa_context * context, const pa_server_info * info, void * u)
106 {
107 WRAP(pa_context_get_sink_info_by_name)(context, info->default_sink_name, sink_info_callback, u);
108 }
109
110 static void
111 context_state_callback(pa_context * c, void * u)
112 {
113 cubeb * ctx = u;
114 if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(c))) {
115 ctx->error = 1;
116 }
117 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
118 }
119
120 static void
121 context_notify_callback(pa_context * c, void * u)
122 {
123 cubeb * ctx = u;
124 WRAP(pa_threaded_mainloop_signal)(ctx->mainloop, 0);
125 }
126
127 static void
128 stream_success_callback(pa_stream * s, int success, void * u)
129 {
130 cubeb_stream * stm = u;
131 WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
132 }
133
134 static void
135 stream_drain_callback(pa_mainloop_api * a, pa_time_event * e, struct timeval const * tv, void * u)
136 {
137 cubeb_stream * stm = u;
138 /* there's no pa_rttime_free, so use this instead. */
139 a->time_free(stm->drain_timer);
140 stm->drain_timer = NULL;
141 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
142 }
143
144 static void
145 stream_state_callback(pa_stream * s, void * u)
146 {
147 cubeb_stream * stm = u;
148 if (!PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(s))) {
149 stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_ERROR);
150 }
151 WRAP(pa_threaded_mainloop_signal)(stm->context->mainloop, 0);
152 }
153
154 static void
155 stream_request_callback(pa_stream * s, size_t nbytes, void * u)
156 {
157 cubeb_stream * stm;
158 void * buffer;
159 size_t size;
160 int r;
161 long got;
162 size_t towrite;
163 size_t frame_size;
164
165 stm = u;
166
167 if (stm->shutdown)
168 return;
169
170 frame_size = WRAP(pa_frame_size)(&stm->sample_spec);
171
172 assert(nbytes % frame_size == 0);
173
174 towrite = nbytes;
175
176 while (towrite) {
177 size = towrite;
178 r = WRAP(pa_stream_begin_write)(s, &buffer, &size);
179 assert(r == 0);
180 assert(size > 0);
181 assert(size % frame_size == 0);
182
183 got = stm->data_callback(stm, stm->user_ptr, buffer, size / frame_size);
184 if (got < 0) {
185 WRAP(pa_stream_cancel_write)(s);
186 stm->shutdown = 1;
187 return;
188 }
189
190 r = WRAP(pa_stream_write)(s, buffer, got * frame_size, NULL, 0, PA_SEEK_RELATIVE);
191 assert(r == 0);
192
193 if ((size_t) got < size / frame_size) {
194 pa_usec_t latency = 0;
195 r = WRAP(pa_stream_get_latency)(s, &latency, NULL);
196 if (r == -PA_ERR_NODATA) {
197 /* this needs a better guess. */
198 latency = 100 * PA_USEC_PER_MSEC;
199 }
200 assert(r == 0 || r == -PA_ERR_NODATA);
201 /* pa_stream_drain is useless, see PA bug# 866. this is a workaround. */
202 /* arbitrary safety margin: double the current latency. */
203 stm->drain_timer = WRAP(pa_context_rttime_new)(stm->context->context, WRAP(pa_rtclock_now)() + 2 * latency, stream_drain_callback, stm);
204 stm->shutdown = 1;
205 return;
206 }
207
208 towrite -= size;
209 }
210
211 assert(towrite == 0);
212 }
213
214 static int
215 wait_until_context_ready(cubeb * ctx)
216 {
217 for (;;) {
218 pa_context_state_t state = WRAP(pa_context_get_state)(ctx->context);
219 if (!PA_CONTEXT_IS_GOOD(state))
220 return -1;
221 if (state == PA_CONTEXT_READY)
222 break;
223 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
224 }
225 return 0;
226 }
227
228 static int
229 wait_until_stream_ready(cubeb_stream * stm)
230 {
231 for (;;) {
232 pa_stream_state_t state = WRAP(pa_stream_get_state)(stm->stream);
233 if (!PA_STREAM_IS_GOOD(state))
234 return -1;
235 if (state == PA_STREAM_READY)
236 break;
237 WRAP(pa_threaded_mainloop_wait)(stm->context->mainloop);
238 }
239 return 0;
240 }
241
242 static int
243 operation_wait(cubeb * ctx, pa_stream * stream, pa_operation * o)
244 {
245 while (WRAP(pa_operation_get_state)(o) == PA_OPERATION_RUNNING) {
246 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
247 if (!PA_CONTEXT_IS_GOOD(WRAP(pa_context_get_state)(ctx->context)))
248 return -1;
249 if (stream && !PA_STREAM_IS_GOOD(WRAP(pa_stream_get_state)(stream)))
250 return -1;
251 }
252 return 0;
253 }
254
255 static void
256 stream_cork(cubeb_stream * stm, enum cork_state state)
257 {
258 pa_operation * o;
259
260 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
261 o = WRAP(pa_stream_cork)(stm->stream, state & CORK, stream_success_callback, stm);
262 if (o) {
263 operation_wait(stm->context, stm->stream, o);
264 WRAP(pa_operation_unref)(o);
265 }
266 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
267
268 if (state & NOTIFY) {
269 stm->state_callback(stm, stm->user_ptr,
270 state & CORK ? CUBEB_STATE_STOPPED : CUBEB_STATE_STARTED);
271 }
272 }
273
274 static void pulse_context_destroy(cubeb * ctx);
275 static void pulse_destroy(cubeb * ctx);
276
277 static int
278 pulse_context_init(cubeb * ctx)
279 {
280 if (ctx->context) {
281 assert(ctx->error == 1);
282 pulse_context_destroy(ctx);
283 }
284
285 ctx->context = WRAP(pa_context_new)(WRAP(pa_threaded_mainloop_get_api)(ctx->mainloop),
286 ctx->context_name);
287 WRAP(pa_context_set_state_callback)(ctx->context, context_state_callback, ctx);
288
289 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
290 WRAP(pa_context_connect)(ctx->context, NULL, 0, NULL);
291
292 if (wait_until_context_ready(ctx) != 0) {
293 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
294 pulse_context_destroy(ctx);
295 ctx->context = NULL;
296 return -1;
297 }
298
299 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
300
301 ctx->error = 0;
302
303 return 0;
304 }
305
306 /*static*/ int
307 pulse_init(cubeb ** context, char const * context_name)
308 {
309 void * libpulse = NULL;
310 cubeb * ctx;
311
312 *context = NULL;
313
314 #ifndef DISABLE_LIBPULSE_DLOPEN
315 libpulse = dlopen("libpulse.so.0", RTLD_LAZY);
316 if (!libpulse) {
317 return CUBEB_ERROR;
318 }
319
320 #define LOAD(x) do { \
321 cubeb_##x = dlsym(libpulse, #x); \
322 if (!cubeb_##x) { \
323 dlclose(libpulse); \
324 return CUBEB_ERROR; \
325 } \
326 } while(0)
327 LOAD(pa_channel_map_init_auto);
328 LOAD(pa_context_connect);
329 LOAD(pa_context_disconnect);
330 LOAD(pa_context_drain);
331 LOAD(pa_context_get_state);
332 LOAD(pa_context_new);
333 LOAD(pa_context_rttime_new);
334 LOAD(pa_context_set_state_callback);
335 LOAD(pa_context_get_sink_info_by_name);
336 LOAD(pa_context_get_server_info);
337 LOAD(pa_context_unref);
338 LOAD(pa_frame_size);
339 LOAD(pa_operation_get_state);
340 LOAD(pa_operation_unref);
341 LOAD(pa_rtclock_now);
342 LOAD(pa_stream_begin_write);
343 LOAD(pa_stream_cancel_write);
344 LOAD(pa_stream_connect_playback);
345 LOAD(pa_stream_cork);
346 LOAD(pa_stream_disconnect);
347 LOAD(pa_stream_get_latency);
348 LOAD(pa_stream_get_state);
349 LOAD(pa_stream_get_time);
350 LOAD(pa_stream_new);
351 LOAD(pa_stream_set_state_callback);
352 LOAD(pa_stream_set_write_callback);
353 LOAD(pa_stream_unref);
354 LOAD(pa_stream_update_timing_info);
355 LOAD(pa_stream_write);
356 LOAD(pa_threaded_mainloop_free);
357 LOAD(pa_threaded_mainloop_get_api);
358 LOAD(pa_threaded_mainloop_lock);
359 LOAD(pa_threaded_mainloop_in_thread);
360 LOAD(pa_threaded_mainloop_new);
361 LOAD(pa_threaded_mainloop_signal);
362 LOAD(pa_threaded_mainloop_start);
363 LOAD(pa_threaded_mainloop_stop);
364 LOAD(pa_threaded_mainloop_unlock);
365 LOAD(pa_threaded_mainloop_wait);
366 LOAD(pa_usec_to_bytes);
367 #undef LOAD
368 #endif
369
370 ctx = calloc(1, sizeof(*ctx));
371 assert(ctx);
372
373 ctx->ops = &pulse_ops;
374 ctx->libpulse = libpulse;
375
376 ctx->mainloop = WRAP(pa_threaded_mainloop_new)();
377 ctx->default_sink_info = NULL;
378
379 WRAP(pa_threaded_mainloop_start)(ctx->mainloop);
380
381 ctx->context_name = context_name ? strdup(context_name) : NULL;
382 if (pulse_context_init(ctx) != 0) {
383 pulse_destroy(ctx);
384 return CUBEB_ERROR;
385 }
386
387 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
388 WRAP(pa_context_get_server_info)(ctx->context, server_info_callback, ctx);
389 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
390
391 *context = ctx;
392
393 return CUBEB_OK;
394 }
395
396 static char const *
397 pulse_get_backend_id(cubeb * ctx)
398 {
399 return "pulse";
400 }
401
402 static int
403 pulse_get_max_channel_count(cubeb * ctx, uint32_t * max_channels)
404 {
405 assert(ctx && max_channels);
406
407 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
408 while (!ctx->default_sink_info) {
409 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
410 }
411 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
412
413 *max_channels = ctx->default_sink_info->channel_map.channels;
414
415 return CUBEB_OK;
416 }
417
418 static int
419 pulse_get_preferred_sample_rate(cubeb * ctx, uint32_t * rate)
420 {
421 assert(ctx && rate);
422
423 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
424 while (!ctx->default_sink_info) {
425 WRAP(pa_threaded_mainloop_wait)(ctx->mainloop);
426 }
427 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
428
429 *rate = ctx->default_sink_info->sample_spec.rate;
430
431 return CUBEB_OK;
432 }
433
434 static int
435 pulse_get_min_latency(cubeb * ctx, cubeb_stream_params params, uint32_t * latency_ms)
436 {
437 // According to PulseAudio developers, this is a safe minimum.
438 *latency_ms = 40;
439
440 return CUBEB_OK;
441 }
442
443 static void
444 pulse_context_destroy(cubeb * ctx)
445 {
446 pa_operation * o;
447
448 WRAP(pa_threaded_mainloop_lock)(ctx->mainloop);
449 o = WRAP(pa_context_drain)(ctx->context, context_notify_callback, ctx);
450 if (o) {
451 operation_wait(ctx, NULL, o);
452 WRAP(pa_operation_unref)(o);
453 }
454 WRAP(pa_context_set_state_callback)(ctx->context, NULL, NULL);
455 WRAP(pa_context_disconnect)(ctx->context);
456 WRAP(pa_context_unref)(ctx->context);
457 WRAP(pa_threaded_mainloop_unlock)(ctx->mainloop);
458 }
459
460 static void
461 pulse_destroy(cubeb * ctx)
462 {
463 pa_operation * o;
464
465 if (ctx->context_name) {
466 free(ctx->context_name);
467 }
468 if (ctx->context) {
469 pulse_context_destroy(ctx);
470 }
471
472 if (ctx->mainloop) {
473 WRAP(pa_threaded_mainloop_stop)(ctx->mainloop);
474 WRAP(pa_threaded_mainloop_free)(ctx->mainloop);
475 }
476
477 if (ctx->libpulse) {
478 dlclose(ctx->libpulse);
479 }
480 if (ctx->default_sink_info) {
481 free(ctx->default_sink_info);
482 }
483 free(ctx);
484 }
485
486 static void pulse_stream_destroy(cubeb_stream * stm);
487
488 static int
489 pulse_stream_init(cubeb * context, cubeb_stream ** stream, char const * stream_name,
490 cubeb_stream_params stream_params, unsigned int latency,
491 cubeb_data_callback data_callback, cubeb_state_callback state_callback,
492 void * user_ptr)
493 {
494 pa_sample_spec ss;
495 cubeb_stream * stm;
496 pa_operation * o;
497 pa_buffer_attr battr;
498 int r;
499
500 assert(context);
501
502 *stream = NULL;
503
504 switch (stream_params.format) {
505 case CUBEB_SAMPLE_S16LE:
506 ss.format = PA_SAMPLE_S16LE;
507 break;
508 case CUBEB_SAMPLE_S16BE:
509 ss.format = PA_SAMPLE_S16BE;
510 break;
511 case CUBEB_SAMPLE_FLOAT32LE:
512 ss.format = PA_SAMPLE_FLOAT32LE;
513 break;
514 case CUBEB_SAMPLE_FLOAT32BE:
515 ss.format = PA_SAMPLE_FLOAT32BE;
516 break;
517 default:
518 return CUBEB_ERROR_INVALID_FORMAT;
519 }
520
521 // If the connection failed for some reason, try to reconnect
522 if (context->error == 1 && pulse_context_init(context) != 0) {
523 return CUBEB_ERROR;
524 }
525
526 ss.rate = stream_params.rate;
527 ss.channels = stream_params.channels;
528
529 stm = calloc(1, sizeof(*stm));
530 assert(stm);
531
532 stm->context = context;
533
534 stm->data_callback = data_callback;
535 stm->state_callback = state_callback;
536 stm->user_ptr = user_ptr;
537
538 stm->sample_spec = ss;
539
540 battr.maxlength = -1;
541 battr.tlength = WRAP(pa_usec_to_bytes)(latency * PA_USEC_PER_MSEC, &stm->sample_spec);
542 battr.prebuf = -1;
543 battr.minreq = battr.tlength / 4;
544 battr.fragsize = -1;
545
546 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
547 stm->stream = WRAP(pa_stream_new)(stm->context->context, stream_name, &ss, NULL);
548 if (!stm->stream) {
549 pulse_stream_destroy(stm);
550 return CUBEB_ERROR;
551 }
552 WRAP(pa_stream_set_state_callback)(stm->stream, stream_state_callback, stm);
553 WRAP(pa_stream_set_write_callback)(stm->stream, stream_request_callback, stm);
554 WRAP(pa_stream_connect_playback)(stm->stream, NULL, &battr,
555 PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING |
556 PA_STREAM_START_CORKED,
557 NULL, NULL);
558
559 r = wait_until_stream_ready(stm);
560 if (r == 0) {
561 /* force a timing update now, otherwise timing info does not become valid
562 until some point after initialization has completed. */
563 o = WRAP(pa_stream_update_timing_info)(stm->stream, stream_success_callback, stm);
564 if (o) {
565 r = operation_wait(stm->context, stm->stream, o);
566 WRAP(pa_operation_unref)(o);
567 }
568 }
569 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
570
571 if (r != 0) {
572 pulse_stream_destroy(stm);
573 return CUBEB_ERROR;
574 }
575
576 *stream = stm;
577
578 return CUBEB_OK;
579 }
580
581 static void
582 pulse_stream_destroy(cubeb_stream * stm)
583 {
584 if (stm->stream) {
585 stream_cork(stm, CORK);
586
587 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
588
589 if (stm->drain_timer) {
590 /* there's no pa_rttime_free, so use this instead. */
591 WRAP(pa_threaded_mainloop_get_api)(stm->context->mainloop)->time_free(stm->drain_timer);
592 }
593
594 WRAP(pa_stream_set_state_callback)(stm->stream, NULL, NULL);
595 WRAP(pa_stream_disconnect)(stm->stream);
596 WRAP(pa_stream_unref)(stm->stream);
597 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
598 }
599
600 free(stm);
601 }
602
603 static int
604 pulse_stream_start(cubeb_stream * stm)
605 {
606 stream_cork(stm, UNCORK | NOTIFY);
607 return CUBEB_OK;
608 }
609
610 static int
611 pulse_stream_stop(cubeb_stream * stm)
612 {
613 stream_cork(stm, CORK | NOTIFY);
614 return CUBEB_OK;
615 }
616
617 static int
618 pulse_stream_get_position(cubeb_stream * stm, uint64_t * position)
619 {
620 int r, in_thread;
621 pa_usec_t r_usec;
622 uint64_t bytes;
623
624 in_thread = WRAP(pa_threaded_mainloop_in_thread)(stm->context->mainloop);
625
626 if (!in_thread) {
627 WRAP(pa_threaded_mainloop_lock)(stm->context->mainloop);
628 }
629 r = WRAP(pa_stream_get_time)(stm->stream, &r_usec);
630 if (!in_thread) {
631 WRAP(pa_threaded_mainloop_unlock)(stm->context->mainloop);
632 }
633
634 if (r != 0) {
635 return CUBEB_ERROR;
636 }
637
638 bytes = WRAP(pa_usec_to_bytes)(r_usec, &stm->sample_spec);
639 *position = bytes / WRAP(pa_frame_size)(&stm->sample_spec);
640
641 return CUBEB_OK;
642 }
643
644 int
645 pulse_stream_get_latency(cubeb_stream * stm, uint32_t * latency)
646 {
647 pa_usec_t r_usec;
648 int negative, r;
649
650 if (!stm) {
651 return CUBEB_ERROR;
652 }
653
654 r = WRAP(pa_stream_get_latency)(stm->stream, &r_usec, &negative);
655 assert(!negative);
656 if (r) {
657 return CUBEB_ERROR;
658 }
659
660 *latency = r_usec * stm->sample_spec.rate / PA_USEC_PER_SEC;
661 return CUBEB_OK;
662 }
663
664 static struct cubeb_ops const pulse_ops = {
665 .init = pulse_init,
666 .get_backend_id = pulse_get_backend_id,
667 .get_max_channel_count = pulse_get_max_channel_count,
668 .get_min_latency = pulse_get_min_latency,
669 .get_preferred_sample_rate = pulse_get_preferred_sample_rate,
670 .destroy = pulse_destroy,
671 .stream_init = pulse_stream_init,
672 .stream_destroy = pulse_stream_destroy,
673 .stream_start = pulse_stream_start,
674 .stream_stop = pulse_stream_stop,
675 .stream_get_position = pulse_stream_get_position,
676 .stream_get_latency = pulse_stream_get_latency
677 };

mercurial