remove the concept of period duration from the API

also more progress on WASAPI
This commit is contained in:
Andrew Kelley 2015-08-13 22:54:15 -07:00
parent 61b95a458c
commit fbc7318268
13 changed files with 375 additions and 254 deletions

View file

@ -30,6 +30,9 @@ behavior on every platform.
performance but prevent other applications from using them. Shared devices performance but prevent other applications from using them. Shared devices
are default and usually provide sample rate conversion and format are default and usually provide sample rate conversion and format
conversion. 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 * 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 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 CoreAudio, or whether it allows directly managing the buffer, such as on
@ -258,9 +261,9 @@ view `coverage/index.html` in a browser.
- microphone - microphone
0. Make sure PulseAudio can handle refresh devices crashing before 0. Make sure PulseAudio can handle refresh devices crashing before
block_until_have_devices block_until_have_devices
0. Do we really want `period_duration` in the API?
0. Integrate into libgroove and test with Groove Basin 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. 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. 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 If not, might need to hav xrun callback set a flag and have process callback
call the underflow callback. call the underflow callback.

View file

@ -67,15 +67,11 @@ static void print_device(struct SoundIoDevice *device, bool is_default) {
if (device->current_format != SoundIoFormatInvalid) if (device->current_format != SoundIoFormatInvalid)
fprintf(stderr, " current format: %s\n", soundio_format_string(device->current_format)); 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, " min software latency: %0.8f sec\n", device->software_latency_min);
fprintf(stderr, " max buffer duration: %0.8f sec\n", device->buffer_duration_max); fprintf(stderr, " max software latency: %0.8f sec\n", device->software_latency_max);
if (device->buffer_duration_current != 0.0) if (device->software_latency_current != 0.0)
fprintf(stderr, " current buffer duration: %0.8f sec\n", device->buffer_duration_current); 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"); fprintf(stderr, "\n");
} }

View file

