1 /// Translated from C to D 2 module sio_record; 3 4 extern(C): @nogc: nothrow: __gshared: 5 6 import soundio.api; 7 import soundio.util: printf_stderr; 8 9 import core.stdc.stdio; 10 import core.stdc.stdlib; 11 import core.stdc.string; 12 import core.stdc.math; 13 import core.stdc.errno; 14 15 version (Posix) 16 import core.sys.posix.unistd; 17 else version(Windows) 18 import core.sys.windows.winbase; 19 else 20 static assert(false); 21 22 struct RecordContext { 23 SoundIoRingBuffer* ring_buffer; 24 } 25 26 immutable SoundIoFormat[19] prioritized_formats = [ 27 SoundIoFormat.Float32NE, 28 SoundIoFormat.Float32FE, 29 SoundIoFormat.S32NE, 30 SoundIoFormat.S32FE, 31 SoundIoFormat.S24NE, 32 SoundIoFormat.S24FE, 33 SoundIoFormat.S16NE, 34 SoundIoFormat.S16FE, 35 SoundIoFormat.Float64NE, 36 SoundIoFormat.Float64FE, 37 SoundIoFormat.U32NE, 38 SoundIoFormat.U32FE, 39 SoundIoFormat.U24NE, 40 SoundIoFormat.U24FE, 41 SoundIoFormat.U16NE, 42 SoundIoFormat.U16FE, 43 SoundIoFormat.S8, 44 SoundIoFormat.U8, 45 SoundIoFormat.Invalid, 46 ]; 47 48 immutable int[5] prioritized_sample_rates = [ 49 48000, 50 44100, 51 96000, 52 24000, 53 0, 54 ]; 55 56 static int min_int(int a, int b) { 57 return (a < b) ? a : b; 58 } 59 60 static void read_callback(SoundIoInStream* instream, int frame_count_min, int frame_count_max) { 61 RecordContext* rc = cast(RecordContext*) instream.userdata; 62 SoundIoChannelArea* areas; 63 64 char* write_ptr = soundio_ring_buffer_write_ptr(rc.ring_buffer); 65 int free_bytes = soundio_ring_buffer_free_count(rc.ring_buffer); 66 int free_count = free_bytes / instream.bytes_per_frame; 67 68 if (free_count < frame_count_min) { 69 printf_stderr("ring buffer overflow\n"); 70 exit(1); 71 } 72 73 int write_frames = min_int(free_count, frame_count_max); 74 int frames_left = write_frames; 75 76 for (;;) { 77 int frame_count = frames_left; 78 79 if (auto err = soundio_instream_begin_read(instream, &areas, &frame_count)) { 80 printf_stderr("begin read error: %s", soundio_strerror(err)); 81 exit(1); 82 } 83 84 if (!frame_count) 85 break; 86 87 if (!areas) { 88 // Due to an overflow there is a hole. Fill the ring buffer with 89 // silence for the size of the hole. 90 memset(write_ptr, 0, frame_count * instream.bytes_per_frame); 91 } else { 92 for (int frame = 0; frame < frame_count; frame += 1) { 93 for (int ch = 0; ch < instream.layout.channel_count; ch += 1) { 94 memcpy(write_ptr, areas[ch].ptr, instream.bytes_per_sample); 95 areas[ch].ptr += areas[ch].step; 96 write_ptr += instream.bytes_per_sample; 97 } 98 } 99 } 100 101 if (auto err = soundio_instream_end_read(instream)) { 102 printf_stderr("end read error: %s", soundio_strerror(err)); 103 exit(1); 104 } 105 106 frames_left -= frame_count; 107 if (frames_left <= 0) 108 break; 109 } 110 111 int advance_bytes = write_frames * instream.bytes_per_frame; 112 soundio_ring_buffer_advance_write_ptr(rc.ring_buffer, advance_bytes); 113 } 114 115 static void overflow_callback(SoundIoInStream* instream) { 116 static int count = 0; 117 printf_stderr("overflow %d\n", ++count); 118 } 119 120 static int usage(char* exe) { 121 printf_stderr("Usage: %s [options] outfile\n" 122 ~ "Options:\n" 123 ~ " [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n" 124 ~ " [--device id]\n" 125 ~ " [--raw]\n" 126 , exe); 127 return 1; 128 } 129 130 int main(int argc, char** argv) { 131 char* exe = argv[0]; 132 SoundIoBackend backend = SoundIoBackend.None; 133 char* device_id = null; 134 bool is_raw = false; 135 char* outfile = null; 136 for (int i = 1; i < argc; i += 1) { 137 char* arg = argv[i]; 138 if (arg[0] == '-' && arg[1] == '-') { 139 if (strcmp(arg, "--raw") == 0) { 140 is_raw = true; 141 } else if (++i >= argc) { 142 return usage(exe); 143 } else if (strcmp(arg, "--backend") == 0) { 144 if (strcmp("dummy", argv[i]) == 0) { 145 backend = SoundIoBackend.Dummy; 146 } else if (strcmp("alsa", argv[i]) == 0) { 147 backend = SoundIoBackend.Alsa; 148 } else if (strcmp("pulseaudio", argv[i]) == 0) { 149 backend = SoundIoBackend.PulseAudio; 150 } else if (strcmp("jack", argv[i]) == 0) { 151 backend = SoundIoBackend.Jack; 152 } else if (strcmp("coreaudio", argv[i]) == 0) { 153 backend = SoundIoBackend.CoreAudio; 154 } else if (strcmp("wasapi", argv[i]) == 0) { 155 backend = SoundIoBackend.Wasapi; 156 } else { 157 printf_stderr("Invalid backend: %s\n", argv[i]); 158 return 1; 159 } 160 } else if (strcmp(arg, "--device") == 0) { 161 device_id = argv[i]; 162 } else { 163 return usage(exe); 164 } 165 } else if (!outfile) { 166 outfile = argv[i]; 167 } else { 168 return usage(exe); 169 } 170 } 171 172 if (!outfile) 173 return usage(exe); 174 175 RecordContext rc; 176 177 SoundIo* soundio = soundio_create(); 178 if (!soundio) { 179 printf_stderr("out of memory\n"); 180 return 1; 181 } 182 183 if (auto err = (backend == SoundIoBackend.None) ? 184 soundio_connect(soundio) : soundio_connect_backend(soundio, backend)) { 185 printf_stderr("error connecting: %s", soundio_strerror(err)); 186 return 1; 187 } 188 189 soundio_flush_events(soundio); 190 191 SoundIoDevice* selected_device = null; 192 193 if (device_id) { 194 for (int i = 0; i < soundio_input_device_count(soundio); i += 1) { 195 SoundIoDevice* device = soundio_get_input_device(soundio, i); 196 if (device.is_raw == is_raw && strcmp(device.id, device_id) == 0) { 197 selected_device = device; 198 break; 199 } 200 soundio_device_unref(device); 201 } 202 if (!selected_device) { 203 printf_stderr("Invalid device id: %s\n", device_id); 204 return 1; 205 } 206 } else { 207 int device_index = soundio_default_input_device_index(soundio); 208 selected_device = soundio_get_input_device(soundio, device_index); 209 if (!selected_device) { 210 printf_stderr("No input devices available.\n"); 211 return 1; 212 } 213 } 214 215 printf_stderr("Device: %s\n", selected_device.name); 216 217 if (selected_device.probe_error) { 218 printf_stderr("Unable to probe device: %s\n", soundio_strerror(selected_device.probe_error)); 219 return 1; 220 } 221 222 soundio_device_sort_channel_layouts(selected_device); 223 224 int sample_rate = 0; 225 const(int)* sample_rate_ptr; 226 foreach (sr; prioritized_sample_rates) { 227 if (soundio_device_supports_sample_rate(selected_device, sr)) { 228 sample_rate = sr; 229 break; 230 } 231 } 232 if (!sample_rate) 233 sample_rate = selected_device.sample_rates[0].max; 234 235 SoundIoFormat fmt = SoundIoFormat.Invalid; 236 const(SoundIoFormat)* fmt_ptr; 237 for (fmt_ptr = prioritized_formats.ptr; *fmt_ptr != SoundIoFormat.Invalid; fmt_ptr += 1) { 238 if (soundio_device_supports_format(selected_device, *fmt_ptr)) { 239 fmt = *fmt_ptr; 240 break; 241 } 242 } 243 if (fmt == SoundIoFormat.Invalid) 244 fmt = selected_device.formats[0]; 245 246 FILE* out_f = fopen(outfile, "wb"); 247 if (!out_f) { 248 printf_stderr("unable to open %s: %s\n", outfile, strerror(errno)); 249 return 1; 250 } 251 SoundIoInStream* instream = soundio_instream_create(selected_device); 252 if (!instream) { 253 printf_stderr("out of memory\n"); 254 return 1; 255 } 256 instream.format = fmt; 257 instream.sample_rate = sample_rate; 258 instream.read_callback = &read_callback; 259 instream.overflow_callback = &overflow_callback; 260 instream.userdata = &rc; 261 262 if (auto err = soundio_instream_open(instream)) { 263 printf_stderr("unable to open input stream: %s", soundio_strerror(err)); 264 return 1; 265 } 266 267 printf_stderr("%s %dHz %s interleaved\n", 268 instream.layout.name, sample_rate, soundio_format_string(fmt)); 269 270 const(int) ring_buffer_duration_seconds = 30; 271 int capacity = ring_buffer_duration_seconds * instream.sample_rate * instream.bytes_per_frame; 272 rc.ring_buffer = soundio_ring_buffer_create(soundio, capacity); 273 if (!rc.ring_buffer) { 274 printf_stderr("out of memory\n"); 275 return 1; 276 } 277 278 if (auto err = soundio_instream_start(instream)) { 279 printf_stderr("unable to start input device: %s", soundio_strerror(err)); 280 return 1; 281 } 282 283 // Note: in this example, if you send SIGINT (by pressing Ctrl+C for example) 284 // you will lose up to 1 second of recorded audio data. In non-example code, 285 // consider a better shutdown strategy. 286 for (;;) { 287 soundio_flush_events(soundio); 288 version(Posix) 289 sleep(1); 290 else version(Windows) 291 Sleep(1000); 292 else 293 static assert(false); 294 int fill_bytes = soundio_ring_buffer_fill_count(rc.ring_buffer); 295 char* read_buf = soundio_ring_buffer_read_ptr(rc.ring_buffer); 296 size_t amt = fwrite(read_buf, 1, fill_bytes, out_f); 297 if (cast(int)amt != fill_bytes) { 298 printf_stderr("write error: %s\n", strerror(errno)); 299 return 1; 300 } 301 soundio_ring_buffer_advance_read_ptr(rc.ring_buffer, fill_bytes); 302 } 303 304 soundio_instream_destroy(instream); 305 soundio_device_unref(selected_device); 306 soundio_destroy(soundio); 307 return 0; 308 }