1 /// Translated from C to D
2 module soundio.pulseaudio;
3 
4 version(SOUNDIO_HAVE_PULSEAUDIO):
5 @nogc nothrow:
6 extern(C): __gshared:
7 
8 
9 import soundio.api;
10 import soundio.atomics;
11 import soundio.util;
12 import soundio.soundio_private;
13 import soundio.headers.pulseheader;
14 import core.stdc.string;
15 import core.stdc.stdio;
16 import core.stdc.stdlib: free;
17 
18 private:
19 
20 package struct SoundIoDevicePulseAudio { int make_the_struct_not_empty; }
21 
22 package struct SoundIoPulseAudio {
23     int device_query_err;
24     int connection_err;
25     bool emitted_shutdown_cb;
26 
27     pa_context* pulse_context;
28     bool device_scan_queued;
29 
30     // the one that we're working on building
31     SoundIoDevicesInfo* current_devices_info;
32     char* default_sink_name;
33     char* default_source_name;
34 
35     // this one is ready to be read with flush_events. protected by mutex
36     SoundIoDevicesInfo* ready_devices_info;
37 
38     bool ready_flag;
39 
40     pa_threaded_mainloop* main_loop;
41     pa_proplist* props;
42 }
43 
44 package struct SoundIoOutStreamPulseAudio {
45     pa_stream* stream;
46     SoundIoAtomicBool stream_ready;
47     pa_buffer_attr buffer_attr;
48     char* write_ptr;
49     size_t write_byte_count;
50     SoundIoAtomicFlag clear_buffer_flag;
51     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
52 }
53 
54 package struct SoundIoInStreamPulseAudio {
55     pa_stream* stream;
56     SoundIoAtomicBool stream_ready;
57     pa_buffer_attr buffer_attr;
58     char* peek_buf;
59     size_t peek_buf_index;
60     size_t peek_buf_size;
61     int peek_buf_frames_left;
62     int read_frame_count;
63     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
64 }
65 
66 static void subscribe_callback(pa_context* context, pa_subscription_event_type_t event_bits, uint index, void* userdata) {
67     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
68     SoundIo* soundio = &si.pub;
69     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
70     sipa.device_scan_queued = true;
71     pa_threaded_mainloop_signal(sipa.main_loop, 0);
72     soundio.on_events_signal(soundio);
73 }
74 
75 static int subscribe_to_events(SoundIoPrivate* si) {
76     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
77     pa_subscription_mask_t events = cast(pa_subscription_mask_t)(
78             PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE|PA_SUBSCRIPTION_MASK_SERVER
79     );
80     pa_operation* subscribe_op = pa_context_subscribe(sipa.pulse_context, events, null, si);
81     if (!subscribe_op)
82         return SoundIoError.NoMem;
83     pa_operation_unref(subscribe_op);
84     return 0;
85 }
86 
87 static void context_state_callback(pa_context* context, void* userdata) {
88     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
89     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
90     SoundIo* soundio = &si.pub;
91 
92     switch (pa_context_get_state(context)) {
93     case PA_CONTEXT_UNCONNECTED: // The context hasn't been connected yet.
94         return;
95     case PA_CONTEXT_CONNECTING: // A connection is being established.
96         return;
97     case PA_CONTEXT_AUTHORIZING: // The client is authorizing itself to the daemon.
98         return;
99     case PA_CONTEXT_SETTING_NAME: // The client is passing its application name to the daemon.
100         return;
101     case PA_CONTEXT_READY: // The connection is established, the context is ready to execute operations.
102         sipa.ready_flag = true;
103         pa_threaded_mainloop_signal(sipa.main_loop, 0);
104         return;
105     case PA_CONTEXT_TERMINATED: // The connection was terminated cleanly.
106         pa_threaded_mainloop_signal(sipa.main_loop, 0);
107         return;
108     case PA_CONTEXT_FAILED: // The connection failed or was disconnected.
109         if (sipa.ready_flag) {
110             sipa.connection_err = SoundIoError.BackendDisconnected;
111         } else {
112             sipa.connection_err = SoundIoError.InitAudioBackend;
113             sipa.ready_flag = true;
114         }
115         pa_threaded_mainloop_signal(sipa.main_loop, 0);
116         soundio.on_events_signal(soundio);
117         return;
118     default: break;
119     }
120 }
121 
122 static void destroy_pa(SoundIoPrivate* si) {
123     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
124 
125     if (sipa.main_loop)
126         pa_threaded_mainloop_stop(sipa.main_loop);
127 
128     pa_context_disconnect(sipa.pulse_context);
129     pa_context_unref(sipa.pulse_context);
130 
131     soundio_destroy_devices_info(sipa.current_devices_info);
132     soundio_destroy_devices_info(sipa.ready_devices_info);
133 
134     if (sipa.main_loop)
135         pa_threaded_mainloop_free(sipa.main_loop);
136 
137     if (sipa.props)
138         pa_proplist_free(sipa.props);
139 
140     free(sipa.default_sink_name);
141     free(sipa.default_source_name);
142 }
143 
144 static SoundIoFormat from_pulseaudio_format(pa_sample_spec sample_spec) {
145     switch (sample_spec.format) {
146     case PA_SAMPLE_U8:          return SoundIoFormat.U8;
147     case PA_SAMPLE_S16LE:       return SoundIoFormat.S16LE;
148     case PA_SAMPLE_S16BE:       return SoundIoFormat.S16BE;
149     case PA_SAMPLE_FLOAT32LE:   return SoundIoFormat.Float32LE;
150     case PA_SAMPLE_FLOAT32BE:   return SoundIoFormat.Float32BE;
151     case PA_SAMPLE_S32LE:       return SoundIoFormat.S32LE;
152     case PA_SAMPLE_S32BE:       return SoundIoFormat.S32BE;
153     case PA_SAMPLE_S24_32LE:    return SoundIoFormat.S24LE;
154     case PA_SAMPLE_S24_32BE:    return SoundIoFormat.S24BE;
155 
156     case PA_SAMPLE_MAX:
157     case PA_SAMPLE_INVALID:
158     case PA_SAMPLE_ALAW:
159     case PA_SAMPLE_ULAW:
160     case PA_SAMPLE_S24LE:
161     case PA_SAMPLE_S24BE:
162         return SoundIoFormat.Invalid;
163     default: break;
164     }
165     return SoundIoFormat.Invalid;
166 }
167 
168 static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) {
169     switch (pos) {
170     case PA_CHANNEL_POSITION_MONO: return SoundIoChannelId.FrontCenter;
171     case PA_CHANNEL_POSITION_FRONT_LEFT: return SoundIoChannelId.FrontLeft;
172     case PA_CHANNEL_POSITION_FRONT_RIGHT: return SoundIoChannelId.FrontRight;
173     case PA_CHANNEL_POSITION_FRONT_CENTER: return SoundIoChannelId.FrontCenter;
174     case PA_CHANNEL_POSITION_REAR_CENTER: return SoundIoChannelId.BackCenter;
175     case PA_CHANNEL_POSITION_REAR_LEFT: return SoundIoChannelId.BackLeft;
176     case PA_CHANNEL_POSITION_REAR_RIGHT: return SoundIoChannelId.BackRight;
177     case PA_CHANNEL_POSITION_LFE: return SoundIoChannelId.Lfe;
178     case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: return SoundIoChannelId.FrontLeftCenter;
179     case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: return SoundIoChannelId.FrontRightCenter;
180     case PA_CHANNEL_POSITION_SIDE_LEFT: return SoundIoChannelId.SideLeft;
181     case PA_CHANNEL_POSITION_SIDE_RIGHT: return SoundIoChannelId.SideRight;
182     case PA_CHANNEL_POSITION_TOP_CENTER: return SoundIoChannelId.TopCenter;
183     case PA_CHANNEL_POSITION_TOP_FRONT_LEFT: return SoundIoChannelId.TopFrontLeft;
184     case PA_CHANNEL_POSITION_TOP_FRONT_RIGHT: return SoundIoChannelId.TopFrontRight;
185     case PA_CHANNEL_POSITION_TOP_FRONT_CENTER: return SoundIoChannelId.TopFrontCenter;
186     case PA_CHANNEL_POSITION_TOP_REAR_LEFT: return SoundIoChannelId.TopBackLeft;
187     case PA_CHANNEL_POSITION_TOP_REAR_RIGHT: return SoundIoChannelId.TopBackRight;
188     case PA_CHANNEL_POSITION_TOP_REAR_CENTER: return SoundIoChannelId.TopBackCenter;
189 
190     case PA_CHANNEL_POSITION_AUX0: return SoundIoChannelId.Aux0;
191     case PA_CHANNEL_POSITION_AUX1: return SoundIoChannelId.Aux1;
192     case PA_CHANNEL_POSITION_AUX2: return SoundIoChannelId.Aux2;
193     case PA_CHANNEL_POSITION_AUX3: return SoundIoChannelId.Aux3;
194     case PA_CHANNEL_POSITION_AUX4: return SoundIoChannelId.Aux4;
195     case PA_CHANNEL_POSITION_AUX5: return SoundIoChannelId.Aux5;
196     case PA_CHANNEL_POSITION_AUX6: return SoundIoChannelId.Aux6;
197     case PA_CHANNEL_POSITION_AUX7: return SoundIoChannelId.Aux7;
198     case PA_CHANNEL_POSITION_AUX8: return SoundIoChannelId.Aux8;
199     case PA_CHANNEL_POSITION_AUX9: return SoundIoChannelId.Aux9;
200     case PA_CHANNEL_POSITION_AUX10: return SoundIoChannelId.Aux10;
201     case PA_CHANNEL_POSITION_AUX11: return SoundIoChannelId.Aux11;
202     case PA_CHANNEL_POSITION_AUX12: return SoundIoChannelId.Aux12;
203     case PA_CHANNEL_POSITION_AUX13: return SoundIoChannelId.Aux13;
204     case PA_CHANNEL_POSITION_AUX14: return SoundIoChannelId.Aux14;
205     case PA_CHANNEL_POSITION_AUX15: return SoundIoChannelId.Aux15;
206 
207     default: return SoundIoChannelId.Invalid;
208     }
209 }
210 
211 static void set_from_pulseaudio_channel_map(pa_channel_map channel_map, SoundIoChannelLayout* channel_layout) {
212     channel_layout.channel_count = channel_map.channels;
213     for (int i = 0; i < channel_map.channels; i += 1) {
214         channel_layout.channels[i] = from_pulseaudio_channel_pos(channel_map.map[i]);
215     }
216     channel_layout.name = null;
217     int builtin_layout_count = soundio_channel_layout_builtin_count();
218     for (int i = 0; i < builtin_layout_count; i += 1) {
219         const(SoundIoChannelLayout)* builtin_layout = soundio_channel_layout_get_builtin(i);
220         if (soundio_channel_layout_equal(builtin_layout, channel_layout)) {
221             channel_layout.name = builtin_layout.name;
222             break;
223         }
224     }
225 }
226 
227 extern(D) int set_all_device_channel_layouts(SoundIoDevice* device) {
228     device.layout_count = soundio_channel_layout_builtin_count();
229     device.layouts = ALLOCATE!SoundIoChannelLayout(device.layout_count);
230     if (!device.layouts)
231         return SoundIoError.NoMem;
232     for (int i = 0; i < device.layout_count; i += 1)
233         device.layouts[i] = *soundio_channel_layout_get_builtin(i);
234     return 0;
235 }
236 
237 extern(D) int set_all_device_formats(SoundIoDevice* device) {
238     device.format_count = 9;
239     device.formats = ALLOCATE!SoundIoFormat(device.format_count);
240     if (!device.formats)
241         return SoundIoError.NoMem;
242     device.formats[0] = SoundIoFormat.U8;
243     device.formats[1] = SoundIoFormat.S16LE;
244     device.formats[2] = SoundIoFormat.S16BE;
245     device.formats[3] = SoundIoFormat.Float32LE;
246     device.formats[4] = SoundIoFormat.Float32BE;
247     device.formats[5] = SoundIoFormat.S32LE;
248     device.formats[6] = SoundIoFormat.S32BE;
249     device.formats[7] = SoundIoFormat.S24LE;
250     device.formats[8] = SoundIoFormat.S24BE;
251     return 0;
252 }
253 
254 extern(D) int perform_operation(SoundIoPrivate* si, pa_operation* op) {
255     if (!op)
256         return SoundIoError.NoMem;
257     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
258     for (;;) {
259         switch (pa_operation_get_state(op)) {
260         case PA_OPERATION_RUNNING:
261             pa_threaded_mainloop_wait(sipa.main_loop);
262             continue;
263         case PA_OPERATION_DONE:
264             pa_operation_unref(op);
265             return 0;
266         case PA_OPERATION_CANCELLED:
267             pa_operation_unref(op);
268             return SoundIoError.Interrupted;
269         default: break;
270         }
271     }
272 }
273 
274 void sink_info_callback(pa_context* pulse_context, const(pa_sink_info)* info, int eol, void* userdata) {
275     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
276     SoundIo* soundio = &si.pub;
277     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
278     if (eol) {
279         pa_threaded_mainloop_signal(sipa.main_loop, 0);
280         return;
281     }
282     if (sipa.device_query_err)
283         return;
284 
285     SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
286     if (!dev) {
287         sipa.device_query_err = SoundIoError.NoMem;
288         return;
289     }
290     SoundIoDevice* device = &dev.pub;
291 
292     device.ref_count = 1;
293     device.soundio = soundio;
294     device.id = strdup(info.name);
295     device.name = strdup(info.description);
296     if (!device.id || !device.name) {
297         soundio_device_unref(device);
298         sipa.device_query_err = SoundIoError.NoMem;
299         return;
300     }
301 
302     device.sample_rate_current = info.sample_spec.rate;
303     // PulseAudio performs resampling, so any value is valid. Let's pick
304     // some reasonable min and max values.
305     device.sample_rate_count = 1;
306     device.sample_rates = &dev.prealloc_sample_rate_range;
307     device.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE, device.sample_rate_current);
308     device.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE, device.sample_rate_current);
309 
310     device.current_format = from_pulseaudio_format(info.sample_spec);
311     // PulseAudio performs sample format conversion, so any PulseAudio
312     // value is valid.
313     if (auto err = set_all_device_formats(device)) {
314         soundio_device_unref(device);
315         sipa.device_query_err = SoundIoError.NoMem;
316         return;
317     }
318 
319     set_from_pulseaudio_channel_map(info.channel_map, &device.current_layout);
320     // PulseAudio does channel layout remapping, so any channel layout is valid.
321     if (auto err = set_all_device_channel_layouts(device)) {
322         soundio_device_unref(device);
323         sipa.device_query_err = SoundIoError.NoMem;
324         return;
325     }
326 
327     device.aim = SoundIoDeviceAim.Output;
328 
329     if (sipa.current_devices_info.output_devices.append(device)) {
330         soundio_device_unref(device);
331         sipa.device_query_err = SoundIoError.NoMem;
332         return;
333     }
334 }
335 
336 void source_info_callback(pa_context* pulse_context, const(pa_source_info)* info, int eol, void* userdata) {
337     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
338     SoundIo* soundio = &si.pub;
339     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
340 
341     if (eol) {
342         pa_threaded_mainloop_signal(sipa.main_loop, 0);
343         return;
344     }
345     if (sipa.device_query_err)
346         return;
347 
348     SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
349     if (!dev) {
350         sipa.device_query_err = SoundIoError.NoMem;
351         return;
352     }
353     SoundIoDevice* device = &dev.pub;
354 
355     device.ref_count = 1;
356     device.soundio = soundio;
357     device.id = strdup(info.name);
358     device.name = strdup(info.description);
359     if (!device.id || !device.name) {
360         soundio_device_unref(device);
361         sipa.device_query_err = SoundIoError.NoMem;
362         return;
363     }
364 
365     device.sample_rate_current = info.sample_spec.rate;
366     // PulseAudio performs resampling, so any value is valid. Let's pick
367     // some reasonable min and max values.
368     device.sample_rate_count = 1;
369     device.sample_rates = &dev.prealloc_sample_rate_range;
370     device.sample_rates[0].min = soundio_int_min(SOUNDIO_MIN_SAMPLE_RATE, device.sample_rate_current);
371     device.sample_rates[0].max = soundio_int_max(SOUNDIO_MAX_SAMPLE_RATE, device.sample_rate_current);
372 
373     device.current_format = from_pulseaudio_format(info.sample_spec);
374     // PulseAudio performs sample format conversion, so any PulseAudio
375     // value is valid.
376     if (auto err = set_all_device_formats(device)) {
377         soundio_device_unref(device);
378         sipa.device_query_err = SoundIoError.NoMem;
379         return;
380     }
381 
382     set_from_pulseaudio_channel_map(info.channel_map, &device.current_layout);
383     // PulseAudio does channel layout remapping, so any channel layout is valid.
384     if (auto err = set_all_device_channel_layouts(device)) {
385         soundio_device_unref(device);
386         sipa.device_query_err = SoundIoError.NoMem;
387         return;
388     }
389 
390     device.aim = SoundIoDeviceAim.Input;
391 
392     if (sipa.current_devices_info.input_devices.append(device)) {
393         soundio_device_unref(device);
394         sipa.device_query_err = SoundIoError.NoMem;
395         return;
396     }
397 }
398 
399 static void server_info_callback(pa_context* pulse_context, const(pa_server_info)* info, void* userdata) {
400     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
401     assert(si);
402     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
403 
404     assert(!sipa.default_sink_name);
405     assert(!sipa.default_source_name);
406 
407     sipa.default_sink_name = strdup(info.default_sink_name);
408     sipa.default_source_name = strdup(info.default_source_name);
409 
410     if (!sipa.default_sink_name || !sipa.default_source_name)
411         sipa.device_query_err = SoundIoError.NoMem;
412 
413     pa_threaded_mainloop_signal(sipa.main_loop, 0);
414 }
415 
416 // always called even when refresh_devices succeeds
417 extern(D) void cleanup_refresh_devices(SoundIoPrivate* si) {
418     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
419 
420     soundio_destroy_devices_info(sipa.current_devices_info);
421     sipa.current_devices_info = null;
422 
423     free(sipa.default_sink_name);
424     sipa.default_sink_name = null;
425 
426     free(sipa.default_source_name);
427     sipa.default_source_name = null;
428 }
429 
430 // call this while holding the main loop lock
431 extern(D) int refresh_devices(SoundIoPrivate* si) {
432     SoundIo* soundio = &si.pub;
433     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
434 
435     assert(!sipa.current_devices_info);
436     sipa.current_devices_info = ALLOCATE!SoundIoDevicesInfo(1);
437     if (!sipa.current_devices_info)
438         return SoundIoError.NoMem;
439 
440     pa_operation* list_sink_op = pa_context_get_sink_info_list(sipa.pulse_context, &sink_info_callback, si);
441     pa_operation* list_source_op = pa_context_get_source_info_list(sipa.pulse_context, &source_info_callback, si);
442     pa_operation* server_info_op = pa_context_get_server_info(sipa.pulse_context, &server_info_callback, si);
443 
444     if (auto err = perform_operation(si, list_sink_op)) {
445         return err;
446     }
447     if (auto err = perform_operation(si, list_source_op)) {
448         return err;
449     }
450     if (auto err = perform_operation(si, server_info_op)) {
451         return err;
452     }
453 
454     if (sipa.device_query_err) {
455         return sipa.device_query_err;
456     }
457 
458     // based on the default sink name, figure out the default output index
459     // if the name doesn't match just pick the first one. if there are no
460     // devices then we need to set it to -1.
461     sipa.current_devices_info.default_output_index = -1;
462     sipa.current_devices_info.default_input_index = -1;
463 
464     if (sipa.current_devices_info.input_devices.length > 0) {
465         sipa.current_devices_info.default_input_index = 0;
466         for (int i = 0; i < sipa.current_devices_info.input_devices.length; i += 1) {
467             SoundIoDevice* device = sipa.current_devices_info.input_devices.val_at(i);
468 
469             assert(device.aim == SoundIoDeviceAim.Input);
470             if (strcmp(device.id, sipa.default_source_name) == 0) {
471                 sipa.current_devices_info.default_input_index = i;
472             }
473         }
474     }
475 
476     if (sipa.current_devices_info.output_devices.length > 0) {
477         sipa.current_devices_info.default_output_index = 0;
478         for (int i = 0; i < sipa.current_devices_info.output_devices.length; i += 1) {
479             SoundIoDevice* device = sipa.current_devices_info.output_devices.val_at(i);
480 
481             assert(device.aim == SoundIoDeviceAim.Output);
482             if (strcmp(device.id, sipa.default_sink_name) == 0) {
483                 sipa.current_devices_info.default_output_index = i;
484             }
485         }
486     }
487 
488     soundio_destroy_devices_info(sipa.ready_devices_info);
489     sipa.ready_devices_info = sipa.current_devices_info;
490     sipa.current_devices_info = null;
491     pa_threaded_mainloop_signal(sipa.main_loop, 0);
492     soundio.on_events_signal(soundio);
493 
494     return 0;
495 }
496 
497 extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) {
498     SoundIo* soundio = &si.pub;
499     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
500 
501     bool change = false;
502     bool cb_shutdown = false;
503     SoundIoDevicesInfo* old_devices_info = null;
504 
505     pa_threaded_mainloop_lock(sipa.main_loop);
506 
507     if (wait)
508         pa_threaded_mainloop_wait(sipa.main_loop);
509 
510     if (sipa.device_scan_queued && !sipa.connection_err) {
511         sipa.device_scan_queued = false;
512         sipa.connection_err = refresh_devices(si);
513         cleanup_refresh_devices(si);
514     }
515 
516     if (sipa.connection_err && !sipa.emitted_shutdown_cb) {
517         sipa.emitted_shutdown_cb = true;
518         cb_shutdown = true;
519     } else if (sipa.ready_devices_info) {
520         old_devices_info = si.safe_devices_info;
521         si.safe_devices_info = sipa.ready_devices_info;
522         sipa.ready_devices_info = null;
523         change = true;
524     }
525 
526     pa_threaded_mainloop_unlock(sipa.main_loop);
527 
528     if (cb_shutdown)
529         soundio.on_backend_disconnect(soundio, sipa.connection_err);
530     else if (change)
531         soundio.on_devices_change(soundio);
532 
533     soundio_destroy_devices_info(old_devices_info);
534 }
535 
536 static void flush_events_pa(SoundIoPrivate* si) {
537     my_flush_events(si, false);
538 }
539 
540 static void wait_events_pa(SoundIoPrivate* si) {
541     my_flush_events(si, false);
542     my_flush_events(si, true);
543 }
544 
545 static void wakeup_pa(SoundIoPrivate* si) {
546     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
547     pa_threaded_mainloop_lock(sipa.main_loop);
548     pa_threaded_mainloop_signal(sipa.main_loop, 0);
549     pa_threaded_mainloop_unlock(sipa.main_loop);
550 }
551 
552 static void force_device_scan_pa(SoundIoPrivate* si) {
553     SoundIo* soundio = &si.pub;
554     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
555     pa_threaded_mainloop_lock(sipa.main_loop);
556     sipa.device_scan_queued = true;
557     pa_threaded_mainloop_signal(sipa.main_loop, 0);
558     soundio.on_events_signal(soundio);
559     pa_threaded_mainloop_unlock(sipa.main_loop);
560 }
561 
562 static pa_sample_format_t to_pulseaudio_format(SoundIoFormat format) {
563     switch (format) {
564     case SoundIoFormat.U8:         return PA_SAMPLE_U8;
565     case SoundIoFormat.S16LE:      return PA_SAMPLE_S16LE;
566     case SoundIoFormat.S16BE:      return PA_SAMPLE_S16BE;
567     case SoundIoFormat.S24LE:      return PA_SAMPLE_S24_32LE;
568     case SoundIoFormat.S24BE:      return PA_SAMPLE_S24_32BE;
569     case SoundIoFormat.S32LE:      return PA_SAMPLE_S32LE;
570     case SoundIoFormat.S32BE:      return PA_SAMPLE_S32BE;
571     case SoundIoFormat.Float32LE:  return PA_SAMPLE_FLOAT32LE;
572     case SoundIoFormat.Float32BE:  return PA_SAMPLE_FLOAT32BE;
573 
574     case SoundIoFormat.Invalid:
575     case SoundIoFormat.S8:
576     case SoundIoFormat.U16LE:
577     case SoundIoFormat.U16BE:
578     case SoundIoFormat.U24LE:
579     case SoundIoFormat.U24BE:
580     case SoundIoFormat.U32LE:
581     case SoundIoFormat.U32BE:
582     case SoundIoFormat.Float64LE:
583     case SoundIoFormat.Float64BE:
584         return PA_SAMPLE_INVALID;
585     default: break;
586     }
587     return PA_SAMPLE_INVALID;
588 }
589 
590 static pa_channel_position_t to_pulseaudio_channel_pos(SoundIoChannelId channel_id) {
591     switch (channel_id) {
592     case SoundIoChannelId.FrontLeft: return PA_CHANNEL_POSITION_FRONT_LEFT;
593     case SoundIoChannelId.FrontRight: return PA_CHANNEL_POSITION_FRONT_RIGHT;
594     case SoundIoChannelId.FrontCenter: return PA_CHANNEL_POSITION_FRONT_CENTER;
595     case SoundIoChannelId.Lfe: return PA_CHANNEL_POSITION_LFE;
596     case SoundIoChannelId.BackLeft: return PA_CHANNEL_POSITION_REAR_LEFT;
597     case SoundIoChannelId.BackRight: return PA_CHANNEL_POSITION_REAR_RIGHT;
598     case SoundIoChannelId.FrontLeftCenter: return PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER;
599     case SoundIoChannelId.FrontRightCenter: return PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER;
600     case SoundIoChannelId.BackCenter: return PA_CHANNEL_POSITION_REAR_CENTER;
601     case SoundIoChannelId.SideLeft: return PA_CHANNEL_POSITION_SIDE_LEFT;
602     case SoundIoChannelId.SideRight: return PA_CHANNEL_POSITION_SIDE_RIGHT;
603     case SoundIoChannelId.TopCenter: return PA_CHANNEL_POSITION_TOP_CENTER;
604     case SoundIoChannelId.TopFrontLeft: return PA_CHANNEL_POSITION_TOP_FRONT_LEFT;
605     case SoundIoChannelId.TopFrontCenter: return PA_CHANNEL_POSITION_TOP_FRONT_CENTER;
606     case SoundIoChannelId.TopFrontRight: return PA_CHANNEL_POSITION_TOP_FRONT_RIGHT;
607     case SoundIoChannelId.TopBackLeft: return PA_CHANNEL_POSITION_TOP_REAR_LEFT;
608     case SoundIoChannelId.TopBackCenter: return PA_CHANNEL_POSITION_TOP_REAR_CENTER;
609     case SoundIoChannelId.TopBackRight: return PA_CHANNEL_POSITION_TOP_REAR_RIGHT;
610 
611     case SoundIoChannelId.Aux0: return PA_CHANNEL_POSITION_AUX0;
612     case SoundIoChannelId.Aux1: return PA_CHANNEL_POSITION_AUX1;
613     case SoundIoChannelId.Aux2: return PA_CHANNEL_POSITION_AUX2;
614     case SoundIoChannelId.Aux3: return PA_CHANNEL_POSITION_AUX3;
615     case SoundIoChannelId.Aux4: return PA_CHANNEL_POSITION_AUX4;
616     case SoundIoChannelId.Aux5: return PA_CHANNEL_POSITION_AUX5;
617     case SoundIoChannelId.Aux6: return PA_CHANNEL_POSITION_AUX6;
618     case SoundIoChannelId.Aux7: return PA_CHANNEL_POSITION_AUX7;
619     case SoundIoChannelId.Aux8: return PA_CHANNEL_POSITION_AUX8;
620     case SoundIoChannelId.Aux9: return PA_CHANNEL_POSITION_AUX9;
621     case SoundIoChannelId.Aux10: return PA_CHANNEL_POSITION_AUX10;
622     case SoundIoChannelId.Aux11: return PA_CHANNEL_POSITION_AUX11;
623     case SoundIoChannelId.Aux12: return PA_CHANNEL_POSITION_AUX12;
624     case SoundIoChannelId.Aux13: return PA_CHANNEL_POSITION_AUX13;
625     case SoundIoChannelId.Aux14: return PA_CHANNEL_POSITION_AUX14;
626     case SoundIoChannelId.Aux15: return PA_CHANNEL_POSITION_AUX15;
627 
628     default:
629         return PA_CHANNEL_POSITION_INVALID;
630     }
631 }
632 
633 static pa_channel_map to_pulseaudio_channel_map(const(SoundIoChannelLayout)* channel_layout) {
634     pa_channel_map channel_map;
635     channel_map.channels = cast(ubyte) channel_layout.channel_count;
636 
637     assert(cast()channel_layout.channel_count <= PA_CHANNELS_MAX);
638 
639     for (int i = 0; i < channel_layout.channel_count; i += 1)
640         channel_map.map[i] = to_pulseaudio_channel_pos(channel_layout.channels[i]);
641 
642     return channel_map;
643 }
644 
645 static void playback_stream_state_callback(pa_stream* stream, void* userdata) {
646     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*) userdata;
647     SoundIoOutStream* outstream = &os.pub;
648     SoundIo* soundio = outstream.device.soundio;
649     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
650     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
651     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
652     switch (pa_stream_get_state(stream)) {
653         case PA_STREAM_UNCONNECTED:
654         case PA_STREAM_CREATING:
655         case PA_STREAM_TERMINATED:
656             break;
657         case PA_STREAM_READY:
658             SOUNDIO_ATOMIC_STORE(ospa.stream_ready, true);
659             pa_threaded_mainloop_signal(sipa.main_loop, 0);
660             break;
661         case PA_STREAM_FAILED:
662             outstream.error_callback(outstream, SoundIoError.Streaming);
663             break;
664         default: break;
665     }
666 }
667 
668 static void playback_stream_underflow_callback(pa_stream* stream, void* userdata) {
669     SoundIoOutStream* outstream = cast(SoundIoOutStream*)userdata;
670     outstream.underflow_callback(outstream);
671 }
672 
673 static void playback_stream_write_callback(pa_stream* stream, size_t nbytes, void* userdata) {
674     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*)(userdata);
675     SoundIoOutStream* outstream = &os.pub;
676     int frame_count = cast(int) (nbytes / outstream.bytes_per_frame);
677     outstream.write_callback(outstream, 0, frame_count);
678 }
679 
680 static void outstream_destroy_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
681     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
682 
683     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
684     pa_stream* stream = ospa.stream;
685     if (stream) {
686         pa_threaded_mainloop_lock(sipa.main_loop);
687 
688         pa_stream_set_write_callback(stream, null, null);
689         pa_stream_set_state_callback(stream, null, null);
690         pa_stream_set_underflow_callback(stream, null, null);
691         pa_stream_set_overflow_callback(stream, null, null);
692         pa_stream_disconnect(stream);
693 
694         pa_stream_unref(stream);
695 
696         pa_threaded_mainloop_unlock(sipa.main_loop);
697 
698         ospa.stream = null;
699     }
700 }
701 
702 static void timing_update_callback(pa_stream* stream, int success, void* userdata) {
703     SoundIoPrivate* si = cast(SoundIoPrivate*)userdata;
704     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
705     pa_threaded_mainloop_signal(sipa.main_loop, 0);
706 }
707 
708 static int outstream_open_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
709     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
710     SoundIoOutStream* outstream = &os.pub;
711 
712     if (cast()outstream.layout.channel_count > PA_CHANNELS_MAX)
713         return SoundIoError.IncompatibleBackend;
714 
715     if (!outstream.name)
716         outstream.name = "SoundIoOutStream";
717 
718     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
719     SOUNDIO_ATOMIC_STORE(ospa.stream_ready, false);
720     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(ospa.clear_buffer_flag);
721 
722     assert(sipa.pulse_context);
723 
724     pa_threaded_mainloop_lock(sipa.main_loop);
725 
726     pa_sample_spec sample_spec;
727     sample_spec.format = to_pulseaudio_format(outstream.format);
728     sample_spec.rate = outstream.sample_rate;
729 
730     sample_spec.channels = cast(ubyte) outstream.layout.channel_count;
731     pa_channel_map channel_map = to_pulseaudio_channel_map(&outstream.layout);
732 
733     ospa.stream = pa_stream_new(sipa.pulse_context, outstream.name, &sample_spec, &channel_map);
734     if (!ospa.stream) {
735         pa_threaded_mainloop_unlock(sipa.main_loop);
736         outstream_destroy_pa(si, os);
737         return SoundIoError.NoMem;
738     }
739     pa_stream_set_state_callback(ospa.stream, &playback_stream_state_callback, os);
740 
741     ospa.buffer_attr.maxlength = uint.max;
742     ospa.buffer_attr.tlength = uint.max;
743     ospa.buffer_attr.prebuf = 0;
744     ospa.buffer_attr.minreq = uint.max;
745     ospa.buffer_attr.fragsize = uint.max;
746 
747     int bytes_per_second = outstream.bytes_per_frame * outstream.sample_rate;
748     if (outstream.software_latency > 0.0) {
749         int buffer_length = outstream.bytes_per_frame *
750             ceil_dbl_to_int(outstream.software_latency * bytes_per_second / cast(double)outstream.bytes_per_frame);
751 
752         ospa.buffer_attr.maxlength = buffer_length;
753         ospa.buffer_attr.tlength = buffer_length;
754     }
755 
756     pa_stream_flags_t flags = cast(pa_stream_flags_t)(
757         PA_STREAM_START_CORKED | PA_STREAM_AUTO_TIMING_UPDATE |
758         PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY
759     );
760 
761     if (auto err = pa_stream_connect_playback(ospa.stream,outstream.device.id, &ospa.buffer_attr,flags, null, null)) {
762         pa_threaded_mainloop_unlock(sipa.main_loop);
763         return SoundIoError.OpeningDevice;
764     }
765 
766     while (!SOUNDIO_ATOMIC_LOAD(ospa.stream_ready))
767         pa_threaded_mainloop_wait(sipa.main_loop);
768 
769     pa_operation* update_timing_info_op = pa_stream_update_timing_info(ospa.stream, &timing_update_callback, si);
770     if (auto err = perform_operation(si, update_timing_info_op)) {
771         pa_threaded_mainloop_unlock(sipa.main_loop);
772         return err;
773     }
774 
775     size_t writable_size = pa_stream_writable_size(ospa.stream);
776     outstream.software_latency = (cast(double)writable_size) / cast(double)bytes_per_second;
777 
778     pa_threaded_mainloop_unlock(sipa.main_loop);
779 
780     return 0;
781 }
782 
783 static int outstream_start_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
784     SoundIoOutStream* outstream = &os.pub;
785     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
786     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
787 
788     pa_threaded_mainloop_lock(sipa.main_loop);
789 
790     ospa.write_byte_count = pa_stream_writable_size(ospa.stream);
791     int frame_count = cast(int) (ospa.write_byte_count / outstream.bytes_per_frame);
792     outstream.write_callback(outstream, 0, frame_count);
793 
794     pa_operation* op = pa_stream_cork(ospa.stream, false, null, null);
795     if (!op) {
796         pa_threaded_mainloop_unlock(sipa.main_loop);
797         return SoundIoError.Streaming;
798     }
799     pa_operation_unref(op);
800     pa_stream_set_write_callback(ospa.stream, &playback_stream_write_callback, os);
801     pa_stream_set_underflow_callback(ospa.stream, &playback_stream_underflow_callback, outstream);
802     pa_stream_set_overflow_callback(ospa.stream, &playback_stream_underflow_callback, outstream);
803 
804     pa_threaded_mainloop_unlock(sipa.main_loop);
805 
806     return 0;
807 }
808 
809 static int outstream_begin_write_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) {
810     SoundIoOutStream* outstream = &os.pub;
811     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
812     pa_stream* stream = ospa.stream;
813 
814     ospa.write_byte_count = *frame_count * outstream.bytes_per_frame;
815     if (pa_stream_begin_write(stream, cast(void**)&ospa.write_ptr, &ospa.write_byte_count))
816         return SoundIoError.Streaming;
817 
818     for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
819         ospa.areas[ch].ptr = ospa.write_ptr + outstream.bytes_per_sample * ch;
820         ospa.areas[ch].step = outstream.bytes_per_frame;
821     }
822 
823     *frame_count = cast(int) (ospa.write_byte_count / outstream.bytes_per_frame);
824     *out_areas = ospa.areas.ptr;
825 
826     return 0;
827 }
828 
829 static int outstream_end_write_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
830     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
831     pa_stream* stream = ospa.stream;
832 
833     pa_seek_mode_t seek_mode = SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(ospa.clear_buffer_flag) ? PA_SEEK_RELATIVE : PA_SEEK_RELATIVE_ON_READ;
834     if (pa_stream_write(stream, ospa.write_ptr, ospa.write_byte_count, null, 0, seek_mode))
835         return SoundIoError.Streaming;
836 
837     return 0;
838 }
839 
840 static int outstream_clear_buffer_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
841     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
842     SOUNDIO_ATOMIC_FLAG_CLEAR(ospa.clear_buffer_flag);
843     return 0;
844 }
845 
846 static int outstream_pause_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) {
847     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
848     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
849 
850     if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) {
851         pa_threaded_mainloop_lock(sipa.main_loop);
852     }
853 
854     if (pause != pa_stream_is_corked(ospa.stream)) {
855         pa_operation* op = pa_stream_cork(ospa.stream, pause, null, null);
856         if (!op) {
857             pa_threaded_mainloop_unlock(sipa.main_loop);
858             return SoundIoError.Streaming;
859         }
860         pa_operation_unref(op);
861     }
862 
863     if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) {
864         pa_threaded_mainloop_unlock(sipa.main_loop);
865     }
866 
867     return 0;
868 }
869 
870 static int outstream_get_latency_pa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) {
871     SoundIoOutStreamPulseAudio* ospa = &os.backend_data.pulseaudio;
872 
873     pa_usec_t r_usec;
874     int negative;
875     if (auto err = pa_stream_get_latency(ospa.stream, &r_usec, &negative)) {
876         return SoundIoError.Streaming;
877     }
878     *out_latency = r_usec / 1000000.0;
879     return 0;
880 }
881 
882 static void recording_stream_state_callback(pa_stream* stream, void* userdata) {
883     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)userdata;
884     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
885     SoundIoInStream* instream = &is_.pub;
886     SoundIo* soundio = instream.device.soundio;
887     SoundIoPrivate* si = cast(SoundIoPrivate*)soundio;
888     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
889     switch (pa_stream_get_state(stream)) {
890         case PA_STREAM_UNCONNECTED:
891         case PA_STREAM_CREATING:
892         case PA_STREAM_TERMINATED:
893             break;
894         case PA_STREAM_READY:
895             SOUNDIO_ATOMIC_STORE(ispa.stream_ready, true);
896             pa_threaded_mainloop_signal(sipa.main_loop, 0);
897             break;
898         case PA_STREAM_FAILED:
899             instream.error_callback(instream, SoundIoError.Streaming);
900             break;
901         default: break;
902     }
903 }
904 
905 static void recording_stream_read_callback(pa_stream* stream, size_t nbytes, void* userdata) {
906     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*)userdata;
907     SoundIoInStream* instream = &is_.pub;
908     assert(nbytes % instream.bytes_per_frame == 0);
909     assert(nbytes > 0);
910     int available_frame_count = cast(int) (nbytes / instream.bytes_per_frame);
911     instream.read_callback(instream, 0, available_frame_count);
912 }
913 
914 static void instream_destroy_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
915     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
916     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
917     pa_stream* stream = ispa.stream;
918     if (stream) {
919         pa_threaded_mainloop_lock(sipa.main_loop);
920 
921         pa_stream_set_state_callback(stream, null, null);
922         pa_stream_set_read_callback(stream, null, null);
923         pa_stream_disconnect(stream);
924         pa_stream_unref(stream);
925 
926         pa_threaded_mainloop_unlock(sipa.main_loop);
927 
928         ispa.stream = null;
929     }
930 }
931 
932 static int instream_open_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
933     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
934     SoundIoInStream* instream = &is_.pub;
935 
936     if (cast()instream.layout.channel_count > PA_CHANNELS_MAX)
937         return SoundIoError.IncompatibleBackend;
938     if (!instream.name)
939         instream.name = "SoundIoInStream";
940 
941     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
942     SOUNDIO_ATOMIC_STORE(ispa.stream_ready, false);
943 
944     pa_threaded_mainloop_lock(sipa.main_loop);
945 
946     pa_sample_spec sample_spec;
947     sample_spec.format = to_pulseaudio_format(instream.format);
948     sample_spec.rate = instream.sample_rate;
949     sample_spec.channels = cast(ubyte) instream.layout.channel_count;
950 
951     pa_channel_map channel_map = to_pulseaudio_channel_map(&instream.layout);
952 
953     ispa.stream = pa_stream_new(sipa.pulse_context, instream.name, &sample_spec, &channel_map);
954     if (!ispa.stream) {
955         pa_threaded_mainloop_unlock(sipa.main_loop);
956         instream_destroy_pa(si, is_);
957         return SoundIoError.NoMem;
958     }
959 
960     pa_stream* stream = ispa.stream;
961 
962     pa_stream_set_state_callback(stream, &recording_stream_state_callback, is_);
963     pa_stream_set_read_callback(stream, &recording_stream_read_callback, is_);
964 
965     ispa.buffer_attr.maxlength = uint.max;
966     ispa.buffer_attr.tlength = uint.max;
967     ispa.buffer_attr.prebuf = 0;
968     ispa.buffer_attr.minreq = uint.max;
969     ispa.buffer_attr.fragsize = uint.max;
970 
971     if (instream.software_latency > 0.0) {
972         int bytes_per_second = instream.bytes_per_frame * instream.sample_rate;
973         int buffer_length = instream.bytes_per_frame *
974             ceil_dbl_to_int(instream.software_latency * bytes_per_second / cast(double)instream.bytes_per_frame);
975         ispa.buffer_attr.fragsize = buffer_length;
976     }
977 
978     pa_threaded_mainloop_unlock(sipa.main_loop);
979 
980     return 0;
981 }
982 
983 static int instream_start_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
984     SoundIoInStream* instream = &is_.pub;
985     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
986     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
987     pa_threaded_mainloop_lock(sipa.main_loop);
988 
989     pa_stream_flags_t flags = cast(pa_stream_flags_t)(
990         PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY
991     );
992 
993     if (auto err = pa_stream_connect_record(ispa.stream, instream.device.id, &ispa.buffer_attr, flags)) {
994         pa_threaded_mainloop_unlock(sipa.main_loop);
995         return SoundIoError.OpeningDevice;
996     }
997 
998     while (!SOUNDIO_ATOMIC_LOAD(ispa.stream_ready))
999         pa_threaded_mainloop_wait(sipa.main_loop);
1000 
1001     pa_operation* update_timing_info_op = pa_stream_update_timing_info(ispa.stream, &timing_update_callback, si);
1002     if (auto err = perform_operation(si, update_timing_info_op)) {
1003         pa_threaded_mainloop_unlock(sipa.main_loop);
1004         return err;
1005     }
1006 
1007 
1008     pa_threaded_mainloop_unlock(sipa.main_loop);
1009     return 0;
1010 }
1011 
1012 static int instream_begin_read_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) {
1013     SoundIoInStream* instream = &is_.pub;
1014     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
1015     pa_stream* stream = ispa.stream;
1016 
1017     assert(SOUNDIO_ATOMIC_LOAD(ispa.stream_ready));
1018 
1019     if (!ispa.peek_buf) {
1020         if (pa_stream_peek(stream, cast(const(void)**)&ispa.peek_buf, &ispa.peek_buf_size))
1021             return SoundIoError.Streaming;
1022 
1023         ispa.peek_buf_frames_left = cast(int) (ispa.peek_buf_size / instream.bytes_per_frame);
1024         ispa.peek_buf_index = 0;
1025 
1026         // hole
1027         if (!ispa.peek_buf) {
1028             *frame_count = ispa.peek_buf_frames_left;
1029             *out_areas = null;
1030             return 0;
1031         }
1032     }
1033 
1034     ispa.read_frame_count = soundio_int_min(*frame_count, ispa.peek_buf_frames_left);
1035     *frame_count = ispa.read_frame_count;
1036     for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1037         ispa.areas[ch].ptr = ispa.peek_buf + ispa.peek_buf_index + instream.bytes_per_sample * ch;
1038         ispa.areas[ch].step = instream.bytes_per_frame;
1039     }
1040 
1041     *out_areas = ispa.areas.ptr;
1042 
1043     return 0;
1044 }
1045 
1046 static int instream_end_read_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1047     SoundIoInStream* instream = &is_.pub;
1048     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
1049     pa_stream* stream = ispa.stream;
1050 
1051     // hole
1052     if (!ispa.peek_buf) {
1053         if (pa_stream_drop(stream))
1054             return SoundIoError.Streaming;
1055         return 0;
1056     }
1057 
1058     size_t advance_bytes = ispa.read_frame_count * instream.bytes_per_frame;
1059     ispa.peek_buf_index += advance_bytes;
1060     ispa.peek_buf_frames_left -= ispa.read_frame_count;
1061 
1062     if (ispa.peek_buf_index >= ispa.peek_buf_size) {
1063         if (pa_stream_drop(stream))
1064             return SoundIoError.Streaming;
1065         ispa.peek_buf = null;
1066     }
1067 
1068     return 0;
1069 }
1070 
1071 static int instream_pause_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) {
1072     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
1073     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
1074 
1075     if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) {
1076         pa_threaded_mainloop_lock(sipa.main_loop);
1077     }
1078 
1079     if (pause != pa_stream_is_corked(ispa.stream)) {
1080         pa_operation* op = pa_stream_cork(ispa.stream, pause, null, null);
1081         if (!op)
1082             return SoundIoError.Streaming;
1083         pa_operation_unref(op);
1084     }
1085 
1086     if (!pa_threaded_mainloop_in_thread(sipa.main_loop)) {
1087         pa_threaded_mainloop_unlock(sipa.main_loop);
1088     }
1089 
1090     return 0;
1091 }
1092 
1093 static int instream_get_latency_pa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) {
1094     SoundIoInStreamPulseAudio* ispa = &is_.backend_data.pulseaudio;
1095 
1096     pa_usec_t r_usec;
1097     int negative;
1098     if (auto err = pa_stream_get_latency(ispa.stream, &r_usec, &negative)) {
1099         return SoundIoError.Streaming;
1100     }
1101     *out_latency = r_usec / 1000000.0;
1102     return 0;
1103 }
1104 
1105 package int soundio_pulseaudio_init(SoundIoPrivate* si) {
1106     SoundIo* soundio = &si.pub;
1107     SoundIoPulseAudio* sipa = &si.backend_data.pulseaudio;
1108 
1109     sipa.device_scan_queued = true;
1110 
1111     sipa.main_loop = pa_threaded_mainloop_new();
1112     if (!sipa.main_loop) {
1113         destroy_pa(si);
1114         return SoundIoError.NoMem;
1115     }
1116 
1117     pa_mainloop_api* main_loop_api = pa_threaded_mainloop_get_api(sipa.main_loop);
1118 
1119     sipa.props = pa_proplist_new();
1120     if (!sipa.props) {
1121         destroy_pa(si);
1122         return SoundIoError.NoMem;
1123     }
1124 
1125     sipa.pulse_context = pa_context_new_with_proplist(main_loop_api, soundio.app_name, sipa.props);
1126     if (!sipa.pulse_context) {
1127         destroy_pa(si);
1128         return SoundIoError.NoMem;
1129     }
1130 
1131     pa_context_set_subscribe_callback(sipa.pulse_context, &subscribe_callback, si);
1132     pa_context_set_state_callback(sipa.pulse_context, &context_state_callback, si);
1133 
1134     if (auto err = pa_context_connect(sipa.pulse_context, null, cast(pa_context_flags_t)0, null)) {
1135         destroy_pa(si);
1136         return SoundIoError.InitAudioBackend;
1137     }
1138 
1139     if (pa_threaded_mainloop_start(sipa.main_loop)) {
1140         destroy_pa(si);
1141         return SoundIoError.NoMem;
1142     }
1143 
1144     pa_threaded_mainloop_lock(sipa.main_loop);
1145 
1146     // block until ready
1147     while (!sipa.ready_flag)
1148         pa_threaded_mainloop_wait(sipa.main_loop);
1149 
1150     if (sipa.connection_err) {
1151         pa_threaded_mainloop_unlock(sipa.main_loop);
1152         destroy_pa(si);
1153         return sipa.connection_err;
1154     }
1155 
1156     if (auto err = subscribe_to_events(si)) {
1157         pa_threaded_mainloop_unlock(sipa.main_loop);
1158         destroy_pa(si);
1159         return err;
1160     }
1161 
1162     pa_threaded_mainloop_unlock(sipa.main_loop);
1163 
1164     si.destroy = &destroy_pa;
1165     si.flush_events = &flush_events_pa;
1166     si.wait_events = &wait_events_pa;
1167     si.wakeup = &wakeup_pa;
1168     si.force_device_scan = &force_device_scan_pa;
1169 
1170     si.outstream_open = &outstream_open_pa;
1171     si.outstream_destroy = &outstream_destroy_pa;
1172     si.outstream_start = &outstream_start_pa;
1173     si.outstream_begin_write = &outstream_begin_write_pa;
1174     si.outstream_end_write = &outstream_end_write_pa;
1175     si.outstream_clear_buffer = &outstream_clear_buffer_pa;
1176     si.outstream_pause = &outstream_pause_pa;
1177     si.outstream_get_latency = &outstream_get_latency_pa;
1178 
1179     si.instream_open = &instream_open_pa;
1180     si.instream_destroy = &instream_destroy_pa;
1181     si.instream_start = &instream_start_pa;
1182     si.instream_begin_read = &instream_begin_read_pa;
1183     si.instream_end_read = &instream_end_read_pa;
1184     si.instream_pause = &instream_pause_pa;
1185     si.instream_get_latency = &instream_get_latency_pa;
1186 
1187     return 0;
1188 }