1 /// Translated from C to D
2 module soundio.alsa;
3 
4 version(SOUNDIO_HAVE_ALSA):
5 @nogc nothrow:
6 extern(C): __gshared:
7 
8 
9 import soundio.api;
10 import soundio.soundio_private;
11 import soundio.os;
12 import soundio.list;
13 import soundio.atomics;
14 import soundio.headers.alsaheader;
15 
16 import core.sys.linux.sys.inotify;
17 import core.sys.posix.fcntl;
18 import core.sys.posix.unistd;
19 import core.sys.posix.poll; //pollfd, POLLERR etc.
20 import core.stdc.errno;
21 import core.stdc.string: strcmp, strncmp, strlen, strstr, strdup;
22 import core.stdc.stdlib: free;
23 
24 private:
25 
26 static if (__VERSION__ < 2094) {
27     // @nogc inotify headers
28     int inotify_init();
29     int inotify_init1(int flags);
30     int inotify_add_watch(int fd, const(char)* name, uint mask);
31     int inotify_rm_watch(int fd, uint wd);
32 }
33 // in unistd.h, missing from core.sys.posix.unistd
34 int pipe2(int* __pipedes, int __flags);
35 
36 package struct SoundIoDeviceAlsa { int make_the_struct_not_empty; }
37 
38 enum SOUNDIO_MAX_ALSA_SND_FILE_LEN = 16;
39 struct SoundIoAlsaPendingFile {
40     char[SOUNDIO_MAX_ALSA_SND_FILE_LEN] name;
41 }
42 
43 alias SoundIoListAlsaPendingFile = SOUNDIO_LIST!SoundIoAlsaPendingFile;
44 
45 package struct SoundIoAlsa {
46     SoundIoOsMutex* mutex;
47     SoundIoOsCond* cond;
48 
49     SoundIoOsThread* thread;
50     SoundIoAtomicFlag abort_flag;
51     int notify_fd;
52     int notify_wd;
53     bool have_devices_flag;
54     int[2] notify_pipe_fd;
55     SoundIoListAlsaPendingFile pending_files;
56 
57     // this one is ready to be read with flush_events. protected by mutex
58     SoundIoDevicesInfo* ready_devices_info;
59 
60     int shutdown_err;
61     bool emitted_shutdown_cb;
62 }
63 
64 package struct SoundIoOutStreamAlsa {
65     snd_pcm_t* handle;
66     snd_pcm_chmap_t* chmap;
67     int chmap_size;
68     snd_pcm_uframes_t offset;
69     snd_pcm_access_t access;
70     snd_pcm_uframes_t buffer_size_frames;
71     int sample_buffer_size;
72     char* sample_buffer;
73     int poll_fd_count;
74     int poll_fd_count_with_extra;
75     pollfd* poll_fds;
76     int[2] poll_exit_pipe_fd;
77     SoundIoOsThread* thread;
78     SoundIoAtomicFlag thread_exit_flag;
79     snd_pcm_uframes_t period_size;
80     int write_frame_count;
81     bool is_paused;
82     SoundIoAtomicFlag clear_buffer_flag;
83     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
84 }
85 
86 package struct SoundIoInStreamAlsa {
87     snd_pcm_t* handle;
88     snd_pcm_chmap_t* chmap;
89     int chmap_size;
90     snd_pcm_uframes_t offset;
91     snd_pcm_access_t access;
92     int sample_buffer_size;
93     char* sample_buffer;
94     int poll_fd_count;
95     pollfd* poll_fds;
96     SoundIoOsThread* thread;
97     SoundIoAtomicFlag thread_exit_flag;
98     int period_size;
99     int read_frame_count;
100     bool is_paused;
101     SoundIoChannelArea[SOUNDIO_MAX_CHANNELS] areas;
102 }
103 
104 immutable snd_pcm_stream_t[2] stream_types = [SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE];
105 
106 static snd_pcm_access_t[5] prioritized_access_types = [
107     SND_PCM_ACCESS_MMAP_INTERLEAVED,
108     SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
109     SND_PCM_ACCESS_MMAP_COMPLEX,
110     SND_PCM_ACCESS_RW_INTERLEAVED,
111     SND_PCM_ACCESS_RW_NONINTERLEAVED,
112 ];
113 
114 static void wakeup_device_poll(SoundIoAlsa* sia) {
115     ssize_t amt = write(sia.notify_pipe_fd[1], "a".ptr, 1);
116     if (amt == -1) {
117         assert(errno != EBADF);
118         assert(errno != EIO);
119         assert(errno != ENOSPC);
120         assert(errno != EPERM);
121         assert(errno != EPIPE);
122     }
123 }
124 
125 static void wakeup_outstream_poll(SoundIoOutStreamAlsa* osa) {
126     ssize_t amt = write(osa.poll_exit_pipe_fd[1], "a".ptr, 1);
127     if (amt == -1) {
128         assert(errno != EBADF);
129         assert(errno != EIO);
130         assert(errno != ENOSPC);
131         assert(errno != EPERM);
132         assert(errno != EPIPE);
133     }
134 }
135 
136 static void destroy_alsa(SoundIoPrivate* si) {
137     SoundIoAlsa* sia = &si.backend_data.alsa;
138 
139     if (sia.thread) {
140         SOUNDIO_ATOMIC_FLAG_CLEAR(sia.abort_flag);
141         wakeup_device_poll(sia);
142         soundio_os_thread_destroy(sia.thread);
143     }
144 
145     sia.pending_files.deinit();
146 
147     if (sia.cond)
148         soundio_os_cond_destroy(sia.cond);
149 
150     if (sia.mutex)
151         soundio_os_mutex_destroy(sia.mutex);
152 
153     soundio_destroy_devices_info(sia.ready_devices_info);
154 
155 
156 
157     close(sia.notify_pipe_fd[0]);
158     close(sia.notify_pipe_fd[1]);
159     close(sia.notify_fd);
160 }
161 
162 pragma(inline, true) static snd_pcm_uframes_t ceil_dbl_to_uframes(double x) {
163     const(double) truncation = cast(snd_pcm_uframes_t)x;
164     return cast(snd_pcm_uframes_t) (truncation + (truncation < x));
165 }
166 
167 static char* str_partition_on_char(char* str, char c) {
168     if (!str)
169         return null;
170     while (*str) {
171         if (*str == c) {
172             *str = 0;
173             return str + 1;
174         }
175         str += 1;
176     }
177     return null;
178 }
179 
180 static snd_pcm_stream_t aim_to_stream(SoundIoDeviceAim aim) {
181     final switch (aim) {
182         case SoundIoDeviceAim.Output: return SND_PCM_STREAM_PLAYBACK;
183         case SoundIoDeviceAim.Input: return SND_PCM_STREAM_CAPTURE;
184     }
185     //assert(0); // Invalid aim
186     //return SND_PCM_STREAM_PLAYBACK;
187 }
188 
189 static SoundIoChannelId from_alsa_chmap_pos(uint pos) {
190     switch (/*cast(snd_pcm_chmap_position)*/ pos) {
191         case SND_CHMAP_UNKNOWN: return SoundIoChannelId.Invalid;
192         case SND_CHMAP_NA:      return SoundIoChannelId.Invalid;
193         case SND_CHMAP_MONO:    return SoundIoChannelId.FrontCenter;
194         case SND_CHMAP_FL:      return SoundIoChannelId.FrontLeft; // front left
195         case SND_CHMAP_FR:      return SoundIoChannelId.FrontRight; // front right
196         case SND_CHMAP_RL:      return SoundIoChannelId.BackLeft; // rear left
197         case SND_CHMAP_RR:      return SoundIoChannelId.BackRight; // rear right
198         case SND_CHMAP_FC:      return SoundIoChannelId.FrontCenter; // front center
199         case SND_CHMAP_LFE:     return SoundIoChannelId.Lfe; // LFE
200         case SND_CHMAP_SL:      return SoundIoChannelId.SideLeft; // side left
201         case SND_CHMAP_SR:      return SoundIoChannelId.SideRight; // side right
202         case SND_CHMAP_RC:      return SoundIoChannelId.BackCenter; // rear center
203         case SND_CHMAP_FLC:     return SoundIoChannelId.FrontLeftCenter; // front left center
204         case SND_CHMAP_FRC:     return SoundIoChannelId.FrontRightCenter; // front right center
205         case SND_CHMAP_RLC:     return SoundIoChannelId.BackLeftCenter; // rear left center
206         case SND_CHMAP_RRC:     return SoundIoChannelId.BackRightCenter; // rear right center
207         case SND_CHMAP_FLW:     return SoundIoChannelId.FrontLeftWide; // front left wide
208         case SND_CHMAP_FRW:     return SoundIoChannelId.FrontRightWide; // front right wide
209         case SND_CHMAP_FLH:     return SoundIoChannelId.FrontLeftHigh; // front left high
210         case SND_CHMAP_FCH:     return SoundIoChannelId.FrontCenterHigh; // front center high
211         case SND_CHMAP_FRH:     return SoundIoChannelId.FrontRightHigh; // front right high
212         case SND_CHMAP_TC:      return SoundIoChannelId.TopCenter; // top center
213         case SND_CHMAP_TFL:     return SoundIoChannelId.TopFrontLeft; // top front left
214         case SND_CHMAP_TFR:     return SoundIoChannelId.TopFrontRight; // top front right
215         case SND_CHMAP_TFC:     return SoundIoChannelId.TopFrontCenter; // top front center
216         case SND_CHMAP_TRL:     return SoundIoChannelId.TopBackLeft; // top rear left
217         case SND_CHMAP_TRR:     return SoundIoChannelId.TopBackRight; // top rear right
218         case SND_CHMAP_TRC:     return SoundIoChannelId.TopBackCenter; // top rear center
219         case SND_CHMAP_TFLC:    return SoundIoChannelId.TopFrontLeftCenter; // top front left center
220         case SND_CHMAP_TFRC:    return SoundIoChannelId.TopFrontRightCenter; // top front right center
221         case SND_CHMAP_TSL:     return SoundIoChannelId.TopSideLeft; // top side left
222         case SND_CHMAP_TSR:     return SoundIoChannelId.TopSideRight; // top side right
223         case SND_CHMAP_LLFE:    return SoundIoChannelId.LeftLfe; // left LFE
224         case SND_CHMAP_RLFE:    return SoundIoChannelId.RightLfe; // right LFE
225         case SND_CHMAP_BC:      return SoundIoChannelId.BottomCenter; // bottom center
226         case SND_CHMAP_BLC:     return SoundIoChannelId.BottomLeftCenter; // bottom left center
227         case SND_CHMAP_BRC:     return SoundIoChannelId.BottomRightCenter; // bottom right center
228         default: break;
229     }
230     return SoundIoChannelId.Invalid;
231 }
232 
233 static int to_alsa_chmap_pos(SoundIoChannelId channel_id) {
234     switch (channel_id) {
235         case SoundIoChannelId.FrontLeft:             return SND_CHMAP_FL;
236         case SoundIoChannelId.FrontRight:            return SND_CHMAP_FR;
237         case SoundIoChannelId.BackLeft:              return SND_CHMAP_RL;
238         case SoundIoChannelId.BackRight:             return SND_CHMAP_RR;
239         case SoundIoChannelId.FrontCenter:           return SND_CHMAP_FC;
240         case SoundIoChannelId.Lfe:                   return SND_CHMAP_LFE;
241         case SoundIoChannelId.SideLeft:              return SND_CHMAP_SL;
242         case SoundIoChannelId.SideRight:             return SND_CHMAP_SR;
243         case SoundIoChannelId.BackCenter:            return SND_CHMAP_RC;
244         case SoundIoChannelId.FrontLeftCenter:       return SND_CHMAP_FLC;
245         case SoundIoChannelId.FrontRightCenter:      return SND_CHMAP_FRC;
246         case SoundIoChannelId.BackLeftCenter:        return SND_CHMAP_RLC;
247         case SoundIoChannelId.BackRightCenter:       return SND_CHMAP_RRC;
248         case SoundIoChannelId.FrontLeftWide:         return SND_CHMAP_FLW;
249         case SoundIoChannelId.FrontRightWide:        return SND_CHMAP_FRW;
250         case SoundIoChannelId.FrontLeftHigh:         return SND_CHMAP_FLH;
251         case SoundIoChannelId.FrontCenterHigh:       return SND_CHMAP_FCH;
252         case SoundIoChannelId.FrontRightHigh:        return SND_CHMAP_FRH;
253         case SoundIoChannelId.TopCenter:             return SND_CHMAP_TC;
254         case SoundIoChannelId.TopFrontLeft:          return SND_CHMAP_TFL;
255         case SoundIoChannelId.TopFrontRight:         return SND_CHMAP_TFR;
256         case SoundIoChannelId.TopFrontCenter:        return SND_CHMAP_TFC;
257         case SoundIoChannelId.TopBackLeft:           return SND_CHMAP_TRL;
258         case SoundIoChannelId.TopBackRight:          return SND_CHMAP_TRR;
259         case SoundIoChannelId.TopBackCenter:         return SND_CHMAP_TRC;
260         case SoundIoChannelId.TopFrontLeftCenter:    return SND_CHMAP_TFLC;
261         case SoundIoChannelId.TopFrontRightCenter:   return SND_CHMAP_TFRC;
262         case SoundIoChannelId.TopSideLeft:           return SND_CHMAP_TSL;
263         case SoundIoChannelId.TopSideRight:          return SND_CHMAP_TSR;
264         case SoundIoChannelId.LeftLfe:               return SND_CHMAP_LLFE;
265         case SoundIoChannelId.RightLfe:              return SND_CHMAP_RLFE;
266         case SoundIoChannelId.BottomCenter:          return SND_CHMAP_BC;
267         case SoundIoChannelId.BottomLeftCenter:      return SND_CHMAP_BLC;
268         case SoundIoChannelId.BottomRightCenter:     return SND_CHMAP_BRC;
269 
270         default:
271             return SND_CHMAP_UNKNOWN;
272     }
273 }
274 
275 static void get_channel_layout(SoundIoChannelLayout* dest, snd_pcm_chmap_t* chmap) {
276     int channel_count = soundio_int_min(SOUNDIO_MAX_CHANNELS, chmap.channels);
277     dest.channel_count = channel_count;
278     for (int i = 0; i < channel_count; i += 1) {
279         // chmap.pos is a variable length array, typed as a `uint[0]`.
280         // The `.ptr` is needed to avoid a range violation with array bounds checks
281         dest.channels[i] = from_alsa_chmap_pos(chmap.pos.ptr[i]);
282     }
283     soundio_channel_layout_detect_builtin(dest);
284 }
285 
286 static int handle_channel_maps(SoundIoDevice* device, snd_pcm_chmap_query_t** maps) {
287     if (!maps)
288         return 0;
289 
290     snd_pcm_chmap_query_t** p;
291     snd_pcm_chmap_query_t* v;
292 
293     // one iteration to count
294     int layout_count = 0;
295     for (p = maps; cast(bool) (v = *p) && layout_count < SOUNDIO_MAX_CHANNELS; p += 1, layout_count += 1) { }
296     device.layouts = ALLOCATE!SoundIoChannelLayout(layout_count);
297     if (!device.layouts) {
298         snd_pcm_free_chmaps(maps);
299         return SoundIoError.NoMem;
300     }
301     device.layout_count = layout_count;
302 
303     // iterate again to collect data
304     int layout_index;
305     for (p = maps, layout_index = 0;
306         cast(bool) (v = *p) && layout_index < layout_count;
307         p += 1, layout_index += 1)
308     {
309         get_channel_layout(&device.layouts[layout_index], &v.map);
310     }
311     snd_pcm_free_chmaps(maps);
312 
313     return 0;
314 }
315 
316 static snd_pcm_format_t to_alsa_fmt(SoundIoFormat fmt) {
317     switch (fmt) {
318     case SoundIoFormat.S8:           return SND_PCM_FORMAT_S8;
319     case SoundIoFormat.U8:           return SND_PCM_FORMAT_U8;
320     case SoundIoFormat.S16LE:        return SND_PCM_FORMAT_S16_LE;
321     case SoundIoFormat.S16BE:        return SND_PCM_FORMAT_S16_BE;
322     case SoundIoFormat.U16LE:        return SND_PCM_FORMAT_U16_LE;
323     case SoundIoFormat.U16BE:        return SND_PCM_FORMAT_U16_BE;
324     case SoundIoFormat.S24LE:        return SND_PCM_FORMAT_S24_LE;
325     case SoundIoFormat.S24BE:        return SND_PCM_FORMAT_S24_BE;
326     case SoundIoFormat.U24LE:        return SND_PCM_FORMAT_U24_LE;
327     case SoundIoFormat.U24BE:        return SND_PCM_FORMAT_U24_BE;
328     case SoundIoFormat.S32LE:        return SND_PCM_FORMAT_S32_LE;
329     case SoundIoFormat.S32BE:        return SND_PCM_FORMAT_S32_BE;
330     case SoundIoFormat.U32LE:        return SND_PCM_FORMAT_U32_LE;
331     case SoundIoFormat.U32BE:        return SND_PCM_FORMAT_U32_BE;
332     case SoundIoFormat.Float32LE:    return SND_PCM_FORMAT_FLOAT_LE;
333     case SoundIoFormat.Float32BE:    return SND_PCM_FORMAT_FLOAT_BE;
334     case SoundIoFormat.Float64LE:    return SND_PCM_FORMAT_FLOAT64_LE;
335     case SoundIoFormat.Float64BE:    return SND_PCM_FORMAT_FLOAT64_BE;
336     case SoundIoFormat.Invalid:
337     default: break;
338     }
339     return SND_PCM_FORMAT_UNKNOWN;
340 }
341 
342 static void test_fmt_mask(SoundIoDevice* device, const(snd_pcm_format_mask_t)* fmt_mask, SoundIoFormat fmt) {
343     if (snd_pcm_format_mask_test(fmt_mask, to_alsa_fmt(fmt))) {
344         device.formats[device.format_count] = fmt;
345         device.format_count += 1;
346     }
347 }
348 
349 static int set_access(snd_pcm_t* handle, snd_pcm_hw_params_t* hwparams, snd_pcm_access_t* out_access) {
350     for (int i = 0; i < prioritized_access_types.length; i += 1) {
351         snd_pcm_access_t access = prioritized_access_types[i];
352         int err = snd_pcm_hw_params_set_access(handle, hwparams, access);
353         if (err >= 0) {
354             if (out_access)
355                 *out_access = access;
356             return 0;
357         }
358     }
359     return SoundIoError.OpeningDevice;
360 }
361 
362 // this function does not override device->formats, so if you want it to, deallocate and set it to NULL
363 static int probe_open_device(SoundIoDevice* device, snd_pcm_t* handle, int resample, int* out_channels_min, int* out_channels_max) {
364     SoundIoDevicePrivate* dev = cast(SoundIoDevicePrivate*)device;
365     int err;
366 
367     snd_pcm_hw_params_t* hwparams;
368     snd_pcm_hw_params_malloc(&hwparams);
369     if (!hwparams)
370         return SoundIoError.NoMem;
371     scope(exit) snd_pcm_hw_params_free(hwparams);
372 
373     err = snd_pcm_hw_params_any(handle, hwparams);
374     if (err < 0)
375         return SoundIoError.OpeningDevice;
376 
377     err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample);
378     if (err < 0)
379         return SoundIoError.OpeningDevice;
380 
381     if (auto err1 = set_access(handle, hwparams, null))
382         return err1;
383 
384     uint channels_min;
385     uint channels_max;
386 
387     err = snd_pcm_hw_params_get_channels_min(hwparams, &channels_min);
388     if (err < 0)
389         return SoundIoError.OpeningDevice;
390     err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channels_max);
391     if (err < 0)
392         return SoundIoError.OpeningDevice;
393 
394     *out_channels_min = channels_min;
395     *out_channels_max = channels_max;
396 
397     uint rate_min;
398     uint rate_max;
399 
400     err = snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, null);
401     if (err < 0)
402         return SoundIoError.OpeningDevice;
403 
404     err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, null);
405     if (err < 0)
406         return SoundIoError.OpeningDevice;
407 
408     device.sample_rate_count = 1;
409     device.sample_rates = &dev.prealloc_sample_rate_range;
410     device.sample_rates[0].min = rate_min;
411     device.sample_rates[0].max = rate_max;
412 
413     double one_over_actual_rate = 1.0 / cast(double)rate_max;
414 
415     // Purposefully leave the parameters with the highest rate, highest channel count.
416 
417     snd_pcm_uframes_t min_frames;
418     snd_pcm_uframes_t max_frames;
419 
420 
421     err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames);
422     if (err < 0)
423         return SoundIoError.OpeningDevice;
424     err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames);
425     if (err < 0)
426         return SoundIoError.OpeningDevice;
427 
428     device.software_latency_min = min_frames * one_over_actual_rate;
429     device.software_latency_max = max_frames * one_over_actual_rate;
430 
431     err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames);
432     if (err < 0)
433         return SoundIoError.OpeningDevice;
434 
435 
436     snd_pcm_format_mask_t* fmt_mask;
437     snd_pcm_format_mask_malloc(&fmt_mask);
438     if (!fmt_mask)
439         return SoundIoError.NoMem;
440     scope(exit) snd_pcm_format_mask_free(fmt_mask);
441     snd_pcm_format_mask_none(fmt_mask);
442     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S8);
443     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U8);
444     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_LE);
445     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_BE);
446     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_LE);
447     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_BE);
448     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_LE);
449     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_BE);
450     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_LE);
451     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_BE);
452     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_LE);
453     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_BE);
454     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_LE);
455     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_BE);
456     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_LE);
457     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_BE);
458     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_LE);
459     snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_BE);
460 
461     err = snd_pcm_hw_params_set_format_mask(handle, hwparams, fmt_mask);
462     if (err < 0)
463         return SoundIoError.OpeningDevice;
464 
465     if (!device.formats) {
466         snd_pcm_hw_params_get_format_mask(hwparams, fmt_mask);
467         device.formats = ALLOCATE!SoundIoFormat(18);
468         if (!device.formats)
469             return SoundIoError.NoMem;
470 
471         device.format_count = 0;
472         test_fmt_mask(device, fmt_mask, SoundIoFormat.S8);
473         test_fmt_mask(device, fmt_mask, SoundIoFormat.U8);
474         test_fmt_mask(device, fmt_mask, SoundIoFormat.S16LE);
475         test_fmt_mask(device, fmt_mask, SoundIoFormat.S16BE);
476         test_fmt_mask(device, fmt_mask, SoundIoFormat.U16LE);
477         test_fmt_mask(device, fmt_mask, SoundIoFormat.U16BE);
478         test_fmt_mask(device, fmt_mask, SoundIoFormat.S24LE);
479         test_fmt_mask(device, fmt_mask, SoundIoFormat.S24BE);
480         test_fmt_mask(device, fmt_mask, SoundIoFormat.U24LE);
481         test_fmt_mask(device, fmt_mask, SoundIoFormat.U24BE);
482         test_fmt_mask(device, fmt_mask, SoundIoFormat.S32LE);
483         test_fmt_mask(device, fmt_mask, SoundIoFormat.S32BE);
484         test_fmt_mask(device, fmt_mask, SoundIoFormat.U32LE);
485         test_fmt_mask(device, fmt_mask, SoundIoFormat.U32BE);
486         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float32LE);
487         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float32BE);
488         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float64LE);
489         test_fmt_mask(device, fmt_mask, SoundIoFormat.Float64BE);
490     }
491 
492     return 0;
493 }
494 
495 extern(D) int probe_device(SoundIoDevice* device, snd_pcm_chmap_query_t** maps) {
496     int err;
497     snd_pcm_t* handle;
498 
499     snd_pcm_stream_t stream = aim_to_stream(device.aim);
500 
501     err = snd_pcm_open(&handle, device.id, stream, 0);
502     if (err < 0) {
503         handle_channel_maps(device, maps);
504         return SoundIoError.OpeningDevice;
505     }
506 
507     int channels_min;
508     int channels_max;
509     err = probe_open_device(device, handle, 0, &channels_min, &channels_max);
510     if (err) {
511         handle_channel_maps(device, maps);
512         snd_pcm_close(handle);
513         return err;
514     }
515 
516     if (!maps) {
517         maps = snd_pcm_query_chmaps(handle);
518         if (!maps) {
519             // device gave us no channel maps. we're forced to conclude that
520             // the min and max channel counts are correct.
521             int layout_count = 0;
522             for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
523                 const(SoundIoChannelLayout)* layout = soundio_channel_layout_get_builtin(i);
524                 if (layout.channel_count >= channels_min && layout.channel_count <= channels_max) {
525                     layout_count += 1;
526                 }
527             }
528             device.layout_count = layout_count;
529             device.layouts = ALLOCATE!SoundIoChannelLayout( device.layout_count);
530             if (!device.layouts) {
531                 snd_pcm_close(handle);
532                 return SoundIoError.NoMem;
533             }
534             int layout_index = 0;
535             for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
536                 const(SoundIoChannelLayout)* layout = soundio_channel_layout_get_builtin(i);
537                 if (layout.channel_count >= channels_min && layout.channel_count <= channels_max) {
538                     device.layouts[layout_index++] = *soundio_channel_layout_get_builtin(i);
539                 }
540             }
541         }
542     }
543 
544     snd_pcm_chmap_t* chmap = snd_pcm_get_chmap(handle);
545     if (chmap) {
546         get_channel_layout(&device.current_layout, chmap);
547         free(chmap);
548     }
549     err = handle_channel_maps(device, maps);
550     if (err) {
551         snd_pcm_close(handle);
552         return err;
553     }
554     maps = null;
555 
556     if (!device.is_raw) {
557         if (device.sample_rates[0].min == device.sample_rates[0].max)
558             device.sample_rate_current = device.sample_rates[0].min;
559 
560         if (device.software_latency_min == device.software_latency_max)
561             device.software_latency_current = device.software_latency_min;
562 
563         // now say that resampling is OK and see what the real min and max is.
564         err = probe_open_device(device, handle, 1, &channels_min, &channels_max);
565         if (err < 0) {
566             snd_pcm_close(handle);
567             return SoundIoError.OpeningDevice;
568         }
569     }
570 
571     snd_pcm_close(handle);
572     return 0;
573 }
574 
575 pragma(inline, true) static bool str_has_prefix(const(char)* big_str, const(char)* prefix) {
576     return strncmp(big_str, prefix, strlen(prefix)) == 0;
577 }
578 
579 extern(D) int refresh_devices(SoundIoPrivate* si) {
580     SoundIo* soundio = &si.pub;
581     SoundIoAlsa* sia = &si.backend_data.alsa;
582 
583     int err;
584     err = snd_config_update_free_global();
585     if (err < 0)
586         return SoundIoError.SystemResources;
587     err = snd_config_update();
588     if (err < 0)
589         return SoundIoError.SystemResources;
590 
591     SoundIoDevicesInfo* devices_info = ALLOCATE!SoundIoDevicesInfo(1);
592     if (!devices_info)
593         return SoundIoError.NoMem;
594     devices_info.default_output_index = -1;
595     devices_info.default_input_index = -1;
596 
597     void** hints;
598     if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
599         soundio_destroy_devices_info(devices_info);
600         return SoundIoError.NoMem;
601     }
602 
603     int default_output_index = -1;
604     int sysdefault_output_index = -1;
605     int default_input_index = -1;
606     int sysdefault_input_index = -1;
607 
608     for (void** hint_ptr = hints; *hint_ptr; hint_ptr += 1) {
609         char* name = snd_device_name_get_hint(*hint_ptr, "NAME");
610         // null - libsoundio has its own dummy backend. API clients should use
611         // that instead of alsa null device.
612         if (strcmp(name, "null") == 0 ||
613             // all these surround devices are clutter
614             str_has_prefix(name, "front:") ||
615             str_has_prefix(name, "surround21:") ||
616             str_has_prefix(name, "surround40:") ||
617             str_has_prefix(name, "surround41:") ||
618             str_has_prefix(name, "surround50:") ||
619             str_has_prefix(name, "surround51:") ||
620             str_has_prefix(name, "surround71:"))
621         {
622             free(name);
623             continue;
624         }
625 
626         // One or both of descr and descr1 can be NULL.
627         char* descr = snd_device_name_get_hint(*hint_ptr, "DESC");
628         char* descr1 = str_partition_on_char(descr, '\n');
629 
630         char* io = snd_device_name_get_hint(*hint_ptr, "IOID");
631         bool is_playback;
632         bool is_capture;
633 
634         // Workaround for Raspberry Pi driver bug, reporting itself as output
635         // when really it is input.
636         if (descr && strcmp(descr, "bcm2835 ALSA, bcm2835 ALSA") == 0 &&
637             descr1 && strcmp(descr1, "Direct sample snooping device") == 0)
638         {
639             is_playback = false;
640             is_capture = true;
641         } else if (descr && strcmp(descr, "bcm2835 ALSA, bcm2835 IEC958/HDMI") == 0 &&
642                    descr1 && strcmp(descr1, "Direct sample snooping device") == 0)
643         {
644             is_playback = false;
645             is_capture = true;
646         } else if (io) {
647             if (strcmp(io, "Input") == 0) {
648                 is_playback = false;
649                 is_capture = true;
650             } else {
651                 assert(strcmp(io, "Output") == 0);
652                 is_playback = true;
653                 is_capture = false;
654             }
655             free(io);
656         } else {
657             is_playback = true;
658             is_capture = true;
659         }
660 
661         for (int stream_type_i = 0; stream_type_i < stream_types.length; stream_type_i += 1) {
662             snd_pcm_stream_t stream = stream_types[stream_type_i];
663             if (stream == SND_PCM_STREAM_PLAYBACK && !is_playback) continue;
664             if (stream == SND_PCM_STREAM_CAPTURE && !is_capture) continue;
665             if (stream == SND_PCM_STREAM_CAPTURE && descr1 &&
666                 (strstr(descr1, "Output") || strstr(descr1, "output")))
667             {
668                 continue;
669             }
670 
671 
672             SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
673             if (!dev) {
674                 free(name);
675                 free(descr);
676                 soundio_destroy_devices_info(devices_info);
677                 snd_device_name_free_hint(hints);
678                 return SoundIoError.NoMem;
679             }
680             SoundIoDevice* device = &dev.pub;
681             device.ref_count = 1;
682             device.soundio = soundio;
683             device.is_raw = false;
684             device.id = strdup(name);
685             if (descr1) {
686                 device.name = soundio_alloc_sprintf(null, "%s: %s", descr, descr1);
687             } else if (descr) {
688                 device.name = strdup(descr);
689             } else {
690                 device.name = strdup(name);
691             }
692 
693             if (!device.id || !device.name) {
694                 soundio_device_unref(device);
695                 free(name);
696                 free(descr);
697                 soundio_destroy_devices_info(devices_info);
698                 snd_device_name_free_hint(hints);
699                 return SoundIoError.NoMem;
700             }
701 
702             SoundIoListDevicePtr* device_list;
703             bool is_default = str_has_prefix(name, "default:") || strcmp(name, "default") == 0;
704             bool is_sysdefault = str_has_prefix(name, "sysdefault:") || strcmp(name, "sysdefault") == 0;
705 
706             if (stream == SND_PCM_STREAM_PLAYBACK) {
707                 device.aim = SoundIoDeviceAim.Output;
708                 device_list = &devices_info.output_devices;
709                 if (is_default)
710                     default_output_index = device_list.length;
711                 if (is_sysdefault)
712                     sysdefault_output_index = device_list.length;
713                 if (devices_info.default_output_index == -1)
714                     devices_info.default_output_index = device_list.length;
715             } else {
716                 assert(stream == SND_PCM_STREAM_CAPTURE);
717                 device.aim = SoundIoDeviceAim.Input;
718                 device_list = &devices_info.input_devices;
719                 if (is_default)
720                     default_input_index = device_list.length;
721                 if (is_sysdefault)
722                     sysdefault_input_index = device_list.length;
723                 if (devices_info.default_input_index == -1)
724                     devices_info.default_input_index = device_list.length;
725             }
726 
727             device.probe_error = probe_device(device, null);
728 
729             if (device_list.append(device)) {
730                 soundio_device_unref(device);
731                 free(name);
732                 free(descr);
733                 soundio_destroy_devices_info(devices_info);
734                 snd_device_name_free_hint(hints);
735                 return SoundIoError.NoMem;
736             }
737         }
738 
739         free(name);
740         free(descr);
741     }
742 
743     if (default_input_index >= 0) {
744         devices_info.default_input_index = default_input_index;
745     } else if (sysdefault_input_index >= 0) {
746         devices_info.default_input_index = sysdefault_input_index;
747     }
748 
749     if (default_output_index >= 0) {
750         devices_info.default_output_index = default_output_index;
751     } else if (sysdefault_output_index >= 0) {
752         devices_info.default_output_index = sysdefault_output_index;
753     }
754 
755     snd_device_name_free_hint(hints);
756 
757     int card_index = -1;
758 
759     if (snd_card_next(&card_index) < 0)
760         return SoundIoError.SystemResources;
761 
762     snd_ctl_card_info_t* card_info;
763     snd_ctl_card_info_malloc(&card_info);
764     if (!card_info)
765         return SoundIoError.NoMem;
766     scope(exit) snd_ctl_card_info_free(card_info);
767 
768     snd_pcm_info_t* pcm_info;
769     snd_pcm_info_malloc(&pcm_info);
770     if (!pcm_info)
771         return SoundIoError.NoMem;
772     scope(exit) snd_pcm_info_free(pcm_info);
773 
774     while (card_index >= 0) {
775         snd_ctl_t* handle;
776         char[32] name;
777         import core.stdc.stdio: sprintf;
778         sprintf(name.ptr, "hw:%d", card_index);
779         err = snd_ctl_open(&handle, name.ptr, 0);
780         if (err < 0) {
781             if (err == -ENOENT) {
782                 break;
783             } else {
784                 soundio_destroy_devices_info(devices_info);
785                 return SoundIoError.OpeningDevice;
786             }
787         }
788 
789         err = snd_ctl_card_info(handle, card_info);
790         if (err < 0) {
791             snd_ctl_close(handle);
792             soundio_destroy_devices_info(devices_info);
793             return SoundIoError.SystemResources;
794         }
795         const(char)* card_name = snd_ctl_card_info_get_name(card_info);
796 
797         int device_index = -1;
798         for (;;) {
799             if (snd_ctl_pcm_next_device(handle, &device_index) < 0) {
800                 snd_ctl_close(handle);
801                 soundio_destroy_devices_info(devices_info);
802                 return SoundIoError.SystemResources;
803             }
804             if (device_index < 0)
805                 break;
806 
807             snd_pcm_info_set_device(pcm_info, device_index);
808             snd_pcm_info_set_subdevice(pcm_info, 0);
809 
810             for (int stream_type_i = 0; stream_type_i < stream_types.length; stream_type_i += 1) {
811                 snd_pcm_stream_t stream = stream_types[stream_type_i];
812                 snd_pcm_info_set_stream(pcm_info, stream);
813 
814                 err = snd_ctl_pcm_info(handle, pcm_info);
815                 if (err < 0) {
816                     if (err == -ENOENT) {
817                         continue;
818                     } else {
819                         snd_ctl_close(handle);
820                         soundio_destroy_devices_info(devices_info);
821                         return SoundIoError.SystemResources;
822                     }
823                 }
824 
825                 const(char)* device_name = snd_pcm_info_get_name(pcm_info);
826 
827                 SoundIoDevicePrivate* dev = ALLOCATE!SoundIoDevicePrivate(1);
828                 if (!dev) {
829                     snd_ctl_close(handle);
830                     soundio_destroy_devices_info(devices_info);
831                     return SoundIoError.NoMem;
832                 }
833                 SoundIoDevice* device = &dev.pub;
834                 device.ref_count = 1;
835                 device.soundio = soundio;
836                 device.id = soundio_alloc_sprintf(null, "hw:%d,%d", card_index, device_index);
837                 device.name = soundio_alloc_sprintf(null, "%s %s", card_name, device_name);
838                 device.is_raw = true;
839 
840                 if (!device.id || !device.name) {
841                     soundio_device_unref(device);
842                     snd_ctl_close(handle);
843                     soundio_destroy_devices_info(devices_info);
844                     return SoundIoError.NoMem;
845                 }
846 
847                 SoundIoListDevicePtr* device_list;
848                 if (stream == SND_PCM_STREAM_PLAYBACK) {
849                     device.aim = SoundIoDeviceAim.Output;
850                     device_list = &devices_info.output_devices;
851                 } else {
852                     assert(stream == SND_PCM_STREAM_CAPTURE);
853                     device.aim = SoundIoDeviceAim.Input;
854                     device_list = &devices_info.input_devices;
855                 }
856 
857                 snd_pcm_chmap_query_t** maps = snd_pcm_query_chmaps_from_hw(card_index, device_index, -1, stream);
858                 device.probe_error = probe_device(device, maps);
859 
860                 if (device_list.append(device)) {
861                     soundio_device_unref(device);
862                     soundio_destroy_devices_info(devices_info);
863                     return SoundIoError.NoMem;
864                 }
865             }
866         }
867         snd_ctl_close(handle);
868         if (snd_card_next(&card_index) < 0) {
869             soundio_destroy_devices_info(devices_info);
870             return SoundIoError.SystemResources;
871         }
872     }
873 
874     soundio_os_mutex_lock(sia.mutex);
875     soundio_destroy_devices_info(sia.ready_devices_info);
876     sia.ready_devices_info = devices_info;
877     sia.have_devices_flag = true;
878     soundio_os_cond_signal(sia.cond, sia.mutex);
879     soundio.on_events_signal(soundio);
880     soundio_os_mutex_unlock(sia.mutex);
881     return 0;
882 }
883 
884 static void shutdown_backend(SoundIoPrivate* si, int err) {
885     SoundIo* soundio = &si.pub;
886     SoundIoAlsa* sia = &si.backend_data.alsa;
887     soundio_os_mutex_lock(sia.mutex);
888     sia.shutdown_err = err;
889     soundio_os_cond_signal(sia.cond, sia.mutex);
890     soundio.on_events_signal(soundio);
891     soundio_os_mutex_unlock(sia.mutex);
892 }
893 
894 static bool copy_str(char* dest, const(char)* src, int buf_len) {
895     for (;;) {
896         buf_len -= 1;
897         if (buf_len <= 0)
898             return false;
899         *dest = *src;
900         dest += 1;
901         src += 1;
902         if (!*src)
903             break;
904     }
905     *dest = '\0';
906     return true;
907 }
908 
909 static void device_thread_run(void* arg) {
910     SoundIoPrivate* si = cast(SoundIoPrivate*)arg;
911     SoundIoAlsa* sia = &si.backend_data.alsa;
912 
913     // Some systems cannot read integer variables if they are not
914     // properly aligned. On other systems, incorrect alignment may
915     // decrease performance. Hence, the buffer used for reading from
916     // the inotify file descriptor should have the same alignment as
917     // struct inotify_event.
918     char[4096] buf; const(inotify_event)* event;
919 
920     pollfd[2] fds;
921     fds[0].fd = sia.notify_fd;
922     fds[0].events = POLLIN;
923 
924     fds[1].fd = sia.notify_pipe_fd[0];
925     fds[1].events = POLLIN;
926 
927     int err;
928     for (;;) {
929         int poll_num = poll(fds.ptr, 2, -1);
930         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(sia.abort_flag))
931             break;
932         if (poll_num == -1) {
933             if (errno == EINTR)
934                 continue;
935             assert(errno != EFAULT);
936             assert(errno != EINVAL);
937             assert(errno == ENOMEM);
938             // Kernel ran out of polling memory.
939             shutdown_backend(si, SoundIoError.SystemResources);
940             return;
941         }
942         if (poll_num <= 0)
943             continue;
944         bool got_rescan_event = false;
945         if (fds[0].revents & POLLIN) {
946             for (;;) {
947                 ssize_t len = read(sia.notify_fd, buf.ptr, buf.sizeof);
948                 if (len == -1) {
949                     assert(errno != EBADF);
950                     assert(errno != EFAULT);
951                     assert(errno != EINVAL);
952                     assert(errno != EIO);
953                     assert(errno != EISDIR);
954                     if (errno == EBADF || errno == EFAULT || errno == EINVAL ||
955                         errno == EIO || errno == EISDIR)
956                     {
957                         shutdown_backend(si, SoundIoError.SystemResources);
958                         return;
959                     }
960                 }
961 
962                 // catches EINTR and EAGAIN
963                 if (len <= 0)
964                     break;
965 
966                 // loop over all events in the buffer
967                 for (char* ptr = buf.ptr; ptr < buf.ptr + len; ptr += inotify_event.sizeof + event.len) {
968                     event = cast(const(inotify_event)*) ptr;
969 
970                     if (!((event.mask & IN_CLOSE_WRITE) || (event.mask & IN_DELETE) || (event.mask & IN_CREATE)))
971                         continue;
972                     if (event.mask & IN_ISDIR)
973                         continue;
974                     if (!event.len || event.len < 8)
975                         continue;
976                     if (strncmp(event.name.ptr, "controlC", 8) != 0) {
977                         continue;
978                     }
979                     if (event.mask & IN_CREATE) {
980                         err = sia.pending_files.add_one();
981                         if (err) {
982                             shutdown_backend(si, SoundIoError.NoMem);
983                             return;
984                         }
985                         SoundIoAlsaPendingFile* pending_file = sia.pending_files.last_ptr();
986                         if (!copy_str(pending_file.name.ptr, event.name.ptr, SOUNDIO_MAX_ALSA_SND_FILE_LEN)) {
987                             sia.pending_files.pop();
988                         }
989                         continue;
990                     }
991                     if (sia.pending_files.length > 0) {
992                         // At this point ignore IN_DELETE in favor of waiting until the files
993                         // opened with IN_CREATE have their IN_CLOSE_WRITE event.
994                         if (!(event.mask & IN_CLOSE_WRITE))
995                             continue;
996                         for (int i = 0; i < sia.pending_files.length; i += 1) {
997                             SoundIoAlsaPendingFile* pending_file = sia.pending_files.ptr_at(i);
998                             if (strcmp(pending_file.name.ptr, event.name.ptr) == 0) {
999                                 sia.pending_files.swap_remove(i);
1000                                 if (sia.pending_files.length == 0) {
1001                                     got_rescan_event = true;
1002                                 }
1003                                 break;
1004                             }
1005                         }
1006                     } else if (event.mask & IN_DELETE) {
1007                         // We are not waiting on created files to be closed, so when
1008                         // a delete happens we act on it.
1009                         got_rescan_event = true;
1010                     }
1011                 }
1012             }
1013         }
1014         if (fds[1].revents & POLLIN) {
1015             got_rescan_event = true;
1016             for (;;) {
1017                 ssize_t len = read(sia.notify_pipe_fd[0], buf.ptr, buf.sizeof);
1018                 if (len == -1) {
1019                     assert(errno != EBADF);
1020                     assert(errno != EFAULT);
1021                     assert(errno != EINVAL);
1022                     assert(errno != EIO);
1023                     assert(errno != EISDIR);
1024                     if (errno == EBADF || errno == EFAULT || errno == EINVAL ||
1025                         errno == EIO || errno == EISDIR)
1026                     {
1027                         shutdown_backend(si, SoundIoError.SystemResources);
1028                         return;
1029                     }
1030                 }
1031                 if (len <= 0)
1032                     break;
1033             }
1034         }
1035         if (got_rescan_event) {
1036             err = refresh_devices(si);
1037             if (err) {
1038                 shutdown_backend(si, err);
1039                 return;
1040             }
1041         }
1042     }
1043 }
1044 
1045 extern(D) void my_flush_events(SoundIoPrivate* si, bool wait) {
1046     SoundIo* soundio = &si.pub;
1047     SoundIoAlsa* sia = &si.backend_data.alsa;
1048 
1049     bool change = false;
1050     bool cb_shutdown = false;
1051     SoundIoDevicesInfo* old_devices_info = null;
1052 
1053     soundio_os_mutex_lock(sia.mutex);
1054 
1055     // block until have devices
1056     while (wait || (!sia.have_devices_flag && !sia.shutdown_err)) {
1057         soundio_os_cond_wait(sia.cond, sia.mutex);
1058         wait = false;
1059     }
1060 
1061     if (sia.shutdown_err && !sia.emitted_shutdown_cb) {
1062         sia.emitted_shutdown_cb = true;
1063         cb_shutdown = true;
1064     } else if (sia.ready_devices_info) {
1065         old_devices_info = si.safe_devices_info;
1066         si.safe_devices_info = sia.ready_devices_info;
1067         sia.ready_devices_info = null;
1068         change = true;
1069     }
1070 
1071     soundio_os_mutex_unlock(sia.mutex);
1072 
1073     if (cb_shutdown)
1074         soundio.on_backend_disconnect(soundio, sia.shutdown_err);
1075     else if (change)
1076         soundio.on_devices_change(soundio);
1077 
1078     soundio_destroy_devices_info(old_devices_info);
1079 }
1080 
1081 void flush_events_alsa(SoundIoPrivate* si) {
1082     my_flush_events(si, false);
1083 }
1084 
1085 void wait_events_alsa(SoundIoPrivate* si) {
1086     my_flush_events(si, false);
1087     my_flush_events(si, true);
1088 }
1089 
1090 void wakeup_alsa(SoundIoPrivate* si) {
1091     SoundIoAlsa* sia = &si.backend_data.alsa;
1092     soundio_os_mutex_lock(sia.mutex);
1093     soundio_os_cond_signal(sia.cond, sia.mutex);
1094     soundio_os_mutex_unlock(sia.mutex);
1095 }
1096 
1097 void force_device_scan_alsa(SoundIoPrivate* si) {
1098     SoundIoAlsa* sia = &si.backend_data.alsa;
1099     wakeup_device_poll(sia);
1100 }
1101 
1102 void outstream_destroy_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1103     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1104 
1105     if (osa.thread) {
1106         SOUNDIO_ATOMIC_FLAG_CLEAR(osa.thread_exit_flag);
1107         wakeup_outstream_poll(osa);
1108         soundio_os_thread_destroy(osa.thread);
1109         osa.thread = null;
1110     }
1111 
1112     if (osa.handle) {
1113         snd_pcm_close(osa.handle);
1114         osa.handle = null;
1115     }
1116 
1117     free(osa.poll_fds);
1118     osa.poll_fds = null;
1119 
1120     free(osa.chmap);
1121     osa.chmap = null;
1122 
1123     free(osa.sample_buffer);
1124     osa.sample_buffer = null;
1125 }
1126 
1127 int outstream_xrun_recovery(SoundIoOutStreamPrivate* os, int err) {
1128     SoundIoOutStream* outstream = &os.pub;
1129     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1130     if (err == -EPIPE) {
1131         err = snd_pcm_prepare(osa.handle);
1132         if (err >= 0)
1133             outstream.underflow_callback(outstream);
1134     } else if (err == -ESTRPIPE) {
1135         while ((err = snd_pcm_resume(osa.handle)) == -EAGAIN) {
1136             // wait until suspend flag is released
1137             poll(null, 0, 1);
1138         }
1139         if (err < 0)
1140             err = snd_pcm_prepare(osa.handle);
1141         if (err >= 0)
1142             outstream.underflow_callback(outstream);
1143     }
1144     return err;
1145 }
1146 
1147 int instream_xrun_recovery(SoundIoInStreamPrivate* is_, int err) {
1148     SoundIoInStream* instream = &is_.pub;
1149     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1150     if (err == -EPIPE) {
1151         err = snd_pcm_prepare(isa.handle);
1152         if (err >= 0)
1153             instream.overflow_callback(instream);
1154     } else if (err == -ESTRPIPE) {
1155         while ((err = snd_pcm_resume(isa.handle)) == -EAGAIN) {
1156             // wait until suspend flag is released
1157             poll(null, 0, 1);
1158         }
1159         if (err < 0)
1160             err = snd_pcm_prepare(isa.handle);
1161         if (err >= 0)
1162             instream.overflow_callback(instream);
1163     }
1164     return err;
1165 }
1166 
1167 int outstream_wait_for_poll(SoundIoOutStreamPrivate* os) {
1168     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1169     int err;
1170     ushort revents;
1171     for (;;) {
1172         err = poll(osa.poll_fds, osa.poll_fd_count_with_extra, -1);
1173         if (err < 0) {
1174             return SoundIoError.Streaming;
1175         }
1176         if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1177             return SoundIoError.Interrupted;
1178         if ((err = snd_pcm_poll_descriptors_revents(osa.handle,
1179                         osa.poll_fds, osa.poll_fd_count, &revents)) < 0)
1180         {
1181             return SoundIoError.Streaming;
1182         }
1183         if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
1184             return 0;
1185         }
1186         if (revents & POLLOUT)
1187             return 0;
1188     }
1189 }
1190 
1191 int instream_wait_for_poll(SoundIoInStreamPrivate* is_) {
1192     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1193     int err;
1194     ushort revents;
1195     for (;;) {
1196         err = poll(isa.poll_fds, isa.poll_fd_count, -1);
1197         if (err < 0) {
1198             return err;
1199         }
1200         if ((err = snd_pcm_poll_descriptors_revents(isa.handle,
1201                         isa.poll_fds, isa.poll_fd_count, &revents)) < 0)
1202         {
1203             return err;
1204         }
1205         if (revents & (POLLERR|POLLNVAL|POLLHUP)) {
1206             return 0;
1207         }
1208         if (revents & POLLIN)
1209             return 0;
1210     }
1211 }
1212 
1213 void outstream_thread_run(void* arg) {
1214     SoundIoOutStreamPrivate* os = cast(SoundIoOutStreamPrivate*) arg;
1215     SoundIoOutStream* outstream = &os.pub;
1216     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1217 
1218     int err;
1219 
1220     for (;;) {
1221         snd_pcm_state_t state = snd_pcm_state(osa.handle);
1222         switch (state) {
1223             case SND_PCM_STATE_SETUP:
1224             {
1225                 err = snd_pcm_prepare(osa.handle);
1226                 if (err < 0) {
1227                     outstream.error_callback(outstream, SoundIoError.Streaming);
1228                     return;
1229                 }
1230                 continue;
1231             }
1232             case SND_PCM_STATE_PREPARED:
1233             {
1234                 snd_pcm_sframes_t avail = snd_pcm_avail(osa.handle);
1235                 if (avail < 0) {
1236                     outstream.error_callback(outstream, SoundIoError.Streaming);
1237                     return;
1238                 }
1239 
1240                 if (cast(snd_pcm_uframes_t)avail == osa.buffer_size_frames) {
1241                     outstream.write_callback(outstream, 0, cast(int) avail);
1242                     if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1243                         return;
1244                     continue;
1245                 }
1246 
1247                 err = snd_pcm_start(osa.handle);
1248                 if (err < 0) {
1249                     outstream.error_callback(outstream, SoundIoError.Streaming);
1250                     return;
1251                 }
1252                 continue;
1253             }
1254             case SND_PCM_STATE_RUNNING:
1255             case SND_PCM_STATE_PAUSED:
1256             {
1257                 err = outstream_wait_for_poll(os);
1258                 if (err) {
1259                     if (err == SoundIoError.Interrupted)
1260                         return;
1261                     outstream.error_callback(outstream, err);
1262                     return;
1263                 }
1264                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag))
1265                     return;
1266                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.clear_buffer_flag)) {
1267                     err = snd_pcm_drop(osa.handle);
1268                     if (err < 0) {
1269                         outstream.error_callback(outstream, SoundIoError.Streaming);
1270                         return;
1271                     }
1272                     err = snd_pcm_reset(osa.handle);
1273                     if (err < 0) {
1274                         if (err == -EBADFD) {
1275                             // If this happens the snd_pcm_drop will have done
1276                             // the function of the reset so it's ok that this
1277                             // did not work.
1278                         } else {
1279                             outstream.error_callback(outstream, SoundIoError.Streaming);
1280                             return;
1281                         }
1282                     }
1283                     continue;
1284                 }
1285 
1286                 snd_pcm_sframes_t avail = snd_pcm_avail_update(osa.handle);
1287                 if (avail < 0) {
1288                     err = outstream_xrun_recovery(os, cast(int) avail);
1289                     if (err < 0) {
1290                         outstream.error_callback(outstream, SoundIoError.Streaming);
1291                         return;
1292                     }
1293                     continue;
1294                 }
1295 
1296                 if (avail > 0)
1297                     outstream.write_callback(outstream, 0, cast(int) avail);
1298                 continue;
1299             }
1300             case SND_PCM_STATE_XRUN:
1301                 err = outstream_xrun_recovery(os, -EPIPE);
1302                 if (err < 0) {
1303                     outstream.error_callback(outstream, SoundIoError.Streaming);
1304                     return;
1305                 }
1306                 continue;
1307             case SND_PCM_STATE_SUSPENDED:
1308                 err = outstream_xrun_recovery(os, -ESTRPIPE);
1309                 if (err < 0) {
1310                     outstream.error_callback(outstream, SoundIoError.Streaming);
1311                     return;
1312                 }
1313                 continue;
1314             case SND_PCM_STATE_OPEN:
1315             case SND_PCM_STATE_DRAINING:
1316             case SND_PCM_STATE_DISCONNECTED:
1317                 outstream.error_callback(outstream, SoundIoError.Streaming);
1318                 return;
1319             default:
1320                 continue;
1321         }
1322     }
1323 }
1324 
1325 static void instream_thread_run(void* arg) {
1326     SoundIoInStreamPrivate* is_ = cast(SoundIoInStreamPrivate*) arg;
1327     SoundIoInStream* instream = &is_.pub;
1328     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1329 
1330     int err;
1331 
1332     for (;;) {
1333         snd_pcm_state_t state = snd_pcm_state(isa.handle);
1334         switch (state) {
1335             case SND_PCM_STATE_SETUP:
1336                 err = snd_pcm_prepare(isa.handle);
1337                 if (err < 0) {
1338                     instream.error_callback(instream, SoundIoError.Streaming);
1339                     return;
1340                 }
1341                 continue;
1342             case SND_PCM_STATE_PREPARED:
1343                 err = snd_pcm_start(isa.handle);
1344                 if (err < 0) {
1345                     instream.error_callback(instream, SoundIoError.Streaming);
1346                     return;
1347                 }
1348                 continue;
1349             case SND_PCM_STATE_RUNNING:
1350             case SND_PCM_STATE_PAUSED:
1351             {
1352                 err = instream_wait_for_poll(is_);
1353                 if (err < 0) {
1354                     if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag))
1355                         return;
1356                     instream.error_callback(instream, SoundIoError.Streaming);
1357                     return;
1358                 }
1359                 if (!SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag))
1360                     return;
1361 
1362                 snd_pcm_sframes_t avail = snd_pcm_avail_update(isa.handle);
1363 
1364                 if (avail < 0) {
1365                     err = instream_xrun_recovery(is_, cast(int) avail);
1366                     if (err < 0) {
1367                         instream.error_callback(instream, SoundIoError.Streaming);
1368                         return;
1369                     }
1370                     continue;
1371                 }
1372 
1373                 if (avail > 0)
1374                     instream.read_callback(instream, 0, cast(int) avail);
1375                 continue;
1376             }
1377             case SND_PCM_STATE_XRUN:
1378                 err = instream_xrun_recovery(is_, -EPIPE);
1379                 if (err < 0) {
1380                     instream.error_callback(instream, SoundIoError.Streaming);
1381                     return;
1382                 }
1383                 continue;
1384             case SND_PCM_STATE_SUSPENDED:
1385                 err = instream_xrun_recovery(is_, -ESTRPIPE);
1386                 if (err < 0) {
1387                     instream.error_callback(instream, SoundIoError.Streaming);
1388                     return;
1389                 }
1390                 continue;
1391             case SND_PCM_STATE_OPEN:
1392             case SND_PCM_STATE_DRAINING:
1393             case SND_PCM_STATE_DISCONNECTED:
1394                 instream.error_callback(instream, SoundIoError.Streaming);
1395                 return;
1396             default:
1397                 continue;
1398         }
1399     }
1400 }
1401 
1402 static int outstream_open_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1403     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1404     SoundIoOutStream* outstream = &os.pub;
1405     SoundIoDevice* device = outstream.device;
1406 
1407     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.clear_buffer_flag);
1408 
1409     if (outstream.software_latency == 0.0)
1410         outstream.software_latency = 1.0;
1411     outstream.software_latency = soundio_double_clamp(device.software_latency_min, outstream.software_latency, device.software_latency_max);
1412 
1413     int ch_count = outstream.layout.channel_count;
1414 
1415     osa.chmap_size = cast(int) (int.sizeof + int.sizeof * ch_count);
1416     osa.chmap = cast(snd_pcm_chmap_t*)ALLOCATE!char(osa.chmap_size);
1417     if (!osa.chmap) {
1418         outstream_destroy_alsa(si, os);
1419         return SoundIoError.NoMem;
1420     }
1421 
1422     int err;
1423 
1424     snd_pcm_hw_params_t* hwparams;
1425     snd_pcm_hw_params_malloc(&hwparams);
1426     if (!hwparams)
1427         return SoundIoError.NoMem;
1428     scope(exit) snd_pcm_hw_params_free(hwparams);
1429 
1430     snd_pcm_stream_t stream = aim_to_stream(outstream.device.aim);
1431 
1432     err = snd_pcm_open(&osa.handle, outstream.device.id, stream, 0);
1433     if (err < 0) {
1434         outstream_destroy_alsa(si, os);
1435         return SoundIoError.OpeningDevice;
1436     }
1437 
1438     err = snd_pcm_hw_params_any(osa.handle, hwparams);
1439     if (err < 0) {
1440         outstream_destroy_alsa(si, os);
1441         return SoundIoError.OpeningDevice;
1442     }
1443 
1444     int want_resample = !outstream.device.is_raw;
1445     err = snd_pcm_hw_params_set_rate_resample(osa.handle, hwparams, want_resample);
1446     if (err < 0) {
1447         outstream_destroy_alsa(si, os);
1448         return SoundIoError.OpeningDevice;
1449     }
1450 
1451     err = set_access(osa.handle, hwparams, &osa.access);
1452     if (err) {
1453         outstream_destroy_alsa(si, os);
1454         return err;
1455     }
1456 
1457     err = snd_pcm_hw_params_set_channels(osa.handle, hwparams, ch_count);
1458     if (err < 0) {
1459         outstream_destroy_alsa(si, os);
1460         return SoundIoError.OpeningDevice;
1461     }
1462 
1463     err = snd_pcm_hw_params_set_rate(osa.handle, hwparams, outstream.sample_rate, 0);
1464     if (err < 0) {
1465         outstream_destroy_alsa(si, os);
1466         return SoundIoError.OpeningDevice;
1467     }
1468 
1469     snd_pcm_format_t format = to_alsa_fmt(outstream.format);
1470     int phys_bits_per_sample = snd_pcm_format_physical_width(format);
1471     if (phys_bits_per_sample % 8 != 0) {
1472         outstream_destroy_alsa(si, os);
1473         return SoundIoError.IncompatibleDevice;
1474     }
1475     int phys_bytes_per_sample = phys_bits_per_sample / 8;
1476     err = snd_pcm_hw_params_set_format(osa.handle, hwparams, format);
1477     if (err < 0) {
1478         outstream_destroy_alsa(si, os);
1479         return SoundIoError.OpeningDevice;
1480     }
1481 
1482     osa.buffer_size_frames = cast(ulong) (outstream.software_latency * outstream.sample_rate);
1483     err = snd_pcm_hw_params_set_buffer_size_near(osa.handle, hwparams, &osa.buffer_size_frames);
1484     if (err < 0) {
1485         outstream_destroy_alsa(si, os);
1486         return SoundIoError.OpeningDevice;
1487     }
1488     outstream.software_latency = (cast(double)osa.buffer_size_frames) / cast(double)outstream.sample_rate;
1489 
1490     // write the hardware parameters to device
1491     err = snd_pcm_hw_params(osa.handle, hwparams);
1492     if (err < 0) {
1493         outstream_destroy_alsa(si, os);
1494         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1495     }
1496 
1497     if ((snd_pcm_hw_params_get_period_size(hwparams, &osa.period_size, null)) < 0) {
1498         outstream_destroy_alsa(si, os);
1499         return SoundIoError.OpeningDevice;
1500     }
1501 
1502 
1503     // set channel map
1504     osa.chmap.channels = ch_count;
1505     for (int i = 0; i < ch_count; i += 1) {
1506         // `pos` is variable length array typed `uint[0]`, .ptr to avoid range violation
1507         osa.chmap.pos.ptr[i] = to_alsa_chmap_pos(outstream.layout.channels[i]);
1508     }
1509     err = snd_pcm_set_chmap(osa.handle, osa.chmap);
1510     if (err < 0)
1511         outstream.layout_error = SoundIoError.IncompatibleDevice;
1512 
1513     // get current swparams
1514     snd_pcm_sw_params_t* swparams;
1515     snd_pcm_sw_params_malloc(&swparams);
1516     if (!swparams)
1517         return SoundIoError.NoMem;
1518     scope(exit) snd_pcm_sw_params_free(swparams);
1519 
1520     err = snd_pcm_sw_params_current(osa.handle, swparams);
1521     if (err < 0) {
1522         outstream_destroy_alsa(si, os);
1523         return SoundIoError.OpeningDevice;
1524     }
1525 
1526     err = snd_pcm_sw_params_set_start_threshold(osa.handle, swparams, 0);
1527     if (err < 0) {
1528         outstream_destroy_alsa(si, os);
1529         return SoundIoError.OpeningDevice;
1530     }
1531 
1532     err = snd_pcm_sw_params_set_avail_min(osa.handle, swparams, osa.period_size);
1533     if (err < 0) {
1534         outstream_destroy_alsa(si, os);
1535         return SoundIoError.OpeningDevice;
1536     }
1537 
1538     // write the software parameters to device
1539     err = snd_pcm_sw_params(osa.handle, swparams);
1540     if (err < 0) {
1541         outstream_destroy_alsa(si, os);
1542         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1543     }
1544 
1545     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED || osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1546         osa.sample_buffer_size = cast(int) (ch_count * osa.period_size * phys_bytes_per_sample);
1547         osa.sample_buffer = ALLOCATE_NONZERO!(char)(osa.sample_buffer_size);
1548         if (!osa.sample_buffer) {
1549             outstream_destroy_alsa(si, os);
1550             return SoundIoError.NoMem;
1551         }
1552     }
1553 
1554     osa.poll_fd_count = snd_pcm_poll_descriptors_count(osa.handle);
1555     if (osa.poll_fd_count <= 0) {
1556         outstream_destroy_alsa(si, os);
1557         return SoundIoError.OpeningDevice;
1558     }
1559 
1560     osa.poll_fd_count_with_extra = osa.poll_fd_count + 1;
1561     osa.poll_fds = ALLOCATE!pollfd( osa.poll_fd_count_with_extra);
1562     if (!osa.poll_fds) {
1563         outstream_destroy_alsa(si, os);
1564         return SoundIoError.NoMem;
1565     }
1566 
1567     err = snd_pcm_poll_descriptors(osa.handle, osa.poll_fds, osa.poll_fd_count);
1568     if (err < 0) {
1569         outstream_destroy_alsa(si, os);
1570         return SoundIoError.OpeningDevice;
1571     }
1572 
1573     pollfd* extra_fd = &osa.poll_fds[osa.poll_fd_count];
1574     if (pipe2(osa.poll_exit_pipe_fd.ptr, O_NONBLOCK)) {
1575         assert(errno != EFAULT);
1576         assert(errno != EINVAL);
1577         assert(errno == EMFILE || errno == ENFILE);
1578         outstream_destroy_alsa(si, os);
1579         return SoundIoError.SystemResources;
1580     }
1581     extra_fd.fd = osa.poll_exit_pipe_fd[0];
1582     extra_fd.events = POLLIN;
1583 
1584     return 0;
1585 }
1586 
1587 static int outstream_start_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1588     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1589     SoundIo* soundio = &si.pub;
1590 
1591     assert(!osa.thread);
1592 
1593     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(osa.thread_exit_flag);
1594     if (auto err = soundio_os_thread_create(&outstream_thread_run, os, soundio.emit_rtprio_warning, &osa.thread))
1595         return err;
1596 
1597     return 0;
1598 }
1599 
1600 static int outstream_begin_write_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, SoundIoChannelArea** out_areas, int* frame_count) {
1601     *out_areas = null;
1602     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1603     SoundIoOutStream* outstream = &os.pub;
1604 
1605     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1606         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1607             osa.areas[ch].ptr = osa.sample_buffer + ch * outstream.bytes_per_sample;
1608             osa.areas[ch].step = outstream.bytes_per_frame;
1609         }
1610 
1611         osa.write_frame_count = soundio_int_min(*frame_count, cast(int) osa.period_size);
1612         *frame_count = osa.write_frame_count;
1613     } else if (osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1614         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1615             osa.areas[ch].ptr = osa.sample_buffer + ch * outstream.bytes_per_sample * osa.period_size;
1616             osa.areas[ch].step = outstream.bytes_per_sample;
1617         }
1618 
1619         osa.write_frame_count = soundio_int_min(*frame_count, cast(int) osa.period_size);
1620         *frame_count = osa.write_frame_count;
1621     } else {
1622         const(snd_pcm_channel_area_t)* areas;
1623         snd_pcm_uframes_t frames = *frame_count;
1624         int err;
1625 
1626         err = snd_pcm_mmap_begin(osa.handle, &areas, &osa.offset, &frames);
1627         if (err < 0) {
1628             if (err == -EPIPE || err == -ESTRPIPE)
1629                 return SoundIoError.Underflow;
1630             else
1631                 return SoundIoError.Streaming;
1632         }
1633 
1634         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1635             if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
1636                 return SoundIoError.IncompatibleDevice;
1637             osa.areas[ch].step = areas[ch].step / 8;
1638             osa.areas[ch].ptr = (cast(char*)areas[ch].addr) + (areas[ch].first / 8) +
1639                 (osa.areas[ch].step * osa.offset);
1640         }
1641 
1642         osa.write_frame_count = cast(int) frames;
1643         *frame_count = osa.write_frame_count;
1644     }
1645 
1646     *out_areas = osa.areas.ptr;
1647     return 0;
1648 }
1649 
1650 static int outstream_end_write_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1651     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1652     SoundIoOutStream* outstream = &os.pub;
1653 
1654     snd_pcm_sframes_t commitres;
1655     if (osa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1656         commitres = snd_pcm_writei(osa.handle, osa.sample_buffer, osa.write_frame_count);
1657     } else if (osa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1658         char*[SOUNDIO_MAX_CHANNELS] ptrs;
1659         for (int ch = 0; ch < outstream.layout.channel_count; ch += 1) {
1660             ptrs[ch] = osa.sample_buffer + ch * outstream.bytes_per_sample * osa.period_size;
1661         }
1662         commitres = snd_pcm_writen(osa.handle, cast(void**)ptrs, osa.write_frame_count);
1663     } else {
1664         commitres = snd_pcm_mmap_commit(osa.handle, osa.offset, osa.write_frame_count);
1665     }
1666 
1667     if (commitres < 0 || commitres != osa.write_frame_count) {
1668         int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1669         if (err == -EPIPE || err == -ESTRPIPE)
1670             return SoundIoError.Underflow;
1671         else
1672             return SoundIoError.Streaming;
1673     }
1674     return 0;
1675 }
1676 
1677 static int outstream_clear_buffer_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os) {
1678     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1679     SOUNDIO_ATOMIC_FLAG_CLEAR(osa.clear_buffer_flag);
1680     return 0;
1681 }
1682 
1683 static int outstream_pause_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, bool pause) {
1684     if (!si)
1685         return SoundIoError.Invalid;
1686 
1687     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1688 
1689     if (!osa.handle)
1690         return SoundIoError.Invalid;
1691 
1692     if (osa.is_paused == pause)
1693         return 0;
1694 
1695     int err;
1696     err = snd_pcm_pause(osa.handle, pause);
1697     if (err < 0) {
1698         return SoundIoError.IncompatibleDevice;
1699     }
1700 
1701     osa.is_paused = pause;
1702     return 0;
1703 }
1704 
1705 static int outstream_get_latency_alsa(SoundIoPrivate* si, SoundIoOutStreamPrivate* os, double* out_latency) {
1706     SoundIoOutStream* outstream = &os.pub;
1707     SoundIoOutStreamAlsa* osa = &os.backend_data.alsa;
1708     int err;
1709 
1710     snd_pcm_sframes_t delay;
1711     err = snd_pcm_delay(osa.handle, &delay);
1712     if (err < 0) {
1713         return SoundIoError.Streaming;
1714     }
1715 
1716     *out_latency = delay / cast(double)outstream.sample_rate;
1717     return 0;
1718 }
1719 
1720 static void instream_destroy_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1721     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1722 
1723     if (isa.thread) {
1724         SOUNDIO_ATOMIC_FLAG_CLEAR(isa.thread_exit_flag);
1725         soundio_os_thread_destroy(isa.thread);
1726         isa.thread = null;
1727     }
1728 
1729     if (isa.handle) {
1730         snd_pcm_close(isa.handle);
1731         isa.handle = null;
1732     }
1733 
1734     free(isa.poll_fds);
1735     isa.poll_fds = null;
1736 
1737     free(isa.chmap);
1738     isa.chmap = null;
1739 
1740     free(isa.sample_buffer);
1741     isa.sample_buffer = null;
1742 }
1743 
1744 static int instream_open_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1745     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1746     SoundIoInStream* instream = &is_.pub;
1747     SoundIoDevice* device = instream.device;
1748 
1749     if (instream.software_latency == 0.0)
1750         instream.software_latency = 1.0;
1751     instream.software_latency = soundio_double_clamp(device.software_latency_min, instream.software_latency, device.software_latency_max);
1752 
1753     int ch_count = instream.layout.channel_count;
1754 
1755     isa.chmap_size = cast(int) (int.sizeof + int.sizeof * ch_count);
1756     isa.chmap = cast(snd_pcm_chmap_t*) ALLOCATE!char(isa.chmap_size);
1757     if (!isa.chmap) {
1758         instream_destroy_alsa(si, is_);
1759         return SoundIoError.NoMem;
1760     }
1761 
1762     int err;
1763 
1764     snd_pcm_hw_params_t* hwparams;
1765     snd_pcm_hw_params_malloc(&hwparams);
1766     if (!hwparams)
1767         return SoundIoError.NoMem;
1768     scope(exit) snd_pcm_hw_params_free(hwparams);
1769 
1770     snd_pcm_stream_t stream = aim_to_stream(instream.device.aim);
1771 
1772     err = snd_pcm_open(&isa.handle, instream.device.id, stream, 0);
1773     if (err < 0) {
1774         instream_destroy_alsa(si, is_);
1775         return SoundIoError.OpeningDevice;
1776     }
1777 
1778     err = snd_pcm_hw_params_any(isa.handle, hwparams);
1779     if (err < 0) {
1780         instream_destroy_alsa(si, is_);
1781         return SoundIoError.OpeningDevice;
1782     }
1783 
1784     int want_resample = !instream.device.is_raw;
1785     err = snd_pcm_hw_params_set_rate_resample(isa.handle, hwparams, want_resample);
1786     if (err < 0) {
1787         instream_destroy_alsa(si, is_);
1788         return SoundIoError.OpeningDevice;
1789     }
1790 
1791     err = set_access(isa.handle, hwparams, &isa.access);
1792     if (err) {
1793         instream_destroy_alsa(si, is_);
1794         return err;
1795     }
1796 
1797     err = snd_pcm_hw_params_set_channels(isa.handle, hwparams, ch_count);
1798     if (err < 0) {
1799         instream_destroy_alsa(si, is_);
1800         return SoundIoError.OpeningDevice;
1801     }
1802 
1803     err = snd_pcm_hw_params_set_rate(isa.handle, hwparams, instream.sample_rate, 0);
1804     if (err < 0) {
1805         instream_destroy_alsa(si, is_);
1806         return SoundIoError.OpeningDevice;
1807     }
1808 
1809     snd_pcm_format_t format = to_alsa_fmt(instream.format);
1810     int phys_bits_per_sample = snd_pcm_format_physical_width(format);
1811     if (phys_bits_per_sample % 8 != 0) {
1812         instream_destroy_alsa(si, is_);
1813         return SoundIoError.IncompatibleDevice;
1814     }
1815     int phys_bytes_per_sample = phys_bits_per_sample / 8;
1816     err = snd_pcm_hw_params_set_format(isa.handle, hwparams, format);
1817     if (err < 0) {
1818         instream_destroy_alsa(si, is_);
1819         return SoundIoError.OpeningDevice;
1820     }
1821 
1822     snd_pcm_uframes_t period_frames = ceil_dbl_to_uframes(0.5 * instream.software_latency * cast(double)instream.sample_rate);
1823     err = snd_pcm_hw_params_set_period_size_near(isa.handle, hwparams, &period_frames, null);
1824     if (err < 0) {
1825         instream_destroy_alsa(si, is_);
1826         return SoundIoError.OpeningDevice;
1827     }
1828     instream.software_latency = (cast(double)period_frames) / cast(double)instream.sample_rate;
1829     isa.period_size = cast(int) period_frames;
1830 
1831 
1832     snd_pcm_uframes_t buffer_size_frames;
1833     err = snd_pcm_hw_params_set_buffer_size_last(isa.handle, hwparams, &buffer_size_frames);
1834     if (err < 0) {
1835         instream_destroy_alsa(si, is_);
1836         return SoundIoError.OpeningDevice;
1837     }
1838 
1839     // write the hardware parameters to device
1840     err = snd_pcm_hw_params(isa.handle, hwparams);
1841     if (err < 0) {
1842         instream_destroy_alsa(si, is_);
1843         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1844     }
1845 
1846     // set channel map
1847     isa.chmap.channels = ch_count;
1848     for (int i = 0; i < ch_count; i += 1) {
1849         // `pos` is variable length array typed `uint[0]`, .ptr to avoid range violation
1850         isa.chmap.pos.ptr[i] = to_alsa_chmap_pos(instream.layout.channels[i]);
1851     }
1852     err = snd_pcm_set_chmap(isa.handle, isa.chmap);
1853     if (err < 0)
1854         instream.layout_error = SoundIoError.IncompatibleDevice;
1855 
1856     // get current swparams
1857     snd_pcm_sw_params_t* swparams;
1858     snd_pcm_sw_params_malloc(&swparams);
1859     if (!swparams)
1860         return SoundIoError.NoMem;
1861     scope(exit) snd_pcm_sw_params_free(swparams);
1862 
1863     err = snd_pcm_sw_params_current(isa.handle, swparams);
1864     if (err < 0) {
1865         instream_destroy_alsa(si, is_);
1866         return SoundIoError.OpeningDevice;
1867     }
1868 
1869     // write the software parameters to device
1870     err = snd_pcm_sw_params(isa.handle, swparams);
1871     if (err < 0) {
1872         instream_destroy_alsa(si, is_);
1873         return (err == -EINVAL) ? SoundIoError.IncompatibleDevice : SoundIoError.OpeningDevice;
1874     }
1875 
1876     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED || isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1877         isa.sample_buffer_size = ch_count * isa.period_size * phys_bytes_per_sample;
1878         isa.sample_buffer = ALLOCATE_NONZERO!char(isa.sample_buffer_size);
1879         if (!isa.sample_buffer) {
1880             instream_destroy_alsa(si, is_);
1881             return SoundIoError.NoMem;
1882         }
1883     }
1884 
1885     isa.poll_fd_count = snd_pcm_poll_descriptors_count(isa.handle);
1886     if (isa.poll_fd_count <= 0) {
1887         instream_destroy_alsa(si, is_);
1888         return SoundIoError.OpeningDevice;
1889     }
1890 
1891     isa.poll_fds = ALLOCATE!pollfd(isa.poll_fd_count);
1892     if (!isa.poll_fds) {
1893         instream_destroy_alsa(si, is_);
1894         return SoundIoError.NoMem;
1895     }
1896 
1897     err = snd_pcm_poll_descriptors(isa.handle, isa.poll_fds, isa.poll_fd_count);
1898     if (err < 0) {
1899         instream_destroy_alsa(si, is_);
1900         return SoundIoError.OpeningDevice;
1901     }
1902 
1903     return 0;
1904 }
1905 
1906 static int instream_start_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1907     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1908     SoundIo* soundio = &si.pub;
1909 
1910     assert(!isa.thread);
1911 
1912     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(isa.thread_exit_flag);
1913     if (auto err = soundio_os_thread_create(&instream_thread_run, is_, soundio.emit_rtprio_warning, &isa.thread)) {
1914         instream_destroy_alsa(si, is_);
1915         return err;
1916     }
1917 
1918     return 0;
1919 }
1920 
1921 static int instream_begin_read_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, SoundIoChannelArea** out_areas, int* frame_count) {
1922     *out_areas = null;
1923     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1924     SoundIoInStream* instream = &is_.pub;
1925 
1926     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1927         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1928             isa.areas[ch].ptr = isa.sample_buffer + ch * instream.bytes_per_sample;
1929             isa.areas[ch].step = instream.bytes_per_frame;
1930         }
1931 
1932         isa.read_frame_count = soundio_int_min(*frame_count, isa.period_size);
1933         *frame_count = isa.read_frame_count;
1934 
1935         snd_pcm_sframes_t commitres = snd_pcm_readi(isa.handle, isa.sample_buffer, isa.read_frame_count);
1936         if (commitres < 0 || commitres != isa.read_frame_count) {
1937             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1938             err = instream_xrun_recovery(is_, err);
1939             if (err < 0)
1940                 return SoundIoError.Streaming;
1941         }
1942     } else if (isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1943         char*[SOUNDIO_MAX_CHANNELS] ptrs;
1944         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1945             isa.areas[ch].ptr = isa.sample_buffer + ch * instream.bytes_per_sample * isa.period_size;
1946             isa.areas[ch].step = instream.bytes_per_sample;
1947             ptrs[ch] = isa.areas[ch].ptr;
1948         }
1949 
1950         isa.read_frame_count = soundio_int_min(*frame_count, isa.period_size);
1951         *frame_count = isa.read_frame_count;
1952 
1953         snd_pcm_sframes_t commitres = snd_pcm_readn(isa.handle, cast(void**)ptrs, isa.read_frame_count);
1954         if (commitres < 0 || commitres != isa.read_frame_count) {
1955             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1956             err = instream_xrun_recovery(is_, err);
1957             if (err < 0)
1958                 return SoundIoError.Streaming;
1959         }
1960     } else {
1961         const(snd_pcm_channel_area_t)* areas;
1962         snd_pcm_uframes_t frames = *frame_count;
1963         int err;
1964 
1965         err = snd_pcm_mmap_begin(isa.handle, &areas, &isa.offset, &frames);
1966         if (err < 0) {
1967             err = instream_xrun_recovery(is_, err);
1968             if (err < 0)
1969                 return SoundIoError.Streaming;
1970         }
1971 
1972         for (int ch = 0; ch < instream.layout.channel_count; ch += 1) {
1973             if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
1974                 return SoundIoError.IncompatibleDevice;
1975             isa.areas[ch].step = areas[ch].step / 8;
1976             isa.areas[ch].ptr = (cast(char*)areas[ch].addr) + (areas[ch].first / 8) +
1977                 (isa.areas[ch].step * isa.offset);
1978         }
1979 
1980         isa.read_frame_count = cast(int) frames;
1981         *frame_count = isa.read_frame_count;
1982     }
1983 
1984     *out_areas = isa.areas.ptr;
1985     return 0;
1986 }
1987 
1988 static int instream_end_read_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_) {
1989     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
1990 
1991     if (isa.access == SND_PCM_ACCESS_RW_INTERLEAVED) {
1992         // nothing to do
1993     } else if (isa.access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
1994         // nothing to do
1995     } else {
1996         snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(isa.handle, isa.offset, isa.read_frame_count);
1997         if (commitres < 0 || commitres != isa.read_frame_count) {
1998             int err = cast(int) ((commitres >= 0) ? -EPIPE : commitres);
1999             err = instream_xrun_recovery(is_, err);
2000             if (err < 0)
2001                 return SoundIoError.Streaming;
2002         }
2003     }
2004 
2005     return 0;
2006 }
2007 
2008 static int instream_pause_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, bool pause) {
2009     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
2010 
2011     if (isa.is_paused == pause)
2012         return 0;
2013 
2014     int err;
2015     err = snd_pcm_pause(isa.handle, pause);
2016     if (err < 0)
2017         return SoundIoError.IncompatibleDevice;
2018 
2019     isa.is_paused = pause;
2020     return 0;
2021 }
2022 
2023 static int instream_get_latency_alsa(SoundIoPrivate* si, SoundIoInStreamPrivate* is_, double* out_latency) {
2024     SoundIoInStream* instream = &is_.pub;
2025     SoundIoInStreamAlsa* isa = &is_.backend_data.alsa;
2026     int err;
2027 
2028     snd_pcm_sframes_t delay;
2029     err = snd_pcm_delay(isa.handle, &delay);
2030     if (err < 0) {
2031         return SoundIoError.Streaming;
2032     }
2033 
2034     *out_latency = delay / cast(double)instream.sample_rate;
2035     return 0;
2036 }
2037 
2038 package int soundio_alsa_init(SoundIoPrivate* si) {
2039     SoundIoAlsa* sia = &si.backend_data.alsa;
2040     int err;
2041 
2042     sia.notify_fd = -1;
2043     sia.notify_wd = -1;
2044     SOUNDIO_ATOMIC_FLAG_TEST_AND_SET(sia.abort_flag);
2045 
2046     sia.mutex = soundio_os_mutex_create();
2047     if (!sia.mutex) {
2048         destroy_alsa(si);
2049         return SoundIoError.NoMem;
2050     }
2051 
2052     sia.cond = soundio_os_cond_create();
2053     if (!sia.cond) {
2054         destroy_alsa(si);
2055         return SoundIoError.NoMem;
2056     }
2057 
2058 
2059     // set up inotify to watch /dev/snd for devices added or removed
2060     sia.notify_fd = inotify_init1(IN_NONBLOCK);
2061     if (sia.notify_fd == -1) {
2062         err = errno;
2063         assert(err != EINVAL);
2064         destroy_alsa(si);
2065         if (err == EMFILE || err == ENFILE) {
2066             return SoundIoError.SystemResources;
2067         } else {
2068             assert(err == ENOMEM);
2069             return SoundIoError.NoMem;
2070         }
2071     }
2072 
2073     sia.notify_wd = inotify_add_watch(sia.notify_fd, "/dev/snd", IN_CREATE | IN_CLOSE_WRITE | IN_DELETE);
2074     if (sia.notify_wd == -1) {
2075         err = errno;
2076         assert(err != EACCES);
2077         assert(err != EBADF);
2078         assert(err != EFAULT);
2079         assert(err != EINVAL);
2080         assert(err != ENAMETOOLONG);
2081         destroy_alsa(si);
2082         if (err == ENOSPC) {
2083             return SoundIoError.SystemResources;
2084         } else if (err == ENOMEM) {
2085             return SoundIoError.NoMem;
2086         } else {
2087             // Kernel must not have ALSA support.
2088             return SoundIoError.InitAudioBackend;
2089         }
2090     }
2091 
2092     if (pipe2(sia.notify_pipe_fd.ptr, O_NONBLOCK)) {
2093         assert(errno != EFAULT);
2094         assert(errno != EINVAL);
2095         assert(errno == EMFILE || errno == ENFILE);
2096         return SoundIoError.SystemResources;
2097     }
2098 
2099     wakeup_device_poll(sia);
2100 
2101     //device_thread_run(si); TODO removeme
2102     err = soundio_os_thread_create(&device_thread_run, si, null, &sia.thread);
2103     if (err) {
2104         destroy_alsa(si);
2105         return err;
2106     }
2107 
2108     si.destroy = &destroy_alsa;
2109     si.flush_events = &flush_events_alsa;
2110     si.wait_events = &wait_events_alsa;
2111     si.wakeup = &wakeup_alsa;
2112     si.force_device_scan = &force_device_scan_alsa;
2113 
2114     si.outstream_open = &outstream_open_alsa;
2115     si.outstream_destroy = &outstream_destroy_alsa;
2116     si.outstream_start = &outstream_start_alsa;
2117     si.outstream_begin_write = &outstream_begin_write_alsa;
2118     si.outstream_end_write = &outstream_end_write_alsa;
2119     si.outstream_clear_buffer = &outstream_clear_buffer_alsa;
2120     si.outstream_pause = &outstream_pause_alsa;
2121     si.outstream_get_latency = &outstream_get_latency_alsa;
2122 
2123     si.instream_open = &instream_open_alsa;
2124     si.instream_destroy = &instream_destroy_alsa;
2125     si.instream_start = &instream_start_alsa;
2126     si.instream_begin_read = &instream_begin_read_alsa;
2127     si.instream_end_read = &instream_end_read_alsa;
2128     si.instream_pause = &instream_pause_alsa;
2129     si.instream_get_latency = &instream_get_latency_alsa;
2130 
2131     return 0;
2132 }