From c7fff97782bb59815e7ebd38895cc1480719e24c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Jan 2009 21:02:01 +0100 Subject: move alsa and oss modules into their own subdirectories --- src/Makefile.am | 16 +- src/modules/alsa-util.c | 1261 ------------------------- src/modules/alsa-util.h | 98 -- src/modules/alsa/alsa-util.c | 1261 +++++++++++++++++++++++++ src/modules/alsa/alsa-util.h | 98 ++ src/modules/alsa/module-alsa-sink.c | 1674 +++++++++++++++++++++++++++++++++ src/modules/alsa/module-alsa-source.c | 1491 +++++++++++++++++++++++++++++ src/modules/module-alsa-sink.c | 1674 --------------------------------- src/modules/module-alsa-source.c | 1491 ----------------------------- src/modules/module-oss.c | 1536 ------------------------------ src/modules/oss-util.c | 417 -------- src/modules/oss-util.h | 41 - src/modules/oss/module-oss.c | 1536 ++++++++++++++++++++++++++++++ src/modules/oss/oss-util.c | 417 ++++++++ src/modules/oss/oss-util.h | 41 + 15 files changed, 6526 insertions(+), 6526 deletions(-) delete mode 100644 src/modules/alsa-util.c delete mode 100644 src/modules/alsa-util.h create mode 100644 src/modules/alsa/alsa-util.c create mode 100644 src/modules/alsa/alsa-util.h create mode 100644 src/modules/alsa/module-alsa-sink.c create mode 100644 src/modules/alsa/module-alsa-source.c delete mode 100644 src/modules/module-alsa-sink.c delete mode 100644 src/modules/module-alsa-source.c delete mode 100644 src/modules/module-oss.c delete mode 100644 src/modules/oss-util.c delete mode 100644 src/modules/oss-util.h create mode 100644 src/modules/oss/module-oss.c create mode 100644 src/modules/oss/oss-util.c create mode 100644 src/modules/oss/oss-util.h diff --git a/src/Makefile.am b/src/Makefile.am index 4b36f6f2..62df962e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1036,9 +1036,9 @@ SYMDEF_FILES = \ modules/module-x11-bell-symdef.h \ modules/module-x11-publish-symdef.h \ modules/module-x11-xsmp-symdef.h \ - modules/module-oss-symdef.h \ - modules/module-alsa-sink-symdef.h \ - modules/module-alsa-source-symdef.h \ + modules/oss/module-oss-symdef.h \ + modules/alsa/module-alsa-sink-symdef.h \ + modules/alsa/module-alsa-source-symdef.h \ modules/module-solaris-symdef.h \ modules/module-waveout-symdef.h \ modules/module-detect-symdef.h \ @@ -1232,27 +1232,27 @@ module_x11_xsmp_la_LIBADD = $(AM_LIBADD) $(X11_LIBS) libpulsecore-@PA_MAJORMINOR # OSS -liboss_util_la_SOURCES = modules/oss-util.c modules/oss-util.h +liboss_util_la_SOURCES = modules/oss/oss-util.c modules/oss/oss-util.h liboss_util_la_LDFLAGS = -avoid-version liboss_util_la_LIBADD = libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la -module_oss_la_SOURCES = modules/module-oss.c +module_oss_la_SOURCES = modules/oss/module-oss.c module_oss_la_LDFLAGS = $(MODULE_LDFLAGS) module_oss_la_LIBADD = $(AM_LIBADD) liboss-util.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la # ALSA -libalsa_util_la_SOURCES = modules/alsa-util.c modules/alsa-util.h +libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h libalsa_util_la_LDFLAGS = -avoid-version libalsa_util_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) -module_alsa_sink_la_SOURCES = modules/module-alsa-sink.c +module_alsa_sink_la_SOURCES = modules/alsa/module-alsa-sink.c module_alsa_sink_la_LDFLAGS = $(MODULE_LDFLAGS) module_alsa_sink_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_alsa_sink_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) -module_alsa_source_la_SOURCES = modules/module-alsa-source.c +module_alsa_source_la_SOURCES = modules/alsa/module-alsa-source.c module_alsa_source_la_LDFLAGS = $(MODULE_LDFLAGS) module_alsa_source_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore-@PA_MAJORMINORMICRO@.la libpulsecommon-@PA_MAJORMINORMICRO@.la libpulse.la module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c deleted file mode 100644 index ff3af19d..00000000 --- a/src/modules/alsa-util.c +++ /dev/null @@ -1,1261 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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 -#endif - -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include "alsa-util.h" - -struct pa_alsa_fdlist { - unsigned num_fds; - struct pollfd *fds; - /* This is a temporary buffer used to avoid lots of mallocs */ - struct pollfd *work_fds; - - snd_mixer_t *mixer; - - pa_mainloop_api *m; - pa_defer_event *defer; - pa_io_event **ios; - - pa_bool_t polled; - - void (*cb)(void *userdata); - void *userdata; -}; - -static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { - - struct pa_alsa_fdlist *fdl = userdata; - int err; - unsigned i; - unsigned short revents; - - pa_assert(a); - pa_assert(fdl); - pa_assert(fdl->mixer); - pa_assert(fdl->fds); - pa_assert(fdl->work_fds); - - if (fdl->polled) - return; - - fdl->polled = TRUE; - - memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - - for (i = 0; i < fdl->num_fds; i++) { - if (e == fdl->ios[i]) { - if (events & PA_IO_EVENT_INPUT) - fdl->work_fds[i].revents |= POLLIN; - if (events & PA_IO_EVENT_OUTPUT) - fdl->work_fds[i].revents |= POLLOUT; - if (events & PA_IO_EVENT_ERROR) - fdl->work_fds[i].revents |= POLLERR; - if (events & PA_IO_EVENT_HANGUP) - fdl->work_fds[i].revents |= POLLHUP; - break; - } - } - - pa_assert(i != fdl->num_fds); - - if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { - pa_log_error("Unable to get poll revent: %s", snd_strerror(err)); - return; - } - - a->defer_enable(fdl->defer, 1); - - if (revents) - snd_mixer_handle_events(fdl->mixer); -} - -static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { - struct pa_alsa_fdlist *fdl = userdata; - unsigned num_fds, i; - int err; - struct pollfd *temp; - - pa_assert(a); - pa_assert(fdl); - pa_assert(fdl->mixer); - - a->defer_enable(fdl->defer, 0); - - num_fds = (unsigned) snd_mixer_poll_descriptors_count(fdl->mixer); - - if (num_fds != fdl->num_fds) { - if (fdl->fds) - pa_xfree(fdl->fds); - if (fdl->work_fds) - pa_xfree(fdl->work_fds); - fdl->fds = pa_xnew0(struct pollfd, num_fds); - fdl->work_fds = pa_xnew(struct pollfd, num_fds); - } - - memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); - - if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { - pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err)); - return; - } - - fdl->polled = FALSE; - - if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) - return; - - if (fdl->ios) { - for (i = 0; i < fdl->num_fds; i++) - a->io_free(fdl->ios[i]); - - if (num_fds != fdl->num_fds) { - pa_xfree(fdl->ios); - fdl->ios = NULL; - } - } - - if (!fdl->ios) - fdl->ios = pa_xnew(pa_io_event*, num_fds); - - /* Swap pointers */ - temp = fdl->work_fds; - fdl->work_fds = fdl->fds; - fdl->fds = temp; - - fdl->num_fds = num_fds; - - for (i = 0;i < num_fds;i++) - fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, - ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | - ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), - io_cb, fdl); -} - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { - struct pa_alsa_fdlist *fdl; - - fdl = pa_xnew0(struct pa_alsa_fdlist, 1); - - fdl->num_fds = 0; - fdl->fds = NULL; - fdl->work_fds = NULL; - fdl->mixer = NULL; - fdl->m = NULL; - fdl->defer = NULL; - fdl->ios = NULL; - fdl->polled = FALSE; - - return fdl; -} - -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { - pa_assert(fdl); - - if (fdl->defer) { - pa_assert(fdl->m); - fdl->m->defer_free(fdl->defer); - } - - if (fdl->ios) { - unsigned i; - pa_assert(fdl->m); - for (i = 0; i < fdl->num_fds; i++) - fdl->m->io_free(fdl->ios[i]); - pa_xfree(fdl->ios); - } - - if (fdl->fds) - pa_xfree(fdl->fds); - if (fdl->work_fds) - pa_xfree(fdl->work_fds); - - pa_xfree(fdl); -} - -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { - pa_assert(fdl); - pa_assert(mixer_handle); - pa_assert(m); - pa_assert(!fdl->m); - - fdl->mixer = mixer_handle; - fdl->m = m; - fdl->defer = m->defer_new(m, defer_cb, fdl); - - return 0; -} - -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, - }; - - static const pa_sample_format_t try_order[] = { - PA_SAMPLE_FLOAT32NE, - PA_SAMPLE_FLOAT32RE, - PA_SAMPLE_S32NE, - PA_SAMPLE_S32RE, - PA_SAMPLE_S16NE, - PA_SAMPLE_S16RE, - PA_SAMPLE_ALAW, - PA_SAMPLE_ULAW, - PA_SAMPLE_U8, - PA_SAMPLE_INVALID - }; - - int i, 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; - - if (*f == PA_SAMPLE_FLOAT32BE) - *f = PA_SAMPLE_FLOAT32LE; - else if (*f == PA_SAMPLE_FLOAT32LE) - *f = PA_SAMPLE_FLOAT32BE; - 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; - -try_auto: - - for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { - *f = try_order[i]; - - if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) - return 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; - unsigned int _periods = *periods; - snd_pcm_uframes_t buffer_size; - 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); - pa_assert(periods); - pa_assert(period_size); - - snd_pcm_hw_params_alloca(&hwparams); - - if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) - goto finish; - - if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) - goto finish; - - if (_use_mmap) { - if ((ret = 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) - goto finish; - - _use_mmap = FALSE; - } - - } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) - 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) - goto finish; - - /* 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 (require_exact_channel_number) { - if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) - goto finish; - } else { - if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) - goto finish; - } - - if (_use_tsched) { - _period_size = tsched_size; - _periods = 1; - - pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); - pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); - } - - buffer_size = _periods * _period_size; - - if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) - goto finish; - - if (_periods > 0) { - - /* First we pass 0 as direction to get exactly what we asked - * for. That this is necessary is presumably a bug in ALSA */ - - dir = 0; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { - dir = 1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { - dir = -1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) - goto finish; - } - } - } - - if (_period_size > 0) - if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) - goto finish; - - if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) - goto finish; - - if (ss->rate != r) - pa_log_warn("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_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); - - if (ss->format != f) - pa_log_warn("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) - 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) - 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); - - *periods = _periods; - *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; - 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", snd_strerror(err)); - return err; - } - - if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { - pa_log_warn("Unable to set stop threshold: %s\n", snd_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", snd_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", snd_strerror(err)); - return err; - } - - if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { - pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err)); - return err; - } - - return 0; -} - -struct device_info { - pa_channel_map map; - const char *name; -}; - -static const struct device_info device_table[] = { - {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, "front" }, - - {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, "surround40" }, - - {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_LFE }}, "surround41" }, - - {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER }}, "surround50" }, - - {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, "surround51" }, - - {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, - PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, - PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, - PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }} , "surround71" }, - - {{ 0, { 0 }}, NULL } -}; - -static pa_bool_t channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { - pa_bool_t in_a[PA_CHANNEL_POSITION_MAX]; - unsigned i; - - pa_assert(a); - pa_assert(b); - - memset(in_a, 0, sizeof(in_a)); - - for (i = 0; i < a->channels; i++) - in_a[a->map[i]] = TRUE; - - for (i = 0; i < b->channels; i++) - if (!in_a[b->map[i]]) - return FALSE; - - return TRUE; -} - -snd_pcm_t *pa_alsa_open_by_device_id( - 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) { - - int i; - int direction = 1; - int err; - char *d; - snd_pcm_t *pcm_handle; - - pa_assert(dev_id); - pa_assert(dev); - pa_assert(ss); - pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); - - /* First we try to find a device string with a superset of the - * requested channel map and open it without the plug: prefix. 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.*/ - - for (i = 0;; i += direction) { - pa_sample_spec try_ss; - pa_bool_t reformat; - - if (i < 0) { - pa_assert(direction == -1); - - /* OK, so we iterated backwards, and now are at the - * beginning of our list. */ - - break; - - } else if (!device_table[i].name) { - pa_assert(direction == 1); - - /* OK, so we are at the end of our list. at iterated - * forwards. */ - - i--; - direction = -1; - } - - if ((direction > 0) == !channel_map_superset(&device_table[i].map, map)) - continue; - - d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); - - reformat = FALSE; - for (;;) { - pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); - - /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= - * 1.0.17a would then ignore the SND_PCM_NO_xxx - * flags. Instead we enable nonblock mode afterwards via - * snd_pcm_nonblock(). Also see - * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ - - 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("Couldn't open PCM device %s: %s", d, snd_strerror(err)); - break; - } - - try_ss.channels = device_table[i].map.channels; - try_ss.rate = ss->rate; - try_ss.format = ss->format; - - if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { - - if (!reformat) { - reformat = TRUE; - snd_pcm_close(pcm_handle); - continue; - } - - 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("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); - snd_pcm_close(pcm_handle); - break; - } - - *ss = try_ss; - *map = device_table[i].map; - pa_assert(map->channels == ss->channels); - *dev = d; - return pcm_handle; - } - - pa_xfree(d); - } - - /* OK, we didn't find any good device, so let's try the raw plughw: 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); - pa_xfree(d); - - 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) { - - int err; - char *d; - snd_pcm_t *pcm_handle; - pa_bool_t reformat = FALSE; - - pa_assert(device); - pa_assert(dev); - pa_assert(ss); - pa_assert(map); - pa_assert(nfrags); - pa_assert(period_size); - - d = pa_xstrdup(device); - - for (;;) { - pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); - - /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= - * 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead - * we enable nonblock mode afterwards via - * snd_pcm_nonblock(). Also see - * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ - - 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("Error opening PCM device %s: %s", d, snd_strerror(err)); - pa_xfree(d); - return NULL; - } - - if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE)) < 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("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); - pa_xfree(d); - snd_pcm_close(pcm_handle); - return NULL; - } - - *dev = d; - - if (ss->channels != map->channels) - pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); - - return pcm_handle; - } -} - -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { - int err; - - pa_assert(mixer); - pa_assert(dev); - - if ((err = snd_mixer_attach(mixer, dev)) < 0) { - pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); - return -1; - } - - if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { - pa_log_warn("Unable to register mixer: %s", snd_strerror(err)); - return -1; - } - - if ((err = snd_mixer_load(mixer)) < 0) { - pa_log_warn("Unable to load mixer: %s", snd_strerror(err)); - return -1; - } - - pa_log_info("Successfully attached to mixer '%s'", dev); - - return 0; -} - -static pa_bool_t elem_has_volume(snd_mixer_elem_t *elem, pa_bool_t playback) { - pa_assert(elem); - - if (playback && snd_mixer_selem_has_playback_volume(elem)) - return TRUE; - - if (!playback && snd_mixer_selem_has_capture_volume(elem)) - return TRUE; - - return FALSE; -} - -static pa_bool_t elem_has_switch(snd_mixer_elem_t *elem, pa_bool_t playback) { - pa_assert(elem); - - if (playback && snd_mixer_selem_has_playback_switch(elem)) - return TRUE; - - if (!playback && snd_mixer_selem_has_capture_switch(elem)) - return TRUE; - - return FALSE; -} - -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback) { - snd_mixer_elem_t *elem = NULL, *fallback_elem = NULL; - snd_mixer_selem_id_t *sid = NULL; - - snd_mixer_selem_id_alloca(&sid); - - pa_assert(mixer); - pa_assert(name); - - snd_mixer_selem_id_set_name(sid, name); - - if ((elem = snd_mixer_find_selem(mixer, sid))) { - - if (elem_has_volume(elem, playback) && - elem_has_switch(elem, playback)) - goto success; - - if (!elem_has_volume(elem, playback) && - !elem_has_switch(elem, playback)) - elem = NULL; - } - - pa_log_info("Cannot find mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); - - if (fallback) { - snd_mixer_selem_id_set_name(sid, fallback); - - if ((fallback_elem = snd_mixer_find_selem(mixer, sid))) { - - if (elem_has_volume(fallback_elem, playback) && - elem_has_switch(fallback_elem, playback)) { - elem = fallback_elem; - goto success; - } - - if (!elem_has_volume(fallback_elem, playback) && - !elem_has_switch(fallback_elem, playback)) - fallback_elem = NULL; - } - - pa_log_warn("Cannot find fallback mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); - } - - if (elem && fallback_elem) { - - /* Hmm, so we have both elements, but neither has both mute - * and volume. Let's prefer the one with the volume */ - - if (elem_has_volume(elem, playback)) - goto success; - - if (elem_has_volume(fallback_elem, playback)) { - elem = fallback_elem; - goto success; - } - } - - if (!elem && fallback_elem) - elem = fallback_elem; - -success: - - if (elem) - pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); - - return elem; -} - -static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { - [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ - - [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, - [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, - [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, - - [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, - [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, - [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, - - [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, - - [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, - [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, - - [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, - - [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, - [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN -}; - - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { - unsigned i; - pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; - pa_bool_t mono_used = FALSE; - - pa_assert(elem); - pa_assert(channel_map); - pa_assert(mixer_map); - - memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); - - if (channel_map->channels > 1 && - ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || - (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { - pa_log_info("ALSA device lacks independant volume controls for each channel."); - return -1; - } - - for (i = 0; i < channel_map->channels; i++) { - snd_mixer_selem_channel_id_t id; - pa_bool_t is_mono; - - is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; - id = alsa_channel_ids[channel_map->map[i]]; - - if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { - pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer.", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } - - if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { - pa_log_info("Channel map has duplicate channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } - - if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || - (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { - - pa_log_info("ALSA device lacks separate volumes control for channel '%s'", pa_channel_position_to_string(channel_map->map[i])); - return -1; - } - - if (is_mono) { - mixer_map[i] = SND_MIXER_SCHN_MONO; - mono_used = TRUE; - } else { - mixer_map[i] = id; - alsa_channel_used[id] = TRUE; - } - } - - pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); - - return 0; -} - -void pa_alsa_dump(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_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); - else { - char *s = NULL; - snd_output_buffer_string(out, &s); - pa_log_debug("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; - - pa_assert(pcm); - - snd_pcm_status_alloca(&status); - - pa_assert_se(snd_output_buffer_open(&out) == 0); - - pa_assert_se(snd_pcm_status(pcm, status) == 0); - - if ((err = snd_pcm_status_dump(status, out)) < 0) - pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); - else { - char *s = NULL; - snd_output_buffer_string(out, &s); - pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); - } - - pa_assert_se(snd_output_close(out) == 0); -} - -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_redirect_errors_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_redirect_errors_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); -} - -void pa_alsa_init_proplist(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; - char *cn = NULL, *lcn = NULL; - int card; - - pa_assert(p); - pa_assert(pcm_info); - - pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); - - class = snd_pcm_info_get_class(pcm_info); - if (class <= 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]); - } - subclass = snd_pcm_info_get_subclass(pcm_info); - if (subclass <= 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_proplist_setf(p, "alsa.card", "%i", card); - - if (snd_card_get_name(card, &cn) >= 0) - pa_proplist_sets(p, "alsa.card_name", cn); - - if (snd_card_get_longname(card, &lcn) >= 0) - pa_proplist_sets(p, "alsa.long_card_name", lcn); - } - - if (cn && n) - pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s - %s", cn, n); - else if (cn) - pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, cn); - else if (n) - pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, n); - - free(lcn); - free(cn); -} - -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", snd_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", snd_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", snd_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", snd_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", snd_strerror(err)); - pa_rtpoll_item_free(item); - return NULL; - } - - return item; -} - -snd_pcm_sframes_t pa_alsa_safe_avail_update(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_update(pcm); - - if (n <= 0) - return n; - - k = (size_t) n * pa_frame_size(ss); - - if (k >= hwbuf_size * 3 || - k >= pa_bytes_per_second(ss)*10) - pa_log("snd_pcm_avail_update() returned a value that is exceptionally large: %lu bytes (%lu ms) " - "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", - (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC)); - - return n; -} - -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_log("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms) " - "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", - (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC)); - - return r; -} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h deleted file mode 100644 index 95bb983a..00000000 --- a/src/modules/alsa-util.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef fooalsautilhfoo -#define fooalsautilhfoo - -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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. -***/ - -#include - -#include -#include -#include -#include -#include - -#include - -typedef struct pa_alsa_fdlist pa_alsa_fdlist; - -struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); -void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); -int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); - -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 pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); - -int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); -snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback); - -snd_pcm_t *pa_alsa_open_by_device_id( - 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); - -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); - -int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); - -void pa_alsa_dump(snd_pcm_t *pcm); -void pa_alsa_dump_status(snd_pcm_t *pcm); - -void pa_alsa_redirect_errors_inc(void); -void pa_alsa_redirect_errors_dec(void); - -void pa_alsa_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info); - -int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); - -pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); - -snd_pcm_sframes_t pa_alsa_safe_avail_update(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss); -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); - -#endif diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c new file mode 100644 index 00000000..ff3af19d --- /dev/null +++ b/src/modules/alsa/alsa-util.c @@ -0,0 +1,1261 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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 +#endif + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "alsa-util.h" + +struct pa_alsa_fdlist { + unsigned num_fds; + struct pollfd *fds; + /* This is a temporary buffer used to avoid lots of mallocs */ + struct pollfd *work_fds; + + snd_mixer_t *mixer; + + pa_mainloop_api *m; + pa_defer_event *defer; + pa_io_event **ios; + + pa_bool_t polled; + + void (*cb)(void *userdata); + void *userdata; +}; + +static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + + struct pa_alsa_fdlist *fdl = userdata; + int err; + unsigned i; + unsigned short revents; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + pa_assert(fdl->fds); + pa_assert(fdl->work_fds); + + if (fdl->polled) + return; + + fdl->polled = TRUE; + + memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); + + for (i = 0; i < fdl->num_fds; i++) { + if (e == fdl->ios[i]) { + if (events & PA_IO_EVENT_INPUT) + fdl->work_fds[i].revents |= POLLIN; + if (events & PA_IO_EVENT_OUTPUT) + fdl->work_fds[i].revents |= POLLOUT; + if (events & PA_IO_EVENT_ERROR) + fdl->work_fds[i].revents |= POLLERR; + if (events & PA_IO_EVENT_HANGUP) + fdl->work_fds[i].revents |= POLLHUP; + break; + } + } + + pa_assert(i != fdl->num_fds); + + if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { + pa_log_error("Unable to get poll revent: %s", snd_strerror(err)); + return; + } + + a->defer_enable(fdl->defer, 1); + + if (revents) + snd_mixer_handle_events(fdl->mixer); +} + +static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { + struct pa_alsa_fdlist *fdl = userdata; + unsigned num_fds, i; + int err; + struct pollfd *temp; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + + a->defer_enable(fdl->defer, 0); + + num_fds = (unsigned) snd_mixer_poll_descriptors_count(fdl->mixer); + + if (num_fds != fdl->num_fds) { + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + fdl->fds = pa_xnew0(struct pollfd, num_fds); + fdl->work_fds = pa_xnew(struct pollfd, num_fds); + } + + memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); + + if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { + pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err)); + return; + } + + fdl->polled = FALSE; + + if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) + return; + + if (fdl->ios) { + for (i = 0; i < fdl->num_fds; i++) + a->io_free(fdl->ios[i]); + + if (num_fds != fdl->num_fds) { + pa_xfree(fdl->ios); + fdl->ios = NULL; + } + } + + if (!fdl->ios) + fdl->ios = pa_xnew(pa_io_event*, num_fds); + + /* Swap pointers */ + temp = fdl->work_fds; + fdl->work_fds = fdl->fds; + fdl->fds = temp; + + fdl->num_fds = num_fds; + + for (i = 0;i < num_fds;i++) + fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, + ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | + ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), + io_cb, fdl); +} + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { + struct pa_alsa_fdlist *fdl; + + fdl = pa_xnew0(struct pa_alsa_fdlist, 1); + + fdl->num_fds = 0; + fdl->fds = NULL; + fdl->work_fds = NULL; + fdl->mixer = NULL; + fdl->m = NULL; + fdl->defer = NULL; + fdl->ios = NULL; + fdl->polled = FALSE; + + return fdl; +} + +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { + pa_assert(fdl); + + if (fdl->defer) { + pa_assert(fdl->m); + fdl->m->defer_free(fdl->defer); + } + + if (fdl->ios) { + unsigned i; + pa_assert(fdl->m); + for (i = 0; i < fdl->num_fds; i++) + fdl->m->io_free(fdl->ios[i]); + pa_xfree(fdl->ios); + } + + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + + pa_xfree(fdl); +} + +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { + pa_assert(fdl); + pa_assert(mixer_handle); + pa_assert(m); + pa_assert(!fdl->m); + + fdl->mixer = mixer_handle; + fdl->m = m; + fdl->defer = m->defer_new(m, defer_cb, fdl); + + return 0; +} + +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, + }; + + static const pa_sample_format_t try_order[] = { + PA_SAMPLE_FLOAT32NE, + PA_SAMPLE_FLOAT32RE, + PA_SAMPLE_S32NE, + PA_SAMPLE_S32RE, + PA_SAMPLE_S16NE, + PA_SAMPLE_S16RE, + PA_SAMPLE_ALAW, + PA_SAMPLE_ULAW, + PA_SAMPLE_U8, + PA_SAMPLE_INVALID + }; + + int i, 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; + + if (*f == PA_SAMPLE_FLOAT32BE) + *f = PA_SAMPLE_FLOAT32LE; + else if (*f == PA_SAMPLE_FLOAT32LE) + *f = PA_SAMPLE_FLOAT32BE; + 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; + +try_auto: + + for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { + *f = try_order[i]; + + if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) + return 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; + unsigned int _periods = *periods; + snd_pcm_uframes_t buffer_size; + 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); + pa_assert(periods); + pa_assert(period_size); + + snd_pcm_hw_params_alloca(&hwparams); + + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) + goto finish; + + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + goto finish; + + if (_use_mmap) { + if ((ret = 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) + goto finish; + + _use_mmap = FALSE; + } + + } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + 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) + goto finish; + + /* 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 (require_exact_channel_number) { + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) + goto finish; + } else { + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + goto finish; + } + + if (_use_tsched) { + _period_size = tsched_size; + _periods = 1; + + pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); + pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); + } + + buffer_size = _periods * _period_size; + + if ((ret = snd_pcm_hw_params_set_periods_integer(pcm_handle, hwparams)) < 0) + goto finish; + + if (_periods > 0) { + + /* First we pass 0 as direction to get exactly what we asked + * for. That this is necessary is presumably a bug in ALSA */ + + dir = 0; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + dir = 1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + dir = -1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) + goto finish; + } + } + } + + if (_period_size > 0) + if ((ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0) + goto finish; + + if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) + goto finish; + + if (ss->rate != r) + pa_log_warn("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_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); + + if (ss->format != f) + pa_log_warn("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) + 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) + 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); + + *periods = _periods; + *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; + 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", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { + pa_log_warn("Unable to set stop threshold: %s\n", snd_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", snd_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", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { + pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err)); + return err; + } + + return 0; +} + +struct device_info { + pa_channel_map map; + const char *name; +}; + +static const struct device_info device_table[] = { + {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, "front" }, + + {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, "surround40" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE }}, "surround41" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER }}, "surround50" }, + + {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, "surround51" }, + + {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }} , "surround71" }, + + {{ 0, { 0 }}, NULL } +}; + +static pa_bool_t channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { + pa_bool_t in_a[PA_CHANNEL_POSITION_MAX]; + unsigned i; + + pa_assert(a); + pa_assert(b); + + memset(in_a, 0, sizeof(in_a)); + + for (i = 0; i < a->channels; i++) + in_a[a->map[i]] = TRUE; + + for (i = 0; i < b->channels; i++) + if (!in_a[b->map[i]]) + return FALSE; + + return TRUE; +} + +snd_pcm_t *pa_alsa_open_by_device_id( + 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) { + + int i; + int direction = 1; + int err; + char *d; + snd_pcm_t *pcm_handle; + + pa_assert(dev_id); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + /* First we try to find a device string with a superset of the + * requested channel map and open it without the plug: prefix. 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.*/ + + for (i = 0;; i += direction) { + pa_sample_spec try_ss; + pa_bool_t reformat; + + if (i < 0) { + pa_assert(direction == -1); + + /* OK, so we iterated backwards, and now are at the + * beginning of our list. */ + + break; + + } else if (!device_table[i].name) { + pa_assert(direction == 1); + + /* OK, so we are at the end of our list. at iterated + * forwards. */ + + i--; + direction = -1; + } + + if ((direction > 0) == !channel_map_superset(&device_table[i].map, map)) + continue; + + d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); + + reformat = FALSE; + for (;;) { + pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); + + /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= + * 1.0.17a would then ignore the SND_PCM_NO_xxx + * flags. Instead we enable nonblock mode afterwards via + * snd_pcm_nonblock(). Also see + * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ + + 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("Couldn't open PCM device %s: %s", d, snd_strerror(err)); + break; + } + + try_ss.channels = device_table[i].map.channels; + try_ss.rate = ss->rate; + try_ss.format = ss->format; + + if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { + + if (!reformat) { + reformat = TRUE; + snd_pcm_close(pcm_handle); + continue; + } + + 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("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); + snd_pcm_close(pcm_handle); + break; + } + + *ss = try_ss; + *map = device_table[i].map; + pa_assert(map->channels == ss->channels); + *dev = d; + return pcm_handle; + } + + pa_xfree(d); + } + + /* OK, we didn't find any good device, so let's try the raw plughw: 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); + pa_xfree(d); + + 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) { + + int err; + char *d; + snd_pcm_t *pcm_handle; + pa_bool_t reformat = FALSE; + + pa_assert(device); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + d = pa_xstrdup(device); + + for (;;) { + pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with"); + + /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= + * 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead + * we enable nonblock mode afterwards via + * snd_pcm_nonblock(). Also see + * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ + + 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("Error opening PCM device %s: %s", d, snd_strerror(err)); + pa_xfree(d); + return NULL; + } + + if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE)) < 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("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + return NULL; + } + + *dev = d; + + if (ss->channels != map->channels) + pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); + + return pcm_handle; + } +} + +int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { + int err; + + pa_assert(mixer); + pa_assert(dev); + + if ((err = snd_mixer_attach(mixer, dev)) < 0) { + pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { + pa_log_warn("Unable to register mixer: %s", snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_load(mixer)) < 0) { + pa_log_warn("Unable to load mixer: %s", snd_strerror(err)); + return -1; + } + + pa_log_info("Successfully attached to mixer '%s'", dev); + + return 0; +} + +static pa_bool_t elem_has_volume(snd_mixer_elem_t *elem, pa_bool_t playback) { + pa_assert(elem); + + if (playback && snd_mixer_selem_has_playback_volume(elem)) + return TRUE; + + if (!playback && snd_mixer_selem_has_capture_volume(elem)) + return TRUE; + + return FALSE; +} + +static pa_bool_t elem_has_switch(snd_mixer_elem_t *elem, pa_bool_t playback) { + pa_assert(elem); + + if (playback && snd_mixer_selem_has_playback_switch(elem)) + return TRUE; + + if (!playback && snd_mixer_selem_has_capture_switch(elem)) + return TRUE; + + return FALSE; +} + +snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback) { + snd_mixer_elem_t *elem = NULL, *fallback_elem = NULL; + snd_mixer_selem_id_t *sid = NULL; + + snd_mixer_selem_id_alloca(&sid); + + pa_assert(mixer); + pa_assert(name); + + snd_mixer_selem_id_set_name(sid, name); + + if ((elem = snd_mixer_find_selem(mixer, sid))) { + + if (elem_has_volume(elem, playback) && + elem_has_switch(elem, playback)) + goto success; + + if (!elem_has_volume(elem, playback) && + !elem_has_switch(elem, playback)) + elem = NULL; + } + + pa_log_info("Cannot find mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); + + if (fallback) { + snd_mixer_selem_id_set_name(sid, fallback); + + if ((fallback_elem = snd_mixer_find_selem(mixer, sid))) { + + if (elem_has_volume(fallback_elem, playback) && + elem_has_switch(fallback_elem, playback)) { + elem = fallback_elem; + goto success; + } + + if (!elem_has_volume(fallback_elem, playback) && + !elem_has_switch(fallback_elem, playback)) + fallback_elem = NULL; + } + + pa_log_warn("Cannot find fallback mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid)); + } + + if (elem && fallback_elem) { + + /* Hmm, so we have both elements, but neither has both mute + * and volume. Let's prefer the one with the volume */ + + if (elem_has_volume(elem, playback)) + goto success; + + if (elem_has_volume(fallback_elem, playback)) { + elem = fallback_elem; + goto success; + } + } + + if (!elem && fallback_elem) + elem = fallback_elem; + +success: + + if (elem) + pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); + + return elem; +} + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + + [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, + [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { + unsigned i; + pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; + pa_bool_t mono_used = FALSE; + + pa_assert(elem); + pa_assert(channel_map); + pa_assert(mixer_map); + + memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); + + if (channel_map->channels > 1 && + ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || + (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { + pa_log_info("ALSA device lacks independant volume controls for each channel."); + return -1; + } + + for (i = 0; i < channel_map->channels; i++) { + snd_mixer_selem_channel_id_t id; + pa_bool_t is_mono; + + is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; + id = alsa_channel_ids[channel_map->map[i]]; + + if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { + pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { + pa_log_info("Channel map has duplicate channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || + (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { + + pa_log_info("ALSA device lacks separate volumes control for channel '%s'", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if (is_mono) { + mixer_map[i] = SND_MIXER_SCHN_MONO; + mono_used = TRUE; + } else { + mixer_map[i] = id; + alsa_channel_used[id] = TRUE; + } + } + + pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); + + return 0; +} + +void pa_alsa_dump(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_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); + else { + char *s = NULL; + snd_output_buffer_string(out, &s); + pa_log_debug("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; + + pa_assert(pcm); + + snd_pcm_status_alloca(&status); + + pa_assert_se(snd_output_buffer_open(&out) == 0); + + pa_assert_se(snd_pcm_status(pcm, status) == 0); + + if ((err = snd_pcm_status_dump(status, out)) < 0) + pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); + else { + char *s = NULL; + snd_output_buffer_string(out, &s); + pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + } + + pa_assert_se(snd_output_close(out) == 0); +} + +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_redirect_errors_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_redirect_errors_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); +} + +void pa_alsa_init_proplist(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; + char *cn = NULL, *lcn = NULL; + int card; + + pa_assert(p); + pa_assert(pcm_info); + + pa_proplist_sets(p, PA_PROP_DEVICE_API, "alsa"); + + class = snd_pcm_info_get_class(pcm_info); + if (class <= 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]); + } + subclass = snd_pcm_info_get_subclass(pcm_info); + if (subclass <= 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_proplist_setf(p, "alsa.card", "%i", card); + + if (snd_card_get_name(card, &cn) >= 0) + pa_proplist_sets(p, "alsa.card_name", cn); + + if (snd_card_get_longname(card, &lcn) >= 0) + pa_proplist_sets(p, "alsa.long_card_name", lcn); + } + + if (cn && n) + pa_proplist_setf(p, PA_PROP_DEVICE_DESCRIPTION, "%s - %s", cn, n); + else if (cn) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, cn); + else if (n) + pa_proplist_sets(p, PA_PROP_DEVICE_DESCRIPTION, n); + + free(lcn); + free(cn); +} + +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", snd_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", snd_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", snd_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", snd_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", snd_strerror(err)); + pa_rtpoll_item_free(item); + return NULL; + } + + return item; +} + +snd_pcm_sframes_t pa_alsa_safe_avail_update(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_update(pcm); + + if (n <= 0) + return n; + + k = (size_t) n * pa_frame_size(ss); + + if (k >= hwbuf_size * 3 || + k >= pa_bytes_per_second(ss)*10) + pa_log("snd_pcm_avail_update() returned a value that is exceptionally large: %lu bytes (%lu ms) " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", + (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC)); + + return n; +} + +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_log("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms) " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", + (unsigned long) k, (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC)); + + return r; +} diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h new file mode 100644 index 00000000..95bb983a --- /dev/null +++ b/src/modules/alsa/alsa-util.h @@ -0,0 +1,98 @@ +#ifndef fooalsautilhfoo +#define fooalsautilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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. +***/ + +#include + +#include +#include +#include +#include +#include + +#include + +typedef struct pa_alsa_fdlist pa_alsa_fdlist; + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); + +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 pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); + +int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); +snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback); + +snd_pcm_t *pa_alsa_open_by_device_id( + 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); + +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); + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); + +void pa_alsa_dump(snd_pcm_t *pcm); +void pa_alsa_dump_status(snd_pcm_t *pcm); + +void pa_alsa_redirect_errors_inc(void); +void pa_alsa_redirect_errors_dec(void); + +void pa_alsa_init_proplist(pa_proplist *p, snd_pcm_info_t *pcm_info); + +int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); + +pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); + +snd_pcm_sframes_t pa_alsa_safe_avail_update(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss); +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); + +#endif diff --git a/src/modules/alsa/module-alsa-sink.c b/src/modules/alsa/module-alsa-sink.c new file mode 100644 index 00000000..a3e818dd --- /dev/null +++ b/src/modules/alsa/module-alsa-sink.c @@ -0,0 +1,1674 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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 +#endif + +#include + +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alsa-util.h" +#include "module-alsa-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name= " + "device= " + "device_id= " + "format= " + "rate= " + "channels= " + "channel_map= " + "fragments= " + "fragment_size= " + "mmap= " + "tsched= " + "tsched_buffer_size= " + "tsched_buffer_watermark="); + +static const char* const valid_modargs[] = { + "sink_name", + "device", + "device_id", + "format", + "rate", + "channels", + "channel_map", + "fragments", + "fragment_size", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + NULL +}; + +#define DEFAULT_DEVICE "default" +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ +#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ +#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; + snd_mixer_t *mixer_handle; + snd_mixer_elem_t *mixer_elem; + long hw_volume_max, hw_volume_min; + long hw_dB_max, hw_dB_min; + pa_bool_t hw_dB_supported; + pa_bool_t mixer_seperate_channels; + pa_cvolume hardware_volume; + + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; + unsigned nfragments; + pa_memchunk memchunk; + + char *device_name; + + pa_bool_t use_mmap, use_tsched; + + pa_bool_t first, after_rewind; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; + uint64_t since_start; + + snd_pcm_sframes_t hwbuf_unused_frames; +}; + +static void fix_tsched_watermark(struct userdata *u) { + size_t max_use; + size_t min_sleep, min_wakeup; + pa_assert(u); + + max_use = u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size; + + min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec); + min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec); + + if (min_sleep > max_use/2) + min_sleep = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_sleep < u->frame_size) + min_sleep = u->frame_size; + + if (min_wakeup > max_use/2) + min_wakeup = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_wakeup < u->frame_size) + min_wakeup = u->frame_size; + + if (u->tsched_watermark > max_use-min_sleep) + u->tsched_watermark = max_use-min_sleep; + + if (u->tsched_watermark < min_wakeup) + u->tsched_watermark = min_wakeup; +} + +static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { + pa_usec_t usec, wm; + + pa_assert(sleep_usec); + pa_assert(process_usec); + + pa_assert(u); + + usec = pa_sink_get_requested_latency_within_thread(u->sink); + + if (usec == (pa_usec_t) -1) + usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec); + +/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ + + wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec); + + if (usec >= wm) { + *sleep_usec = usec - wm; + *process_usec = wm; + } else + *process_usec = *sleep_usec = usec / 2; + +/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ +} + +static int try_recover(struct userdata *u, const char *call, int err) { + pa_assert(u); + pa_assert(call); + pa_assert(err < 0); + + pa_log_debug("%s: %s", call, snd_strerror(err)); + + pa_assert(err != -EAGAIN); + + if (err == -EPIPE) + pa_log_debug("%s: Buffer underrun!", call); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { + u->first = TRUE; + u->since_start = 0; + return 0; + } + + pa_log("%s: %s", call, snd_strerror(err)); + return -1; +} + +static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { + size_t left_to_play; + + if ((size_t) n*u->frame_size < u->hwbuf_size) + left_to_play = u->hwbuf_size - ((size_t) n*u->frame_size); + else + left_to_play = 0; + + if (left_to_play > 0) { +/* pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); */ + } else if (!u->first && !u->after_rewind) { + pa_log_info("Underrun!"); + + if (u->use_tsched) { + size_t old_watermark = u->tsched_watermark; + + u->tsched_watermark *= 2; + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_notice("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + } + } + + return left_to_play; +} + +static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { + int work_done = 0; + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_play; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + /* First we determine how many samples are missing to fill the + * buffer up to 100% */ + + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; + + return r; + } + + left_to_play = check_left_to_play(u, n); + + if (u->use_tsched) + + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ + + if (!polled && + pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { + + if (polled) + pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " + "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " + "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail_update() returned 0."); + + break; + } + + n -= u->hwbuf_unused_frames; + + polled = FALSE; + +/* pa_log_debug("Filling up"); */ + + for (;;) { + pa_memchunk chunk; + void *p; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; + snd_pcm_sframes_t sframes; + +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ + + if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; + + return r; + } + + /* Make sure that if these memblocks need to be copied they will fit into one slot */ + if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size) + frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size; + + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); + + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); + + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_sink_render_into_full(u->sink, &chunk); + + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); + + if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) + continue; + + return r; + } + + work_done = 1; + + u->frame_index += (int64_t) frames; + u->since_start += frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= (snd_pcm_sframes_t) frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; +} + +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { + int work_done = 0; + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_play; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; + + return r; + } + + left_to_play = check_left_to_play(u, n); + + if (u->use_tsched) + + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ + + if (!polled && + pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { + + if (polled) + pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " + "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " + "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail_update() returned 0."); + + break; + } + + n -= u->hwbuf_unused_frames; + + polled = FALSE; + + for (;;) { + snd_pcm_sframes_t frames; + void *p; + +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, (size_t) n * u->frame_size, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + frames = (snd_pcm_sframes_t) (u->memchunk.length / u->frame_size); + + if (frames > n) + frames = n; + + p = pa_memblock_acquire(u->memchunk.memblock); + frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(frames != 0); + + if (PA_UNLIKELY(frames < 0)) { + + if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) + continue; + + return r; + } + + u->memchunk.index += (size_t) frames * u->frame_size; + u->memchunk.length -= (size_t) frames * u->frame_size; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + work_done = 1; + + u->frame_index += frames; + u->since_start += (size_t) frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= n) + break; + + n -= frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; +} + +static void update_smoother(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + int64_t frames; + int err; + pa_usec_t now1, now2; +/* struct timeval timestamp; */ + snd_pcm_status_t *status; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's update the time smoother */ + + snd_pcm_hwsync(u->pcm_handle); + snd_pcm_avail_update(u->pcm_handle); + +/* if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { */ +/* pa_log("Failed to query DSP status data: %s", snd_strerror(err)); */ +/* return; */ +/* } */ + +/* delay = snd_pcm_status_get_delay(status); */ + + if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return; + } + + frames = u->frame_index - delay; + +/* pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); */ + +/* snd_pcm_status_get_tstamp(status, ×tamp); */ +/* pa_rtclock_from_wallclock(×tamp); */ +/* now1 = pa_timeval_load(×tamp); */ + + now1 = pa_rtclock_usec(); + now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->sink->sample_spec); + pa_smoother_put(u->smoother, now1, now2); +} + +static pa_usec_t sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + int64_t delay; + pa_usec_t now1, now2; + + pa_assert(u); + + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); + + delay = (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2; + + if (delay > 0) + r = (pa_usec_t) delay; + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + + return r; +} + +static int build_pollfd(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) + return -1; + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + + /* Let's suspend -- we don't call snd_pcm_drain() here since that might + * take awfully long with our long buffer sizes today. */ + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int update_sw_params(struct userdata *u) { + snd_pcm_uframes_t avail_min; + int err; + + pa_assert(u); + + /* Use the full buffer if noone asked us for anything specific */ + u->hwbuf_unused_frames = 0; + + if (u->use_tsched) { + pa_usec_t latency; + + if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) { + size_t b; + + pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); + + b = pa_usec_to_bytes(latency, &u->sink->sample_spec); + + /* We need at least one sample in our buffer */ + + if (PA_UNLIKELY(b < u->frame_size)) + b = u->frame_size; + + u->hwbuf_unused_frames = (snd_pcm_sframes_t) + (PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0); + } + + fix_tsched_watermark(u); + } + + pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); + + /* We need at last one frame in the used part of the buffer */ + avail_min = (snd_pcm_uframes_t) u->hwbuf_unused_frames + 1; + + if (u->use_tsched) { + pa_usec_t sleep_usec, process_usec; + + hw_sleep_time(u, &sleep_usec, &process_usec); + avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec) / u->frame_size; + } + + pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); + + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + return err; + } + + pa_sink_set_max_request(u->sink, u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size); + + return 0; +} + +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b, d; + unsigned nfrags; + snd_pcm_uframes_t period_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + snd_config_update_free_global(); + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, + /*SND_PCM_NONBLOCK|*/ + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } + + ss = u->sink->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + d = u->use_tsched; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap || d != u->use_tsched) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", + (unsigned long) u->nfragments, (unsigned long) u->fragment_size, + (unsigned long) nfrags, period_size * u->frame_size); + goto fail; + } + + if (update_sw_params(u) < 0) + goto fail; + + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + u->first = TRUE; + u->since_start = 0; + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = sink_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + if (build_pollfd(u) < 0) + return -1; + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_sink_get_volume(u->sink, TRUE); + pa_sink_get_mute(u->sink, TRUE); + } + + return 0; +} + +static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { + + return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / + (double) (u->hw_volume_max - u->hw_volume_min)); +} + +static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { + long alsa_vol; + + alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) + / PA_VOLUME_NORM) + u->hw_volume_min; + + return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); +} + +static int sink_get_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + unsigned i; + pa_cvolume r; + char t[PA_CVOLUME_SNPRINT_MAX]; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if (u->mixer_seperate_channels) { + + r.channels = s->sample_spec.channels; + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + } else { + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); + } + } + + } else { + long alsa_vol; + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + + } else { + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + } + } + + pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + if (!pa_cvolume_equal(&u->hardware_volume, &r)) { + + u->hardware_volume = s->volume = r; + + if (u->hw_dB_supported) { + pa_cvolume reset; + + /* Hmm, so the hardware volume changed, let's reset our software volume */ + + pa_cvolume_reset(&reset, s->sample_spec.channels); + pa_sink_set_soft_volume(s, &reset); + } + } + + return 0; + +fail: + pa_log_error("Unable to read volume: %s", snd_strerror(err)); + + return -1; +} + +static int sink_set_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + unsigned i; + pa_cvolume r; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if (u->mixer_seperate_channels) { + + r.channels = s->sample_spec.channels; + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; + + vol = s->volume.values[i]; + + if (u->hw_dB_supported) { + + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + + } else { + alsa_vol = to_alsa_volume(u, vol); + + if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); + } + } + + } else { + pa_volume_t vol; + long alsa_vol; + + vol = pa_cvolume_max(&s->volume); + + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_playback_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + + } else { + alsa_vol = to_alsa_volume(u, vol); + + if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + } + } + + u->hardware_volume = r; + + if (u->hw_dB_supported) { + char t[PA_CVOLUME_SNPRINT_MAX]; + + /* Match exactly what the user requested by software */ + + pa_sw_cvolume_divide(&r, &s->volume, &r); + pa_sink_set_soft_volume(s, &r); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); + pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); + pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + } else + + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ + + s->volume = r; + + return 0; + +fail: + pa_log_error("Unable to set volume: %s", snd_strerror(err)); + + return -1; +} + +static int sink_get_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err, sw; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { + pa_log_error("Unable to get switch: %s", snd_strerror(err)); + return -1; + } + + s->muted = !sw; + + return 0; +} + +static int sink_set_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { + pa_log_error("Unable to set switch: %s", snd_strerror(err)); + return -1; + } + + return 0; +} + +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u = s->userdata; + snd_pcm_sframes_t before; + pa_assert(u); + + if (!u->pcm_handle) + return; + + before = u->hwbuf_unused_frames; + update_sw_params(u); + + /* Let's check whether we now use only a smaller part of the + buffer then before. If so, we need to make sure that subsequent + rewinds are relative to the new maxium fill level and not to the + current fill level. Thus, let's do a full rewind once, to clear + things up. */ + + if (u->hwbuf_unused_frames > before) { + pa_log_debug("Requesting rewind due to latency change."); + pa_sink_request_rewind(s, (size_t) -1); + } +} + +static int process_rewind(struct userdata *u) { + snd_pcm_sframes_t unused; + size_t rewind_nbytes, unused_nbytes, limit_nbytes; + pa_assert(u); + + /* Figure out how much we shall rewind and reset the counter */ + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + u->sink->thread_info.rewind_nbytes = 0; + + if (rewind_nbytes <= 0) + goto finish; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + snd_pcm_hwsync(u->pcm_handle); + if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) { + pa_log("snd_pcm_avail_update() failed: %s", snd_strerror((int) unused)); + return -1; + } + + unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size; + + if (u->hwbuf_size > unused_nbytes) + limit_nbytes = u->hwbuf_size - unused_nbytes; + else + limit_nbytes = 0; + + if (rewind_nbytes > limit_nbytes) + rewind_nbytes = limit_nbytes; + + if (rewind_nbytes > 0) { + snd_pcm_sframes_t in_frames, out_frames; + + pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes); + + in_frames = (snd_pcm_sframes_t) (rewind_nbytes / u->frame_size); + pa_log_debug("before: %lu", (unsigned long) in_frames); + if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) { + pa_log("snd_pcm_rewind() failed: %s", snd_strerror((int) out_frames)); + return -1; + } + pa_log_debug("after: %lu", (unsigned long) out_frames); + + rewind_nbytes = (size_t) out_frames * u->frame_size; + + if (rewind_nbytes <= 0) + pa_log_info("Tried rewind, but was apparently not possible."); + else { + u->frame_index -= out_frames; + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + pa_sink_process_rewind(u->sink, rewind_nbytes); + + u->after_rewind = TRUE; + return 0; + } + } else + pa_log_debug("Mhmm, actually there is nothing to rewind."); + +finish: + + pa_sink_process_rewind(u->sink, 0); + + return 0; + +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + +/* pa_log_debug("loop"); */ + + /* Render some data and write it to the dsp */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + int work_done; + pa_usec_t sleep_usec = 0; + + if (u->sink->thread_info.rewind_requested) + if (process_rewind(u) < 0) + goto fail; + + if (u->use_mmap) + work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); + else + work_done = unix_write(u, &sleep_usec, revents & POLLOUT); + + if (work_done < 0) + goto fail; + +/* pa_log_debug("work_done = %i", work_done); */ + + if (work_done) { + + if (u->first) { + pa_log_info("Starting playback."); + snd_pcm_start(u->pcm_handle); + + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + } + + update_smoother(u); + } + + if (u->use_tsched) { + pa_usec_t cusec; + + if (u->since_start <= u->hwbuf_size) { + + /* USB devices on ALSA seem to hit a buffer + * underrun during the first iterations much + * quicker then we calculate here, probably due to + * the transport latency. To accomodate for that + * we artificially decrease the sleep time until + * we have filled the buffer at least once + * completely.*/ + + /*pa_log_debug("Cutting sleep time for the initial iterations by half.");*/ + sleep_usec /= 2; + } + + /* OK, the playback buffer is now full, let's + * calculate when to wake up next */ +/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ + + /* Convert from the sound card time domain to the + * system time domain */ + cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + +/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ + + /* We don't trust the conversion, so we wake up whatever comes first */ + pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + } + + u->first = FALSE; + u->after_rewind = FALSE; + + } else if (u->use_tsched) + + /* OK, we're in an invalid state, let's disable our timers */ + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + struct pollfd *pollfd; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLIN|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { + if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; + + u->first = TRUE; + u->since_start = 0; + } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); + } else + revents = 0; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + struct userdata *u = NULL; + const char *dev_id; + pa_sample_spec ss; + pa_channel_map map; + uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, tsched_frames; + size_t frame_size; + snd_pcm_info_t *pcm_info = NULL; + int err; + const char *name; + char *name_buf = NULL; + pa_bool_t namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; + pa_usec_t usec; + pa_sink_new_data data; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + pa_alsa_redirect_errors_inc(); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { + pa_log("Failed to parse sample specification and channel map"); + goto fail; + } + + frame_size = pa_frame_size(&ss); + + nfrags = m->core->default_n_fragments; + frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); + if (frag_size <= 0) + frag_size = (uint32_t) frame_size; + tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || + pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + + hwbuf_size = frag_size * nfrags; + period_frames = frag_size/frame_size; + tsched_frames = tsched_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse tsched argument."); + goto fail; + } + + if (use_tsched && !pa_rtclock_hrtimer()) { + pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->use_tsched = use_tsched; + u->first = TRUE; + u->since_start = 0; + u->after_rewind = FALSE; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->alsa_rtpoll_item = NULL; + + u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE, 5); + usec = pa_rtclock_usec(); + pa_smoother_set_time_offset(u->smoother, usec); + pa_smoother_pause(u->smoother, usec); + + snd_config_update_free_global(); + + b = use_mmap; + d = use_tsched; + + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_frames, tsched_frames, + &b, &d))) + + goto fail; + + } else { + + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_frames, tsched_frames, + &b, &d))) + goto fail; + + } + + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = FALSE; + } + + if (use_tsched && (!b || !d)) { + pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); + u->use_tsched = use_tsched = FALSE; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if (u->use_tsched) + pa_log_info("Successfully enabled timer-based scheduling mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + pa_log("Error fetching PCM info: %s", snd_strerror(err)); + goto fail; + } + + /* ALSA might tweak the sample spec, so recalculate the frame size */ + frame_size = pa_frame_size(&ss); + + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) + pa_log_warn("Error opening mixer: %s", snd_strerror(err)); + else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + snd_pcm_info_t *info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; + + if ((card = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + pa_xfree(md); + } + } + } + + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM", TRUE))) + found = FALSE; + + if (!found) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } + } + + if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) + namereg_fail = TRUE; + else { + name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); + namereg_fail = FALSE; + } + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, name); + data.namereg_fail = namereg_fail; + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + + pa_alsa_init_proplist(data.proplist, pcm_info); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + pa_xfree(name_buf); + + if (!u->sink) { + pa_log("Failed to create sink object"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->update_requested_latency = sink_update_requested_latency_cb; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->frame_size = frame_size; + u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + u->hwbuf_unused_frames = 0; + u->tsched_watermark = tsched_watermark; + u->frame_index = 0; + u->hw_dB_supported = FALSE; + u->hw_dB_min = u->hw_dB_max = 0; + u->hw_volume_min = u->hw_volume_max = 0; + u->mixer_seperate_channels = FALSE; + pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); + + if (use_tsched) + fix_tsched_watermark(u); + + u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0; + u->sink->thread_info.max_request = u->hwbuf_size; + + pa_sink_set_latency_range(u->sink, + !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, + pa_bytes_to_usec(u->hwbuf_size, &ss)); + + pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", + nfrags, (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); + + if (use_tsched) + pa_log_info("Time scheduling watermark is %0.2fms", + (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); + + if (update_sw_params(u) < 0) + goto fail; + + pa_memchunk_reset(&u->memchunk); + + if (u->mixer_handle) { + pa_assert(u->mixer_elem); + + if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { + pa_bool_t suitable = FALSE; + + if (snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) + pa_log_info("Failed to get volume range. Falling back to software volume control."); + else if (u->hw_volume_min >= u->hw_volume_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); + else { + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + suitable = TRUE; + } + + if (suitable) { + if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) + pa_log_info("Mixer doesn't support dB information."); + else { +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); +#endif + + if (u->hw_dB_min >= u->hw_dB_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + else { + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + u->hw_dB_supported = TRUE; + + if (u->hw_dB_max > 0) { + u->sink->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); + } else + pa_log_info("No particular base volume set, fixing to 0 dB"); + } + } + + if (!u->hw_dB_supported && + u->hw_volume_max - u->hw_volume_min < 3) { + + pa_log_info("Device doesn't do dB volume and has less than 4 volume levels. Falling back to software volume control."); + suitable = FALSE; + } + } + + if (suitable) { + u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0; + + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + + } else + pa_log_info("Using software volume control."); + } + + if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { + u->sink->get_mute = sink_get_mute_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->flags |= PA_SINK_HW_MUTE_CTRL; + } else + pa_log_info("Using software mute control."); + + u->mixer_fdl = pa_alsa_fdlist_new(); + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + goto fail; + } + + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); + snd_mixer_elem_set_callback_private(u->mixer_elem, u); + } else + u->mixer_fdl = NULL; + + pa_alsa_dump(u->pcm_handle); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Get initial mixer settings */ + if (data.volume_is_set) { + if (u->sink->set_volume) + u->sink->set_volume(u->sink); + } else { + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + } + + if (data.muted_is_set) { + if (u->sink->set_mute) + u->sink->set_mute(u->sink); + } else { + if (u->sink->get_mute) + u->sink->get_mute(u->sink); + } + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_sink_linked_by(u->sink); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); + return; + } + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + if (u->smoother) + pa_smoother_free(u->smoother); + + pa_xfree(u->device_name); + pa_xfree(u); + + snd_config_update_free_global(); + + pa_alsa_redirect_errors_dec(); +} diff --git a/src/modules/alsa/module-alsa-source.c b/src/modules/alsa/module-alsa-source.c new file mode 100644 index 00000000..901db01f --- /dev/null +++ b/src/modules/alsa/module-alsa-source.c @@ -0,0 +1,1491 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2008 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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 +#endif + +#include + +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alsa-util.h" +#include "module-alsa-source-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source_name= " + "device= " + "device_id= " + "format= " + "rate= " + "channels= " + "channel_map= " + "fragments= " + "fragment_size= " + "mmap= " + "tsched= " + "tsched_buffer_size= " + "tsched_buffer_watermark="); + +static const char* const valid_modargs[] = { + "source_name", + "device", + "device_id", + "format", + "rate", + "channels", + "channel_map", + "fragments", + "fragment_size", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + NULL +}; + +#define DEFAULT_DEVICE "default" +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ +#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ +#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; + snd_mixer_t *mixer_handle; + snd_mixer_elem_t *mixer_elem; + long hw_volume_max, hw_volume_min; + long hw_dB_max, hw_dB_min; + pa_bool_t hw_dB_supported; + pa_bool_t mixer_seperate_channels; + + pa_cvolume hardware_volume; + + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; + unsigned nfragments; + + char *device_name; + + pa_bool_t use_mmap, use_tsched; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; + + snd_pcm_sframes_t hwbuf_unused_frames; +}; + +static void fix_tsched_watermark(struct userdata *u) { + size_t max_use; + size_t min_sleep, min_wakeup; + pa_assert(u); + + max_use = u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size; + + min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec); + min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec); + + if (min_sleep > max_use/2) + min_sleep = pa_frame_align(max_use/2, &u->source->sample_spec); + if (min_sleep < u->frame_size) + min_sleep = u->frame_size; + + if (min_wakeup > max_use/2) + min_wakeup = pa_frame_align(max_use/2, &u->source->sample_spec); + if (min_wakeup < u->frame_size) + min_wakeup = u->frame_size; + + if (u->tsched_watermark > max_use-min_sleep) + u->tsched_watermark = max_use-min_sleep; + + if (u->tsched_watermark < min_wakeup) + u->tsched_watermark = min_wakeup; +} + +static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { + pa_usec_t wm, usec; + + pa_assert(u); + + usec = pa_source_get_requested_latency_within_thread(u->source); + + if (usec == (pa_usec_t) -1) + usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->sample_spec); + +/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ + + wm = pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec); + + if (usec >= wm) { + *sleep_usec = usec - wm; + *process_usec = wm; + } else + *process_usec = *sleep_usec = usec /= 2; + +/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ + + return usec; +} + +static int try_recover(struct userdata *u, const char *call, int err) { + pa_assert(u); + pa_assert(call); + pa_assert(err < 0); + + pa_log_debug("%s: %s", call, snd_strerror(err)); + + pa_assert(err != -EAGAIN); + + if (err == -EPIPE) + pa_log_debug("%s: Buffer overrun!", call); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { + snd_pcm_start(u->pcm_handle); + return 0; + } + + pa_log("%s: %s", call, snd_strerror(err)); + return -1; +} + +static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) { + size_t left_to_record; + size_t rec_space = u->hwbuf_size - (size_t) u->hwbuf_unused_frames*u->frame_size; + + if ((size_t) n*u->frame_size < rec_space) + left_to_record = rec_space - ((size_t) n*u->frame_size); + else + left_to_record = 0; + + if (left_to_record > 0) { +/* pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); */ + } else { + pa_log_info("Overrun!"); + + if (u->use_tsched) { + size_t old_watermark = u->tsched_watermark; + + u->tsched_watermark *= 2; + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_notice("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); + } + } + + return left_to_record; +} + +static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { + int work_done = 0; + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_record; + + pa_assert(u); + pa_source_assert_ref(u->source); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; + + return r; + } + + left_to_record = check_left_to_record(u, n); + + if (u->use_tsched) + if (!polled && + pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= 0)) { + + if (polled) + pa_log("ALSA woke us up to read new data from the device, but there was actually nothing to read! " + "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " + "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."); + + break; + } + + polled = FALSE; + + for (;;) { + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; + pa_memchunk chunk; + void *p; + snd_pcm_sframes_t sframes; + +/* pa_log_debug("%lu frames to read", (unsigned long) frames); */ + + if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; + + return r; + } + + /* Make sure that if these memblocks need to be copied they will fit into one slot */ + if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size) + frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size; + + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); + + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); + + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + pa_memblock_unref_fixed(chunk.memblock); + + if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) + continue; + + return r; + } + + work_done = 1; + + u->frame_index += (int64_t) frames; + +/* pa_log_debug("read %lu frames", (unsigned long) frames); */ + + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= (snd_pcm_sframes_t) frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; + return work_done; +} + +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { + int work_done = 0; + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_record; + + pa_assert(u); + pa_source_assert_ref(u->source); + + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + + for (;;) { + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; + + return r; + } + + left_to_record = check_left_to_record(u, n); + + if (u->use_tsched) + if (!polled && + pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; + + if (PA_UNLIKELY(n <= 0)) { + + if (polled) + pa_log("ALSA woke us up to read new data from the device, but there was actually nothing to read! " + "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " + "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."); + + return work_done; + } + + polled = FALSE; + + for (;;) { + void *p; + snd_pcm_sframes_t frames; + pa_memchunk chunk; + + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + frames = (snd_pcm_sframes_t) (pa_memblock_get_length(chunk.memblock) / u->frame_size); + + if (frames > n) + frames = n; + +/* pa_log_debug("%lu frames to read", (unsigned long) n); */ + + p = pa_memblock_acquire(chunk.memblock); + frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames); + pa_memblock_release(chunk.memblock); + + pa_assert(frames != 0); + + if (PA_UNLIKELY(frames < 0)) { + pa_memblock_unref(chunk.memblock); + + if ((r = try_recover(u, "snd_pcm_readi", (int) (frames))) == 0) + continue; + + return r; + } + + chunk.index = 0; + chunk.length = (size_t) frames * u->frame_size; + + pa_source_post(u->source, &chunk); + pa_memblock_unref(chunk.memblock); + + work_done = 1; + + u->frame_index += frames; + +/* pa_log_debug("read %lu frames", (unsigned long) frames); */ + + if (frames >= n) + break; + + n -= frames; + } + } + + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; + return work_done; +} + +static void update_smoother(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + int64_t frames; + int err; + pa_usec_t now1, now2; + + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's update the time smoother */ + + snd_pcm_hwsync(u->pcm_handle); + snd_pcm_avail_update(u->pcm_handle); + + if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { + pa_log_warn("Failed to get delay: %s", snd_strerror(err)); + return; + } + + frames = u->frame_index + delay; + + now1 = pa_rtclock_usec(); + now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->source->sample_spec); + + pa_smoother_put(u->smoother, now1, now2); +} + +static pa_usec_t source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + int64_t delay; + pa_usec_t now1, now2; + + pa_assert(u); + + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); + + delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->source->sample_spec); + + if (delay > 0) + r = (pa_usec_t) delay; + + return r; +} + +static int build_pollfd(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) + return -1; + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + + /* Let's suspend */ + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int update_sw_params(struct userdata *u) { + snd_pcm_uframes_t avail_min; + int err; + + pa_assert(u); + + /* Use the full buffer if noone asked us for anything specific */ + u->hwbuf_unused_frames = 0; + + if (u->use_tsched) { + pa_usec_t latency; + + if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) { + size_t b; + + pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); + + b = pa_usec_to_bytes(latency, &u->source->sample_spec); + + /* We need at least one sample in our buffer */ + + if (PA_UNLIKELY(b < u->frame_size)) + b = u->frame_size; + + u->hwbuf_unused_frames = (snd_pcm_sframes_t) + (PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0); + } + + fix_tsched_watermark(u); + } + + pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); + + avail_min = 1; + + if (u->use_tsched) { + pa_usec_t sleep_usec, process_usec; + + hw_sleep_time(u, &sleep_usec, &process_usec); + avail_min += pa_usec_to_bytes(sleep_usec, &u->source->sample_spec) / u->frame_size; + } + + pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); + + if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + return err; + } + + return 0; +} + +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b, d; + unsigned nfrags; + snd_pcm_uframes_t period_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + snd_config_update_free_global(); + + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, + /*SND_PCM_NONBLOCK|*/ + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } + + ss = u->source->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + d = u->use_tsched; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap || d != u->use_tsched) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", + (unsigned long) u->nfragments, (unsigned long) u->fragment_size, + (unsigned long) nfrags, period_size * u->frame_size); + goto fail; + } + + if (update_sw_params(u) < 0) + goto fail; + + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + snd_pcm_start(u->pcm_handle); + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = source_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + if (build_pollfd(u) < 0) + return -1; + + snd_pcm_start(u->pcm_handle); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + } + + break; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_source_get_volume(u->source, TRUE); + pa_source_get_mute(u->source, TRUE); + } + + return 0; +} + +static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { + + return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / + (double) (u->hw_volume_max - u->hw_volume_min)); +} + +static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { + long alsa_vol; + + alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) + / PA_VOLUME_NORM) + u->hw_volume_min; + + return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); +} + +static int source_get_volume_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + unsigned i; + pa_cvolume r; + char t[PA_CVOLUME_SNPRINT_MAX]; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if (u->mixer_seperate_channels) { + + r.channels = s->sample_spec.channels; + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + } else { + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); + } + } + + } else { + long alsa_vol; + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + + } else { + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + } + } + + pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + if (!pa_cvolume_equal(&u->hardware_volume, &r)) { + + u->hardware_volume = s->volume = r; + + if (u->hw_dB_supported) { + pa_cvolume reset; + + /* Hmm, so the hardware volume changed, let's reset our software volume */ + + pa_cvolume_reset(&reset, s->sample_spec.channels); + pa_source_set_soft_volume(s, &reset); + } + } + + return 0; + +fail: + pa_log_error("Unable to read volume: %s", snd_strerror(err)); + + return -1; +} + +static int source_set_volume_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + unsigned i; + pa_cvolume r; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if (u->mixer_seperate_channels) { + + r.channels = s->sample_spec.channels; + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; + + vol = s->volume.values[i]; + + if (u->hw_dB_supported) { + + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + + } else { + alsa_vol = to_alsa_volume(u, vol); + + if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); + } + } + + } else { + pa_volume_t vol; + long alsa_vol; + + vol = pa_cvolume_max(&s->volume); + + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_capture_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + + } else { + alsa_vol = to_alsa_volume(u, vol); + + if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); + } + } + + u->hardware_volume = r; + + if (u->hw_dB_supported) { + char t[PA_CVOLUME_SNPRINT_MAX]; + + /* Match exactly what the user requested by software */ + + pa_sw_cvolume_divide(&r, &s->volume, &r); + pa_source_set_soft_volume(s, &r); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); + pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); + pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + } else + + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ + + s->volume = r; + + return 0; + +fail: + pa_log_error("Unable to set volume: %s", snd_strerror(err)); + + return -1; +} + +static int source_get_mute_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err, sw; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { + pa_log_error("Unable to get switch: %s", snd_strerror(err)); + return -1; + } + + s->muted = !sw; + + return 0; +} + +static int source_set_mute_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { + pa_log_error("Unable to set switch: %s", snd_strerror(err)); + return -1; + } + + return 0; +} + +static void source_update_requested_latency_cb(pa_source *s) { + struct userdata *u = s->userdata; + pa_assert(u); + + if (!u->pcm_handle) + return; + + update_sw_params(u); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + +/* pa_log_debug("loop"); */ + + /* Read some data and pass it to the sources */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + int work_done = 0; + pa_usec_t sleep_usec = 0; + + if (u->use_mmap) + work_done = mmap_read(u, &sleep_usec, revents & POLLIN); + else + work_done = unix_read(u, &sleep_usec, revents & POLLIN); + + if (work_done < 0) + goto fail; + +/* pa_log_debug("work_done = %i", work_done); */ + + if (work_done) + update_smoother(u); + + if (u->use_tsched) { + pa_usec_t cusec; + + /* OK, the capture buffer is now empty, let's + * calculate when to wake up next */ + +/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ + + /* Convert from the sound card time domain to the + * system time domain */ + cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); + +/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ + + /* We don't trust the conversion, so we wake up whatever comes first */ + pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); + } + } else if (u->use_tsched) + + /* OK, we're in an invalid state, let's disable our timers */ + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + struct pollfd *pollfd; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLOUT|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { + if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; + + snd_pcm_start(u->pcm_handle); + } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); + } else + revents = 0; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + struct userdata *u = NULL; + const char *dev_id; + pa_sample_spec ss; + pa_channel_map map; + uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; + snd_pcm_uframes_t period_frames, tsched_frames; + size_t frame_size; + snd_pcm_info_t *pcm_info = NULL; + int err; + const char *name; + char *name_buf = NULL; + pa_bool_t namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; + pa_source_new_data data; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + pa_alsa_redirect_errors_inc(); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { + pa_log("Failed to parse sample specification"); + goto fail; + } + + frame_size = pa_frame_size(&ss); + + nfrags = m->core->default_n_fragments; + frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); + if (frag_size <= 0) + frag_size = (uint32_t) frame_size; + tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || + pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || + pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + + hwbuf_size = frag_size * nfrags; + period_frames = frag_size/frame_size; + tsched_frames = tsched_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { + pa_log("Failed to parse timer_scheduling argument."); + goto fail; + } + + if (use_tsched && !pa_rtclock_hrtimer()) { + pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->use_tsched = use_tsched; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->alsa_rtpoll_item = NULL; + + u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC, DEFAULT_TSCHED_WATERMARK_USEC, TRUE, 5); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + snd_config_update_free_global(); + + b = use_mmap; + d = use_tsched; + + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_frames, tsched_frames, + &b, &d))) + goto fail; + + } else { + + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_frames, tsched_frames, + &b, &d))) + goto fail; + } + + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = FALSE; + } + + if (use_tsched && (!b || !d)) { + pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); + u->use_tsched = use_tsched = FALSE; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if (u->use_tsched) + pa_log_info("Successfully enabled timer-based scheduling mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + pa_log("Error fetching PCM info: %s", snd_strerror(err)); + goto fail; + } + + /* ALSA might tweak the sample spec, so recalculate the frame size */ + frame_size = pa_frame_size(&ss); + + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) + pa_log("Error opening mixer: %s", snd_strerror(err)); + else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + snd_pcm_info_t* info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; + + if ((card = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + pa_xfree(md); + } + } + } + + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic", FALSE))) + found = FALSE; + + if (!found) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } + } + + if ((name = pa_modargs_get_value(ma, "source_name", NULL))) + namereg_fail = TRUE; + else { + name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); + namereg_fail = FALSE; + } + + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, name); + data.namereg_fail = namereg_fail; + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + + pa_alsa_init_proplist(data.proplist, pcm_info); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + pa_xfree(name_buf); + + if (!u->source) { + pa_log("Failed to create source object"); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->update_requested_latency = source_update_requested_latency_cb; + u->source->userdata = u; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->frame_size = frame_size; + u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + u->hwbuf_unused_frames = 0; + u->tsched_watermark = tsched_watermark; + u->frame_index = 0; + u->hw_dB_supported = FALSE; + u->hw_dB_min = u->hw_dB_max = 0; + u->hw_volume_min = u->hw_volume_max = 0; + u->mixer_seperate_channels = FALSE; + pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels); + + if (use_tsched) + fix_tsched_watermark(u); + + pa_source_set_latency_range(u->source, + !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, + pa_bytes_to_usec(u->hwbuf_size, &ss)); + + pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", + nfrags, (long unsigned) u->fragment_size, + (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); + + if (use_tsched) + pa_log_info("Time scheduling watermark is %0.2fms", + (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); + + if (update_sw_params(u) < 0) + goto fail; + + if (u->mixer_handle) { + pa_assert(u->mixer_elem); + + if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { + pa_bool_t suitable = FALSE; + + if (snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) + pa_log_info("Failed to get volume range. Falling back to software volume control."); + else if (u->hw_volume_min >= u->hw_volume_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); + else { + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + suitable = TRUE; + } + + if (suitable) { + if (snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) + pa_log_info("Mixer doesn't support dB information."); + else { +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); +#endif + + if (u->hw_dB_min >= u->hw_dB_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + else { + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + u->hw_dB_supported = TRUE; + + if (u->hw_dB_max > 0) { + u->source->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); + } else + pa_log_info("No particular base volume set, fixing to 0 dB"); + + } + } + + if (!u->hw_dB_supported && + u->hw_volume_max - u->hw_volume_min < 3) { + + pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); + suitable = FALSE; + } + } + + if (suitable) { + u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0; + + u->source->get_volume = source_get_volume_cb; + u->source->set_volume = source_set_volume_cb; + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0); + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + } else + pa_log_info("Using software volume control."); + } + + if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { + u->source->get_mute = source_get_mute_cb; + u->source->set_mute = source_set_mute_cb; + u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; + } else + pa_log_info("Using software mute control."); + + u->mixer_fdl = pa_alsa_fdlist_new(); + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + goto fail; + } + + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); + snd_mixer_elem_set_callback_private(u->mixer_elem, u); + } else + u->mixer_fdl = NULL; + + pa_alsa_dump(u->pcm_handle); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + /* Get initial mixer settings */ + if (data.volume_is_set) { + if (u->source->set_volume) + u->source->set_volume(u->source); + } else { + if (u->source->get_volume) + u->source->get_volume(u->source); + } + + if (data.muted_is_set) { + if (u->source->set_mute) + u->source->set_mute(u->source); + } else { + if (u->source->get_mute) + u->source->get_mute(u->source); + } + + pa_source_put(u->source); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +int pa__get_n_used(pa_module *m) { + struct userdata *u; + + pa_assert(m); + pa_assert_se(u = m->userdata); + + return pa_source_linked_by(u->source); +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); + return; + } + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->source) + pa_source_unref(u->source); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + if (u->smoother) + pa_smoother_free(u->smoother); + + pa_xfree(u->device_name); + pa_xfree(u); + + snd_config_update_free_global(); + pa_alsa_redirect_errors_dec(); +} diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c deleted file mode 100644 index a3e818dd..00000000 --- a/src/modules/module-alsa-sink.c +++ /dev/null @@ -1,1674 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2008 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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 -#endif - -#include - -#include - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alsa-util.h" -#include "module-alsa-sink-symdef.h" - -PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("ALSA Sink"); -PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE( - "sink_name= " - "device= " - "device_id= " - "format= " - "rate= " - "channels= " - "channel_map= " - "fragments= " - "fragment_size= " - "mmap= " - "tsched= " - "tsched_buffer_size= " - "tsched_buffer_watermark="); - -static const char* const valid_modargs[] = { - "sink_name", - "device", - "device_id", - "format", - "rate", - "channels", - "channel_map", - "fragments", - "fragment_size", - "mmap", - "tsched", - "tsched_buffer_size", - "tsched_buffer_watermark", - NULL -}; - -#define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ -#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ -#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ - -struct userdata { - pa_core *core; - pa_module *module; - pa_sink *sink; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - snd_pcm_t *pcm_handle; - - pa_alsa_fdlist *mixer_fdl; - snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - long hw_volume_max, hw_volume_min; - long hw_dB_max, hw_dB_min; - pa_bool_t hw_dB_supported; - pa_bool_t mixer_seperate_channels; - pa_cvolume hardware_volume; - - size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; - unsigned nfragments; - pa_memchunk memchunk; - - char *device_name; - - pa_bool_t use_mmap, use_tsched; - - pa_bool_t first, after_rewind; - - pa_rtpoll_item *alsa_rtpoll_item; - - snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; - - pa_smoother *smoother; - int64_t frame_index; - uint64_t since_start; - - snd_pcm_sframes_t hwbuf_unused_frames; -}; - -static void fix_tsched_watermark(struct userdata *u) { - size_t max_use; - size_t min_sleep, min_wakeup; - pa_assert(u); - - max_use = u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size; - - min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec); - min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec); - - if (min_sleep > max_use/2) - min_sleep = pa_frame_align(max_use/2, &u->sink->sample_spec); - if (min_sleep < u->frame_size) - min_sleep = u->frame_size; - - if (min_wakeup > max_use/2) - min_wakeup = pa_frame_align(max_use/2, &u->sink->sample_spec); - if (min_wakeup < u->frame_size) - min_wakeup = u->frame_size; - - if (u->tsched_watermark > max_use-min_sleep) - u->tsched_watermark = max_use-min_sleep; - - if (u->tsched_watermark < min_wakeup) - u->tsched_watermark = min_wakeup; -} - -static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { - pa_usec_t usec, wm; - - pa_assert(sleep_usec); - pa_assert(process_usec); - - pa_assert(u); - - usec = pa_sink_get_requested_latency_within_thread(u->sink); - - if (usec == (pa_usec_t) -1) - usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec); - -/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ - - wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec); - - if (usec >= wm) { - *sleep_usec = usec - wm; - *process_usec = wm; - } else - *process_usec = *sleep_usec = usec / 2; - -/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ -} - -static int try_recover(struct userdata *u, const char *call, int err) { - pa_assert(u); - pa_assert(call); - pa_assert(err < 0); - - pa_log_debug("%s: %s", call, snd_strerror(err)); - - pa_assert(err != -EAGAIN); - - if (err == -EPIPE) - pa_log_debug("%s: Buffer underrun!", call); - - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { - u->first = TRUE; - u->since_start = 0; - return 0; - } - - pa_log("%s: %s", call, snd_strerror(err)); - return -1; -} - -static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { - size_t left_to_play; - - if ((size_t) n*u->frame_size < u->hwbuf_size) - left_to_play = u->hwbuf_size - ((size_t) n*u->frame_size); - else - left_to_play = 0; - - if (left_to_play > 0) { -/* pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); */ - } else if (!u->first && !u->after_rewind) { - pa_log_info("Underrun!"); - - if (u->use_tsched) { - size_t old_watermark = u->tsched_watermark; - - u->tsched_watermark *= 2; - fix_tsched_watermark(u); - - if (old_watermark != u->tsched_watermark) - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); - } - } - - return left_to_play; -} - -static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { - int work_done = 0; - pa_usec_t max_sleep_usec = 0, process_usec = 0; - size_t left_to_play; - - pa_assert(u); - pa_sink_assert_ref(u->sink); - - if (u->use_tsched) - hw_sleep_time(u, &max_sleep_usec, &process_usec); - - for (;;) { - snd_pcm_sframes_t n; - int r; - - snd_pcm_hwsync(u->pcm_handle); - - /* First we determine how many samples are missing to fill the - * buffer up to 100% */ - - if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) - continue; - - return r; - } - - left_to_play = check_left_to_play(u, n); - - if (u->use_tsched) - - /* We won't fill up the playback buffer before at least - * half the sleep time is over because otherwise we might - * ask for more data from the clients then they expect. We - * need to guarantee that clients only have to keep around - * a single hw buffer length. */ - - if (!polled && - pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) - break; - - if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { - - if (polled) - pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " - "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " - "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail_update() returned 0."); - - break; - } - - n -= u->hwbuf_unused_frames; - - polled = FALSE; - -/* pa_log_debug("Filling up"); */ - - for (;;) { - pa_memchunk chunk; - void *p; - int err; - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; - snd_pcm_sframes_t sframes; - -/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - - if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) - continue; - - return r; - } - - /* Make sure that if these memblocks need to be copied they will fit into one slot */ - if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size) - frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size; - - /* Check these are multiples of 8 bit */ - pa_assert((areas[0].first & 7) == 0); - pa_assert((areas[0].step & 7)== 0); - - /* We assume a single interleaved memory buffer */ - pa_assert((areas[0].first >> 3) == 0); - pa_assert((areas[0].step >> 3) == u->frame_size); - - p = (uint8_t*) areas[0].addr + (offset * u->frame_size); - - chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; - - pa_sink_render_into_full(u->sink, &chunk); - - /* FIXME: Maybe we can do something to keep this memory block - * a little bit longer around? */ - pa_memblock_unref_fixed(chunk.memblock); - - if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) - continue; - - return r; - } - - work_done = 1; - - u->frame_index += (int64_t) frames; - u->since_start += frames * u->frame_size; - -/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ - - if (frames >= (snd_pcm_uframes_t) n) - break; - - n -= (snd_pcm_sframes_t) frames; - } - } - - *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; - return work_done; -} - -static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { - int work_done = 0; - pa_usec_t max_sleep_usec = 0, process_usec = 0; - size_t left_to_play; - - pa_assert(u); - pa_sink_assert_ref(u->sink); - - if (u->use_tsched) - hw_sleep_time(u, &max_sleep_usec, &process_usec); - - for (;;) { - snd_pcm_sframes_t n; - int r; - - snd_pcm_hwsync(u->pcm_handle); - - if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) - continue; - - return r; - } - - left_to_play = check_left_to_play(u, n); - - if (u->use_tsched) - - /* We won't fill up the playback buffer before at least - * half the sleep time is over because otherwise we might - * ask for more data from the clients then they expect. We - * need to guarantee that clients only have to keep around - * a single hw buffer length. */ - - if (!polled && - pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) - break; - - if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { - - if (polled) - pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " - "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " - "We were woken up with POLLOUT set -- however a subsequent snd_pcm_avail_update() returned 0."); - - break; - } - - n -= u->hwbuf_unused_frames; - - polled = FALSE; - - for (;;) { - snd_pcm_sframes_t frames; - void *p; - -/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, (size_t) n * u->frame_size, &u->memchunk); - - pa_assert(u->memchunk.length > 0); - - frames = (snd_pcm_sframes_t) (u->memchunk.length / u->frame_size); - - if (frames > n) - frames = n; - - p = pa_memblock_acquire(u->memchunk.memblock); - frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames); - pa_memblock_release(u->memchunk.memblock); - - pa_assert(frames != 0); - - if (PA_UNLIKELY(frames < 0)) { - - if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) - continue; - - return r; - } - - u->memchunk.index += (size_t) frames * u->frame_size; - u->memchunk.length -= (size_t) frames * u->frame_size; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } - - work_done = 1; - - u->frame_index += frames; - u->since_start += (size_t) frames * u->frame_size; - -/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ - - if (frames >= n) - break; - - n -= frames; - } - } - - *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; - return work_done; -} - -static void update_smoother(struct userdata *u) { - snd_pcm_sframes_t delay = 0; - int64_t frames; - int err; - pa_usec_t now1, now2; -/* struct timeval timestamp; */ - snd_pcm_status_t *status; - - snd_pcm_status_alloca(&status); - - pa_assert(u); - pa_assert(u->pcm_handle); - - /* Let's update the time smoother */ - - snd_pcm_hwsync(u->pcm_handle); - snd_pcm_avail_update(u->pcm_handle); - -/* if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { */ -/* pa_log("Failed to query DSP status data: %s", snd_strerror(err)); */ -/* return; */ -/* } */ - -/* delay = snd_pcm_status_get_delay(status); */ - - if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { - pa_log("Failed to query DSP status data: %s", snd_strerror(err)); - return; - } - - frames = u->frame_index - delay; - -/* pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); */ - -/* snd_pcm_status_get_tstamp(status, ×tamp); */ -/* pa_rtclock_from_wallclock(×tamp); */ -/* now1 = pa_timeval_load(×tamp); */ - - now1 = pa_rtclock_usec(); - now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->sink->sample_spec); - pa_smoother_put(u->smoother, now1, now2); -} - -static pa_usec_t sink_get_latency(struct userdata *u) { - pa_usec_t r = 0; - int64_t delay; - pa_usec_t now1, now2; - - pa_assert(u); - - now1 = pa_rtclock_usec(); - now2 = pa_smoother_get(u->smoother, now1); - - delay = (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2; - - if (delay > 0) - r = (pa_usec_t) delay; - - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); - - return r; -} - -static int build_pollfd(struct userdata *u) { - pa_assert(u); - pa_assert(u->pcm_handle); - - if (u->alsa_rtpoll_item) - pa_rtpoll_item_free(u->alsa_rtpoll_item); - - if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) - return -1; - - return 0; -} - -static int suspend(struct userdata *u) { - pa_assert(u); - pa_assert(u->pcm_handle); - - pa_smoother_pause(u->smoother, pa_rtclock_usec()); - - /* Let's suspend -- we don't call snd_pcm_drain() here since that might - * take awfully long with our long buffer sizes today. */ - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - - if (u->alsa_rtpoll_item) { - pa_rtpoll_item_free(u->alsa_rtpoll_item); - u->alsa_rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -static int update_sw_params(struct userdata *u) { - snd_pcm_uframes_t avail_min; - int err; - - pa_assert(u); - - /* Use the full buffer if noone asked us for anything specific */ - u->hwbuf_unused_frames = 0; - - if (u->use_tsched) { - pa_usec_t latency; - - if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) { - size_t b; - - pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); - - b = pa_usec_to_bytes(latency, &u->sink->sample_spec); - - /* We need at least one sample in our buffer */ - - if (PA_UNLIKELY(b < u->frame_size)) - b = u->frame_size; - - u->hwbuf_unused_frames = (snd_pcm_sframes_t) - (PA_LIKELY(b < u->hwbuf_size) ? - ((u->hwbuf_size - b) / u->frame_size) : 0); - } - - fix_tsched_watermark(u); - } - - pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); - - /* We need at last one frame in the used part of the buffer */ - avail_min = (snd_pcm_uframes_t) u->hwbuf_unused_frames + 1; - - if (u->use_tsched) { - pa_usec_t sleep_usec, process_usec; - - hw_sleep_time(u, &sleep_usec, &process_usec); - avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec) / u->frame_size; - } - - pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); - return err; - } - - pa_sink_set_max_request(u->sink, u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size); - - return 0; -} - -static int unsuspend(struct userdata *u) { - pa_sample_spec ss; - int err; - pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, - /*SND_PCM_NONBLOCK|*/ - SND_PCM_NO_AUTO_RESAMPLE| - SND_PCM_NO_AUTO_CHANNELS| - SND_PCM_NO_AUTO_FORMAT)) < 0) { - pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); - goto fail; - } - - ss = u->sink->sample_spec; - nfrags = u->nfragments; - period_size = u->fragment_size / u->frame_size; - b = u->use_mmap; - d = u->use_tsched; - - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); - goto fail; - } - - if (b != u->use_mmap || d != u->use_tsched) { - pa_log_warn("Resume failed, couldn't get original access mode."); - goto fail; - } - - if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) { - pa_log_warn("Resume failed, couldn't restore original sample settings."); - goto fail; - } - - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); - goto fail; - } - - if (update_sw_params(u) < 0) - goto fail; - - if (build_pollfd(u) < 0) - goto fail; - - /* FIXME: We need to reload the volume somehow */ - - u->first = TRUE; - u->since_start = 0; - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - } - - return -1; -} - -static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK(o)->userdata; - - switch (code) { - - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t r = 0; - - if (u->pcm_handle) - r = sink_get_latency(u); - - *((pa_usec_t*) data) = r; - - return 0; - } - - case PA_SINK_MESSAGE_SET_STATE: - - switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - - case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - - if (suspend(u) < 0) - return -1; - - break; - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: - - if (u->sink->thread_info.state == PA_SINK_INIT) { - if (build_pollfd(u) < 0) - return -1; - } - - if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; - } - - break; - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - ; - } - - break; - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} - -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { - struct userdata *u = snd_mixer_elem_get_callback_private(elem); - - pa_assert(u); - pa_assert(u->mixer_handle); - - if (mask == SND_CTL_EVENT_MASK_REMOVE) - return 0; - - if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_sink_get_volume(u->sink, TRUE); - pa_sink_get_mute(u->sink, TRUE); - } - - return 0; -} - -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { - - return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / - (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { - long alsa_vol; - - alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) - / PA_VOLUME_NORM) + u->hw_volume_min; - - return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} - -static int sink_get_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - int err; - unsigned i; - pa_cvolume r; - char t[PA_CVOLUME_SNPRINT_MAX]; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - } else { - - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - - } else { - - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } - } - - pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - if (!pa_cvolume_equal(&u->hardware_volume, &r)) { - - u->hardware_volume = s->volume = r; - - if (u->hw_dB_supported) { - pa_cvolume reset; - - /* Hmm, so the hardware volume changed, let's reset our software volume */ - - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_sink_set_soft_volume(s, &reset); - } - } - - return 0; - -fail: - pa_log_error("Unable to read volume: %s", snd_strerror(err)); - - return -1; -} - -static int sink_set_volume_cb(pa_sink *s) { - struct userdata *u = s->userdata; - int err; - unsigned i; - pa_cvolume r; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - pa_volume_t vol; - - vol = s->volume.values[i]; - - if (u->hw_dB_supported) { - - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - - } else { - alsa_vol = to_alsa_volume(u, vol); - - if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - pa_volume_t vol; - long alsa_vol; - - vol = pa_cvolume_max(&s->volume); - - if (u->hw_dB_supported) { - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_playback_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - - } else { - alsa_vol = to_alsa_volume(u, vol); - - if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } - } - - u->hardware_volume = r; - - if (u->hw_dB_supported) { - char t[PA_CVOLUME_SNPRINT_MAX]; - - /* Match exactly what the user requested by software */ - - pa_sw_cvolume_divide(&r, &s->volume, &r); - pa_sink_set_soft_volume(s, &r); - - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); - pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - } else - - /* We can't match exactly what the user requested, hence let's - * at least tell the user about it */ - - s->volume = r; - - return 0; - -fail: - pa_log_error("Unable to set volume: %s", snd_strerror(err)); - - return -1; -} - -static int sink_get_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - int err, sw; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { - pa_log_error("Unable to get switch: %s", snd_strerror(err)); - return -1; - } - - s->muted = !sw; - - return 0; -} - -static int sink_set_mute_cb(pa_sink *s) { - struct userdata *u = s->userdata; - int err; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { - pa_log_error("Unable to set switch: %s", snd_strerror(err)); - return -1; - } - - return 0; -} - -static void sink_update_requested_latency_cb(pa_sink *s) { - struct userdata *u = s->userdata; - snd_pcm_sframes_t before; - pa_assert(u); - - if (!u->pcm_handle) - return; - - before = u->hwbuf_unused_frames; - update_sw_params(u); - - /* Let's check whether we now use only a smaller part of the - buffer then before. If so, we need to make sure that subsequent - rewinds are relative to the new maxium fill level and not to the - current fill level. Thus, let's do a full rewind once, to clear - things up. */ - - if (u->hwbuf_unused_frames > before) { - pa_log_debug("Requesting rewind due to latency change."); - pa_sink_request_rewind(s, (size_t) -1); - } -} - -static int process_rewind(struct userdata *u) { - snd_pcm_sframes_t unused; - size_t rewind_nbytes, unused_nbytes, limit_nbytes; - pa_assert(u); - - /* Figure out how much we shall rewind and reset the counter */ - rewind_nbytes = u->sink->thread_info.rewind_nbytes; - u->sink->thread_info.rewind_nbytes = 0; - - if (rewind_nbytes <= 0) - goto finish; - - pa_assert(rewind_nbytes > 0); - pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); - - snd_pcm_hwsync(u->pcm_handle); - if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) { - pa_log("snd_pcm_avail_update() failed: %s", snd_strerror((int) unused)); - return -1; - } - - unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size; - - if (u->hwbuf_size > unused_nbytes) - limit_nbytes = u->hwbuf_size - unused_nbytes; - else - limit_nbytes = 0; - - if (rewind_nbytes > limit_nbytes) - rewind_nbytes = limit_nbytes; - - if (rewind_nbytes > 0) { - snd_pcm_sframes_t in_frames, out_frames; - - pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes); - - in_frames = (snd_pcm_sframes_t) (rewind_nbytes / u->frame_size); - pa_log_debug("before: %lu", (unsigned long) in_frames); - if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) { - pa_log("snd_pcm_rewind() failed: %s", snd_strerror((int) out_frames)); - return -1; - } - pa_log_debug("after: %lu", (unsigned long) out_frames); - - rewind_nbytes = (size_t) out_frames * u->frame_size; - - if (rewind_nbytes <= 0) - pa_log_info("Tried rewind, but was apparently not possible."); - else { - u->frame_index -= out_frames; - pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); - pa_sink_process_rewind(u->sink, rewind_nbytes); - - u->after_rewind = TRUE; - return 0; - } - } else - pa_log_debug("Mhmm, actually there is nothing to rewind."); - -finish: - - pa_sink_process_rewind(u->sink, 0); - - return 0; - -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - - for (;;) { - int ret; - -/* pa_log_debug("loop"); */ - - /* Render some data and write it to the dsp */ - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - int work_done; - pa_usec_t sleep_usec = 0; - - if (u->sink->thread_info.rewind_requested) - if (process_rewind(u) < 0) - goto fail; - - if (u->use_mmap) - work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); - else - work_done = unix_write(u, &sleep_usec, revents & POLLOUT); - - if (work_done < 0) - goto fail; - -/* pa_log_debug("work_done = %i", work_done); */ - - if (work_done) { - - if (u->first) { - pa_log_info("Starting playback."); - snd_pcm_start(u->pcm_handle); - - pa_smoother_resume(u->smoother, pa_rtclock_usec()); - } - - update_smoother(u); - } - - if (u->use_tsched) { - pa_usec_t cusec; - - if (u->since_start <= u->hwbuf_size) { - - /* USB devices on ALSA seem to hit a buffer - * underrun during the first iterations much - * quicker then we calculate here, probably due to - * the transport latency. To accomodate for that - * we artificially decrease the sleep time until - * we have filled the buffer at least once - * completely.*/ - - /*pa_log_debug("Cutting sleep time for the initial iterations by half.");*/ - sleep_usec /= 2; - } - - /* OK, the playback buffer is now full, let's - * calculate when to wake up next */ -/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ - - /* Convert from the sound card time domain to the - * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); - -/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ - - /* We don't trust the conversion, so we wake up whatever comes first */ - pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); - } - - u->first = FALSE; - u->after_rewind = FALSE; - - } else if (u->use_tsched) - - /* OK, we're in an invalid state, let's disable our timers */ - pa_rtpoll_set_timer_disabled(u->rtpoll); - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - /* Tell ALSA about this and process its response */ - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { - struct pollfd *pollfd; - int err; - unsigned n; - - pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); - - if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { - pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); - goto fail; - } - - if (revents & (POLLIN|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { - if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) - goto fail; - - u->first = TRUE; - u->since_start = 0; - } - - if (revents && u->use_tsched) - pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); - } else - revents = 0; - } - -fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -int pa__init(pa_module*m) { - - pa_modargs *ma = NULL; - struct userdata *u = NULL; - const char *dev_id; - pa_sample_spec ss; - pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; - size_t frame_size; - snd_pcm_info_t *pcm_info = NULL; - int err; - const char *name; - char *name_buf = NULL; - pa_bool_t namereg_fail; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; - pa_usec_t usec; - pa_sink_new_data data; - - snd_pcm_info_alloca(&pcm_info); - - pa_assert(m); - - pa_alsa_redirect_errors_inc(); - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments"); - goto fail; - } - - ss = m->core->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("Failed to parse sample specification and channel map"); - goto fail; - } - - frame_size = pa_frame_size(&ss); - - nfrags = m->core->default_n_fragments; - frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); - if (frag_size <= 0) - frag_size = (uint32_t) frame_size; - tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); - tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); - - if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || - pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || - pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || - pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { - pa_log("Failed to parse buffer metrics"); - goto fail; - } - - hwbuf_size = frag_size * nfrags; - period_frames = frag_size/frame_size; - tsched_frames = tsched_size/frame_size; - - if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { - pa_log("Failed to parse mmap argument."); - goto fail; - } - - if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { - pa_log("Failed to parse tsched argument."); - goto fail; - } - - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; - } - - u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - m->userdata = u; - u->use_mmap = use_mmap; - u->use_tsched = use_tsched; - u->first = TRUE; - u->since_start = 0; - u->after_rewind = FALSE; - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->alsa_rtpoll_item = NULL; - - u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE, 5); - usec = pa_rtclock_usec(); - pa_smoother_set_time_offset(u->smoother, usec); - pa_smoother_pause(u->smoother, usec); - - snd_config_update_free_global(); - - b = use_mmap; - d = use_tsched; - - if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { - - if (!(u->pcm_handle = pa_alsa_open_by_device_id( - dev_id, - &u->device_name, - &ss, &map, - SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, - &b, &d))) - - goto fail; - - } else { - - if (!(u->pcm_handle = pa_alsa_open_by_device_string( - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), - &u->device_name, - &ss, &map, - SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_frames, tsched_frames, - &b, &d))) - goto fail; - - } - - pa_assert(u->device_name); - pa_log_info("Successfully opened device %s.", u->device_name); - - if (use_mmap && !b) { - pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = FALSE; - } - - if (use_tsched && (!b || !d)) { - pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); - u->use_tsched = use_tsched = FALSE; - } - - if (u->use_mmap) - pa_log_info("Successfully enabled mmap() mode."); - - if (u->use_tsched) - pa_log_info("Successfully enabled timer-based scheduling mode."); - - if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { - pa_log("Error fetching PCM info: %s", snd_strerror(err)); - goto fail; - } - - /* ALSA might tweak the sample spec, so recalculate the frame size */ - frame_size = pa_frame_size(&ss); - - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) - pa_log_warn("Error opening mixer: %s", snd_strerror(err)); - else { - pa_bool_t found = FALSE; - - if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) - found = TRUE; - else { - snd_pcm_info_t *info; - - snd_pcm_info_alloca(&info); - - if (snd_pcm_info(u->pcm_handle, info) >= 0) { - char *md; - int card; - - if ((card = snd_pcm_info_get_card(info)) >= 0) { - - md = pa_sprintf_malloc("hw:%i", card); - - if (strcmp(u->device_name, md)) - if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) - found = TRUE; - pa_xfree(md); - } - } - } - - if (found) - if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM", TRUE))) - found = FALSE; - - if (!found) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } - } - - if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = TRUE; - else { - name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); - namereg_fail = FALSE; - } - - pa_sink_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_sink_new_data_set_name(&data, name); - data.namereg_fail = namereg_fail; - pa_sink_new_data_set_sample_spec(&data, &ss); - pa_sink_new_data_set_channel_map(&data, &map); - - pa_alsa_init_proplist(data.proplist, pcm_info); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); - - u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); - pa_sink_new_data_done(&data); - pa_xfree(name_buf); - - if (!u->sink) { - pa_log("Failed to create sink object"); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg; - u->sink->update_requested_latency = sink_update_requested_latency_cb; - u->sink->userdata = u; - - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - - u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->hwbuf_unused_frames = 0; - u->tsched_watermark = tsched_watermark; - u->frame_index = 0; - u->hw_dB_supported = FALSE; - u->hw_dB_min = u->hw_dB_max = 0; - u->hw_volume_min = u->hw_volume_max = 0; - u->mixer_seperate_channels = FALSE; - pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); - - if (use_tsched) - fix_tsched_watermark(u); - - u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0; - u->sink->thread_info.max_request = u->hwbuf_size; - - pa_sink_set_latency_range(u->sink, - !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, - pa_bytes_to_usec(u->hwbuf_size, &ss)); - - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, - (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); - - if (use_tsched) - pa_log_info("Time scheduling watermark is %0.2fms", - (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); - - if (update_sw_params(u) < 0) - goto fail; - - pa_memchunk_reset(&u->memchunk); - - if (u->mixer_handle) { - pa_assert(u->mixer_elem); - - if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { - pa_bool_t suitable = FALSE; - - if (snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) - pa_log_info("Failed to get volume range. Falling back to software volume control."); - else if (u->hw_volume_min >= u->hw_volume_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); - else { - pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); - suitable = TRUE; - } - - if (suitable) { - if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) - pa_log_info("Mixer doesn't support dB information."); - else { -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif - - if (u->hw_dB_min >= u->hw_dB_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - else { - pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - u->hw_dB_supported = TRUE; - - if (u->hw_dB_max > 0) { - u->sink->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); - pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); - } else - pa_log_info("No particular base volume set, fixing to 0 dB"); - } - } - - if (!u->hw_dB_supported && - u->hw_volume_max - u->hw_volume_min < 3) { - - pa_log_info("Device doesn't do dB volume and has less than 4 volume levels. Falling back to software volume control."); - suitable = FALSE; - } - } - - if (suitable) { - u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0; - - u->sink->get_volume = sink_get_volume_cb; - u->sink->set_volume = sink_set_volume_cb; - u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); - pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); - - } else - pa_log_info("Using software volume control."); - } - - if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { - u->sink->get_mute = sink_get_mute_cb; - u->sink->set_mute = sink_set_mute_cb; - u->sink->flags |= PA_SINK_HW_MUTE_CTRL; - } else - pa_log_info("Using software mute control."); - - u->mixer_fdl = pa_alsa_fdlist_new(); - - if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { - pa_log("Failed to initialize file descriptor monitoring"); - goto fail; - } - - snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); - snd_mixer_elem_set_callback_private(u->mixer_elem, u); - } else - u->mixer_fdl = NULL; - - pa_alsa_dump(u->pcm_handle); - - if (!(u->thread = pa_thread_new(thread_func, u))) { - pa_log("Failed to create thread."); - goto fail; - } - - /* Get initial mixer settings */ - if (data.volume_is_set) { - if (u->sink->set_volume) - u->sink->set_volume(u->sink); - } else { - if (u->sink->get_volume) - u->sink->get_volume(u->sink); - } - - if (data.muted_is_set) { - if (u->sink->set_mute) - u->sink->set_mute(u->sink); - } else { - if (u->sink->get_mute) - u->sink->get_mute(u->sink); - } - - pa_sink_put(u->sink); - - pa_modargs_free(ma); - - return 0; - -fail: - - if (ma) - pa_modargs_free(ma); - - pa__done(m); - - return -1; -} - -int pa__get_n_used(pa_module *m) { - struct userdata *u; - - pa_assert(m); - pa_assert_se(u = m->userdata); - - return pa_sink_linked_by(u->sink); -} - -void pa__done(pa_module*m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) { - pa_alsa_redirect_errors_dec(); - return; - } - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); - - if (u->alsa_rtpoll_item) - pa_rtpoll_item_free(u->alsa_rtpoll_item); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); - - if (u->mixer_handle) - snd_mixer_close(u->mixer_handle); - - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - } - - if (u->smoother) - pa_smoother_free(u->smoother); - - pa_xfree(u->device_name); - pa_xfree(u); - - snd_config_update_free_global(); - - pa_alsa_redirect_errors_dec(); -} diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c deleted file mode 100644 index 901db01f..00000000 --- a/src/modules/module-alsa-source.c +++ /dev/null @@ -1,1491 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2008 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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 -#endif - -#include - -#include - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alsa-util.h" -#include "module-alsa-source-symdef.h" - -PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("ALSA Source"); -PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE( - "source_name= " - "device= " - "device_id= " - "format= " - "rate= " - "channels= " - "channel_map= " - "fragments= " - "fragment_size= " - "mmap= " - "tsched= " - "tsched_buffer_size= " - "tsched_buffer_watermark="); - -static const char* const valid_modargs[] = { - "source_name", - "device", - "device_id", - "format", - "rate", - "channels", - "channel_map", - "fragments", - "fragment_size", - "mmap", - "tsched", - "tsched_buffer_size", - "tsched_buffer_watermark", - NULL -}; - -#define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ -#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ -#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ -#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ - -struct userdata { - pa_core *core; - pa_module *module; - pa_source *source; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - snd_pcm_t *pcm_handle; - - pa_alsa_fdlist *mixer_fdl; - snd_mixer_t *mixer_handle; - snd_mixer_elem_t *mixer_elem; - long hw_volume_max, hw_volume_min; - long hw_dB_max, hw_dB_min; - pa_bool_t hw_dB_supported; - pa_bool_t mixer_seperate_channels; - - pa_cvolume hardware_volume; - - size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; - unsigned nfragments; - - char *device_name; - - pa_bool_t use_mmap, use_tsched; - - pa_rtpoll_item *alsa_rtpoll_item; - - snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; - - pa_smoother *smoother; - int64_t frame_index; - - snd_pcm_sframes_t hwbuf_unused_frames; -}; - -static void fix_tsched_watermark(struct userdata *u) { - size_t max_use; - size_t min_sleep, min_wakeup; - pa_assert(u); - - max_use = u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size; - - min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec); - min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec); - - if (min_sleep > max_use/2) - min_sleep = pa_frame_align(max_use/2, &u->source->sample_spec); - if (min_sleep < u->frame_size) - min_sleep = u->frame_size; - - if (min_wakeup > max_use/2) - min_wakeup = pa_frame_align(max_use/2, &u->source->sample_spec); - if (min_wakeup < u->frame_size) - min_wakeup = u->frame_size; - - if (u->tsched_watermark > max_use-min_sleep) - u->tsched_watermark = max_use-min_sleep; - - if (u->tsched_watermark < min_wakeup) - u->tsched_watermark = min_wakeup; -} - -static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { - pa_usec_t wm, usec; - - pa_assert(u); - - usec = pa_source_get_requested_latency_within_thread(u->source); - - if (usec == (pa_usec_t) -1) - usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->sample_spec); - -/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ - - wm = pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec); - - if (usec >= wm) { - *sleep_usec = usec - wm; - *process_usec = wm; - } else - *process_usec = *sleep_usec = usec /= 2; - -/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ - - return usec; -} - -static int try_recover(struct userdata *u, const char *call, int err) { - pa_assert(u); - pa_assert(call); - pa_assert(err < 0); - - pa_log_debug("%s: %s", call, snd_strerror(err)); - - pa_assert(err != -EAGAIN); - - if (err == -EPIPE) - pa_log_debug("%s: Buffer overrun!", call); - - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { - snd_pcm_start(u->pcm_handle); - return 0; - } - - pa_log("%s: %s", call, snd_strerror(err)); - return -1; -} - -static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) { - size_t left_to_record; - size_t rec_space = u->hwbuf_size - (size_t) u->hwbuf_unused_frames*u->frame_size; - - if ((size_t) n*u->frame_size < rec_space) - left_to_record = rec_space - ((size_t) n*u->frame_size); - else - left_to_record = 0; - - if (left_to_record > 0) { -/* pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); */ - } else { - pa_log_info("Overrun!"); - - if (u->use_tsched) { - size_t old_watermark = u->tsched_watermark; - - u->tsched_watermark *= 2; - fix_tsched_watermark(u); - - if (old_watermark != u->tsched_watermark) - pa_log_notice("Increasing wakeup watermark to %0.2f ms", - (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC); - } - } - - return left_to_record; -} - -static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { - int work_done = 0; - pa_usec_t max_sleep_usec = 0, process_usec = 0; - size_t left_to_record; - - pa_assert(u); - pa_source_assert_ref(u->source); - - if (u->use_tsched) - hw_sleep_time(u, &max_sleep_usec, &process_usec); - - for (;;) { - snd_pcm_sframes_t n; - int r; - - snd_pcm_hwsync(u->pcm_handle); - - if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) - continue; - - return r; - } - - left_to_record = check_left_to_record(u, n); - - if (u->use_tsched) - if (!polled && - pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) - break; - - if (PA_UNLIKELY(n <= 0)) { - - if (polled) - pa_log("ALSA woke us up to read new data from the device, but there was actually nothing to read! " - "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " - "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."); - - break; - } - - polled = FALSE; - - for (;;) { - int err; - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; - pa_memchunk chunk; - void *p; - snd_pcm_sframes_t sframes; - -/* pa_log_debug("%lu frames to read", (unsigned long) frames); */ - - if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) - continue; - - return r; - } - - /* Make sure that if these memblocks need to be copied they will fit into one slot */ - if (frames > pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size) - frames = pa_mempool_block_size_max(u->source->core->mempool)/u->frame_size; - - /* Check these are multiples of 8 bit */ - pa_assert((areas[0].first & 7) == 0); - pa_assert((areas[0].step & 7)== 0); - - /* We assume a single interleaved memory buffer */ - pa_assert((areas[0].first >> 3) == 0); - pa_assert((areas[0].step >> 3) == u->frame_size); - - p = (uint8_t*) areas[0].addr + (offset * u->frame_size); - - chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; - - pa_source_post(u->source, &chunk); - pa_memblock_unref_fixed(chunk.memblock); - - if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) - continue; - - return r; - } - - work_done = 1; - - u->frame_index += (int64_t) frames; - -/* pa_log_debug("read %lu frames", (unsigned long) frames); */ - - if (frames >= (snd_pcm_uframes_t) n) - break; - - n -= (snd_pcm_sframes_t) frames; - } - } - - *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; - return work_done; -} - -static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { - int work_done = 0; - pa_usec_t max_sleep_usec = 0, process_usec = 0; - size_t left_to_record; - - pa_assert(u); - pa_source_assert_ref(u->source); - - if (u->use_tsched) - hw_sleep_time(u, &max_sleep_usec, &process_usec); - - for (;;) { - snd_pcm_sframes_t n; - int r; - - snd_pcm_hwsync(u->pcm_handle); - - if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) { - - if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) - continue; - - return r; - } - - left_to_record = check_left_to_record(u, n); - - if (u->use_tsched) - if (!polled && - pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) - break; - - if (PA_UNLIKELY(n <= 0)) { - - if (polled) - pa_log("ALSA woke us up to read new data from the device, but there was actually nothing to read! " - "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. " - "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."); - - return work_done; - } - - polled = FALSE; - - for (;;) { - void *p; - snd_pcm_sframes_t frames; - pa_memchunk chunk; - - chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); - - frames = (snd_pcm_sframes_t) (pa_memblock_get_length(chunk.memblock) / u->frame_size); - - if (frames > n) - frames = n; - -/* pa_log_debug("%lu frames to read", (unsigned long) n); */ - - p = pa_memblock_acquire(chunk.memblock); - frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames); - pa_memblock_release(chunk.memblock); - - pa_assert(frames != 0); - - if (PA_UNLIKELY(frames < 0)) { - pa_memblock_unref(chunk.memblock); - - if ((r = try_recover(u, "snd_pcm_readi", (int) (frames))) == 0) - continue; - - return r; - } - - chunk.index = 0; - chunk.length = (size_t) frames * u->frame_size; - - pa_source_post(u->source, &chunk); - pa_memblock_unref(chunk.memblock); - - work_done = 1; - - u->frame_index += frames; - -/* pa_log_debug("read %lu frames", (unsigned long) frames); */ - - if (frames >= n) - break; - - n -= frames; - } - } - - *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; - return work_done; -} - -static void update_smoother(struct userdata *u) { - snd_pcm_sframes_t delay = 0; - int64_t frames; - int err; - pa_usec_t now1, now2; - - pa_assert(u); - pa_assert(u->pcm_handle); - - /* Let's update the time smoother */ - - snd_pcm_hwsync(u->pcm_handle); - snd_pcm_avail_update(u->pcm_handle); - - if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { - pa_log_warn("Failed to get delay: %s", snd_strerror(err)); - return; - } - - frames = u->frame_index + delay; - - now1 = pa_rtclock_usec(); - now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->source->sample_spec); - - pa_smoother_put(u->smoother, now1, now2); -} - -static pa_usec_t source_get_latency(struct userdata *u) { - pa_usec_t r = 0; - int64_t delay; - pa_usec_t now1, now2; - - pa_assert(u); - - now1 = pa_rtclock_usec(); - now2 = pa_smoother_get(u->smoother, now1); - - delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->source->sample_spec); - - if (delay > 0) - r = (pa_usec_t) delay; - - return r; -} - -static int build_pollfd(struct userdata *u) { - pa_assert(u); - pa_assert(u->pcm_handle); - - if (u->alsa_rtpoll_item) - pa_rtpoll_item_free(u->alsa_rtpoll_item); - - if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) - return -1; - - return 0; -} - -static int suspend(struct userdata *u) { - pa_assert(u); - pa_assert(u->pcm_handle); - - pa_smoother_pause(u->smoother, pa_rtclock_usec()); - - /* Let's suspend */ - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - - if (u->alsa_rtpoll_item) { - pa_rtpoll_item_free(u->alsa_rtpoll_item); - u->alsa_rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -static int update_sw_params(struct userdata *u) { - snd_pcm_uframes_t avail_min; - int err; - - pa_assert(u); - - /* Use the full buffer if noone asked us for anything specific */ - u->hwbuf_unused_frames = 0; - - if (u->use_tsched) { - pa_usec_t latency; - - if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) { - size_t b; - - pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); - - b = pa_usec_to_bytes(latency, &u->source->sample_spec); - - /* We need at least one sample in our buffer */ - - if (PA_UNLIKELY(b < u->frame_size)) - b = u->frame_size; - - u->hwbuf_unused_frames = (snd_pcm_sframes_t) - (PA_LIKELY(b < u->hwbuf_size) ? - ((u->hwbuf_size - b) / u->frame_size) : 0); - } - - fix_tsched_watermark(u); - } - - pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); - - avail_min = 1; - - if (u->use_tsched) { - pa_usec_t sleep_usec, process_usec; - - hw_sleep_time(u, &sleep_usec, &process_usec); - avail_min += pa_usec_to_bytes(sleep_usec, &u->source->sample_spec) / u->frame_size; - } - - pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); - - if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); - return err; - } - - return 0; -} - -static int unsuspend(struct userdata *u) { - pa_sample_spec ss; - int err; - pa_bool_t b, d; - unsigned nfrags; - snd_pcm_uframes_t period_size; - - pa_assert(u); - pa_assert(!u->pcm_handle); - - pa_log_info("Trying resume..."); - - snd_config_update_free_global(); - - if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, - /*SND_PCM_NONBLOCK|*/ - SND_PCM_NO_AUTO_RESAMPLE| - SND_PCM_NO_AUTO_CHANNELS| - SND_PCM_NO_AUTO_FORMAT)) < 0) { - pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); - goto fail; - } - - ss = u->source->sample_spec; - nfrags = u->nfragments; - period_size = u->fragment_size / u->frame_size; - b = u->use_mmap; - d = u->use_tsched; - - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); - goto fail; - } - - if (b != u->use_mmap || d != u->use_tsched) { - pa_log_warn("Resume failed, couldn't get original access mode."); - goto fail; - } - - if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) { - pa_log_warn("Resume failed, couldn't restore original sample settings."); - goto fail; - } - - if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", - (unsigned long) u->nfragments, (unsigned long) u->fragment_size, - (unsigned long) nfrags, period_size * u->frame_size); - goto fail; - } - - if (update_sw_params(u) < 0) - goto fail; - - if (build_pollfd(u) < 0) - goto fail; - - /* FIXME: We need to reload the volume somehow */ - - snd_pcm_start(u->pcm_handle); - pa_smoother_resume(u->smoother, pa_rtclock_usec()); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - if (u->pcm_handle) { - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - } - - return -1; -} - -static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SOURCE(o)->userdata; - - switch (code) { - - case PA_SOURCE_MESSAGE_GET_LATENCY: { - pa_usec_t r = 0; - - if (u->pcm_handle) - r = source_get_latency(u); - - *((pa_usec_t*) data) = r; - - return 0; - } - - case PA_SOURCE_MESSAGE_SET_STATE: - - switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { - - case PA_SOURCE_SUSPENDED: - pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); - - if (suspend(u) < 0) - return -1; - - break; - - case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: - - if (u->source->thread_info.state == PA_SOURCE_INIT) { - if (build_pollfd(u) < 0) - return -1; - - snd_pcm_start(u->pcm_handle); - } - - if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { - if (unsuspend(u) < 0) - return -1; - } - - break; - - case PA_SOURCE_UNLINKED: - case PA_SOURCE_INIT: - ; - } - - break; - } - - return pa_source_process_msg(o, code, data, offset, chunk); -} - -static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { - struct userdata *u = snd_mixer_elem_get_callback_private(elem); - - pa_assert(u); - pa_assert(u->mixer_handle); - - if (mask == SND_CTL_EVENT_MASK_REMOVE) - return 0; - - if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_source_get_volume(u->source, TRUE); - pa_source_get_mute(u->source, TRUE); - } - - return 0; -} - -static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { - - return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / - (double) (u->hw_volume_max - u->hw_volume_min)); -} - -static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { - long alsa_vol; - - alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) - / PA_VOLUME_NORM) + u->hw_volume_min; - - return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); -} - -static int source_get_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - int err; - unsigned i; - pa_cvolume r; - char t[PA_CVOLUME_SNPRINT_MAX]; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - } else { - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - long alsa_vol; - - if (u->hw_dB_supported) { - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); -#endif - - pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - - } else { - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } - } - - pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - if (!pa_cvolume_equal(&u->hardware_volume, &r)) { - - u->hardware_volume = s->volume = r; - - if (u->hw_dB_supported) { - pa_cvolume reset; - - /* Hmm, so the hardware volume changed, let's reset our software volume */ - - pa_cvolume_reset(&reset, s->sample_spec.channels); - pa_source_set_soft_volume(s, &reset); - } - } - - return 0; - -fail: - pa_log_error("Unable to read volume: %s", snd_strerror(err)); - - return -1; -} - -static int source_set_volume_cb(pa_source *s) { - struct userdata *u = s->userdata; - int err; - unsigned i; - pa_cvolume r; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if (u->mixer_seperate_channels) { - - r.channels = s->sample_spec.channels; - - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - pa_volume_t vol; - - vol = s->volume.values[i]; - - if (u->hw_dB_supported) { - - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - - } else { - alsa_vol = to_alsa_volume(u, vol); - - if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; - - r.values[i] = from_alsa_volume(u, alsa_vol); - } - } - - } else { - pa_volume_t vol; - long alsa_vol; - - vol = pa_cvolume_max(&s->volume); - - if (u->hw_dB_supported) { - alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); - alsa_vol += u->hw_dB_max; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - - if ((err = snd_mixer_selem_set_capture_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - - } else { - alsa_vol = to_alsa_volume(u, vol); - - if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) - goto fail; - - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) - goto fail; - - pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); - } - } - - u->hardware_volume = r; - - if (u->hw_dB_supported) { - char t[PA_CVOLUME_SNPRINT_MAX]; - - /* Match exactly what the user requested by software */ - - pa_sw_cvolume_divide(&r, &s->volume, &r); - pa_source_set_soft_volume(s, &r); - - pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); - pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); - pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); - - } else - - /* We can't match exactly what the user requested, hence let's - * at least tell the user about it */ - - s->volume = r; - - return 0; - -fail: - pa_log_error("Unable to set volume: %s", snd_strerror(err)); - - return -1; -} - -static int source_get_mute_cb(pa_source *s) { - struct userdata *u = s->userdata; - int err, sw; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { - pa_log_error("Unable to get switch: %s", snd_strerror(err)); - return -1; - } - - s->muted = !sw; - - return 0; -} - -static int source_set_mute_cb(pa_source *s) { - struct userdata *u = s->userdata; - int err; - - pa_assert(u); - pa_assert(u->mixer_elem); - - if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { - pa_log_error("Unable to set switch: %s", snd_strerror(err)); - return -1; - } - - return 0; -} - -static void source_update_requested_latency_cb(pa_source *s) { - struct userdata *u = s->userdata; - pa_assert(u); - - if (!u->pcm_handle) - return; - - update_sw_params(u); -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - unsigned short revents = 0; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - - for (;;) { - int ret; - -/* pa_log_debug("loop"); */ - - /* Read some data and pass it to the sources */ - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - int work_done = 0; - pa_usec_t sleep_usec = 0; - - if (u->use_mmap) - work_done = mmap_read(u, &sleep_usec, revents & POLLIN); - else - work_done = unix_read(u, &sleep_usec, revents & POLLIN); - - if (work_done < 0) - goto fail; - -/* pa_log_debug("work_done = %i", work_done); */ - - if (work_done) - update_smoother(u); - - if (u->use_tsched) { - pa_usec_t cusec; - - /* OK, the capture buffer is now empty, let's - * calculate when to wake up next */ - -/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ - - /* Convert from the sound card time domain to the - * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); - -/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ - - /* We don't trust the conversion, so we wake up whatever comes first */ - pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); - } - } else if (u->use_tsched) - - /* OK, we're in an invalid state, let's disable our timers */ - pa_rtpoll_set_timer_disabled(u->rtpoll); - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - /* Tell ALSA about this and process its response */ - if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - struct pollfd *pollfd; - int err; - unsigned n; - - pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); - - if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { - pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); - goto fail; - } - - if (revents & (POLLOUT|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { - if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) - goto fail; - - snd_pcm_start(u->pcm_handle); - } - - if (revents && u->use_tsched) - pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); - } else - revents = 0; - } - -fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -int pa__init(pa_module*m) { - - pa_modargs *ma = NULL; - struct userdata *u = NULL; - const char *dev_id; - pa_sample_spec ss; - pa_channel_map map; - uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark; - snd_pcm_uframes_t period_frames, tsched_frames; - size_t frame_size; - snd_pcm_info_t *pcm_info = NULL; - int err; - const char *name; - char *name_buf = NULL; - pa_bool_t namereg_fail; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; - pa_source_new_data data; - - snd_pcm_info_alloca(&pcm_info); - - pa_assert(m); - - pa_alsa_redirect_errors_inc(); - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments"); - goto fail; - } - - ss = m->core->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { - pa_log("Failed to parse sample specification"); - goto fail; - } - - frame_size = pa_frame_size(&ss); - - nfrags = m->core->default_n_fragments; - frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); - if (frag_size <= 0) - frag_size = (uint32_t) frame_size; - tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); - tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); - - if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || - pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || - pa_modargs_get_value_u32(ma, "tsched_buffer_size", &tsched_size) < 0 || - pa_modargs_get_value_u32(ma, "tsched_buffer_watermark", &tsched_watermark) < 0) { - pa_log("Failed to parse buffer metrics"); - goto fail; - } - - hwbuf_size = frag_size * nfrags; - period_frames = frag_size/frame_size; - tsched_frames = tsched_size/frame_size; - - if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { - pa_log("Failed to parse mmap argument."); - goto fail; - } - - if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { - pa_log("Failed to parse timer_scheduling argument."); - goto fail; - } - - if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); - use_tsched = FALSE; - } - - u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - m->userdata = u; - u->use_mmap = use_mmap; - u->use_tsched = use_tsched; - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->alsa_rtpoll_item = NULL; - - u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC, DEFAULT_TSCHED_WATERMARK_USEC, TRUE, 5); - pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); - - snd_config_update_free_global(); - - b = use_mmap; - d = use_tsched; - - if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { - - if (!(u->pcm_handle = pa_alsa_open_by_device_id( - dev_id, - &u->device_name, - &ss, &map, - SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, - &b, &d))) - goto fail; - - } else { - - if (!(u->pcm_handle = pa_alsa_open_by_device_string( - pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), - &u->device_name, - &ss, &map, - SND_PCM_STREAM_CAPTURE, - &nfrags, &period_frames, tsched_frames, - &b, &d))) - goto fail; - } - - pa_assert(u->device_name); - pa_log_info("Successfully opened device %s.", u->device_name); - - if (use_mmap && !b) { - pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = FALSE; - } - - if (use_tsched && (!b || !d)) { - pa_log_info("Cannot enabled timer-based scheduling, falling back to sound IRQ scheduling."); - u->use_tsched = use_tsched = FALSE; - } - - if (u->use_mmap) - pa_log_info("Successfully enabled mmap() mode."); - - if (u->use_tsched) - pa_log_info("Successfully enabled timer-based scheduling mode."); - - if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { - pa_log("Error fetching PCM info: %s", snd_strerror(err)); - goto fail; - } - - /* ALSA might tweak the sample spec, so recalculate the frame size */ - frame_size = pa_frame_size(&ss); - - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) - pa_log("Error opening mixer: %s", snd_strerror(err)); - else { - pa_bool_t found = FALSE; - - if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) - found = TRUE; - else { - snd_pcm_info_t* info; - - snd_pcm_info_alloca(&info); - - if (snd_pcm_info(u->pcm_handle, info) >= 0) { - char *md; - int card; - - if ((card = snd_pcm_info_get_card(info)) >= 0) { - - md = pa_sprintf_malloc("hw:%i", card); - - if (strcmp(u->device_name, md)) - if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) - found = TRUE; - pa_xfree(md); - } - } - } - - if (found) - if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic", FALSE))) - found = FALSE; - - if (!found) { - snd_mixer_close(u->mixer_handle); - u->mixer_handle = NULL; - } - } - - if ((name = pa_modargs_get_value(ma, "source_name", NULL))) - namereg_fail = TRUE; - else { - name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); - namereg_fail = FALSE; - } - - pa_source_new_data_init(&data); - data.driver = __FILE__; - data.module = m; - pa_source_new_data_set_name(&data, name); - data.namereg_fail = namereg_fail; - pa_source_new_data_set_sample_spec(&data, &ss); - pa_source_new_data_set_channel_map(&data, &map); - - pa_alsa_init_proplist(data.proplist, pcm_info); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); - pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); - - u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); - pa_source_new_data_done(&data); - pa_xfree(name_buf); - - if (!u->source) { - pa_log("Failed to create source object"); - goto fail; - } - - u->source->parent.process_msg = source_process_msg; - u->source->update_requested_latency = source_update_requested_latency_cb; - u->source->userdata = u; - - pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); - pa_source_set_rtpoll(u->source, u->rtpoll); - - u->frame_size = frame_size; - u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); - u->nfragments = nfrags; - u->hwbuf_size = u->fragment_size * nfrags; - u->hwbuf_unused_frames = 0; - u->tsched_watermark = tsched_watermark; - u->frame_index = 0; - u->hw_dB_supported = FALSE; - u->hw_dB_min = u->hw_dB_max = 0; - u->hw_volume_min = u->hw_volume_max = 0; - u->mixer_seperate_channels = FALSE; - pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels); - - if (use_tsched) - fix_tsched_watermark(u); - - pa_source_set_latency_range(u->source, - !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, - pa_bytes_to_usec(u->hwbuf_size, &ss)); - - pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", - nfrags, (long unsigned) u->fragment_size, - (double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC); - - if (use_tsched) - pa_log_info("Time scheduling watermark is %0.2fms", - (double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC); - - if (update_sw_params(u) < 0) - goto fail; - - if (u->mixer_handle) { - pa_assert(u->mixer_elem); - - if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { - pa_bool_t suitable = FALSE; - - if (snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) - pa_log_info("Failed to get volume range. Falling back to software volume control."); - else if (u->hw_volume_min >= u->hw_volume_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); - else { - pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); - suitable = TRUE; - } - - if (suitable) { - if (snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) - pa_log_info("Mixer doesn't support dB information."); - else { -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); - VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); -#endif - - if (u->hw_dB_min >= u->hw_dB_max) - pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - else { - pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); - u->hw_dB_supported = TRUE; - - if (u->hw_dB_max > 0) { - u->source->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); - pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume)); - } else - pa_log_info("No particular base volume set, fixing to 0 dB"); - - } - } - - if (!u->hw_dB_supported && - u->hw_volume_max - u->hw_volume_min < 3) { - - pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); - suitable = FALSE; - } - } - - if (suitable) { - u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0; - - u->source->get_volume = source_get_volume_cb; - u->source->set_volume = source_set_volume_cb; - u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0); - pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); - } else - pa_log_info("Using software volume control."); - } - - if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { - u->source->get_mute = source_get_mute_cb; - u->source->set_mute = source_set_mute_cb; - u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; - } else - pa_log_info("Using software mute control."); - - u->mixer_fdl = pa_alsa_fdlist_new(); - - if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { - pa_log("Failed to initialize file descriptor monitoring"); - goto fail; - } - - snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); - snd_mixer_elem_set_callback_private(u->mixer_elem, u); - } else - u->mixer_fdl = NULL; - - pa_alsa_dump(u->pcm_handle); - - if (!(u->thread = pa_thread_new(thread_func, u))) { - pa_log("Failed to create thread."); - goto fail; - } - /* Get initial mixer settings */ - if (data.volume_is_set) { - if (u->source->set_volume) - u->source->set_volume(u->source); - } else { - if (u->source->get_volume) - u->source->get_volume(u->source); - } - - if (data.muted_is_set) { - if (u->source->set_mute) - u->source->set_mute(u->source); - } else { - if (u->source->get_mute) - u->source->get_mute(u->source); - } - - pa_source_put(u->source); - - pa_modargs_free(ma); - - return 0; - -fail: - - if (ma) - pa_modargs_free(ma); - - pa__done(m); - - return -1; -} - -int pa__get_n_used(pa_module *m) { - struct userdata *u; - - pa_assert(m); - pa_assert_se(u = m->userdata); - - return pa_source_linked_by(u->source); -} - -void pa__done(pa_module*m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) { - pa_alsa_redirect_errors_dec(); - return; - } - - if (u->source) - pa_source_unlink(u->source); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->source) - pa_source_unref(u->source); - - if (u->alsa_rtpoll_item) - pa_rtpoll_item_free(u->alsa_rtpoll_item); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->mixer_fdl) - pa_alsa_fdlist_free(u->mixer_fdl); - - if (u->mixer_handle) - snd_mixer_close(u->mixer_handle); - - if (u->pcm_handle) { - snd_pcm_drop(u->pcm_handle); - snd_pcm_close(u->pcm_handle); - } - - if (u->smoother) - pa_smoother_free(u->smoother); - - pa_xfree(u->device_name); - pa_xfree(u); - - snd_config_update_free_global(); - pa_alsa_redirect_errors_dec(); -} diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c deleted file mode 100644 index 23a32549..00000000 --- a/src/modules/module-oss.c +++ /dev/null @@ -1,1536 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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. -***/ - -/* General power management rules: - * - * When SUSPENDED we close the audio device. - * - * We make no difference between IDLE and RUNNING in our handling. - * - * As long as we are in RUNNING/IDLE state we will *always* write data to - * the device. If none is avilable from the inputs, we write silence - * instead. - * - * If power should be saved on IDLE module-suspend-on-idle should be used. - * - */ - -#ifdef HAVE_CONFIG_H -#include -#endif - -#ifdef HAVE_SYS_MMAN_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "oss-util.h" -#include "module-oss-symdef.h" - -PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("OSS Sink/Source"); -PA_MODULE_VERSION(PACKAGE_VERSION); -PA_MODULE_LOAD_ONCE(FALSE); -PA_MODULE_USAGE( - "sink_name= " - "source_name= " - "device= " - "record= " - "playback= " - "format= " - "channels= " - "rate= " - "fragments= " - "fragment_size= " - "channel_map= " - "mmap="); - -#define DEFAULT_DEVICE "/dev/dsp" - -struct userdata { - pa_core *core; - pa_module *module; - pa_sink *sink; - pa_source *source; - - pa_thread *thread; - pa_thread_mq thread_mq; - pa_rtpoll *rtpoll; - - char *device_name; - - pa_memchunk memchunk; - - size_t frame_size; - uint32_t in_fragment_size, out_fragment_size, in_nfrags, out_nfrags, in_hwbuf_size, out_hwbuf_size; - pa_bool_t use_getospace, use_getispace; - pa_bool_t use_getodelay; - - pa_bool_t sink_suspended, source_suspended; - - int fd; - int mode; - - int mixer_fd; - int mixer_devmask; - - int nfrags, frag_size; - - pa_bool_t use_mmap; - unsigned out_mmap_current, in_mmap_current; - void *in_mmap, *out_mmap; - pa_memblock **in_mmap_memblocks, **out_mmap_memblocks; - - int in_mmap_saved_nfrags, out_mmap_saved_nfrags; - - pa_rtpoll_item *rtpoll_item; -}; - -static const char* const valid_modargs[] = { - "sink_name", - "source_name", - "device", - "record", - "playback", - "fragments", - "fragment_size", - "format", - "rate", - "channels", - "channel_map", - "mmap", - NULL -}; - -static void trigger(struct userdata *u, pa_bool_t quick) { - int enable_bits = 0, zero = 0; - - pa_assert(u); - - if (u->fd < 0) - return; - - pa_log_debug("trigger"); - - if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) - enable_bits |= PCM_ENABLE_INPUT; - - if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) - enable_bits |= PCM_ENABLE_OUTPUT; - - pa_log_debug("trigger: %i", enable_bits); - - - if (u->use_mmap) { - - if (!quick) - ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero); - -#ifdef SNDCTL_DSP_HALT - if (enable_bits == 0) - if (ioctl(u->fd, SNDCTL_DSP_HALT, NULL) < 0) - pa_log_warn("SNDCTL_DSP_HALT: %s", pa_cstrerror(errno)); -#endif - - if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) - pa_log_warn("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); - - if (u->sink && !(enable_bits & PCM_ENABLE_OUTPUT)) { - pa_log_debug("clearing playback buffer"); - pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &u->sink->sample_spec); - } - - } else { - - if (enable_bits) - if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0) - pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno)); - - if (!quick) { - /* - * Some crappy drivers do not start the recording until we - * read something. Without this snippet, poll will never - * register the fd as ready. - */ - - if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { - uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size); - pa_read(u->fd, buf, u->in_fragment_size, NULL); - pa_xfree(buf); - } - } - } -} - -static void mmap_fill_memblocks(struct userdata *u, unsigned n) { - pa_assert(u); - pa_assert(u->out_mmap_memblocks); - -/* pa_log("Mmmap writing %u blocks", n); */ - - while (n > 0) { - pa_memchunk chunk; - - if (u->out_mmap_memblocks[u->out_mmap_current]) - pa_memblock_unref_fixed(u->out_mmap_memblocks[u->out_mmap_current]); - - chunk.memblock = u->out_mmap_memblocks[u->out_mmap_current] = - pa_memblock_new_fixed( - u->core->mempool, - (uint8_t*) u->out_mmap + u->out_fragment_size * u->out_mmap_current, - u->out_fragment_size, - 1); - - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; - - pa_sink_render_into_full(u->sink, &chunk); - - u->out_mmap_current++; - while (u->out_mmap_current >= u->out_nfrags) - u->out_mmap_current -= u->out_nfrags; - - n--; - } -} - -static int mmap_write(struct userdata *u) { - struct count_info info; - - pa_assert(u); - pa_assert(u->sink); - -/* pa_log("Mmmap writing..."); */ - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - return -1; - } - - info.blocks += u->out_mmap_saved_nfrags; - u->out_mmap_saved_nfrags = 0; - - if (info.blocks > 0) - mmap_fill_memblocks(u, (unsigned) info.blocks); - - return info.blocks; -} - -static void mmap_post_memblocks(struct userdata *u, unsigned n) { - pa_assert(u); - pa_assert(u->in_mmap_memblocks); - -/* pa_log("Mmmap reading %u blocks", n); */ - - while (n > 0) { - pa_memchunk chunk; - - if (!u->in_mmap_memblocks[u->in_mmap_current]) { - - chunk.memblock = u->in_mmap_memblocks[u->in_mmap_current] = - pa_memblock_new_fixed( - u->core->mempool, - (uint8_t*) u->in_mmap + u->in_fragment_size*u->in_mmap_current, - u->in_fragment_size, - 1); - - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; - - pa_source_post(u->source, &chunk); - } - - u->in_mmap_current++; - while (u->in_mmap_current >= u->in_nfrags) - u->in_mmap_current -= u->in_nfrags; - - n--; - } -} - -static void mmap_clear_memblocks(struct userdata*u, unsigned n) { - unsigned i = u->in_mmap_current; - - pa_assert(u); - pa_assert(u->in_mmap_memblocks); - - if (n > u->in_nfrags) - n = u->in_nfrags; - - while (n > 0) { - if (u->in_mmap_memblocks[i]) { - pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); - u->in_mmap_memblocks[i] = NULL; - } - - i++; - while (i >= u->in_nfrags) - i -= u->in_nfrags; - - n--; - } -} - -static int mmap_read(struct userdata *u) { - struct count_info info; - pa_assert(u); - pa_assert(u->source); - -/* pa_log("Mmmap reading..."); */ - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - return -1; - } - -/* pa_log("... %i", info.blocks); */ - - info.blocks += u->in_mmap_saved_nfrags; - u->in_mmap_saved_nfrags = 0; - - if (info.blocks > 0) { - mmap_post_memblocks(u, (unsigned) info.blocks); - mmap_clear_memblocks(u, u->in_nfrags/2); - } - - return info.blocks; -} - -static pa_usec_t mmap_sink_get_latency(struct userdata *u) { - struct count_info info; - size_t bpos, n; - - pa_assert(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->out_mmap_saved_nfrags += info.blocks; - - bpos = ((u->out_mmap_current + (unsigned) u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; - - if (bpos <= (size_t) info.ptr) - n = u->out_hwbuf_size - ((size_t) info.ptr - bpos); - else - n = bpos - (size_t) info.ptr; - -/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ - - return pa_bytes_to_usec(n, &u->sink->sample_spec); -} - -static pa_usec_t mmap_source_get_latency(struct userdata *u) { - struct count_info info; - size_t bpos, n; - - pa_assert(u); - - if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { - pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); - return 0; - } - - u->in_mmap_saved_nfrags += info.blocks; - bpos = ((u->in_mmap_current + (unsigned) u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; - - if (bpos <= (size_t) info.ptr) - n = (size_t) info.ptr - bpos; - else - n = u->in_hwbuf_size - bpos + (size_t) info.ptr; - -/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ - - return pa_bytes_to_usec(n, &u->source->sample_spec); -} - -static pa_usec_t io_sink_get_latency(struct userdata *u) { - pa_usec_t r = 0; - - pa_assert(u); - - if (u->use_getodelay) { - int arg; - - if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { - pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); - u->use_getodelay = 0; - } else - r = pa_bytes_to_usec((size_t) arg, &u->sink->sample_spec); - - } - - if (!u->use_getodelay && u->use_getospace) { - struct audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { - pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); - u->use_getospace = 0; - } else - r = pa_bytes_to_usec((size_t) info.bytes, &u->sink->sample_spec); - } - - if (u->memchunk.memblock) - r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); - - return r; -} - - -static pa_usec_t io_source_get_latency(struct userdata *u) { - pa_usec_t r = 0; - - pa_assert(u); - - if (u->use_getispace) { - struct audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); - u->use_getispace = 0; - } else - r = pa_bytes_to_usec((size_t) info.bytes, &u->source->sample_spec); - } - - return r; -} - -static void build_pollfd(struct userdata *u) { - struct pollfd *pollfd; - - pa_assert(u); - pa_assert(u->fd >= 0); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - pollfd->fd = u->fd; - pollfd->events = 0; - pollfd->revents = 0; -} - -static int suspend(struct userdata *u) { - pa_assert(u); - pa_assert(u->fd >= 0); - - pa_log_info("Suspending..."); - - if (u->out_mmap_memblocks) { - unsigned i; - for (i = 0; i < u->out_nfrags; i++) - if (u->out_mmap_memblocks[i]) { - pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); - u->out_mmap_memblocks[i] = NULL; - } - } - - if (u->in_mmap_memblocks) { - unsigned i; - for (i = 0; i < u->in_nfrags; i++) - if (u->in_mmap_memblocks[i]) { - pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); - u->in_mmap_memblocks[i] = NULL; - } - } - - if (u->in_mmap && u->in_mmap != MAP_FAILED) { - munmap(u->in_mmap, u->in_hwbuf_size); - u->in_mmap = NULL; - } - - if (u->out_mmap && u->out_mmap != MAP_FAILED) { - munmap(u->out_mmap, u->out_hwbuf_size); - u->out_mmap = NULL; - } - - /* Let's suspend */ - ioctl(u->fd, SNDCTL_DSP_SYNC, NULL); - pa_close(u->fd); - u->fd = -1; - - if (u->rtpoll_item) { - pa_rtpoll_item_free(u->rtpoll_item); - u->rtpoll_item = NULL; - } - - pa_log_info("Device suspended..."); - - return 0; -} - -static int sink_get_volume(pa_sink *s); -static int source_get_volume(pa_source *s); - -static int unsuspend(struct userdata *u) { - int m; - pa_sample_spec ss, *ss_original; - int frag_size, in_frag_size, out_frag_size; - int in_nfrags, out_nfrags; - struct audio_buf_info info; - - pa_assert(u); - pa_assert(u->fd < 0); - - m = u->mode; - - pa_log_info("Trying resume..."); - - if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) { - pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno)); - return -1; - } - - if (m != u->mode) { - pa_log_warn("Resume failed, couldn't open device with original access mode."); - goto fail; - } - - if (u->nfrags >= 2 && u->frag_size >= 1) - if (pa_oss_set_fragments(u->fd, u->nfrags, u->frag_size) < 0) { - pa_log_warn("Resume failed, couldn't set original fragment settings."); - goto fail; - } - - ss = *(ss_original = u->sink ? &u->sink->sample_spec : &u->source->sample_spec); - if (pa_oss_auto_format(u->fd, &ss) < 0 || !pa_sample_spec_equal(&ss, ss_original)) { - pa_log_warn("Resume failed, couldn't set original sample format settings."); - goto fail; - } - - if (ioctl(u->fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { - pa_log_warn("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); - goto fail; - } - - in_frag_size = out_frag_size = frag_size; - in_nfrags = out_nfrags = u->nfrags; - - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { - in_frag_size = info.fragsize; - in_nfrags = info.fragstotal; - } - - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { - out_frag_size = info.fragsize; - out_nfrags = info.fragstotal; - } - - if ((u->source && (in_frag_size != (int) u->in_fragment_size || in_nfrags != (int) u->in_nfrags)) || - (u->sink && (out_frag_size != (int) u->out_fragment_size || out_nfrags != (int) u->out_nfrags))) { - pa_log_warn("Resume failed, input fragment settings don't match."); - goto fail; - } - - if (u->use_mmap) { - if (u->source) { - if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); - goto fail; - } - } - - if (u->sink) { - if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { - pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); - if (u->in_mmap && u->in_mmap != MAP_FAILED) { - munmap(u->in_mmap, u->in_hwbuf_size); - u->in_mmap = NULL; - } - - goto fail; - } - - pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); - } - } - - u->out_mmap_current = u->in_mmap_current = 0; - u->out_mmap_saved_nfrags = u->in_mmap_saved_nfrags = 0; - - pa_assert(!u->rtpoll_item); - - build_pollfd(u); - - if (u->sink) - sink_get_volume(u->sink); - if (u->source) - source_get_volume(u->source); - - pa_log_info("Resumed successfully..."); - - return 0; - -fail: - pa_close(u->fd); - u->fd = -1; - return -1; -} - -static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK(o)->userdata; - int ret; - pa_bool_t do_trigger = FALSE, quick = TRUE; - - switch (code) { - - case PA_SINK_MESSAGE_GET_LATENCY: { - pa_usec_t r = 0; - - if (u->fd >= 0) { - if (u->use_mmap) - r = mmap_sink_get_latency(u); - else - r = io_sink_get_latency(u); - } - - *((pa_usec_t*) data) = r; - - return 0; - } - - case PA_SINK_MESSAGE_SET_STATE: - - switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - - case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); - - if (!u->source || u->source_suspended) { - if (suspend(u) < 0) - return -1; - } - - do_trigger = TRUE; - - u->sink_suspended = TRUE; - break; - - case PA_SINK_IDLE: - case PA_SINK_RUNNING: - - if (u->sink->thread_info.state == PA_SINK_INIT) { - do_trigger = TRUE; - quick = u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state); - } - - if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { - - if (!u->source || u->source_suspended) { - if (unsuspend(u) < 0) - return -1; - quick = FALSE; - } - - do_trigger = TRUE; - - u->out_mmap_current = 0; - u->out_mmap_saved_nfrags = 0; - - u->sink_suspended = FALSE; - } - - break; - - case PA_SINK_UNLINKED: - case PA_SINK_INIT: - ; - } - - break; - - } - - ret = pa_sink_process_msg(o, code, data, offset, chunk); - - if (do_trigger) - trigger(u, quick); - - return ret; -} - -static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SOURCE(o)->userdata; - int ret; - int do_trigger = FALSE, quick = TRUE; - - switch (code) { - - case PA_SOURCE_MESSAGE_GET_LATENCY: { - pa_usec_t r = 0; - - if (u->fd >= 0) { - if (u->use_mmap) - r = mmap_source_get_latency(u); - else - r = io_source_get_latency(u); - } - - *((pa_usec_t*) data) = r; - return 0; - } - - case PA_SOURCE_MESSAGE_SET_STATE: - - switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { - case PA_SOURCE_SUSPENDED: - pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); - - if (!u->sink || u->sink_suspended) { - if (suspend(u) < 0) - return -1; - } - - do_trigger = TRUE; - - u->source_suspended = TRUE; - break; - - case PA_SOURCE_IDLE: - case PA_SOURCE_RUNNING: - - if (u->source->thread_info.state == PA_SOURCE_INIT) { - do_trigger = TRUE; - quick = u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state); - } - - if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { - - if (!u->sink || u->sink_suspended) { - if (unsuspend(u) < 0) - return -1; - quick = FALSE; - } - - do_trigger = TRUE; - - u->in_mmap_current = 0; - u->in_mmap_saved_nfrags = 0; - - u->source_suspended = FALSE; - } - break; - - case PA_SOURCE_UNLINKED: - case PA_SOURCE_INIT: - ; - - } - break; - - } - - ret = pa_source_process_msg(o, code, data, offset, chunk); - - if (do_trigger) - trigger(u, quick); - - return ret; -} - -static int sink_get_volume(pa_sink *s) { - struct userdata *u; - int r; - - pa_assert_se(u = s->userdata); - - pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); - - if (u->mixer_devmask & SOUND_MASK_VOLUME) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0) - return r; - - if (u->mixer_devmask & SOUND_MASK_PCM) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0) - return r; - - pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - return -1; -} - -static int sink_set_volume(pa_sink *s) { - struct userdata *u; - int r; - - pa_assert_se(u = s->userdata); - - pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); - - if (u->mixer_devmask & SOUND_MASK_VOLUME) - if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0) - return r; - - if (u->mixer_devmask & SOUND_MASK_PCM) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0) - return r; - - pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - return -1; -} - -static int source_get_volume(pa_source *s) { - struct userdata *u; - int r; - - pa_assert_se(u = s->userdata); - - pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); - - if (u->mixer_devmask & SOUND_MASK_IGAIN) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0) - return r; - - if (u->mixer_devmask & SOUND_MASK_RECLEV) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0) - return r; - - pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); - return -1; -} - -static int source_set_volume(pa_source *s) { - struct userdata *u; - int r; - - pa_assert_se(u = s->userdata); - - pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); - - if (u->mixer_devmask & SOUND_MASK_IGAIN) - if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0) - return r; - - if (u->mixer_devmask & SOUND_MASK_RECLEV) - if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0) - return r; - - pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); - return -1; -} - -static void thread_func(void *userdata) { - struct userdata *u = userdata; - int write_type = 0, read_type = 0; - short revents = 0; - - pa_assert(u); - - pa_log_debug("Thread starting up"); - - if (u->core->realtime_scheduling) - pa_make_realtime(u->core->realtime_priority); - - pa_thread_mq_install(&u->thread_mq); - pa_rtpoll_install(u->rtpoll); - - for (;;) { - int ret; - -/* pa_log("loop"); */ - - if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) - if (u->sink->thread_info.rewind_requested) - pa_sink_process_rewind(u->sink, 0); - - /* Render some data and write it to the dsp */ - - if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { - - if (u->use_mmap) { - - if ((ret = mmap_write(u)) < 0) - goto fail; - - revents &= ~POLLOUT; - - if (ret > 0) - continue; - - } else { - ssize_t l; - pa_bool_t loop = FALSE, work_done = FALSE; - - l = (ssize_t) u->out_fragment_size; - - if (u->use_getospace) { - audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { - pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); - u->use_getospace = FALSE; - } else { - l = info.bytes; - - /* We loop only if GETOSPACE worked and we - * actually *know* that we can write more than - * one fragment at a time */ - loop = TRUE; - } - } - - /* Round down to multiples of the fragment size, - * because OSS needs that (at least some versions - * do) */ - l = (l/(ssize_t) u->out_fragment_size) * (ssize_t) u->out_fragment_size; - - /* Hmm, so poll() signalled us that we can read - * something, but GETOSPACE told us there was nothing? - * Hmm, make the best of it, try to read some data, to - * avoid spinning forever. */ - if (l <= 0 && (revents & POLLOUT)) { - l = (ssize_t) u->out_fragment_size; - loop = FALSE; - } - - while (l > 0) { - void *p; - ssize_t t; - - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, (size_t) l, &u->memchunk); - - pa_assert(u->memchunk.length > 0); - - p = pa_memblock_acquire(u->memchunk.memblock); - t = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); - pa_memblock_release(u->memchunk.memblock); - -/* pa_log("wrote %i bytes of %u", t, l); */ - - pa_assert(t != 0); - - if (t < 0) { - - if (errno == EINTR) - continue; - - else if (errno == EAGAIN) { - pa_log_debug("EAGAIN"); - - revents &= ~POLLOUT; - break; - - } else { - pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); - goto fail; - } - - } else { - - u->memchunk.index += (size_t) t; - u->memchunk.length -= (size_t) t; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } - - l -= t; - - revents &= ~POLLOUT; - work_done = TRUE; - } - - if (!loop) - break; - } - - if (work_done) - continue; - } - } - - /* Try to read some data and pass it on to the source driver. */ - - if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { - - if (u->use_mmap) { - - if ((ret = mmap_read(u)) < 0) - goto fail; - - revents &= ~POLLIN; - - if (ret > 0) - continue; - - } else { - - void *p; - ssize_t l; - pa_memchunk memchunk; - pa_bool_t loop = FALSE, work_done = FALSE; - - l = (ssize_t) u->in_fragment_size; - - if (u->use_getispace) { - audio_buf_info info; - - if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { - pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); - u->use_getispace = FALSE; - } else { - l = info.bytes; - loop = TRUE; - } - } - - l = (l/(ssize_t) u->in_fragment_size) * (ssize_t) u->in_fragment_size; - - if (l <= 0 && (revents & POLLIN)) { - l = (ssize_t) u->in_fragment_size; - loop = FALSE; - } - - while (l > 0) { - ssize_t t; - size_t k; - - pa_assert(l > 0); - - memchunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); - - k = pa_memblock_get_length(memchunk.memblock); - - if (k > (size_t) l) - k = (size_t) l; - - k = (k/u->frame_size)*u->frame_size; - - p = pa_memblock_acquire(memchunk.memblock); - t = pa_read(u->fd, p, k, &read_type); - pa_memblock_release(memchunk.memblock); - - pa_assert(t != 0); /* EOF cannot happen */ - -/* pa_log("read %i bytes of %u", t, l); */ - - if (t < 0) { - pa_memblock_unref(memchunk.memblock); - - if (errno == EINTR) - continue; - - else if (errno == EAGAIN) { - pa_log_debug("EAGAIN"); - - revents &= ~POLLIN; - break; - - } else { - pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); - goto fail; - } - - } else { - memchunk.index = 0; - memchunk.length = (size_t) t; - - pa_source_post(u->source, &memchunk); - pa_memblock_unref(memchunk.memblock); - - l -= t; - - revents &= ~POLLIN; - work_done = TRUE; - } - - if (!loop) - break; - } - - if (work_done) - continue; - } - } - -/* pa_log("loop2 revents=%i", revents); */ - - if (u->rtpoll_item) { - struct pollfd *pollfd; - - pa_assert(u->fd >= 0); - - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - pollfd->events = (short) - (((u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | - ((u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0)); - } - - /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) - goto fail; - - if (ret == 0) - goto finish; - - if (u->rtpoll_item) { - struct pollfd *pollfd; - - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - - if (pollfd->revents & ~(POLLOUT|POLLIN)) { - pa_log("DSP shutdown."); - goto fail; - } - - revents = pollfd->revents; - } else - revents = 0; - } - -fail: - /* If this was no regular exit from the loop we have to continue - * processing messages until we received PA_MESSAGE_SHUTDOWN */ - pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); - pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); - -finish: - pa_log_debug("Thread shutting down"); -} - -int pa__init(pa_module*m) { - - struct audio_buf_info info; - struct userdata *u = NULL; - const char *dev; - int fd = -1; - int nfrags, frag_size; - int mode, caps; - pa_bool_t record = TRUE, playback = TRUE, use_mmap = TRUE; - pa_sample_spec ss; - pa_channel_map map; - pa_modargs *ma = NULL; - char hwdesc[64]; - const char *name; - pa_bool_t namereg_fail; - pa_sink_new_data sink_new_data; - pa_source_new_data source_new_data; - - pa_assert(m); - - if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments."); - goto fail; - } - - if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { - pa_log("record= and playback= expect boolean argument."); - goto fail; - } - - if (!playback && !record) { - pa_log("Neither playback nor record enabled for device."); - goto fail; - } - - mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); - - ss = m->core->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_OSS) < 0) { - pa_log("Failed to parse sample specification or channel map"); - goto fail; - } - - nfrags = (int) m->core->default_n_fragments; - frag_size = (int) pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); - if (frag_size <= 0) - frag_size = (int) pa_frame_size(&ss); - - if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { - pa_log("Failed to parse fragments arguments"); - goto fail; - } - - if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { - pa_log("Failed to parse mmap argument."); - goto fail; - } - - if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) - goto fail; - - if (use_mmap && (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER))) { - pa_log_info("OSS device not mmap capable, falling back to UNIX read/write mode."); - use_mmap = 0; - } - - if (use_mmap && mode == O_WRONLY) { - pa_log_info("Device opened for playback only, cannot do memory mapping, falling back to UNIX write() mode."); - use_mmap = 0; - } - - if (pa_oss_get_hw_description(dev, hwdesc, sizeof(hwdesc)) >= 0) - pa_log_info("Hardware name is '%s'.", hwdesc); - else - hwdesc[0] = 0; - - pa_log_info("Device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); - - if (nfrags >= 2 && frag_size >= 1) - if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0) - goto fail; - - if (pa_oss_auto_format(fd, &ss) < 0) - goto fail; - - if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { - pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); - goto fail; - } - pa_assert(frag_size > 0); - - u = pa_xnew0(struct userdata, 1); - u->core = m->core; - u->module = m; - m->userdata = u; - u->fd = fd; - u->mixer_fd = -1; - u->use_getospace = u->use_getispace = TRUE; - u->use_getodelay = TRUE; - u->mode = mode; - u->frame_size = pa_frame_size(&ss); - u->device_name = pa_xstrdup(dev); - u->in_nfrags = u->out_nfrags = (uint32_t) (u->nfrags = nfrags); - u->out_fragment_size = u->in_fragment_size = (uint32_t) (u->frag_size = frag_size); - u->use_mmap = use_mmap; - u->rtpoll = pa_rtpoll_new(); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->rtpoll_item = NULL; - build_pollfd(u); - - if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { - pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->in_fragment_size = (uint32_t) info.fragsize; - u->in_nfrags = (uint32_t) info.fragstotal; - u->use_getispace = TRUE; - } - - if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { - pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->out_fragment_size = (uint32_t) info.fragsize; - u->out_nfrags = (uint32_t) info.fragstotal; - u->use_getospace = TRUE; - } - - u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size; - u->out_hwbuf_size = u->out_nfrags * u->out_fragment_size; - - if (mode != O_WRONLY) { - char *name_buf = NULL; - - if (use_mmap) { - if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { - pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); - use_mmap = u->use_mmap = FALSE; - u->in_mmap = NULL; - } else - pa_log_debug("Successfully mmap()ed input buffer."); - } - - if ((name = pa_modargs_get_value(ma, "source_name", NULL))) - namereg_fail = TRUE; - else { - name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); - namereg_fail = FALSE; - } - - pa_source_new_data_init(&source_new_data); - source_new_data.driver = __FILE__; - source_new_data.module = m; - pa_source_new_data_set_name(&source_new_data, name); - source_new_data.namereg_fail = namereg_fail; - pa_source_new_data_set_sample_spec(&source_new_data, &ss); - pa_source_new_data_set_channel_map(&source_new_data, &map); - pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, dev); - pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "oss"); - pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); - pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); - pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->in_hwbuf_size)); - pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->in_fragment_size)); - - u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); - pa_source_new_data_done(&source_new_data); - pa_xfree(name_buf); - - if (!u->source) { - pa_log("Failed to create source object"); - goto fail; - } - - u->source->parent.process_msg = source_process_msg; - u->source->userdata = u; - - pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); - pa_source_set_rtpoll(u->source, u->rtpoll); - u->source->refresh_volume = TRUE; - - if (use_mmap) - u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); - } - - if (mode != O_RDONLY) { - char *name_buf = NULL; - - if (use_mmap) { - if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { - if (mode == O_RDWR) { - pa_log_debug("mmap() failed for input. Changing to O_WRONLY mode."); - mode = O_WRONLY; - goto go_on; - } else { - pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); - u->use_mmap = use_mmap = FALSE; - u->out_mmap = NULL; - } - } else { - pa_log_debug("Successfully mmap()ed output buffer."); - pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); - } - } - - if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = TRUE; - else { - name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); - namereg_fail = FALSE; - } - - pa_sink_new_data_init(&sink_new_data); - sink_new_data.driver = __FILE__; - sink_new_data.module = m; - pa_sink_new_data_set_name(&sink_new_data, name); - sink_new_data.namereg_fail = namereg_fail; - pa_sink_new_data_set_sample_spec(&sink_new_data, &ss); - pa_sink_new_data_set_channel_map(&sink_new_data, &map); - pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, dev); - pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "oss"); - pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); - pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); - pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->out_hwbuf_size)); - pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->out_fragment_size)); - - u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY); - pa_sink_new_data_done(&sink_new_data); - pa_xfree(name_buf); - - if (!u->sink) { - pa_log("Failed to create sink object"); - goto fail; - } - - u->sink->parent.process_msg = sink_process_msg; - u->sink->userdata = u; - - pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - pa_sink_set_rtpoll(u->sink, u->rtpoll); - u->sink->refresh_volume = TRUE; - - u->sink->thread_info.max_request = u->out_hwbuf_size; - - if (use_mmap) - u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); - } - - if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { - pa_bool_t do_close = TRUE; - u->mixer_devmask = 0; - - if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) - pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno)); - - else { - if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) { - pa_log_debug("Found hardware mixer track for playback."); - u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; - u->sink->get_volume = sink_get_volume; - u->sink->set_volume = sink_set_volume; - do_close = FALSE; - } - - if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) { - pa_log_debug("Found hardware mixer track for recording."); - u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; - u->source->get_volume = source_get_volume; - u->source->set_volume = source_set_volume; - do_close = FALSE; - } - } - - if (do_close) { - pa_close(u->mixer_fd); - u->mixer_fd = -1; - } - } - -go_on: - - pa_assert(u->source || u->sink); - - pa_memchunk_reset(&u->memchunk); - - if (!(u->thread = pa_thread_new(thread_func, u))) { - pa_log("Failed to create thread."); - goto fail; - } - - /* Read mixer settings */ - if (u->sink) { - if (sink_new_data.volume_is_set) { - if (u->sink->set_volume) - u->sink->set_volume(u->sink); - } else { - if (u->sink->get_volume) - u->sink->get_volume(u->sink); - } - } - - if (u->source) { - if (source_new_data.volume_is_set) { - if (u->source->set_volume) - u->source->set_volume(u->source); - } else { - if (u->source->get_volume) - u->source->get_volume(u->source); - } - } - - if (u->sink) - pa_sink_put(u->sink); - if (u->source) - pa_source_put(u->source); - - pa_modargs_free(ma); - - return 0; - -fail: - - if (u) - pa__done(m); - else if (fd >= 0) - pa_close(fd); - - if (ma) - pa_modargs_free(ma); - - return -1; -} - -void pa__done(pa_module*m) { - struct userdata *u; - - pa_assert(m); - - if (!(u = m->userdata)) - return; - - if (u->sink) - pa_sink_unlink(u->sink); - - if (u->source) - pa_source_unlink(u->source); - - if (u->thread) { - pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); - pa_thread_free(u->thread); - } - - pa_thread_mq_done(&u->thread_mq); - - if (u->sink) - pa_sink_unref(u->sink); - - if (u->source) - pa_source_unref(u->source); - - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); - - if (u->rtpoll_item) - pa_rtpoll_item_free(u->rtpoll_item); - - if (u->rtpoll) - pa_rtpoll_free(u->rtpoll); - - if (u->out_mmap_memblocks) { - unsigned i; - for (i = 0; i < u->out_nfrags; i++) - if (u->out_mmap_memblocks[i]) - pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); - pa_xfree(u->out_mmap_memblocks); - } - - if (u->in_mmap_memblocks) { - unsigned i; - for (i = 0; i < u->in_nfrags; i++) - if (u->in_mmap_memblocks[i]) - pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); - pa_xfree(u->in_mmap_memblocks); - } - - if (u->in_mmap && u->in_mmap != MAP_FAILED) - munmap(u->in_mmap, u->in_hwbuf_size); - - if (u->out_mmap && u->out_mmap != MAP_FAILED) - munmap(u->out_mmap, u->out_hwbuf_size); - - if (u->fd >= 0) - pa_close(u->fd); - - if (u->mixer_fd >= 0) - pa_close(u->mixer_fd); - - pa_xfree(u->device_name); - - pa_xfree(u); -} diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c deleted file mode 100644 index f766030d..00000000 --- a/src/modules/oss-util.c +++ /dev/null @@ -1,417 +0,0 @@ -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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 -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "oss-util.h" - -int pa_oss_open(const char *device, int *mode, int* pcaps) { - int fd = -1; - int caps; - - pa_assert(device); - pa_assert(mode); - pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); - - if(!pcaps) - pcaps = ∩︀ - - if (*mode == O_RDWR) { - if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { - ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); - - if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { - pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); - goto fail; - } - - if (*pcaps & DSP_CAP_DUPLEX) - goto success; - - pa_log_warn("'%s' doesn't support full duplex", device); - - pa_close(fd); - } - - if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { - if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { - pa_log("open('%s'): %s", device, pa_cstrerror(errno)); - goto fail; - } - } - } else { - if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { - pa_log("open('%s'): %s", device, pa_cstrerror(errno)); - goto fail; - } - } - - *pcaps = 0; - - if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { - pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); - goto fail; - } - -success: - - pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s", - *pcaps & DSP_CAP_BATCH ? " BATCH" : "", -#ifdef DSP_CAP_BIND - *pcaps & DSP_CAP_BIND ? " BIND" : "", -#else - "", -#endif - *pcaps & DSP_CAP_COPROC ? " COPROC" : "", - *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "", -#ifdef DSP_CAP_FREERATE - *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "", -#else - "", -#endif -#ifdef DSP_CAP_INPUT - *pcaps & DSP_CAP_INPUT ? " INPUT" : "", -#else - "", -#endif - *pcaps & DSP_CAP_MMAP ? " MMAP" : "", -#ifdef DSP_CAP_MODEM - *pcaps & DSP_CAP_MODEM ? " MODEM" : "", -#else - "", -#endif -#ifdef DSP_CAP_MULTI - *pcaps & DSP_CAP_MULTI ? " MULTI" : "", -#else - "", -#endif -#ifdef DSP_CAP_OUTPUT - *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", -#else - "", -#endif - *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "", -#ifdef DSP_CAP_SHADOW - *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "", -#else - "", -#endif -#ifdef DSP_CAP_VIRTUAL - *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "", -#else - "", -#endif - *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); - - pa_make_fd_cloexec(fd); - - return fd; - -fail: - if (fd >= 0) - pa_close(fd); - return -1; -} - -int pa_oss_auto_format(int fd, pa_sample_spec *ss) { - int format, channels, speed, reqformat; - pa_sample_format_t orig_format; - - static const int format_trans[PA_SAMPLE_MAX] = { - [PA_SAMPLE_U8] = AFMT_U8, - [PA_SAMPLE_ALAW] = AFMT_A_LAW, - [PA_SAMPLE_ULAW] = AFMT_MU_LAW, - [PA_SAMPLE_S16LE] = AFMT_S16_LE, - [PA_SAMPLE_S16BE] = AFMT_S16_BE, - [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */ - [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ - [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */ - [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */ - }; - - pa_assert(fd >= 0); - pa_assert(ss); - - orig_format = ss->format; - - reqformat = format = format_trans[ss->format]; - if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) { - format = AFMT_S16_NE; - if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) { - int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE; - format = f; - if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) { - format = AFMT_U8; - if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) { - pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno)); - return -1; - } else - ss->format = PA_SAMPLE_U8; - } else - ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; - } else - ss->format = PA_SAMPLE_S16NE; - } - - if (orig_format != ss->format) - pa_log_warn("device doesn't support sample format %s, changed to %s.", - pa_sample_format_to_string(orig_format), - pa_sample_format_to_string(ss->format)); - - channels = ss->channels; - if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { - pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno)); - return -1; - } - pa_assert(channels > 0); - - if (ss->channels != channels) { - pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); - ss->channels = (uint8_t) channels; - } - - speed = (int) ss->rate; - if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { - pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); - return -1; - } - pa_assert(speed > 0); - - if (ss->rate != (unsigned) speed) { - pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed); - - /* If the sample rate deviates too much, we need to resample */ - if (speed < ss->rate*.95 || speed > ss->rate*1.05) - ss->rate = (uint32_t) speed; - } - - return 0; -} - -static int simple_log2(int v) { - int k = 0; - - for (;;) { - v >>= 1; - if (!v) break; - k++; - } - - return k; -} - -int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { - int arg; - arg = ((int) nfrags << 16) | simple_log2(frag_size); - - if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) { - pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno)); - return -1; - } - - return 0; -} - -int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) { - char cv[PA_CVOLUME_SNPRINT_MAX]; - unsigned vol; - - pa_assert(fd >= 0); - pa_assert(ss); - pa_assert(volume); - - if (ioctl(fd, mixer, &vol) < 0) - return -1; - - pa_cvolume_reset(volume, ss->channels); - - volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100; - - if (volume->channels >= 2) - volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100; - - pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); - return 0; -} - -int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { - char cv[PA_CVOLUME_SNPRINT_MAX]; - unsigned vol; - pa_volume_t l, r; - - l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; - - vol = (l*100)/PA_VOLUME_NORM; - - if (ss->channels >= 2) { - r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; - vol |= ((r*100)/PA_VOLUME_NORM) << 8; - } - - if (ioctl(fd, mixer, &vol) < 0) - return -1; - - pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); - return 0; -} - -static int get_device_number(const char *dev) { - const char *p, *e; - char *rp = NULL; - int r; - - if (!(p = rp = pa_readlink(dev))) { - if (errno != EINVAL && errno != ENOLINK) { - r = -1; - goto finish; - } - - p = dev; - } - - if ((e = strrchr(p, '/'))) - p = e+1; - - if (p == 0) { - r = 0; - goto finish; - } - - p = strchr(p, 0) -1; - - if (*p >= '0' && *p <= '9') { - r = *p - '0'; - goto finish; - } - - r = -1; - -finish: - pa_xfree(rp); - return r; -} - -int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { - FILE *f; - int n, r = -1; - int b = 0; - - if ((n = get_device_number(dev)) < 0) - return -1; - - if (!(f = fopen("/dev/sndstat", "r")) && - !(f = fopen("/proc/sndstat", "r")) && - !(f = fopen("/proc/asound/oss/sndstat", "r"))) { - - if (errno != ENOENT) - pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); - - return -1; - } - - while (!feof(f)) { - char line[64]; - int device; - - if (!fgets(line, sizeof(line), f)) - break; - - line[strcspn(line, "\r\n")] = 0; - - if (!b) { - b = strcmp(line, "Audio devices:") == 0; - continue; - } - - if (line[0] == 0) - break; - - if (sscanf(line, "%i: ", &device) != 1) - continue; - - if (device == n) { - char *k = strchr(line, ':'); - pa_assert(k); - k++; - k += strspn(k, " "); - - if (pa_endswith(k, " (DUPLEX)")) - k[strlen(k)-9] = 0; - - pa_strlcpy(name, k, l); - r = 0; - break; - } - } - - fclose(f); - return r; -} - -static int open_mixer(const char *mixer) { - int fd; - - if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) - return fd; - - return -1; -} - -int pa_oss_open_mixer_for_device(const char *device) { - int n; - char *fn; - int fd; - - if ((n = get_device_number(device)) < 0) - return -1; - - if (n == 0) - if ((fd = open_mixer("/dev/mixer")) >= 0) - return fd; - - fn = pa_sprintf_malloc("/dev/mixer%i", n); - fd = open_mixer(fn); - pa_xfree(fn); - - if (fd < 0) - pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno)); - - return fd; -} diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h deleted file mode 100644 index 654f7bba..00000000 --- a/src/modules/oss-util.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef fooossutilhfoo -#define fooossutilhfoo - -/*** - This file is part of PulseAudio. - - Copyright 2004-2006 Lennart Poettering - Copyright 2006 Pierre Ossman 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 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. -***/ - -#include -#include - -int pa_oss_open(const char *device, int *mode, int* pcaps); -int pa_oss_auto_format(int fd, pa_sample_spec *ss); - -int pa_oss_set_fragments(int fd, int frags, int frag_size); - -int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); -int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume); - -int pa_oss_get_hw_description(const char *dev, char *name, size_t l); - -int pa_oss_open_mixer_for_device(const char *device); - -#endif diff --git a/src/modules/oss/module-oss.c b/src/modules/oss/module-oss.c new file mode 100644 index 00000000..23a32549 --- /dev/null +++ b/src/modules/oss/module-oss.c @@ -0,0 +1,1536 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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. +***/ + +/* General power management rules: + * + * When SUSPENDED we close the audio device. + * + * We make no difference between IDLE and RUNNING in our handling. + * + * As long as we are in RUNNING/IDLE state we will *always* write data to + * the device. If none is avilable from the inputs, we write silence + * instead. + * + * If power should be saved on IDLE module-suspend-on-idle should be used. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_SYS_MMAN_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "oss-util.h" +#include "module-oss-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("OSS Sink/Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name= " + "source_name= " + "device= " + "record= " + "playback= " + "format= " + "channels= " + "rate= " + "fragments= " + "fragment_size= " + "channel_map= " + "mmap="); + +#define DEFAULT_DEVICE "/dev/dsp" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *device_name; + + pa_memchunk memchunk; + + size_t frame_size; + uint32_t in_fragment_size, out_fragment_size, in_nfrags, out_nfrags, in_hwbuf_size, out_hwbuf_size; + pa_bool_t use_getospace, use_getispace; + pa_bool_t use_getodelay; + + pa_bool_t sink_suspended, source_suspended; + + int fd; + int mode; + + int mixer_fd; + int mixer_devmask; + + int nfrags, frag_size; + + pa_bool_t use_mmap; + unsigned out_mmap_current, in_mmap_current; + void *in_mmap, *out_mmap; + pa_memblock **in_mmap_memblocks, **out_mmap_memblocks; + + int in_mmap_saved_nfrags, out_mmap_saved_nfrags; + + pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "source_name", + "device", + "record", + "playback", + "fragments", + "fragment_size", + "format", + "rate", + "channels", + "channel_map", + "mmap", + NULL +}; + +static void trigger(struct userdata *u, pa_bool_t quick) { + int enable_bits = 0, zero = 0; + + pa_assert(u); + + if (u->fd < 0) + return; + + pa_log_debug("trigger"); + + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) + enable_bits |= PCM_ENABLE_INPUT; + + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) + enable_bits |= PCM_ENABLE_OUTPUT; + + pa_log_debug("trigger: %i", enable_bits); + + + if (u->use_mmap) { + + if (!quick) + ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero); + +#ifdef SNDCTL_DSP_HALT + if (enable_bits == 0) + if (ioctl(u->fd, SNDCTL_DSP_HALT, NULL) < 0) + pa_log_warn("SNDCTL_DSP_HALT: %s", pa_cstrerror(errno)); +#endif + + if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) + pa_log_warn("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); + + if (u->sink && !(enable_bits & PCM_ENABLE_OUTPUT)) { + pa_log_debug("clearing playback buffer"); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &u->sink->sample_spec); + } + + } else { + + if (enable_bits) + if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0) + pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno)); + + if (!quick) { + /* + * Some crappy drivers do not start the recording until we + * read something. Without this snippet, poll will never + * register the fd as ready. + */ + + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size); + pa_read(u->fd, buf, u->in_fragment_size, NULL); + pa_xfree(buf); + } + } + } +} + +static void mmap_fill_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->out_mmap_memblocks); + +/* pa_log("Mmmap writing %u blocks", n); */ + + while (n > 0) { + pa_memchunk chunk; + + if (u->out_mmap_memblocks[u->out_mmap_current]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[u->out_mmap_current]); + + chunk.memblock = u->out_mmap_memblocks[u->out_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->out_mmap + u->out_fragment_size * u->out_mmap_current, + u->out_fragment_size, + 1); + + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_sink_render_into_full(u->sink, &chunk); + + u->out_mmap_current++; + while (u->out_mmap_current >= u->out_nfrags) + u->out_mmap_current -= u->out_nfrags; + + n--; + } +} + +static int mmap_write(struct userdata *u) { + struct count_info info; + + pa_assert(u); + pa_assert(u->sink); + +/* pa_log("Mmmap writing..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return -1; + } + + info.blocks += u->out_mmap_saved_nfrags; + u->out_mmap_saved_nfrags = 0; + + if (info.blocks > 0) + mmap_fill_memblocks(u, (unsigned) info.blocks); + + return info.blocks; +} + +static void mmap_post_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->in_mmap_memblocks); + +/* pa_log("Mmmap reading %u blocks", n); */ + + while (n > 0) { + pa_memchunk chunk; + + if (!u->in_mmap_memblocks[u->in_mmap_current]) { + + chunk.memblock = u->in_mmap_memblocks[u->in_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->in_mmap + u->in_fragment_size*u->in_mmap_current, + u->in_fragment_size, + 1); + + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + } + + u->in_mmap_current++; + while (u->in_mmap_current >= u->in_nfrags) + u->in_mmap_current -= u->in_nfrags; + + n--; + } +} + +static void mmap_clear_memblocks(struct userdata*u, unsigned n) { + unsigned i = u->in_mmap_current; + + pa_assert(u); + pa_assert(u->in_mmap_memblocks); + + if (n > u->in_nfrags) + n = u->in_nfrags; + + while (n > 0) { + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; + } + + i++; + while (i >= u->in_nfrags) + i -= u->in_nfrags; + + n--; + } +} + +static int mmap_read(struct userdata *u) { + struct count_info info; + pa_assert(u); + pa_assert(u->source); + +/* pa_log("Mmmap reading..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return -1; + } + +/* pa_log("... %i", info.blocks); */ + + info.blocks += u->in_mmap_saved_nfrags; + u->in_mmap_saved_nfrags = 0; + + if (info.blocks > 0) { + mmap_post_memblocks(u, (unsigned) info.blocks); + mmap_clear_memblocks(u, u->in_nfrags/2); + } + + return info.blocks; +} + +static pa_usec_t mmap_sink_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->out_mmap_saved_nfrags += info.blocks; + + bpos = ((u->out_mmap_current + (unsigned) u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = u->out_hwbuf_size - ((size_t) info.ptr - bpos); + else + n = bpos - (size_t) info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ + + return pa_bytes_to_usec(n, &u->sink->sample_spec); +} + +static pa_usec_t mmap_source_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->in_mmap_saved_nfrags += info.blocks; + bpos = ((u->in_mmap_current + (unsigned) u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = (size_t) info.ptr - bpos; + else + n = u->in_hwbuf_size - bpos + (size_t) info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ + + return pa_bytes_to_usec(n, &u->source->sample_spec); +} + +static pa_usec_t io_sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + + pa_assert(u); + + if (u->use_getodelay) { + int arg; + + if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); + u->use_getodelay = 0; + } else + r = pa_bytes_to_usec((size_t) arg, &u->sink->sample_spec); + + } + + if (!u->use_getodelay && u->use_getospace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = 0; + } else + r = pa_bytes_to_usec((size_t) info.bytes, &u->sink->sample_spec); + } + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + + return r; +} + + +static pa_usec_t io_source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + + pa_assert(u); + + if (u->use_getispace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = 0; + } else + r = pa_bytes_to_usec((size_t) info.bytes, &u->source->sample_spec); + } + + return r; +} + +static void build_pollfd(struct userdata *u) { + struct pollfd *pollfd; + + pa_assert(u); + pa_assert(u->fd >= 0); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = 0; + pollfd->revents = 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->fd >= 0); + + pa_log_info("Suspending..."); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + u->out_mmap_memblocks[i] = NULL; + } + } + + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; + } + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + if (u->out_mmap && u->out_mmap != MAP_FAILED) { + munmap(u->out_mmap, u->out_hwbuf_size); + u->out_mmap = NULL; + } + + /* Let's suspend */ + ioctl(u->fd, SNDCTL_DSP_SYNC, NULL); + pa_close(u->fd); + u->fd = -1; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int sink_get_volume(pa_sink *s); +static int source_get_volume(pa_source *s); + +static int unsuspend(struct userdata *u) { + int m; + pa_sample_spec ss, *ss_original; + int frag_size, in_frag_size, out_frag_size; + int in_nfrags, out_nfrags; + struct audio_buf_info info; + + pa_assert(u); + pa_assert(u->fd < 0); + + m = u->mode; + + pa_log_info("Trying resume..."); + + if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) { + pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno)); + return -1; + } + + if (m != u->mode) { + pa_log_warn("Resume failed, couldn't open device with original access mode."); + goto fail; + } + + if (u->nfrags >= 2 && u->frag_size >= 1) + if (pa_oss_set_fragments(u->fd, u->nfrags, u->frag_size) < 0) { + pa_log_warn("Resume failed, couldn't set original fragment settings."); + goto fail; + } + + ss = *(ss_original = u->sink ? &u->sink->sample_spec : &u->source->sample_spec); + if (pa_oss_auto_format(u->fd, &ss) < 0 || !pa_sample_spec_equal(&ss, ss_original)) { + pa_log_warn("Resume failed, couldn't set original sample format settings."); + goto fail; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { + pa_log_warn("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); + goto fail; + } + + in_frag_size = out_frag_size = frag_size; + in_nfrags = out_nfrags = u->nfrags; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { + in_frag_size = info.fragsize; + in_nfrags = info.fragstotal; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { + out_frag_size = info.fragsize; + out_nfrags = info.fragstotal; + } + + if ((u->source && (in_frag_size != (int) u->in_fragment_size || in_nfrags != (int) u->in_nfrags)) || + (u->sink && (out_frag_size != (int) u->out_fragment_size || out_nfrags != (int) u->out_nfrags))) { + pa_log_warn("Resume failed, input fragment settings don't match."); + goto fail; + } + + if (u->use_mmap) { + if (u->source) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + if (u->sink) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + goto fail; + } + + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } + } + + u->out_mmap_current = u->in_mmap_current = 0; + u->out_mmap_saved_nfrags = u->in_mmap_saved_nfrags = 0; + + pa_assert(!u->rtpoll_item); + + build_pollfd(u); + + if (u->sink) + sink_get_volume(u->sink); + if (u->source) + source_get_volume(u->source); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + pa_close(u->fd); + u->fd = -1; + return -1; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + int ret; + pa_bool_t do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_sink_get_latency(u); + else + r = io_sink_get_latency(u); + } + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + + if (!u->source || u->source_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->sink_suspended = TRUE; + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + do_trigger = TRUE; + quick = u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state); + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + + if (!u->source || u->source_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->out_mmap_current = 0; + u->out_mmap_saved_nfrags = 0; + + u->sink_suspended = FALSE; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + + } + + ret = pa_sink_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + int ret; + int do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_source_get_latency(u); + else + r = io_source_get_latency(u); + } + + *((pa_usec_t*) data) = r; + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); + + if (!u->sink || u->sink_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->source_suspended = TRUE; + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + do_trigger = TRUE; + quick = u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + + if (!u->sink || u->sink_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->in_mmap_current = 0; + u->in_mmap_saved_nfrags = 0; + + u->source_suspended = FALSE; + } + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + + } + break; + + } + + ret = pa_source_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; +} + +static int sink_get_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int sink_set_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int source_get_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int source_set_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0, read_type = 0; + short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + +/* pa_log("loop"); */ + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + /* Render some data and write it to the dsp */ + + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { + + if (u->use_mmap) { + + if ((ret = mmap_write(u)) < 0) + goto fail; + + revents &= ~POLLOUT; + + if (ret > 0) + continue; + + } else { + ssize_t l; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = (ssize_t) u->out_fragment_size; + + if (u->use_getospace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = FALSE; + } else { + l = info.bytes; + + /* We loop only if GETOSPACE worked and we + * actually *know* that we can write more than + * one fragment at a time */ + loop = TRUE; + } + } + + /* Round down to multiples of the fragment size, + * because OSS needs that (at least some versions + * do) */ + l = (l/(ssize_t) u->out_fragment_size) * (ssize_t) u->out_fragment_size; + + /* Hmm, so poll() signalled us that we can read + * something, but GETOSPACE told us there was nothing? + * Hmm, make the best of it, try to read some data, to + * avoid spinning forever. */ + if (l <= 0 && (revents & POLLOUT)) { + l = (ssize_t) u->out_fragment_size; + loop = FALSE; + } + + while (l > 0) { + void *p; + ssize_t t; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, (size_t) l, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + t = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + +/* pa_log("wrote %i bytes of %u", t, l); */ + + pa_assert(t != 0); + + if (t < 0) { + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLOUT; + break; + + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += (size_t) t; + u->memchunk.length -= (size_t) t; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + l -= t; + + revents &= ~POLLOUT; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + + /* Try to read some data and pass it on to the source driver. */ + + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { + + if (u->use_mmap) { + + if ((ret = mmap_read(u)) < 0) + goto fail; + + revents &= ~POLLIN; + + if (ret > 0) + continue; + + } else { + + void *p; + ssize_t l; + pa_memchunk memchunk; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = (ssize_t) u->in_fragment_size; + + if (u->use_getispace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = FALSE; + } else { + l = info.bytes; + loop = TRUE; + } + } + + l = (l/(ssize_t) u->in_fragment_size) * (ssize_t) u->in_fragment_size; + + if (l <= 0 && (revents & POLLIN)) { + l = (ssize_t) u->in_fragment_size; + loop = FALSE; + } + + while (l > 0) { + ssize_t t; + size_t k; + + pa_assert(l > 0); + + memchunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + k = pa_memblock_get_length(memchunk.memblock); + + if (k > (size_t) l) + k = (size_t) l; + + k = (k/u->frame_size)*u->frame_size; + + p = pa_memblock_acquire(memchunk.memblock); + t = pa_read(u->fd, p, k, &read_type); + pa_memblock_release(memchunk.memblock); + + pa_assert(t != 0); /* EOF cannot happen */ + +/* pa_log("read %i bytes of %u", t, l); */ + + if (t < 0) { + pa_memblock_unref(memchunk.memblock); + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLIN; + break; + + } else { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + memchunk.index = 0; + memchunk.length = (size_t) t; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + + l -= t; + + revents &= ~POLLIN; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + +/* pa_log("loop2 revents=%i", revents); */ + + if (u->rtpoll_item) { + struct pollfd *pollfd; + + pa_assert(u->fd >= 0); + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = (short) + (((u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | + ((u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0)); + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (u->rtpoll_item) { + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~(POLLOUT|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } + + revents = pollfd->revents; + } else + revents = 0; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + struct audio_buf_info info; + struct userdata *u = NULL; + const char *dev; + int fd = -1; + int nfrags, frag_size; + int mode, caps; + pa_bool_t record = TRUE, playback = TRUE, use_mmap = TRUE; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + char hwdesc[64]; + const char *name; + pa_bool_t namereg_fail; + pa_sink_new_data sink_new_data; + pa_source_new_data source_new_data; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { + pa_log("record= and playback= expect boolean argument."); + goto fail; + } + + if (!playback && !record) { + pa_log("Neither playback nor record enabled for device."); + goto fail; + } + + mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_OSS) < 0) { + pa_log("Failed to parse sample specification or channel map"); + goto fail; + } + + nfrags = (int) m->core->default_n_fragments; + frag_size = (int) pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = (int) pa_frame_size(&ss); + + if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse fragments arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) + goto fail; + + if (use_mmap && (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER))) { + pa_log_info("OSS device not mmap capable, falling back to UNIX read/write mode."); + use_mmap = 0; + } + + if (use_mmap && mode == O_WRONLY) { + pa_log_info("Device opened for playback only, cannot do memory mapping, falling back to UNIX write() mode."); + use_mmap = 0; + } + + if (pa_oss_get_hw_description(dev, hwdesc, sizeof(hwdesc)) >= 0) + pa_log_info("Hardware name is '%s'.", hwdesc); + else + hwdesc[0] = 0; + + pa_log_info("Device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); + + if (nfrags >= 2 && frag_size >= 1) + if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0) + goto fail; + + if (pa_oss_auto_format(fd, &ss) < 0) + goto fail; + + if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { + pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); + goto fail; + } + pa_assert(frag_size > 0); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->fd = fd; + u->mixer_fd = -1; + u->use_getospace = u->use_getispace = TRUE; + u->use_getodelay = TRUE; + u->mode = mode; + u->frame_size = pa_frame_size(&ss); + u->device_name = pa_xstrdup(dev); + u->in_nfrags = u->out_nfrags = (uint32_t) (u->nfrags = nfrags); + u->out_fragment_size = u->in_fragment_size = (uint32_t) (u->frag_size = frag_size); + u->use_mmap = use_mmap; + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->rtpoll_item = NULL; + build_pollfd(u); + + if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { + pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->in_fragment_size = (uint32_t) info.fragsize; + u->in_nfrags = (uint32_t) info.fragstotal; + u->use_getispace = TRUE; + } + + if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { + pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->out_fragment_size = (uint32_t) info.fragsize; + u->out_nfrags = (uint32_t) info.fragstotal; + u->use_getospace = TRUE; + } + + u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size; + u->out_hwbuf_size = u->out_nfrags * u->out_fragment_size; + + if (mode != O_WRONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + use_mmap = u->use_mmap = FALSE; + u->in_mmap = NULL; + } else + pa_log_debug("Successfully mmap()ed input buffer."); + } + + if ((name = pa_modargs_get_value(ma, "source_name", NULL))) + namereg_fail = TRUE; + else { + name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); + namereg_fail = FALSE; + } + + pa_source_new_data_init(&source_new_data); + source_new_data.driver = __FILE__; + source_new_data.module = m; + pa_source_new_data_set_name(&source_new_data, name); + source_new_data.namereg_fail = namereg_fail; + pa_source_new_data_set_sample_spec(&source_new_data, &ss); + pa_source_new_data_set_channel_map(&source_new_data, &map); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_STRING, dev); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_API, "oss"); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); + pa_proplist_sets(source_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); + pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->in_hwbuf_size)); + pa_proplist_setf(source_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->in_fragment_size)); + + u->source = pa_source_new(m->core, &source_new_data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY); + pa_source_new_data_done(&source_new_data); + pa_xfree(name_buf); + + if (!u->source) { + pa_log("Failed to create source object"); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->userdata = u; + + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + u->source->refresh_volume = TRUE; + + if (use_mmap) + u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); + } + + if (mode != O_RDONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + if (mode == O_RDWR) { + pa_log_debug("mmap() failed for input. Changing to O_WRONLY mode."); + mode = O_WRONLY; + goto go_on; + } else { + pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + u->use_mmap = use_mmap = FALSE; + u->out_mmap = NULL; + } + } else { + pa_log_debug("Successfully mmap()ed output buffer."); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } + } + + if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) + namereg_fail = TRUE; + else { + name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); + namereg_fail = FALSE; + } + + pa_sink_new_data_init(&sink_new_data); + sink_new_data.driver = __FILE__; + sink_new_data.module = m; + pa_sink_new_data_set_name(&sink_new_data, name); + sink_new_data.namereg_fail = namereg_fail; + pa_sink_new_data_set_sample_spec(&sink_new_data, &ss); + pa_sink_new_data_set_channel_map(&sink_new_data, &map); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_STRING, dev); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_API, "oss"); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, hwdesc[0] ? hwdesc : dev); + pa_proplist_sets(sink_new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, use_mmap ? "mmap" : "serial"); + pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (u->out_hwbuf_size)); + pa_proplist_setf(sink_new_data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (u->out_fragment_size)); + + u->sink = pa_sink_new(m->core, &sink_new_data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_new_data); + pa_xfree(name_buf); + + if (!u->sink) { + pa_log("Failed to create sink object"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + u->sink->refresh_volume = TRUE; + + u->sink->thread_info.max_request = u->out_hwbuf_size; + + if (use_mmap) + u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); + } + + if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { + pa_bool_t do_close = TRUE; + u->mixer_devmask = 0; + + if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) + pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno)); + + else { + if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) { + pa_log_debug("Found hardware mixer track for playback."); + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + u->sink->get_volume = sink_get_volume; + u->sink->set_volume = sink_set_volume; + do_close = FALSE; + } + + if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) { + pa_log_debug("Found hardware mixer track for recording."); + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + u->source->get_volume = source_get_volume; + u->source->set_volume = source_set_volume; + do_close = FALSE; + } + } + + if (do_close) { + pa_close(u->mixer_fd); + u->mixer_fd = -1; + } + } + +go_on: + + pa_assert(u->source || u->sink); + + pa_memchunk_reset(&u->memchunk); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Read mixer settings */ + if (u->sink) { + if (sink_new_data.volume_is_set) { + if (u->sink->set_volume) + u->sink->set_volume(u->sink); + } else { + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + } + } + + if (u->source) { + if (source_new_data.volume_is_set) { + if (u->source->set_volume) + u->source->set_volume(u->source); + } else { + if (u->source->get_volume) + u->source->get_volume(u->source); + } + } + + if (u->sink) + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (u) + pa__done(m); + else if (fd >= 0) + pa_close(fd); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->source) + pa_source_unlink(u->source); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + pa_xfree(u->out_mmap_memblocks); + } + + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + pa_xfree(u->in_mmap_memblocks); + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) + munmap(u->in_mmap, u->in_hwbuf_size); + + if (u->out_mmap && u->out_mmap != MAP_FAILED) + munmap(u->out_mmap, u->out_hwbuf_size); + + if (u->fd >= 0) + pa_close(u->fd); + + if (u->mixer_fd >= 0) + pa_close(u->mixer_fd); + + pa_xfree(u->device_name); + + pa_xfree(u); +} diff --git a/src/modules/oss/oss-util.c b/src/modules/oss/oss-util.c new file mode 100644 index 00000000..f766030d --- /dev/null +++ b/src/modules/oss/oss-util.c @@ -0,0 +1,417 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "oss-util.h" + +int pa_oss_open(const char *device, int *mode, int* pcaps) { + int fd = -1; + int caps; + + pa_assert(device); + pa_assert(mode); + pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); + + if(!pcaps) + pcaps = ∩︀ + + if (*mode == O_RDWR) { + if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { + ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); + + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { + pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); + goto fail; + } + + if (*pcaps & DSP_CAP_DUPLEX) + goto success; + + pa_log_warn("'%s' doesn't support full duplex", device); + + pa_close(fd); + } + + if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", device, pa_cstrerror(errno)); + goto fail; + } + } + } else { + if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", device, pa_cstrerror(errno)); + goto fail; + } + } + + *pcaps = 0; + + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { + pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); + goto fail; + } + +success: + + pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + *pcaps & DSP_CAP_BATCH ? " BATCH" : "", +#ifdef DSP_CAP_BIND + *pcaps & DSP_CAP_BIND ? " BIND" : "", +#else + "", +#endif + *pcaps & DSP_CAP_COPROC ? " COPROC" : "", + *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "", +#ifdef DSP_CAP_FREERATE + *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "", +#else + "", +#endif +#ifdef DSP_CAP_INPUT + *pcaps & DSP_CAP_INPUT ? " INPUT" : "", +#else + "", +#endif + *pcaps & DSP_CAP_MMAP ? " MMAP" : "", +#ifdef DSP_CAP_MODEM + *pcaps & DSP_CAP_MODEM ? " MODEM" : "", +#else + "", +#endif +#ifdef DSP_CAP_MULTI + *pcaps & DSP_CAP_MULTI ? " MULTI" : "", +#else + "", +#endif +#ifdef DSP_CAP_OUTPUT + *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", +#else + "", +#endif + *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "", +#ifdef DSP_CAP_SHADOW + *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "", +#else + "", +#endif +#ifdef DSP_CAP_VIRTUAL + *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "", +#else + "", +#endif + *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); + + pa_make_fd_cloexec(fd); + + return fd; + +fail: + if (fd >= 0) + pa_close(fd); + return -1; +} + +int pa_oss_auto_format(int fd, pa_sample_spec *ss) { + int format, channels, speed, reqformat; + pa_sample_format_t orig_format; + + static const int format_trans[PA_SAMPLE_MAX] = { + [PA_SAMPLE_U8] = AFMT_U8, + [PA_SAMPLE_ALAW] = AFMT_A_LAW, + [PA_SAMPLE_ULAW] = AFMT_MU_LAW, + [PA_SAMPLE_S16LE] = AFMT_S16_LE, + [PA_SAMPLE_S16BE] = AFMT_S16_BE, + [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */ + }; + + pa_assert(fd >= 0); + pa_assert(ss); + + orig_format = ss->format; + + reqformat = format = format_trans[ss->format]; + if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) { + format = AFMT_S16_NE; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) { + int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE; + format = f; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) { + format = AFMT_U8; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) { + pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno)); + return -1; + } else + ss->format = PA_SAMPLE_U8; + } else + ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; + } else + ss->format = PA_SAMPLE_S16NE; + } + + if (orig_format != ss->format) + pa_log_warn("device doesn't support sample format %s, changed to %s.", + pa_sample_format_to_string(orig_format), + pa_sample_format_to_string(ss->format)); + + channels = ss->channels; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { + pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno)); + return -1; + } + pa_assert(channels > 0); + + if (ss->channels != channels) { + pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); + ss->channels = (uint8_t) channels; + } + + speed = (int) ss->rate; + if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { + pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); + return -1; + } + pa_assert(speed > 0); + + if (ss->rate != (unsigned) speed) { + pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed); + + /* If the sample rate deviates too much, we need to resample */ + if (speed < ss->rate*.95 || speed > ss->rate*1.05) + ss->rate = (uint32_t) speed; + } + + return 0; +} + +static int simple_log2(int v) { + int k = 0; + + for (;;) { + v >>= 1; + if (!v) break; + k++; + } + + return k; +} + +int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { + int arg; + arg = ((int) nfrags << 16) | simple_log2(frag_size); + + if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) { + pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume) { + char cv[PA_CVOLUME_SNPRINT_MAX]; + unsigned vol; + + pa_assert(fd >= 0); + pa_assert(ss); + pa_assert(volume); + + if (ioctl(fd, mixer, &vol) < 0) + return -1; + + pa_cvolume_reset(volume, ss->channels); + + volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100; + + if (volume->channels >= 2) + volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100; + + pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); + return 0; +} + +int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { + char cv[PA_CVOLUME_SNPRINT_MAX]; + unsigned vol; + pa_volume_t l, r; + + l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; + + vol = (l*100)/PA_VOLUME_NORM; + + if (ss->channels >= 2) { + r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; + vol |= ((r*100)/PA_VOLUME_NORM) << 8; + } + + if (ioctl(fd, mixer, &vol) < 0) + return -1; + + pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); + return 0; +} + +static int get_device_number(const char *dev) { + const char *p, *e; + char *rp = NULL; + int r; + + if (!(p = rp = pa_readlink(dev))) { + if (errno != EINVAL && errno != ENOLINK) { + r = -1; + goto finish; + } + + p = dev; + } + + if ((e = strrchr(p, '/'))) + p = e+1; + + if (p == 0) { + r = 0; + goto finish; + } + + p = strchr(p, 0) -1; + + if (*p >= '0' && *p <= '9') { + r = *p - '0'; + goto finish; + } + + r = -1; + +finish: + pa_xfree(rp); + return r; +} + +int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { + FILE *f; + int n, r = -1; + int b = 0; + + if ((n = get_device_number(dev)) < 0) + return -1; + + if (!(f = fopen("/dev/sndstat", "r")) && + !(f = fopen("/proc/sndstat", "r")) && + !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + + if (errno != ENOENT) + pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); + + return -1; + } + + while (!feof(f)) { + char line[64]; + int device; + + if (!fgets(line, sizeof(line), f)) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (!b) { + b = strcmp(line, "Audio devices:") == 0; + continue; + } + + if (line[0] == 0) + break; + + if (sscanf(line, "%i: ", &device) != 1) + continue; + + if (device == n) { + char *k = strchr(line, ':'); + pa_assert(k); + k++; + k += strspn(k, " "); + + if (pa_endswith(k, " (DUPLEX)")) + k[strlen(k)-9] = 0; + + pa_strlcpy(name, k, l); + r = 0; + break; + } + } + + fclose(f); + return r; +} + +static int open_mixer(const char *mixer) { + int fd; + + if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) + return fd; + + return -1; +} + +int pa_oss_open_mixer_for_device(const char *device) { + int n; + char *fn; + int fd; + + if ((n = get_device_number(device)) < 0) + return -1; + + if (n == 0) + if ((fd = open_mixer("/dev/mixer")) >= 0) + return fd; + + fn = pa_sprintf_malloc("/dev/mixer%i", n); + fd = open_mixer(fn); + pa_xfree(fn); + + if (fd < 0) + pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno)); + + return fd; +} diff --git a/src/modules/oss/oss-util.h b/src/modules/oss/oss-util.h new file mode 100644 index 00000000..654f7bba --- /dev/null +++ b/src/modules/oss/oss-util.h @@ -0,0 +1,41 @@ +#ifndef fooossutilhfoo +#define fooossutilhfoo + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman 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 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. +***/ + +#include +#include + +int pa_oss_open(const char *device, int *mode, int* pcaps); +int pa_oss_auto_format(int fd, pa_sample_spec *ss); + +int pa_oss_set_fragments(int fd, int frags, int frag_size); + +int pa_oss_set_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_get_volume(int fd, unsigned long mixer, const pa_sample_spec *ss, pa_cvolume *volume); + +int pa_oss_get_hw_description(const char *dev, char *name, size_t l); + +int pa_oss_open_mixer_for_device(const char *device); + +#endif -- cgit