@ -310,7 +310,7 @@ int main(int argc, char **argv) {
instream->format = *fmt; instream->format = *fmt;
instream->sample_rate = *sample_rate; instream->sample_rate = *sample_rate;
instream->layout = *layout; instream->layout = *layout;
instream->period_duration = microphone_latency / 4.0; instream->software_latency = microphone_latency;
instream->read_callback = read_callback; instream->read_callback = read_callback;
if ((err = soundio_instream_open(instream))) if ((err = soundio_instream_open(instream)))
@ -322,7 +322,7 @@ int main(int argc, char **argv) {
outstream->format = *fmt; outstream->format = *fmt;
outstream->sample_rate = *sample_rate; outstream->sample_rate = *sample_rate;
outstream->layout = *layout; outstream->layout = *layout;
outstream->buffer_duration = microphone_latency; outstream->software_latency = microphone_latency;
outstream->write_callback = write_callback; outstream->write_callback = write_callback;
outstream->underflow_callback = underflow_callback; outstream->underflow_callback = underflow_callback;

View file

@ -352,27 +352,13 @@ struct SoundIoDevice {
int sample_rate_count; int sample_rate_count;
int sample_rate_current; int sample_rate_current;
// Buffer duration in seconds. If `buffer_duration_current` is unknown or // Software latency in seconds. If any of these values are unknown or
// irrelevant, it is set to 0.0. // irrelevant, they are set to 0.0.
// PulseAudio allows any value and so reasonable min/max of 0.10 and 4.0 // For PulseAudio and WASAPI these values are unknown until you open a
// are used. You may check that the current backend is PulseAudio and // stream.
// ignore these min/max values. double software_latency_min;
// For JACK and CoreAudio, buffer duration and period duration are the same. double software_latency_max;
// For WASAPI, buffer duration is unknown. double software_latency_current;
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;
// Raw means that you are directly opening the hardware device and not // 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 // 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. // Defaults to Stereo, if available, followed by the first layout supported.
struct SoundIoChannelLayout layout; struct SoundIoChannelLayout layout;
// Buffer duration in seconds. // Ignoring hardware latency, this is the number of seconds it takes for
// After you call `soundio_outstream_open` this value is replaced with the // the last sample in a full buffer to be played.
// actual duration, as near to this value as possible. // After you call `soundio_outstream_open`, this value is replaced with the
// Defaults to a big buffer, potentially upwards of 1 second. If you want // actual software latency, as near to this value as possible.
// lower latency, set this value to the latency you want. // On systems that support clearing the buffer, this defaults to a large
// If the device has unknown buffer duration min and max values, you may // latency, potentially upwards of 2 seconds, with the understanding that
// still set this, but you might not get the value you requested. If you // you will call `soundio_outstream_clear_buffer` when you want to reduce
// set this and the backend is PulseAudio, it sets // 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 // `PA_STREAM_ADJUST_LATENCY` and is the value used for `maxlength` and
// `tlength`. // `tlength`.
double buffer_duration; // For JACK, this value is always equal to `software_latency_current` of
// the device.
// `period_duration` is the latency; how much time it takes double software_latency;
// 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;
// Defaults to NULL. Put whatever you want here. // Defaults to NULL. Put whatever you want here.
void *userdata; void *userdata;
@ -493,25 +475,19 @@ struct SoundIoInStream {
// Defaults to Stereo, if available, followed by the first layout supported. // Defaults to Stereo, if available, followed by the first layout supported.
struct SoundIoChannelLayout layout; struct SoundIoChannelLayout layout;
// Buffer duration in seconds. // Ignoring hardware latency, this is the number of seconds it takes for a
// After you call `soundio_instream_open` this value is replaced with the // captured sample to become available for reading.
// actual duration, as near to this value as possible. // After you call `soundio_instream_open`, this value is replaced with the
// If the captured audio frames exceeds this before they are read, a buffer // actual software latency, as near to this value as possible.
// overrun occurs and the frames are lost. // A higher value means less CPU usage. Defaults to a large value,
// Defaults to 1 second (and then clamped into range). For PulseAudio, // potentially upwards of 2 seconds.
// defaults to PulseAudio's default value, usually large. If you set this // If the device has unknown software latency min and max values, you may
// and the backend is PulseAudio, it sets `PA_STREAM_ADJUST_LATENCY` and // still set this, but you might not get the value you requested.
// is the value used for `maxlength`. // For PulseAudio, if you set this value to non-default, it sets
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
// `PA_STREAM_ADJUST_LATENCY` and is the value used for `fragsize`. // `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. // Defaults to NULL. Put whatever you want here.
void *userdata; 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. // You may not call this function from the `write_callback` thread context.
void soundio_outstream_destroy(struct SoundIoOutStream *outstream); void soundio_outstream_destroy(struct SoundIoOutStream *outstream);
// After you call this function, `buffer_duration` and `period_duration` are // After you call this function, `software_latency` is set to the correct
// set to the correct values, if available. // value.
// The next thing to do is call `soundio_instream_start`. // The next thing to do is call `soundio_instream_start`.
int soundio_outstream_open(struct SoundIoOutStream *outstream); 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`. // You may not call this function from `read_callback`.
void soundio_instream_destroy(struct SoundIoInStream *instream); void soundio_instream_destroy(struct SoundIoInStream *instream);
// After you call this function, `buffer_duration` and `period_duration` are // After you call this function, `software_latency` is set to the correct
// set to the correct values, if available. // value.
// The next thing to do is call `soundio_instream_start`. // The next thing to do is call `soundio_instream_start`.
int soundio_instream_open(struct SoundIoInStream *instream); int soundio_instream_open(struct SoundIoInStream *instream);

View file

@ -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 min_frames;
snd_pcm_uframes_t max_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) if ((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0) if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
device->buffer_duration_min = min_frames * one_over_actual_rate; device->software_latency_min = min_frames * one_over_actual_rate;
device->buffer_duration_max = max_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) if ((err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames)) < 0)
return SoundIoErrorOpeningDevice; 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) if (device->sample_rates[0].min == device->sample_rates[0].max)
device->sample_rate_current = device->sample_rates[0].min; device->sample_rate_current = device->sample_rates[0].min;
if (device->buffer_duration_min == device->buffer_duration_max) if (device->software_latency_min == device->software_latency_max)
device->buffer_duration_current = device->buffer_duration_min; device->software_latency_current = device->software_latency_min;
if (device->period_duration_min == device->period_duration_max)
device->period_duration_current = device->period_duration_min;
// now say that resampling is OK and see what the real min and max is. // 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) { 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; SoundIoOutStream *outstream = &os->pub;
SoundIoDevice *device = outstream->device; SoundIoDevice *device = outstream->device;
if (outstream->buffer_duration == 0.0) if (outstream->software_latency == 0.0)
outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); outstream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
if (outstream->period_duration == 0.0) {
outstream->period_duration = clamp(device->period_duration_min,
outstream->buffer_duration / 2.0, device->period_duration_max);
}
int ch_count = outstream->layout.channel_count; int ch_count = outstream->layout.channel_count;
@ -1177,21 +1155,31 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
snd_pcm_uframes_t period_frames = ceil(outstream->period_duration * (double)outstream->sample_rate);
if (device->is_raw) {
unsigned int microseconds;
if ((err = snd_pcm_hw_params_set_period_time_first(osa->handle, hwparams, &microseconds, 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) { if ((err = snd_pcm_hw_params_set_period_size_near(osa->handle, hwparams, &period_frames, nullptr)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; 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) { if ((err = snd_pcm_hw_params_set_buffer_size_near(osa->handle, hwparams, &buffer_size_frames)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; 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; SoundIoInStream *instream = &is->pub;
SoundIoDevice *device = instream->device; SoundIoDevice *device = instream->device;
if (instream->buffer_duration == 0.0) if (instream->software_latency == 0.0)
instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); instream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
if (instream->period_duration == 0.0) {
instream->period_duration = clamp(device->period_duration_min,
instream->buffer_duration / 8.0, device->period_duration_max);
}
int ch_count = instream->layout.channel_count; int ch_count = instream->layout.channel_count;
@ -1466,21 +1450,19 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
return SoundIoErrorOpeningDevice; 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) { if ((err = snd_pcm_hw_params_set_period_size_near(isa->handle, hwparams, &period_frames, nullptr)) < 0) {
instream_destroy_alsa(si, is); instream_destroy_alsa(si, is);
return SoundIoErrorOpeningDevice; 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); snd_pcm_uframes_t buffer_size_frames;
if ((err = snd_pcm_hw_params_set_buffer_size_last(isa->handle, hwparams, &buffer_size_frames)) < 0) {
if ((err = snd_pcm_hw_params_set_buffer_size_near(isa->handle, hwparams, &buffer_size_frames)) < 0) {
instream_destroy_alsa(si, is); instream_destroy_alsa(si, is);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
instream->buffer_duration = ((double)buffer_size_frames) / (double)instream->sample_rate;
snd_pcm_uframes_t period_size; snd_pcm_uframes_t period_size;
if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) { if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) {

View file

@ -714,7 +714,7 @@ static int refresh_devices(struct SoundIoPrivate *si) {
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
double use_sample_rate = rd.device->sample_rate_current; 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.mSelector = kAudioDevicePropertyBufferFrameSizeRange;
prop_address.mScope = aim_to_scope(aim); prop_address.mScope = aim_to_scope(aim);
@ -727,13 +727,8 @@ static int refresh_devices(struct SoundIoPrivate *si) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
rd.device->buffer_duration_min = avr.mMinimum / use_sample_rate; rd.device->software_latency_min = avr.mMinimum / use_sample_rate;
rd.device->buffer_duration_max = avr.mMaximum / use_sample_rate; rd.device->software_latency_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;
SoundIoList<SoundIoDevice *> *device_list; SoundIoList<SoundIoDevice *> *device_list;
@ -909,15 +904,13 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP
SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device; SoundIoDevicePrivate *dev = (SoundIoDevicePrivate *)device;
SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio; SoundIoDeviceCoreAudio *dca = &dev->backend_data.coreaudio;
if (outstream->buffer_duration == 0.0) if (outstream->software_latency == 0.0)
outstream->buffer_duration = device->buffer_duration_current; outstream->software_latency = device->software_latency_current;
outstream->buffer_duration = clamp( outstream->software_latency = clamp(
device->buffer_duration_min, device->software_latency_min,
outstream->buffer_duration, outstream->software_latency,
device->buffer_duration_max); device->software_latency_max);
outstream->period_duration = outstream->outstream->buffer_duration;
AudioComponentDescription desc = {0}; AudioComponentDescription desc = {0};
desc.componentType = kAudioUnitType_Output; desc.componentType = kAudioUnitType_Output;
@ -978,7 +971,7 @@ static int outstream_open_ca(struct SoundIoPrivate *si, struct SoundIoOutStreamP
kAudioObjectPropertyScopeInput, kAudioObjectPropertyScopeInput,
OUTPUT_ELEMENT 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, if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address,
0, nullptr, sizeof(UInt32), &buffer_frame_size))) 0, nullptr, sizeof(UInt32), &buffer_frame_size)))
{ {
@ -1142,13 +1135,13 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
UInt32 io_size; UInt32 io_size;
OSStatus os_err; OSStatus os_err;
if (instream->buffer_duration == 0.0) if (instream->software_latency == 0.0)
instream->buffer_duration = device->buffer_duration_current; instream->software_latency = device->software_latency_current;
instream->buffer_duration = clamp( instream->software_latency = clamp(
device->buffer_duration_min, device->software_latency_min,
instream->buffer_duration, instream->software_latency,
device->buffer_duration_max); device->software_latency_max);
AudioObjectPropertyAddress prop_address; AudioObjectPropertyAddress prop_address;
@ -1251,7 +1244,7 @@ static int instream_open_ca(struct SoundIoPrivate *si, struct SoundIoInStreamPri
prop_address.mSelector = kAudioDevicePropertyBufferFrameSize; prop_address.mSelector = kAudioDevicePropertyBufferFrameSize;
prop_address.mScope = kAudioObjectPropertyScopeOutput; prop_address.mScope = kAudioObjectPropertyScopeOutput;
prop_address.mElement = INPUT_ELEMENT; 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, if ((os_err = AudioObjectSetPropertyData(dca->device_id, &prop_address,
0, nullptr, sizeof(UInt32), &buffer_frame_size))) 0, nullptr, sizeof(UInt32), &buffer_frame_size)))
{ {

View file

@ -29,7 +29,7 @@ static void playback_thread_run(void *arg) {
double now = soundio_os_get_time(); double now = soundio_os_get_time();
double time_passed = now - start_time; double time_passed = now - start_time;
double next_period = 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; double relative_time = next_period - now;
soundio_os_cond_timed_wait(osd->cond, nullptr, relative_time); 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 now = soundio_os_get_time();
double time_passed = now - start_time; double time_passed = now - start_time;
double next_period = 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; double relative_time = next_period - now;
soundio_os_cond_timed_wait(isd->cond, nullptr, relative_time); 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; SoundIoOutStream *outstream = &os->pub;
SoundIoDevice *device = outstream->device; SoundIoDevice *device = outstream->device;
if (outstream->buffer_duration == 0.0) if (outstream->software_latency == 0.0)
outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max); outstream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
if (outstream->period_duration == 0.0) {
outstream->period_duration = clamp(device->period_duration_min, osd->period_duration = outstream->software_latency / 2.0;
outstream->buffer_duration / 2.0, device->period_duration_max);
}
int err; 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))) { if ((err = soundio_ring_buffer_init(&osd->ring_buffer, buffer_size))) {
outstream_destroy_dummy(si, os); outstream_destroy_dummy(si, os);
return err; return err;
} }
int actual_capacity = soundio_ring_buffer_capacity(&osd->ring_buffer); int actual_capacity = soundio_ring_buffer_capacity(&osd->ring_buffer);
osd->buffer_frame_count = actual_capacity / outstream->bytes_per_frame; 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(); osd->cond = soundio_os_cond_create();
if (!osd->cond) { if (!osd->cond) {
@ -254,15 +252,16 @@ static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
SoundIoInStream *instream = &is->pub; SoundIoInStream *instream = &is->pub;
SoundIoDevice *device = instream->device; 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->software_latency == 0.0)
if (instream->period_duration == 0.0) { instream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
instream->period_duration = clamp(instream->device->period_duration_min,
instream->buffer_duration / 8.0, instream->device->period_duration_max); isd->period_duration = instream->software_latency;
}
double target_buffer_duration = isd->period_duration * 4.0;
int err; 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))) { if ((err = soundio_ring_buffer_init(&isd->ring_buffer, buffer_size))) {
instream_destroy_dummy(si, is); instream_destroy_dummy(si, is);
return err; 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); int actual_capacity = soundio_ring_buffer_capacity(&isd->ring_buffer);
isd->buffer_frame_count = actual_capacity / instream->bytes_per_frame; 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(); isd->cond = soundio_os_cond_create();
if (!isd->cond) { if (!isd->cond) {
@ -439,13 +437,11 @@ int soundio_dummy_init(SoundIoPrivate *si) {
} }
set_all_device_sample_rates(device); set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01; device->software_latency_current = 0.1;
device->buffer_duration_max = 4; device->software_latency_min = 0.01;
device->buffer_duration_current = 0.1; device->software_latency_max = 4.0;
device->sample_rate_current = 48000; 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; device->aim = SoundIoDeviceAimOutput;
if (si->safe_devices_info->output_devices.append(device)) { if (si->safe_devices_info->output_devices.append(device)) {
@ -487,13 +483,10 @@ int soundio_dummy_init(SoundIoPrivate *si) {
return err; return err;
} }
set_all_device_sample_rates(device); set_all_device_sample_rates(device);
device->buffer_duration_min = 0.01; device->software_latency_current = 0.1;
device->buffer_duration_max = 4; device->software_latency_min = 0.01;
device->buffer_duration_current = 0.1; device->software_latency_max = 4.0;
device->sample_rate_current = 48000; 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; device->aim = SoundIoDeviceAimInput;
if (si->safe_devices_info->input_devices.append(device)) { if (si->safe_devices_info->input_devices.append(device)) {

View file

@ -21,14 +21,13 @@ struct SoundIoDummy {
bool devices_emitted; bool devices_emitted;
}; };
struct SoundIoDeviceDummy { struct SoundIoDeviceDummy { };
};
struct SoundIoOutStreamDummy { struct SoundIoOutStreamDummy {
struct SoundIoOsThread *thread; struct SoundIoOsThread *thread;
struct SoundIoOsCond *cond; struct SoundIoOsCond *cond;
atomic_flag abort_flag; atomic_flag abort_flag;
double period_duration;
int buffer_frame_count; int buffer_frame_count;
int frames_left; int frames_left;
int write_frame_count; int write_frame_count;
@ -41,6 +40,7 @@ struct SoundIoInStreamDummy {
struct SoundIoOsThread *thread; struct SoundIoOsThread *thread;
struct SoundIoOsCond *cond; struct SoundIoOsCond *cond;
atomic_flag abort_flag; atomic_flag abort_flag;
double period_duration;
int frames_left; int frames_left;
int read_frame_count; int read_frame_count;
int buffer_frame_count; int buffer_frame_count;

View file

@ -193,12 +193,11 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
device->sample_rates[0].min = sij->sample_rate; device->sample_rates[0].min = sij->sample_rate;
device->sample_rates[0].max = sij->sample_rate; device->sample_rates[0].max = sij->sample_rate;
device->sample_rate_current = 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->software_latency_current = sij->period_size / (double) sij->sample_rate;
device->period_duration_current = device->period_duration_min; device->software_latency_min = sij->period_size / (double) sij->sample_rate;
device->buffer_duration_min = device->period_duration_min; device->software_latency_max = sij->period_size / (double) sij->sample_rate;
device->buffer_duration_max = device->period_duration_min;
device->buffer_duration_current = device->period_duration_min;
dj->port_count = client->port_count; dj->port_count = client->port_count;
dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count); dj->ports = allocate<SoundIoDeviceJackPort>(dj->port_count);
@ -416,8 +415,7 @@ static int outstream_open_jack(struct SoundIoPrivate *si, struct SoundIoOutStrea
if (sij->is_shutdown) if (sij->is_shutdown)
return SoundIoErrorBackendDisconnected; return SoundIoErrorBackendDisconnected;
outstream->buffer_duration = device->period_duration_current; outstream->software_latency = device->software_latency_current;
outstream->period_duration = device->period_duration_current;
osj->period_size = sij->period_size; osj->period_size = sij->period_size;
jack_status_t status; jack_status_t status;
@ -619,8 +617,7 @@ static int instream_open_jack(struct SoundIoPrivate *si, struct SoundIoInStreamP
if (sij->is_shutdown) if (sij->is_shutdown)
return SoundIoErrorBackendDisconnected; return SoundIoErrorBackendDisconnected;
instream->buffer_duration = device->period_duration_current; instream->software_latency = device->software_latency_current;
instream->period_duration = device->period_duration_current;
isj->period_size = sij->period_size; isj->period_size = sij->period_size;
jack_status_t status; jack_status_t status;

View file

@ -318,10 +318,6 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
return; return;
} }
device->buffer_duration_min = 0.10;
device->buffer_duration_max = 4.0;
// "period" is not a recognized concept in PulseAudio.
device->aim = SoundIoDeviceAimOutput; device->aim = SoundIoDeviceAimOutput;
@ -390,11 +386,6 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
return; return;
} }
device->buffer_duration_min = 0.10;
device->buffer_duration_max = 4.0;
// "period" is not a recognized concept in PulseAudio.
device->aim = SoundIoDeviceAimInput; device->aim = SoundIoDeviceAimInput;
if (sipa->current_devices_info->input_devices.append(device)) { 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; ospa->buffer_attr.fragsize = UINT32_MAX;
int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate; 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 * 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.maxlength = buffer_length;
ospa->buffer_attr.tlength = buffer_length; ospa->buffer_attr.tlength = buffer_length;
} }
pa_stream_flags_t flags = PA_STREAM_START_CORKED; 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); flags = (pa_stream_flags_t) (flags | PA_STREAM_ADJUST_LATENCY);
int err = pa_stream_connect_playback(ospa->stream, 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); pa_threaded_mainloop_wait(sipa->main_loop);
size_t writable_size = pa_stream_writable_size(ospa->stream); 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); 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.minreq = UINT32_MAX;
ispa->buffer_attr.fragsize = UINT32_MAX; ispa->buffer_attr.fragsize = UINT32_MAX;
if (instream->software_latency > 0.0) {
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate; int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
if (instream->buffer_duration > 0.0) {
int buffer_length = instream->bytes_per_frame * int buffer_length = instream->bytes_per_frame *
ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame); ceil(instream->software_latency * 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);
ispa->buffer_attr.fragsize = buffer_length; 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; SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
pa_threaded_mainloop_lock(sipa->main_loop); 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, int err = pa_stream_connect_record(ispa->stream,
instream->device->id, instream->device->id,
@ -959,12 +946,6 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
while (!ispa->stream_ready) while (!ispa->stream_ready)
pa_threaded_mainloop_wait(sipa->main_loop); 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); pa_threaded_mainloop_unlock(sipa->main_loop);
return 0; return 0;
} }

View file

@ -18,6 +18,8 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = { const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}}; 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. // Adding more common sample rates helps the heuristics; feel free to do that.
static int test_sample_rates[] = { static int test_sample_rates[] = {
8000, 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) { static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput; return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput;
} }
@ -280,10 +287,14 @@ static double from_reference_time(REFERENCE_TIME rt) {
return ((double)rt) / 10000000.0; 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) { static void destruct_device(SoundIoDevicePrivate *dev) {
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
if (dw->audio_client) if (dw->mm_device)
IUnknown_Release(dw->audio_client); IMMDevice_Release(dw->mm_device);
dw->sample_rates.deinit(); dw->sample_rates.deinit();
} }
@ -294,6 +305,7 @@ struct RefreshDevices {
IMMDevice *default_capture_device; IMMDevice *default_capture_device;
IMMEndpoint *endpoint; IMMEndpoint *endpoint;
IPropertyStore *prop_store; IPropertyStore *prop_store;
IAudioClient *audio_client;
LPWSTR lpwstr; LPWSTR lpwstr;
PROPVARIANT prop_variant_value; PROPVARIANT prop_variant_value;
WAVEFORMATEXTENSIBLE *wave_format; WAVEFORMATEXTENSIBLE *wave_format;
@ -329,6 +341,8 @@ static void deinit_refresh_devices(RefreshDevices *rd) {
PropVariantClear(&rd->prop_variant_value); PropVariantClear(&rd->prop_variant_value);
if (rd->wave_format) if (rd->wave_format)
CoTaskMemFree(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, 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]; SoundIoChannelLayoutId test_layout_id = test_layouts[i];
const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id); const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id);
to_wave_format_layout(test_layout, wave_format); 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); (WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) { if (closest_match) {
CoTaskMemFree(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) { for (int i = 0; i < array_length(test_formats); i += 1) {
SoundIoFormat test_format = test_formats[i]; SoundIoFormat test_format = test_formats[i];
to_wave_format_format(test_format, wave_format); 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); (WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) { if (closest_match) {
CoTaskMemFree(closest_match); CoTaskMemFree(closest_match);
@ -421,7 +437,7 @@ static int add_sample_rate(SoundIoList<SoundIoSampleRateRange> *sample_rates, in
return 0; 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) int test_sample_rate, AUDCLNT_SHAREMODE share_mode, int *current_min, int *last_success_rate)
{ {
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi; SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
@ -429,7 +445,7 @@ static int do_sample_rate_test(SoundIoDevicePrivate *dev, WAVEFORMATEXTENSIBLE *
int err; int err;
wave_format->Format.nSamplesPerSec = test_sample_rate; 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); (WAVEFORMATEX*)wave_format, &closest_match);
if (closest_match) { if (closest_match) {
CoTaskMemFree(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 i = 0; i < array_length(test_sample_rates); i += 1) {
for (int offset = -1; offset <= 1; offset += 1) { for (int offset = -1; offset <= 1; offset += 1) {
int test_sample_rate = test_sample_rates[i] + offset; 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,
&current_min, &last_success_rate))) &current_min, &last_success_rate)))
{ {
wave_format->Format.nSamplesPerSec = orig_sample_rate; wave_format->Format.nSamplesPerSec = orig_sample_rate;
@ -507,8 +523,10 @@ static int refresh_devices(SoundIoPrivate *si) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if (rd.lpwstr) if (rd.lpwstr) {
CoTaskMemFree(rd.lpwstr); CoTaskMemFree(rd.lpwstr);
rd.lpwstr = nullptr;
}
if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) { if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
@ -525,8 +543,10 @@ static int refresh_devices(SoundIoPrivate *si) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if (rd.lpwstr) if (rd.lpwstr) {
CoTaskMemFree(rd.lpwstr); CoTaskMemFree(rd.lpwstr);
rd.lpwstr = nullptr;
}
if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) { if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
@ -565,14 +585,18 @@ static int refresh_devices(SoundIoPrivate *si) {
rd.devices_info->default_output_index = -1; rd.devices_info->default_output_index = -1;
for (int device_i = 0; device_i < device_count; device_i += 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); IMMDevice_Release(rd.mm_device);
rd.mm_device = nullptr;
}
if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) { if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if (rd.lpwstr) if (rd.lpwstr) {
CoTaskMemFree(rd.lpwstr); CoTaskMemFree(rd.lpwstr);
rd.lpwstr = nullptr;
}
if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) { if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
@ -618,37 +642,33 @@ static int refresh_devices(SoundIoPrivate *si) {
return SoundIoErrorNoMem; 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, 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); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; 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 default_device_period;
REFERENCE_TIME min_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))) &default_device_period, &min_device_period)))
{ {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
rd.device_shared->period_duration_current = from_reference_time(default_device_period); dev_w_shared->period_duration = from_reference_time(default_device_period);
rd.device_shared->period_duration_min = rd.device_shared->period_duration_current; dev_w_raw->period_duration = from_reference_time(min_device_period);
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;
if (rd.endpoint) if (rd.endpoint) {
IMMEndpoint_Release(rd.endpoint); IMMEndpoint_Release(rd.endpoint);
rd.endpoint = nullptr;
}
if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMEndpoint, (void**)&rd.endpoint))) { if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMEndpoint, (void**)&rd.endpoint))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
@ -663,15 +683,19 @@ static int refresh_devices(SoundIoPrivate *si) {
rd.device_shared->aim = data_flow_to_aim(data_flow); rd.device_shared->aim = data_flow_to_aim(data_flow);
rd.device_raw->aim = rd.device_shared->aim; rd.device_raw->aim = rd.device_shared->aim;
if (rd.prop_store) if (rd.prop_store) {
IPropertyStore_Release(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))) { if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) {
deinit_refresh_devices(&rd); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if (rd.prop_variant_value_inited) if (rd.prop_variant_value_inited) {
PropVariantClear(&rd.prop_variant_value); PropVariantClear(&rd.prop_variant_value);
rd.prop_variant_value_inited = false;
}
PropVariantInit(&rd.prop_variant_value); PropVariantInit(&rd.prop_variant_value);
rd.prop_variant_value_inited = true; rd.prop_variant_value_inited = true;
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, 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. // 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 // This is guaranteed to work, so we use this to modulate the sample
// rate while holding the format constant and vice versa. // 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); PropVariantClear(&rd.prop_variant_value);
rd.prop_variant_value_inited = false;
}
PropVariantInit(&rd.prop_variant_value); PropVariantInit(&rd.prop_variant_value);
rd.prop_variant_value_inited = true; rd.prop_variant_value_inited = true;
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AudioEngine_DeviceFormat, if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AudioEngine_DeviceFormat,
@ -727,9 +753,11 @@ static int refresh_devices(SoundIoPrivate *si) {
return err; return err;
} }
if (rd.wave_format) if (rd.wave_format) {
CoTaskMemFree(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); deinit_refresh_devices(&rd);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -768,6 +796,9 @@ static int refresh_devices(SoundIoPrivate *si) {
return err; return err;
} }
dev_w_shared->mm_device = rd.mm_device;
dev_w_raw->mm_device = rd.mm_device;
rd.mm_device = nullptr;
SoundIoList<SoundIoDevice *> *device_list; SoundIoList<SoundIoDevice *> *device_list;
if (rd.device_shared->aim == SoundIoDeviceAimOutput) { 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) { 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) { 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) { 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) { 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, 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) { 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;
} }

View file

@ -22,12 +22,14 @@
#include <functiondiscoverykeys_devpkey.h> #include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h> #include <mmreg.h>
#include <audioclient.h> #include <audioclient.h>
#include <audiosessiontypes.h>
int soundio_wasapi_init(struct SoundIoPrivate *si); int soundio_wasapi_init(struct SoundIoPrivate *si);
struct SoundIoDeviceWasapi { struct SoundIoDeviceWasapi {
IAudioClient *audio_client;
SoundIoList<SoundIoSampleRateRange> sample_rates; SoundIoList<SoundIoSampleRateRange> sample_rates;
double period_duration;
IMMDevice *mm_device;
}; };
struct SoundIoWasapi { struct SoundIoWasapi {
@ -49,6 +51,13 @@ struct SoundIoWasapi {
}; };
struct SoundIoOutStreamWasapi { 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 { struct SoundIoInStreamWasapi {

View file

@ -39,7 +39,7 @@ static void test_create_outstream(void) {
outstream->format = SoundIoFormatFloat32NE; outstream->format = SoundIoFormatFloat32NE;
outstream->sample_rate = 48000; outstream->sample_rate = 48000;
outstream->layout = device->layouts[0]; outstream->layout = device->layouts[0];
outstream->buffer_duration = 0.1; outstream->software_latency = 0.1;
outstream->write_callback = write_callback; outstream->write_callback = write_callback;
outstream->error_callback = error_callback; outstream->error_callback = error_callback;