From fbc7318268b3e7a12ad160ee9a043d371066a5f1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 13 Aug 2015 22:54:15 -0700 Subject: [PATCH] remove the concept of period duration from the API also more progress on WASAPI --- README.md | 5 +- example/sio_list_devices.c | 12 +- example/sio_microphone.c | 4 +- soundio/soundio.h | 100 ++++++-------- src/alsa.cpp | 76 ++++------- src/coreaudio.cpp | 41 +++--- src/dummy.cpp | 53 ++++---- src/dummy.hpp | 6 +- src/jack.cpp | 17 +-- src/pulseaudio.cpp | 39 ++---- src/wasapi.cpp | 263 ++++++++++++++++++++++++++++++++----- src/wasapi.hpp | 11 +- test/unit_tests.cpp | 2 +- 13 files changed, 375 insertions(+), 254 deletions(-) diff --git a/README.md b/README.md index 360855d..cf9fd46 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ behavior on every platform. performance but prevent other applications from using them. Shared devices are default and usually provide sample rate conversion and format conversion. + * Exposes both device id and friendly name. id you could save in a config file + because it persists between devices becoming plugged and unplugged, while + friendly name is suitable for exposing to users. * Supports optimal usage of each supported backend. The same API does the right thing whether the backend has a fixed buffer size, such as on JACK and CoreAudio, or whether it allows directly managing the buffer, such as on @@ -258,9 +261,9 @@ view `coverage/index.html` in a browser. - microphone 0. Make sure PulseAudio can handle refresh devices crashing before block_until_have_devices - 0. Do we really want `period_duration` in the API? 0. Integrate into libgroove and test with Groove Basin 0. clear buffer maybe could take an argument to say how many frames to not clear + 0. create a test for clear buffer; ensure pause/play semantics work 0. Verify that JACK xrun callback context is the same as process callback. If not, might need to hav xrun callback set a flag and have process callback call the underflow callback. diff --git a/example/sio_list_devices.c b/example/sio_list_devices.c index 4dc0fc8..fc1f8a7 100644 --- a/example/sio_list_devices.c +++ b/example/sio_list_devices.c @@ -67,15 +67,11 @@ static void print_device(struct SoundIoDevice *device, bool is_default) { if (device->current_format != SoundIoFormatInvalid) fprintf(stderr, " current format: %s\n", soundio_format_string(device->current_format)); - fprintf(stderr, " min buffer duration: %0.8f sec\n", device->buffer_duration_min); - fprintf(stderr, " max buffer duration: %0.8f sec\n", device->buffer_duration_max); - if (device->buffer_duration_current != 0.0) - fprintf(stderr, " current buffer duration: %0.8f sec\n", device->buffer_duration_current); + fprintf(stderr, " min software latency: %0.8f sec\n", device->software_latency_min); + fprintf(stderr, " max software latency: %0.8f sec\n", device->software_latency_max); + if (device->software_latency_current != 0.0) + fprintf(stderr, " current software latency: %0.8f sec\n", device->software_latency_current); - fprintf(stderr, " min period duration: %0.8f sec\n", device->period_duration_min); - fprintf(stderr, " max period duration: %0.8f sec\n", device->period_duration_max); - if (device->period_duration_current != 0.0) - fprintf(stderr, " current period duration: %0.8f sec\n", device->period_duration_current); } fprintf(stderr, "\n"); } diff --git a/example/sio_microphone.c b/example/sio_microphone.c index 88a57c9..3cc25ee 100644 --- a/example/sio_microphone.c +++ b/example/sio_microphone.c @@ -310,7 +310,7 @@ int main(int argc, char **argv) { instream->format = *fmt; instream->sample_rate = *sample_rate; instream->layout = *layout; - instream->period_duration = microphone_latency / 4.0; + instream->software_latency = microphone_latency; instream->read_callback = read_callback; if ((err = soundio_instream_open(instream))) @@ -322,7 +322,7 @@ int main(int argc, char **argv) { outstream->format = *fmt; outstream->sample_rate = *sample_rate; outstream->layout = *layout; - outstream->buffer_duration = microphone_latency; + outstream->software_latency = microphone_latency; outstream->write_callback = write_callback; outstream->underflow_callback = underflow_callback; diff --git a/soundio/soundio.h b/soundio/soundio.h index c8ba5f3..e5065d9 100644 --- a/soundio/soundio.h +++ b/soundio/soundio.h @@ -352,27 +352,13 @@ struct SoundIoDevice { int sample_rate_count; int sample_rate_current; - // Buffer duration in seconds. If `buffer_duration_current` is unknown or - // irrelevant, it is set to 0.0. - // PulseAudio allows any value and so reasonable min/max of 0.10 and 4.0 - // are used. You may check that the current backend is PulseAudio and - // ignore these min/max values. - // For JACK and CoreAudio, buffer duration and period duration are the same. - // For WASAPI, buffer duration is unknown. - double buffer_duration_min; - double buffer_duration_max; - double buffer_duration_current; - - // Period duration in seconds. After this much time passes, write_callback - // is called. If values are unknown, they are set to 0.0. - // For PulseAudio and CoreAudio, these values are meaningless. - // For JACK, buffer duration and period duration are the same. - // For WASAPI, `period_duration_max` is unknown for raw devices, so a - // reasonable max of 4.0 is used. You may check that the current backend is - // WASAPI and ignore the max value for raw devices. - double period_duration_min; - double period_duration_max; - double period_duration_current; + // Software latency in seconds. If any of these values are unknown or + // irrelevant, they are set to 0.0. + // For PulseAudio and WASAPI these values are unknown until you open a + // stream. + double software_latency_min; + double software_latency_max; + double software_latency_current; // Raw means that you are directly opening the hardware device and not // going through a proxy such as dmix, PulseAudio, or JACK. When you open a @@ -408,27 +394,23 @@ struct SoundIoOutStream { // Defaults to Stereo, if available, followed by the first layout supported. struct SoundIoChannelLayout layout; - // Buffer duration in seconds. - // After you call `soundio_outstream_open` this value is replaced with the - // actual duration, as near to this value as possible. - // Defaults to a big buffer, potentially upwards of 1 second. If you want - // lower latency, set this value to the latency you want. - // If the device has unknown buffer duration min and max values, you may - // still set this, but you might not get the value you requested. If you - // set this and the backend is PulseAudio, it sets + // 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 + // 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. + // 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`. - double buffer_duration; - - // `period_duration` is the latency; how much time it takes - // for a sample put in the buffer to get played. - // After you call `soundio_outstream_open` this value is replaced with the - // actual period duration, as near to this value as possible. - // Defaults to `buffer_duration / 2` (and then clamped into range). - // If the device has unknown period duration min and max values, you may - // still set this. This value is meaningless for PulseAudio, JACK, and - // CoreAudio. - double period_duration; + // For JACK, this value is always equal to `software_latency_current` of + // the device. + double software_latency; // Defaults to NULL. Put whatever you want here. void *userdata; @@ -493,25 +475,19 @@ struct SoundIoInStream { // Defaults to Stereo, if available, followed by the first layout supported. struct SoundIoChannelLayout layout; - // Buffer duration in seconds. - // After you call `soundio_instream_open` this value is replaced with the - // actual duration, as near to this value as possible. - // If the captured audio frames exceeds this before they are read, a buffer - // overrun occurs and the frames are lost. - // Defaults to 1 second (and then clamped into range). For PulseAudio, - // defaults to PulseAudio's default value, usually large. If you set this - // and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and - // is the value used for `maxlength`. - double buffer_duration; - - // The latency of the captured audio. - // After you call `soundio_instream_open` this value is replaced with the - // actual duration, as near to this value as possible. - // After this many seconds pass, `read_callback` is called. - // Defaults to `buffer_duration / 8`. - // If you set this and the backend is PulseAudio, it sets + // 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`. - double period_duration; + // For JACK, this value is always equal to `software_latency_current` of + // the device. + double software_latency; // Defaults to NULL. Put whatever you want here. void *userdata; @@ -736,8 +712,8 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device); // You may not call this function from the `write_callback` thread context. void soundio_outstream_destroy(struct SoundIoOutStream *outstream); -// After you call this function, `buffer_duration` and `period_duration` are -// set to the correct values, if available. +// After you call this function, `software_latency` is set to the correct +// value. // The next thing to do is call `soundio_instream_start`. int soundio_outstream_open(struct SoundIoOutStream *outstream); @@ -790,8 +766,8 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device); // You may not call this function from `read_callback`. void soundio_instream_destroy(struct SoundIoInStream *instream); -// After you call this function, `buffer_duration` and `period_duration` are -// set to the correct values, if available. +// After you call this function, `software_latency` is set to the correct +// value. // The next thing to do is call `soundio_instream_start`. int soundio_instream_open(struct SoundIoInStream *instream); diff --git a/src/alsa.cpp b/src/alsa.cpp index 6d786a8..cdc6c6b 100644 --- a/src/alsa.cpp +++ b/src/alsa.cpp @@ -299,29 +299,14 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam snd_pcm_uframes_t min_frames; snd_pcm_uframes_t max_frames; - if ((err = snd_pcm_hw_params_set_period_size_integer(handle, hwparams)) < 0) - return SoundIoErrorIncompatibleDevice; - - if ((err = snd_pcm_hw_params_get_period_size_min(hwparams, &min_frames, nullptr)) < 0) - return SoundIoErrorIncompatibleDevice; - - if ((err = snd_pcm_hw_params_get_period_size_max(hwparams, &max_frames, nullptr)) < 0) - return SoundIoErrorIncompatibleDevice; - - device->period_duration_min = min_frames * one_over_actual_rate; - device->period_duration_max = max_frames * one_over_actual_rate; - - if ((err = snd_pcm_hw_params_set_period_size_first(handle, hwparams, &min_frames, nullptr)) < 0) - return SoundIoErrorIncompatibleDevice; - if ((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames)) < 0) return SoundIoErrorOpeningDevice; if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) return SoundIoErrorOpeningDevice; - device->buffer_duration_min = min_frames * one_over_actual_rate; - device->buffer_duration_max = max_frames * one_over_actual_rate; + device->software_latency_min = min_frames * one_over_actual_rate; + device->software_latency_max = max_frames * one_over_actual_rate; if ((err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames)) < 0) return SoundIoErrorOpeningDevice; @@ -443,11 +428,8 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) { if (device->sample_rates[0].min == device->sample_rates[0].max) device->sample_rate_current = device->sample_rates[0].min; - if (device->buffer_duration_min == device->buffer_duration_max) - device->buffer_duration_current = device->buffer_duration_min; - - if (device->period_duration_min == device->period_duration_max) - device->period_duration_current = device->period_duration_min; + if (device->software_latency_min == device->software_latency_max) + device->software_latency_current = device->software_latency_min; // now say that resampling is OK and see what the real min and max is. if ((err = probe_open_device(device, handle, 1, &channels_min, &channels_max)) < 0) { @@ -1111,12 +1093,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) SoundIoOutStream *outstream = &os->pub; SoundIoDevice *device = outstream->device; - if (outstream->buffer_duration == 0.0) - outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); - if (outstream->period_duration == 0.0) { - outstream->period_duration = clamp(device->period_duration_min, - outstream->buffer_duration / 2.0, device->period_duration_max); - } + if (outstream->software_latency == 0.0) + outstream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max); int ch_count = outstream->layout.channel_count; @@ -1177,21 +1155,31 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) return SoundIoErrorOpeningDevice; } - snd_pcm_uframes_t period_frames = ceil(outstream->period_duration * (double)outstream->sample_rate); - if ((err = snd_pcm_hw_params_set_period_size_near(osa->handle, hwparams, &period_frames, nullptr)) < 0) { - outstream_destroy_alsa(si, os); - return SoundIoErrorOpeningDevice; + + if (device->is_raw) { + unsigned int microseconds; + if ((err = snd_pcm_hw_params_set_period_time_first(osa->handle, hwparams, µseconds, nullptr)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } + } else { + double period_duration = outstream->software_latency / 2.0; + snd_pcm_uframes_t period_frames = ceil(period_duration * (double)outstream->sample_rate); + + if ((err = snd_pcm_hw_params_set_period_size_near(osa->handle, hwparams, &period_frames, nullptr)) < 0) { + outstream_destroy_alsa(si, os); + return SoundIoErrorOpeningDevice; + } } - outstream->period_duration = ((double)period_frames) / (double)outstream->sample_rate; - snd_pcm_uframes_t buffer_size_frames = ceil(outstream->buffer_duration * (double)outstream->sample_rate); + snd_pcm_uframes_t buffer_size_frames = ceil(outstream->software_latency * (double)outstream->sample_rate); if ((err = snd_pcm_hw_params_set_buffer_size_near(osa->handle, hwparams, &buffer_size_frames)) < 0) { outstream_destroy_alsa(si, os); return SoundIoErrorOpeningDevice; } - outstream->buffer_duration = ((double)buffer_size_frames) / (double)outstream->sample_rate; + outstream->software_latency = ((double)buffer_size_frames) / (double)outstream->sample_rate; @@ -1400,12 +1388,8 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; SoundIoDevice *device = instream->device; - if (instream->buffer_duration == 0.0) - instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); - if (instream->period_duration == 0.0) { - instream->period_duration = clamp(device->period_duration_min, - instream->buffer_duration / 8.0, device->period_duration_max); - } + if (instream->software_latency == 0.0) + instream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max); int ch_count = instream->layout.channel_count; @@ -1466,21 +1450,19 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { return SoundIoErrorOpeningDevice; } - snd_pcm_uframes_t period_frames = ceil(instream->period_duration * (double)instream->sample_rate); + snd_pcm_uframes_t period_frames = ceil(instream->software_latency * (double)instream->sample_rate); if ((err = snd_pcm_hw_params_set_period_size_near(isa->handle, hwparams, &period_frames, nullptr)) < 0) { instream_destroy_alsa(si, is); return SoundIoErrorOpeningDevice; } - instream->period_duration = ((double)period_frames) / (double)instream->sample_rate; + instream->software_latency = ((double)period_frames) / (double)instream->sample_rate; - snd_pcm_uframes_t buffer_size_frames = ceil(instream->buffer_duration * (double)instream->sample_rate); - - if ((err = snd_pcm_hw_params_set_buffer_size_near(isa->handle, hwparams, &buffer_size_frames)) < 0) { + snd_pcm_uframes_t buffer_size_frames; + if ((err = snd_pcm_hw_params_set_buffer_size_last(isa->handle, hwparams, &buffer_size_frames)) < 0) { instream_destroy_alsa(si, is); return SoundIoErrorOpeningDevice; } - instream->buffer_duration = ((double)buffer_size_frames) / (double)instream->sample_rate; snd_pcm_uframes_t period_size; if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) { diff --git a/src/coreaudio.cpp b/src/coreaudio.cpp index bec7f42..0285a7e 100644 --- a/src/coreaudio.cpp +++ b/src/coreaudio.cpp @@ -714,7 +714,7 @@ static int refresh_devices(struct SoundIoPrivate *si) { return SoundIoErrorOpeningDevice; } double use_sample_rate = rd.device->sample_rate_current; - rd.device->buffer_duration_current = buffer_frame_size / use_sample_rate; + rd.device->software_latency_current = buffer_frame_size / use_sample_rate; prop_address.mSelector = kAudioDevicePropertyBufferFrameSizeRange; prop_address.mScope = aim_to_scope(aim); @@ -727,13 +727,8 @@ static int refresh_devices(struct SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - rd.device->buffer_duration_min = avr.mMinimum / use_sample_rate; - rd.device->buffer_duration_max = avr.mMaximum / use_sample_rate; - - rd.device->period_duration_min = rd.device->buffer_duration_min; - rd.device->period_duration_max = rd.device->buffer_duration_max; - rd.device->period_duration_current = rd.device->buffer_duration_current; - + rd.device->software_latency_min = avr.mMinimum / use_sample_rate; + rd.device->software_latency_max = avr.mMaximum / use_sample_rate; SoundIoList *device_list; @@ -909,15 +904,13 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; - if (outstream->buffer_duration == 0.0) - outstream->buffer_duration = device->buffer_duration_current; + if (outstream->software_latency == 0.0) + outstream->software_latency = device->software_latency_current; - outstream->buffer_duration = clamp( - device->buffer_duration_min, - outstream->buffer_duration, - device->buffer_duration_max); - - outstream->period_duration = outstream->outstream->buffer_duration; + outstream->software_latency = clamp( + device->software_latency_min, + outstream->software_latency, + device->software_latency_max); AudioComponentDescription desc = {0}; desc.componentType = kAudioUnitType_Output; @@ -978,7 +971,7 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP kAudioObjectPropertyScopeInput, OUTPUT_ELEMENT }; - UInt32 buffer_frame_size = outstream->buffer_duration * outstream->sample_rate; + UInt32 buffer_frame_size = outstream->software_latency * outstream->sample_rate; if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address, 0, nullptr, sizeof(UInt32), &buffer_frame_size))) { @@ -1142,13 +1135,13 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri UInt32 io_size; OSStatus os_err; - if (instream->buffer_duration == 0.0) - instream->buffer_duration = device->buffer_duration_current; + if (instream->software_latency == 0.0) + instream->software_latency = device->software_latency_current; - instream->buffer_duration = clamp( - device->buffer_duration_min, - instream->buffer_duration, - device->buffer_duration_max); + instream->software_latency = clamp( + device->software_latency_min, + instream->software_latency, + device->software_latency_max); AudioObjectPropertyAddress prop_address; @@ -1251,7 +1244,7 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mScope = kAudioObjectPropertyScopeOutput; prop_address.mElement = INPUT_ELEMENT; - UInt32 buffer_frame_size = instream->buffer_duration * instream->sample_rate; + UInt32 buffer_frame_size = instream->software_latency * instream->sample_rate; if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address, 0, nullptr, sizeof(UInt32), &buffer_frame_size))) { diff --git a/src/dummy.cpp b/src/dummy.cpp index d0c83f4..bb3c8e3 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -29,7 +29,7 @@ static void playback_thread_run(void *arg) { double now = soundio_os_get_time(); double time_passed = now - start_time; double next_period = start_time + - ceil(time_passed / outstream->period_duration) * outstream->period_duration; + ceil(time_passed / osd->period_duration) * osd->period_duration; double relative_time = next_period - now; soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time); @@ -70,7 +70,7 @@ static void capture_thread_run(void *arg) { double now = soundio_os_get_time(); double time_passed = now - start_time; double next_period = start_time + - ceil(time_passed / instream->period_duration) * instream->period_duration; + ceil(time_passed / isd->period_duration) * isd->period_duration; double relative_time = next_period - now; soundio_os_cond_timed_wait(isd->cond, nullptr, relative_time); @@ -149,22 +149,20 @@ static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) SoundIoOutStream *outstream = &os->pub; SoundIoDevice *device = outstream->device; - if (outstream->buffer_duration == 0.0) - outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); - if (outstream->period_duration == 0.0) { - outstream->period_duration = clamp(device->period_duration_min, - outstream->buffer_duration / 2.0, device->period_duration_max); - } + if (outstream->software_latency == 0.0) + outstream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max); + + osd->period_duration = outstream->software_latency / 2.0; int err; - int buffer_size = outstream->bytes_per_frame * outstream->sample_rate * outstream->buffer_duration; + int buffer_size = outstream->bytes_per_frame * outstream->sample_rate * outstream->software_latency; if ((err = soundio_ring_buffer_init(&osd->ring_buffer, buffer_size))) { outstream_destroy_dummy(si, os); return err; } int actual_capacity = soundio_ring_buffer_capacity(&osd->ring_buffer); osd->buffer_frame_count = actual_capacity / outstream->bytes_per_frame; - outstream->buffer_duration = osd->buffer_frame_count / (double) outstream->sample_rate; + outstream->software_latency = osd->buffer_frame_count / (double) outstream->sample_rate; osd->cond = soundio_os_cond_create(); if (!osd->cond) { @@ -254,15 +252,16 @@ static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoInStream *instream = &is->pub; SoundIoDevice *device = instream->device; - if (instream->buffer_duration == 0.0) - instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); - if (instream->period_duration == 0.0) { - instream->period_duration = clamp(instream->device->period_duration_min, - instream->buffer_duration / 8.0, instream->device->period_duration_max); - } + + if (instream->software_latency == 0.0) + instream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max); + + isd->period_duration = instream->software_latency; + + double target_buffer_duration = isd->period_duration * 4.0; int err; - int buffer_size = instream->bytes_per_frame * instream->sample_rate * instream->buffer_duration; + int buffer_size = instream->bytes_per_frame * instream->sample_rate * target_buffer_duration; if ((err = soundio_ring_buffer_init(&isd->ring_buffer, buffer_size))) { instream_destroy_dummy(si, is); return err; @@ -270,7 +269,6 @@ static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { int actual_capacity = soundio_ring_buffer_capacity(&isd->ring_buffer); isd->buffer_frame_count = actual_capacity / instream->bytes_per_frame; - instream->buffer_duration = isd->buffer_frame_count / (double) instream->sample_rate; isd->cond = soundio_os_cond_create(); if (!isd->cond) { @@ -439,13 +437,11 @@ int soundio_dummy_init(SoundIoPrivate *si) { } set_all_device_sample_rates(device); - device->buffer_duration_min = 0.01; - device->buffer_duration_max = 4; - device->buffer_duration_current = 0.1; + device->software_latency_current = 0.1; + device->software_latency_min = 0.01; + device->software_latency_max = 4.0; + device->sample_rate_current = 48000; - device->period_duration_min = 0.01; - device->period_duration_max = 2; - device->period_duration_current = 0.05; device->aim = SoundIoDeviceAimOutput; if (si->safe_devices_info->output_devices.append(device)) { @@ -487,13 +483,10 @@ int soundio_dummy_init(SoundIoPrivate *si) { return err; } set_all_device_sample_rates(device); - device->buffer_duration_min = 0.01; - device->buffer_duration_max = 4; - device->buffer_duration_current = 0.1; + device->software_latency_current = 0.1; + device->software_latency_min = 0.01; + device->software_latency_max = 4.0; device->sample_rate_current = 48000; - device->period_duration_min = 0.01; - device->period_duration_max = 2; - device->period_duration_current = 0.05; device->aim = SoundIoDeviceAimInput; if (si->safe_devices_info->input_devices.append(device)) { diff --git a/src/dummy.hpp b/src/dummy.hpp index 280d676..b4e40b4 100644 --- a/src/dummy.hpp +++ b/src/dummy.hpp @@ -21,14 +21,13 @@ struct SoundIoDummy { bool devices_emitted; }; -struct SoundIoDeviceDummy { - -}; +struct SoundIoDeviceDummy { }; struct SoundIoOutStreamDummy { struct SoundIoOsThread *thread; struct SoundIoOsCond *cond; atomic_flag abort_flag; + double period_duration; int buffer_frame_count; int frames_left; int write_frame_count; @@ -41,6 +40,7 @@ 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; diff --git a/src/jack.cpp b/src/jack.cpp index 451e1fe..975f6a7 100644 --- a/src/jack.cpp +++ b/src/jack.cpp @@ -193,12 +193,11 @@ static int refresh_devices_bare(SoundIoPrivate *si) { device->sample_rates[0].min = sij->sample_rate; device->sample_rates[0].max = sij->sample_rate; device->sample_rate_current = sij->sample_rate; - device->period_duration_min = sij->period_size / (double) sij->sample_rate; - device->period_duration_max = device->period_duration_min; - device->period_duration_current = device->period_duration_min; - device->buffer_duration_min = device->period_duration_min; - device->buffer_duration_max = device->period_duration_min; - device->buffer_duration_current = device->period_duration_min; + + device->software_latency_current = sij->period_size / (double) sij->sample_rate; + device->software_latency_min = sij->period_size / (double) sij->sample_rate; + device->software_latency_max = sij->period_size / (double) sij->sample_rate; + dj->port_count = client->port_count; dj->ports = allocate(dj->port_count); @@ -416,8 +415,7 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea if (sij->is_shutdown) return SoundIoErrorBackendDisconnected; - outstream->buffer_duration = device->period_duration_current; - outstream->period_duration = device->period_duration_current; + outstream->software_latency = device->software_latency_current; osj->period_size = sij->period_size; jack_status_t status; @@ -619,8 +617,7 @@ static int instream_open_jack(struct SoundIoPrivate *si, struct SoundIoInStreamP if (sij->is_shutdown) return SoundIoErrorBackendDisconnected; - instream->buffer_duration = device->period_duration_current; - instream->period_duration = device->period_duration_current; + instream->software_latency = device->software_latency_current; isj->period_size = sij->period_size; jack_status_t status; diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index db10c4f..e6183b0 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -318,10 +318,6 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in return; } - device->buffer_duration_min = 0.10; - device->buffer_duration_max = 4.0; - - // "period" is not a recognized concept in PulseAudio. device->aim = SoundIoDeviceAimOutput; @@ -390,11 +386,6 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info return; } - device->buffer_duration_min = 0.10; - device->buffer_duration_max = 4.0; - - // "period" is not a recognized concept in PulseAudio. - device->aim = SoundIoDeviceAimInput; if (sipa->current_devices_info->input_devices.append(device)) { @@ -710,16 +701,16 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { ospa->buffer_attr.fragsize = UINT32_MAX; int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; - if (outstream->buffer_duration > 0.0) { + if (outstream->software_latency > 0.0) { int buffer_length = outstream->bytes_per_frame * - ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame); + ceil(outstream->software_latency * bytes_per_second / (double)outstream->bytes_per_frame); ospa->buffer_attr.maxlength = buffer_length; ospa->buffer_attr.tlength = buffer_length; } pa_stream_flags_t flags = PA_STREAM_START_CORKED; - if (outstream->buffer_duration > 0.0) + if (outstream->software_latency > 0.0) flags = (pa_stream_flags_t) (flags | PA_STREAM_ADJUST_LATENCY); int err = pa_stream_connect_playback(ospa->stream, @@ -734,7 +725,9 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) { pa_threaded_mainloop_wait(sipa->main_loop); size_t writable_size = pa_stream_writable_size(ospa->stream); - outstream->buffer_duration = writable_size / bytes_per_second; + outstream->software_latency = writable_size / bytes_per_second; + + // TODO get the correct software_latency value pa_threaded_mainloop_unlock(sipa->main_loop); @@ -922,16 +915,10 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { ispa->buffer_attr.minreq = UINT32_MAX; ispa->buffer_attr.fragsize = UINT32_MAX; - int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; - if (instream->buffer_duration > 0.0) { + if (instream->software_latency > 0.0) { + int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; int buffer_length = instream->bytes_per_frame * - ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame); - ispa->buffer_attr.maxlength = buffer_length; - } - - if (instream->period_duration > 0.0) { - int buffer_length = instream->bytes_per_frame * - ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame); + ceil(instream->software_latency * bytes_per_second / (double)instream->bytes_per_frame); ispa->buffer_attr.fragsize = buffer_length; } @@ -946,7 +933,7 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio; pa_threaded_mainloop_lock(sipa->main_loop); - pa_stream_flags_t flags = (instream->period_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; + pa_stream_flags_t flags = (instream->software_latency > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS; int err = pa_stream_connect_record(ispa->stream, instream->device->id, @@ -959,12 +946,6 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) { while (!ispa->stream_ready) pa_threaded_mainloop_wait(sipa->main_loop); - const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ispa->stream); - instream->buffer_duration = (attr->maxlength / - (double)instream->bytes_per_frame) / (double)instream->sample_rate; - instream->period_duration = (attr->fragsize / - (double)instream->bytes_per_frame) / (double)instream->sample_rate; - pa_threaded_mainloop_unlock(sipa->main_loop); return 0; } diff --git a/src/wasapi.cpp b/src/wasapi.cpp index 11bc830..d3aa64f 100644 --- a/src/wasapi.cpp +++ b/src/wasapi.cpp @@ -18,6 +18,8 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = { const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = { 0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; +const static DWORD SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000; + // Adding more common sample rates helps the heuristics; feel free to do that. static int test_sample_rates[] = { 8000, @@ -271,6 +273,11 @@ static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wa } } +static void complete_wave_format_data(WAVEFORMATEXTENSIBLE *wave_format) { + wave_format->Format.nBlockAlign = (wave_format->Format.wBitsPerSample * wave_format->Format.nChannels) / 8; + wave_format->Format.nAvgBytesPerSec = wave_format->Format.nSamplesPerSec * wave_format->Format.nBlockAlign; +} + static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) { return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput; } @@ -280,10 +287,14 @@ static double from_reference_time(REFERENCE_TIME rt) { return ((double)rt) / 10000000.0; } +static REFERENCE_TIME to_reference_time(double seconds) { + return seconds * 10000000.0 + 0.5; +} + static void destruct_device(SoundIoDevicePrivate *dev) { SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; - if (dw->audio_client) - IUnknown_Release(dw->audio_client); + if (dw->mm_device) + IMMDevice_Release(dw->mm_device); dw->sample_rates.deinit(); } @@ -294,6 +305,7 @@ struct RefreshDevices { IMMDevice *default_capture_device; IMMEndpoint *endpoint; IPropertyStore *prop_store; + IAudioClient *audio_client; LPWSTR lpwstr; PROPVARIANT prop_variant_value; WAVEFORMATEXTENSIBLE *wave_format; @@ -329,6 +341,8 @@ static void deinit_refresh_devices(RefreshDevices *rd) { PropVariantClear(&rd->prop_variant_value); if (rd->wave_format) CoTaskMemFree(rd->wave_format); + if (rd->audio_client) + IUnknown_Release(rd->audio_client); } static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format, @@ -350,8 +364,9 @@ static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_f SoundIoChannelLayoutId test_layout_id = test_layouts[i]; const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id); to_wave_format_layout(test_layout, wave_format); + complete_wave_format_data(wave_format); - HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode, + HRESULT hr = IAudioClient_IsFormatSupported(rd->audio_client, share_mode, (WAVEFORMATEX*)wave_format, &closest_match); if (closest_match) { CoTaskMemFree(closest_match); @@ -389,8 +404,9 @@ static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_f for (int i = 0; i < array_length(test_formats); i += 1) { SoundIoFormat test_format = test_formats[i]; to_wave_format_format(test_format, wave_format); + complete_wave_format_data(wave_format); - HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode, + HRESULT hr = IAudioClient_IsFormatSupported(rd->audio_client, share_mode, (WAVEFORMATEX*)wave_format, &closest_match); if (closest_match) { CoTaskMemFree(closest_match); @@ -421,7 +437,7 @@ static int add_sample_rate(SoundIoList *sample_rates, in return 0; } -static int do_sample_rate_test(SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE *wave_format, +static int do_sample_rate_test(RefreshDevices *rd, SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE *wave_format, int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int *current_min, int *last_success_rate) { SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; @@ -429,7 +445,7 @@ static int do_sample_rate_test(SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE * int err; wave_format->Format.nSamplesPerSec = test_sample_rate; - HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode, + HRESULT hr = IAudioClient_IsFormatSupported(rd->audio_client, share_mode, (WAVEFORMATEX*)wave_format, &closest_match); if (closest_match) { CoTaskMemFree(closest_match); @@ -468,7 +484,7 @@ static int detect_valid_sample_rates(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *w for (int i = 0; i < array_length(test_sample_rates); i += 1) { for (int offset = -1; offset <= 1; offset += 1) { int test_sample_rate = test_sample_rates[i] + offset; - if ((err = do_sample_rate_test(dev, wave_format, test_sample_rate, share_mode, + if ((err = do_sample_rate_test(rd, dev, wave_format, test_sample_rate, share_mode, ¤t_min, &last_success_rate))) { wave_format->Format.nSamplesPerSec = orig_sample_rate; @@ -507,8 +523,10 @@ static int refresh_devices(SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - if (rd.lpwstr) + if (rd.lpwstr) { CoTaskMemFree(rd.lpwstr); + rd.lpwstr = nullptr; + } if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; @@ -525,8 +543,10 @@ static int refresh_devices(SoundIoPrivate *si) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - if (rd.lpwstr) + if (rd.lpwstr) { CoTaskMemFree(rd.lpwstr); + rd.lpwstr = nullptr; + } if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; @@ -565,14 +585,18 @@ static int refresh_devices(SoundIoPrivate *si) { rd.devices_info->default_output_index = -1; for (int device_i = 0; device_i < device_count; device_i += 1) { - if (rd.mm_device) + if (rd.mm_device) { IMMDevice_Release(rd.mm_device); + rd.mm_device = nullptr; + } if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - if (rd.lpwstr) + if (rd.lpwstr) { CoTaskMemFree(rd.lpwstr); + rd.lpwstr = nullptr; + } if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; @@ -618,37 +642,33 @@ static int refresh_devices(SoundIoPrivate *si) { return SoundIoErrorNoMem; } + if (rd.audio_client) { + IUnknown_Release(rd.audio_client); + rd.audio_client = nullptr; + } if (FAILED(hr = IMMDevice_Activate(rd.mm_device, IID_IAudioClient, - CLSCTX_ALL, nullptr, (void**)&dev_w_shared->audio_client))) + CLSCTX_ALL, nullptr, (void**)&rd.audio_client))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - if (FAILED(hr = IAudioClient_AddRef(dev_w_shared->audio_client))) { - deinit_refresh_devices(&rd); - return SoundIoErrorOpeningDevice; - } - dev_w_raw->audio_client = dev_w_shared->audio_client; - REFERENCE_TIME default_device_period; REFERENCE_TIME min_device_period; - if (FAILED(hr = IAudioClient_GetDevicePeriod(dev_w_shared->audio_client, + if (FAILED(hr = IAudioClient_GetDevicePeriod(rd.audio_client, &default_device_period, &min_device_period))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - rd.device_shared->period_duration_current = from_reference_time(default_device_period); - rd.device_shared->period_duration_min = rd.device_shared->period_duration_current; - rd.device_shared->period_duration_max = rd.device_shared->period_duration_current; - - rd.device_raw->period_duration_min = from_reference_time(min_device_period); - rd.device_raw->period_duration_max = 4.0; + dev_w_shared->period_duration = from_reference_time(default_device_period); + dev_w_raw->period_duration = from_reference_time(min_device_period); - if (rd.endpoint) + if (rd.endpoint) { IMMEndpoint_Release(rd.endpoint); + rd.endpoint = nullptr; + } if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMEndpoint, (void**)&rd.endpoint))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; @@ -663,15 +683,19 @@ static int refresh_devices(SoundIoPrivate *si) { rd.device_shared->aim = data_flow_to_aim(data_flow); rd.device_raw->aim = rd.device_shared->aim; - if (rd.prop_store) + if (rd.prop_store) { IPropertyStore_Release(rd.prop_store); + rd.prop_store = nullptr; + } if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } - if (rd.prop_variant_value_inited) + if (rd.prop_variant_value_inited) { PropVariantClear(&rd.prop_variant_value); + rd.prop_variant_value_inited = false; + } PropVariantInit(&rd.prop_variant_value); rd.prop_variant_value_inited = true; if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, @@ -699,8 +723,10 @@ static int refresh_devices(SoundIoPrivate *si) { // Get the format that WASAPI opens the device with for shared streams. // This is guaranteed to work, so we use this to modulate the sample // rate while holding the format constant and vice versa. - if (rd.prop_variant_value_inited) + if (rd.prop_variant_value_inited) { PropVariantClear(&rd.prop_variant_value); + rd.prop_variant_value_inited = false; + } PropVariantInit(&rd.prop_variant_value); rd.prop_variant_value_inited = true; if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AudioEngine_DeviceFormat, @@ -727,9 +753,11 @@ static int refresh_devices(SoundIoPrivate *si) { return err; } - if (rd.wave_format) + if (rd.wave_format) { CoTaskMemFree(rd.wave_format); - if (FAILED(hr = IAudioClient_GetMixFormat(dev_w_shared->audio_client, (WAVEFORMATEX**)&rd.wave_format))) { + rd.wave_format = nullptr; + } + if (FAILED(hr = IAudioClient_GetMixFormat(rd.audio_client, (WAVEFORMATEX**)&rd.wave_format))) { deinit_refresh_devices(&rd); return SoundIoErrorOpeningDevice; } @@ -768,6 +796,9 @@ static int refresh_devices(SoundIoPrivate *si) { return err; } + dev_w_shared->mm_device = rd.mm_device; + dev_w_raw->mm_device = rd.mm_device; + rd.mm_device = nullptr; SoundIoList *device_list; if (rd.device_shared->aim == SoundIoDeviceAimOutput) { @@ -923,19 +954,175 @@ static void wakeup_wasapi(struct SoundIoPrivate *si) { } static void outstream_destroy_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { - soundio_panic("TODO"); + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + + if (osw->audio_clock_adjustment) + IUnknown_Release(osw->audio_clock_adjustment); + if (osw->audio_client) + IUnknown_Release(osw->audio_client); } static int outstream_open_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { - soundio_panic("TODO"); + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + SoundIoOutStream *outstream = &os->pub; + SoundIoDevice *device = outstream->device; + SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; + SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; + HRESULT hr; + int err; + + osw->is_raw = device->is_raw; + + if (FAILED(hr = IMMDevice_Activate(dw->mm_device, IID_IAudioClient, + CLSCTX_ALL, nullptr, (void**)&osw->audio_client))) + { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + + + AUDCLNT_SHAREMODE share_mode; + DWORD flags; + REFERENCE_TIME buffer_duration; + REFERENCE_TIME periodicity; + WAVEFORMATEXTENSIBLE wave_format = {0}; + wave_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + wave_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); + if (osw->is_raw) { + wave_format.Format.nSamplesPerSec = outstream->sample_rate; + flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; + share_mode = AUDCLNT_SHAREMODE_EXCLUSIVE; + periodicity = to_reference_time(dw->period_duration); + buffer_duration = periodicity; + } else { + WAVEFORMATEXTENSIBLE *mix_format; + if (FAILED(hr = IAudioClient_GetMixFormat(osw->audio_client, (WAVEFORMATEX **)&mix_format))) { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + wave_format.Format.nSamplesPerSec = mix_format->Format.nSamplesPerSec; + CoTaskMemFree(mix_format); + mix_format = nullptr; + osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate); + // TODO do we need SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ? + flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0; + share_mode = AUDCLNT_SHAREMODE_SHARED; + periodicity = 0; + buffer_duration = to_reference_time(4.0); + } + to_wave_format_layout(&outstream->layout, &wave_format); + to_wave_format_format(outstream->format, &wave_format); + complete_wave_format_data(&wave_format); + + if (FAILED(hr = IAudioClient_Initialize(osw->audio_client, share_mode, flags, + buffer_duration, periodicity, (WAVEFORMATEX*)&wave_format, nullptr))) + { + if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { + if (FAILED(hr = IAudioClient_GetBufferSize(osw->audio_client, &osw->buffer_frame_count))) { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + IUnknown_Release(osw->audio_client); + osw->audio_client = nullptr; + if (FAILED(hr = IMMDevice_Activate(dw->mm_device, IID_IAudioClient, + CLSCTX_ALL, nullptr, (void**)&osw->audio_client))) + { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + if (!osw->is_raw) { + WAVEFORMATEXTENSIBLE *mix_format; + if (FAILED(hr = IAudioClient_GetMixFormat(osw->audio_client, (WAVEFORMATEX **)&mix_format))) { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + wave_format.Format.nSamplesPerSec = mix_format->Format.nSamplesPerSec; + CoTaskMemFree(mix_format); + mix_format = nullptr; + osw->need_resample = (wave_format.Format.nSamplesPerSec != (DWORD)outstream->sample_rate); + // TODO do we need SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ? + flags = osw->need_resample ? AUDCLNT_STREAMFLAGS_RATEADJUST : 0; + to_wave_format_layout(&outstream->layout, &wave_format); + to_wave_format_format(outstream->format, &wave_format); + complete_wave_format_data(&wave_format); + } + + buffer_duration = to_reference_time(osw->buffer_frame_count / outstream->sample_rate); + if (osw->is_raw) + periodicity = buffer_duration; + if (FAILED(hr = IAudioClient_Initialize(osw->audio_client, share_mode, flags, + buffer_duration, periodicity, (WAVEFORMATEX*)&wave_format, nullptr))) + { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + } else { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + } + if (FAILED(hr = IAudioClient_GetBufferSize(osw->audio_client, &osw->buffer_frame_count))) { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + outstream->software_latency = osw->buffer_frame_count / (double)outstream->sample_rate; + + if (osw->is_raw) { + soundio_panic("TODO event callback"); + } else if (osw->need_resample) { + if (FAILED(hr = IAudioClient_GetService(osw->audio_client, IID_IAudioClockAdjustment, + (void**)&osw->audio_clock_adjustment))) + { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + if (FAILED(hr = IAudioClockAdjustment_SetSampleRate(osw->audio_clock_adjustment, + outstream->sample_rate))) + { + outstream_destroy_wasapi(si, os); + return SoundIoErrorOpeningDevice; + } + } + + return 0; } + static int outstream_pause_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) { - soundio_panic("TODO"); + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + HRESULT hr; + if (FAILED(hr = IAudioClient_Stop(osw->audio_client))) + return SoundIoErrorStreaming; + return 0; +} + +void outstream_shared_run(void *arg) { + SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *) arg; + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + + // TODO set up timer to wake up at the appropriate time + soundio_panic("TODO thread"); + //HRESULT hr; + //if (FAILED(hr = IAudioClient_Start(osw->audio_client))) + // return SoundIoErrorStreaming; + //return 0; } static int outstream_start_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { - soundio_panic("TODO"); + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + int err; + + if (osw->is_raw) { + soundio_panic("TODO start raw"); + } else { + assert(!osw->thread); + + osw->thread_exit_flag.test_and_set(); + if ((err = soundio_os_thread_create(outstream_shared_run, os, true, &osw->thread))) + return err; + } + + return 0; } static int outstream_begin_write_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, @@ -949,7 +1136,11 @@ static int outstream_end_write_wasapi(struct SoundIoPrivate *si, struct SoundIoO } static int outstream_clear_buffer_wasapi(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os) { - soundio_panic("TODO"); + SoundIoOutStreamWasapi *osw = &os->backend_data.wasapi; + HRESULT hr; + if (FAILED(hr = IAudioClient_Reset(osw->audio_client))) + return SoundIoErrorStreaming; + return 0; } diff --git a/src/wasapi.hpp b/src/wasapi.hpp index a939819..f8b7d93 100644 --- a/src/wasapi.hpp +++ b/src/wasapi.hpp @@ -22,12 +22,14 @@ #include #include #include +#include int soundio_wasapi_init(struct SoundIoPrivate *si); struct SoundIoDeviceWasapi { - IAudioClient *audio_client; SoundIoList sample_rates; + double period_duration; + IMMDevice *mm_device; }; struct SoundIoWasapi { @@ -49,6 +51,13 @@ struct SoundIoWasapi { }; struct SoundIoOutStreamWasapi { + IAudioClient *audio_client; + IAudioClockAdjustment *audio_clock_adjustment; + bool need_resample; + SoundIoOsThread *thread; + atomic_flag thread_exit_flag; + bool is_raw; + UINT32 buffer_frame_count; }; struct SoundIoInStreamWasapi { diff --git a/test/unit_tests.cpp b/test/unit_tests.cpp index f87f29d..cd04bc4 100644 --- a/test/unit_tests.cpp +++ b/test/unit_tests.cpp @@ -39,7 +39,7 @@ static void test_create_outstream(void) { outstream->format = SoundIoFormatFloat32NE; outstream->sample_rate = 48000; outstream->layout = device->layouts[0]; - outstream->buffer_duration = 0.1; + outstream->software_latency = 0.1; outstream->write_callback = write_callback; outstream->error_callback = error_callback;