mirror of
https://github.com/Ryujinx/libsoundio.git
synced 2024-12-22 22:25:28 +00:00
remove the concept of period duration from the API
also more progress on WASAPI
This commit is contained in:
parent
61b95a458c
commit
fbc7318268
|
@ -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.
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
76
src/alsa.cpp
76
src/alsa.cpp
|
@ -299,29 +299,14 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam
|
|||
snd_pcm_uframes_t min_frames;
|
||||
snd_pcm_uframes_t max_frames;
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_period_size_integer(handle, hwparams)) < 0)
|
||||
return SoundIoErrorIncompatibleDevice;
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_period_size_min(hwparams, &min_frames, nullptr)) < 0)
|
||||
return SoundIoErrorIncompatibleDevice;
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_period_size_max(hwparams, &max_frames, nullptr)) < 0)
|
||||
return SoundIoErrorIncompatibleDevice;
|
||||
|
||||
device->period_duration_min = min_frames * one_over_actual_rate;
|
||||
device->period_duration_max = max_frames * one_over_actual_rate;
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_period_size_first(handle, hwparams, &min_frames, nullptr)) < 0)
|
||||
return SoundIoErrorIncompatibleDevice;
|
||||
|
||||
|
||||
if ((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames)) < 0)
|
||||
return SoundIoErrorOpeningDevice;
|
||||
if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
||||
device->buffer_duration_min = min_frames * one_over_actual_rate;
|
||||
device->buffer_duration_max = max_frames * one_over_actual_rate;
|
||||
device->software_latency_min = min_frames * one_over_actual_rate;
|
||||
device->software_latency_max = max_frames * one_over_actual_rate;
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames)) < 0)
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
@ -443,11 +428,8 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
|
|||
if (device->sample_rates[0].min == device->sample_rates[0].max)
|
||||
device->sample_rate_current = device->sample_rates[0].min;
|
||||
|
||||
if (device->buffer_duration_min == device->buffer_duration_max)
|
||||
device->buffer_duration_current = device->buffer_duration_min;
|
||||
|
||||
if (device->period_duration_min == device->period_duration_max)
|
||||
device->period_duration_current = device->period_duration_min;
|
||||
if (device->software_latency_min == device->software_latency_max)
|
||||
device->software_latency_current = device->software_latency_min;
|
||||
|
||||
// now say that resampling is OK and see what the real min and max is.
|
||||
if ((err = probe_open_device(device, handle, 1, &channels_min, &channels_max)) < 0) {
|
||||
|
@ -1111,12 +1093,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
|
|||
SoundIoOutStream *outstream = &os->pub;
|
||||
SoundIoDevice *device = outstream->device;
|
||||
|
||||
if (outstream->buffer_duration == 0.0)
|
||||
outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max);
|
||||
if (outstream->period_duration == 0.0) {
|
||||
outstream->period_duration = clamp(device->period_duration_min,
|
||||
outstream->buffer_duration / 2.0, device->period_duration_max);
|
||||
}
|
||||
if (outstream->software_latency == 0.0)
|
||||
outstream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
|
||||
|
||||
int ch_count = outstream->layout.channel_count;
|
||||
|
||||
|
@ -1177,21 +1155,31 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
|
|||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t period_frames = ceil(outstream->period_duration * (double)outstream->sample_rate);
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(osa->handle, hwparams, &period_frames, nullptr)) < 0) {
|
||||
outstream_destroy_alsa(si, os);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
||||
if (device->is_raw) {
|
||||
unsigned int microseconds;
|
||||
if ((err = snd_pcm_hw_params_set_period_time_first(osa->handle, hwparams, µseconds, nullptr)) < 0) {
|
||||
outstream_destroy_alsa(si, os);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
} else {
|
||||
double period_duration = outstream->software_latency / 2.0;
|
||||
snd_pcm_uframes_t period_frames = ceil(period_duration * (double)outstream->sample_rate);
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(osa->handle, hwparams, &period_frames, nullptr)) < 0) {
|
||||
outstream_destroy_alsa(si, os);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
}
|
||||
outstream->period_duration = ((double)period_frames) / (double)outstream->sample_rate;
|
||||
|
||||
|
||||
snd_pcm_uframes_t buffer_size_frames = ceil(outstream->buffer_duration * (double)outstream->sample_rate);
|
||||
snd_pcm_uframes_t buffer_size_frames = ceil(outstream->software_latency * (double)outstream->sample_rate);
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_buffer_size_near(osa->handle, hwparams, &buffer_size_frames)) < 0) {
|
||||
outstream_destroy_alsa(si, os);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
outstream->buffer_duration = ((double)buffer_size_frames) / (double)outstream->sample_rate;
|
||||
outstream->software_latency = ((double)buffer_size_frames) / (double)outstream->sample_rate;
|
||||
|
||||
|
||||
|
||||
|
@ -1400,12 +1388,8 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|||
SoundIoInStream *instream = &is->pub;
|
||||
SoundIoDevice *device = instream->device;
|
||||
|
||||
if (instream->buffer_duration == 0.0)
|
||||
instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max);
|
||||
if (instream->period_duration == 0.0) {
|
||||
instream->period_duration = clamp(device->period_duration_min,
|
||||
instream->buffer_duration / 8.0, device->period_duration_max);
|
||||
}
|
||||
if (instream->software_latency == 0.0)
|
||||
instream->software_latency = clamp(device->software_latency_min, 1.0, device->software_latency_max);
|
||||
|
||||
int ch_count = instream->layout.channel_count;
|
||||
|
||||
|
@ -1466,21 +1450,19 @@ static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
|
||||
snd_pcm_uframes_t period_frames = ceil(instream->period_duration * (double)instream->sample_rate);
|
||||
snd_pcm_uframes_t period_frames = ceil(instream->software_latency * (double)instream->sample_rate);
|
||||
if ((err = snd_pcm_hw_params_set_period_size_near(isa->handle, hwparams, &period_frames, nullptr)) < 0) {
|
||||
instream_destroy_alsa(si, is);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
instream->period_duration = ((double)period_frames) / (double)instream->sample_rate;
|
||||
instream->software_latency = ((double)period_frames) / (double)instream->sample_rate;
|
||||
|
||||
|
||||
snd_pcm_uframes_t buffer_size_frames = ceil(instream->buffer_duration * (double)instream->sample_rate);
|
||||
|
||||
if ((err = snd_pcm_hw_params_set_buffer_size_near(isa->handle, hwparams, &buffer_size_frames)) < 0) {
|
||||
snd_pcm_uframes_t buffer_size_frames;
|
||||
if ((err = snd_pcm_hw_params_set_buffer_size_last(isa->handle, hwparams, &buffer_size_frames)) < 0) {
|
||||
instream_destroy_alsa(si, is);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
instream->buffer_duration = ((double)buffer_size_frames) / (double)instream->sample_rate;
|
||||
|
||||
snd_pcm_uframes_t period_size;
|
||||
if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) {
|
||||
|
|
|
@ -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)))
|
||||
{
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
17
src/jack.cpp
17
src/jack.cpp
|
@ -193,12 +193,11 @@ static int refresh_devices_bare(SoundIoPrivate *si) {
|
|||
device->sample_rates[0].min = sij->sample_rate;
|
||||
device->sample_rates[0].max = sij->sample_rate;
|
||||
device->sample_rate_current = sij->sample_rate;
|
||||
device->period_duration_min = sij->period_size / (double) sij->sample_rate;
|
||||
device->period_duration_max = device->period_duration_min;
|
||||
device->period_duration_current = device->period_duration_min;
|
||||
device->buffer_duration_min = device->period_duration_min;
|
||||
device->buffer_duration_max = device->period_duration_min;
|
||||
device->buffer_duration_current = device->period_duration_min;
|
||||
|
||||
device->software_latency_current = sij->period_size / (double) sij->sample_rate;
|
||||
device->software_latency_min = sij->period_size / (double) sij->sample_rate;
|
||||
device->software_latency_max = sij->period_size / (double) sij->sample_rate;
|
||||
|
||||
dj->port_count = client->port_count;
|
||||
dj->ports = allocate<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;
|
||||
|
|
|
@ -318,10 +318,6 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
|
|||
return;
|
||||
}
|
||||
|
||||
device->buffer_duration_min = 0.10;
|
||||
device->buffer_duration_max = 4.0;
|
||||
|
||||
// "period" is not a recognized concept in PulseAudio.
|
||||
|
||||
device->aim = SoundIoDeviceAimOutput;
|
||||
|
||||
|
@ -390,11 +386,6 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
|
|||
return;
|
||||
}
|
||||
|
||||
device->buffer_duration_min = 0.10;
|
||||
device->buffer_duration_max = 4.0;
|
||||
|
||||
// "period" is not a recognized concept in PulseAudio.
|
||||
|
||||
device->aim = SoundIoDeviceAimInput;
|
||||
|
||||
if (sipa->current_devices_info->input_devices.append(device)) {
|
||||
|
@ -710,16 +701,16 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
|||
ospa->buffer_attr.fragsize = UINT32_MAX;
|
||||
|
||||
int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate;
|
||||
if (outstream->buffer_duration > 0.0) {
|
||||
if (outstream->software_latency > 0.0) {
|
||||
int buffer_length = outstream->bytes_per_frame *
|
||||
ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame);
|
||||
ceil(outstream->software_latency * bytes_per_second / (double)outstream->bytes_per_frame);
|
||||
|
||||
ospa->buffer_attr.maxlength = buffer_length;
|
||||
ospa->buffer_attr.tlength = buffer_length;
|
||||
}
|
||||
|
||||
pa_stream_flags_t flags = PA_STREAM_START_CORKED;
|
||||
if (outstream->buffer_duration > 0.0)
|
||||
if (outstream->software_latency > 0.0)
|
||||
flags = (pa_stream_flags_t) (flags | PA_STREAM_ADJUST_LATENCY);
|
||||
|
||||
int err = pa_stream_connect_playback(ospa->stream,
|
||||
|
@ -734,7 +725,9 @@ static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
|||
pa_threaded_mainloop_wait(sipa->main_loop);
|
||||
|
||||
size_t writable_size = pa_stream_writable_size(ospa->stream);
|
||||
outstream->buffer_duration = writable_size / bytes_per_second;
|
||||
outstream->software_latency = writable_size / bytes_per_second;
|
||||
|
||||
// TODO get the correct software_latency value
|
||||
|
||||
pa_threaded_mainloop_unlock(sipa->main_loop);
|
||||
|
||||
|
@ -922,16 +915,10 @@ static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|||
ispa->buffer_attr.minreq = UINT32_MAX;
|
||||
ispa->buffer_attr.fragsize = UINT32_MAX;
|
||||
|
||||
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
|
||||
if (instream->buffer_duration > 0.0) {
|
||||
if (instream->software_latency > 0.0) {
|
||||
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
|
||||
int buffer_length = instream->bytes_per_frame *
|
||||
ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame);
|
||||
ispa->buffer_attr.maxlength = buffer_length;
|
||||
}
|
||||
|
||||
if (instream->period_duration > 0.0) {
|
||||
int buffer_length = instream->bytes_per_frame *
|
||||
ceil(instream->period_duration * bytes_per_second / (double)instream->bytes_per_frame);
|
||||
ceil(instream->software_latency * bytes_per_second / (double)instream->bytes_per_frame);
|
||||
ispa->buffer_attr.fragsize = buffer_length;
|
||||
}
|
||||
|
||||
|
@ -946,7 +933,7 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|||
SoundIoPulseAudio *sipa = &si->backend_data.pulseaudio;
|
||||
pa_threaded_mainloop_lock(sipa->main_loop);
|
||||
|
||||
pa_stream_flags_t flags = (instream->period_duration > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS;
|
||||
pa_stream_flags_t flags = (instream->software_latency > 0.0) ? PA_STREAM_ADJUST_LATENCY : PA_STREAM_NOFLAGS;
|
||||
|
||||
int err = pa_stream_connect_record(ispa->stream,
|
||||
instream->device->id,
|
||||
|
@ -959,12 +946,6 @@ static int instream_start_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
|||
while (!ispa->stream_ready)
|
||||
pa_threaded_mainloop_wait(sipa->main_loop);
|
||||
|
||||
const pa_buffer_attr *attr = pa_stream_get_buffer_attr(ispa->stream);
|
||||
instream->buffer_duration = (attr->maxlength /
|
||||
(double)instream->bytes_per_frame) / (double)instream->sample_rate;
|
||||
instream->period_duration = (attr->fragsize /
|
||||
(double)instream->bytes_per_frame) / (double)instream->sample_rate;
|
||||
|
||||
pa_threaded_mainloop_unlock(sipa->main_loop);
|
||||
return 0;
|
||||
}
|
||||
|
|
263
src/wasapi.cpp
263
src/wasapi.cpp
|
@ -18,6 +18,8 @@ const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = {
|
|||
const static GUID SOUNDIO_KSDATAFORMAT_SUBTYPE_PCM = {
|
||||
0x00000001,0x0000,0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
|
||||
|
||||
const static DWORD SOUNDIO_AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000;
|
||||
|
||||
// Adding more common sample rates helps the heuristics; feel free to do that.
|
||||
static int test_sample_rates[] = {
|
||||
8000,
|
||||
|
@ -271,6 +273,11 @@ static void to_wave_format_format(SoundIoFormat format, WAVEFORMATEXTENSIBLE *wa
|
|||
}
|
||||
}
|
||||
|
||||
static void complete_wave_format_data(WAVEFORMATEXTENSIBLE *wave_format) {
|
||||
wave_format->Format.nBlockAlign = (wave_format->Format.wBitsPerSample * wave_format->Format.nChannels) / 8;
|
||||
wave_format->Format.nAvgBytesPerSec = wave_format->Format.nSamplesPerSec * wave_format->Format.nBlockAlign;
|
||||
}
|
||||
|
||||
static SoundIoDeviceAim data_flow_to_aim(EDataFlow data_flow) {
|
||||
return (data_flow == eRender) ? SoundIoDeviceAimOutput : SoundIoDeviceAimInput;
|
||||
}
|
||||
|
@ -280,10 +287,14 @@ static double from_reference_time(REFERENCE_TIME rt) {
|
|||
return ((double)rt) / 10000000.0;
|
||||
}
|
||||
|
||||
static REFERENCE_TIME to_reference_time(double seconds) {
|
||||
return seconds * 10000000.0 + 0.5;
|
||||
}
|
||||
|
||||
static void destruct_device(SoundIoDevicePrivate *dev) {
|
||||
SoundIoDeviceWasapi *dw = &dev->backend_data.wasapi;
|
||||
if (dw->audio_client)
|
||||
IUnknown_Release(dw->audio_client);
|
||||
if (dw->mm_device)
|
||||
IMMDevice_Release(dw->mm_device);
|
||||
dw->sample_rates.deinit();
|
||||
}
|
||||
|
||||
|
@ -294,6 +305,7 @@ struct RefreshDevices {
|
|||
IMMDevice *default_capture_device;
|
||||
IMMEndpoint *endpoint;
|
||||
IPropertyStore *prop_store;
|
||||
IAudioClient *audio_client;
|
||||
LPWSTR lpwstr;
|
||||
PROPVARIANT prop_variant_value;
|
||||
WAVEFORMATEXTENSIBLE *wave_format;
|
||||
|
@ -329,6 +341,8 @@ static void deinit_refresh_devices(RefreshDevices *rd) {
|
|||
PropVariantClear(&rd->prop_variant_value);
|
||||
if (rd->wave_format)
|
||||
CoTaskMemFree(rd->wave_format);
|
||||
if (rd->audio_client)
|
||||
IUnknown_Release(rd->audio_client);
|
||||
}
|
||||
|
||||
static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_format,
|
||||
|
@ -350,8 +364,9 @@ static int detect_valid_layouts(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_f
|
|||
SoundIoChannelLayoutId test_layout_id = test_layouts[i];
|
||||
const SoundIoChannelLayout *test_layout = soundio_channel_layout_get_builtin(test_layout_id);
|
||||
to_wave_format_layout(test_layout, wave_format);
|
||||
complete_wave_format_data(wave_format);
|
||||
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(rd->audio_client, share_mode,
|
||||
(WAVEFORMATEX*)wave_format, &closest_match);
|
||||
if (closest_match) {
|
||||
CoTaskMemFree(closest_match);
|
||||
|
@ -389,8 +404,9 @@ static int detect_valid_formats(RefreshDevices *rd, WAVEFORMATEXTENSIBLE *wave_f
|
|||
for (int i = 0; i < array_length(test_formats); i += 1) {
|
||||
SoundIoFormat test_format = test_formats[i];
|
||||
to_wave_format_format(test_format, wave_format);
|
||||
complete_wave_format_data(wave_format);
|
||||
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(dw->audio_client, share_mode,
|
||||
HRESULT hr = IAudioClient_IsFormatSupported(rd->audio_client, share_mode,
|
||||
(WAVEFORMATEX*)wave_format, &closest_match);
|
||||
if (closest_match) {
|
||||
CoTaskMemFree(closest_match);
|
||||
|
@ -421,7 +437,7 @@ static int add_sample_rate(SoundIoList<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,
|
||||
¤t_min, &last_success_rate)))
|
||||
{
|
||||
wave_format->Format.nSamplesPerSec = orig_sample_rate;
|
||||
|
@ -507,8 +523,10 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
if (rd.lpwstr)
|
||||
if (rd.lpwstr) {
|
||||
CoTaskMemFree(rd.lpwstr);
|
||||
rd.lpwstr = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_GetId(rd.default_render_device, &rd.lpwstr))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
@ -525,8 +543,10 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
if (rd.lpwstr)
|
||||
if (rd.lpwstr) {
|
||||
CoTaskMemFree(rd.lpwstr);
|
||||
rd.lpwstr = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_GetId(rd.default_capture_device, &rd.lpwstr))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
@ -565,14 +585,18 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
rd.devices_info->default_output_index = -1;
|
||||
|
||||
for (int device_i = 0; device_i < device_count; device_i += 1) {
|
||||
if (rd.mm_device)
|
||||
if (rd.mm_device) {
|
||||
IMMDevice_Release(rd.mm_device);
|
||||
rd.mm_device = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDeviceCollection_Item(rd.collection, device_i, &rd.mm_device))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
if (rd.lpwstr)
|
||||
if (rd.lpwstr) {
|
||||
CoTaskMemFree(rd.lpwstr);
|
||||
rd.lpwstr = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_GetId(rd.mm_device, &rd.lpwstr))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
@ -618,37 +642,33 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
return SoundIoErrorNoMem;
|
||||
}
|
||||
|
||||
if (rd.audio_client) {
|
||||
IUnknown_Release(rd.audio_client);
|
||||
rd.audio_client = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_Activate(rd.mm_device, IID_IAudioClient,
|
||||
CLSCTX_ALL, nullptr, (void**)&dev_w_shared->audio_client)))
|
||||
CLSCTX_ALL, nullptr, (void**)&rd.audio_client)))
|
||||
{
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
|
||||
if (FAILED(hr = IAudioClient_AddRef(dev_w_shared->audio_client))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
dev_w_raw->audio_client = dev_w_shared->audio_client;
|
||||
|
||||
REFERENCE_TIME default_device_period;
|
||||
REFERENCE_TIME min_device_period;
|
||||
if (FAILED(hr = IAudioClient_GetDevicePeriod(dev_w_shared->audio_client,
|
||||
if (FAILED(hr = IAudioClient_GetDevicePeriod(rd.audio_client,
|
||||
&default_device_period, &min_device_period)))
|
||||
{
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
rd.device_shared->period_duration_current = from_reference_time(default_device_period);
|
||||
rd.device_shared->period_duration_min = rd.device_shared->period_duration_current;
|
||||
rd.device_shared->period_duration_max = rd.device_shared->period_duration_current;
|
||||
|
||||
rd.device_raw->period_duration_min = from_reference_time(min_device_period);
|
||||
rd.device_raw->period_duration_max = 4.0;
|
||||
dev_w_shared->period_duration = from_reference_time(default_device_period);
|
||||
dev_w_raw->period_duration = from_reference_time(min_device_period);
|
||||
|
||||
|
||||
if (rd.endpoint)
|
||||
if (rd.endpoint) {
|
||||
IMMEndpoint_Release(rd.endpoint);
|
||||
rd.endpoint = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_QueryInterface(rd.mm_device, IID_IMMEndpoint, (void**)&rd.endpoint))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
|
@ -663,15 +683,19 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
rd.device_shared->aim = data_flow_to_aim(data_flow);
|
||||
rd.device_raw->aim = rd.device_shared->aim;
|
||||
|
||||
if (rd.prop_store)
|
||||
if (rd.prop_store) {
|
||||
IPropertyStore_Release(rd.prop_store);
|
||||
rd.prop_store = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IMMDevice_OpenPropertyStore(rd.mm_device, STGM_READ, &rd.prop_store))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
|
||||
if (rd.prop_variant_value_inited)
|
||||
if (rd.prop_variant_value_inited) {
|
||||
PropVariantClear(&rd.prop_variant_value);
|
||||
rd.prop_variant_value_inited = false;
|
||||
}
|
||||
PropVariantInit(&rd.prop_variant_value);
|
||||
rd.prop_variant_value_inited = true;
|
||||
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store,
|
||||
|
@ -699,8 +723,10 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
// Get the format that WASAPI opens the device with for shared streams.
|
||||
// This is guaranteed to work, so we use this to modulate the sample
|
||||
// rate while holding the format constant and vice versa.
|
||||
if (rd.prop_variant_value_inited)
|
||||
if (rd.prop_variant_value_inited) {
|
||||
PropVariantClear(&rd.prop_variant_value);
|
||||
rd.prop_variant_value_inited = false;
|
||||
}
|
||||
PropVariantInit(&rd.prop_variant_value);
|
||||
rd.prop_variant_value_inited = true;
|
||||
if (FAILED(hr = IPropertyStore_GetValue(rd.prop_store, PKEY_AudioEngine_DeviceFormat,
|
||||
|
@ -727,9 +753,11 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
return err;
|
||||
}
|
||||
|
||||
if (rd.wave_format)
|
||||
if (rd.wave_format) {
|
||||
CoTaskMemFree(rd.wave_format);
|
||||
if (FAILED(hr = IAudioClient_GetMixFormat(dev_w_shared->audio_client, (WAVEFORMATEX**)&rd.wave_format))) {
|
||||
rd.wave_format = nullptr;
|
||||
}
|
||||
if (FAILED(hr = IAudioClient_GetMixFormat(rd.audio_client, (WAVEFORMATEX**)&rd.wave_format))) {
|
||||
deinit_refresh_devices(&rd);
|
||||
return SoundIoErrorOpeningDevice;
|
||||
}
|
||||
|
@ -768,6 +796,9 @@ static int refresh_devices(SoundIoPrivate *si) {
|
|||
return err;
|
||||
}
|
||||
|
||||
dev_w_shared->mm_device = rd.mm_device;
|
||||
dev_w_raw->mm_device = rd.mm_device;
|
||||
rd.mm_device = nullptr;
|
||||
|
||||
SoundIoList<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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue