diff --git a/CMakeLists.txt b/CMakeLists.txt index c49f63b..5430d0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,14 @@ include_directories(${EXAMPLE_INCLUDES}) target_link_libraries(sio_microphone libsoundio_shared) install(TARGETS sio_microphone DESTINATION ${CMAKE_INSTALL_BINDIR}) +add_executable(sio_record example/sio_record.c) +set_target_properties(sio_record PROPERTIES + LINKER_LANGUAGE C + COMPILE_FLAGS ${EXAMPLE_CFLAGS}) +include_directories(${EXAMPLE_INCLUDES}) +target_link_libraries(sio_record libsoundio_shared) +install(TARGETS sio_record DESTINATION ${CMAKE_INSTALL_BINDIR}) + add_executable(unit_tests ${TEST_SOURCES}) diff --git a/example/sio_record.c b/example/sio_record.c new file mode 100644 index 0000000..2b31c37 --- /dev/null +++ b/example/sio_record.c @@ -0,0 +1,246 @@ +/* + * 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 + +static enum SoundIoFormat prioritized_formats[] = { + SoundIoFormatFloat32NE, + SoundIoFormatFloat32FE, + SoundIoFormatS32NE, + SoundIoFormatS32FE, + SoundIoFormatS24NE, + SoundIoFormatS24FE, + SoundIoFormatS16NE, + SoundIoFormatS16FE, + SoundIoFormatFloat64NE, + SoundIoFormatFloat64FE, + SoundIoFormatU32NE, + SoundIoFormatU32FE, + SoundIoFormatU24NE, + SoundIoFormatU24FE, + SoundIoFormatU16NE, + SoundIoFormatU16FE, + SoundIoFormatS8, + SoundIoFormatU8, + SoundIoFormatInvalid, +}; + +static int prioritized_sample_rates[] = { + 48000, + 44100, + 96000, + 24000, + 0, +}; + +static FILE *out_f = NULL; + +static void read_callback(struct SoundIoInStream *instream, int frame_count_min, int frame_count_max) { + struct SoundIoChannelArea *areas; + int err; + + int frames_left = frame_count_max; + + for (;;) { + int frame_count = frames_left; + + if ((err = soundio_instream_begin_read(instream, &areas, &frame_count))) { + fprintf(stderr, "begin read error: %s", soundio_strerror(err)); + exit(1); + } + + if (!frame_count) + break; + + if (!areas) { + fprintf(stderr, "hole\n"); + exit(1); + } else { + for (int frame = 0; frame < frame_count; frame += 1) { + for (int ch = 0; ch < instream->layout.channel_count; ch += 1) { + fwrite(areas[ch].ptr, 1, instream->bytes_per_sample, out_f); + areas[ch].ptr += areas[ch].step; + } + } + } + + if ((err = soundio_instream_end_read(instream))) { + fprintf(stderr, "end read error: %s", soundio_strerror(err)); + exit(1); + } + + frames_left -= frame_count; + if (frames_left <= 0) + break; + } +} + +static void overflow_callback(struct SoundIoInStream *instream) { + static int count = 0; + fprintf(stderr, "overflow %d\n", ++count); +} + +static int usage(char *exe) { + fprintf(stderr, "Usage: %s [options] outfile.wav\n" + "Options:\n" + " [--backend dummy|alsa|pulseaudio|jack|coreaudio|wasapi]\n" + " [--device id]\n" + " [--raw]\n" + , exe); + return 1; +} + +int main(int argc, char **argv) { + char *exe = argv[0]; + enum SoundIoBackend backend = SoundIoBackendNone; + char *device_id = NULL; + bool is_raw = false; + char *outfile = NULL; + for (int i = 1; i < argc; i += 1) { + char *arg = argv[i]; + if (arg[0] == '-' && arg[1] == '-') { + if (strcmp(arg, "--raw") == 0) { + is_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 { + return usage(exe); + } + } else if (!outfile) { + outfile = argv[i]; + } else { + return usage(exe); + } + } + + if (!outfile) + return usage(exe); + + struct SoundIo *soundio = soundio_create(); + if (!soundio) { + fprintf(stderr, "out of memory\n"); + return 1; + } + + int err = (backend == SoundIoBackendNone) ? + soundio_connect(soundio) : soundio_connect_backend(soundio, backend); + if (err) { + fprintf(stderr, "error connecting: %s", soundio_strerror(err)); + return 1; + } + + soundio_flush_events(soundio); + + struct SoundIoDevice *selected_device = NULL; + + if (device_id) { + 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 == is_raw && strcmp(device->id, device_id) == 0) { + selected_device = device; + break; + } + soundio_device_unref(device); + } + if (!selected_device) { + fprintf(stderr, "Invalid device id: %s\n", device_id); + return 1; + } + } else { + int device_index = soundio_default_input_device_index(soundio); + selected_device = soundio_get_input_device(soundio, device_index); + } + + fprintf(stderr, "Device: %s\n", selected_device->name); + + soundio_device_sort_channel_layouts(selected_device); + struct SoundIoChannelLayout *layout = &selected_device->layouts[0]; + + int sample_rate = 0; + int *sample_rate_ptr; + for (sample_rate_ptr = prioritized_sample_rates; *sample_rate_ptr; sample_rate_ptr += 1) { + if (soundio_device_supports_sample_rate(selected_device, *sample_rate_ptr)) { + sample_rate = *sample_rate_ptr; + break; + } + } + if (!sample_rate) + sample_rate = selected_device->sample_rates[0].max; + + enum SoundIoFormat fmt = SoundIoFormatInvalid; + enum SoundIoFormat *fmt_ptr; + for (fmt_ptr = prioritized_formats; *fmt_ptr != SoundIoFormatInvalid; fmt_ptr += 1) { + if (soundio_device_supports_format(selected_device, *fmt_ptr)) { + fmt = *fmt_ptr; + break; + } + } + if (fmt == SoundIoFormatInvalid) + fmt = selected_device->formats[0]; + + fprintf(stderr, "%s %dHz %s interleaved\n", layout->name, sample_rate, soundio_format_string(fmt)); + + out_f = fopen(outfile, "wb"); + if (!out_f) { + fprintf(stderr, "unable to open %s: %s\n", outfile, strerror(errno)); + return 1; + } + + struct SoundIoInStream *instream = soundio_instream_create(selected_device); + if (!instream) { + fprintf(stderr, "out of memory\n"); + return 1; + } + instream->format = fmt; + instream->sample_rate = sample_rate; + instream->layout = *layout; + instream->read_callback = read_callback; + instream->overflow_callback = overflow_callback; + + if ((err = soundio_instream_open(instream))) { + fprintf(stderr, "unable to open input stream: %s", soundio_strerror(err)); + return 1; + } + + if ((err = soundio_instream_start(instream))) { + fprintf(stderr, "unable to start input device: %s", soundio_strerror(err)); + return 1; + } + + for (;;) + soundio_wait_events(soundio); + + soundio_instream_destroy(instream); + soundio_device_unref(selected_device); + soundio_destroy(soundio); + return 0; +} diff --git a/soundio/soundio.h b/soundio/soundio.h index 0982aa5..ca3b834 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -50,6 +50,11 @@ * Supports specifying device and backend to use. */ +/** \example sio_record.c + * Record audio to an output file. + * Supports specifying device and backend to use. + */ + /** \example sio_microphone.c * Stream the default input device over the default output device. * Supports specifying device and backend to use.