|
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 }; |