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
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.

View file

@ -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");
}

View file

@ -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;

View file

@ -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);

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 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 (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) {
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) {

View file

@ -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<SoundIoDevice *> *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)))
{

View file

@ -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)) {

View file

@ -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;

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].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<SoundIoDeviceJackPort>(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;

View file

@ -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;
if (instream->software_latency > 0.0) {
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
if (instream->buffer_duration > 0.0) {
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;
}

View file

@ -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<SoundIoSampleRateRange> *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,
&current_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<SoundIoDevice *> *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;
}

View file

@ -22,12 +22,14 @@
#include <functiondiscoverykeys_devpkey.h>
#include <mmreg.h>
#include <audioclient.h>
#include <audiosessiontypes.h>
int soundio_wasapi_init(struct SoundIoPrivate *si);
struct SoundIoDeviceWasapi {
IAudioClient *audio_client;
SoundIoList<SoundIoSampleRateRange> 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 {

View file

@ -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;