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