2015-07-07 09:55:32 +00:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2015 Andrew Kelley
|
|
|
|
*
|
|
|
|
* This file is part of libsoundio, which is MIT licensed.
|
|
|
|
* See http://opensource.org/licenses/MIT
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "alsa.hpp"
|
|
|
|
#include "soundio.hpp"
|
|
|
|
|
|
|
|
#include <sys/inotify.h>
|
2015-07-14 04:30:37 +00:00
|
|
|
#include <math.h>
|
2015-07-07 09:55:32 +00:00
|
|
|
|
|
|
|
static snd_pcm_stream_t stream_types[] = {SND_PCM_STREAM_PLAYBACK, SND_PCM_STREAM_CAPTURE};
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
static snd_pcm_access_t prioritized_access_types[] = {
|
|
|
|
SND_PCM_ACCESS_MMAP_INTERLEAVED,
|
|
|
|
SND_PCM_ACCESS_MMAP_NONINTERLEAVED,
|
|
|
|
SND_PCM_ACCESS_MMAP_COMPLEX,
|
|
|
|
SND_PCM_ACCESS_RW_INTERLEAVED,
|
|
|
|
SND_PCM_ACCESS_RW_NONINTERLEAVED,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
static void wakeup_device_poll(SoundIoAlsa *sia) {
|
|
|
|
ssize_t amt = write(sia->notify_pipe_fd[1], "a", 1);
|
|
|
|
if (amt == -1) {
|
|
|
|
assert(errno != EBADF);
|
|
|
|
assert(errno != EIO);
|
|
|
|
assert(errno != ENOSPC);
|
|
|
|
assert(errno != EPERM);
|
|
|
|
assert(errno != EPIPE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void destroy_alsa(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
|
|
|
|
if (sia->thread) {
|
|
|
|
sia->abort_flag.clear();
|
|
|
|
wakeup_device_poll(sia);
|
|
|
|
soundio_os_thread_destroy(sia->thread);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sia->cond)
|
|
|
|
soundio_os_cond_destroy(sia->cond);
|
|
|
|
|
|
|
|
if (sia->mutex)
|
|
|
|
soundio_os_mutex_destroy(sia->mutex);
|
|
|
|
|
|
|
|
soundio_destroy_devices_info(sia->ready_devices_info);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
close(sia->notify_pipe_fd[0]);
|
|
|
|
close(sia->notify_pipe_fd[1]);
|
|
|
|
close(sia->notify_fd);
|
|
|
|
}
|
|
|
|
|
2015-07-08 06:29:09 +00:00
|
|
|
static char * str_partition_on_char(char *str, char c) {
|
|
|
|
while (*str) {
|
|
|
|
if (*str == c) {
|
|
|
|
*str = 0;
|
|
|
|
return str + 1;
|
|
|
|
}
|
|
|
|
str += 1;
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-07-10 06:35:58 +00:00
|
|
|
static snd_pcm_stream_t purpose_to_stream(SoundIoDevicePurpose purpose) {
|
|
|
|
switch (purpose) {
|
|
|
|
case SoundIoDevicePurposeOutput: return SND_PCM_STREAM_PLAYBACK;
|
|
|
|
case SoundIoDevicePurposeInput: return SND_PCM_STREAM_CAPTURE;
|
|
|
|
}
|
|
|
|
soundio_panic("invalid purpose");
|
|
|
|
}
|
|
|
|
|
|
|
|
static SoundIoChannelId from_alsa_chmap_pos(unsigned int pos) {
|
|
|
|
switch ((snd_pcm_chmap_position)pos) {
|
|
|
|
case SND_CHMAP_UNKNOWN: return SoundIoChannelIdInvalid;
|
|
|
|
case SND_CHMAP_NA: return SoundIoChannelIdInvalid;
|
|
|
|
case SND_CHMAP_MONO: return SoundIoChannelIdFrontCenter;
|
|
|
|
case SND_CHMAP_FL: return SoundIoChannelIdFrontLeft; // front left
|
|
|
|
case SND_CHMAP_FR: return SoundIoChannelIdFrontRight; // front right
|
|
|
|
case SND_CHMAP_RL: return SoundIoChannelIdBackLeft; // rear left
|
|
|
|
case SND_CHMAP_RR: return SoundIoChannelIdBackRight; // rear right
|
|
|
|
case SND_CHMAP_FC: return SoundIoChannelIdFrontCenter; // front center
|
|
|
|
case SND_CHMAP_LFE: return SoundIoChannelIdLfe; // LFE
|
|
|
|
case SND_CHMAP_SL: return SoundIoChannelIdSideLeft; // side left
|
|
|
|
case SND_CHMAP_SR: return SoundIoChannelIdSideRight; // side right
|
|
|
|
case SND_CHMAP_RC: return SoundIoChannelIdBackCenter; // rear center
|
|
|
|
case SND_CHMAP_FLC: return SoundIoChannelIdFrontLeftCenter; // front left center
|
|
|
|
case SND_CHMAP_FRC: return SoundIoChannelIdFrontRightCenter; // front right center
|
|
|
|
case SND_CHMAP_RLC: return SoundIoChannelIdBackLeftCenter; // rear left center
|
|
|
|
case SND_CHMAP_RRC: return SoundIoChannelIdBackRightCenter; // rear right center
|
|
|
|
case SND_CHMAP_FLW: return SoundIoChannelIdFrontLeftWide; // front left wide
|
|
|
|
case SND_CHMAP_FRW: return SoundIoChannelIdFrontRightWide; // front right wide
|
|
|
|
case SND_CHMAP_FLH: return SoundIoChannelIdFrontLeftHigh; // front left high
|
|
|
|
case SND_CHMAP_FCH: return SoundIoChannelIdFrontCenterHigh; // front center high
|
|
|
|
case SND_CHMAP_FRH: return SoundIoChannelIdFrontRightHigh; // front right high
|
|
|
|
case SND_CHMAP_TC: return SoundIoChannelIdTopCenter; // top center
|
|
|
|
case SND_CHMAP_TFL: return SoundIoChannelIdTopFrontLeft; // top front left
|
|
|
|
case SND_CHMAP_TFR: return SoundIoChannelIdTopFrontRight; // top front right
|
|
|
|
case SND_CHMAP_TFC: return SoundIoChannelIdTopFrontCenter; // top front center
|
|
|
|
case SND_CHMAP_TRL: return SoundIoChannelIdTopBackLeft; // top rear left
|
|
|
|
case SND_CHMAP_TRR: return SoundIoChannelIdTopBackRight; // top rear right
|
|
|
|
case SND_CHMAP_TRC: return SoundIoChannelIdTopBackCenter; // top rear center
|
|
|
|
case SND_CHMAP_TFLC: return SoundIoChannelIdTopFrontLeftCenter; // top front left center
|
|
|
|
case SND_CHMAP_TFRC: return SoundIoChannelIdTopFrontRightCenter; // top front right center
|
|
|
|
case SND_CHMAP_TSL: return SoundIoChannelIdTopSideLeft; // top side left
|
|
|
|
case SND_CHMAP_TSR: return SoundIoChannelIdTopSideRight; // top side right
|
|
|
|
case SND_CHMAP_LLFE: return SoundIoChannelIdLeftLfe; // left LFE
|
|
|
|
case SND_CHMAP_RLFE: return SoundIoChannelIdRightLfe; // right LFE
|
|
|
|
case SND_CHMAP_BC: return SoundIoChannelIdBottomCenter; // bottom center
|
|
|
|
case SND_CHMAP_BLC: return SoundIoChannelIdBottomLeftCenter; // bottom left center
|
|
|
|
case SND_CHMAP_BRC: return SoundIoChannelIdBottomRightCenter; // bottom right center
|
|
|
|
}
|
|
|
|
return SoundIoChannelIdInvalid;
|
|
|
|
}
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void get_channel_layout(SoundIoChannelLayout *dest, snd_pcm_chmap_t *chmap) {
|
2015-07-10 06:35:58 +00:00
|
|
|
int channel_count = min((unsigned int)SOUNDIO_MAX_CHANNELS, chmap->channels);
|
2015-07-13 16:17:20 +00:00
|
|
|
dest->channel_count = channel_count;
|
2015-07-10 06:35:58 +00:00
|
|
|
for (int i = 0; i < channel_count; i += 1) {
|
2015-07-13 16:17:20 +00:00
|
|
|
dest->channels[i] = from_alsa_chmap_pos(chmap->pos[i]);
|
2015-07-10 06:35:58 +00:00
|
|
|
}
|
2015-07-13 16:17:20 +00:00
|
|
|
soundio_channel_layout_detect_builtin(dest);
|
2015-07-10 06:35:58 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int handle_channel_maps(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
|
2015-07-10 06:35:58 +00:00
|
|
|
if (!maps)
|
2015-07-13 16:17:20 +00:00
|
|
|
return 0;
|
|
|
|
|
2015-07-10 06:35:58 +00:00
|
|
|
snd_pcm_chmap_query_t **p;
|
|
|
|
snd_pcm_chmap_query_t *v;
|
2015-07-13 16:17:20 +00:00
|
|
|
|
|
|
|
// one iteration to count
|
|
|
|
int layout_count = 0;
|
|
|
|
for (p = maps; (v = *p) && layout_count < SOUNDIO_MAX_CHANNELS; p += 1, layout_count += 1) { }
|
|
|
|
device->layouts = allocate<SoundIoChannelLayout>(layout_count);
|
|
|
|
if (!device->layouts) {
|
|
|
|
snd_pcm_free_chmaps(maps);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
device->layout_count = layout_count;
|
|
|
|
|
|
|
|
// iterate again to collect data
|
|
|
|
int layout_index;
|
|
|
|
for (p = maps, layout_index = 0;
|
|
|
|
(v = *p) && layout_index < layout_count;
|
|
|
|
p += 1, layout_index += 1)
|
|
|
|
{
|
|
|
|
get_channel_layout(&device->layouts[layout_index], &v->map);
|
2015-07-10 06:35:58 +00:00
|
|
|
}
|
|
|
|
snd_pcm_free_chmaps(maps);
|
2015-07-13 16:17:20 +00:00
|
|
|
|
|
|
|
return 0;
|
2015-07-10 06:35:58 +00:00
|
|
|
}
|
|
|
|
|
2015-07-10 09:21:47 +00:00
|
|
|
static snd_pcm_format_t to_alsa_fmt(SoundIoFormat fmt) {
|
|
|
|
switch (fmt) {
|
|
|
|
case SoundIoFormatS8: return SND_PCM_FORMAT_S8;
|
|
|
|
case SoundIoFormatU8: return SND_PCM_FORMAT_U8;
|
|
|
|
case SoundIoFormatS16LE: return SND_PCM_FORMAT_S16_LE;
|
|
|
|
case SoundIoFormatS16BE: return SND_PCM_FORMAT_S16_BE;
|
|
|
|
case SoundIoFormatU16LE: return SND_PCM_FORMAT_U16_LE;
|
|
|
|
case SoundIoFormatU16BE: return SND_PCM_FORMAT_U16_BE;
|
|
|
|
case SoundIoFormatS24LE: return SND_PCM_FORMAT_S24_LE;
|
|
|
|
case SoundIoFormatS24BE: return SND_PCM_FORMAT_S24_BE;
|
|
|
|
case SoundIoFormatU24LE: return SND_PCM_FORMAT_U24_LE;
|
|
|
|
case SoundIoFormatU24BE: return SND_PCM_FORMAT_U24_BE;
|
|
|
|
case SoundIoFormatS32LE: return SND_PCM_FORMAT_S32_LE;
|
|
|
|
case SoundIoFormatS32BE: return SND_PCM_FORMAT_S32_BE;
|
|
|
|
case SoundIoFormatU32LE: return SND_PCM_FORMAT_U32_LE;
|
|
|
|
case SoundIoFormatU32BE: return SND_PCM_FORMAT_U32_BE;
|
|
|
|
case SoundIoFormatFloat32LE: return SND_PCM_FORMAT_FLOAT_LE;
|
|
|
|
case SoundIoFormatFloat32BE: return SND_PCM_FORMAT_FLOAT_BE;
|
|
|
|
case SoundIoFormatFloat64LE: return SND_PCM_FORMAT_FLOAT64_LE;
|
|
|
|
case SoundIoFormatFloat64BE: return SND_PCM_FORMAT_FLOAT64_BE;
|
|
|
|
|
|
|
|
case SoundIoFormatInvalid:
|
|
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
|
|
}
|
|
|
|
return SND_PCM_FORMAT_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void test_fmt_mask(SoundIoDevice *device, const snd_pcm_format_mask_t *fmt_mask, SoundIoFormat fmt) {
|
|
|
|
if (snd_pcm_format_mask_test(fmt_mask, to_alsa_fmt(fmt))) {
|
|
|
|
device->formats[device->format_count] = fmt;
|
|
|
|
device->format_count += 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
static int set_access(snd_pcm_t *handle, snd_pcm_hw_params_t *hwparams, snd_pcm_access_t *out_access) {
|
|
|
|
for (int i = 0; i < array_length(prioritized_access_types); i += 1) {
|
|
|
|
snd_pcm_access_t access = prioritized_access_types[i];
|
|
|
|
int err = snd_pcm_hw_params_set_access(handle, hwparams, access);
|
|
|
|
if (err >= 0) {
|
|
|
|
if (out_access)
|
|
|
|
*out_access = access;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
// this function does not override device->formats, so if you want it to, deallocate and set it to NULL
|
2015-07-22 07:04:17 +00:00
|
|
|
static int probe_open_device(SoundIoDevice *device, snd_pcm_t *handle, int resample,
|
|
|
|
int *out_channels_min, int *out_channels_max)
|
|
|
|
{
|
2015-07-13 16:17:20 +00:00
|
|
|
int err;
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
snd_pcm_hw_params_t *hwparams;
|
|
|
|
snd_pcm_hw_params_alloca(&hwparams);
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = snd_pcm_hw_params_any(handle, hwparams)) < 0)
|
2015-07-10 06:35:58 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_rate_resample(handle, hwparams, resample)) < 0)
|
2015-07-10 06:35:58 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
if ((err = set_access(handle, hwparams, nullptr)))
|
|
|
|
return err;
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
unsigned int channels_min;
|
|
|
|
unsigned int channels_max;
|
|
|
|
|
|
|
|
if ((err = snd_pcm_hw_params_get_channels_min(hwparams, &channels_min)) < 0)
|
2015-07-10 06:35:58 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
2015-07-22 07:04:17 +00:00
|
|
|
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;
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
unsigned int rate_min;
|
|
|
|
unsigned int rate_max;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
if ((err = snd_pcm_hw_params_get_rate_min(hwparams, &rate_min, nullptr)) < 0)
|
2015-07-14 04:30:37 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_rate_last(handle, hwparams, &rate_max, nullptr)) < 0)
|
2015-07-14 04:30:37 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
device->sample_rate_min = rate_min;
|
|
|
|
device->sample_rate_max = rate_max;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
double one_over_actual_rate = 1.0 / (double)rate_max;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
|
|
|
// Purposefully leave the parameters with the highest rate, highest channel count.
|
|
|
|
|
|
|
|
snd_pcm_uframes_t min_frames;
|
|
|
|
snd_pcm_uframes_t max_frames;
|
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_period_size_integer(handle, hwparams)) < 0)
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
if ((err = snd_pcm_hw_params_get_period_size_min(hwparams, &min_frames, nullptr)) < 0)
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
if ((err = snd_pcm_hw_params_get_period_size_max(hwparams, &max_frames, nullptr)) < 0)
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
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;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
if ((err = snd_pcm_hw_params_get_buffer_size_min(hwparams, &min_frames)) < 0)
|
2015-07-10 06:35:58 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
2015-07-16 06:45:21 +00:00
|
|
|
if ((err = snd_pcm_hw_params_get_buffer_size_max(hwparams, &max_frames)) < 0)
|
2015-07-10 06:35:58 +00:00
|
|
|
return SoundIoErrorOpeningDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
device->buffer_duration_min = min_frames * one_over_actual_rate;
|
|
|
|
device->buffer_duration_max = max_frames * one_over_actual_rate;
|
2015-07-16 06:45:21 +00:00
|
|
|
|
2015-07-17 06:37:17 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_buffer_size_first(handle, hwparams, &min_frames)) < 0)
|
|
|
|
return SoundIoErrorOpeningDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-10 09:21:47 +00:00
|
|
|
snd_pcm_format_mask_t *fmt_mask;
|
|
|
|
snd_pcm_format_mask_alloca(&fmt_mask);
|
|
|
|
snd_pcm_format_mask_none(fmt_mask);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S8);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U8);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S16_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U16_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S24_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U24_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_S32_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_U32_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT_BE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_LE);
|
|
|
|
snd_pcm_format_mask_set(fmt_mask, SND_PCM_FORMAT_FLOAT64_BE);
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_format_mask(handle, hwparams, fmt_mask)) < 0)
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
|
|
|
|
if (!device->formats) {
|
|
|
|
snd_pcm_hw_params_get_format_mask(hwparams, fmt_mask);
|
|
|
|
device->formats = allocate<SoundIoFormat>(18);
|
|
|
|
if (!device->formats)
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
|
|
|
|
device->format_count = 0;
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS8);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU8);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS16LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS16BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU16LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU16BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS24LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS24BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU24LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU24BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS32LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatS32BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU32LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatU32BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatFloat32LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatFloat32BE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatFloat64LE);
|
|
|
|
test_fmt_mask(device, fmt_mask, SoundIoFormatFloat64BE);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int probe_device(SoundIoDevice *device, snd_pcm_chmap_query_t **maps) {
|
|
|
|
int err;
|
|
|
|
snd_pcm_t *handle;
|
|
|
|
|
|
|
|
snd_pcm_stream_t stream = purpose_to_stream(device->purpose);
|
|
|
|
|
|
|
|
if ((err = snd_pcm_open(&handle, device->name, stream, 0)) < 0) {
|
2015-07-10 09:21:47 +00:00
|
|
|
handle_channel_maps(device, maps);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
int channels_min, channels_max;
|
|
|
|
if ((err = probe_open_device(device, handle, 0, &channels_min, &channels_max))) {
|
2015-07-10 09:21:47 +00:00
|
|
|
handle_channel_maps(device, maps);
|
|
|
|
snd_pcm_close(handle);
|
2015-07-13 16:17:20 +00:00
|
|
|
return err;
|
2015-07-10 09:21:47 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
if (!maps) {
|
2015-07-13 16:17:20 +00:00
|
|
|
maps = snd_pcm_query_chmaps(handle);
|
2015-07-22 07:04:17 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-07-10 06:35:58 +00:00
|
|
|
|
|
|
|
snd_pcm_chmap_t *chmap = snd_pcm_get_chmap(handle);
|
|
|
|
if (chmap) {
|
2015-07-13 16:17:20 +00:00
|
|
|
get_channel_layout(&device->current_layout, chmap);
|
2015-07-10 06:35:58 +00:00
|
|
|
free(chmap);
|
|
|
|
}
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = handle_channel_maps(device, maps))) {
|
|
|
|
snd_pcm_close(handle);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
maps = nullptr;
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
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;
|
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
if (device->period_duration_min == device->period_duration_max)
|
|
|
|
device->period_duration_current = device->period_duration_min;
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
// now say that resampling is OK and see what the real min and max is.
|
2015-07-22 07:04:17 +00:00
|
|
|
if ((err = probe_open_device(device, handle, 1, &channels_min, &channels_max)) < 0) {
|
2015-07-13 16:17:20 +00:00
|
|
|
snd_pcm_close(handle);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
}
|
2015-07-10 06:35:58 +00:00
|
|
|
|
|
|
|
snd_pcm_close(handle);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool str_has_prefix(const char *big_str, const char *prefix) {
|
|
|
|
return strncmp(big_str, prefix, strlen(prefix)) == 0;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int refresh_devices(SoundIoPrivate *si) {
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-08 06:29:09 +00:00
|
|
|
|
|
|
|
SoundIoDevicesInfo *devices_info = create<SoundIoDevicesInfo>();
|
|
|
|
if (!devices_info)
|
|
|
|
return SoundIoErrorNoMem;
|
2015-07-22 19:07:20 +00:00
|
|
|
devices_info->default_output_index = -1;
|
|
|
|
devices_info->default_input_index = -1;
|
2015-07-08 06:29:09 +00:00
|
|
|
|
|
|
|
void **hints;
|
|
|
|
if (snd_device_name_hint(-1, "pcm", &hints) < 0) {
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (void **hint_ptr = hints; *hint_ptr; hint_ptr += 1) {
|
|
|
|
char *name = snd_device_name_get_hint(*hint_ptr, "NAME");
|
|
|
|
// null - libsoundio has its own dummy backend. API clients should use
|
|
|
|
// that instead of alsa null device.
|
|
|
|
if (strcmp(name, "null") == 0 ||
|
|
|
|
// sysdefault is confusing - the name and description is identical
|
|
|
|
// to default, and my best guess for what it does is ignore ~/.asoundrc
|
|
|
|
// which is just an accident waiting to happen.
|
|
|
|
str_has_prefix(name, "sysdefault:") ||
|
|
|
|
// all these surround devices are clutter
|
|
|
|
str_has_prefix(name, "front:") ||
|
|
|
|
str_has_prefix(name, "surround21:") ||
|
|
|
|
str_has_prefix(name, "surround40:") ||
|
|
|
|
str_has_prefix(name, "surround41:") ||
|
|
|
|
str_has_prefix(name, "surround50:") ||
|
|
|
|
str_has_prefix(name, "surround51:") ||
|
|
|
|
str_has_prefix(name, "surround71:"))
|
|
|
|
{
|
|
|
|
free(name);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
char *descr = snd_device_name_get_hint(*hint_ptr, "DESC");
|
|
|
|
char *descr1 = str_partition_on_char(descr, '\n');
|
|
|
|
|
|
|
|
char *io = snd_device_name_get_hint(*hint_ptr, "IOID");
|
|
|
|
bool is_playback;
|
|
|
|
bool is_capture;
|
|
|
|
if (io) {
|
|
|
|
if (strcmp(io, "Input") == 0) {
|
|
|
|
is_playback = false;
|
|
|
|
is_capture = true;
|
|
|
|
} else if (strcmp(io, "Output") == 0) {
|
|
|
|
is_playback = true;
|
|
|
|
is_capture = false;
|
|
|
|
} else {
|
|
|
|
soundio_panic("invalid io hint value");
|
|
|
|
}
|
|
|
|
free(io);
|
|
|
|
} else {
|
|
|
|
is_playback = true;
|
|
|
|
is_capture = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int stream_type_i = 0; stream_type_i < array_length(stream_types); stream_type_i += 1) {
|
|
|
|
snd_pcm_stream_t stream = stream_types[stream_type_i];
|
|
|
|
if (stream == SND_PCM_STREAM_PLAYBACK && !is_playback) continue;
|
|
|
|
if (stream == SND_PCM_STREAM_CAPTURE && !is_capture) continue;
|
|
|
|
if (stream == SND_PCM_STREAM_CAPTURE && descr1 &&
|
|
|
|
(strstr(descr1, "Output") || strstr(descr1, "output")))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
|
|
|
|
if (!dev) {
|
2015-07-08 06:29:09 +00:00
|
|
|
free(name);
|
|
|
|
free(descr);
|
|
|
|
destroy(devices_info);
|
|
|
|
snd_device_name_free_hint(hints);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevice *device = &dev->pub;
|
2015-07-08 06:29:09 +00:00
|
|
|
device->ref_count = 1;
|
|
|
|
device->soundio = soundio;
|
2015-07-22 07:04:17 +00:00
|
|
|
device->is_raw = false;
|
2015-07-08 06:29:09 +00:00
|
|
|
device->name = strdup(name);
|
|
|
|
device->description = descr1 ?
|
|
|
|
soundio_alloc_sprintf(nullptr, "%s: %s", descr, descr1) : strdup(descr);
|
|
|
|
|
|
|
|
if (!device->name || !device->description) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
free(name);
|
|
|
|
free(descr);
|
|
|
|
destroy(devices_info);
|
|
|
|
snd_device_name_free_hint(hints);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
SoundIoList<SoundIoDevice *> *device_list;
|
|
|
|
if (stream == SND_PCM_STREAM_PLAYBACK) {
|
|
|
|
device->purpose = SoundIoDevicePurposeOutput;
|
|
|
|
device_list = &devices_info->output_devices;
|
2015-07-24 03:55:36 +00:00
|
|
|
if (devices_info->default_output_index < 0 && str_has_prefix(name, "default:"))
|
2015-07-08 06:29:09 +00:00
|
|
|
devices_info->default_output_index = device_list->length;
|
|
|
|
} else {
|
|
|
|
assert(stream == SND_PCM_STREAM_CAPTURE);
|
|
|
|
device->purpose = SoundIoDevicePurposeInput;
|
|
|
|
device_list = &devices_info->input_devices;
|
2015-07-24 03:55:36 +00:00
|
|
|
if (devices_info->default_input_index < 0 && str_has_prefix(name, "default:"))
|
2015-07-08 06:29:09 +00:00
|
|
|
devices_info->default_input_index = device_list->length;
|
|
|
|
}
|
|
|
|
|
2015-07-10 09:21:47 +00:00
|
|
|
device->probe_error = probe_device(device, nullptr);
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-08 06:29:09 +00:00
|
|
|
if (device_list->append(device)) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
free(name);
|
|
|
|
free(descr);
|
|
|
|
destroy(devices_info);
|
|
|
|
snd_device_name_free_hint(hints);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
free(name);
|
|
|
|
free(descr);
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_device_name_free_hint(hints);
|
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
int card_index = -1;
|
|
|
|
|
|
|
|
if (snd_card_next(&card_index) < 0)
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
|
|
|
|
snd_ctl_card_info_t *card_info;
|
|
|
|
snd_ctl_card_info_alloca(&card_info);
|
|
|
|
|
|
|
|
snd_pcm_info_t *pcm_info;
|
|
|
|
snd_pcm_info_alloca(&pcm_info);
|
|
|
|
|
|
|
|
while (card_index >= 0) {
|
|
|
|
int err;
|
|
|
|
snd_ctl_t *handle;
|
|
|
|
char name[32];
|
|
|
|
sprintf(name, "hw:%d", card_index);
|
|
|
|
if ((err = snd_ctl_open(&handle, name, 0)) < 0) {
|
|
|
|
if (err == -ENOENT) {
|
|
|
|
break;
|
|
|
|
} else {
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_ctl_card_info(handle, card_info)) < 0) {
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
}
|
|
|
|
const char *card_name = snd_ctl_card_info_get_name(card_info);
|
|
|
|
|
|
|
|
int device_index = -1;
|
|
|
|
for (;;) {
|
|
|
|
if (snd_ctl_pcm_next_device(handle, &device_index) < 0) {
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
}
|
|
|
|
if (device_index < 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
snd_pcm_info_set_device(pcm_info, device_index);
|
|
|
|
snd_pcm_info_set_subdevice(pcm_info, 0);
|
|
|
|
|
|
|
|
for (int stream_type_i = 0; stream_type_i < array_length(stream_types); stream_type_i += 1) {
|
|
|
|
snd_pcm_stream_t stream = stream_types[stream_type_i];
|
|
|
|
snd_pcm_info_set_stream(pcm_info, stream);
|
|
|
|
|
|
|
|
if ((err = snd_ctl_pcm_info(handle, pcm_info)) < 0) {
|
|
|
|
if (err == -ENOENT) {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *device_name = snd_pcm_info_get_name(pcm_info);
|
|
|
|
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevicePrivate *dev = create<SoundIoDevicePrivate>();
|
|
|
|
if (!dev) {
|
2015-07-07 09:55:32 +00:00
|
|
|
snd_ctl_close(handle);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
2015-07-28 18:28:07 +00:00
|
|
|
SoundIoDevice *device = &dev->pub;
|
2015-07-07 09:55:32 +00:00
|
|
|
device->ref_count = 1;
|
|
|
|
device->soundio = soundio;
|
2015-07-08 06:29:09 +00:00
|
|
|
device->name = soundio_alloc_sprintf(nullptr, "hw:%d,%d", card_index, device_index);
|
2015-07-07 09:55:32 +00:00
|
|
|
device->description = soundio_alloc_sprintf(nullptr, "%s %s", card_name, device_name);
|
2015-07-08 06:29:09 +00:00
|
|
|
device->is_raw = true;
|
2015-07-07 09:55:32 +00:00
|
|
|
|
|
|
|
if (!device->name || !device->description) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
SoundIoList<SoundIoDevice *> *device_list;
|
|
|
|
if (stream == SND_PCM_STREAM_PLAYBACK) {
|
|
|
|
device->purpose = SoundIoDevicePurposeOutput;
|
|
|
|
device_list = &devices_info->output_devices;
|
|
|
|
} else {
|
|
|
|
assert(stream == SND_PCM_STREAM_CAPTURE);
|
|
|
|
device->purpose = SoundIoDevicePurposeInput;
|
|
|
|
device_list = &devices_info->input_devices;
|
|
|
|
}
|
|
|
|
|
2015-07-10 06:35:58 +00:00
|
|
|
snd_pcm_chmap_query_t **maps = snd_pcm_query_chmaps_from_hw(card_index, device_index, -1, stream);
|
2015-07-10 09:21:47 +00:00
|
|
|
device->probe_error = probe_device(device, maps);
|
2015-07-10 06:35:58 +00:00
|
|
|
|
2015-07-07 09:55:32 +00:00
|
|
|
if (device_list->append(device)) {
|
|
|
|
soundio_device_unref(device);
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
snd_ctl_close(handle);
|
|
|
|
if (snd_card_next(&card_index) < 0) {
|
|
|
|
destroy(devices_info);
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
soundio_os_mutex_lock(sia->mutex);
|
|
|
|
soundio_destroy_devices_info(sia->ready_devices_info);
|
|
|
|
sia->ready_devices_info = devices_info;
|
|
|
|
sia->have_devices_flag.store(true);
|
|
|
|
soundio_os_cond_signal(sia->cond, sia->mutex);
|
2015-07-18 07:22:32 +00:00
|
|
|
soundio->on_events_signal(soundio);
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_os_mutex_unlock(sia->mutex);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void device_thread_run(void *arg) {
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoPrivate *si = (SoundIoPrivate *)arg;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
|
|
|
|
// Some systems cannot read integer variables if they are not
|
|
|
|
// properly aligned. On other systems, incorrect alignment may
|
|
|
|
// decrease performance. Hence, the buffer used for reading from
|
|
|
|
// the inotify file descriptor should have the same alignment as
|
|
|
|
// struct inotify_event.
|
|
|
|
char buf[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
|
|
|
|
const struct inotify_event *event;
|
|
|
|
|
|
|
|
struct pollfd fds[2];
|
|
|
|
fds[0].fd = sia->notify_fd;
|
|
|
|
fds[0].events = POLLIN;
|
|
|
|
|
|
|
|
fds[1].fd = sia->notify_pipe_fd[0];
|
|
|
|
fds[1].events = POLLIN;
|
|
|
|
|
|
|
|
int err;
|
|
|
|
for (;;) {
|
|
|
|
int poll_num = poll(fds, 2, -1);
|
|
|
|
if (!sia->abort_flag.test_and_set())
|
|
|
|
break;
|
|
|
|
if (poll_num == -1) {
|
|
|
|
if (errno == EINTR)
|
|
|
|
continue;
|
|
|
|
assert(errno != EFAULT);
|
|
|
|
assert(errno != EFAULT);
|
|
|
|
assert(errno != EINVAL);
|
|
|
|
assert(errno == ENOMEM);
|
|
|
|
soundio_panic("kernel ran out of polling memory");
|
|
|
|
}
|
|
|
|
if (poll_num <= 0)
|
|
|
|
continue;
|
|
|
|
bool got_rescan_event = false;
|
|
|
|
if (fds[0].revents & POLLIN) {
|
|
|
|
for (;;) {
|
|
|
|
ssize_t len = read(sia->notify_fd, buf, sizeof(buf));
|
|
|
|
if (len == -1) {
|
|
|
|
assert(errno != EBADF);
|
|
|
|
assert(errno != EFAULT);
|
|
|
|
assert(errno != EINVAL);
|
|
|
|
assert(errno != EIO);
|
|
|
|
assert(errno != EISDIR);
|
|
|
|
}
|
|
|
|
|
|
|
|
// catches EINTR and EAGAIN
|
|
|
|
if (len <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
// loop over all events in the buffer
|
|
|
|
for (char *ptr = buf; ptr < buf + len; ptr += sizeof(struct inotify_event) + event->len) {
|
|
|
|
event = (const struct inotify_event *) ptr;
|
|
|
|
|
|
|
|
if (!((event->mask & IN_CREATE) || (event->mask & IN_DELETE)))
|
|
|
|
continue;
|
|
|
|
if (event->mask & IN_ISDIR)
|
|
|
|
continue;
|
|
|
|
if (!event->len || event->len < 8)
|
|
|
|
continue;
|
|
|
|
if (event->name[0] != 'p' ||
|
|
|
|
event->name[1] != 'c' ||
|
|
|
|
event->name[2] != 'm')
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
got_rescan_event = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (fds[1].revents & POLLIN) {
|
|
|
|
got_rescan_event = true;
|
|
|
|
for (;;) {
|
|
|
|
ssize_t len = read(sia->notify_pipe_fd[0], buf, sizeof(buf));
|
|
|
|
if (len == -1) {
|
|
|
|
assert(errno != EBADF);
|
|
|
|
assert(errno != EFAULT);
|
|
|
|
assert(errno != EINVAL);
|
|
|
|
assert(errno != EIO);
|
|
|
|
assert(errno != EISDIR);
|
|
|
|
}
|
|
|
|
if (len <= 0)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (got_rescan_event) {
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = refresh_devices(si)))
|
2015-07-10 09:21:47 +00:00
|
|
|
soundio_panic("error refreshing devices: %s", soundio_strerror(err));
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void block_until_have_devices(SoundIoAlsa *sia) {
|
|
|
|
if (sia->have_devices_flag.load())
|
|
|
|
return;
|
|
|
|
soundio_os_mutex_lock(sia->mutex);
|
|
|
|
while (!sia->have_devices_flag.load())
|
|
|
|
soundio_os_cond_wait(sia->cond, sia->mutex);
|
|
|
|
soundio_os_mutex_unlock(sia->mutex);
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void flush_events(SoundIoPrivate *si) {
|
|
|
|
SoundIo *soundio = &si->pub;
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
block_until_have_devices(sia);
|
|
|
|
|
|
|
|
bool change = false;
|
|
|
|
SoundIoDevicesInfo *old_devices_info = nullptr;
|
|
|
|
|
|
|
|
soundio_os_mutex_lock(sia->mutex);
|
|
|
|
|
|
|
|
if (sia->ready_devices_info) {
|
2015-07-13 16:17:20 +00:00
|
|
|
old_devices_info = si->safe_devices_info;
|
|
|
|
si->safe_devices_info = sia->ready_devices_info;
|
2015-07-07 09:55:32 +00:00
|
|
|
sia->ready_devices_info = nullptr;
|
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
soundio_os_mutex_unlock(sia->mutex);
|
|
|
|
|
|
|
|
if (change)
|
|
|
|
soundio->on_devices_change(soundio);
|
|
|
|
|
|
|
|
soundio_destroy_devices_info(old_devices_info);
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void wait_events(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
flush_events(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_os_mutex_lock(sia->mutex);
|
|
|
|
soundio_os_cond_wait(sia->cond, sia->mutex);
|
|
|
|
soundio_os_mutex_unlock(sia->mutex);
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void wakeup(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
soundio_os_mutex_lock(sia->mutex);
|
|
|
|
soundio_os_cond_signal(sia->cond, sia->mutex);
|
|
|
|
soundio_os_mutex_unlock(sia->mutex);
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static void outstream_destroy_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
if (osa->thread) {
|
|
|
|
osa->thread_exit_flag.clear();
|
|
|
|
soundio_os_thread_destroy(osa->thread);
|
|
|
|
}
|
2015-07-14 04:30:37 +00:00
|
|
|
|
|
|
|
if (osa->handle)
|
|
|
|
snd_pcm_close(osa->handle);
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
deallocate(osa->poll_fds, osa->poll_fd_count);
|
|
|
|
|
|
|
|
deallocate(osa->chmap, osa->chmap_size);
|
|
|
|
|
|
|
|
deallocate(osa->sample_buffer, osa->sample_buffer_size);
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 03:57:00 +00:00
|
|
|
static int xrun_recovery(SoundIoOutStreamPrivate *os, int err) {
|
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-16 03:57:00 +00:00
|
|
|
if (err == -EPIPE) {
|
|
|
|
err = snd_pcm_prepare(osa->handle);
|
2015-07-23 22:04:41 +00:00
|
|
|
if (err >= 0)
|
|
|
|
outstream->underflow_callback(outstream);
|
2015-07-16 03:57:00 +00:00
|
|
|
} else if (err == -ESTRPIPE) {
|
|
|
|
while ((err = snd_pcm_resume(osa->handle)) == -EAGAIN) {
|
|
|
|
// wait until suspend flag is released
|
|
|
|
poll(nullptr, 0, 1);
|
|
|
|
}
|
|
|
|
if (err < 0)
|
|
|
|
err = snd_pcm_prepare(osa->handle);
|
2015-07-23 22:04:41 +00:00
|
|
|
if (err >= 0)
|
|
|
|
outstream->underflow_callback(outstream);
|
2015-07-16 03:57:00 +00:00
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-07-24 03:55:36 +00:00
|
|
|
static int instream_xrun_recovery(SoundIoInStreamPrivate *is, int err) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-29 01:47:28 +00:00
|
|
|
// TODO do something with this overflow
|
2015-07-24 03:55:36 +00:00
|
|
|
if (err == -EPIPE) {
|
|
|
|
err = snd_pcm_prepare(isa->handle);
|
|
|
|
} else if (err == -ESTRPIPE) {
|
|
|
|
while ((err = snd_pcm_resume(isa->handle)) == -EAGAIN) {
|
|
|
|
// wait until suspend flag is released
|
|
|
|
poll(nullptr, 0, 1);
|
|
|
|
}
|
|
|
|
if (err < 0)
|
|
|
|
err = snd_pcm_prepare(isa->handle);
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
static int wait_for_poll(SoundIoOutStreamAlsa *osa) {
|
|
|
|
int err;
|
|
|
|
unsigned short revents;
|
|
|
|
for (;;) {
|
|
|
|
if ((err = poll(osa->poll_fds, osa->poll_fd_count, -1)) < 0)
|
|
|
|
return err;
|
|
|
|
if ((err = snd_pcm_poll_descriptors_revents(osa->handle,
|
|
|
|
osa->poll_fds, osa->poll_fd_count, &revents)) < 0)
|
|
|
|
{
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if (revents & POLLERR)
|
|
|
|
return -EIO;
|
|
|
|
if (revents & POLLOUT)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-24 03:55:36 +00:00
|
|
|
static int instream_wait_for_poll(SoundIoInStreamAlsa *isa) {
|
|
|
|
int err;
|
|
|
|
unsigned short revents;
|
|
|
|
for (;;) {
|
|
|
|
if ((err = poll(isa->poll_fds, isa->poll_fd_count, -1)) < 0)
|
|
|
|
return err;
|
|
|
|
if ((err = snd_pcm_poll_descriptors_revents(isa->handle,
|
|
|
|
isa->poll_fds, isa->poll_fd_count, &revents)) < 0)
|
|
|
|
{
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
if (revents & POLLERR)
|
|
|
|
return -EIO;
|
|
|
|
if (revents & POLLIN)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
void outstream_thread_run(void *arg) {
|
|
|
|
SoundIoOutStreamPrivate *os = (SoundIoOutStreamPrivate *) arg;
|
2015-07-16 03:57:00 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-16 03:57:00 +00:00
|
|
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
snd_pcm_state_t state = snd_pcm_state(osa->handle);
|
|
|
|
switch (state) {
|
|
|
|
case SND_PCM_STATE_SETUP:
|
|
|
|
if ((err = snd_pcm_prepare(osa->handle)) < 0) {
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case SND_PCM_STATE_PREPARED:
|
2015-07-25 01:43:14 +00:00
|
|
|
{
|
2015-07-16 03:57:00 +00:00
|
|
|
if ((err = snd_pcm_start(osa->handle)) < 0) {
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
2015-07-25 01:43:14 +00:00
|
|
|
}
|
2015-07-16 03:57:00 +00:00
|
|
|
case SND_PCM_STATE_RUNNING:
|
|
|
|
{
|
2015-07-24 03:55:36 +00:00
|
|
|
if ((err = wait_for_poll(osa)) < 0) {
|
|
|
|
if (!osa->thread_exit_flag.test_and_set())
|
|
|
|
return;
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!osa->thread_exit_flag.test_and_set())
|
|
|
|
return;
|
|
|
|
|
2015-07-16 03:57:00 +00:00
|
|
|
snd_pcm_sframes_t avail = snd_pcm_avail_update(osa->handle);
|
|
|
|
if (avail < 0) {
|
|
|
|
if ((err = xrun_recovery(os, avail)) < 0) {
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
outstream->write_callback(outstream, avail);
|
2015-07-22 04:21:00 +00:00
|
|
|
continue;
|
2015-07-16 03:57:00 +00:00
|
|
|
}
|
|
|
|
case SND_PCM_STATE_XRUN:
|
|
|
|
if ((err = xrun_recovery(os, -EPIPE)) < 0) {
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case SND_PCM_STATE_SUSPENDED:
|
|
|
|
if ((err = xrun_recovery(os, -ESTRPIPE)) < 0) {
|
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
2015-07-24 21:03:49 +00:00
|
|
|
case SND_PCM_STATE_OPEN:
|
|
|
|
case SND_PCM_STATE_DRAINING:
|
|
|
|
case SND_PCM_STATE_PAUSED:
|
2015-07-16 03:57:00 +00:00
|
|
|
case SND_PCM_STATE_DISCONNECTED:
|
2015-07-24 21:03:49 +00:00
|
|
|
outstream->error_callback(outstream, SoundIoErrorStreaming);
|
|
|
|
return;
|
2015-07-16 03:57:00 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-24 03:55:36 +00:00
|
|
|
static void instream_thread_run(void *arg) {
|
|
|
|
SoundIoInStreamPrivate *is = (SoundIoInStreamPrivate *) arg;
|
|
|
|
SoundIoInStream *instream = &is->pub;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
for (;;) {
|
2015-07-28 00:06:12 +00:00
|
|
|
snd_pcm_state_t state = snd_pcm_state(isa->handle);
|
2015-07-24 03:55:36 +00:00
|
|
|
switch (state) {
|
|
|
|
case SND_PCM_STATE_SETUP:
|
2015-07-28 00:06:12 +00:00
|
|
|
if ((err = snd_pcm_prepare(isa->handle)) < 0) {
|
2015-07-24 03:55:36 +00:00
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case SND_PCM_STATE_PREPARED:
|
2015-07-28 00:06:12 +00:00
|
|
|
if ((err = snd_pcm_start(isa->handle)) < 0) {
|
2015-07-24 03:55:36 +00:00
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case SND_PCM_STATE_RUNNING:
|
|
|
|
{
|
2015-07-28 00:06:12 +00:00
|
|
|
if ((err = instream_wait_for_poll(isa)) < 0) {
|
|
|
|
if (!isa->thread_exit_flag.test_and_set())
|
2015-07-24 03:55:36 +00:00
|
|
|
return;
|
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
2015-07-28 00:06:12 +00:00
|
|
|
if (!isa->thread_exit_flag.test_and_set())
|
2015-07-24 03:55:36 +00:00
|
|
|
return;
|
|
|
|
|
2015-07-28 00:06:12 +00:00
|
|
|
snd_pcm_sframes_t avail = snd_pcm_avail_update(isa->handle);
|
2015-07-24 03:55:36 +00:00
|
|
|
if (avail < 0) {
|
|
|
|
if ((err = instream_xrun_recovery(is, avail)) < 0) {
|
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
instream->read_callback(instream, avail);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
case SND_PCM_STATE_XRUN:
|
|
|
|
if ((err = instream_xrun_recovery(is, -EPIPE)) < 0) {
|
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
case SND_PCM_STATE_SUSPENDED:
|
|
|
|
if ((err = instream_xrun_recovery(is, -ESTRPIPE)) < 0) {
|
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
continue;
|
2015-07-24 21:03:49 +00:00
|
|
|
case SND_PCM_STATE_OPEN:
|
|
|
|
case SND_PCM_STATE_DRAINING:
|
|
|
|
case SND_PCM_STATE_PAUSED:
|
2015-07-24 03:55:36 +00:00
|
|
|
case SND_PCM_STATE_DISCONNECTED:
|
2015-07-24 21:03:49 +00:00
|
|
|
instream->error_callback(instream, SoundIoErrorStreaming);
|
|
|
|
return;
|
2015-07-24 03:55:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
static int outstream_open_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-14 04:30:37 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
2015-07-21 05:55:30 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-07-23 23:49:44 +00:00
|
|
|
if (outstream->prebuf_duration == -1.0)
|
|
|
|
outstream->prebuf_duration = outstream->buffer_duration;
|
2015-07-21 05:55:30 +00:00
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
int ch_count = outstream->layout.channel_count;
|
|
|
|
|
|
|
|
osa->chmap_size = sizeof(int) + sizeof(int) * ch_count;
|
2015-07-14 04:30:37 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
if ((err = set_access(osa->handle, hwparams, &osa->access))) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return err;
|
2015-07-14 04:30:37 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
if ((err = snd_pcm_hw_params_set_channels(osa->handle, hwparams, ch_count)) < 0) {
|
2015-07-14 04:30:37 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
snd_pcm_format_t format = to_alsa_fmt(outstream->format);
|
|
|
|
int phys_bits_per_sample = snd_pcm_format_physical_width(format);
|
|
|
|
if (phys_bits_per_sample % 8 != 0) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
}
|
|
|
|
int phys_bytes_per_sample = phys_bits_per_sample / 8;
|
|
|
|
if ((err = snd_pcm_hw_params_set_format(osa->handle, hwparams, format)) < 0) {
|
2015-07-14 04:30:37 +00:00
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-16 06:45:21 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
outstream->period_duration = ((double)period_frames) / (double)outstream->sample_rate;
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
|
|
|
|
snd_pcm_uframes_t buffer_size_frames = ceil(outstream->buffer_duration * (double)outstream->sample_rate);
|
2015-07-16 06:45:21 +00:00
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2015-07-22 07:04:17 +00:00
|
|
|
osa->period_size = period_size;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
|
|
|
// write the hardware parameters to device
|
|
|
|
if ((err = snd_pcm_hw_params(osa->handle, hwparams)) < 0) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
2015-07-16 20:37:41 +00:00
|
|
|
return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// set channel map
|
2015-07-22 04:21:00 +00:00
|
|
|
osa->chmap->channels = ch_count;
|
|
|
|
for (int i = 0; i < ch_count; i += 1) {
|
2015-07-14 04:30:37 +00:00
|
|
|
osa->chmap->pos[i] = to_alsa_chmap_pos(outstream->layout.channels[i]);
|
|
|
|
}
|
2015-07-22 07:04:17 +00:00
|
|
|
if ((err = snd_pcm_set_chmap(osa->handle, osa->chmap)) < 0)
|
|
|
|
outstream->layout_error = SoundIoErrorIncompatibleDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2015-07-23 23:49:44 +00:00
|
|
|
snd_pcm_uframes_t prebuf_frames = ceil(outstream->prebuf_duration * (double)outstream->sample_rate);
|
|
|
|
if ((err = snd_pcm_sw_params_set_start_threshold(osa->handle, swparams, prebuf_frames)) < 0) {
|
2015-07-14 04:30:37 +00:00
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
if ((err = snd_pcm_sw_params_set_avail_min(osa->handle, swparams, osa->period_size)) < 0) {
|
2015-07-14 04:30:37 +00:00
|
|
|
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);
|
2015-07-16 20:37:41 +00:00
|
|
|
return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice;
|
2015-07-14 04:30:37 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
if (osa->access == SND_PCM_ACCESS_RW_INTERLEAVED || osa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
|
2015-07-22 07:04:17 +00:00
|
|
|
osa->sample_buffer_size = ch_count * osa->period_size * phys_bytes_per_sample;
|
2015-07-22 04:21:00 +00:00
|
|
|
osa->sample_buffer = allocate_nonzero<char>(osa->sample_buffer_size);
|
|
|
|
if (!osa->sample_buffer) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
osa->poll_fd_count = snd_pcm_poll_descriptors_count(osa->handle);
|
|
|
|
if (osa->poll_fd_count <= 0) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
osa->poll_fds = allocate<struct pollfd>(osa->poll_fd_count);
|
|
|
|
if (!osa->poll_fds) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_pcm_poll_descriptors(osa->handle, osa->poll_fds, osa->poll_fd_count)) < 0) {
|
2015-07-16 03:57:00 +00:00
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int outstream_start_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-16 03:57:00 +00:00
|
|
|
|
2015-07-22 04:21:00 +00:00
|
|
|
assert(!osa->thread);
|
|
|
|
|
|
|
|
osa->thread_exit_flag.test_and_set();
|
|
|
|
int err;
|
|
|
|
if ((err = soundio_os_thread_create(outstream_thread_run, os, true, &osa->thread))) {
|
|
|
|
outstream_destroy_alsa(si, os);
|
|
|
|
return err;
|
|
|
|
}
|
2015-07-16 03:57:00 +00:00
|
|
|
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 03:57:00 +00:00
|
|
|
int outstream_begin_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os,
|
|
|
|
struct SoundIoChannelArea **out_areas, int *frame_count)
|
2015-07-07 09:55:32 +00:00
|
|
|
{
|
2015-07-16 03:57:00 +00:00
|
|
|
*out_areas = nullptr;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-16 03:57:00 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
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;
|
|
|
|
}
|
2015-07-16 03:57:00 +00:00
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
*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;
|
|
|
|
}
|
2015-07-16 03:57:00 +00:00
|
|
|
|
2015-07-22 07:04:17 +00:00
|
|
|
*frame_count = min(*frame_count, osa->period_size);
|
|
|
|
} else {
|
|
|
|
const snd_pcm_channel_area_t *areas;
|
|
|
|
snd_pcm_uframes_t frames = *frame_count;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if ((err = snd_pcm_mmap_begin(osa->handle, &areas, &osa->offset, &frames)) < 0) {
|
|
|
|
if ((err = xrun_recovery(os, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int ch = 0; ch < outstream->layout.channel_count; ch += 1) {
|
|
|
|
if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
osa->areas[ch].step = areas[ch].step / 8;
|
|
|
|
osa->areas[ch].ptr = ((char *)areas[ch].addr) + (areas[ch].first / 8) +
|
|
|
|
(osa->areas[ch].step * osa->offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
*frame_count = frames;
|
2015-07-16 03:57:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
*out_areas = osa->areas;
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
static int outstream_end_write_alsa(SoundIoPrivate *si, SoundIoOutStreamPrivate *os, int frame_count) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-22 07:04:17 +00:00
|
|
|
SoundIoOutStream *outstream = &os->pub;
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2015-07-16 03:57:00 +00:00
|
|
|
if (commitres < 0 || commitres != frame_count) {
|
2015-07-22 07:04:17 +00:00
|
|
|
int err = (commitres >= 0) ? -EPIPE : commitres;
|
2015-07-16 03:57:00 +00:00
|
|
|
if ((err = xrun_recovery(os, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-24 21:03:49 +00:00
|
|
|
static int outstream_clear_buffer_alsa(SoundIoPrivate *si,
|
2015-07-13 16:17:20 +00:00
|
|
|
SoundIoOutStreamPrivate *os)
|
2015-07-07 09:55:32 +00:00
|
|
|
{
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-24 21:03:49 +00:00
|
|
|
int err;
|
|
|
|
if ((err = snd_pcm_reset(osa->handle)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 20:37:41 +00:00
|
|
|
static int outstream_pause_alsa(struct SoundIoPrivate *si, struct SoundIoOutStreamPrivate *os, bool pause) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoOutStreamAlsa *osa = &os->backend_data.alsa;
|
2015-07-16 20:37:41 +00:00
|
|
|
int err;
|
|
|
|
if ((err = snd_pcm_pause(osa->handle, pause)) < 0)
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-24 03:55:36 +00:00
|
|
|
static void instream_destroy_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
|
2015-07-24 04:07:40 +00:00
|
|
|
if (isa->thread) {
|
|
|
|
isa->thread_exit_flag.clear();
|
|
|
|
soundio_os_thread_destroy(isa->thread);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isa->handle)
|
|
|
|
snd_pcm_close(isa->handle);
|
|
|
|
|
|
|
|
deallocate(isa->poll_fds, isa->poll_fd_count);
|
2015-07-24 03:55:36 +00:00
|
|
|
deallocate(isa->chmap, isa->chmap_size);
|
2015-07-24 04:07:40 +00:00
|
|
|
deallocate(isa->sample_buffer, isa->sample_buffer_size);
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-24 03:55:36 +00:00
|
|
|
static int instream_open_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ch_count = instream->layout.channel_count;
|
|
|
|
|
|
|
|
isa->chmap_size = sizeof(int) + sizeof(int) * ch_count;
|
|
|
|
isa->chmap = (snd_pcm_chmap_t *)allocate<char>(isa->chmap_size);
|
|
|
|
if (!isa->chmap) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
snd_pcm_hw_params_t *hwparams;
|
|
|
|
snd_pcm_hw_params_alloca(&hwparams);
|
|
|
|
|
|
|
|
snd_pcm_stream_t stream = purpose_to_stream(instream->device->purpose);
|
|
|
|
|
|
|
|
if ((err = snd_pcm_open(&isa->handle, instream->device->name, stream, 0)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_pcm_hw_params_any(isa->handle, hwparams)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
int want_resample = !instream->device->is_raw;
|
|
|
|
if ((err = snd_pcm_hw_params_set_rate_resample(isa->handle, hwparams, want_resample)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = set_access(isa->handle, hwparams, &isa->access))) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_pcm_hw_params_set_channels(isa->handle, hwparams, ch_count)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_pcm_hw_params_set_rate(isa->handle, hwparams, instream->sample_rate, 0)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_pcm_format_t format = to_alsa_fmt(instream->format);
|
|
|
|
int phys_bits_per_sample = snd_pcm_format_physical_width(format);
|
|
|
|
if (phys_bits_per_sample % 8 != 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
}
|
|
|
|
int phys_bytes_per_sample = phys_bits_per_sample / 8;
|
|
|
|
if ((err = snd_pcm_hw_params_set_format(isa->handle, hwparams, format)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
snd_pcm_uframes_t period_frames = ceil(instream->period_duration * (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;
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
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) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
isa->period_size = period_size;
|
|
|
|
|
|
|
|
// write the hardware parameters to device
|
|
|
|
if ((err = snd_pcm_hw_params(isa->handle, hwparams)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set channel map
|
|
|
|
isa->chmap->channels = ch_count;
|
|
|
|
for (int i = 0; i < ch_count; i += 1) {
|
|
|
|
isa->chmap->pos[i] = to_alsa_chmap_pos(instream->layout.channels[i]);
|
|
|
|
}
|
|
|
|
if ((err = snd_pcm_set_chmap(isa->handle, isa->chmap)) < 0)
|
|
|
|
instream->layout_error = SoundIoErrorIncompatibleDevice;
|
|
|
|
|
|
|
|
// get current swparams
|
|
|
|
snd_pcm_sw_params_t *swparams;
|
|
|
|
snd_pcm_sw_params_alloca(&swparams);
|
|
|
|
|
|
|
|
if ((err = snd_pcm_sw_params_current(isa->handle, swparams)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write the software parameters to device
|
|
|
|
if ((err = snd_pcm_sw_params(isa->handle, swparams)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return (err == -EINVAL) ? SoundIoErrorIncompatibleDevice : SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isa->access == SND_PCM_ACCESS_RW_INTERLEAVED || isa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
|
|
|
|
isa->sample_buffer_size = ch_count * isa->period_size * phys_bytes_per_sample;
|
|
|
|
isa->sample_buffer = allocate_nonzero<char>(isa->sample_buffer_size);
|
|
|
|
if (!isa->sample_buffer) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
isa->poll_fd_count = snd_pcm_poll_descriptors_count(isa->handle);
|
|
|
|
if (isa->poll_fd_count <= 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
isa->poll_fds = allocate<struct pollfd>(isa->poll_fd_count);
|
|
|
|
if (!isa->poll_fds) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((err = snd_pcm_poll_descriptors(isa->handle, isa->poll_fds, isa->poll_fd_count)) < 0) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return SoundIoErrorOpeningDevice;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
static int instream_start_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
|
|
|
|
assert(!isa->thread);
|
|
|
|
|
|
|
|
isa->thread_exit_flag.test_and_set();
|
|
|
|
int err;
|
|
|
|
if ((err = soundio_os_thread_create(instream_thread_run, is, true, &isa->thread))) {
|
|
|
|
instream_destroy_alsa(si, is);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
static int instream_begin_read_alsa(SoundIoPrivate *si,
|
|
|
|
SoundIoInStreamPrivate *is, SoundIoChannelArea **out_areas, int *frame_count)
|
2015-07-07 09:55:32 +00:00
|
|
|
{
|
2015-07-24 03:55:36 +00:00
|
|
|
*out_areas = nullptr;
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
SoundIoInStream *instream = &is->pub;
|
|
|
|
|
|
|
|
if (isa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
|
|
|
|
for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
|
|
|
|
isa->areas[ch].ptr = isa->sample_buffer + ch * instream->bytes_per_sample;
|
|
|
|
isa->areas[ch].step = instream->bytes_per_frame;
|
|
|
|
}
|
|
|
|
|
|
|
|
isa->read_frame_count = min(*frame_count, isa->period_size);
|
|
|
|
*frame_count = isa->read_frame_count;
|
|
|
|
snd_pcm_sframes_t commitres = snd_pcm_readi(isa->handle, isa->sample_buffer, isa->read_frame_count);
|
|
|
|
if (commitres < 0 || commitres != *frame_count) {
|
|
|
|
int err = (commitres >= 0) ? -EPIPE : commitres;
|
|
|
|
if ((err = instream_xrun_recovery(is, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
} else if (isa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
|
|
|
|
char *ptrs[SOUNDIO_MAX_CHANNELS];
|
|
|
|
for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
|
|
|
|
isa->areas[ch].ptr = isa->sample_buffer + ch * instream->bytes_per_sample * isa->period_size;
|
|
|
|
isa->areas[ch].step = instream->bytes_per_sample;
|
|
|
|
ptrs[ch] = isa->areas[ch].ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
isa->read_frame_count = min(*frame_count, isa->period_size);
|
|
|
|
*frame_count = isa->read_frame_count;
|
|
|
|
snd_pcm_sframes_t commitres = snd_pcm_readn(isa->handle, (void**)ptrs, isa->read_frame_count);
|
|
|
|
if (commitres < 0 || commitres != *frame_count) {
|
|
|
|
int err = (commitres >= 0) ? -EPIPE : commitres;
|
|
|
|
if ((err = instream_xrun_recovery(is, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const snd_pcm_channel_area_t *areas;
|
|
|
|
snd_pcm_uframes_t frames = *frame_count;
|
|
|
|
int err;
|
|
|
|
|
|
|
|
if ((err = snd_pcm_mmap_begin(isa->handle, &areas, &isa->offset, &frames)) < 0) {
|
|
|
|
if ((err = instream_xrun_recovery(is, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (int ch = 0; ch < instream->layout.channel_count; ch += 1) {
|
|
|
|
if ((areas[ch].first % 8 != 0) || (areas[ch].step % 8 != 0))
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
isa->areas[ch].step = areas[ch].step / 8;
|
|
|
|
isa->areas[ch].ptr = ((char *)areas[ch].addr) + (areas[ch].first / 8) +
|
|
|
|
(isa->areas[ch].step * isa->offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
isa->read_frame_count = frames;
|
|
|
|
*frame_count = isa->read_frame_count;
|
|
|
|
}
|
|
|
|
|
|
|
|
*out_areas = isa->areas;
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-22 22:43:45 +00:00
|
|
|
static int instream_end_read_alsa(SoundIoPrivate *si, SoundIoInStreamPrivate *is) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-24 03:55:36 +00:00
|
|
|
|
|
|
|
if (isa->access == SND_PCM_ACCESS_RW_INTERLEAVED) {
|
|
|
|
return 0;
|
|
|
|
} else if (isa->access == SND_PCM_ACCESS_RW_NONINTERLEAVED) {
|
|
|
|
return 0;
|
|
|
|
} else {
|
|
|
|
snd_pcm_sframes_t commitres = snd_pcm_mmap_commit(isa->handle, isa->offset, isa->read_frame_count);
|
|
|
|
if (commitres < 0 || commitres != isa->read_frame_count) {
|
|
|
|
int err = (commitres >= 0) ? -EPIPE : commitres;
|
|
|
|
if ((err = instream_xrun_recovery(is, err)) < 0)
|
|
|
|
return SoundIoErrorStreaming;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2015-07-07 09:55:32 +00:00
|
|
|
}
|
|
|
|
|
2015-07-16 20:37:41 +00:00
|
|
|
static int instream_pause_alsa(struct SoundIoPrivate *si, struct SoundIoInStreamPrivate *is, bool pause) {
|
2015-07-28 00:06:12 +00:00
|
|
|
SoundIoInStreamAlsa *isa = &is->backend_data.alsa;
|
2015-07-16 20:37:41 +00:00
|
|
|
int err;
|
|
|
|
if ((err = snd_pcm_pause(isa->handle, pause)) < 0)
|
|
|
|
return SoundIoErrorIncompatibleDevice;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
int soundio_alsa_init(SoundIoPrivate *si) {
|
2015-07-27 23:37:45 +00:00
|
|
|
SoundIoAlsa *sia = &si->backend_data.alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
int err;
|
|
|
|
|
|
|
|
sia->notify_fd = -1;
|
|
|
|
sia->notify_wd = -1;
|
|
|
|
sia->have_devices_flag.store(false);
|
|
|
|
sia->abort_flag.test_and_set();
|
|
|
|
|
|
|
|
sia->mutex = soundio_os_mutex_create();
|
|
|
|
if (!sia->mutex) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_alsa(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
sia->cond = soundio_os_cond_create();
|
|
|
|
if (!sia->cond) {
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_alsa(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// set up inotify to watch /dev/snd for devices added or removed
|
|
|
|
sia->notify_fd = inotify_init1(IN_NONBLOCK);
|
|
|
|
if (sia->notify_fd == -1) {
|
|
|
|
err = errno;
|
|
|
|
assert(err != EINVAL);
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_alsa(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
if (err == EMFILE || err == ENFILE) {
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
} else {
|
|
|
|
assert(err == ENOMEM);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sia->notify_wd = inotify_add_watch(sia->notify_fd, "/dev/snd", IN_CREATE | IN_DELETE);
|
|
|
|
if (sia->notify_wd == -1) {
|
|
|
|
err = errno;
|
|
|
|
assert(err != EACCES);
|
|
|
|
assert(err != EBADF);
|
|
|
|
assert(err != EFAULT);
|
|
|
|
assert(err != EINVAL);
|
|
|
|
assert(err != ENAMETOOLONG);
|
|
|
|
assert(err != ENOENT);
|
2015-07-13 16:17:20 +00:00
|
|
|
destroy_alsa(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
if (err == ENOSPC) {
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
} else {
|
|
|
|
assert(err == ENOMEM);
|
|
|
|
return SoundIoErrorNoMem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pipe2(sia->notify_pipe_fd, O_NONBLOCK)) {
|
|
|
|
assert(errno != EFAULT);
|
|
|
|
assert(errno != EINVAL);
|
|
|
|
assert(errno == EMFILE || errno == ENFILE);
|
|
|
|
return SoundIoErrorSystemResources;
|
|
|
|
}
|
|
|
|
|
|
|
|
wakeup_device_poll(sia);
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
if ((err = soundio_os_thread_create(device_thread_run, si, false, &sia->thread))) {
|
|
|
|
destroy_alsa(si);
|
2015-07-07 09:55:32 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-07-13 16:17:20 +00:00
|
|
|
si->destroy = destroy_alsa;
|
|
|
|
si->flush_events = flush_events;
|
|
|
|
si->wait_events = wait_events;
|
|
|
|
si->wakeup = wakeup;
|
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
si->outstream_open = outstream_open_alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->outstream_destroy = outstream_destroy_alsa;
|
|
|
|
si->outstream_start = outstream_start_alsa;
|
|
|
|
si->outstream_begin_write = outstream_begin_write_alsa;
|
2015-07-22 22:43:45 +00:00
|
|
|
si->outstream_end_write = outstream_end_write_alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->outstream_clear_buffer = outstream_clear_buffer_alsa;
|
2015-07-16 20:37:41 +00:00
|
|
|
si->outstream_pause = outstream_pause_alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
|
2015-07-14 04:30:37 +00:00
|
|
|
si->instream_open = instream_open_alsa;
|
2015-07-13 16:17:20 +00:00
|
|
|
si->instream_destroy = instream_destroy_alsa;
|
|
|
|
si->instream_start = instream_start_alsa;
|
2015-07-22 22:43:45 +00:00
|
|
|
si->instream_begin_read = instream_begin_read_alsa;
|
|
|
|
si->instream_end_read = instream_end_read_alsa;
|
2015-07-16 20:37:41 +00:00
|
|
|
si->instream_pause = instream_pause_alsa;
|
2015-07-07 09:55:32 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|