ALSA: support non mmap device access

This commit is contained in:
Andrew Kelley 2015-07-22 00:04:17 -07:00
parent eca4bc6074
commit db06391646
4 changed files with 108 additions and 69 deletions

View file

@ -238,11 +238,9 @@ view `coverage/index.html` in a browser.
## Roadmap ## Roadmap
0. ALSA: poll instead of callback
0. ALSA: support devices that don't support mmap access (test with pulseaudio alsa default)
0. implement ALSA (Linux) backend, get examples working
0. pipe record to playback example working with dummy linux, osx, windows 0. pipe record to playback example working with dummy linux, osx, windows
0. pipe record to playback example working with pulseaudio linux 0. pipe record to playback example working with pulseaudio linux
0. pipe record to playback example working with ALSA linux
0. implement CoreAudio (OSX) backend, get examples working 0. implement CoreAudio (OSX) backend, get examples working
0. implement WASAPI (Windows) backend, get examples working 0. implement WASAPI (Windows) backend, get examples working
0. implement JACK backend, get examples working 0. implement JACK backend, get examples working
@ -254,7 +252,6 @@ view `coverage/index.html` in a browser.
0. use a documentation generator and host the docs somewhere 0. use a documentation generator and host the docs somewhere
0. -fvisibility=hidden and then explicitly export stuff 0. -fvisibility=hidden and then explicitly export stuff
0. Integrate into libgroove and test with Groove Basin 0. Integrate into libgroove and test with Groove Basin
0. Consider testing on FreeBSD
0. look at microphone example and determine if fewer memcpys can be done 0. look at microphone example and determine if fewer memcpys can be done
with the audio data with the audio data
- pulseaudio has peek() drop() which sucks, but what if libsoundio lets you - pulseaudio has peek() drop() which sucks, but what if libsoundio lets you
@ -268,6 +265,7 @@ view `coverage/index.html` in a browser.
0. mlock memory which is accessed in the real time path 0. mlock memory which is accessed in the real time path
0. instead of `void *backend_data` use a union for better cache locality 0. instead of `void *backend_data` use a union for better cache locality
and smaller mlock requirements and smaller mlock requirements
0. Consider testing on FreeBSD
## Planned Uses for libsoundio ## Planned Uses for libsoundio

View file

