From b62daa25a2f81f8d14d70dc7bb3ab849d536e837 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 24 Sep 2015 08:01:00 -0700 Subject: [PATCH] WIP --- CHANGELOG.md | 7 + CMakeLists.txt | 1 - example/sio_microphone.c | 207 +++++--------- soundio/soundio.h | 577 +++++++++++++++++---------------------- src/alsa.hpp | 27 +- src/dummy.cpp | 88 +++--- src/dummy.hpp | 31 +-- src/jack.hpp | 21 +- src/pulseaudio.hpp | 18 +- src/ring_buffer.cpp | 91 ------ src/ring_buffer.hpp | 24 -- src/soundio.cpp | 385 +++++++++++++------------- src/soundio.hpp | 71 ++--- 13 files changed, 613 insertions(+), 935 deletions(-) delete mode 100644 src/ring_buffer.cpp delete mode 100644 src/ring_buffer.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b08bed..8aa9c41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +### Version 2.0.0 (UNRELEASED) + + * Default sample rate is now 44100 instead of 48000. + * `SoundIoOutStream` and `SoundIoInStream` no longer exist. Instead, there is + `SoundIoStream` and a stream can be an output stream, an input stream, or + both. + ### Version 1.0.1 (2015-09-11) * libsoundio no longer depends on or links against libm. diff --git a/CMakeLists.txt b/CMakeLists.txt index a2c4cb2..936ba88 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,7 +140,6 @@ set(LIBSOUNDIO_SOURCES "${CMAKE_SOURCE_DIR}/src/os.cpp" "${CMAKE_SOURCE_DIR}/src/dummy.cpp" "${CMAKE_SOURCE_DIR}/src/channel_layout.cpp" - "${CMAKE_SOURCE_DIR}/src/ring_buffer.cpp" ) set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h") diff --git a/example/sio_microphone.c b/example/sio_microphone.c index 7d8e306..133e2c6 100644 --- a/example/sio_microphone.c +++ b/example/sio_microphone.c @@ -13,39 +13,6 @@ #include #include -struct SoundIoRingBuffer *ring_buffer = NULL; - -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, -}; - - __attribute__ ((cold)) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2))) @@ -62,28 +29,46 @@ static int min_int(int a, int b) { return (a < b) ? a : b; } -static void read_callback(struct SoundIoInStream *instream, int frame_count_min, int frame_count_max) { - struct SoundIoChannelArea *areas; +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; - char *write_ptr = soundio_ring_buffer_write_ptr(ring_buffer); - int free_bytes = soundio_ring_buffer_free_count(ring_buffer); - int free_count = free_bytes / instream->bytes_per_frame; - if (frame_count_min > free_count) - panic("ring buffer overflow"); + int frames_left = frame_count_max; - int write_frames = min_int(free_count, frame_count_max); - int frames_left = write_frames; + int read_frame_count = -1; - for (;;) { - int frame_count = frames_left; + while (frames_left > 0) { + int write_frame_count = frames_left; - if ((err = soundio_instream_begin_read(instream, &areas, &frame_count))) + if ((err = soundio_stream_begin_write(stream, &out_areas, &write_frame_count))) panic("begin read error: %s", soundio_strerror(err)); - if (!frame_count) + 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. @@ -106,9 +91,6 @@ static void read_callback(struct SoundIoInStream *instream, int frame_count_min, if (frames_left <= 0) break; } - - int advance_bytes = write_frames * instream->bytes_per_frame; - soundio_ring_buffer_advance_write_ptr(ring_buffer, advance_bytes); } static void write_callback(struct SoundIoOutStream *outstream, int frame_count_min, int frame_count_max) { @@ -167,19 +149,22 @@ static void write_callback(struct SoundIoOutStream *outstream, int frame_count_m soundio_ring_buffer_advance_read_ptr(ring_buffer, read_count * outstream->bytes_per_frame); } -static void underflow_callback(struct SoundIoOutStream *outstream) { +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" - " [--in-device id]\n" - " [--in-raw]\n" - " [--out-device id]\n" - " [--out-raw]\n" + " [--device id]\n" + " [--raw]\n" " [--latency seconds]\n" , exe); return 1; @@ -188,20 +173,16 @@ static int usage(char *exe) { int main(int argc, char **argv) { char *exe = argv[0]; enum SoundIoBackend backend = SoundIoBackendNone; - char *in_device_id = NULL; - char *out_device_id = NULL; - bool in_raw = false; - bool out_raw = false; + 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, "--in-raw") == 0) { - in_raw = true; - } else if (strcmp(arg, "--out-raw") == 0) { - out_raw = true; + if (strcmp(arg, "--raw") == 0) { + raw = true; } else if (++i >= argc) { return usage(exe); } else if (strcmp(arg, "--backend") == 0) { @@ -221,10 +202,8 @@ int main(int argc, char **argv) { fprintf(stderr, "Invalid backend: %s\n", argv[i]); return 1; } - } else if (strcmp(arg, "--in-device") == 0) { - in_device_id = argv[i]; - } else if (strcmp(arg, "--out-device") == 0) { - out_device_id = argv[i]; + } else if (strcmp(arg, "--device") == 0) { + device_id = argv[i]; } else if (strcmp(arg, "--latency") == 0) { microphone_latency = atof(argv[i]); } else { @@ -253,89 +232,52 @@ int main(int argc, char **argv) { if (default_in_device_index < 0) panic("no output device found"); - int in_device_index = default_in_device_index; - if (in_device_id) { + 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 == in_raw && strcmp(device->id, in_device_id) == 0) { - in_device_index = i; + if (device->is_raw == raw && strcmp(device->id, device_id) == 0) { + in_device = device; found = true; - soundio_device_unref(device); break; } soundio_device_unref(device); } if (!found) - panic("invalid input device id: %s", in_device_id); + panic("invalid input device id: %s", device_id); + } else { + in_device = soundio_get_input_device(soundio, default_in_device_index); } - int out_device_index = default_out_device_index; - if (out_device_id) { - 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 == out_raw && strcmp(device->id, out_device_id) == 0) { - out_device_index = i; - found = true; - soundio_device_unref(device); - break; - } - soundio_device_unref(device); + 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; } - if (!found) - panic("invalid output device id: %s", out_device_id); + soundio_device_unref(device); } + if (!found) + panic("invalid output device id: %s", in_device->id); - struct SoundIoDevice *out_device = soundio_get_output_device(soundio, out_device_index); - if (!out_device) - panic("could not get output device: out of memory"); - - struct SoundIoDevice *in_device = soundio_get_input_device(soundio, in_device_index); - if (!in_device) - panic("could not get input device: out of memory"); fprintf(stderr, "Input device: %s\n", in_device->name); fprintf(stderr, "Output device: %s\n", out_device->name); - soundio_device_sort_channel_layouts(out_device); - const struct SoundIoChannelLayout *layout = soundio_best_matching_channel_layout( - out_device->layouts, out_device->layout_count, - in_device->layouts, in_device->layout_count); - - if (!layout) - panic("channel layouts not compatible"); - - int *sample_rate; - for (sample_rate = prioritized_sample_rates; *sample_rate; sample_rate += 1) { - if (soundio_device_supports_sample_rate(in_device, *sample_rate) && - soundio_device_supports_sample_rate(out_device, *sample_rate)) - { - break; - } - } - if (!*sample_rate) - panic("incompatible sample rates"); - - enum SoundIoFormat *fmt; - for (fmt = prioritized_formats; *fmt != SoundIoFormatInvalid; fmt += 1) { - if (soundio_device_supports_format(in_device, *fmt) && - soundio_device_supports_format(out_device, *fmt)) - { - break; - } - } - if (*fmt == SoundIoFormatInvalid) - panic("incompatible sample formats"); - - struct SoundIoInStream *instream = soundio_instream_create(in_device); - if (!instream) + struct SoundIoStream *stream = soundio_stream_create(in_device, out_device); + if (!stream) panic("out of memory"); - instream->format = *fmt; - instream->sample_rate = *sample_rate; - instream->layout = *layout; - instream->software_latency = microphone_latency; - instream->read_callback = read_callback; + 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)); @@ -345,12 +287,7 @@ int main(int argc, char **argv) { struct SoundIoOutStream *outstream = soundio_outstream_create(out_device); if (!outstream) panic("out of memory"); - outstream->format = *fmt; - outstream->sample_rate = *sample_rate; - outstream->layout = *layout; outstream->software_latency = microphone_latency; - outstream->write_callback = write_callback; - outstream->underflow_callback = underflow_callback; if ((err = soundio_outstream_open(outstream))) { fprintf(stderr, "unable to open output stream: %s", soundio_strerror(err)); diff --git a/soundio/soundio.h b/soundio/soundio.h index e39af12..0a670d9 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -228,6 +228,27 @@ enum SoundIoDeviceAim { /// For your convenience, Native Endian and Foreign Endian constants are defined /// which point to the respective SoundIoFormat values. +/// +/// When choosing a default sample format, libsoundio tries the formats in +/// this order: +/// * #SoundIoFormatFloat32NE +/// * #SoundIoFormatFloat32FE +/// * #SoundIoFormatS32NE +/// * #SoundIoFormatS32FE +/// * #SoundIoFormatU32NE +/// * #SoundIoFormatU32FE +/// * #SoundIoFormatFloat64NE +/// * #SoundIoFormatFloat64FE +/// * #SoundIoFormatS24NE +/// * #SoundIoFormatS24FE +/// * #SoundIoFormatU24NE +/// * #SoundIoFormatU24FE +/// * #SoundIoFormatS16NE +/// * #SoundIoFormatS16FE +/// * #SoundIoFormatU16NE +/// * #SoundIoFormatU16FE +/// * #SoundIoFormatS8 +/// * #SoundIoFormatU8 enum SoundIoFormat { SoundIoFormatInvalid, SoundIoFormatS8, ///< Signed 8 bit @@ -293,7 +314,7 @@ enum SoundIoFormat { #error unknown byte order #endif -#define SOUNDIO_MAX_CHANNELS 24 +#define SOUNDIO_MAX_CHANNELS 16 /// The size of this struct is OK to use. struct SoundIoChannelLayout { const char *name; @@ -388,6 +409,8 @@ struct SoundIoDevice { /// supports raw mode, there may be up to four devices with the same id: /// one for each value of SoundIoDevice::is_raw and one for each value of /// SoundIoDevice::aim. + /// To open a duplex stream, input_device and output_device must have + /// the same id. char *id; /// User-friendly UTF-8 encoded text to describe the device. char *name; @@ -485,60 +508,99 @@ struct SoundIoDevice { int probe_error; }; +/// This struct represents an output stream, an input stream, or a duplex +/// stream. A duplex stream is both an output stream and an input stream. /// The size of this struct is not part of the API or ABI. -struct SoundIoOutStream { - /// Populated automatically when you call ::soundio_outstream_create. - struct SoundIoDevice *device; +struct SoundIoStream { + /// Populated automatically when you call ::soundio_stream_create. + /// NULL if this stream is not an input stream. + struct SoundIoDevice *input_device; - /// Defaults to #SoundIoFormatFloat32NE, followed by the first one + /// Populated automatically when you call ::soundio_stream_create. + /// NULL if this stream is not an output stream. + struct SoundIoDevice *output_device; + + /// For input streams. + /// Defaults to the best format supported by input_device. + /// See SoundIoFormat for the priority listing. + enum SoundIoFormat input_format; + + /// For output streams. + /// Defaults to the best format supported by output_device. + /// See SoundIoFormat for the priority listing. + enum SoundIoFormat output_format; + + /// For input streams. + /// Defaults to the layout with the largest number of channels that is /// supported. - enum SoundIoFormat format; + struct SoundIoChannelLayout input_layout; + + /// For output streams. + /// Defaults to the layout with the largest number of channels that is + /// supported. + struct SoundIoChannelLayout output_layout; /// Sample rate is the number of frames per second. - /// Defaults to 48000 (and then clamped into range). + /// Defaults to 44100 and then clamped into the closest supported value + /// by input_device if applicable and output_device if applicable. int sample_rate; - /// Defaults to Stereo, if available, followed by the first layout - /// supported. - struct SoundIoChannelLayout layout; - - /// Ignoring hardware latency, this is the number of seconds it takes for - /// the last sample in a full buffer to be played. - /// After you call ::soundio_outstream_open, this value is replaced with the + /// For output streams, ignoring hardware latency, this is the number of + /// seconds it takes for the last sample in a full buffer to be played. + /// + /// For input streams, ignoring hardware latency, this is the number of + /// seconds it takes for a captured sample to become available for reading. + /// + /// After you call ::soundio_stream_open, this value is replaced with the /// actual software latency, as near to this value as possible. - /// On systems that support clearing the buffer, this defaults to a large - /// latency, potentially upwards of 2 seconds, with the understanding that - /// you will call ::soundio_outstream_clear_buffer when you want to reduce - /// the latency to 0. On systems that do not support clearing the buffer, - /// this defaults to a reasonable lower latency value. /// - /// On backends with high latencies (such as 2 seconds), `frame_count_min` - /// will be 0, meaning you don't have to fill the entire buffer. In this - /// case, the large buffer is there if you want it; you only have to fill - /// as much as you want. On backends like JACK, `frame_count_min` will be - /// equal to `frame_count_max` and if you don't fill that many frames, you - /// will get glitches. + /// For output streams, on systems that support clearing the buffer, this + /// defaults to a large latency, potentially upwards of 2 seconds, with the + /// understanding that you will call ::soundio_stream_clear_buffer when you + /// want to reduce the latency to 0. On systems that do not support clearing + /// the buffer, this defaults to a reasonable lower latency value. /// - /// If the device has unknown software latency min and max values, you may - /// still set this, but you might not get the value you requested. - /// For PulseAudio, if you set this value to non-default, it sets - /// `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and - /// `tlength`. + /// For output streams, on backends with high latencies (such as 2 seconds), + /// `frame_count_min` will be 0, meaning you don't have to fill the entire + /// buffer. In this case, the large buffer is there if you want it; you only + /// have to fill as much as you want. On backends like JACK, + ///`frame_count_min` will be equal to `frame_count_max` and if you don't + /// fill that many frames, you will get glitches. + /// + /// If the device has unknown software latency min and + /// max values, you may still set this, but you might not get the value you + /// requested. + /// + /// For PulseAudio output streams, if you set this value to non-default, it + /// sets `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` + /// and `tlength`. + /// + /// For input streams, a higher value means less CPU usage. Defaults to a + /// large value, potentially upwards of 2 seconds. For PulseAudio, if you + /// set this value to non-default, it sets `PA_STREAM_ADJUST_LATENCY` and + /// is the value used for `fragsize`. /// /// For JACK, this value is always equal to - /// SoundIoDevice::software_latency_current of the device. + /// SoundIoDevice::software_latency_current. double software_latency; /// Defaults to NULL. Put whatever you want here. void *userdata; - /// In this callback, you call ::soundio_outstream_begin_write and - /// ::soundio_outstream_end_write as many times as necessary to write + /// In this callback, for output streams, call ::soundio_stream_begin_write + /// and ::soundio_stream_end_write as many times as necessary to write /// at minimum `frame_count_min` frames and at maximum `frame_count_max` /// frames. `frame_count_max` will always be greater than 0. Note that you /// should write as many frames as you can; `frame_count_min` might be 0 and /// you can still get a buffer underflow if you always write /// `frame_count_min` frames. /// + /// In this callback, for input streams, call ::soundio_stream_begin_read + /// and ::soundio_stream_end_read as many times as necessary to read at + /// minimum `frame_count_min` frames and at maximum `frame_count_max` + /// frames. If you return from read_callback without having read + /// `frame_count_min`, the frames will be dropped. `frame_count_max` is how + /// many frames are available to read. + /// /// For Dummy, ALSA, and PulseAudio, `frame_count_min` will be 0. For JACK /// and CoreAudio `frame_count_min` will be equal to `frame_count_max`. /// @@ -547,106 +609,31 @@ struct SoundIoOutStream { /// for a long time. This includes all I/O functions (disk, TTY, network), /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select, /// pthread_join, pthread_cond_wait, etc. - void (*write_callback)(struct SoundIoOutStream *, + void (*audio_callback)(struct SoundIoStream *, int frame_count_min, int frame_count_max); + /// This callback is for output streams. /// This optional callback happens when the sound device runs out of - /// buffered audio data to play. After this occurs, the outstream waits + /// buffered audio data to play. After this occurs, the stream waits /// until the buffer is full to resume playback. - /// This is called from the SoundIoOutStream::write_callback thread context. - void (*underflow_callback)(struct SoundIoOutStream *); - /// Optional callback. `err` is always SoundIoErrorStreaming. - /// SoundIoErrorStreaming is an unrecoverable error. The stream is in an - /// invalid state and must be destroyed. - /// If you do not supply error_callback, the default callback will print - /// a message to stderr and then call `abort`. - /// This is called from the SoundIoOutStream::write_callback thread context. - void (*error_callback)(struct SoundIoOutStream *, int err); - - /// Optional: Name of the stream. Defaults to "SoundIoOutStream" - /// PulseAudio uses this for the stream name. - /// JACK uses this for the client name of the client that connects when you - /// open the stream. - /// WASAPI uses this for the session display name. - /// Must not contain a colon (":"). - const char *name; - - /// Optional: Hint that this output stream is nonterminal. This is used by - /// JACK and it means that the output stream data originates from an input - /// stream. Defaults to `false`. - bool non_terminal_hint; - - - /// computed automatically when you call ::soundio_outstream_open - int bytes_per_frame; - /// computed automatically when you call ::soundio_outstream_open - int bytes_per_sample; - - /// If setting the channel layout fails for some reason, this field is set - /// to an error code. Possible error codes are: - /// * #SoundIoErrorIncompatibleDevice - int layout_error; -}; - -/// The size of this struct is not part of the API or ABI. -struct SoundIoInStream { - /// Populated automatically when you call ::soundio_outstream_create. - struct SoundIoDevice *device; - - /// Defaults to #SoundIoFormatFloat32NE, followed by the first one - /// supported. - enum SoundIoFormat format; - - /// Sample rate is the number of frames per second. - /// Defaults to max(sample_rate_min, min(sample_rate_max, 48000)) - int sample_rate; - - /// Defaults to Stereo, if available, followed by the first layout - /// supported. - struct SoundIoChannelLayout layout; - - /// Ignoring hardware latency, this is the number of seconds it takes for a - /// captured sample to become available for reading. - /// After you call ::soundio_instream_open, this value is replaced with the - /// actual software latency, as near to this value as possible. - /// A higher value means less CPU usage. Defaults to a large value, - /// potentially upwards of 2 seconds. - /// If the device has unknown software latency min and max values, you may - /// still set this, but you might not get the value you requested. - /// For PulseAudio, if you set this value to non-default, it sets - /// `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. - /// For JACK, this value is always equal to - /// SoundIoDevice::software_latency_current - double software_latency; - - /// Defaults to NULL. Put whatever you want here. - void *userdata; - /// In this function call ::soundio_instream_begin_read and - /// ::soundio_instream_end_read as many times as necessary to read at - /// minimum `frame_count_min` frames and at maximum `frame_count_max` - /// frames. If you return from read_callback without having read - /// `frame_count_min`, the frames will be dropped. `frame_count_max` is how - /// many frames are available to read. - /// - /// The code in the supplied function must be suitable for real-time - /// execution. That means that it cannot call functions that might block - /// for a long time. This includes all I/O functions (disk, TTY, network), - /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select, - /// pthread_join, pthread_cond_wait, etc. - void (*read_callback)(struct SoundIoInStream *, int frame_count_min, int frame_count_max); + /// This is called from the SoundIoStream::write_callback thread context. + void (*underflow_callback)(struct SoundIoStream *); + /// This function is for input streams. /// This optional callback happens when the sound device buffer is full, /// yet there is more captured audio to put in it. /// This is never fired for PulseAudio. - /// This is called from the SoundIoInStream::read_callback thread context. - void (*overflow_callback)(struct SoundIoInStream *); - /// Optional callback. `err` is always SoundIoErrorStreaming. - /// SoundIoErrorStreaming is an unrecoverable error. The stream is in an + /// This is called from the SoundIoStream::read_callback thread context. + void (*overflow_callback)(struct SoundIoStream *); + /// This callback is for both input and output streams. + /// Optional callback. `err` is always #SoundIoErrorStreaming. + /// #SoundIoErrorStreaming is an unrecoverable error. The stream is in an /// invalid state and must be destroyed. /// If you do not supply `error_callback`, the default callback will print - /// a message to stderr and then abort(). - /// This is called from the SoundIoInStream::read_callback thread context. - void (*error_callback)(struct SoundIoInStream *, int err); + /// a message to stderr and then call `abort`. + /// This is called from the SoundIoStream::write_callback or + /// SoundIoStream::read_callback thread context. + void (*error_callback)(struct SoundIoStream *, int err); - /// Optional: Name of the stream. Defaults to "SoundIoInStream"; + /// Optional: Name of the stream. Defaults to "SoundIoStream" /// PulseAudio uses this for the stream name. /// JACK uses this for the client name of the client that connects when you /// open the stream. @@ -655,21 +642,40 @@ struct SoundIoInStream { const char *name; /// Optional: Hint that this input stream is nonterminal. This is used by - /// JACK and it means that the data received by the stream will be - /// passed on or made available to another stream. Defaults to `false`. - bool non_terminal_hint; + /// JACK. It means that the data received by the stream will be + /// passed on or made available to another stream. + /// Defaults to `false`. + bool input_non_terminal_hint; - /// computed automatically when you call ::soundio_instream_open - int bytes_per_frame; - /// computed automatically when you call ::soundio_instream_open - int bytes_per_sample; + /// Optional: Hint that this output stream is nonterminal. This is used by + /// JACK. It means that the output stream data originates from an input + /// stream. + /// Defaults to `false`. + bool output_non_terminal_hint; - /// If setting the channel layout fails for some reason, this field is set - /// to an error code. Possible error codes are: #SoundIoErrorIncompatibleDevice - int layout_error; + /// computed automatically when you call ::soundio_stream_open + int input_bytes_per_frame; + + /// computed automatically when you call ::soundio_stream_open + int output_bytes_per_frame; + + /// computed automatically when you call ::soundio_stream_open + int input_bytes_per_sample; + + /// computed automatically when you call ::soundio_stream_open + int output_bytes_per_sample; + + /// If setting the input channel layout fails for some reason, this field + /// is set to an error code. Possible error codes are: + /// * #SoundIoErrorIncompatibleDevice + int input_layout_error; + + /// If setting the output channel layout fails for some reason, this field + /// is set to an error code. Possible error codes are: + /// * #SoundIoErrorIncompatibleDevice + int output_layout_error; }; -// Main Context /// Create a SoundIo context. You may create multiple instances of this to /// connect to multiple backends. Sets all fields to defaults. @@ -760,12 +766,10 @@ SOUNDIO_EXPORT void soundio_wakeup(struct SoundIo *soundio); /// SoundIo::on_devices_change callback. /// /// This can be called from any thread context except for -/// SoundIoOutStream::write_callback and SoundIoInStream::read_callback +/// SoundIoStream::write_callback and SoundIoStream::read_callback SOUNDIO_EXPORT void soundio_force_device_scan(struct SoundIo *soundio); -// Channel Layouts - /// Returns whether the channel count field and each channel id matches in /// the supplied channel layouts. SOUNDIO_EXPORT bool soundio_channel_layout_equal( @@ -808,8 +812,6 @@ SOUNDIO_EXPORT const struct SoundIoChannelLayout *soundio_best_matching_channel_ SOUNDIO_EXPORT void soundio_sort_channel_layouts(struct SoundIoChannelLayout *layouts, int layout_count); -// Sample Formats - /// Returns -1 on invalid format. SOUNDIO_EXPORT int soundio_get_bytes_per_sample(enum SoundIoFormat format); @@ -831,15 +833,6 @@ SOUNDIO_EXPORT const char * soundio_format_string(enum SoundIoFormat format); -// Devices - -/// When you call ::soundio_flush_events, a snapshot of all device state is -/// saved and these functions merely access the snapshot data. When you want -/// to check for new devices, call ::soundio_flush_events. Or you can call -/// ::soundio_wait_events to block until devices change. If an error occurs -/// scanning devices in a background thread, SoundIo::on_backend_disconnect is called -/// with the error code. - /// Get the number of input devices. /// Returns -1 if you never called ::soundio_flush_events. SOUNDIO_EXPORT int soundio_input_device_count(struct SoundIo *soundio); @@ -905,63 +898,76 @@ SOUNDIO_EXPORT int soundio_device_nearest_sample_rate(struct SoundIoDevice *devi -// Output Streams -/// Allocates memory and sets defaults. Next you should fill out the struct fields -/// and then call ::soundio_outstream_open. Sets all fields to defaults. +/// Allocates memory and sets defaults. Next you should complete the struct fields +/// and then call ::soundio_stream_open. Sets all fields to defaults. /// Returns `NULL` if and only if memory could not be allocated. -/// See also ::soundio_outstream_destroy -SOUNDIO_EXPORT struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device); -/// You may not call this function from the SoundIoOutStream::write_callback thread context. -SOUNDIO_EXPORT void soundio_outstream_destroy(struct SoundIoOutStream *outstream); +/// See also ::soundio_stream_destroy +SOUNDIO_EXPORT struct SoundIoStream *soundio_stream_create( + struct SoundIoDevice *input_device, struct SoundIoDevice *output_device); +/// You may not call this function from the SoundIoStream::write_callback or +/// SoundIoStream::read_callback thread context. +SOUNDIO_EXPORT void soundio_stream_destroy(struct SoundIoStream *stream); -/// After you call this function, SoundIoOutStream:software_latency is set to the correct +/// After you call this function, SoundIoStream:software_latency is set to the correct /// value. -/// The next thing to do is call ::soundio_instream_start. -/// If this function returns an error, the outstream is in an invalid state and -/// you must call ::soundio_outstream_destroy on it. +/// The next thing to do is call ::soundio_stream_start. +/// If this function returns an error, the stream is in an invalid state and +/// you must call ::soundio_stream_destroy on it. +/// +/// If you call this function without specifying SoundIoStream::layout, +/// ::soundio_sort_channel_layouts may be called on SoundIoDevice::layouts of +/// SoundIoStream::input_device or SoundIoStream::output_device in order to +/// select a default channel layout. /// /// Possible errors: /// * #SoundIoErrorInvalid -/// * SoundIoDevice::aim is not #SoundIoDeviceAimOutput -/// * SoundIoOutStream::format is not valid -/// * SoundIoOutStream::channel_count is greater than #SOUNDIO_MAX_CHANNELS +/// * SoundIoStream::input_device is not an input device. +/// * SoundIoStream::output_device is not an output device. +/// * SoundIoStream::format is not valid +/// * SoundIoStream::channel_count is greater than #SOUNDIO_MAX_CHANNELS +/// * SoundIoStream::input_device and SoundIoStream::output_device do not +/// have equal ids. /// * #SoundIoErrorNoMem /// * #SoundIoErrorOpeningDevice /// * #SoundIoErrorBackendDisconnected /// * #SoundIoErrorSystemResources /// * #SoundIoErrorNoSuchClient - when JACK returns `JackNoSuchClient` /// * #SoundIoErrorOpeningDevice -/// * #SoundIoErrorIncompatibleBackend - SoundIoOutStream::channel_count is +/// * #SoundIoErrorIncompatibleBackend - SoundIoStream::channel_count is /// greater than the number of channels the backend can handle. /// * #SoundIoErrorIncompatibleDevice - stream parameters requested are not -/// compatible with the chosen device. -SOUNDIO_EXPORT int soundio_outstream_open(struct SoundIoOutStream *outstream); +/// compatible with the chosen devices. +SOUNDIO_EXPORT int soundio_stream_open(struct SoundIoStream *stream); -/// After you call this function, SoundIoOutStream::write_callback will be called. +/// After you this function is called, SoundIoStream::write_callback will be +/// called for output streams and SoundIoStream::read_callback will be called +/// for input streams. /// /// Possible errors: /// * #SoundIoErrorStreaming /// * #SoundIoErrorNoMem /// * #SoundIoErrorSystemResources /// * #SoundIoErrorBackendDisconnected -SOUNDIO_EXPORT int soundio_outstream_start(struct SoundIoOutStream *outstream); +/// * #SoundIoErrorOpeningDevice +SOUNDIO_EXPORT int soundio_stream_start(struct SoundIoStream *stream); +/// This function is for output streams. /// Call this function when you are ready to begin writing to the device buffer. -/// * `outstream` - (in) The output stream you want to write to. +/// * `stream` - (in) The output stream you want to write to. /// * `areas` - (out) The memory addresses you can write data to, one per /// channel. It is OK to modify the pointers if that helps you iterate. /// * `frame_count` - (in/out) Provide the number of frames you want to write. /// Returned will be the number of frames you can actually write, which is /// also the number of frames that will be written when you call -/// ::soundio_outstream_end_write. The value returned will always be less +/// ::soundio_stream_end_write. The value returned will always be less /// than or equal to the value provided. /// It is your responsibility to call this function exactly as many times as /// necessary to meet the `frame_count_min` and `frame_count_max` criteria from -/// SoundIoOutStream::write_callback. -/// You must call this function only from the SoundIoOutStream::write_callback thread context. +/// SoundIoStream::write_callback. +/// You must call this function only from the SoundIoStream::write_callback thread context. /// After calling this function, write data to `areas` and then call -/// ::soundio_outstream_end_write. -/// If this function returns an error, do not call ::soundio_outstream_end_write. +/// ::soundio_stream_end_write. +/// If this function returns an error, do not call ::soundio_stream_end_write. /// /// Possible errors: /// * #SoundIoErrorInvalid @@ -970,116 +976,31 @@ SOUNDIO_EXPORT int soundio_outstream_start(struct SoundIoOutStream *outstream); /// * function called too many times without respecting `frame_count_max` /// * #SoundIoErrorStreaming /// * #SoundIoErrorUnderflow - an underflow caused this call to fail. You might -/// also get a SoundIoOutStream::underflow_callback, and you might not get +/// also get a SoundIoStream::underflow_callback, and you might not get /// this error code when an underflow occurs. Unlike #SoundIoErrorStreaming, -/// the outstream is still in a valid state and streaming can continue. +/// the stream is still in a valid state and streaming can continue. /// * #SoundIoErrorIncompatibleDevice - in rare cases it might just now /// be discovered that the device uses non-byte-aligned access, in which /// case this error code is returned. -SOUNDIO_EXPORT int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, +SOUNDIO_EXPORT int soundio_stream_begin_write(struct SoundIoStream *stream, struct SoundIoChannelArea **areas, int *frame_count); -/// Commits the write that you began with ::soundio_outstream_begin_write. -/// You must call this function only from the SoundIoOutStream::write_callback thread context. +/// This function is for output streams. +/// Commits the write that you began with ::soundio_stream_begin_write. +/// You must call this function only from the SoundIoStream::write_callback thread context. /// /// Possible errors: /// * #SoundIoErrorStreaming /// * #SoundIoErrorUnderflow - an underflow caused this call to fail. You might -/// also get a SoundIoOutStream::underflow_callback, and you might not get +/// also get a SoundIoStream::underflow_callback, and you might not get /// this error code when an underflow occurs. Unlike #SoundIoErrorStreaming, -/// the outstream is still in a valid state and streaming can continue. -SOUNDIO_EXPORT int soundio_outstream_end_write(struct SoundIoOutStream *outstream); - -/// Clears the output stream buffer. -/// This function can be called from any thread. -/// This function can be called regardless of whether the outstream is paused -/// or not. -/// Some backends do not support clearing the buffer. On these backends this -/// function will return SoundIoErrorIncompatibleBackend. -/// Some devices do not support clearing the buffer. On these devices this -/// function might return SoundIoErrorIncompatibleDevice. -/// Possible errors: -/// -/// * #SoundIoErrorStreaming -/// * #SoundIoErrorIncompatibleBackend -/// * #SoundIoErrorIncompatibleDevice -SOUNDIO_EXPORT int soundio_outstream_clear_buffer(struct SoundIoOutStream *outstream); - -/// If the underlying device supports pausing, this pauses the stream. -/// SoundIoOutStream::write_callback may be called a few more times if the -/// buffer is not full. -/// Pausing might put the hardware into a low power state which is ideal if your -/// software is silent for some time. -/// This function may be called from any thread. -/// Pausing when already paused or unpausing when already unpaused has no -/// effect and always returns SoundIoErrorNone. -/// -/// Possible errors: -/// * #SoundIoErrorBackendDisconnected -/// * #SoundIoErrorStreaming -/// * #SoundIoErrorIncompatibleDevice - device does not support -/// pausing/unpausing. This error code might not be returned even if the -/// device does not support pausing/unpausing. -/// * #SoundIoErrorInvalid - outstream not opened and started -SOUNDIO_EXPORT int soundio_outstream_pause(struct SoundIoOutStream *outstream, bool pause); - -/// Obtain the total number of seconds that the next frame written after the -/// last frame written with ::soundio_outstream_end_write will take to become -/// audible. This includes both software and hardware latency. In other words, -/// if you call this function directly after calling ::soundio_outstream_end_write, -/// this gives you the number of seconds that the next frame written will take -/// to become audible. -/// -/// This function must be called only from within SoundIoOutStream::write_callback. -/// -/// Possible errors: -/// * #SoundIoErrorStreaming -SOUNDIO_EXPORT int soundio_outstream_get_latency(struct SoundIoOutStream *outstream, - double *out_latency); - - - -// Input Streams -/// Allocates memory and sets defaults. Next you should fill out the struct fields -/// and then call ::soundio_instream_open. Sets all fields to defaults. -/// Returns `NULL` if and only if memory could not be allocated. -/// See also ::soundio_instream_destroy -SOUNDIO_EXPORT struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device); -/// You may not call this function from SoundIoInStream::read_callback. -SOUNDIO_EXPORT void soundio_instream_destroy(struct SoundIoInStream *instream); - -/// After you call this function, SoundIoInStream::software_latency is set to the correct -/// value. -/// The next thing to do is call ::soundio_instream_start. -/// If this function returns an error, the instream is in an invalid state and -/// you must call ::soundio_instream_destroy on it. -/// -/// Possible errors: -/// * #SoundIoErrorInvalid -/// * device aim is not #SoundIoDeviceAimInput -/// * format is not valid -/// * requested layout channel count > #SOUNDIO_MAX_CHANNELS -/// * #SoundIoErrorOpeningDevice -/// * #SoundIoErrorNoMem -/// * #SoundIoErrorBackendDisconnected -/// * #SoundIoErrorSystemResources -/// * #SoundIoErrorNoSuchClient -/// * #SoundIoErrorIncompatibleBackend -/// * #SoundIoErrorIncompatibleDevice -SOUNDIO_EXPORT int soundio_instream_open(struct SoundIoInStream *instream); - -/// After you call this function, SoundIoInStream::read_callback will be called. -/// -/// Possible errors: -/// * #SoundIoErrorBackendDisconnected -/// * #SoundIoErrorStreaming -/// * #SoundIoErrorOpeningDevice -/// * #SoundIoErrorSystemResources -SOUNDIO_EXPORT int soundio_instream_start(struct SoundIoInStream *instream); +/// the stream is still in a valid state and streaming can continue. +SOUNDIO_EXPORT int soundio_stream_end_write(struct SoundIoStream *stream); +/// This function is for input streams. /// Call this function when you are ready to begin reading from the device /// buffer. -/// * `instream` - (in) The input stream you want to read from. +/// * `stream` - (in) The input stream you want to read from. /// * `areas` - (out) The memory addresses you can read data from. It is OK /// to modify the pointers if that helps you iterate. There might be a "hole" /// in the buffer. To indicate this, `areas` will be `NULL` and `frame_count` @@ -1087,15 +1008,15 @@ SOUNDIO_EXPORT int soundio_instream_start(struct SoundIoInStream *instream); /// * `frame_count` - (in/out) - Provide the number of frames you want to read; /// returns the number of frames you can actually read. The returned value /// will always be less than or equal to the provided value. If the provided -/// value is less than `frame_count_min` from SoundIoInStream::read_callback this function +/// value is less than `frame_count_min` from SoundIoStream::read_callback this function /// returns with #SoundIoErrorInvalid. /// It is your responsibility to call this function no more and no fewer than the /// correct number of times according to the `frame_count_min` and -/// `frame_count_max` criteria from SoundIoInStream::read_callback. -/// You must call this function only from the SoundIoInStream::read_callback thread context. +/// `frame_count_max` criteria from SoundIoStream::read_callback. +/// You must call this function only from the SoundIoStream::read_callback thread context. /// After calling this function, read data from `areas` and then use -/// ::soundio_instream_end_read` to actually remove the data from the buffer -/// and move the read index forward. ::soundio_instream_end_read should not be +/// ::soundio_stream_end_read` to actually remove the data from the buffer +/// and move the read index forward. ::soundio_stream_end_read should not be /// called if the buffer is empty (`frame_count` == 0), but it should be called /// if there is a hole. /// @@ -1106,77 +1027,77 @@ SOUNDIO_EXPORT int soundio_instream_start(struct SoundIoInStream *instream); /// * #SoundIoErrorIncompatibleDevice - in rare cases it might just now /// be discovered that the device uses non-byte-aligned access, in which /// case this error code is returned. -SOUNDIO_EXPORT int soundio_instream_begin_read(struct SoundIoInStream *instream, +SOUNDIO_EXPORT int soundio_stream_begin_read(struct SoundIoStream *stream, struct SoundIoChannelArea **areas, int *frame_count); + +/// This function is for input streams. /// This will drop all of the frames from when you called -/// ::soundio_instream_begin_read. -/// You must call this function only from the SoundIoInStream::read_callback thread context. +/// ::soundio_stream_begin_read. +/// You must call this function only from the SoundIoStream::read_callback thread context. /// You must call this function only after a successful call to -/// ::soundio_instream_begin_read. +/// ::soundio_stream_begin_read. /// /// Possible errors: /// * #SoundIoErrorStreaming -SOUNDIO_EXPORT int soundio_instream_end_read(struct SoundIoInStream *instream); +SOUNDIO_EXPORT int soundio_stream_end_read(struct SoundIoStream *stream); -/// If the underyling device supports pausing, this pauses the stream and -/// prevents SoundIoInStream::read_callback from being called. Otherwise this returns -/// #SoundIoErrorIncompatibleDevice. +/// Clears the stream hardware buffer. +/// This function can be called from any thread. +/// This function can be called regardless of whether the stream is paused. +/// Some backends do not support clearing the buffer. On these backends this +/// function will return SoundIoErrorIncompatibleBackend. +/// Some devices do not support clearing the buffer. On these devices this +/// function might return SoundIoErrorIncompatibleDevice. +/// +/// Possible errors: +/// +/// * #SoundIoErrorStreaming +/// * #SoundIoErrorIncompatibleBackend +/// * #SoundIoErrorIncompatibleDevice +SOUNDIO_EXPORT int soundio_stream_clear_buffer(struct SoundIoStream *stream); + +/// If the underlying device supports pausing, this pauses the stream. +/// SoundIoStream::write_callback may be called a few more times if the +/// buffer is not full. +/// SoundIoStream::read_callback will stop being called if this function +/// succeeds, however it may be called once more after the function returns +/// and/or may be in the middle of being called when this function returns. +/// Pausing might put the hardware into a low power state which is ideal if your +/// software is silent for some time. /// This function may be called from any thread. /// Pausing when already paused or unpausing when already unpaused has no -/// effect and always returns SoundIoErrorNone. +/// effect and always returns #SoundIoErrorNone. /// /// Possible errors: /// * #SoundIoErrorBackendDisconnected /// * #SoundIoErrorStreaming -/// * #SoundIoErrorIncompatibleDevice - device does not support pausing/unpausing -SOUNDIO_EXPORT int soundio_instream_pause(struct SoundIoInStream *instream, bool pause); +/// * #SoundIoErrorIncompatibleDevice - device does not support +/// pausing/unpausing. This error code might not be returned even if the +/// device does not support pausing/unpausing. +/// * #SoundIoErrorInvalid - stream not opened and started +SOUNDIO_EXPORT int soundio_stream_pause(struct SoundIoStream *stream, bool pause); -/// Obtain the number of seconds that the next frame of sound being -/// captured will take to arrive in the buffer, plus the amount of time that is -/// represented in the buffer. This includes both software and hardware latency. +/// For output streams, obtain the total number of seconds that the next frame +/// written after the last frame written with ::soundio_stream_end_write will +/// take to become audible. This includes both software and hardware latency. +/// In other words, if you call this function directly after calling +/// ::soundio_stream_end_write, this gives you the number of seconds that the +/// next frame written will take to become audible. /// -/// This function must be called only from within SoundIoInStream::read_callback. +/// For input streams, obtain the number of seconds that the next frame of +/// sound being captured will take to arrive in the buffer, plus the amount of +/// time that is represented in the buffer. This includes both software and +/// hardware latency. +/// +/// For output streams, this function must be called only from within +/// SoundIoStream::write_callback. +/// +/// For input streams, this function must be called only from within +/// SoundIoStream::read_callback. /// /// Possible errors: /// * #SoundIoErrorStreaming -SOUNDIO_EXPORT int soundio_instream_get_latency(struct SoundIoInStream *instream, +SOUNDIO_EXPORT int soundio_stream_get_latency(struct SoundIoStream *stream, double *out_latency); - -/// A ring buffer is a single-reader single-writer lock-free fixed-size queue. -/// libsoundio ring buffers use memory mapping techniques to enable a -/// contiguous buffer when reading or writing across the boundary of the ring -/// buffer's capacity. -struct SoundIoRingBuffer; -/// `requested_capacity` in bytes. -/// Returns `NULL` if and only if memory could not be allocated. -/// Use ::soundio_ring_buffer_capacity to get the actual capacity, which might -/// be greater for alignment purposes. -/// See also ::soundio_ring_buffer_destroy -SOUNDIO_EXPORT struct SoundIoRingBuffer *soundio_ring_buffer_create(struct SoundIo *soundio, int requested_capacity); -SOUNDIO_EXPORT void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *ring_buffer); - -/// When you create a ring buffer, capacity might be more than the requested -/// capacity for alignment purposes. This function returns the actual capacity. -SOUNDIO_EXPORT int soundio_ring_buffer_capacity(struct SoundIoRingBuffer *ring_buffer); - -/// Do not write more than capacity. -SOUNDIO_EXPORT char *soundio_ring_buffer_write_ptr(struct SoundIoRingBuffer *ring_buffer); -/// `count` in bytes. -SOUNDIO_EXPORT void soundio_ring_buffer_advance_write_ptr(struct SoundIoRingBuffer *ring_buffer, int count); - -/// Do not read more than capacity. -SOUNDIO_EXPORT char *soundio_ring_buffer_read_ptr(struct SoundIoRingBuffer *ring_buffer); -/// `count` in bytes. -SOUNDIO_EXPORT void soundio_ring_buffer_advance_read_ptr(struct SoundIoRingBuffer *ring_buffer, int count); - -/// Returns how many bytes of the buffer is used, ready for reading. -SOUNDIO_EXPORT int soundio_ring_buffer_fill_count(struct SoundIoRingBuffer *ring_buffer); - -/// Returns how many bytes of the buffer is free, ready for writing. -SOUNDIO_EXPORT int soundio_ring_buffer_free_count(struct SoundIoRingBuffer *ring_buffer); - -/// Must be called by the writer. -SOUNDIO_EXPORT void soundio_ring_buffer_clear(struct SoundIoRingBuffer *ring_buffer); - #endif diff --git a/src/alsa.hpp b/src/alsa.hpp index cb6eda2..a98e4e1 100644 --- a/src/alsa.hpp +++ b/src/alsa.hpp @@ -43,8 +43,9 @@ struct SoundIoAlsa { bool emitted_shutdown_cb; }; -struct SoundIoOutStreamAlsa { - snd_pcm_t *handle; +struct SoundIoStreamAlsa { + snd_pcm_t *out_handle; + snd_pcm_t *in_handle; snd_pcm_chmap_t *chmap; int chmap_size; snd_pcm_uframes_t offset; @@ -58,27 +59,11 @@ struct SoundIoOutStreamAlsa { atomic_flag thread_exit_flag; int period_size; int write_frame_count; - bool is_paused; - atomic_flag clear_buffer_flag; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; -}; - -struct SoundIoInStreamAlsa { - snd_pcm_t *handle; - snd_pcm_chmap_t *chmap; - int chmap_size; - snd_pcm_uframes_t offset; - snd_pcm_access_t access; - int sample_buffer_size; - char *sample_buffer; - int poll_fd_count; - struct pollfd *poll_fds; - SoundIoOsThread *thread; - atomic_flag thread_exit_flag; - int period_size; int read_frame_count; bool is_paused; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; + atomic_flag clear_buffer_flag; + SoundIoChannelArea in_areas[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea out_areas[SOUNDIO_MAX_CHANNELS]; }; #endif diff --git a/src/dummy.cpp b/src/dummy.cpp index da5a871..9c5ed63 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -11,62 +11,62 @@ #include #include -static void playback_thread_run(void *arg) { - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)arg; - SoundIoOutStream *outstream = &os->pub; - SoundIoOutStreamDummy *osd = &os->backend_data.dummy; +static void stream_thread_run(void *arg) { + SoundIoStreamPrivate *os = (SoundIoStreamPrivate *)arg; + SoundIoStream *stream = &os->pub; + SoundIoStreamDummy *sd = &os->backend_data.dummy; - int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer); - int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes; - int free_frames = free_bytes / outstream->bytes_per_frame; - osd->frames_left = free_frames; - if (free_frames > 0) - outstream->write_callback(outstream, 0, free_frames); + if (stream->output_device) { + int out_fill_frames = (sd->out_buffer_write_offset - sd->out_buffer_read_offset); + int out_free_frames = sd->buffer_frame_count - out_fill_frames; + sd->out_frames_left = out_free_frames; + if (out_free_frames > 0) + stream->write_callback(stream, 0, out_free_frames); + } double start_time = soundio_os_get_time(); long frames_consumed = 0; - while (osd->abort_flag.test_and_set()) { + while (sd->abort_flag.test_and_set()) { double now = soundio_os_get_time(); double time_passed = now - start_time; double next_period = start_time + - ceil_dbl(time_passed / osd->period_duration) * osd->period_duration; + ceil_dbl(time_passed / sd->period_duration) * sd->period_duration; double relative_time = next_period - now; - soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time); - if (!osd->clear_buffer_flag.test_and_set()) { - soundio_ring_buffer_clear(&osd->ring_buffer); - int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer); - int free_frames = free_bytes / outstream->bytes_per_frame; - osd->frames_left = free_frames; - if (free_frames > 0) - outstream->write_callback(outstream, 0, free_frames); + soundio_os_cond_timed_wait(sd->cond, nullptr, relative_time); + if (!sd->clear_buffer_flag.test_and_set()) { + sd->out_buffer_write_offset.store(0); + sd->out_buffer_read_offset.store(0); + sd->in_buffer_write_offset.store(0); + sd->in_buffer_read_offset.store(0); + sd->out_frames_left = sd->buffer_frame_count; + assert(sd->buffer_frame_count > 0); + stream->write_callback(stream, 0, sd->buffer_frame_count); frames_consumed = 0; start_time = soundio_os_get_time(); continue; } - int fill_bytes = soundio_ring_buffer_fill_count(&osd->ring_buffer); - int fill_frames = fill_bytes / outstream->bytes_per_frame; - int free_bytes = soundio_ring_buffer_capacity(&osd->ring_buffer) - fill_bytes; - int free_frames = free_bytes / outstream->bytes_per_frame; + int out_fill_frames = (sd->out_buffer_write_offset - sd->out_buffer_read_offset); + int out_free_frames = sd->buffer_frame_count - out_fill_frames; double total_time = soundio_os_get_time() - start_time; - long total_frames = total_time * outstream->sample_rate; + long total_frames = total_time * stream->sample_rate; int frames_to_kill = total_frames - frames_consumed; int read_count = min(frames_to_kill, fill_frames); - int byte_count = read_count * outstream->bytes_per_frame; - soundio_ring_buffer_advance_read_ptr(&osd->ring_buffer, byte_count); + int byte_count = read_count * stream->bytes_per_frame; + soundio_ring_buffer_advance_read_ptr(&sd->ring_buffer, byte_count); frames_consumed += read_count; if (frames_to_kill > fill_frames) { - outstream->underflow_callback(outstream); - osd->frames_left = free_frames; + stream->underflow_callback(stream); + sd->out_frames_left = free_frames; if (free_frames > 0) - outstream->write_callback(outstream, 0, free_frames); + stream->write_callback(stream, 0, free_frames); frames_consumed = 0; start_time = soundio_os_get_time(); } else if (free_frames > 0) { - osd->frames_left = free_frames; - outstream->write_callback(outstream, 0, free_frames); + sd->out_frames_left = free_frames; + stream->write_callback(stream, 0, free_frames); } } } @@ -205,7 +205,7 @@ static int outstream_pause_dummy(struct SoundIoPrivate *si, struct SoundIoOutStr if (!osd->thread) { osd->abort_flag.test_and_set(); int err; - if ((err = soundio_os_thread_create(playback_thread_run, os, + if ((err = soundio_os_thread_create(stream_thread_run, os, soundio->emit_rtprio_warning, &osd->thread))) { return err; @@ -547,22 +547,16 @@ int soundio_dummy_init(SoundIoPrivate *si) { si->wakeup = wakeup_dummy; si->force_device_scan = force_device_scan_dummy; - si->outstream_open = outstream_open_dummy; - si->outstream_destroy = outstream_destroy_dummy; - si->outstream_start = outstream_start_dummy; - si->outstream_begin_write = outstream_begin_write_dummy; - si->outstream_end_write = outstream_end_write_dummy; - si->outstream_clear_buffer = outstream_clear_buffer_dummy; - si->outstream_pause = outstream_pause_dummy; - si->outstream_get_latency = outstream_get_latency_dummy; - - si->instream_open = instream_open_dummy; - si->instream_destroy = instream_destroy_dummy; - si->instream_start = instream_start_dummy; + si->stream_open = stream_open_dummy; + si->stream_destroy = stream_destroy_dummy; + si->stream_start = stream_start_dummy; + si->stream_begin_write = stream_begin_write_dummy; + si->stream_end_write = stream_end_write_dummy; si->instream_begin_read = instream_begin_read_dummy; si->instream_end_read = instream_end_read_dummy; - si->instream_pause = instream_pause_dummy; - si->instream_get_latency = instream_get_latency_dummy; + si->stream_clear_buffer = stream_clear_buffer_dummy; + si->stream_pause = stream_pause_dummy; + si->stream_get_latency = stream_get_latency_dummy; return 0; } diff --git a/src/dummy.hpp b/src/dummy.hpp index 4100a86..152b8b7 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -11,7 +11,6 @@ #include "soundio_private.h" #include "os.h" #include "atomics.hpp" -#include "ring_buffer.hpp" int soundio_dummy_init(struct SoundIoPrivate *si); @@ -23,30 +22,28 @@ struct SoundIoDummy { struct SoundIoDeviceDummy { }; -struct SoundIoOutStreamDummy { +struct SoundIoStreamDummy { struct SoundIoOsThread *thread; struct SoundIoOsCond *cond; atomic_flag abort_flag; double period_duration; + int buffer_frame_count; - int frames_left; + + int out_frames_left; int write_frame_count; - struct SoundIoRingBuffer ring_buffer; + atomic_long out_buffer_write_offset; + atomic_long out_buffer_read_offset; + + int in_frames_left; + int read_frame_count; + atomic_long in_buffer_write_offset; + atomic_long in_buffer_read_offset; + double playback_start_time; atomic_flag clear_buffer_flag; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; -}; - -struct SoundIoInStreamDummy { - struct SoundIoOsThread *thread; - struct SoundIoOsCond *cond; - atomic_flag abort_flag; - double period_duration; - int frames_left; - int read_frame_count; - int buffer_frame_count; - struct SoundIoRingBuffer ring_buffer; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea in_areas[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea out_areas[SOUNDIO_MAX_CHANNELS]; }; #endif diff --git a/src/jack.hpp b/src/jack.hpp index 13335ee..2472f57 100644 --- a/src/jack.hpp +++ b/src/jack.hpp @@ -45,30 +45,23 @@ struct SoundIoOutStreamJackPort { int dest_port_name_len; }; -struct SoundIoOutStreamJack { - jack_client_t *client; - int period_size; - int frames_left; - bool is_paused; - double hardware_latency; - SoundIoOutStreamJackPort ports[SOUNDIO_MAX_CHANNELS]; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; -}; - struct SoundIoInStreamJackPort { jack_port_t *dest_port; const char *source_port_name; int source_port_name_len; }; -struct SoundIoInStreamJack { +struct SoundIoStreamJack { jack_client_t *client; int period_size; int frames_left; + bool is_paused; double hardware_latency; - SoundIoInStreamJackPort ports[SOUNDIO_MAX_CHANNELS]; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; - char *buf_ptrs[SOUNDIO_MAX_CHANNELS]; + SoundIoOutStreamJackPort out_ports[SOUNDIO_MAX_CHANNELS]; + SoundIoOutStreamJackPort in_ports[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea in_areas[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea out_areas[SOUNDIO_MAX_CHANNELS]; + char *in_buf_ptrs[SOUNDIO_MAX_CHANNELS]; }; #endif diff --git a/src/pulseaudio.hpp b/src/pulseaudio.hpp index b400452..a9f099e 100644 --- a/src/pulseaudio.hpp +++ b/src/pulseaudio.hpp @@ -39,26 +39,24 @@ struct SoundIoPulseAudio { pa_proplist *props; }; -struct SoundIoOutStreamPulseAudio { +struct SoundIoStreamPulseAudio { pa_stream *stream; - atomic_bool stream_ready; - pa_buffer_attr buffer_attr; + atomic_bool out_stream_ready; + pa_buffer_attr out_buffer_attr; char *write_ptr; size_t write_byte_count; atomic_flag clear_buffer_flag; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; -}; -struct SoundIoInStreamPulseAudio { - pa_stream *stream; - atomic_bool stream_ready; - pa_buffer_attr buffer_attr; + atomic_bool in_stream_ready; + pa_buffer_attr in_buffer_attr; char *peek_buf; size_t peek_buf_index; size_t peek_buf_size; int peek_buf_frames_left; int read_frame_count; - SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS]; + + SoundIoChannelArea in_areas[SOUNDIO_MAX_CHANNELS]; + SoundIoChannelArea out_areas[SOUNDIO_MAX_CHANNELS]; }; #endif diff --git a/src/ring_buffer.cpp b/src/ring_buffer.cpp deleted file mode 100644 index 4d5859a..0000000 --- a/src/ring_buffer.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2015 Andrew Kelley - * - * This file is part of libsoundio, which is MIT licensed. - * See http://opensource.org/licenses/MIT - */ - -#include "ring_buffer.hpp" -#include "soundio.hpp" -#include "util.hpp" - -#include - -struct SoundIoRingBuffer *soundio_ring_buffer_create(struct SoundIo *soundio, int requested_capacity) { - SoundIoRingBuffer *rb = allocate(1); - - assert(requested_capacity > 0); - - if (!rb) { - soundio_ring_buffer_destroy(rb); - return nullptr; - } - - if (soundio_ring_buffer_init(rb, requested_capacity)) { - soundio_ring_buffer_destroy(rb); - return nullptr; - } - - return rb; -} - -void soundio_ring_buffer_destroy(struct SoundIoRingBuffer *rb) { - if (!rb) - return; - - soundio_ring_buffer_deinit(rb); - - free(rb); -} - -int soundio_ring_buffer_capacity(struct SoundIoRingBuffer *rb) { - return rb->capacity; -} - -char *soundio_ring_buffer_write_ptr(struct SoundIoRingBuffer *rb) { - return rb->mem.address + (rb->write_offset % rb->capacity); -} - -void soundio_ring_buffer_advance_write_ptr(struct SoundIoRingBuffer *rb, int count) { - rb->write_offset += count; - assert(soundio_ring_buffer_fill_count(rb) >= 0); -} - -char *soundio_ring_buffer_read_ptr(struct SoundIoRingBuffer *rb) { - return rb->mem.address + (rb->read_offset % rb->capacity); -} - -void soundio_ring_buffer_advance_read_ptr(struct SoundIoRingBuffer *rb, int count) { - rb->read_offset += count; - assert(soundio_ring_buffer_fill_count(rb) >= 0); -} - -int soundio_ring_buffer_fill_count(struct SoundIoRingBuffer *rb) { - int count = rb->write_offset - rb->read_offset; - assert(count >= 0); - assert(count <= rb->capacity); - return count; -} - -int soundio_ring_buffer_free_count(struct SoundIoRingBuffer *rb) { - return rb->capacity - soundio_ring_buffer_fill_count(rb); -} - -void soundio_ring_buffer_clear(struct SoundIoRingBuffer *rb) { - return rb->write_offset.store(rb->read_offset.load()); -} - -int soundio_ring_buffer_init(struct SoundIoRingBuffer *rb, int requested_capacity) { - int err; - if ((err = soundio_os_init_mirrored_memory(&rb->mem, requested_capacity))) - return err; - rb->write_offset = 0; - rb->read_offset = 0; - rb->capacity = rb->mem.capacity; - - return 0; -} - -void soundio_ring_buffer_deinit(struct SoundIoRingBuffer *rb) { - soundio_os_deinit_mirrored_memory(&rb->mem); -} diff --git a/src/ring_buffer.hpp b/src/ring_buffer.hpp deleted file mode 100644 index a34a322..0000000 --- a/src/ring_buffer.hpp +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2015 Andrew Kelley - * - * This file is part of libsoundio, which is MIT licensed. - * See http://opensource.org/licenses/MIT - */ - -#ifndef SOUNDIO_RING_BUFFER_HPP -#define SOUNDIO_RING_BUFFER_HPP - -#include "atomics.hpp" -#include "os.h" - -struct SoundIoRingBuffer { - SoundIoOsMirroredMemory mem; - atomic_long write_offset; - atomic_long read_offset; - int capacity; -}; - -int soundio_ring_buffer_init(struct SoundIoRingBuffer *rb, int requested_capacity); -void soundio_ring_buffer_deinit(struct SoundIoRingBuffer *rb); - -#endif diff --git a/src/soundio.cpp b/src/soundio.cpp index d6c017a..6423b80 100644 --- a/src/soundio.cpp +++ b/src/soundio.cpp @@ -63,6 +63,27 @@ static int (*backend_init_fns[])(SoundIoPrivate *) = { [SoundIoBackendDummy] = soundio_dummy_init, }; +static const SoundIoFormat prioritized_formats[] = { + SoundIoFormatFloat32NE, + SoundIoFormatFloat32FE, + SoundIoFormatS32NE, + SoundIoFormatS32FE, + SoundIoFormatU32NE, + SoundIoFormatU32FE, + SoundIoFormatFloat64NE, + SoundIoFormatFloat64FE, + SoundIoFormatS24NE, + SoundIoFormatS24FE, + SoundIoFormatU24NE, + SoundIoFormatU24FE, + SoundIoFormatS16NE, + SoundIoFormatS16FE, + SoundIoFormatU16NE, + SoundIoFormatU16FE, + SoundIoFormatS8, + SoundIoFormatU8, +}; + const char *soundio_strerror(int error) { switch ((enum SoundIoError)error) { case SoundIoErrorNone: return "(no error)"; @@ -254,22 +275,16 @@ void soundio_disconnect(struct SoundIo *soundio) { si->wakeup = nullptr; si->force_device_scan = nullptr; - si->outstream_open = nullptr; - si->outstream_destroy = nullptr; - si->outstream_start = nullptr; - si->outstream_begin_write = nullptr; - si->outstream_end_write = nullptr; - si->outstream_clear_buffer = nullptr; - si->outstream_pause = nullptr; - si->outstream_get_latency = nullptr; - - si->instream_open = nullptr; - si->instream_destroy = nullptr; - si->instream_start = nullptr; - si->instream_begin_read = nullptr; - si->instream_end_read = nullptr; - si->instream_pause = nullptr; - si->instream_get_latency = nullptr; + si->stream_open = nullptr; + si->stream_destroy = nullptr; + si->stream_start = nullptr; + si->stream_begin_write = nullptr; + si->stream_end_write = nullptr; + si->stream_begin_read = nullptr; + si->stream_end_read = nullptr; + si->stream_clear_buffer = nullptr; + si->stream_pause = nullptr; + si->stream_get_latency = nullptr; } void soundio_flush_events(struct SoundIo *soundio) { @@ -428,238 +443,216 @@ void soundio_force_device_scan(struct SoundIo *soundio) { si->force_device_scan(si); } -int soundio_outstream_begin_write(struct SoundIoOutStream *outstream, +int soundio_stream_begin_write(struct SoundIoStream *stream, SoundIoChannelArea **areas, int *frame_count) { - SoundIo *soundio = outstream->device->soundio; + SoundIo *soundio = stream->output_device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; if (*frame_count <= 0) return SoundIoErrorInvalid; - return si->outstream_begin_write(si, os, areas, frame_count); + return si->stream_begin_write(si, st, areas, frame_count); } -int soundio_outstream_end_write(struct SoundIoOutStream *outstream) { - SoundIo *soundio = outstream->device->soundio; +int soundio_stream_end_write(struct SoundIoStream *stream) { + SoundIo *soundio = stream->output_device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_end_write(si, os); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_end_write(si, st); } -static void default_outstream_error_callback(struct SoundIoOutStream *os, int err) { +static void default_stream_error_callback(struct SoundIoStream *s, int err) { soundio_panic("libsoundio: %s", soundio_strerror(err)); } -static void default_underflow_callback(struct SoundIoOutStream *outstream) { } +static void default_underflow_callback(struct SoundIoStream *stream) { } +static void default_overflow_callback(struct SoundIoStream *stream) { } -struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device) { - SoundIoOutStreamPrivate *os = allocate(1); - SoundIoOutStream *outstream = &os->pub; - if (!os) - return nullptr; - if (!device) +struct SoundIoStream *soundio_stream_create( + struct SoundIoDevice *input_device, struct SoundIoDevice *output_device) +{ + SoundIoStreamPrivate *st = allocate(1); + SoundIoStream *stream = &st->pub; + + if (!st) return nullptr; - outstream->device = device; - soundio_device_ref(device); + if (!input_device && !output_device) + return nullptr; - outstream->error_callback = default_outstream_error_callback; - outstream->underflow_callback = default_underflow_callback; + stream->input_device = input_device; + stream->output_device = output_device; - return outstream; + soundio_device_ref(input_device); + soundio_device_ref(output_device); + + stream->error_callback = default_stream_error_callback; + stream->underflow_callback = default_underflow_callback; + stream->overflow_callback = default_overflow_callback; + + return stream; } -int soundio_outstream_open(struct SoundIoOutStream *outstream) { - SoundIoDevice *device = outstream->device; +static enum SoundIoFormat get_default_stream_format(struct SoundIoDevice *device) { + for (int i = 0; i < array_length(prioritized_formats); i += 1) { + SoundIoFormat format = prioritized_formats[i]; + if (soundio_device_supports_format(device, format)) + return format; + } + return SoundIoFormatInvalid; +} - if (device->aim != SoundIoDeviceAimOutput) - return SoundIoErrorInvalid; +static int choose_default_stream_sample_rate(struct SoundIoStream *stream) { + static const int target_sample_rate = 44100; + if (stream->input_device && stream->output_device) { + int nearest_input = soundio_device_nearest_sample_rate(stream->input_device, target_sample_rate); + int nearest_output = soundio_device_nearest_sample_rate(stream->output_device, nearest_input); - if (device->probe_error) - return device->probe_error; + if (nearest_input == nearest_output) { + stream->sample_rate = nearest_input; + return 0; + } - if (outstream->layout.channel_count > SOUNDIO_MAX_CHANNELS) - return SoundIoErrorInvalid; - - if (outstream->format == SoundIoFormatInvalid) { - outstream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ? - SoundIoFormatFloat32NE : device->formats[0]; + return SoundIoErrorIncompatibleDevice; + } else if (stream->input_device) { + return soundio_device_nearest_sample_rate(stream->input_device, target_sample_rate); + } else if (stream->output_device) { + return soundio_device_nearest_sample_rate(stream->output_device, target_sample_rate); } - if (outstream->format <= SoundIoFormatInvalid) + return SoundIoErrorInvalid; +} + +static SoundIo *stream_soundio(SoundIoStream *stream) { + return stream->input_device ? stream->input_device->soundio : stream->output_device->soundio; +} + +int soundio_stream_open(struct SoundIoStream *stream) { + SoundIoDevice *input_device = stream->input_device; + SoundIoDevice *output_device = stream->output_device; + int err; + + if (input_device && output_device && strcmp(input_device->id, output_device->id) != 0) return SoundIoErrorInvalid; - if (!outstream->layout.channel_count) { - const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); - outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0]; + if (input_device) { + if (input_device->aim != SoundIoDeviceAimInput) + return SoundIoErrorInvalid; + if (stream->input_layout.channel_count > SOUNDIO_MAX_CHANNELS) + return SoundIoErrorInvalid; + if (stream->input_format < SoundIoFormatInvalid) + return SoundIoErrorInvalid; + if (input_device->probe_error) + return input_device->probe_error; + if (stream->input_format == SoundIoFormatInvalid) { + stream->input_format = get_default_stream_format(input_device); + if (stream->input_format == SoundIoFormatInvalid) + return SoundIoErrorIncompatibleDevice; + } + if (!stream->input_layout.channel_count) { + soundio_sort_channel_layouts(stream->input_device->layouts, stream->input_device->layout_count); + stream->input_layout = stream->input_device->layouts[0]; + } } - if (!outstream->sample_rate) - outstream->sample_rate = soundio_device_nearest_sample_rate(device, 48000); + if (output_device) { + if (output_device->aim != SoundIoDeviceAimOutput) + return SoundIoErrorInvalid; + if (stream->output_layout.channel_count > SOUNDIO_MAX_CHANNELS) + return SoundIoErrorInvalid; + if (stream->output_format < SoundIoFormatInvalid) + return SoundIoErrorInvalid; + if (output_device->probe_error) + return output_device->probe_error; + if (stream->output_format == SoundIoFormatInvalid) { + stream->output_format = get_default_stream_format(output_device); + if (stream->output_format == SoundIoFormatInvalid) + return SoundIoErrorIncompatibleDevice; + } + if (!stream->output_layout.channel_count) { + soundio_sort_channel_layouts(stream->output_device->layouts, stream->output_device->layout_count); + stream->output_layout = stream->output_device->layouts[0]; + } + } - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - outstream->bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, outstream->layout.channel_count); - outstream->bytes_per_sample = soundio_get_bytes_per_sample(outstream->format); + if (!stream->sample_rate) { + if ((err = choose_default_stream_sample_rate(stream))) { + return err; + } + } - SoundIo *soundio = device->soundio; + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + stream->input_bytes_per_frame = soundio_get_bytes_per_frame(stream->input_format, + stream->input_layout.channel_count); + stream->input_bytes_per_sample = soundio_get_bytes_per_sample(stream->input_format); + stream->output_bytes_per_frame = soundio_get_bytes_per_frame(stream->output_format, + stream->output_layout.channel_count); + stream->output_bytes_per_sample = soundio_get_bytes_per_sample(stream->output_format); + + SoundIo *soundio = stream_soundio(stream); SoundIoPrivate *si = (SoundIoPrivate *)soundio; - return si->outstream_open(si, os); + return si->stream_open(si, st); } -void soundio_outstream_destroy(SoundIoOutStream *outstream) { - if (!outstream) +void soundio_stream_destroy(SoundIoStream *stream) { + if (!stream) return; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - SoundIo *soundio = outstream->device->soundio; + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + SoundIo *soundio = stream->input_device ? stream->input_device->soundio : stream->output_device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - if (si->outstream_destroy) - si->outstream_destroy(si, os); + if (si->stream_destroy) + si->stream_destroy(si, st); - soundio_device_unref(outstream->device); - free(os); + soundio_device_unref(stream->input_device); + soundio_device_unref(stream->output_device); + free(st); } -int soundio_outstream_start(struct SoundIoOutStream *outstream) { - SoundIo *soundio = outstream->device->soundio; +int soundio_stream_start(struct SoundIoStream *stream) { + SoundIo *soundio = stream_soundio(stream); SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_start(si, os); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_start(si, st); } -int soundio_outstream_pause(struct SoundIoOutStream *outstream, bool pause) { - SoundIo *soundio = outstream->device->soundio; +int soundio_stream_pause(struct SoundIoStream *stream, bool pause) { + SoundIo *soundio = stream_soundio(stream); SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_pause(si, os, pause); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_pause(si, st, pause); } -int soundio_outstream_clear_buffer(struct SoundIoOutStream *outstream) { - SoundIo *soundio = outstream->device->soundio; +int soundio_stream_clear_buffer(struct SoundIoStream *stream) { + SoundIo *soundio = stream_soundio(stream); SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_clear_buffer(si, os); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_clear_buffer(si, st); } -int soundio_outstream_get_latency(struct SoundIoOutStream *outstream, double *out_latency) { - SoundIo *soundio = outstream->device->soundio; +int soundio_stream_get_latency(struct SoundIoStream *stream, double *out_latency) { + SoundIo *soundio = stream_soundio(stream); SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream; - return si->outstream_get_latency(si, os, out_latency); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_get_latency(si, st, out_latency); } -static void default_instream_error_callback(struct SoundIoInStream *is, int err) { - soundio_panic("libsoundio: %s", soundio_strerror(err)); -} - -static void default_overflow_callback(struct SoundIoInStream *instream) { } - -struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) { - SoundIoInStreamPrivate *is = allocate(1); - SoundIoInStream *instream = &is->pub; - - if (!is) - return nullptr; - if (!device) - return nullptr; - - instream->device = device; - soundio_device_ref(device); - - instream->error_callback = default_instream_error_callback; - instream->overflow_callback = default_overflow_callback; - - return instream; -} - -int soundio_instream_open(struct SoundIoInStream *instream) { - SoundIoDevice *device = instream->device; - if (device->aim != SoundIoDeviceAimInput) - return SoundIoErrorInvalid; - - if (instream->format <= SoundIoFormatInvalid) - return SoundIoErrorInvalid; - - if (instream->layout.channel_count > SOUNDIO_MAX_CHANNELS) - return SoundIoErrorInvalid; - - if (device->probe_error) - return device->probe_error; - - if (instream->format == SoundIoFormatInvalid) { - instream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ? - SoundIoFormatFloat32NE : device->formats[0]; - } - - if (!instream->layout.channel_count) { - const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo); - instream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0]; - } - - if (!instream->sample_rate) - instream->sample_rate = soundio_device_nearest_sample_rate(device, 48000); - - - instream->bytes_per_frame = soundio_get_bytes_per_frame(instream->format, instream->layout.channel_count); - instream->bytes_per_sample = soundio_get_bytes_per_sample(instream->format); - SoundIo *soundio = device->soundio; - SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_open(si, is); -} - -int soundio_instream_start(struct SoundIoInStream *instream) { - SoundIo *soundio = instream->device->soundio; - SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_start(si, is); -} - -void soundio_instream_destroy(struct SoundIoInStream *instream) { - if (!instream) - return; - - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - SoundIo *soundio = instream->device->soundio; - SoundIoPrivate *si = (SoundIoPrivate *)soundio; - - if (si->instream_destroy) - si->instream_destroy(si, is); - - soundio_device_unref(instream->device); - free(is); -} - -int soundio_instream_pause(struct SoundIoInStream *instream, bool pause) { - SoundIo *soundio = instream->device->soundio; - SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_pause(si, is, pause); -} - -int soundio_instream_begin_read(struct SoundIoInStream *instream, +int soundio_stream_begin_read(struct SoundIoStream *stream, struct SoundIoChannelArea **areas, int *frame_count) { - SoundIo *soundio = instream->device->soundio; + SoundIo *soundio = stream->input_device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_begin_read(si, is, areas, frame_count); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_begin_read(si, st, areas, frame_count); } -int soundio_instream_end_read(struct SoundIoInStream *instream) { - SoundIo *soundio = instream->device->soundio; +int soundio_stream_end_read(struct SoundIoStream *stream) { + SoundIo *soundio = stream->input_device->soundio; SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_end_read(si, is); -} - -int soundio_instream_get_latency(struct SoundIoInStream *instream, double *out_latency) { - SoundIo *soundio = instream->device->soundio; - SoundIoPrivate *si = (SoundIoPrivate *)soundio; - SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream; - return si->instream_get_latency(si, is, out_latency); + SoundIoStreamPrivate *st = (SoundIoStreamPrivate *)stream; + return si->stream_end_read(si, st); } void soundio_destroy_devices_info(SoundIoDevicesInfo *devices_info) { @@ -691,10 +684,10 @@ SoundIoBackend soundio_get_backend(struct SoundIo *soundio, int index) { return available_backends[index]; } -static bool layout_contains(const SoundIoChannelLayout *available_layouts, int available_layouts_count, +static bool layout_contains(const SoundIoChannelLayout *available_layouts, int available_layout_count, const SoundIoChannelLayout *target_layout) { - for (int i = 0; i < available_layouts_count; i += 1) { + for (int i = 0; i < available_layout_count; i += 1) { const SoundIoChannelLayout *available_layout = &available_layouts[i]; if (soundio_channel_layout_equal(target_layout, available_layout)) return true; @@ -703,12 +696,12 @@ static bool layout_contains(const SoundIoChannelLayout *available_layouts, int a } const struct SoundIoChannelLayout *soundio_best_matching_channel_layout( - const struct SoundIoChannelLayout *preferred_layouts, int preferred_layouts_count, - const struct SoundIoChannelLayout *available_layouts, int available_layouts_count) + const struct SoundIoChannelLayout *preferred_layouts, int preferred_layout_count, + const struct SoundIoChannelLayout *available_layouts, int available_layout_count) { - for (int i = 0; i < preferred_layouts_count; i += 1) { + for (int i = 0; i < preferred_layout_count; i += 1) { const SoundIoChannelLayout *preferred_layout = &preferred_layouts[i]; - if (layout_contains(available_layouts, available_layouts_count, preferred_layout)) + if (layout_contains(available_layouts, available_layout_count, preferred_layout)) return preferred_layout; } return nullptr; @@ -725,11 +718,11 @@ static int compare_layouts(const void *a, const void *b) { return 0; } -void soundio_sort_channel_layouts(struct SoundIoChannelLayout *layouts, int layouts_count) { +void soundio_sort_channel_layouts(struct SoundIoChannelLayout *layouts, int layout_count) { if (!layouts) return; - qsort(layouts, layouts_count, sizeof(SoundIoChannelLayout), compare_layouts); + qsort(layouts, layout_count, sizeof(SoundIoChannelLayout), compare_layouts); } void soundio_device_sort_channel_layouts(struct SoundIoDevice *device) { diff --git a/src/soundio.hpp b/src/soundio.hpp index 5751270..cdcafb6 100644 --- a/src/soundio.hpp +++ b/src/soundio.hpp @@ -71,42 +71,23 @@ union SoundIoDeviceBackendData { SoundIoDeviceDummy dummy; }; -union SoundIoOutStreamBackendData { +union SoundIoStreamBackendData { #ifdef SOUNDIO_HAVE_JACK - SoundIoOutStreamJack jack; + SoundIoStreamJack jack; #endif #ifdef SOUNDIO_HAVE_PULSEAUDIO - SoundIoOutStreamPulseAudio pulseaudio; + SoundIoStreamPulseAudio pulseaudio; #endif #ifdef SOUNDIO_HAVE_ALSA - SoundIoOutStreamAlsa alsa; + SoundIoStreamAlsa alsa; #endif #ifdef SOUNDIO_HAVE_COREAUDIO - SoundIoOutStreamCoreAudio coreaudio; + SoundIoStreamCoreAudio coreaudio; #endif #ifdef SOUNDIO_HAVE_WASAPI - SoundIoOutStreamWasapi wasapi; + SoundIoStreamWasapi wasapi; #endif - SoundIoOutStreamDummy dummy; -}; - -union SoundIoInStreamBackendData { -#ifdef SOUNDIO_HAVE_JACK - SoundIoInStreamJack jack; -#endif -#ifdef SOUNDIO_HAVE_PULSEAUDIO - SoundIoInStreamPulseAudio pulseaudio; -#endif -#ifdef SOUNDIO_HAVE_ALSA - SoundIoInStreamAlsa alsa; -#endif -#ifdef SOUNDIO_HAVE_COREAUDIO - SoundIoInStreamCoreAudio coreaudio; -#endif -#ifdef SOUNDIO_HAVE_WASAPI - SoundIoInStreamWasapi wasapi; -#endif - SoundIoInStreamDummy dummy; + SoundIoStreamDummy dummy; }; struct SoundIoDevicesInfo { @@ -117,14 +98,9 @@ struct SoundIoDevicesInfo { int default_input_index; }; -struct SoundIoOutStreamPrivate { - SoundIoOutStream pub; - SoundIoOutStreamBackendData backend_data; -}; - -struct SoundIoInStreamPrivate { - SoundIoInStream pub; - SoundIoInStreamBackendData backend_data; +struct SoundIoStreamPrivate { + SoundIoStream pub; + SoundIoStreamBackendData backend_data; }; struct SoundIoPrivate { @@ -139,25 +115,18 @@ struct SoundIoPrivate { void (*wakeup)(struct SoundIoPrivate *); void (*force_device_scan)(struct SoundIoPrivate *); - int (*outstream_open)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - void (*outstream_destroy)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - int (*outstream_start)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - int (*outstream_begin_write)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, + int (*stream_open)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + void (*stream_destroy)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + int (*stream_start)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + int (*stream_begin_write)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *, SoundIoChannelArea **out_areas, int *out_frame_count); - int (*outstream_end_write)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - int (*outstream_clear_buffer)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *); - int (*outstream_pause)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, bool pause); - int (*outstream_get_latency)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *, double *out_latency); - - - int (*instream_open)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); - void (*instream_destroy)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); - int (*instream_start)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); - int (*instream_begin_read)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, + int (*stream_end_write)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + int (*stream_begin_read)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *, SoundIoChannelArea **out_areas, int *out_frame_count); - int (*instream_end_read)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *); - int (*instream_pause)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, bool pause); - int (*instream_get_latency)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *, double *out_latency); + int (*stream_end_read)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + int (*stream_clear_buffer)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *); + int (*stream_pause)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *, bool pause); + int (*stream_get_latency)(struct SoundIoPrivate *, struct SoundIoStreamPrivate *, double *out_latency); SoundIoBackendData backend_data; };