/* * Copyright (c) 2015 Andrew Kelley * * This file is part of libsoundio, which is MIT licensed. * See http://opensource.org/licenses/MIT */ #include #include #include #include #include #include __attribute__ ((cold)) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2))) static void panic(const char *format, ...) { va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); fprintf(stderr, "\n"); va_end(ap); abort(); } static int min_int(int a, int b) { return (a < b) ? a : b; } static void error_callback(struct SoundIoStream *stream, int err) { panic("stream aborted: %s", soundio_strerror(err)); } static void audio_callback(struct SoundIoStream *stream, int frame_count_min, int frame_count_max) { struct SoundIoChannelArea *in_areas; struct SoundIoChannelArea *out_areas; int err; int frames_left = frame_count_max; int read_frame_count = -1; while (frames_left > 0) { int write_frame_count = frames_left; if ((err = soundio_stream_begin_write(stream, &out_areas, &write_frame_count))) panic("begin read error: %s", soundio_strerror(err)); if (!write_frame_count) break; for (int frame = 0; frame < write_frame_count; frame += 1, read_frame_count -= 1) { if (read_frame_count == 0) { if ((err = soundio_stream_end_read(stream))) panic("begin read error: %s", soundio_strerror(err)); } if (read_frame_count <= 0) { if ((err = soundio_stream_begin_read(stream, &in_areas, &read_frame_count))) panic("begin read error: %s", soundio_strerror(err)); } for (int ch = 0; ch < stream->output_layout.channel_count; ch += 1) { memcpy(write_ptr, areas[ch].ptr, stream->bytes_per_sample); out_areas[ch].ptr += out_areas[ch].step; write_ptr += instream->bytes_per_sample; } } if (!areas) { // Due to an overflow there is a hole. Fill the ring buffer with // silence for the size of the hole. memset(write_ptr, 0, frame_count * instream->bytes_per_frame); fprintf(stderr, "Dropped %d frames due to internal overflow\n", frame_count); } else { for (int frame = 0; frame < frame_count; frame += 1) { for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { memcpy(write_ptr, areas[ch].ptr, instream->bytes_per_sample); areas[ch].ptr += areas[ch].step; write_ptr += instream->bytes_per_sample; } } } if ((err = soundio_instream_end_read(instream))) panic("end read error: %s", soundio_strerror(err)); frames_left -= frame_count; if (frames_left <= 0) break; } } static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { struct SoundIoChannelArea *areas; int frame_count; int err; char *read_ptr = soundio_ring_buffer_read_ptr(ring_buffer); int fill_bytes = soundio_ring_buffer_fill_count(ring_buffer); int fill_count = fill_bytes / outstream->bytes_per_frame; if (frame_count_min > fill_count) { // Ring buffer does not have enough data, fill with zeroes. for (;;) { if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("begin write error: %s", soundio_strerror(err)); if (frame_count <= 0) return; for (int frame = 0; frame < frame_count; frame += 1) { for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { memset(areas[ch].ptr, 0, outstream->bytes_per_sample); areas[ch].ptr += areas[ch].step; } } if ((err = soundio_outstream_end_write(outstream))) panic("end write error: %s", soundio_strerror(err)); } } int read_count = min_int(frame_count_max, fill_count); int frames_left = read_count; while (frames_left > 0) { int frame_count = frames_left; if ((err = soundio_outstream_begin_write(outstream, &areas, &frame_count))) panic("begin write error: %s", soundio_strerror(err)); if (frame_count <= 0) break; for (int frame = 0; frame < frame_count; frame += 1) { for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) { memcpy(areas[ch].ptr, read_ptr, outstream->bytes_per_sample); areas[ch].ptr += areas[ch].step; read_ptr += outstream->bytes_per_sample; } } if ((err = soundio_outstream_end_write(outstream))) panic("end write error: %s", soundio_strerror(err)); frames_left -= frame_count; } soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); } static void underflow_callback(struct SoundIoStream *stream) { static int count = 0; fprintf(stderr, "underflow %d\n", ++count); } static void overflow_callback(struct SoundIoStream *stream) { static int count = 0; fprintf(stderr, "overflow %d\n", ++count); } static int usage(char *exe) { fprintf(stderr, "Usage: %s [options]\n" "Options:\n" " [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n" " [--device id]\n" " [--raw]\n" " [--latency seconds]\n" , exe); return 1; } int main(int argc, char **argv) { char *exe = argv[0]; enum SoundIoBackend backend = SoundIoBackendNone; char *device_id = NULL; bool raw = false; double microphone_latency = 0.2; // seconds for (int i = 1; i < argc; i += 1) { char *arg = argv[i]; if (arg[0] == '-' && arg[1] == '-') { if (strcmp(arg, "--raw") == 0) { raw = true; } else if (++i >= argc) { return usage(exe); } else if (strcmp(arg, "--backend") == 0) { if (strcmp("dummy", argv[i]) == 0) { backend = SoundIoBackendDummy; } else if (strcmp("alsa", argv[i]) == 0) { backend = SoundIoBackendAlsa; } else if (strcmp("pulseaudio", argv[i]) == 0) { backend = SoundIoBackendPulseAudio; } else if (strcmp("jack", argv[i]) == 0) { backend = SoundIoBackendJack; } else if (strcmp("coreaudio", argv[i]) == 0) { backend = SoundIoBackendCoreAudio; } else if (strcmp("wasapi", argv[i]) == 0) { backend = SoundIoBackendWasapi; } else { fprintf(stderr, "Invalid backend: %s\n", argv[i]); return 1; } } else if (strcmp(arg, "--device") == 0) { device_id = argv[i]; } else if (strcmp(arg, "--latency") == 0) { microphone_latency = atof(argv[i]); } else { return usage(exe); } } else { return usage(exe); } } struct SoundIo *soundio = soundio_create(); if (!soundio) panic("out of memory"); int err = (backend == SoundIoBackendNone) ? soundio_connect(soundio) : soundio_connect_backend(soundio, backend); if (err) panic("error connecting: %s", soundio_strerror(err)); soundio_flush_events(soundio); int default_out_device_index = soundio_default_output_device_index(soundio); if (default_out_device_index < 0) panic("no output device found"); int default_in_device_index = soundio_default_input_device_index(soundio); if (default_in_device_index < 0) panic("no output device found"); struct SoundIoDevice *in_device; if (device_id) { bool found = false; for (int i = 0; i < soundio_input_device_count(soundio); i += 1) { struct SoundIoDevice *device = soundio_get_input_device(soundio, i); if (device->is_raw == raw && strcmp(device->id, device_id) == 0) { in_device = device; found = true; break; } soundio_device_unref(device); } if (!found) panic("invalid input device id: %s", device_id); } else { in_device = soundio_get_input_device(soundio, default_in_device_index); } struct SoundIoDevice *out_device; bool found = false; for (int i = 0; i < soundio_output_device_count(soundio); i += 1) { struct SoundIoDevice *device = soundio_get_output_device(soundio, i); if (device->is_raw == in_device->is_raw && strcmp(device->id, in_device->id) == 0) { out_device = device; found = true; break; } soundio_device_unref(device); } if (!found) panic("invalid output device id: %s", in_device->id); fprintf(stderr, "Input device: %s\n", in_device->name); fprintf(stderr, "Output device: %s\n", out_device->name); struct SoundIoStream *stream = soundio_stream_create(in_device, out_device); if (!stream) panic("out of memory"); stream->software_latency = microphone_latency; stream->audio_callback = audio_callback; stream->underflow_callback = underflow_callback; stream->overflow_callback = overflow_callback; stream->error_callback = error_callback; if ((err = soundio_instream_open(instream))) { fprintf(stderr, "unable to open input stream: %s", soundio_strerror(err)); return 1; } struct SoundIoOutStream *outstream = soundio_outstream_create(out_device); if (!outstream) panic("out of memory"); outstream->software_latency = microphone_latency; if ((err = soundio_outstream_open(outstream))) { fprintf(stderr, "unable to open output stream: %s", soundio_strerror(err)); return 1; } int capacity = microphone_latency * 2 * instream->sample_rate * instream->bytes_per_frame; ring_buffer = soundio_ring_buffer_create(soundio, capacity); if (!ring_buffer) panic("unable to create ring buffer: out of memory"); char *buf = soundio_ring_buffer_write_ptr(ring_buffer); int fill_count = microphone_latency * outstream->sample_rate * outstream->bytes_per_frame; memset(buf, 0, fill_count); soundio_ring_buffer_advance_write_ptr(ring_buffer, fill_count); if ((err = soundio_instream_start(instream))) panic("unable to start input device: %s", soundio_strerror(err)); if ((err = soundio_outstream_start(outstream))) panic("unable to start output device: %s", soundio_strerror(err)); for (;;) soundio_wait_events(soundio); soundio_outstream_destroy(outstream); soundio_instream_destroy(instream); soundio_device_unref(in_device); soundio_device_unref(out_device); soundio_destroy(soundio); return 0; }