@ -123,6 +123,9 @@ int main(int argc, char **argv) {
if ((err = soundio_outstream_open(outstream))) if ((err = soundio_outstream_open(outstream)))
panic("unable to open device: %s", soundio_strerror(err)); panic("unable to open device: %s", soundio_strerror(err));
if (outstream->layout_error)
fprintf(stderr, "unable to set channel layout: %s\n", soundio_strerror(outstream->layout_error));
if ((err = soundio_outstream_start(outstream))) if ((err = soundio_outstream_start(outstream)))
panic("unable to start device: %s", soundio_strerror(err)); panic("unable to start device: %s", soundio_strerror(err));

View file

@ -45,16 +45,15 @@ struct SoundIoOutStreamAlsa {
snd_pcm_chmap_t *chmap; snd_pcm_chmap_t *chmap;
int chmap_size; int chmap_size;
snd_pcm_uframes_t offset; snd_pcm_uframes_t offset;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
snd_pcm_access_t access; snd_pcm_access_t access;
int sample_buffer_size; int sample_buffer_size;
char *sample_buffer; char *sample_buffer;
int alsa_areas_size;
snd_pcm_channel_area_t *alsa_areas;
int poll_fd_count; int poll_fd_count;
struct pollfd *poll_fds; struct pollfd *poll_fds;
SoundIoOsThread *thread; SoundIoOsThread *thread;
atomic_flag thread_exit_flag; atomic_flag thread_exit_flag;
int period_size;
SoundIoChannelArea areas[SOUNDIO_MAX_CHANNELS];
}; };
struct SoundIoInStreamAlsa { struct SoundIoInStreamAlsa {
@ -291,7 +290,9 @@ static int set_access(snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams, snd_pcm_
} }
// this function does not override device->formats, so if you want it to, deallocate and set it to NULL // 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, int resample) { static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample,
int *out_channels_min, int *out_channels_max)
{
int err; int err;
snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_t *hwparams;
@ -306,9 +307,16 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam
if ((err = set_access(handle, hwparams, nullptr))) if ((err = set_access(handle, hwparams, nullptr)))
return err; return err;
unsigned int channel_count; unsigned int channels_min;
if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channel_count)) < 0) unsigned int channels_max;
if ((err = snd_pcm_hw_params_get_channels_min(hwparams, &channels_min)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_set_channels_last(handle, hwparams, &channels_max)) < 0)
return SoundIoErrorOpeningDevice;
*out_channels_min = channels_min;
*out_channels_max = channels_max;
unsigned int rate_min; unsigned int rate_min;
unsigned int rate_max; unsigned int rate_max;
@ -316,19 +324,12 @@ static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resam
if ((err = snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, nullptr)) < 0) if ((err = snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, nullptr)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
if ((err = snd_pcm_hw_params_get_rate_max(hwparams, &rate_max, nullptr)) < 0) if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, nullptr)) < 0)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
device->sample_rate_min = rate_min; device->sample_rate_min = rate_min;
device->sample_rate_max = rate_max; device->sample_rate_max = rate_max;
if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, nullptr)) < 0)
return SoundIoErrorOpeningDevice;
rate_max = 48000;
if ((err = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate_max, nullptr)) < 0)
return SoundIoErrorOpeningDevice;
double one_over_actual_rate = 1.0 / (double)rate_max; double one_over_actual_rate = 1.0 / (double)rate_max;
// Purposefully leave the parameters with the highest rate, highest channel count. // Purposefully leave the parameters with the highest rate, highest channel count.
@ -430,14 +431,40 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if ((err = probe_open_device(device, handle, 0))) { int channels_min, channels_max;
if ((err = probe_open_device(device, handle, 0, &channels_min, &channels_max))) {
handle_channel_maps(device, maps); handle_channel_maps(device, maps);
snd_pcm_close(handle); snd_pcm_close(handle);
return err; return err;
} }
if (!maps) if (!maps) {
maps = snd_pcm_query_chmaps(handle); maps = snd_pcm_query_chmaps(handle);
if (!maps) {
// device gave us no channel maps. we're forced to conclude that
// the min and max channel counts are correct.
int layout_count = 0;
for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
const SoundIoChannelLayout *layout = soundio_channel_layout_get_builtin(i);
if (layout->channel_count >= channels_min && layout->channel_count <= channels_max) {
layout_count += 1;
}
}
device->layout_count = layout_count;
device->layouts = allocate<SoundIoChannelLayout>(device->layout_count);
if (!device->layouts) {
snd_pcm_close(handle);
return SoundIoErrorNoMem;
}
int layout_index = 0;
for (int i = 0; i < soundio_channel_layout_builtin_count(); i += 1) {
const SoundIoChannelLayout *layout = soundio_channel_layout_get_builtin(i);
if (layout->channel_count >= channels_min && layout->channel_count <= channels_max) {
device->layouts[layout_index++] = *soundio_channel_layout_get_builtin(i);
}
}
}
}
snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(handle); snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(handle);
if (chmap) { if (chmap) {
@ -461,7 +488,7 @@ static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
device->period_duration_current = device->period_duration_min; device->period_duration_current = device->period_duration_min;
// now say that resampling is OK and see what the real min and max is. // now say that resampling is OK and see what the real min and max is.
if ((err = probe_open_device(device, handle, 1)) < 0) { if ((err = probe_open_device(device, handle, 1, &channels_min, &channels_max)) < 0) {
snd_pcm_close(handle); snd_pcm_close(handle);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -554,10 +581,10 @@ static int refresh_devices(SoundIoPrivate *si) {
} }
device->ref_count = 1; device->ref_count = 1;
device->soundio = soundio; device->soundio = soundio;
device->is_raw = false;
device->name = strdup(name); device->name = strdup(name);
device->description = descr1 ? device->description = descr1 ?
soundio_alloc_sprintf(nullptr, "%s: %s", descr, descr1) : strdup(descr); soundio_alloc_sprintf(nullptr, "%s: %s", descr, descr1) : strdup(descr);
device->is_raw = false;
if (!device->name || !device->description) { if (!device->name || !device->description) {
soundio_device_unref(device); soundio_device_unref(device);
@ -878,7 +905,6 @@ static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *
deallocate(osa->chmap, osa->chmap_size); deallocate(osa->chmap, osa->chmap_size);
deallocate(osa->alsa_areas, osa->alsa_areas_size);
deallocate(osa->sample_buffer, osa->sample_buffer_size); deallocate(osa->sample_buffer, osa->sample_buffer_size);
destroy(osa); destroy(osa);
@ -1089,6 +1115,7 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
osa->period_size = period_size;
// write the hardware parameters to device // write the hardware parameters to device
if ((err = snd_pcm_hw_params(osa->handle, hwparams)) < 0) { if ((err = snd_pcm_hw_params(osa->handle, hwparams)) < 0) {
@ -1101,10 +1128,8 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
for (int i = 0; i < ch_count; i += 1) { for (int i = 0; i < ch_count; i += 1) {
osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]); osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]);
} }
if (snd_pcm_set_chmap(osa->handle, osa->chmap) < 0) { if ((err = snd_pcm_set_chmap(osa->handle, osa->chmap)) < 0)
outstream_destroy_alsa(si, os); outstream->layout_error = SoundIoErrorIncompatibleDevice;
return SoundIoErrorOpeningDevice;
}
// get current swparams // get current swparams
snd_pcm_sw_params_t *swparams; snd_pcm_sw_params_t *swparams;
@ -1120,7 +1145,7 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
if ((err = snd_pcm_sw_params_set_avail_min(osa->handle, swparams, period_size)) < 0) { if ((err = snd_pcm_sw_params_set_avail_min(osa->handle, swparams, osa->period_size)) < 0) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorOpeningDevice; return SoundIoErrorOpeningDevice;
} }
@ -1132,33 +1157,12 @@ static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os)
} }
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED || osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) { if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED || osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
osa->sample_buffer_size = ch_count * period_size * phys_bytes_per_sample; osa->sample_buffer_size = ch_count * osa->period_size * phys_bytes_per_sample;
osa->sample_buffer = allocate_nonzero<char>(osa->sample_buffer_size); osa->sample_buffer = allocate_nonzero<char>(osa->sample_buffer_size);
if (!osa->sample_buffer) { if (!osa->sample_buffer) {
outstream_destroy_alsa(si, os); outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem; return SoundIoErrorNoMem;
} }
osa->alsa_areas_size = ch_count;
osa->alsa_areas = allocate<snd_pcm_channel_area_t>(osa->alsa_areas_size);
if (!osa->alsa_areas) {
outstream_destroy_alsa(si, os);
return SoundIoErrorNoMem;
}
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
for (int ch = 0; ch < ch_count; ch += 1) {
osa->alsa_areas[ch].addr = osa->sample_buffer;
osa->alsa_areas[ch].first = ch * phys_bits_per_sample;
osa->alsa_areas[ch].step = ch_count * phys_bits_per_sample;
}
} else {
for (int ch = 0; ch < ch_count; ch += 1) {
osa->alsa_areas[ch].addr = osa->sample_buffer;
osa->alsa_areas[ch].first = ch * phys_bits_per_sample * period_size;
osa->alsa_areas[ch].step = phys_bits_per_sample;
}
}
} }
osa->poll_fd_count = snd_pcm_poll_descriptors_count(osa->handle); osa->poll_fd_count = snd_pcm_poll_descriptors_count(osa->handle);
@ -1207,10 +1211,25 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os,
SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data; SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data;
SoundIoOutStream *outstream = &os->pub; SoundIoOutStream *outstream = &os->pub;
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
osa->areas[ch].ptr = osa->sample_buffer + ch * outstream->bytes_per_sample;
osa->areas[ch].step = outstream->bytes_per_frame;
}
*frame_count = min(*frame_count, osa->period_size);
} else if (osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
osa->areas[ch].ptr = osa->sample_buffer + ch * outstream->bytes_per_sample * osa->period_size;
osa->areas[ch].step = outstream->bytes_per_sample;
}
*frame_count = min(*frame_count, osa->period_size);
} else {
const snd_pcm_channel_area_t *areas; const snd_pcm_channel_area_t *areas;
snd_pcm_uframes_t frames = *frame_count; snd_pcm_uframes_t frames = *frame_count;
int err; int err;
if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) { if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) {
if ((err = xrun_recovery(os, err)) < 0) if ((err = xrun_recovery(os, err)) < 0)
return SoundIoErrorStreaming; return SoundIoErrorStreaming;
@ -1225,17 +1244,31 @@ int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os,
} }
*frame_count = frames; *frame_count = frames;
*out_areas = osa->areas; }
*out_areas = osa->areas;
return 0; return 0;
} }
static int outstream_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) { static int outstream_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) {
SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data; SoundIoOutStreamAlsa *osa = (SoundIoOutStreamAlsa *) os->backend_data;
snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, frame_count); SoundIoOutStream *outstream = &os->pub;
int err;
snd_pcm_sframes_t commitres;
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
commitres = snd_pcm_writei(osa->handle, osa->sample_buffer, frame_count);
} else if (osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
char *ptrs[SOUNDIO_MAX_CHANNELS];
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
ptrs[ch] = osa->sample_buffer + ch * outstream->bytes_per_sample * osa->period_size;
}
commitres = snd_pcm_writen(osa->handle, (void**)ptrs, frame_count);
} else {
commitres = snd_pcm_mmap_commit(osa->handle, osa->offset, frame_count);
}
if (commitres < 0 || commitres != frame_count) { if (commitres < 0 || commitres != frame_count) {
err = (commitres >= 0) ? -EPIPE : commitres; int err = (commitres >= 0) ? -EPIPE : commitres;
if ((err = xrun_recovery(os, err)) < 0) if ((err = xrun_recovery(os, err)) < 0)
return SoundIoErrorStreaming; return SoundIoErrorStreaming;
} }

View file

@ -339,9 +339,14 @@ struct SoundIoOutStream {
// Name of the stream. This is used by PulseAudio. Defaults to "SoundIo". // Name of the stream. This is used by PulseAudio. Defaults to "SoundIo".
const char *name; const char *name;
// computed automatically when you call soundio_outstream_open // computed automatically when you call soundio_outstream_open
int bytes_per_frame; int bytes_per_frame;
int bytes_per_sample; int bytes_per_sample;
// If setting the channel layout fails for some reason, this field is set
// to an error code. Possible error codes are: SoundIoErrorIncompatibleDevice
int layout_error;
}; };
// The size of this struct is not part of the API or ABI. // The size of this struct is not part of the API or ABI.