ALSA: open output device with parameters

This commit is contained in:
Andrew Kelley 2015-07-13 21:30:37 -07:00
parent 59026ccba7
commit 1e4d87e608
13 changed files with 418 additions and 92 deletions

View file

@ -15,10 +15,12 @@ exposed.
* [PortAudio](http://www.portaudio.com/)
- It does not support [PulseAudio](http://www.freedesktop.org/wiki/Software/PulseAudio/).
- It logs messages to stdio and you can't turn that off.
- It does not support channel layouts / channel maps.
- It is not written by me.
* [rtaudio](https://www.music.mcgill.ca/~gary/rtaudio/)
- It is not a C library.
- It uses [exceptions](http://stackoverflow.com/questions/1736146/why-is-exception-handling-bad).
- It does not support channel layouts / channel maps.
- It is not written by me.
* [SDL](https://www.libsdl.org/)
- It comes with a bunch of other baggage - display, windowing, input
@ -26,6 +28,7 @@ exposed.
- It is not designed with real-time low latency audio in mind.
- Listing audio devices is [broken](https://github.com/andrewrk/node-groove/issues/13).
- It does not support recording devices.
- It does not support channel layouts / channel maps.
- It is not written by me.
## How It Works

View file

@ -121,7 +121,7 @@ int main(int argc, char **argv) {
instream->format = *fmt;
instream->sample_rate = sample_rate;
instream->layout = *layout;
instream->latency = 0.1;
instream->buffer_duration = 0.1;
instream->read_callback = read_callback;
if ((err = soundio_instream_open(instream)))
@ -133,7 +133,7 @@ int main(int argc, char **argv) {
outstream->format = *fmt;
outstream->sample_rate = sample_rate;
outstream->layout = *layout;
outstream->latency = 0.1;
outstream->buffer_duration = 0.1;
outstream->write_callback = write_callback;
outstream->underrun_callback = underrun_callback;

View file

@ -27,8 +27,6 @@ static void panic(const char *format, ...) {
static const float PI = 3.1415926535f;
static float seconds_offset = 0.0f;
static int target_sample_rate = 48000;
static void write_callback(struct SoundIoOutStream *outstream, int requested_frame_count) {
float float_sample_rate = outstream->sample_rate;
float seconds_per_frame = 1.0f / float_sample_rate;
@ -45,7 +43,6 @@ static void write_callback(struct SoundIoOutStream *outstream, int requested_fra
float *ptr = (float *)data;
// 69 is A 440
float pitch = 440.0f;
float radians_per_second = pitch * 2.0f * PI;
for (int frame = 0; frame < frame_count; frame += 1) {
@ -83,16 +80,12 @@ int main(int argc, char **argv) {
struct SoundIoDevice *device = soundio_get_output_device(soundio, default_out_device_index);
if (!device)
panic("could not get output device: out of memory");
panic("out of memory");
fprintf(stderr, "Output device: %s: %s\n", device->name, device->description);
struct SoundIoOutStream *outstream = soundio_outstream_create(device);
outstream->format = SoundIoFormatFloat32NE;
outstream->sample_rate = (device->sample_rate_min <= target_sample_rate &&
target_sample_rate <= device->sample_rate_max) ? target_sample_rate : device->sample_rate_max;
outstream->layout = device->layouts[0];
outstream->latency = 0.1;
outstream->write_callback = write_callback;
outstream->underrun_callback = underrun_callback;

View file

@ -12,6 +12,7 @@
#include <alsa/asoundlib.h>
#include <sys/inotify.h>
#include <math.h>
static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE};
@ -31,7 +32,9 @@ struct SoundIoAlsa {
};
struct SoundIoOutStreamAlsa {
snd_pcm_t *handle;
snd_pcm_chmap_t *chmap;
int chmap_size;
};
static void wakeup_device_poll(SoundIoAlsa *sia) {
@ -136,6 +139,47 @@ static SoundIoChannelId from_alsa_chmap_pos(unsigned int pos) {
return SoundIoChannelIdInvalid;
}
static int to_alsa_chmap_pos(SoundIoChannelId channel_id) {
switch (channel_id) {
case SoundIoChannelIdFrontLeft: return SND_CHMAP_FL;
case SoundIoChannelIdFrontRight: return SND_CHMAP_FR;
case SoundIoChannelIdBackLeft: return SND_CHMAP_RL;
case SoundIoChannelIdBackRight: return SND_CHMAP_RR;
case SoundIoChannelIdFrontCenter: return SND_CHMAP_FC;
case SoundIoChannelIdLfe: return SND_CHMAP_LFE;
case SoundIoChannelIdSideLeft: return SND_CHMAP_SL;
case SoundIoChannelIdSideRight: return SND_CHMAP_SR;
case SoundIoChannelIdBackCenter: return SND_CHMAP_RC;
case SoundIoChannelIdFrontLeftCenter: return SND_CHMAP_FLC;
case SoundIoChannelIdFrontRightCenter: return SND_CHMAP_FRC;
case SoundIoChannelIdBackLeftCenter: return SND_CHMAP_RLC;
case SoundIoChannelIdBackRightCenter: return SND_CHMAP_RRC;
case SoundIoChannelIdFrontLeftWide: return SND_CHMAP_FLW;
case SoundIoChannelIdFrontRightWide: return SND_CHMAP_FRW;
case SoundIoChannelIdFrontLeftHigh: return SND_CHMAP_FLH;
case SoundIoChannelIdFrontCenterHigh: return SND_CHMAP_FCH;
case SoundIoChannelIdFrontRightHigh: return SND_CHMAP_FRH;
case SoundIoChannelIdTopCenter: return SND_CHMAP_TC;
case SoundIoChannelIdTopFrontLeft: return SND_CHMAP_TFL;
case SoundIoChannelIdTopFrontRight: return SND_CHMAP_TFR;
case SoundIoChannelIdTopFrontCenter: return SND_CHMAP_TFC;
case SoundIoChannelIdTopBackLeft: return SND_CHMAP_TRL;
case SoundIoChannelIdTopBackRight: return SND_CHMAP_TRR;
case SoundIoChannelIdTopBackCenter: return SND_CHMAP_TRC;
case SoundIoChannelIdTopFrontLeftCenter: return SND_CHMAP_TFLC;
case SoundIoChannelIdTopFrontRightCenter: return SND_CHMAP_TFRC;
case SoundIoChannelIdTopSideLeft: return SND_CHMAP_TSL;
case SoundIoChannelIdTopSideRight: return SND_CHMAP_TSR;
case SoundIoChannelIdLeftLfe: return SND_CHMAP_LLFE;
case SoundIoChannelIdRightLfe: return SND_CHMAP_RLFE;
case SoundIoChannelIdBottomCenter: return SND_CHMAP_BC;
case SoundIoChannelIdBottomLeftCenter: return SND_CHMAP_BLC;
case SoundIoChannelIdBottomRightCenter: return SND_CHMAP_BRC;
case SoundIoChannelIdInvalid: return SND_CHMAP_UNKNOWN;
}
return SND_CHMAP_UNKNOWN;
}
static void get_channel_layout(SoundIoChannelLayout *dest, snd_pcm_chmap_t *chmap) {
int channel_count = min((unsigned int)SOUNDIO_MAX_CHANNELS, chmap->channels);
dest->channel_count = channel_count;
@ -209,15 +253,6 @@ static void test_fmt_mask(SoundIoDevice *device, const snd_pcm_format_mask_t *fm
}
}
// TODO: look at http://www.alsa-project.org/alsa-doc/alsa-lib/_2test_2pcm_8c-example.html#a27
// deterimine what do do about:
// * hw buffer size
// * hw period time
// * sw start threshold
// * sw avail min
// TODO: device->default_latency
// this function does not override device->formats, so if you want it to, deallocate and set it to NULL
static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle,
snd_pcm_hw_params_t *hwparams, int resample)
@ -237,20 +272,67 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle,
if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channel_count)) < 0)
return SoundIoErrorOpeningDevice;
unsigned int min_sample_rate;
unsigned int max_sample_rate;
int min_dir;
int max_dir;
unsigned int num;
unsigned int den;
if ((err = snd_pcm_hw_params_get_rate_max(hwparams, &max_sample_rate, &max_dir)) < 0)
int dir = 0;
if ((err = snd_pcm_hw_params_set_rate_first(handle, hwparams, &num, &dir)) < 0)
return SoundIoErrorOpeningDevice;
if (max_dir < 0)
max_sample_rate -= 1;
if ((err = snd_pcm_hw_params_get_rate_min(hwparams, &min_sample_rate, &min_dir)) < 0)
if ((err = snd_pcm_hw_params_get_rate_numden(hwparams, &num, &den)) < 0)
return SoundIoErrorOpeningDevice;
if (min_dir > 0)
min_sample_rate += 1;
if (den != 1)
return SoundIoErrorOpeningDevice;
device->sample_rate_min = num;
dir = 0;
if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &num, &dir)) < 0)
return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_get_rate_numden(hwparams, &num, &den)) < 0)
return SoundIoErrorOpeningDevice;
if (den != 1)
return SoundIoErrorOpeningDevice;
device->sample_rate_max = num;
// Purposefully leave the parameters with the highest rate, highest channel count.
snd_pcm_uframes_t min_frames;
snd_pcm_uframes_t max_frames;
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;
// divide the frame counts by the max sample rate
device->buffer_duration_min = ((double)min_frames) / (double)device->sample_rate_max;
device->buffer_duration_max = ((double)max_frames) / (double)device->sample_rate_max;
if ((err = snd_pcm_hw_params_set_periods_integer(handle, hwparams)) < 0)
return SoundIoErrorOpeningDevice;
dir = 0;
if ((err = snd_pcm_hw_params_get_periods_min(hwparams, &num, &dir)) < 0)
return SoundIoErrorOpeningDevice;
if (dir != 0)
return SoundIoErrorOpeningDevice;
device->period_count_min = num;
dir = 0;
if ((err = snd_pcm_hw_params_get_periods_max(hwparams, &num, &dir)) < 0)
return SoundIoErrorOpeningDevice;
if (dir != 0)
return SoundIoErrorOpeningDevice;
device->period_count_max = num;
snd_pcm_format_mask_t *fmt_mask;
snd_pcm_format_mask_alloca(&fmt_mask);
@ -304,9 +386,6 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle,
test_fmt_mask(device, fmt_mask, SoundIoFormatFloat64BE);
}
device->sample_rate_min = min_sample_rate;
device->sample_rate_max = max_sample_rate;
return 0;
}
@ -347,8 +426,15 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
}
maps = nullptr;
if (device->sample_rate_min == device->sample_rate_max && !device->is_raw) {
device->sample_rate_current = device->sample_rate_min;
if (!device->is_raw) {
if (device->sample_rate_min == device->sample_rate_max)
device->sample_rate_current = device->sample_rate_min;
if (device->buffer_duration_min == device->buffer_duration_max)
device->buffer_duration_current = device->buffer_duration_min;
if (device->period_count_min == device->period_count_max)
device->period_count_current = device->period_count_min;
// now say that resampling is OK and see what the real min and max is.
if ((err = probe_open_device(device, handle, hwparams, 1)) < 0) {
@ -754,11 +840,17 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
if (!osa)
return;
deallocate(osa->chmap, osa->chmap_size);
if (osa->handle)
snd_pcm_close(osa->handle);
destroy(osa);
os->backend_data = nullptr;
}
static int outstream_init_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamAlsa *osa = create<SoundIoOutStreamAlsa>();
if (!osa) {
outstream_destroy_alsa(si, os);
@ -766,6 +858,119 @@ static int outstream_init_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
}
os->backend_data = osa;
osa->chmap_size = sizeof(int) + sizeof(int) * outstream->layout.channel_count;
osa->chmap = (snd_pcm_chmap_t *)allocate<char>(osa->chmap_size);
if (!osa->chmap) {
outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem;
}
int err;
snd_pcm_hw_params_t *hwparams;
snd_pcm_hw_params_alloca(&hwparams);
snd_pcm_stream_t stream = purpose_to_stream(outstream->device->purpose);
if ((err = snd_pcm_open(&osa->handle, outstream->device->name, stream, 0)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_any(osa->handle, hwparams)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
int want_resample = !outstream->device->is_raw;
if ((err = snd_pcm_hw_params_set_rate_resample(osa->handle, hwparams, want_resample)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_set_access(osa->handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_set_channels(osa->handle, hwparams, outstream->layout.channel_count)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_set_rate(osa->handle, hwparams, outstream->sample_rate, 0)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_hw_params_set_format(osa->handle, hwparams, to_alsa_fmt(outstream->format))) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
snd_pcm_uframes_t buffer_size_frames = ceil(outstream->buffer_duration * (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;
unsigned int actual_periods_count = outstream->period_count;
if ((err = snd_pcm_hw_params_set_periods_near(osa->handle, hwparams, &actual_periods_count, nullptr)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
outstream->period_count = actual_periods_count;
snd_pcm_uframes_t period_size;
if ((snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
// write the hardware parameters to device
if ((err = snd_pcm_hw_params(osa->handle, hwparams)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
// set channel map
osa->chmap->channels = outstream->layout.channel_count;
for (int i = 0; i < outstream->layout.channel_count; i += 1) {
osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]);
}
if (snd_pcm_set_chmap(osa->handle, osa->chmap) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
// get current swparams
snd_pcm_sw_params_t *swparams;
snd_pcm_sw_params_alloca(&swparams);
if ((err = snd_pcm_sw_params_current(osa->handle, swparams)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, buffer_size_frames)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
if ((err = snd_pcm_sw_params_set_avail_min(osa->handle, swparams, period_size)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
// write the software parameters to device
if ((err = snd_pcm_sw_params(osa->handle, swparams)) < 0) {
outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice;
}
return 0;
}
@ -795,7 +1000,7 @@ static void outstream_clear_buffer_alsa(SoundIoPrivate *si,
soundio_panic("TODO");
}
static int instream_init_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
@ -900,7 +1105,7 @@ int soundio_alsa_init(SoundIoPrivate *si) {
si->wait_events = wait_events;
si->wakeup = wakeup;
si->outstream_init = outstream_init_alsa;
si->outstream_open = outstream_open_alsa;
si->outstream_destroy = outstream_destroy_alsa;
si->outstream_start = outstream_start_alsa;
si->outstream_free_count = outstream_free_count_alsa;
@ -908,7 +1113,7 @@ int soundio_alsa_init(SoundIoPrivate *si) {
si->outstream_write = outstream_write_alsa;
si->outstream_clear_buffer = outstream_clear_buffer_alsa;
si->instream_init = instream_init_alsa;
si->instream_open = instream_open_alsa;
si->instream_destroy = instream_destroy_alsa;
si->instream_start = instream_start_alsa;
si->instream_peek = instream_peek_alsa;

View file

@ -131,7 +131,7 @@ static void outstream_destroy_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate
os->backend_data = nullptr;
}
static int outstream_init_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
static int outstream_open_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamDummy *osd = create<SoundIoOutStreamDummy>();
if (!osd) {
@ -140,9 +140,8 @@ static int outstream_init_dummy(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
}
os->backend_data = osd;
int buffer_frame_count = outstream->latency * outstream->sample_rate;
osd->buffer_size = outstream->bytes_per_frame * buffer_frame_count;
osd->period = outstream->latency * 0.5;
osd->buffer_size = outstream->bytes_per_frame * outstream->buffer_duration;
osd->period = outstream->buffer_duration / (double)outstream->period_count;
soundio_ring_buffer_init(&osd->ring_buffer, osd->buffer_size);
@ -205,7 +204,7 @@ static void outstream_clear_buffer_dummy(SoundIoPrivate *si, SoundIoOutStreamPri
soundio_ring_buffer_clear(&osd->ring_buffer);
}
static int instream_init_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
static int instream_open_dummy(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
soundio_panic("TODO");
}
@ -237,24 +236,24 @@ static int set_all_device_formats(SoundIoDevice *device) {
if (!device->formats)
return SoundIoErrorNoMem;
device->formats[0] = SoundIoFormatS8;
device->formats[1] = SoundIoFormatU8;
device->formats[2] = SoundIoFormatS16LE;
device->formats[3] = SoundIoFormatS16BE;
device->formats[4] = SoundIoFormatU16LE;
device->formats[5] = SoundIoFormatU16BE;
device->formats[6] = SoundIoFormatS24LE;
device->formats[7] = SoundIoFormatS24BE;
device->formats[8] = SoundIoFormatU24LE;
device->formats[9] = SoundIoFormatU24BE;
device->formats[10] = SoundIoFormatS32LE;
device->formats[11] = SoundIoFormatS32BE;
device->formats[12] = SoundIoFormatU32LE;
device->formats[13] = SoundIoFormatU32BE;
device->formats[14] = SoundIoFormatFloat32LE;
device->formats[15] = SoundIoFormatFloat32BE;
device->formats[16] = SoundIoFormatFloat64LE;
device->formats[17] = SoundIoFormatFloat64BE;
device->formats[0] = SoundIoFormatFloat32NE;
device->formats[1] = SoundIoFormatFloat32FE;
device->formats[2] = SoundIoFormatS32NE;
device->formats[3] = SoundIoFormatS32FE;
device->formats[4] = SoundIoFormatU32NE;
device->formats[5] = SoundIoFormatU32FE;
device->formats[6] = SoundIoFormatS24NE;
device->formats[7] = SoundIoFormatS24FE;
device->formats[8] = SoundIoFormatU24NE;
device->formats[9] = SoundIoFormatU24FE;
device->formats[10] = SoundIoFormatFloat64NE;
device->formats[11] = SoundIoFormatFloat64FE;
device->formats[12] = SoundIoFormatS16NE;
device->formats[13] = SoundIoFormatS16FE;
device->formats[14] = SoundIoFormatU16NE;
device->formats[15] = SoundIoFormatU16FE;
device->formats[16] = SoundIoFormatS8;
device->formats[17] = SoundIoFormatU8;
return 0;
}
@ -324,10 +323,15 @@ int soundio_dummy_init(SoundIoPrivate *si) {
return err;
}
device->default_latency = 0.01;
device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000;
device->period_count_min = 1;
device->period_count_max = 16;
device->period_count_current = 2;
device->purpose = SoundIoDevicePurposeOutput;
if (si->safe_devices_info->output_devices.append(device)) {
@ -370,10 +374,15 @@ int soundio_dummy_init(SoundIoPrivate *si) {
destroy_dummy(si);
return err;
}
device->default_latency = 0.01;
device->buffer_duration_min = 0.01;
device->buffer_duration_max = 4;
device->buffer_duration_current = 0.1;
device->sample_rate_min = 2;
device->sample_rate_max = 5644800;
device->sample_rate_current = 48000;
device->period_count_min = 1;
device->period_count_max = 16;
device->period_count_current = 2;
device->purpose = SoundIoDevicePurposeInput;
if (si->safe_devices_info->input_devices.append(device)) {
@ -389,7 +398,7 @@ int soundio_dummy_init(SoundIoPrivate *si) {
si->wait_events = wait_events;
si->wakeup = wakeup;
si->outstream_init = outstream_init_dummy;
si->outstream_open = outstream_open_dummy;
si->outstream_destroy = outstream_destroy_dummy;
si->outstream_start = outstream_start_dummy;
si->outstream_free_count = outstream_free_count_dummy;
@ -397,7 +406,7 @@ int soundio_dummy_init(SoundIoPrivate *si) {
si->outstream_write = outstream_write_dummy;
si->outstream_clear_buffer = outstream_clear_buffer_dummy;
si->instream_init = instream_init_dummy;
si->instream_open = instream_open_dummy;
si->instream_destroy = instream_destroy_dummy;
si->instream_start = instream_start_dummy;
si->instream_peek = instream_peek_dummy;

View file

@ -695,6 +695,7 @@ void soundio_os_destroy_mirrored_memory(char *address, size_t capacity) {
if (!address)
return;
#if defined(SOUNDIO_OS_WINDOWS)
// TODO
#else
int err = munmap(address, 2 * capacity);
assert(!err);

View file

@ -134,9 +134,11 @@ static void destroy_pa(SoundIoPrivate *si) {
si->backend_data = nullptr;
}
/* TODO
static double usec_to_sec(pa_usec_t usec) {
return (double)usec / (double)PA_USEC_PER_SEC;
}
*/
static SoundIoFormat format_from_pulseaudio(pa_sample_spec sample_spec) {
@ -162,9 +164,11 @@ static SoundIoFormat format_from_pulseaudio(pa_sample_spec sample_spec) {
return SoundIoFormatInvalid;
}
/* TODO
static int sample_rate_from_pulseaudio(pa_sample_spec sample_spec) {
return sample_spec.rate;
}
*/
/* TODO
static SoundIoChannelId from_pulseaudio_channel_pos(pa_channel_position_t pos) {
@ -287,8 +291,10 @@ static void sink_info_callback(pa_context *pulse_context, const pa_sink_info *in
// TODO determine the channel layouts supported
//TODO set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout);
device->current_format = format_from_pulseaudio(info->sample_spec);
device->default_latency = usec_to_sec(info->configured_latency);
device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec);
// TODO set min, max, current latency
//device->default_latency = usec_to_sec(info->configured_latency);
// TODO set min, max, current sample rate
//device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec);
device->purpose = SoundIoDevicePurposeOutput;
if (sipa->current_devices_info->output_devices.append(device))
@ -319,8 +325,10 @@ static void source_info_callback(pa_context *pulse_context, const pa_source_info
// TODO determine the channel layouts supported
// TODO set_from_pulseaudio_channel_map(info->channel_map, &device->channel_layout);
device->current_format = format_from_pulseaudio(info->sample_spec);
device->default_latency = usec_to_sec(info->configured_latency);
device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec);
// TODO set min, max, current latency
//device->default_latency = usec_to_sec(info->configured_latency);
// TODO set min, max, current sample rate
//device->sample_rate_current = sample_rate_from_pulseaudio(info->sample_spec);
device->purpose = SoundIoDevicePurposeInput;
if (sipa->current_devices_info->input_devices.append(device))
@ -590,8 +598,9 @@ static void outstream_destroy_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os
os->backend_data = nullptr;
}
static int outstream_init_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
static int outstream_open_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
SoundIoOutStream *outstream = &os->pub;
SoundIoOutStreamPulseAudio *ospa = create<SoundIoOutStreamPulseAudio>();
if (!ospa) {
outstream_destroy_pa(si, os);
@ -626,7 +635,7 @@ static int outstream_init_pa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
int bytes_per_second = outstream->bytes_per_frame * outstream->sample_rate;
int buffer_length = outstream->bytes_per_frame *
ceil(outstream->latency * bytes_per_second / (double)outstream->bytes_per_frame);
ceil(outstream->buffer_duration * bytes_per_second / (double)outstream->bytes_per_frame);
ospa->buffer_attr.maxlength = buffer_length;
ospa->buffer_attr.tlength = buffer_length;
@ -757,7 +766,7 @@ static void instream_destroy_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *inst
}
}
static int instream_init_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
static int instream_open_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
SoundIoInStream *instream = &is->pub;
SoundIoInStreamPulseAudio *ispa = create<SoundIoInStreamPulseAudio>();
if (!ispa) {
@ -793,7 +802,7 @@ static int instream_init_pa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
int bytes_per_second = instream->bytes_per_frame * instream->sample_rate;
int buffer_length = instream->bytes_per_frame *
ceil(instream->latency * bytes_per_second / (double)instream->bytes_per_frame);
ceil(instream->buffer_duration * bytes_per_second / (double)instream->bytes_per_frame);
ispa->buffer_attr.maxlength = UINT32_MAX;
ispa->buffer_attr.tlength = UINT32_MAX;
@ -937,7 +946,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) {
si->wait_events = wait_events;
si->wakeup = wakeup;
si->outstream_init = outstream_init_pa;
si->outstream_open = outstream_open_pa;
si->outstream_destroy = outstream_destroy_pa;
si->outstream_start = outstream_start_pa;
si->outstream_free_count = outstream_free_count_pa;
@ -945,7 +954,7 @@ int soundio_pulseaudio_init(SoundIoPrivate *si) {
si->outstream_write = outstream_write_pa;
si->outstream_clear_buffer = outstream_clear_buffer_pa;
si->instream_init = instream_init_pa;
si->instream_open = instream_open_pa;
si->instream_destroy = instream_destroy_pa;
si->instream_start = instream_start_pa;
si->instream_peek = instream_peek_pa;

View file

@ -208,7 +208,7 @@ void soundio_disconnect(struct SoundIo *soundio) {
si->wait_events = nullptr;
si->wakeup = nullptr;
si->outstream_init = nullptr;
si->outstream_open = nullptr;
si->outstream_destroy = nullptr;
si->outstream_start = nullptr;
si->outstream_free_count = nullptr;
@ -216,7 +216,7 @@ void soundio_disconnect(struct SoundIo *soundio) {
si->outstream_write = nullptr;
si->outstream_clear_buffer = nullptr;
si->instream_init = nullptr;
si->instream_open = nullptr;
si->instream_destroy = nullptr;
si->instream_start = nullptr;
si->instream_peek = nullptr;
@ -304,6 +304,7 @@ void soundio_device_unref(struct SoundIoDevice *device) {
free(device->name);
free(device->description);
deallocate(device->formats, device->format_count);
deallocate(device->layouts, device->layout_count);
destroy(device);
}
}
@ -369,18 +370,28 @@ struct SoundIoOutStream *soundio_outstream_create(struct SoundIoDevice *device)
outstream->device = device;
soundio_device_ref(device);
// TODO set defaults
const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo);
outstream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ?
SoundIoFormatFloat32NE : device->formats[0];
outstream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0];
outstream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max);
outstream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max);
outstream->period_count = clamp(device->period_count_min, 2, device->period_count_max);
return outstream;
}
int soundio_outstream_open(struct SoundIoOutStream *outstream) {
if (outstream->format <= SoundIoFormatInvalid)
return SoundIoErrorInvalid;
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *)outstream;
outstream->bytes_per_frame = soundio_get_bytes_per_frame(outstream->format, outstream->layout.channel_count);
SoundIo *soundio = outstream->device->soundio;
SoundIoPrivate *si = (SoundIoPrivate *)soundio;
return si->outstream_init(si, os);
return si->outstream_open(si, os);
}
void soundio_outstream_destroy(SoundIoOutStream *outstream) {
@ -414,17 +425,26 @@ struct SoundIoInStream *soundio_instream_create(struct SoundIoDevice *device) {
instream->device = device;
soundio_device_ref(device);
// TODO set defaults
const SoundIoChannelLayout *stereo = soundio_channel_layout_get_builtin(SoundIoChannelLayoutIdStereo);
instream->format = soundio_device_supports_format(device, SoundIoFormatFloat32NE) ?
SoundIoFormatFloat32NE : device->formats[0];
instream->layout = soundio_device_supports_layout(device, stereo) ? *stereo : device->layouts[0];
instream->sample_rate = clamp(device->sample_rate_min, 48000, device->sample_rate_max);
instream->buffer_duration = clamp(device->buffer_duration_min, 1.0, device->buffer_duration_max);
instream->period_count = clamp(device->period_count_min, 8, device->period_count_max);
return instream;
}
int soundio_instream_open(struct SoundIoInStream *instream) {
if (instream->format <= SoundIoFormatInvalid)
return SoundIoErrorInvalid;
instream->bytes_per_frame = soundio_get_bytes_per_frame(instream->format, instream->layout.channel_count);
SoundIo *soundio = instream->device->soundio;
SoundIoPrivate *si = (SoundIoPrivate *)soundio;
SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *)instream;
return si->instream_init(si, is);
return si->instream_open(si, is);
}
int soundio_instream_start(struct SoundIoInStream *instream) {
@ -543,3 +563,13 @@ bool soundio_device_supports_format(struct SoundIoDevice *device, enum SoundIoFo
}
return false;
}
bool soundio_device_supports_layout(struct SoundIoDevice *device,
const struct SoundIoChannelLayout *layout)
{
for (int i = 0; i < device->layout_count; i += 1) {
if (soundio_channel_layout_equal(&device->layouts[i], layout))
return true;
}
return false;
}

View file

@ -185,6 +185,7 @@ struct SoundIoChannelLayout {
enum SoundIoChannelId channels[SOUNDIO_MAX_CHANNELS];
};
// The size of this struct is not part of the API or ABI.
struct SoundIoDevice {
// Read-only. Set automatically.
@ -232,7 +233,15 @@ struct SoundIoDevice {
int sample_rate_max;
int sample_rate_current;
double default_latency;
// Buffer duration in seconds.
double buffer_duration_min;
double buffer_duration_max;
double buffer_duration_current;
// How many slices it is possible to cut the buffer into.
int period_count_min;
int period_count_max;
int period_count_current;
// Tells whether this device is an input device or an output device.
enum SoundIoDevicePurpose purpose;
@ -258,12 +267,34 @@ struct SoundIoDevice {
// The size of this struct is not part of the API or ABI.
struct SoundIoOutStream {
// Populated automatically when you call soundio_outstream_create.
struct SoundIoDevice *device;
enum SoundIoFormat format;
int sample_rate;
struct SoundIoChannelLayout layout;
double latency;
// Defaults to SoundIoFormatFloat32NE, followed by the first one supported.
enum SoundIoFormat format;
// Defaults to 48000 (and then clamped into range).
int sample_rate;
// Defaults to Stereo, if available, followed by the first layout supported.
struct SoundIoChannelLayout layout;
// Buffer duration in seconds.
// (buffer_duration / period_count) 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 duration, as near to this value as possible.
// Defaults to 1 second (and then clamped into range).
double buffer_duration;
// How many slices the buffer is cut into. The IRQ will happen every
// (buffer_frame_count / period_count) frames.
// After you call soundio_outstream_open this value is replaced with the
// actual period count, as near to this value as possible.
// Defaults to 2 (and then clamped into range).
int period_count;
// Defaults to NULL.
void *userdata;
void (*underrun_callback)(struct SoundIoOutStream *);
void (*write_callback)(struct SoundIoOutStream *, int frame_count);
@ -274,11 +305,28 @@ struct SoundIoOutStream {
// The size of this struct is not part of the API or ABI.
struct SoundIoInStream {
// Populated automatically when you call soundio_outstream_create.
struct SoundIoDevice *device;
// Defaults to SoundIoFormatFloat32NE, followed by the first one supported.
enum SoundIoFormat format;
// Defaults to max(sample_rate_min, min(sample_rate_max, 48000))
int sample_rate;
// Defaults to Stereo, if available, followed by the first layout supported.
struct SoundIoChannelLayout layout;
double latency;
// Buffer duration in seconds. 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).
double buffer_duration;
// How many slices the buffer is cut into. The IRQ will happen every
// (buffer_duration / period_count) seconds, and that is the latency of the
// captured audio. This value must be a power of 2.
// Defaults to 8.
int period_count;
void *userdata;
void (*read_callback)(struct SoundIoInStream *);
@ -413,7 +461,13 @@ enum SoundIoDevicePurpose soundio_device_purpose(const struct SoundIoDevice *dev
void soundio_device_sort_channel_layouts(struct SoundIoDevice *device);
// Returns whether `format` is included in the devices supported formats.
bool soundio_device_supports_format(struct SoundIoDevice *device, enum SoundIoFormat format);
bool soundio_device_supports_format(struct SoundIoDevice *device,
enum SoundIoFormat format);
// Returns whether `layout` is included in the devices supported channel
// layouts.
bool soundio_device_supports_layout(struct SoundIoDevice *device,
const struct SoundIoChannelLayout *layout);

View file

@ -43,7 +43,7 @@ struct SoundIoPrivate {
void (*wait_events)(struct SoundIoPrivate *);
void (*wakeup)(struct SoundIoPrivate *);
int (*outstream_init)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
int (*outstream_open)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
void (*outstream_destroy)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
int (*outstream_start)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
int (*outstream_free_count)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
@ -54,7 +54,7 @@ struct SoundIoPrivate {
void (*outstream_clear_buffer)(struct SoundIoPrivate *, struct SoundIoOutStreamPrivate *);
int (*instream_init)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *);
int (*instream_open)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *);
void (*instream_destroy)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *);
int (*instream_start)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *);
void (*instream_peek)(struct SoundIoPrivate *, struct SoundIoInStreamPrivate *,

View file

@ -103,4 +103,9 @@ template <typename T>
static inline T min(T a, T b) {
return (a <= b) ? a : b;
}
template<typename T>
static inline T clamp(T min_value, T value, T max_value) {
return max(min(value, max_value), min_value);
}
#endif

View file

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

View file

@ -94,3 +94,20 @@
...
fun:snd1_dlobj_cache_get
}
{
<insert_a_suppression_name_here>
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
fun:snd_pcm_hw_get_chmap
fun:snd_pcm_get_chmap
fun:snd1_pcm_direct_get_chmap
fun:snd_pcm_get_chmap
fun:snd1_pcm_generic_get_chmap
fun:snd_pcm_get_chmap
fun:snd1_pcm_generic_get_chmap
fun:snd_pcm_get_chmap
fun:snd1_pcm_generic_get_chmap
fun:snd_pcm_get_chmap
fun:snd_pcm_set_chmap
}