pulseaudio/src/modules/alsa/alsa-util.c
Lennart Poettering 39aa1cf94d alsa: revert to first set number of periods, then set buffer size
Apparently some ALSA drivers aren't happy with getting the buffer size
configured first followed the period size. So swap the order again and
document this for future reference so that we don't turn that around
again.
2009-07-31 02:07:24 +02:00

1227 lines
35 KiB
C

/***
This file is part of PulseAudio.
Copyright 2004-2009 Lennart Poettering
Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB
PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with PulseAudio; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA.
***/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <sys/types.h>
#include <limits.h>
#include <asoundlib.h>
#include <pulse/sample.h>
#include <pulse/xmalloc.h>
#include <pulse/timeval.h>
#include <pulse/util.h>
#include <pulse/i18n.h>
#include <pulse/utf8.h>
#include <pulsecore/log.h>
#include <pulsecore/macro.h>
#include <pulsecore/core-util.h>
#include <pulsecore/atomic.h>
#include <pulsecore/core-error.h>
#include <pulsecore/once.h>
#include <pulsecore/thread.h>
#include <pulsecore/conf-parser.h>
#include "alsa-util.h"
#include "alsa-mixer.h"
#ifdef HAVE_HAL
#include "hal-util.h"
#endif
#ifdef HAVE_UDEV
#include "udev-util.h"
#endif
static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) {
static const snd_pcm_format_t format_trans[] = {
[PA_SAMPLE_U8] = SND_PCM_FORMAT_U8,
[PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW,
[PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW,
[PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE,
[PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE,
[PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE,
[PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE,
[PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE,
[PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE,
[PA_SAMPLE_S24LE] = SND_PCM_FORMAT_S24_3LE,
[PA_SAMPLE_S24BE] = SND_PCM_FORMAT_S24_3BE,
[PA_SAMPLE_S24_32LE] = SND_PCM_FORMAT_S24_LE,
[PA_SAMPLE_S24_32BE] = SND_PCM_FORMAT_S24_BE,
};
static const pa_sample_format_t try_order[] = {
PA_SAMPLE_FLOAT32NE,
PA_SAMPLE_FLOAT32RE,
PA_SAMPLE_S32NE,
PA_SAMPLE_S32RE,
PA_SAMPLE_S24_32NE,
PA_SAMPLE_S24_32RE,
PA_SAMPLE_S24NE,
PA_SAMPLE_S24RE,
PA_SAMPLE_S16NE,
PA_SAMPLE_S16RE,
PA_SAMPLE_ALAW,
PA_SAMPLE_ULAW,
PA_SAMPLE_U8
};
unsigned i;
int ret;
pa_assert(pcm_handle);
pa_assert(f);
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
return ret;
pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s",
snd_pcm_format_description(format_trans[*f]),
pa_alsa_strerror(ret));
if (*f == PA_SAMPLE_FLOAT32BE)
*f = PA_SAMPLE_FLOAT32LE;
else if (*f == PA_SAMPLE_FLOAT32LE)
*f = PA_SAMPLE_FLOAT32BE;
else if (*f == PA_SAMPLE_S24BE)
*f = PA_SAMPLE_S24LE;
else if (*f == PA_SAMPLE_S24LE)
*f = PA_SAMPLE_S24BE;
else if (*f == PA_SAMPLE_S24_32BE)
*f = PA_SAMPLE_S24_32LE;
else if (*f == PA_SAMPLE_S24_32LE)
*f = PA_SAMPLE_S24_32BE;
else if (*f == PA_SAMPLE_S16BE)
*f = PA_SAMPLE_S16LE;
else if (*f == PA_SAMPLE_S16LE)
*f = PA_SAMPLE_S16BE;
else if (*f == PA_SAMPLE_S32BE)
*f = PA_SAMPLE_S32LE;
else if (*f == PA_SAMPLE_S32LE)
*f = PA_SAMPLE_S32BE;
else
goto try_auto;
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
return ret;
pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s",
snd_pcm_format_description(format_trans[*f]),
pa_alsa_strerror(ret));
try_auto:
for (i = 0; i < PA_ELEMENTSOF(try_order); i++) {
*f = try_order[i];
if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0)
return ret;
pa_log_debug("snd_pcm_hw_params_set_format(%s) failed: %s",
snd_pcm_format_description(format_trans[*f]),
pa_alsa_strerror(ret));
}
return -1;
}
/* Set the hardware parameters of the given ALSA device. Returns the
* selected fragment settings in *period and *period_size */
int pa_alsa_set_hw_params(
snd_pcm_t *pcm_handle,
pa_sample_spec *ss,
uint32_t *periods,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_bool_t require_exact_channel_number) {
int ret = -1;
snd_pcm_uframes_t _period_size = period_size ? *period_size : 0;
unsigned int _periods = periods ? *periods : 0;
unsigned int r = ss->rate;
unsigned int c = ss->channels;
pa_sample_format_t f = ss->format;
snd_pcm_hw_params_t *hwparams;
pa_bool_t _use_mmap = use_mmap && *use_mmap;
pa_bool_t _use_tsched = use_tsched && *use_tsched;
int dir;
pa_assert(pcm_handle);
pa_assert(ss);
snd_pcm_hw_params_alloca(&hwparams);
if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) {
pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_rate_resample() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (_use_mmap) {
if (snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED) < 0) {
/* mmap() didn't work, fall back to interleaved */
if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
_use_mmap = FALSE;
}
} else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_access() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (!_use_mmap)
_use_tsched = FALSE;
if ((ret = set_format(pcm_handle, hwparams, &f)) < 0)
goto finish;
if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_rate_near() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (require_exact_channel_number) {
if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_channels(%u) failed: %s", c, pa_alsa_strerror(ret));
goto finish;
}
} else {
if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_channels_near(%u) failed: %s", c, pa_alsa_strerror(ret));
goto finish;
}
}
if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) {
pa_log_debug("snd_pcm_hw_params_set_periods_integer() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if (_period_size > 0 && tsched_size > 0 && _periods > 0) {
snd_pcm_uframes_t buffer_size;
unsigned int p;
/* Adjust the buffer sizes, if we didn't get the rate we were asking for */
_period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate);
tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate);
if (_use_tsched) {
buffer_size = 0;
if ((ret = snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size)) < 0)
pa_log_warn("snd_pcm_hw_params_get_buffer_size_max() failed: %s", pa_alsa_strerror(ret));
else
pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r);
_period_size = tsched_size;
_periods = 1;
}
/* Some ALSA drivers really don't like if we set the buffer
* size first and the number of periods second. (which would
* make a lot more sense to me) So, follow this rule and
* adjust the periods first and the buffer size second */
/* First we pass 0 as direction to get exactly what we
* asked for. That this is necessary is presumably a bug
* in ALSA. All in all this is mostly a hint to ALSA, so
* we don't care if this fails. */
p = _periods;
dir = 0;
if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) {
p = _periods;
dir = 1;
if (snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir) < 0) {
p = _periods;
dir = -1;
if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &p, &dir)) < 0)
pa_log_info("snd_pcm_hw_params_set_periods_near() failed: %s", pa_alsa_strerror(ret));
}
}
/* Now set the buffer size */
buffer_size = _periods * _period_size;
if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0)
pa_log_info("snd_pcm_hw_params_set_buffer_size_near() failed: %s", pa_alsa_strerror(ret));
}
if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0)
goto finish;
if (ss->rate != r)
pa_log_info("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r);
if (ss->channels != c)
pa_log_info("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c);
if (ss->format != f)
pa_log_info("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f));
if ((ret = snd_pcm_prepare(pcm_handle)) < 0) {
pa_log_info("snd_pcm_prepare() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
if ((ret = snd_pcm_hw_params_get_period_size(hwparams, &_period_size, &dir)) < 0 ||
(ret = snd_pcm_hw_params_get_periods(hwparams, &_periods, &dir)) < 0) {
pa_log_info("snd_pcm_hw_params_get_period{s|_size}() failed: %s", pa_alsa_strerror(ret));
goto finish;
}
/* If the sample rate deviates too much, we need to resample */
if (r < ss->rate*.95 || r > ss->rate*1.05)
ss->rate = r;
ss->channels = (uint8_t) c;
ss->format = f;
pa_assert(_periods > 0);
pa_assert(_period_size > 0);
if (periods)
*periods = _periods;
if (period_size)
*period_size = _period_size;
if (use_mmap)
*use_mmap = _use_mmap;
if (use_tsched)
*use_tsched = _use_tsched;
ret = 0;
snd_pcm_nonblock(pcm_handle, 1);
finish:
return ret;
}
int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) {
snd_pcm_sw_params_t *swparams;
snd_pcm_uframes_t boundary;
int err;
pa_assert(pcm);
snd_pcm_sw_params_alloca(&swparams);
if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) {
pa_log_warn("Unable to determine current swparams: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_set_period_event(pcm, swparams, 0)) < 0) {
pa_log_warn("Unable to disable period event: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_set_tstamp_mode(pcm, swparams, SND_PCM_TSTAMP_ENABLE)) < 0) {
pa_log_warn("Unable to enable time stamping: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_get_boundary(swparams, &boundary)) < 0) {
pa_log_warn("Unable to get boundary: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, boundary)) < 0) {
pa_log_warn("Unable to set stop threshold: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) {
pa_log_warn("Unable to set start threshold: %s\n", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, avail_min)) < 0) {
pa_log_error("snd_pcm_sw_params_set_avail_min() failed: %s", pa_alsa_strerror(err));
return err;
}
if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) {
pa_log_warn("Unable to set sw params: %s\n", pa_alsa_strerror(err));
return err;
}
return 0;
}
snd_pcm_t *pa_alsa_open_by_device_id_auto(
const char *dev_id,
char **dev,
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_alsa_profile_set *ps,
pa_alsa_mapping **mapping) {
char *d;
snd_pcm_t *pcm_handle;
void *state;
pa_alsa_mapping *m;
pa_assert(dev_id);
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
pa_assert(nfrags);
pa_assert(period_size);
pa_assert(ps);
/* First we try to find a device string with a superset of the
* requested channel map. We iterate through our device table from
* top to bottom and take the first that matches. If we didn't
* find a working device that way, we iterate backwards, and check
* all devices that do not provide a superset of the requested
* channel map.*/
PA_HASHMAP_FOREACH(m, ps->mappings, state) {
if (!pa_channel_map_superset(&m->channel_map, map))
continue;
pa_log_debug("Checking for superset %s (%s)", m->name, m->device_strings[0]);
pcm_handle = pa_alsa_open_by_device_id_mapping(
dev_id,
dev,
ss,
map,
mode,
nfrags,
period_size,
tsched_size,
use_mmap,
use_tsched,
m);
if (pcm_handle) {
if (mapping)
*mapping = m;
return pcm_handle;
}
}
PA_HASHMAP_FOREACH_BACKWARDS(m, ps->mappings, state) {
if (pa_channel_map_superset(&m->channel_map, map))
continue;
pa_log_debug("Checking for subset %s (%s)", m->name, m->device_strings[0]);
pcm_handle = pa_alsa_open_by_device_id_mapping(
dev_id,
dev,
ss,
map,
mode,
nfrags,
period_size,
tsched_size,
use_mmap,
use_tsched,
m);
if (pcm_handle) {
if (mapping)
*mapping = m;
return pcm_handle;
}
}
/* OK, we didn't find any good device, so let's try the raw hw: stuff */
d = pa_sprintf_malloc("hw:%s", dev_id);
pa_log_debug("Trying %s as last resort...", d);
pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE);
pa_xfree(d);
if (pcm_handle && mapping)
*mapping = NULL;
return pcm_handle;
}
snd_pcm_t *pa_alsa_open_by_device_id_mapping(
const char *dev_id,
char **dev,
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_alsa_mapping *m) {
snd_pcm_t *pcm_handle;
pa_sample_spec try_ss;
pa_channel_map try_map;
pa_assert(dev_id);
pa_assert(dev);
pa_assert(ss);
pa_assert(map);
pa_assert(nfrags);
pa_assert(period_size);
pa_assert(m);
try_ss.channels = m->channel_map.channels;
try_ss.rate = ss->rate;
try_ss.format = ss->format;
try_map = m->channel_map;
pcm_handle = pa_alsa_open_by_template(
m->device_strings,
dev_id,
dev,
&try_ss,
&try_map,
mode,
nfrags,
period_size,
tsched_size,
use_mmap,
use_tsched,
TRUE);
if (!pcm_handle)
return NULL;
*ss = try_ss;
*map = try_map;
pa_assert(map->channels == ss->channels);
return pcm_handle;
}
snd_pcm_t *pa_alsa_open_by_device_string(
const char *device,
char **dev,
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_bool_t require_exact_channel_number) {
int err;
char *d;
snd_pcm_t *pcm_handle;
pa_bool_t reformat = FALSE;
pa_assert(device);
pa_assert(ss);
pa_assert(map);
d = pa_xstrdup(device);
for (;;) {
pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with");
if ((err = snd_pcm_open(&pcm_handle, d, mode,
SND_PCM_NONBLOCK|
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
(reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) {
pa_log_info("Error opening PCM device %s: %s", d, pa_alsa_strerror(err));
goto fail;
}
pa_log_debug("Managed to open %s", d);
if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, require_exact_channel_number)) < 0) {
if (!reformat) {
reformat = TRUE;
snd_pcm_close(pcm_handle);
continue;
}
/* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */
if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) {
char *t;
t = pa_sprintf_malloc("plug:%s", d);
pa_xfree(d);
d = t;
reformat = FALSE;
snd_pcm_close(pcm_handle);
continue;
}
pa_log_info("Failed to set hardware parameters on %s: %s", d, pa_alsa_strerror(err));
snd_pcm_close(pcm_handle);
goto fail;
}
if (dev)
*dev = d;
else
pa_xfree(d);
if (ss->channels != map->channels)
pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA);
return pcm_handle;
}
fail:
pa_xfree(d);
return NULL;
}
snd_pcm_t *pa_alsa_open_by_template(
char **template,
const char *dev_id,
char **dev,
pa_sample_spec *ss,
pa_channel_map* map,
int mode,
uint32_t *nfrags,
snd_pcm_uframes_t *period_size,
snd_pcm_uframes_t tsched_size,
pa_bool_t *use_mmap,
pa_bool_t *use_tsched,
pa_bool_t require_exact_channel_number) {
snd_pcm_t *pcm_handle;
char **i;
for (i = template; *i; i++) {
char *d;
d = pa_replace(*i, "%f", dev_id);
pcm_handle = pa_alsa_open_by_device_string(
d,
dev,
ss,
map,
mode,
nfrags,
period_size,
tsched_size,
use_mmap,
use_tsched,
require_exact_channel_number);
pa_xfree(d);
if (pcm_handle)
return pcm_handle;
}
return NULL;
}
void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) {
int err;
snd_output_t *out;
pa_assert(pcm);
pa_assert_se(snd_output_buffer_open(&out) == 0);
if ((err = snd_pcm_dump(pcm, out)) < 0)
pa_logl(level, "snd_pcm_dump(): %s", pa_alsa_strerror(err));
else {
char *s = NULL;
snd_output_buffer_string(out, &s);
pa_logl(level, "snd_pcm_dump():\n%s", pa_strnull(s));
}
pa_assert_se(snd_output_close(out) == 0);
}
void pa_alsa_dump_status(snd_pcm_t *pcm) {
int err;
snd_output_t *out;
snd_pcm_status_t *status;
char *s = NULL;
pa_assert(pcm);
snd_pcm_status_alloca(&status);
if ((err = snd_output_buffer_open(&out)) < 0) {
pa_log_debug("snd_output_buffer_open() failed: %s", pa_cstrerror(err));
return;
}
if ((err = snd_pcm_status(pcm, status)) < 0) {
pa_log_debug("snd_pcm_status() failed: %s", pa_cstrerror(err));
goto finish;
}
if ((err = snd_pcm_status_dump(status, out)) < 0) {
pa_log_debug("snd_pcm_dump(): %s", pa_alsa_strerror(err));
goto finish;
}
snd_output_buffer_string(out, &s);
pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s));
finish:
snd_output_close(out);
}
static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) {
va_list ap;
char *alsa_file;
alsa_file = pa_sprintf_malloc("(alsa-lib)%s", file);
va_start(ap, fmt);
pa_log_levelv_meta(PA_LOG_INFO, alsa_file, line, function, fmt, ap);
va_end(ap);
pa_xfree(alsa_file);
}
static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0);
void pa_alsa_refcnt_inc(void) {
/* This is not really thread safe, but we do our best */
if (pa_atomic_inc(&n_error_handler_installed) == 0)
snd_lib_error_set_handler(alsa_error_handler);
}
void pa_alsa_refcnt_dec(void) {
int r;
pa_assert_se((r = pa_atomic_dec(&n_error_handler_installed)) >= 1);
if (r == 1) {
snd_lib_error_set_handler(NULL);
snd_config_update_free_global();
}
}
pa_bool_t pa_alsa_init_description(pa_proplist *p) {
const char *d, *k;
pa_assert(p);
if (pa_device_init_description(p))
return TRUE;
if (!(d = pa_proplist_gets(p, "alsa.card_name")))
d = pa_proplist_gets(p, "alsa.name");
if (!d)
return FALSE;
k = pa_proplist_gets(p, PA_PROP_DEVICE_PROFILE_DESCRIPTION);
if (d && k)
pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, _("%s %s"), d, k);
else if (d)
pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, d);
return FALSE;
}
void pa_alsa_init_proplist_card(pa_core *c, pa_proplist *p, int card) {
char *cn, *lcn, *dn;
pa_assert(p);
pa_assert(card >= 0);
pa_proplist_setf(p, "alsa.card", "%i", card);
if (snd_card_get_name(card, &cn) >= 0) {
pa_proplist_sets(p, "alsa.card_name", cn);
free(cn);
}
if (snd_card_get_longname(card, &lcn) >= 0) {
pa_proplist_sets(p, "alsa.long_card_name", lcn);
free(lcn);
}
if ((dn = pa_alsa_get_driver_name(card))) {
pa_proplist_sets(p, "alsa.driver_name", dn);
pa_xfree(dn);
}
#ifdef HAVE_UDEV
pa_udev_get_info(card, p);
#endif
#ifdef HAVE_HAL
pa_hal_get_info(c, p, card);
#endif
}
void pa_alsa_init_proplist_pcm_info(pa_core *c, pa_proplist *p, snd_pcm_info_t *pcm_info) {
static const char * const alsa_class_table[SND_PCM_CLASS_LAST+1] = {
[SND_PCM_CLASS_GENERIC] = "generic",
[SND_PCM_CLASS_MULTI] = "multi",
[SND_PCM_CLASS_MODEM] = "modem",
[SND_PCM_CLASS_DIGITIZER] = "digitizer"
};
static const char * const class_table[SND_PCM_CLASS_LAST+1] = {
[SND_PCM_CLASS_GENERIC] = "sound",
[SND_PCM_CLASS_MULTI] = NULL,
[SND_PCM_CLASS_MODEM] = "modem",
[SND_PCM_CLASS_DIGITIZER] = NULL
};
static const char * const alsa_subclass_table[SND_PCM_SUBCLASS_LAST+1] = {
[SND_PCM_SUBCLASS_GENERIC_MIX] = "generic-mix",
[SND_PCM_SUBCLASS_MULTI_MIX] = "multi-mix"
};
snd_pcm_class_t class;
snd_pcm_subclass_t subclass;
const char *n, *id, *sdn;
int card;
pa_assert(p);
pa_assert(pcm_info);
pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa");
if ((class = snd_pcm_info_get_class(pcm_info)) <= SND_PCM_CLASS_LAST) {
if (class_table[class])
pa_proplist_sets(p, PA_PROP_DEVICE_CLASS, class_table[class]);
if (alsa_class_table[class])
pa_proplist_sets(p, "alsa.class", alsa_class_table[class]);
}
if ((subclass = snd_pcm_info_get_subclass(pcm_info)) <= SND_PCM_SUBCLASS_LAST)
if (alsa_subclass_table[subclass])
pa_proplist_sets(p, "alsa.subclass", alsa_subclass_table[subclass]);
if ((n = snd_pcm_info_get_name(pcm_info)))
pa_proplist_sets(p, "alsa.name", n);
if ((id = snd_pcm_info_get_id(pcm_info)))
pa_proplist_sets(p, "alsa.id", id);
pa_proplist_setf(p, "alsa.subdevice", "%u", snd_pcm_info_get_subdevice(pcm_info));
if ((sdn = snd_pcm_info_get_subdevice_name(pcm_info)))
pa_proplist_sets(p, "alsa.subdevice_name", sdn);
pa_proplist_setf(p, "alsa.device", "%u", snd_pcm_info_get_device(pcm_info));
if ((card = snd_pcm_info_get_card(pcm_info)) >= 0)
pa_alsa_init_proplist_card(c, p, card);
}
void pa_alsa_init_proplist_pcm(pa_core *c, pa_proplist *p, snd_pcm_t *pcm) {
snd_pcm_hw_params_t *hwparams;
snd_pcm_info_t *info;
int bits, err;
snd_pcm_hw_params_alloca(&hwparams);
snd_pcm_info_alloca(&info);
if ((err = snd_pcm_hw_params_current(pcm, hwparams)) < 0)
pa_log_warn("Error fetching hardware parameter info: %s", pa_alsa_strerror(err));
else {
if ((bits = snd_pcm_hw_params_get_sbits(hwparams)) >= 0)
pa_proplist_setf(p, "alsa.resolution_bits", "%i", bits);
}
if ((err = snd_pcm_info(pcm, info)) < 0)
pa_log_warn("Error fetching PCM info: %s", pa_alsa_strerror(err));
else
pa_alsa_init_proplist_pcm_info(c, p, info);
}
void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) {
int err;
snd_ctl_t *ctl;
snd_ctl_card_info_t *info;
const char *t;
pa_assert(p);
snd_ctl_card_info_alloca(&info);
if ((err = snd_ctl_open(&ctl, name, 0)) < 0) {
pa_log_warn("Error opening low-level control device '%s'", name);
return;
}
if ((err = snd_ctl_card_info(ctl, info)) < 0) {
pa_log_warn("Control device %s card info: %s", name, snd_strerror(err));
snd_ctl_close(ctl);
return;
}
if ((t = snd_ctl_card_info_get_mixername(info)) && *t)
pa_proplist_sets(p, "alsa.mixer_name", t);
if ((t = snd_ctl_card_info_get_components(info)) && *t)
pa_proplist_sets(p, "alsa.components", t);
snd_ctl_close(ctl);
}
int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
snd_pcm_state_t state;
int err;
pa_assert(pcm);
if (revents & POLLERR)
pa_log_debug("Got POLLERR from ALSA");
if (revents & POLLNVAL)
pa_log_warn("Got POLLNVAL from ALSA");
if (revents & POLLHUP)
pa_log_warn("Got POLLHUP from ALSA");
if (revents & POLLPRI)
pa_log_warn("Got POLLPRI from ALSA");
if (revents & POLLIN)
pa_log_debug("Got POLLIN from ALSA");
if (revents & POLLOUT)
pa_log_debug("Got POLLOUT from ALSA");
state = snd_pcm_state(pcm);
pa_log_debug("PCM state is %s", snd_pcm_state_name(state));
/* Try to recover from this error */
switch (state) {
case SND_PCM_STATE_XRUN:
if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) {
pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", pa_alsa_strerror(err));
return -1;
}
break;
case SND_PCM_STATE_SUSPENDED:
if ((err = snd_pcm_recover(pcm, -ESTRPIPE, 1)) != 0) {
pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", pa_alsa_strerror(err));
return -1;
}
break;
default:
snd_pcm_drop(pcm);
if ((err = snd_pcm_prepare(pcm)) < 0) {
pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", pa_alsa_strerror(err));
return -1;
}
break;
}
return 0;
}
pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) {
int n, err;
struct pollfd *pollfd;
pa_rtpoll_item *item;
pa_assert(pcm);
if ((n = snd_pcm_poll_descriptors_count(pcm)) < 0) {
pa_log("snd_pcm_poll_descriptors_count() failed: %s", pa_alsa_strerror(n));
return NULL;
}
item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, (unsigned) n);
pollfd = pa_rtpoll_item_get_pollfd(item, NULL);
if ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) {
pa_log("snd_pcm_poll_descriptors() failed: %s", pa_alsa_strerror(err));
pa_rtpoll_item_free(item);
return NULL;
}
return item;
}
snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss) {
snd_pcm_sframes_t n;
size_t k;
pa_assert(pcm);
pa_assert(hwbuf_size > 0);
pa_assert(ss);
/* Some ALSA driver expose weird bugs, let's inform the user about
* what is going on */
n = snd_pcm_avail(pcm);
if (n <= 0)
return n;
k = (size_t) n * pa_frame_size(ss);
if (k >= hwbuf_size * 5 ||
k >= pa_bytes_per_second(ss)*10) {
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(_("snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
(unsigned long) k,
(unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
} PA_ONCE_END;
/* Mhmm, let's try not to fail completely */
n = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
}
return n;
}
int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_size, const pa_sample_spec *ss) {
ssize_t k;
size_t abs_k;
int r;
pa_assert(pcm);
pa_assert(delay);
pa_assert(hwbuf_size > 0);
pa_assert(ss);
/* Some ALSA driver expose weird bugs, let's inform the user about
* what is going on */
if ((r = snd_pcm_delay(pcm, delay)) < 0)
return r;
k = (ssize_t) *delay * (ssize_t) pa_frame_size(ss);
abs_k = k >= 0 ? (size_t) k : (size_t) -k;
if (abs_k >= hwbuf_size * 5 ||
abs_k >= pa_bytes_per_second(ss)*10) {
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(_("snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
(signed long) k,
k < 0 ? "-" : "",
(unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
} PA_ONCE_END;
/* Mhmm, let's try not to fail completely */
if (k < 0)
*delay = -(snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
else
*delay = (snd_pcm_sframes_t) (hwbuf_size / pa_frame_size(ss));
}
return 0;
}
int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss) {
int r;
snd_pcm_uframes_t before;
size_t k;
pa_assert(pcm);
pa_assert(areas);
pa_assert(offset);
pa_assert(frames);
pa_assert(hwbuf_size > 0);
pa_assert(ss);
before = *frames;
r = snd_pcm_mmap_begin(pcm, areas, offset, frames);
if (r < 0)
return r;
k = (size_t) *frames * pa_frame_size(ss);
if (*frames > before ||
k >= hwbuf_size * 3 ||
k >= pa_bytes_per_second(ss)*10)
PA_ONCE_BEGIN {
char *dn = pa_alsa_get_driver_name_by_pcm(pcm);
pa_log(_("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."),
(unsigned long) k,
(unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC),
pa_strnull(dn));
pa_xfree(dn);
pa_alsa_dump(PA_LOG_ERROR, pcm);
} PA_ONCE_END;
return r;
}
char *pa_alsa_get_driver_name(int card) {
char *t, *m, *n;
pa_assert(card >= 0);
t = pa_sprintf_malloc("/sys/class/sound/card%i/device/driver/module", card);
m = pa_readlink(t);
pa_xfree(t);
if (!m)
return NULL;
n = pa_xstrdup(pa_path_get_filename(m));
pa_xfree(m);
return n;
}
char *pa_alsa_get_driver_name_by_pcm(snd_pcm_t *pcm) {
int card;
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);
pa_assert(pcm);
if (snd_pcm_info(pcm, info) < 0)
return NULL;
if ((card = snd_pcm_info_get_card(info)) < 0)
return NULL;
return pa_alsa_get_driver_name(card);
}
char *pa_alsa_get_reserve_name(const char *device) {
const char *t;
int i;
pa_assert(device);
if ((t = strchr(device, ':')))
device = t+1;
if ((i = snd_card_get_index(device)) < 0) {
int32_t k;
if (pa_atoi(device, &k) < 0)
return NULL;
i = (int) k;
}
return pa_sprintf_malloc("Audio%i", i);
}
pa_bool_t pa_alsa_pcm_is_hw(snd_pcm_t *pcm) {
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);
pa_assert(pcm);
if (snd_pcm_info(pcm, info) < 0)
return FALSE;
return snd_pcm_info_get_card(info) >= 0;
}
pa_bool_t pa_alsa_pcm_is_modem(snd_pcm_t *pcm) {
snd_pcm_info_t* info;
snd_pcm_info_alloca(&info);
pa_assert(pcm);
if (snd_pcm_info(pcm, info) < 0)
return FALSE;
return snd_pcm_info_get_class(info) == SND_PCM_CLASS_MODEM;
}
PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree);
const char* pa_alsa_strerror(int errnum) {
const char *original = NULL;
char *translated, *t;
char errbuf[128];
if ((t = PA_STATIC_TLS_GET(cstrerror)))
pa_xfree(t);
original = snd_strerror(errnum);
if (!original) {
pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", errnum);
original = errbuf;
}
if (!(translated = pa_locale_to_utf8(original))) {
pa_log_warn("Unable to convert error string to locale, filtering.");
translated = pa_utf8_filter(original);
}
PA_STATIC_TLS_SET(cstrerror, translated);
return translated;
}