diff options
Diffstat (limited to 'src/modules')
58 files changed, 5155 insertions, 1766 deletions
diff --git a/src/modules/.gitignore b/src/modules/.gitignore new file mode 100644 index 00000000..2d2d942d --- /dev/null +++ b/src/modules/.gitignore @@ -0,0 +1 @@ +module-*-symdef.h diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index 6afec3bc..5d52cbc9 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -27,6 +25,7 @@ #endif #include <sys/types.h> +#include <limits.h> #include <asoundlib.h> #include <pulse/sample.h> @@ -35,6 +34,7 @@ #include <pulsecore/log.h> #include <pulsecore/macro.h> #include <pulsecore/core-util.h> +#include <pulsecore/atomic.h> #include "alsa-util.h" @@ -290,16 +290,22 @@ int pa_alsa_set_hw_params( 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); @@ -308,8 +314,6 @@ int pa_alsa_set_hw_params( snd_pcm_hw_params_alloca(&hwparams); - buffer_size = *periods * *period_size; - if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) goto finish; @@ -330,12 +334,19 @@ int pa_alsa_set_hw_params( } 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; @@ -344,10 +355,32 @@ int pa_alsa_set_hw_params( goto finish; } - if ((*period_size > 0 && (ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, period_size, NULL)) < 0) || - (*periods > 0 && (ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0)) + 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) { + 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; @@ -363,8 +396,8 @@ int pa_alsa_set_hw_params( if ((ret = snd_pcm_prepare(pcm_handle)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size)) < 0 || - (ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0) + 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 */ @@ -373,14 +406,18 @@ int pa_alsa_set_hw_params( ss->channels = c; ss->format = f; - pa_assert(buffer_size > 0); - pa_assert(*period_size > 0); - *periods = buffer_size / *period_size; - pa_assert(*periods > 0); + 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; finish: @@ -388,7 +425,7 @@ finish: return ret; } -int pa_alsa_set_sw_params(snd_pcm_t *pcm) { +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min) { snd_pcm_sw_params_t *swparams; int err; @@ -411,6 +448,11 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm) { 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; @@ -477,7 +519,9 @@ snd_pcm_t *pa_alsa_open_by_device_id( int mode, uint32_t *nfrags, snd_pcm_uframes_t *period_size, - pa_bool_t *use_mmap) { + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { int i; int direction = 1; @@ -526,7 +570,11 @@ snd_pcm_t *pa_alsa_open_by_device_id( d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); pa_log_debug("Trying %s...", d); - if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) { + if ((err = snd_pcm_open(&pcm_handle, d, mode, + SND_PCM_NONBLOCK| + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 0) { pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err)); pa_xfree(d); continue; @@ -536,7 +584,7 @@ snd_pcm_t *pa_alsa_open_by_device_id( try_ss.rate = ss->rate; try_ss.format = ss->format; - if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, use_mmap, TRUE)) < 0) { + if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); pa_xfree(d); snd_pcm_close(pcm_handle); @@ -550,11 +598,11 @@ snd_pcm_t *pa_alsa_open_by_device_id( return pcm_handle; } - /* OK, we didn't find any good device, so let's try the raw hw: stuff */ + /* OK, we didn't find any good device, so let's try the raw plughw: stuff */ - d = pa_sprintf_malloc("hw:%s", dev_id); + d = pa_sprintf_malloc("plughw:%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, use_mmap); + 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; @@ -568,7 +616,9 @@ snd_pcm_t *pa_alsa_open_by_device_string( int mode, uint32_t *nfrags, snd_pcm_uframes_t *period_size, - pa_bool_t *use_mmap) { + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { int err; char *d; @@ -585,13 +635,16 @@ snd_pcm_t *pa_alsa_open_by_device_string( for (;;) { - if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) { + if ((err = snd_pcm_open(&pcm_handle, d, mode, 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", d, snd_strerror(err)); pa_xfree(d); return NULL; } - if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, use_mmap, FALSE)) < 0) { + if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE)) < 0) { if (err == -EPERM) { /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ @@ -615,10 +668,8 @@ snd_pcm_t *pa_alsa_open_by_device_string( *dev = d; - if (ss->channels != map->channels) { - pa_assert_se(pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_ALSA); - } + if (ss->channels != map->channels) + pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); return pcm_handle; } @@ -773,7 +824,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel } if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { - pa_log_info("Channel map has duplicate channel '%s', failling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + 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; } @@ -793,7 +844,275 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel } } - pa_log_info("All %u channels can be mapped to mixer channels. Using hardware volume control.", channel_map->channels); + pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); return 0; } + +void pa_alsa_0dB_playback(snd_mixer_elem_t *elem) { + long min, max, v; + + pa_assert(elem); + + /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use + * raw volume levels and fix them to 75% */ + + if (snd_mixer_selem_set_playback_dB_all(elem, 0, -1) >= 0) + return; + + if (snd_mixer_selem_set_playback_dB_all(elem, 0, 1) >= 0) + return; + + if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0) + return; + + v = min + ((max - min) * 3) / 4; /* 75% */ + + if (v <= min) + v = max; + + snd_mixer_selem_set_playback_volume_all(elem, v); +} + +void pa_alsa_0dB_capture(snd_mixer_elem_t *elem) { + long min, max, v; + + pa_assert(elem); + + /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use + * raw volume levels and fix them to 75% */ + + if (snd_mixer_selem_set_capture_dB_all(elem, 0, -1) >= 0) + return; + + if (snd_mixer_selem_set_capture_dB_all(elem, 0, 1) >= 0) + return; + + if (snd_mixer_selem_get_capture_volume_range(elem, &min, &max) < 0) + return; + + v = min + ((max - min) * 3) / 4; /* 75% */ + + if (v <= min) + v = max; + + snd_mixer_selem_set_capture_volume_all(elem, v); +} + +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; + + va_start(ap, fmt); + + pa_log_levelv_meta(PA_LOG_WARN, file, line, function, fmt, ap); + + va_end(ap); +} + +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_warn("Got POLLERR from ALSA"); + if (revents & POLLNVAL) + pa_log_warn("Got POLLNVAL from ALSA"); + if (revents & POLLHUP) + pa_log_warn("Got POLLHUP from ALSA"); + + state = snd_pcm_state(pcm); + pa_log_warn("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, n); + pollfd = pa_rtpoll_item_get_pollfd(item, NULL); + + if ((err = snd_pcm_poll_descriptors(pcm, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + pa_rtpoll_item_free(item); + return NULL; + } + + return item; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index 53d9a2fb..4de8bcd2 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -1,8 +1,6 @@ #ifndef fooalsautilhfoo #define fooalsautilhfoo -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -29,8 +27,10 @@ #include <pulse/sample.h> #include <pulse/mainloop-api.h> - #include <pulse/channelmap.h> +#include <pulse/proplist.h> + +#include <pulsecore/rtpoll.h> typedef struct pa_alsa_fdlist pa_alsa_fdlist; @@ -43,10 +43,12 @@ int pa_alsa_set_hw_params( 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); +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); @@ -59,7 +61,9 @@ snd_pcm_t *pa_alsa_open_by_device_id( int mode, uint32_t *nfrags, snd_pcm_uframes_t *period_size, - pa_bool_t *use_mmap); + 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, @@ -69,8 +73,25 @@ snd_pcm_t *pa_alsa_open_by_device_string( int mode, uint32_t *nfrags, snd_pcm_uframes_t *period_size, - pa_bool_t *use_mmap); + 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_0dB_playback(snd_mixer_elem_t *elem); +void pa_alsa_0dB_capture(snd_mixer_elem_t *elem); + +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); + #endif diff --git a/src/modules/bt-proximity-helper.c b/src/modules/bt-proximity-helper.c index 5f042c37..3767f01c 100644 --- a/src/modules/bt-proximity-helper.c +++ b/src/modules/bt-proximity-helper.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /* * Small SUID helper that allows us to ping a BT device. Borrows * heavily from bluez-utils' l2ping, which is licensed as GPL2+ diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c index fc1e91ea..905be13f 100644 --- a/src/modules/dbus-util.c +++ b/src/modules/dbus-util.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/dbus-util.h b/src/modules/dbus-util.h index 8dca54fe..2b24ac63 100644 --- a/src/modules/dbus-util.h +++ b/src/modules/dbus-util.h @@ -1,8 +1,6 @@ #ifndef foodbusutilhfoo #define foodbusutilhfoo -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c index abd13287..f5016faf 100644 --- a/src/modules/gconf/gconf-helper.c +++ b/src/modules/gconf/gconf-helper.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index 836157d0..a2a43278 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index 14aef7c9..6765775a 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB PulseAudio is free software; you can redistribute it and/or modify @@ -32,6 +30,7 @@ #include <pulse/xmalloc.h> #include <pulse/util.h> +#include <pulse/timeval.h> #include <pulsecore/core.h> #include <pulsecore/module.h> @@ -46,6 +45,8 @@ #include <pulsecore/core-error.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/time-smoother.h> #include "alsa-util.h" #include "module-alsa-sink-symdef.h" @@ -57,16 +58,42 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "device=<ALSA device> " - "device_id=<ALSA device id> " + "device_id=<ALSA card index> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map> " - "mmap=<enable memory mapping?>"); + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<lower fill watermark> " + "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); + +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", + "mixer_reset", + 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; @@ -83,227 +110,404 @@ struct userdata { 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; - size_t frame_size, fragment_size, hwbuf_size; + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; unsigned nfragments; pa_memchunk memchunk; char *device_name; - pa_bool_t use_mmap; + pa_bool_t use_mmap, use_tsched; - pa_bool_t first; + pa_bool_t first, after_rewind; pa_rtpoll_item *alsa_rtpoll_item; snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; -}; -static const char* const valid_modargs[] = { - "device", - "device_id", - "sink_name", - "format", - "channels", - "rate", - "fragments", - "fragment_size", - "channel_map", - "mmap", - NULL + pa_smoother *smoother; + int64_t frame_index; + uint64_t since_start; + + snd_pcm_sframes_t hwbuf_unused_frames; }; -static int mmap_write(struct userdata *u) { +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 - 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 (n*u->frame_size < u->hwbuf_size) + left_to_play = u->hwbuf_size - (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) { int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + 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 (;;) { - pa_memchunk chunk; - void *p; snd_pcm_sframes_t n; - int err; - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, frames; + int r; - if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { + snd_pcm_hwsync(u->pcm_handle); - if (n == -EPIPE) { - pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); - u->first = TRUE; - } + /* First we determine how many samples are missing to fill the + * buffer up to 100% */ - if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) - continue; + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { - if (err == -EAGAIN) - return work_done; + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + continue; - pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); - return -1; + return r; } -/* pa_log("Got request for %i samples", (int) n); */ + left_to_play = check_left_to_play(u, n); - if (n <= 0) - return work_done; + if (u->use_tsched) - frames = n; + /* 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 ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { + if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; - if (err == -EPIPE) { - pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); - u->first = TRUE; - } + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) + break; - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) - continue; + n -= u->hwbuf_unused_frames; - if (err == -EAGAIN) - return work_done; +/* pa_log_debug("Filling up"); */ - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } + 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; - /* Check these are multiples of 8 bit */ - pa_assert((areas[0].first & 7) == 0); - pa_assert((areas[0].step & 7)== 0); +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - /* We assume a single interleaved memory buffer */ - pa_assert((areas[0].first >> 3) == 0); - pa_assert((areas[0].step >> 3) == u->frame_size); + if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { - p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; - chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; + return r; + } - pa_sink_render_into_full(u->sink, &chunk); + /* 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; - /* FIXME: Maybe we can do something to keep this memory block - * a little bit longer around? */ - pa_memblock_unref_fixed(chunk.memblock); + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); - if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); - if (err == -EPIPE) { - pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); - u->first = TRUE; - } + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) - continue; + 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; - if (err == -EAGAIN) - return work_done; + pa_sink_render_into_full(u->sink, &chunk); - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } + /* 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((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + continue; - work_done = 1; + return r; + } + + work_done = 1; + + u->frame_index += frames; + u->since_start += frames * u->frame_size; - if (frames >= (snd_pcm_uframes_t) n) - return work_done; +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ -/* pa_log("wrote %i samples", (int) frames); */ + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= 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) { - snd_pcm_status_t *status; +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { int work_done = 0; - - snd_pcm_status_alloca(&status); + pa_usec_t max_sleep_usec, process_usec; + 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 (;;) { - void *p; - snd_pcm_sframes_t t; - ssize_t l; - int err; + snd_pcm_sframes_t n; + int r; - if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { - pa_log("Failed to query DSP status data: %s", snd_strerror(err)); - return -1; + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + continue; + + return r; } - if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) - pa_log_debug("Buffer underrun!"); + left_to_play = check_left_to_play(u, n); - l = snd_pcm_status_get_avail(status) * u->frame_size; + if (u->use_tsched) -/* pa_log("%u bytes to write", l); */ + /* 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 (l <= 0) - return work_done; + if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, l, &u->memchunk); + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) + break; - pa_assert(u->memchunk.length > 0); + n -= u->hwbuf_unused_frames; - p = pa_memblock_acquire(u->memchunk.memblock); - t = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size); - pa_memblock_release(u->memchunk.memblock); + for (;;) { + snd_pcm_sframes_t frames; + void *p; -/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - pa_assert(t != 0); + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, n * u->frame_size, &u->memchunk); - if (t < 0) { + pa_assert(u->memchunk.length > 0); - if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) - continue; + frames = 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, frames); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(frames != 0); + + if (PA_UNLIKELY(frames < 0)) { - if (t == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } else { - pa_log("Failed to write data to DSP: %s", snd_strerror(t)); - return -1; + if ((r = try_recover(u, "snd_pcm_writei", n)) == 0) + continue; + + return r; } - } - u->memchunk.index += t * u->frame_size; - u->memchunk.length -= t * u->frame_size; + u->memchunk.index += frames * u->frame_size; + u->memchunk.length -= frames * u->frame_size; - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + work_done = 1; - work_done = 1; + u->frame_index += frames; + u->since_start += frames * u->frame_size; - if (t * u->frame_size >= (unsigned) l) - return work_done; +/* 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 pa_usec_t sink_get_latency(struct userdata *u) { - pa_usec_t r = 0; - snd_pcm_status_t *status; - snd_pcm_sframes_t frames = 0; +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); - if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) - pa_log("Failed to get delay: %s", snd_strerror(err)); - else - frames = snd_pcm_status_get_delay(status); + /* 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(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); - if (frames > 0) - r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); + delay = (int64_t) pa_bytes_to_usec(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); @@ -312,28 +516,14 @@ static pa_usec_t sink_get_latency(struct userdata *u) { } static int build_pollfd(struct userdata *u) { - int err; - struct pollfd *pollfd; - int n; - pa_assert(u); pa_assert(u->pcm_handle); - if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { - pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); - return -1; - } - if (u->alsa_rtpoll_item) pa_rtpoll_item_free(u->alsa_rtpoll_item); - u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); - pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); - - if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { - pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) return -1; - } return 0; } @@ -342,6 +532,8 @@ 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_drain(u->pcm_handle); snd_pcm_close(u->pcm_handle); @@ -357,10 +549,66 @@ static int suspend(struct userdata *u) { 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.2f", (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 = + 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 = 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); + } + + 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 - 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; + pa_bool_t b, d; unsigned nfrags; snd_pcm_uframes_t period_size; @@ -379,13 +627,14 @@ static int unsuspend(struct userdata *u) { 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, &b, TRUE)) < 0) { + 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) { + if (b != u->use_mmap || d != u->use_tsched) { pa_log_warn("Resume failed, couldn't get original access mode."); goto fail; } @@ -400,10 +649,8 @@ static int unsuspend(struct userdata *u) { goto fail; } - if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); + if (update_sw_params(u) < 0) goto fail; - } if (build_pollfd(u) < 0) goto fail; @@ -411,6 +658,7 @@ static int unsuspend(struct userdata *u) { /* FIXME: We need to reload the volume somehow */ u->first = TRUE; + u->since_start = 0; pa_log_info("Resumed successfully..."); @@ -446,7 +694,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); if (suspend(u) < 0) return -1; @@ -505,18 +753,24 @@ static int sink_get_volume_cb(pa_sink *s) { pa_assert(u->mixer_elem); for (i = 0; i < s->sample_spec.channels; i++) { - long set_vol, vol; + long alsa_vol; pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0) - goto fail; + if (u->hw_dB_supported) { - set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) { + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + continue; + } - /* Try to avoid superfluous volume changes */ - if (set_vol != vol) - s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + u->hw_dB_supported = FALSE; + } + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; @@ -524,8 +778,6 @@ static int sink_get_volume_cb(pa_sink *s) { fail: pa_log_error("Unable to read volume: %s", snd_strerror(err)); - s->get_volume = NULL; - s->set_volume = NULL; return -1; } @@ -543,15 +795,32 @@ static int sink_set_volume_cb(pa_sink *s) { pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); - vol = s->volume.values[i]; + vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + 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) { - if (vol > PA_VOLUME_NORM) - vol = PA_VOLUME_NORM; + if (snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + + continue; + } + + u->hw_dB_supported = FALSE; + + } alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) goto fail; + + if (snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; @@ -559,8 +828,6 @@ static int sink_set_volume_cb(pa_sink *s) { fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_volume = NULL; - s->set_volume = NULL; return -1; } @@ -573,9 +840,6 @@ static int sink_get_mute_cb(pa_sink *s) { 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)); - - s->get_mute = NULL; - s->set_mute = NULL; return -1; } @@ -593,12 +857,90 @@ static int sink_set_mute_cb(pa_sink *s) { 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; + } - s->get_mute = NULL; - s->set_mute = NULL; + 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, 0); + } +} + +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; + + 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(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, in_frames)) < 0) { + pa_log("snd_pcm_rewind() failed: %s", snd_strerror(out_frames)); + return -1; + } + pa_log_debug("after: %lu", (unsigned long) out_frames); + + rewind_nbytes = 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; + } + } else + pa_log_debug("Mhmm, actually there is nothing to rewind."); + return 0; } @@ -618,25 +960,77 @@ static void thread_func(void *userdata) { for (;;) { int ret; +/* pa_log_debug("loop"); */ + /* Render some data and write it to the dsp */ - if (PA_SINK_OPENED(u->sink->thread_info.state)) { - int work_done = 0; + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + int work_done; + pa_usec_t sleep_usec; - if (u->use_mmap) { - if ((work_done = mmap_write(u)) < 0) - goto fail; - } else { - if ((work_done = unix_write(u)) < 0) + if (u->sink->thread_info.rewind_nbytes > 0) + if (process_rewind(u) < 0) goto fail; + + if (u->use_mmap) + work_done = mmap_write(u, &sleep_usec); + else + work_done = unix_write(u, &sleep_usec); + + 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 (work_done && u->first) { - pa_log_info("Starting playback."); - snd_pcm_start(u->pcm_handle); - u->first = FALSE; - continue; + 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, 1)) < 0) @@ -646,7 +1040,7 @@ static void thread_func(void *userdata) { goto finish; /* Tell ALSA about this and process its response */ - if (PA_SINK_OPENED(u->sink->thread_info.state)) { + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { struct pollfd *pollfd; unsigned short revents = 0; int err; @@ -660,43 +1054,15 @@ static void thread_func(void *userdata) { } if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; - if (revents & POLLERR) - pa_log_warn("Got POLLERR from ALSA"); - if (revents & POLLNVAL) - pa_log_warn("Got POLLNVAL from ALSA"); - if (revents & POLLHUP) - pa_log_warn("Got POLLHUP from ALSA"); - - /* Try to recover from this error */ - - switch (snd_pcm_state(u->pcm_handle)) { - - case SND_PCM_STATE_XRUN: - if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); - goto fail; - } - break; - - case SND_PCM_STATE_SUSPENDED: - if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); - goto fail; - } - break; - - default: - - snd_pcm_drop(u->pcm_handle); - - if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); - goto fail; - } - break; - } + u->first = TRUE; + u->since_start = 0; } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA! (%i)", revents); } } @@ -717,21 +1083,24 @@ int pa__init(pa_module*m) { const char *dev_id; pa_sample_spec ss; pa_channel_map map; - uint32_t nfrags, frag_size; - snd_pcm_uframes_t period_size; + 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; - char *t; const char *name; char *name_buf = NULL; - int namereg_fail; - pa_bool_t use_mmap = TRUE, b; + pa_bool_t namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + 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; @@ -746,35 +1115,66 @@ int pa__init(pa_module*m) { frame_size = pa_frame_size(&ss); nfrags = m->core->default_n_fragments; - frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); if (frag_size <= 0) frag_size = frame_size; + tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = 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) { + 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; } - period_size = frag_size/frame_size; + + 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("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { + pa_log("Failed to parse mixer_reset argument."); + goto fail; + } + 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; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + 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; - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + 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))) { @@ -783,8 +1183,8 @@ int pa__init(pa_module*m) { &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_size, - &b))) + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; @@ -795,8 +1195,8 @@ int pa__init(pa_module*m) { &u->device_name, &ss, &map, SND_PCM_STREAM_PLAYBACK, - &nfrags, &period_size, - &b))) + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; } @@ -806,22 +1206,25 @@ int pa__init(pa_module*m) { if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = b; + 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; } - if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); - goto fail; - } - /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); @@ -833,13 +1236,24 @@ int pa__init(pa_module*m) { if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) found = TRUE; else { - char *md = pa_sprintf_malloc("hw:%s", dev_id); + snd_pcm_info_t *info; + + snd_pcm_info_alloca(&info); - if (strcmp(u->device_name, md)) - if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) - found = TRUE; + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; - pa_xfree(md); + 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) @@ -853,13 +1267,28 @@ int pa__init(pa_module*m) { } if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = 1; + namereg_fail = TRUE; else { name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); - namereg_fail = 0; + namereg_fail = FALSE; } - u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + 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) { @@ -868,26 +1297,43 @@ int pa__init(pa_module*m) { } 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_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc( - "ALSA PCM on %s (%s)%s", - u->device_name, - snd_pcm_info_get_name(pcm_info), - use_mmap ? " via DMA" : "")); - pa_xfree(t); - - u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY; u->frame_size = frame_size; - u->fragment_size = frag_size = period_size * frame_size; + u->fragment_size = frag_size = 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; + + if (use_tsched) + fix_tsched_watermark(u); - pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + 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); @@ -895,17 +1341,74 @@ int pa__init(pa_module*m) { pa_assert(u->mixer_elem); if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) - if (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; - snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); - u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + + if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0 && + snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { + + pa_bool_t suitable = TRUE; + + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + + if (u->hw_volume_min > u->hw_volume_max) { + + pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max); + suitable = FALSE; + + } else if (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; + + } else if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) { + + /* u->hw_dB_max = 0; u->hw_dB_min = -3000; Use this to make valgrind shut up */ + + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0); + + /* Let's see if this thing actually is useful for muting */ + if (u->hw_dB_min > -6000) { + pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100); + + suitable = FALSE; + } else if (u->hw_dB_max < 0) { + + pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else if (u->hw_dB_min >= u->hw_dB_max) { + + pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else { + + if (u->hw_dB_max > 0) { + /* dB > 0 means overamplification, and clipping, we don't want that here */ + pa_log_info("Device can do overamplification for %0.2f dB. Limiting to 0 db", ((double) u->hw_dB_max) / 100); + u->hw_dB_max = 0; + } + + u->hw_dB_supported = TRUE; + } + } + + if (suitable) { + 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 if (mixer_reset) { + pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); + pa_alsa_0dB_playback(u->mixer_elem); + } else + pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); } 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_VOLUME_CTRL; + u->sink->flags |= PA_SINK_HW_MUTE_CTRL; } u->mixer_fdl = pa_alsa_fdlist_new(); @@ -920,16 +1423,29 @@ int pa__init(pa_module*m) { } 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 (u->sink->get_volume) - u->sink->get_volume(u->sink); - if (u->sink->get_mute) - u->sink->get_mute(u->sink); + 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); @@ -952,8 +1468,10 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!(u = m->userdata)) + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); return; + } if (u->sink) pa_sink_unlink(u->sink); @@ -988,8 +1506,13 @@ void pa__done(pa_module*m) { 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 index 23a2f921..1cc467d9 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB PulseAudio is free software; you can redistribute it and/or modify @@ -32,6 +30,7 @@ #include <pulse/xmalloc.h> #include <pulse/util.h> +#include <pulse/timeval.h> #include <pulsecore/core-error.h> #include <pulsecore/core.h> @@ -47,6 +46,8 @@ #include <pulsecore/core-error.h> #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/rtclock.h> #include "alsa-util.h" #include "module-alsa-source-symdef.h" @@ -58,16 +59,42 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source_name=<name for the source> " "device=<ALSA device> " - "device_id=<ALSA device id> " + "device_id=<ALSA card index> " "format=<sample format> " - "channels=<number of channels> " "rate=<sample rate> " + "channels=<number of channels> " + "channel_map=<channel map> " "fragments=<number of fragments> " "fragment_size=<fragment size> " - "channel_map=<channel map> " - "mmap=<enable memory mapping?>"); + "mmap=<enable memory mapping?> " + "tsched=<enable system timer based scheduling mode?> " + "tsched_buffer_size=<buffer size when using timer based scheduling> " + "tsched_buffer_watermark=<upper fill watermark> " + "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); + +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", + "mixer_reset", + 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; @@ -84,244 +111,364 @@ struct userdata { 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; - size_t frame_size, fragment_size, hwbuf_size; + size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; unsigned nfragments; char *device_name; - pa_bool_t use_mmap; + 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]; -}; -static const char* const valid_modargs[] = { - "device", - "device_id", - "source_name", - "channels", - "rate", - "format", - "fragments", - "fragment_size", - "channel_map", - "mmap", - NULL + pa_smoother *smoother; + int64_t frame_index; + + snd_pcm_sframes_t hwbuf_unused_frames; }; -static int mmap_read(struct userdata *u) { +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 - 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; + + if (n*u->frame_size < u->hwbuf_size) + left_to_record = u->hwbuf_size - (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) { int work_done = 0; + pa_usec_t max_sleep_usec, process_usec; + 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 err; - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, frames; - pa_memchunk chunk; - void *p; + int r; - if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { + snd_pcm_hwsync(u->pcm_handle); - if (n == -EPIPE) - pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { - if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) continue; - if (err == -EAGAIN) - return work_done; - - pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); - return -1; + return r; } -/* pa_log("Got request for %i samples", (int) n); */ + left_to_record = check_left_to_record(u, n); - if (n <= 0) - return work_done; + if (u->use_tsched) + if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; - frames = n; + if (PA_UNLIKELY(n <= 0)) + break; - if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { + 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; - if (err == -EPIPE) - pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); +/* pa_log_debug("%lu frames to read", (unsigned long) frames); */ - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) - continue; + if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { - if (err == -EAGAIN) - return work_done; + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } + return r; + } - /* Check these are multiples of 8 bit */ - pa_assert((areas[0].first & 7) == 0); - pa_assert((areas[0].step & 7)== 0); + /* 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; - /* We assume a single interleaved memory buffer */ - pa_assert((areas[0].first >> 3) == 0); - pa_assert((areas[0].step >> 3) == u->frame_size); + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); - p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); - chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); - pa_source_post(u->source, &chunk); + 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; - /* FIXME: Maybe we can do something to keep this memory block - * a little bit longer around? */ - pa_memblock_unref_fixed(chunk.memblock); + pa_source_post(u->source, &chunk); + pa_memblock_unref_fixed(chunk.memblock); - if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - if (err == -EPIPE) - pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); + if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + continue; - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) - continue; + return r; + } - if (err == -EAGAIN) - return work_done; + work_done = 1; - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } + u->frame_index += frames; + +/* pa_log_debug("read %lu frames", (unsigned long) frames); */ - work_done = 1; + if (frames >= (snd_pcm_uframes_t) n) + break; -/* pa_log("wrote %i samples", (int) frames); */ + n -= 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) { - snd_pcm_status_t *status; +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { int work_done = 0; - - snd_pcm_status_alloca(&status); + pa_usec_t max_sleep_usec, process_usec; + 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 (;;) { - void *p; - snd_pcm_sframes_t t, k; - ssize_t l; - int err; - pa_memchunk chunk; - - if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { - pa_log("Failed to query DSP status data: %s", snd_strerror(err)); - return -1; + snd_pcm_sframes_t n; + int r; + + snd_pcm_hwsync(u->pcm_handle); + + if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + continue; + + return r; } - if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) - pa_log_debug("Buffer overrun!"); + left_to_record = check_left_to_record(u, n); - l = snd_pcm_status_get_avail(status) * u->frame_size; + if (u->use_tsched) + if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + break; - if (l <= 0) + if (PA_UNLIKELY(n <= 0)) return work_done; - chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + for (;;) { + void *p; + snd_pcm_sframes_t frames; + pa_memchunk chunk; - k = pa_memblock_get_length(chunk.memblock); + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); - if (k > l) - k = l; + frames = pa_memblock_get_length(chunk.memblock) / u->frame_size; - k = (k/u->frame_size)*u->frame_size; + if (frames > n) + frames = n; - p = pa_memblock_acquire(chunk.memblock); - t = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, k / u->frame_size); - pa_memblock_release(chunk.memblock); +/* pa_log_debug("%lu frames to read", (unsigned long) n); */ -/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ + p = pa_memblock_acquire(chunk.memblock); + frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, frames); + pa_memblock_release(chunk.memblock); - pa_assert(t != 0); + pa_assert(frames != 0); - if (t < 0) { - pa_memblock_unref(chunk.memblock); + if (PA_UNLIKELY(frames < 0)) { + pa_memblock_unref(chunk.memblock); - if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) - continue; + if ((r = try_recover(u, "snd_pcm_readi", n)) == 0) + continue; - if (t == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } else { - pa_log("Failed to read data from DSP: %s", snd_strerror(t)); - return -1; + return r; } + + chunk.index = 0; + chunk.length = 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; } + } - chunk.index = 0; - chunk.length = t * u->frame_size; + *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec; + return work_done; +} - pa_source_post(u->source, &chunk); - pa_memblock_unref(chunk.memblock); +static void update_smoother(struct userdata *u) { + snd_pcm_sframes_t delay = 0; + int64_t frames; + int err; + pa_usec_t now1, now2; - work_done = 1; + pa_assert(u); + pa_assert(u->pcm_handle); - if (t * u->frame_size >= (unsigned) l) - return work_done; + /* 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(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; - snd_pcm_status_t *status; - snd_pcm_sframes_t frames = 0; - int err; - - snd_pcm_status_alloca(&status); + int64_t delay; + pa_usec_t now1, now2; pa_assert(u); - pa_assert(u->pcm_handle); - if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) - pa_log("Failed to get delay: %s", snd_strerror(err)); - else - frames = snd_pcm_status_get_delay(status); + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); - if (frames > 0) - r = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec); + delay = (int64_t) now2 - pa_bytes_to_usec(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) { - int err; - struct pollfd *pollfd; - int n; - pa_assert(u); pa_assert(u->pcm_handle); - if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { - pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); - return -1; - } - if (u->alsa_rtpoll_item) pa_rtpoll_item_free(u->alsa_rtpoll_item); - u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); - pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); - - if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { - pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) return -1; - } return 0; } @@ -330,6 +477,8 @@ 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; @@ -344,10 +493,63 @@ static int suspend(struct userdata *u) { 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.2f", (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 = + 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); + } + + 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; + pa_bool_t b, d; unsigned nfrags; snd_pcm_uframes_t period_size; @@ -366,13 +568,14 @@ static int unsuspend(struct userdata *u) { 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, &b, TRUE)) < 0) { + 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) { + if (b != u->use_mmap || d != u->use_tsched) { pa_log_warn("Resume failed, couldn't get original access mode."); goto fail; } @@ -387,18 +590,17 @@ static int unsuspend(struct userdata *u) { goto fail; } - if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); + if (update_sw_params(u) < 0) goto fail; - } if (build_pollfd(u) < 0) goto fail; - snd_pcm_start(u->pcm_handle); - /* 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; @@ -433,7 +635,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { case PA_SOURCE_SUSPENDED: - pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); if (suspend(u) < 0) return -1; @@ -494,18 +696,24 @@ static int source_get_volume_cb(pa_source *s) { pa_assert(u->mixer_elem); for (i = 0; i < s->sample_spec.channels; i++) { - long set_vol, vol; + long alsa_vol; pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); - if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0) - goto fail; + if (u->hw_dB_supported) { - set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) { + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + continue; + } - /* Try to avoid superfluous volume changes */ - if (set_vol != vol) - s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + u->hw_dB_supported = FALSE; + } + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; @@ -513,8 +721,6 @@ static int source_get_volume_cb(pa_source *s) { fail: pa_log_error("Unable to read volume: %s", snd_strerror(err)); - s->get_volume = NULL; - s->set_volume = NULL; return -1; } @@ -532,15 +738,32 @@ static int source_set_volume_cb(pa_source *s) { pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); - vol = s->volume.values[i]; + vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + + if (u->hw_dB_supported) { + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + - if (vol > PA_VOLUME_NORM) - vol = PA_VOLUME_NORM; + if ((err = snd_mixer_selem_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + + if (snd_mixer_selem_get_capture_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + + continue; + } + + u->hw_dB_supported = FALSE; + } alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) goto fail; + + if (snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) + s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); } return 0; @@ -548,8 +771,6 @@ static int source_set_volume_cb(pa_source *s) { fail: pa_log_error("Unable to set volume: %s", snd_strerror(err)); - s->get_volume = NULL; - s->set_volume = NULL; return -1; } @@ -562,9 +783,6 @@ static int source_get_mute_cb(pa_source *s) { 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)); - - s->get_mute = NULL; - s->set_mute = NULL; return -1; } @@ -582,15 +800,22 @@ static int source_set_mute_cb(pa_source *s) { 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)); - - s->get_mute = NULL; - s->set_mute = NULL; 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; @@ -607,18 +832,47 @@ static void thread_func(void *userdata) { for (;;) { int ret; +/* pa_log_debug("loop"); */ + /* Read some data and pass it to the sources */ - if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { + int work_done = 0; + pa_usec_t sleep_usec; - if (u->use_mmap) { - if (mmap_read(u) < 0) - goto fail; + if (u->use_mmap) + work_done = mmap_read(u, &sleep_usec); + else + work_done = unix_read(u, &sleep_usec); - } else { - if (unix_read(u) < 0) - goto fail; + 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, 1)) < 0) @@ -628,7 +882,7 @@ static void thread_func(void *userdata) { goto finish; /* Tell ALSA about this and process its response */ - if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { struct pollfd *pollfd; unsigned short revents = 0; int err; @@ -642,43 +896,14 @@ static void thread_func(void *userdata) { } if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; - if (revents & POLLERR) - pa_log_warn("Got POLLERR from ALSA"); - if (revents & POLLNVAL) - pa_log_warn("Got POLLNVAL from ALSA"); - if (revents & POLLHUP) - pa_log_warn("Got POLLHUP from ALSA"); - - /* Try to recover from this error */ - - switch (snd_pcm_state(u->pcm_handle)) { - - case SND_PCM_STATE_XRUN: - if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); - goto fail; - } - break; - - case SND_PCM_STATE_SUSPENDED: - if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); - goto fail; - } - break; - - default: - - snd_pcm_drop(u->pcm_handle); - - if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); - goto fail; - } - break; - } + snd_pcm_start(u->pcm_handle); } + + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA! (%i)", revents); } } @@ -699,21 +924,23 @@ int pa__init(pa_module*m) { const char *dev_id; pa_sample_spec ss; pa_channel_map map; - uint32_t nfrags, frag_size; - snd_pcm_uframes_t period_size; + 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; - char *t; const char *name; char *name_buf = NULL; - int namereg_fail; - pa_bool_t use_mmap = TRUE, b; + pa_bool_t namereg_fail; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + 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; @@ -728,34 +955,61 @@ int pa__init(pa_module*m) { frame_size = pa_frame_size(&ss); nfrags = m->core->default_n_fragments; - frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); if (frag_size <= 0) frag_size = frame_size; + tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = 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) { + 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; } - period_size = frag_size/frame_size; + + 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("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + use_tsched = FALSE; + } + + if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { + pa_log("Failed to parse mixer_reset argument."); + goto fail; + } + u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; m->userdata = u; u->use_mmap = use_mmap; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + 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; - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + 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))) { @@ -764,8 +1018,8 @@ int pa__init(pa_module*m) { &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_size, - &b))) + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; } else { @@ -775,8 +1029,8 @@ int pa__init(pa_module*m) { &u->device_name, &ss, &map, SND_PCM_STREAM_CAPTURE, - &nfrags, &period_size, - &b))) + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; } @@ -785,22 +1039,25 @@ int pa__init(pa_module*m) { if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = b; + 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; } - if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { - pa_log("Failed to set software parameters: %s", snd_strerror(err)); - goto fail; - } - /* ALSA might tweak the sample spec, so recalculate the frame size */ frame_size = pa_frame_size(&ss); @@ -812,13 +1069,24 @@ int pa__init(pa_module*m) { if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) found = TRUE; else { - char *md = pa_sprintf_malloc("hw:%s", dev_id); + snd_pcm_info_t* info; - if (strcmp(u->device_name, md)) - if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) - found = TRUE; + snd_pcm_info_alloca(&info); - pa_xfree(md); + 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) @@ -832,13 +1100,28 @@ int pa__init(pa_module*m) { } if ((name = pa_modargs_get_value(ma, "source_name", NULL))) - namereg_fail = 1; + namereg_fail = TRUE; else { name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); - namereg_fail = 0; + namereg_fail = FALSE; } - u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + 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) { @@ -847,42 +1130,104 @@ int pa__init(pa_module*m) { } 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_module(u->source, m); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - pa_source_set_description(u->source, t = pa_sprintf_malloc( - "ALSA PCM on %s (%s)%s", - u->device_name, - snd_pcm_info_get_name(pcm_info), - use_mmap ? " via DMA" : "")); - pa_xfree(t); - - u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY; u->frame_size = frame_size; - u->fragment_size = frag_size = period_size * frame_size; + u->fragment_size = frag_size = 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; + + 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); - pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + 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)) - if (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; - snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); - u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0 && + snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { + + pa_bool_t suitable = TRUE; + + pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + + if (u->hw_volume_min > u->hw_volume_max) { + + pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max); + suitable = FALSE; + + } else if (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; + + } else if (snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) { + + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0); + + /* Let's see if this thing actually is useful for muting */ + if (u->hw_dB_min > -6000) { + pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100); + + suitable = FALSE; + } else if (u->hw_dB_max < 0) { + + pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else if (u->hw_dB_min >= u->hw_dB_max) { + + pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100); + suitable = FALSE; + + } else + u->hw_dB_supported = TRUE; + } + + if (suitable) { + 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 if (mixer_reset) { + pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); + pa_alsa_0dB_capture(u->mixer_elem); + } else + pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); + } + 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_VOLUME_CTRL; + u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; } u->mixer_fdl = pa_alsa_fdlist_new(); @@ -897,15 +1242,28 @@ int pa__init(pa_module*m) { } 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 (u->source->get_volume) - u->source->get_volume(u->source); - if (u->source->get_mute) - u->source->get_mute(u->source); + 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); @@ -928,8 +1286,10 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!(u = m->userdata)) + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); return; + } if (u->source) pa_source_unlink(u->source); @@ -961,8 +1321,12 @@ void pa__done(pa_module*m) { 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-always-sink.c b/src/modules/module-always-sink.c new file mode 100644 index 00000000..8b67a36d --- /dev/null +++ b/src/modules/module-always-sink.c @@ -0,0 +1,178 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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 <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> + +#include "module-always-sink-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("Always keeps at least one sink loaded even if it's a null one"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "sink_name=<name of sink>"); + +#define DEFAULT_SINK_NAME "auto_null" + +static const char* const valid_modargs[] = { + "sink_name", + NULL, +}; + +struct userdata { + pa_hook_slot *put_slot, *unlink_slot; + pa_module* null_module; + pa_bool_t ignore; + char *sink_name; +}; + +static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata* u) { + pa_sink *target; + uint32_t idx; + char *t; + + pa_assert(c); + pa_assert(u); + pa_assert(!u->null_module); + + /* Loop through all sinks and check to see if we have *any* + * sinks. Ignore the sink passed in (if it's not null) */ + for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + if (!sink || target != sink) + break; + + if (target) + return; + + pa_log_debug("Autoloading null-sink as no other sinks detected."); + + u->ignore = TRUE; + + t = pa_sprintf_malloc("sink_name=%s", u->sink_name); + u->null_module = pa_module_load(c, "module-null-sink", t); + pa_xfree(t); + + u->ignore = FALSE; + + if (!u->null_module) + pa_log_warn("Unable to load module-null-sink"); +} + +static pa_hook_result_t put_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + /* This is us detecting ourselves on load... just ignore this. */ + if (u->ignore) + return PA_HOOK_OK; + + /* Auto-loaded null-sink not active, so ignoring newly detected sink. */ + if (!u->null_module) + return PA_HOOK_OK; + + /* This is us detecting ourselves on load in a different way... just ignore this too. */ + if (sink->module == u->null_module) + return PA_HOOK_OK; + + pa_log_info("A new sink has been discovered. Unloading null-sink."); + + pa_module_unload_request(u->null_module); + u->null_module = NULL; + + return PA_HOOK_OK; +} + +static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(sink); + pa_assert(u); + + /* First check to see if it's our own null-sink that's been removed... */ + if (u->null_module && sink->module == u->null_module) { + pa_log_debug("Autoloaded null-sink removed"); + u->null_module = NULL; + return PA_HOOK_OK; + } + + load_null_sink_if_needed(c, sink, u); + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + return -1; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + u->put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u); + u->unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u); + u->null_module = NULL; + u->ignore = FALSE; + + pa_modargs_free(ma); + + load_null_sink_if_needed(m->core, NULL, u); + + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->put_slot) + pa_hook_slot_free(u->put_slot); + if (u->unlink_slot) + pa_hook_slot_free(u->unlink_slot); + if (u->null_module) + pa_module_unload_request(u->null_module); + + pa_xfree(u->sink_name); + pa_xfree(u); +} diff --git a/src/modules/module-bt-proximity.c b/src/modules/module-bt-proximity.c index 62d530d4..77b95868 100644 --- a/src/modules/module-bt-proximity.c +++ b/src/modules/module-bt-proximity.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c index ab311a82..df7783fa 100644 --- a/src/modules/module-cli.c +++ b/src/modules/module-cli.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 996cd4f6..cef7a99a 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -47,6 +45,7 @@ #include <pulsecore/rtpoll.h> #include <pulsecore/rtclock.h> #include <pulsecore/core-error.h> +#include <pulsecore/time-smoother.h> #include "module-combine-symdef.h" @@ -56,7 +55,6 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " - "master=<master sink> " "slaves=<slave sinks> " "adjust_time=<seconds> " "resample_method=<method> " @@ -66,13 +64,15 @@ PA_MODULE_USAGE( "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "combined" -#define MEMBLOCKQ_MAXLENGTH (1024*170) + +#define MEMBLOCKQ_MAXLENGTH (1024*1024*16) #define DEFAULT_ADJUST_TIME 10 +#define REQUEST_LATENCY_USEC (PA_USEC_PER_MSEC * 200) + static const char* const valid_modargs[] = { "sink_name", - "master", "slaves", "adjust_time", "resample_method", @@ -91,12 +91,15 @@ struct output { pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ *outq; /* Message queue from this sink input to the sink thread */ - pa_rtpoll_item *inq_rtpoll_item, *outq_rtpoll_item; + pa_rtpoll_item *inq_rtpoll_item_read, *inq_rtpoll_item_write; + pa_rtpoll_item *outq_rtpoll_item_read, *outq_rtpoll_item_write; pa_memblockq *memblockq; pa_usec_t total_latency; + pa_atomic_t max_request; + PA_LLIST_FIELDS(struct output); }; @@ -113,45 +116,48 @@ struct userdata { uint32_t adjust_time; pa_bool_t automatic; - size_t block_size; - pa_hook_slot *sink_new_slot, *sink_unlink_slot, *sink_state_changed_slot; + pa_hook_slot *sink_put_slot, *sink_unlink_slot, *sink_state_changed_slot; pa_resample_method_t resample_method; struct timeval adjust_timestamp; - struct output *master; + pa_usec_t block_usec; + pa_idxset* outputs; /* managed in main context */ struct { PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */ pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */ - struct timeval timestamp; + pa_usec_t timestamp; pa_bool_t in_null_mode; + pa_smoother *smoother; + uint64_t counter; } thread_info; }; enum { SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, SINK_MESSAGE_REMOVE_OUTPUT, - SINK_MESSAGE_NEED + SINK_MESSAGE_NEED, + SINK_MESSAGE_UPDATE_LATENCY, + SINK_MESSAGE_UPDATE_MAX_REQUEST }; enum { - SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX, }; static void output_free(struct output *o); static int output_create_sink_input(struct output *o); -static void update_master(struct userdata *u, struct output *o); -static void pick_master(struct userdata *u, struct output *except); static void adjust_rates(struct userdata *u) { struct output *o; - pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency; + pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency, avg_total_latency = 0; uint32_t base_rate; uint32_t idx; + unsigned n = 0; pa_assert(u); pa_sink_assert_ref(u->sink); @@ -159,42 +165,44 @@ static void adjust_rates(struct userdata *u) { if (pa_idxset_size(u->outputs) <= 0) return; - if (!u->master) - return; - - if (!PA_SINK_OPENED(pa_sink_get_state(u->sink))) + if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))) return; for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { pa_usec_t sink_latency; - if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) continue; - sink_latency = pa_sink_get_latency(o->sink); - o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input); + o->total_latency = pa_sink_input_get_latency(o->sink_input, &sink_latency); + o->total_latency += sink_latency; if (sink_latency > max_sink_latency) max_sink_latency = sink_latency; if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) min_total_latency = o->total_latency; + + avg_total_latency += o->total_latency; + n++; } if (min_total_latency == (pa_usec_t) -1) return; + avg_total_latency /= n; + target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency; - pa_log_info("[%s] target latency is %0.0f usec.", u->sink->name, (float) target_latency); - pa_log_info("[%s] master %s latency %0.0f usec.", u->sink->name, u->master->sink->name, (float) u->master->total_latency); + pa_log_info("[%s] avg total latency is %0.2f msec.", u->sink->name, (double) avg_total_latency / PA_USEC_PER_MSEC); + pa_log_info("[%s] target latency is %0.2f msec.", u->sink->name, (double) target_latency / PA_USEC_PER_MSEC); base_rate = u->sink->sample_spec.rate; for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { uint32_t r = base_rate; - if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) continue; if (o->total_latency < target_latency) @@ -203,13 +211,15 @@ static void adjust_rates(struct userdata *u) { r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { - pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->name, base_rate, r); + pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r); pa_sink_input_set_rate(o->sink_input, base_rate); } else { - pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency); + pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), r, (double) r / base_rate, (float) o->total_latency); pa_sink_input_set_rate(o->sink_input, r); } } + + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, NULL, (int64_t) avg_total_latency, NULL); } static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { @@ -227,6 +237,36 @@ static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct time u->sink->core->mainloop->time_restart(e, &n); } +static void process_render_null(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + pa_assert(u); + + if (u->thread_info.in_null_mode) + u->thread_info.timestamp = now; + + while (u->thread_info.timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + + u->thread_info.counter += chunk.length; + +/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ + u->thread_info.timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) + break; + } + +/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ + + pa_smoother_put(u->thread_info.smoother, now, + pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec) - (u->thread_info.timestamp - now)); +} + static void thread_func(void *userdata) { struct userdata *u = userdata; @@ -240,30 +280,23 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); - pa_rtclock_get(&u->thread_info.timestamp); + u->thread_info.timestamp = pa_rtclock_usec(); u->thread_info.in_null_mode = FALSE; for (;;) { int ret; /* If no outputs are connected, render some data and drop it immediately. */ - if (u->sink->thread_info.state == PA_SINK_RUNNING && !u->thread_info.active_outputs) { - struct timeval now; - - pa_rtclock_get(&now); + if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && !u->thread_info.active_outputs) { + pa_usec_t now; - if (!u->thread_info.in_null_mode || pa_timeval_cmp(&u->thread_info.timestamp, &now) <= 0) { - pa_sink_skip(u->sink, u->block_size); + now = pa_rtclock_usec(); - if (!u->thread_info.in_null_mode) - u->thread_info.timestamp = now; + if (!u->thread_info.in_null_mode || u->thread_info.timestamp <= now) + process_render_null(u, now); - pa_timeval_add(&u->thread_info.timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); - } - - pa_rtpoll_set_timer_absolute(u->rtpoll, &u->thread_info.timestamp); + pa_rtpoll_set_timer_absolute(u->rtpoll, u->thread_info.timestamp); u->thread_info.in_null_mode = TRUE; - } else { pa_rtpoll_set_timer_disabled(u->rtpoll); u->thread_info.in_null_mode = FALSE; @@ -295,7 +328,7 @@ static void render_memblock(struct userdata *u, struct output *o, size_t length) pa_assert(o); /* We are run by the sink thread, on behalf of an output (o). The - * other output is waiting for us, hence it is safe to access its + * output is waiting for us, hence it is safe to access its * mainblockq and asyncmsgq directly. */ /* If we are not running, we cannot produce any data */ @@ -315,6 +348,8 @@ static void render_memblock(struct userdata *u, struct output *o, size_t length) /* Render data! */ pa_sink_render(u->sink, length, &chunk); + u->thread_info.counter += chunk.length; + /* OK, let's send this data to the other threads */ for (j = u->thread_info.active_outputs; j; j = j->next) @@ -354,27 +389,56 @@ static void request_memblock(struct output *o, size_t length) { } /* Called from I/O thread context */ -static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct output *o; pa_sink_input_assert_ref(i); pa_assert_se(o = i->userdata); /* If necessary, get some new data */ - request_memblock(o, length); + request_memblock(o, nbytes); + + if (pa_memblockq_peek(o->memblockq, chunk) < 0) + return -1; + + pa_memblockq_drop(o->memblockq, chunk->length); + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(nbytes > 0); + pa_assert_se(o = i->userdata); + + pa_memblockq_rewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); - return pa_memblockq_peek(o->memblockq, chunk); + pa_memblockq_set_maxrewind(o->memblockq, nbytes); } /* Called from I/O thread context */ -static void sink_input_drop_cb(pa_sink_input *i, size_t length) { +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { struct output *o; pa_sink_input_assert_ref(i); - pa_assert(length > 0); pa_assert_se(o = i->userdata); - pa_memblockq_drop(o->memblockq, length); + if (pa_atomic_load(&o->max_request) == (int) nbytes) + return; + + pa_atomic_store(&o->max_request, (int) nbytes); + + pa_asyncmsgq_post(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_UPDATE_MAX_REQUEST, NULL, 0, NULL, NULL); } /* Called from I/O thread context */ @@ -385,11 +449,17 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_assert_se(o = i->userdata); /* Set up the queue from the sink thread to us */ - pa_assert(!o->inq_rtpoll_item); - o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + pa_assert(!o->inq_rtpoll_item_read && !o->outq_rtpoll_item_write); + + o->inq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( i->sink->rtpoll, PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ o->inq); + + o->outq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + i->sink->rtpoll, + PA_RTPOLL_EARLY, + o->outq); } /* Called from I/O thread context */ @@ -400,9 +470,13 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_assert_se(o = i->userdata); /* Shut down the queue from the sink thread to us */ - pa_assert(o->inq_rtpoll_item); - pa_rtpoll_item_free(o->inq_rtpoll_item); - o->inq_rtpoll_item = NULL; + pa_assert(o->inq_rtpoll_item_read && o->outq_rtpoll_item_write); + + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + o->inq_rtpoll_item_read = NULL; + + pa_rtpoll_item_free(o->outq_rtpoll_item_write); + o->outq_rtpoll_item_write = NULL; } /* Called from main context */ @@ -416,6 +490,20 @@ static void sink_input_kill_cb(pa_sink_input *i) { output_free(o); } +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + /* Called from thread context */ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct output *o = PA_SINK_INPUT(obj)->userdata; @@ -434,12 +522,12 @@ static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64 case SINK_INPUT_MESSAGE_POST: - if (PA_SINK_OPENED(o->sink_input->sink->thread_info.state)) + if (PA_SINK_IS_OPENED(o->sink_input->sink->thread_info.state)) pa_memblockq_push_align(o->memblockq, chunk); else pa_memblockq_flush(o->memblockq); - break; + return 0; } return pa_sink_input_process_msg(obj, code, data, offset, chunk); @@ -452,11 +540,10 @@ static void disable_output(struct output *o) { if (!o->sink_input) return; - pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); pa_sink_input_unlink(o->sink_input); + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); pa_sink_input_unref(o->sink_input); o->sink_input = NULL; - } /* Called from main context */ @@ -472,7 +559,7 @@ static void enable_output(struct output *o) { pa_sink_input_put(o->sink_input); - if (o->userdata->sink && PA_SINK_LINKED(pa_sink_get_state(o->userdata->sink))) + if (o->userdata->sink && PA_SINK_IS_LINKED(pa_sink_get_state(o->userdata->sink))) pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); } } @@ -488,8 +575,6 @@ static void suspend(struct userdata *u) { for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) disable_output(o); - pick_master(u, NULL); - pa_log_info("Device suspended..."); } @@ -505,12 +590,10 @@ static void unsuspend(struct userdata *u) { pa_sink_suspend(o->sink, FALSE); - if (PA_SINK_OPENED(pa_sink_get_state(o->sink))) + if (PA_SINK_IS_OPENED(pa_sink_get_state(o->sink))) enable_output(o); } - pick_master(u, NULL); - pa_log_info("Resumed successfully..."); } @@ -526,7 +609,7 @@ static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { switch (state) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(pa_sink_get_state(u->sink))); + pa_assert(PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))); suspend(u); break; @@ -547,7 +630,25 @@ static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { return 0; } -/* Called from thread context of the master */ +/* Called from IO context */ +static void update_max_request(struct userdata *u) { + size_t max_request = 0; + struct output *o; + + for (o = u->thread_info.active_outputs; o; o = o->next) { + size_t mr = (size_t) pa_atomic_load(&o->max_request); + + if (mr > max_request) + max_request = mr; + } + + if (max_request <= 0) + max_request = pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); + + pa_sink_set_max_request(u->sink, max_request); +} + +/* Called from thread context of the io thread */ 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; @@ -555,41 +656,47 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_SET_STATE: pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING); - break; - case PA_SINK_MESSAGE_GET_LATENCY: + if (PA_PTR_TO_UINT(data) == PA_SINK_SUSPENDED) + pa_smoother_pause(u->thread_info.smoother, pa_rtclock_usec()); + else + pa_smoother_resume(u->thread_info.smoother, pa_rtclock_usec()); - /* This code will only be called when running in NULL - * mode, i.e. when no output is attached. See - * sink_get_latency_cb() below */ + break; - if (u->thread_info.in_null_mode) { - struct timeval now; + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t x, y, c, *delay = data; - if (pa_timeval_cmp(&u->thread_info.timestamp, pa_rtclock_get(&now)) > 0) { - *((pa_usec_t*) data) = pa_timeval_diff(&u->thread_info.timestamp, &now); - break; - } - } + x = pa_rtclock_usec(); + y = pa_smoother_get(u->thread_info.smoother, x); - *((pa_usec_t*) data) = 0; + c = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); - break; + if (y < c) + *delay = c - y; + else + *delay = 0; + + return 0; + } case SINK_MESSAGE_ADD_OUTPUT: { struct output *op = data; PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op); - pa_assert(!op->outq_rtpoll_item); - - /* Create pa_asyncmsgq to the sink thread */ + pa_assert(!op->outq_rtpoll_item_read && !op->inq_rtpoll_item_write); - op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + op->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( u->rtpoll, PA_RTPOLL_EARLY-1, /* This item is very important */ op->outq); + op->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + u->rtpoll, + PA_RTPOLL_EARLY, + op->inq); + update_max_request(u); return 0; } @@ -598,56 +705,48 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); - /* Remove the q that leads from this output to the sink thread */ + pa_assert(op->outq_rtpoll_item_read && op->inq_rtpoll_item_write); - pa_assert(op->outq_rtpoll_item); - pa_rtpoll_item_free(op->outq_rtpoll_item); - op->outq_rtpoll_item = NULL; + pa_rtpoll_item_free(op->outq_rtpoll_item_read); + op->outq_rtpoll_item_read = NULL; + pa_rtpoll_item_free(op->inq_rtpoll_item_write); + op->inq_rtpoll_item_write = NULL; + + update_max_request(u); return 0; } case SINK_MESSAGE_NEED: - render_memblock(u, data, (size_t) offset); + render_memblock(u, (struct output*) data, (size_t) offset); return 0; - } - - return pa_sink_process_msg(o, code, data, offset, chunk); -} -/* Called from main context */ -static pa_usec_t sink_get_latency_cb(pa_sink *s) { - struct userdata *u; + case SINK_MESSAGE_UPDATE_LATENCY: { + pa_usec_t x, y, latency = (pa_usec_t) offset; - pa_sink_assert_ref(s); - pa_assert_se(u = s->userdata); + x = pa_rtclock_usec(); + y = pa_bytes_to_usec(u->thread_info.counter, &u->sink->sample_spec); - if (u->master) { - /* If we have a master sink, we just return the latency of it - * and add our own buffering on top */ + if (y > latency) + y -= latency; + else + y = 0; - if (!u->master->sink_input) + pa_smoother_put(u->thread_info.smoother, x, y); return 0; + } - return - pa_sink_input_get_latency(u->master->sink_input) + - pa_sink_get_latency(u->master->sink); - - } else { - pa_usec_t usec = 0; - - /* We have no master, hence let's ask our own thread which - * implements the NULL sink */ - - if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) - return 0; + case SINK_MESSAGE_UPDATE_MAX_REQUEST: - return usec; + update_max_request(u); + break; } + + return pa_sink_process_msg(o, code, data, offset, chunk); } static void update_description(struct userdata *u) { - int first = 1; + pa_bool_t first = TRUE; char *t; struct output *o; uint32_t idx; @@ -665,10 +764,10 @@ static void update_description(struct userdata *u) { char *e; if (first) { - e = pa_sprintf_malloc("%s %s", t, o->sink->description); - first = 0; + e = pa_sprintf_malloc("%s %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + first = FALSE; } else - e = pa_sprintf_malloc("%s, %s", t, o->sink->description); + e = pa_sprintf_malloc("%s, %s", t, pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); pa_xfree(t); t = e; @@ -678,57 +777,19 @@ static void update_description(struct userdata *u) { pa_xfree(t); } -static void update_master(struct userdata *u, struct output *o) { - pa_assert(u); - - if (u->master == o) - return; - - if ((u->master = o)) - pa_log_info("Master sink is now '%s'", o->sink_input->sink->name); - else - pa_log_info("No master selected, lacking suitable outputs."); -} - -static void pick_master(struct userdata *u, struct output *except) { - struct output *o; - uint32_t idx; - pa_assert(u); - - if (u->master && - u->master != except && - u->master->sink_input && - PA_SINK_OPENED(pa_sink_get_state(u->master->sink))) { - update_master(u, u->master); - return; - } - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - if (o != except && - o->sink_input && - PA_SINK_OPENED(pa_sink_get_state(o->sink))) { - update_master(u, o); - return; - } - - update_master(u, NULL); -} - static int output_create_sink_input(struct output *o) { pa_sink_input_new_data data; - char *t; pa_assert(o); if (o->sink_input) return 0; - t = pa_sprintf_malloc("Simultaneous output on %s", o->sink->description); - pa_sink_input_new_data_init(&data); data.sink = o->sink; data.driver = __FILE__; - data.name = t; + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "Simultaneous output on %s", pa_strnull(pa_proplist_gets(o->sink->proplist, PA_PROP_DEVICE_DESCRIPTION))); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "filter"); pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec); pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); data.module = o->userdata->module; @@ -736,25 +797,30 @@ static int output_create_sink_input(struct output *o) { o->sink_input = pa_sink_input_new(o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE); - pa_xfree(t); + pa_sink_input_new_data_done(&data); if (!o->sink_input) return -1; o->sink_input->parent.process_msg = sink_input_process_msg; - o->sink_input->peek = sink_input_peek_cb; - o->sink_input->drop = sink_input_drop_cb; + o->sink_input->pop = sink_input_pop_cb; + o->sink_input->process_rewind = sink_input_process_rewind_cb; + o->sink_input->state_change = sink_input_state_change_cb; + o->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + o->sink_input->update_max_request = sink_input_update_max_request_cb; o->sink_input->attach = sink_input_attach_cb; o->sink_input->detach = sink_input_detach_cb; o->sink_input->kill = sink_input_kill_cb; o->sink_input->userdata = o; + pa_sink_input_set_requested_latency(o->sink_input, REQUEST_LATENCY_USEC); return 0; } static struct output *output_new(struct userdata *u, pa_sink *sink) { struct output *o; + pa_sink_state_t state; pa_assert(u); pa_assert(sink); @@ -764,8 +830,8 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) { o->userdata = u; o->inq = pa_asyncmsgq_new(0); o->outq = pa_asyncmsgq_new(0); - o->inq_rtpoll_item = NULL; - o->outq_rtpoll_item = NULL; + o->inq_rtpoll_item_write = o->inq_rtpoll_item_read = NULL; + o->outq_rtpoll_item_write = o->outq_rtpoll_item_read = NULL; o->sink = sink; o->sink_input = NULL; o->memblockq = pa_memblockq_new( @@ -775,31 +841,39 @@ static struct output *output_new(struct userdata *u, pa_sink *sink) { pa_frame_size(&u->sink->sample_spec), 1, 0, + 0, NULL); + pa_atomic_store(&o->max_request, 0); + PA_LLIST_INIT(struct output, o); pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); - if (u->sink && PA_SINK_LINKED(pa_sink_get_state(u->sink))) + state = pa_sink_get_state(u->sink); + + if (state != PA_SINK_INIT) pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); else { /* If the sink is not yet started, we need to do the activation ourselves */ PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o); - o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + o->outq_rtpoll_item_read = pa_rtpoll_item_new_asyncmsgq_read( u->rtpoll, PA_RTPOLL_EARLY-1, /* This item is very important */ o->outq); + o->inq_rtpoll_item_write = pa_rtpoll_item_new_asyncmsgq_write( + u->rtpoll, + PA_RTPOLL_EARLY, + o->inq); } - if (PA_SINK_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) { + if (PA_SINK_IS_OPENED(state) || state == PA_SINK_INIT) { pa_sink_suspend(sink, FALSE); - if (PA_SINK_OPENED(pa_sink_get_state(sink))) + if (PA_SINK_IS_OPENED(pa_sink_get_state(sink))) if (output_create_sink_input(o) < 0) goto fail; } - update_description(u); return o; @@ -829,7 +903,25 @@ fail: return NULL; } -static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { +static pa_bool_t is_suitable_sink(struct userdata *u, pa_sink *s) { + const char *t; + + pa_sink_assert_ref(s); + + if (!(s->flags & PA_SINK_HARDWARE)) + return FALSE; + + if (s == u->sink) + return FALSE; + + if ((t = pa_proplist_gets(s->proplist, PA_PROP_DEVICE_CLASS))) + if (strcmp(t, "sound")) + return FALSE; + + return TRUE; +} + +static pa_hook_result_t sink_put_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; pa_core_assert_ref(c); @@ -837,7 +929,7 @@ static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata pa_assert(u); pa_assert(u->automatic); - if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + if (!is_suitable_sink(u, s)) return PA_HOOK_OK; pa_log_info("Configuring new sink: %s", s->name); @@ -850,27 +942,34 @@ static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata if (o->sink_input) pa_sink_input_put(o->sink_input); - pick_master(u, NULL); - return PA_HOOK_OK; } -static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { +static struct output* find_output(struct userdata *u, pa_sink *s) { struct output *o; uint32_t idx; - pa_assert(c); - pa_sink_assert_ref(s); pa_assert(u); + pa_assert(s); - if (s == u->sink) - return PA_HOOK_OK; + if (u->sink == s) + return NULL; for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) if (o->sink == s) - break; + return o; + + return NULL; +} + +static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; - if (!o) + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (!(o = find_output(u, s))) return PA_HOOK_OK; pa_log_info("Unconfiguring sink: %s", s->name); @@ -882,30 +981,18 @@ static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userd static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { struct output *o; - uint32_t idx; pa_sink_state_t state; - if (s == u->sink) - return PA_HOOK_OK; - - for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) - if (o->sink == s) - break; - - if (!o) + if (!(o = find_output(u, s))) return PA_HOOK_OK; state = pa_sink_get_state(s); - if (PA_SINK_OPENED(state) && PA_SINK_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) { + if (PA_SINK_IS_OPENED(state) && PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) enable_output(o); - pick_master(u, NULL); - } - if (state == PA_SINK_SUSPENDED && o->sink_input) { + if (state == PA_SINK_SUSPENDED && o->sink_input) disable_output(o); - pick_master(u, o); - } return PA_HOOK_OK; } @@ -913,13 +1000,13 @@ static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struc int pa__init(pa_module*m) { struct userdata *u; pa_modargs *ma = NULL; - const char *master_name, *slaves, *rm; - pa_sink *master_sink = NULL; + const char *slaves, *rm; int resample_method = PA_RESAMPLER_TRIVIAL; pa_sample_spec ss; pa_channel_map map; struct output *o; uint32_t idx; + pa_sink_new_data data; pa_assert(m); @@ -935,111 +1022,85 @@ int pa__init(pa_module*m) { } } - u = pa_xnew(struct userdata, 1); + m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; u->module = m; - m->userdata = u; u->sink = NULL; - u->master = NULL; u->time_event = NULL; u->adjust_time = DEFAULT_ADJUST_TIME; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->thread = NULL; u->resample_method = resample_method; u->outputs = pa_idxset_new(NULL, NULL); memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp)); - u->sink_new_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; + u->sink_put_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs); pa_atomic_store(&u->thread_info.running, FALSE); u->thread_info.in_null_mode = FALSE; - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->thread_info.counter = 0; + u->thread_info.smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { pa_log("Failed to parse adjust_time value"); goto fail; } - master_name = pa_modargs_get_value(ma, "master", NULL); slaves = pa_modargs_get_value(ma, "slaves", NULL); - if (!master_name != !slaves) { - pa_log("No master or slave sinks specified"); - goto fail; - } - - if (master_name) { - if (!(master_sink = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK, 1))) { - pa_log("Invalid master sink '%s'", master_name); - goto fail; - } - - ss = master_sink->sample_spec; - u->automatic = FALSE; - } else { - master_sink = NULL; - ss = m->core->default_sample_spec; - u->automatic = TRUE; - } + u->automatic = !slaves; + ss = m->core->default_sample_spec; - if ((pa_modargs_get_sample_spec(ma, &ss) < 0)) { + if ((pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0)) { pa_log("Invalid sample specification."); goto fail; } - if (master_sink && ss.channels == master_sink->sample_spec.channels) - map = master_sink->channel_map; - else { - pa_assert_se(pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); - } + pa_sink_new_data_init(&data); + data.namereg_fail = FALSE; + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Simultaneous Output"); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "filter"); - if ((pa_modargs_get_channel_map(ma, NULL, &map) < 0)) { - pa_log("Invalid channel map."); - goto fail; - } + if (slaves) + pa_proplist_sets(data.proplist, "combine.slaves", slaves); - if (ss.channels != map.channels) { - pa_log("Channel map and sample specification don't match."); - goto fail; - } + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + if (!u->sink) { pa_log("Failed to create sink"); goto fail; } u->sink->parent.process_msg = sink_process_msg; - u->sink->get_latency = sink_get_latency_cb; u->sink->set_state = sink_set_state; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); - pa_sink_set_description(u->sink, "Simultaneous output"); pa_sink_set_rtpoll(u->sink, u->rtpoll); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); - u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ - if (u->block_size <= 0) - u->block_size = pa_frame_size(&ss); + pa_sink_set_latency_range(u->sink, REQUEST_LATENCY_USEC, REQUEST_LATENCY_USEC); + u->block_usec = u->sink->thread_info.max_latency; + + u->sink->thread_info.max_request = + pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); if (!u->automatic) { const char*split_state; char *n = NULL; pa_assert(slaves); - /* The master and slaves have been specified manually */ - - if (!(u->master = output_new(u, master_sink))) { - pa_log("Failed to create master sink input on sink '%s'.", master_sink->name); - goto fail; - } + /* The slaves have been specified manually */ split_state = NULL; while ((n = pa_split(slaves, ",", &split_state))) { pa_sink *slave_sink; - if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK, 1)) || slave_sink == u->sink) { + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK, TRUE)) || slave_sink == u->sink) { pa_log("Invalid slave sink '%s'", n); pa_xfree(n); goto fail; @@ -1056,17 +1117,16 @@ int pa__init(pa_module*m) { if (pa_idxset_size(u->outputs) <= 1) pa_log_warn("No slave sinks specified."); - u->sink_new_slot = NULL; + u->sink_put_slot = NULL; } else { pa_sink *s; - /* We're in automatic mode, we elect one hw sink to the master - * and attach all other hw sinks as slaves to it */ + /* We're in automatic mode, we add every sink that matches our needs */ for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { - if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + if (!is_suitable_sink(u, s)) continue; if (!output_new(u, s)) { @@ -1075,13 +1135,11 @@ int pa__init(pa_module*m) { } } - u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) sink_new_hook_cb, u); + u->sink_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) sink_put_hook_cb, u); } - u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_unlink_hook_cb, u); - u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) sink_state_changed_hook_cb, u); - - pick_master(u, NULL); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) sink_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_hook_cb, u); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); @@ -1119,19 +1177,21 @@ fail: static void output_free(struct output *o) { pa_assert(o); - pick_master(o->userdata, o); - disable_output(o); pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); update_description(o->userdata); - if (o->inq_rtpoll_item) - pa_rtpoll_item_free(o->inq_rtpoll_item); + if (o->inq_rtpoll_item_read) + pa_rtpoll_item_free(o->inq_rtpoll_item_read); + if (o->inq_rtpoll_item_write) + pa_rtpoll_item_free(o->inq_rtpoll_item_write); - if (o->outq_rtpoll_item) - pa_rtpoll_item_free(o->outq_rtpoll_item); + if (o->outq_rtpoll_item_read) + pa_rtpoll_item_free(o->outq_rtpoll_item_read); + if (o->outq_rtpoll_item_write) + pa_rtpoll_item_free(o->outq_rtpoll_item_write); if (o->inq) pa_asyncmsgq_unref(o->inq); @@ -1154,8 +1214,8 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink_new_slot) - pa_hook_slot_free(u->sink_new_slot); + if (u->sink_put_slot) + pa_hook_slot_free(u->sink_put_slot); if (u->sink_unlink_slot) pa_hook_slot_free(u->sink_unlink_slot); @@ -1189,5 +1249,8 @@ void pa__done(pa_module*m) { if (u->time_event) u->core->mainloop->time_free(u->time_event); + if (u->thread_info.smoother) + pa_smoother_free(u->thread_info.smoother); + pa_xfree(u); } diff --git a/src/modules/module-console-kit.c b/src/modules/module-console-kit.c new file mode 100644 index 00000000..3adee99e --- /dev/null +++ b/src/modules/module-console-kit.c @@ -0,0 +1,334 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + 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 <config.h> +#endif + +#include <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> + +#include "dbus-util.h" +#include "module-console-kit-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Create a client for each ConsoleKit session of this user"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL +}; + +struct session { + char *id; + pa_client *client; +}; + +struct userdata { + pa_core *core; + pa_dbus_connection *connection; + pa_hashmap *sessions; +}; + +static void add_session(struct userdata *u, const char *id) { + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + int32_t uid; + struct session *session; + char *t; + + dbus_error_init (&error); + + if (pa_hashmap_get(u->sessions, id)) { + pa_log_warn("Duplicate session %s, ignoring.", id); + return; + } + + if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", id, "org.freedesktop.ConsoleKit.Session", "GetUnixUser"))) { + pa_log("Failed to allocate GetUnixUser() method call."); + goto fail; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { + pa_log("GetUnixUser() call failed: %s: %s", error.name, error.message); + goto fail; + } + + /* FIXME: Why is this in int32? and not an uint32? */ + if (!dbus_message_get_args(reply, &error, DBUS_TYPE_INT32, &uid, DBUS_TYPE_INVALID)) { + pa_log("Failed to parse GetUnixUser() result: %s: %s", error.name, error.message); + goto fail; + } + + /* We only care about our own sessions */ + if ((uid_t) uid != getuid()) + goto fail; + + session = pa_xnew(struct session, 1); + session->id = pa_xstrdup(id); + + t = pa_sprintf_malloc("ConsoleKit Session %s", id); + session->client = pa_client_new(u->core, __FILE__, t); + pa_xfree(t); + + pa_proplist_sets(session->client->proplist, "console-kit.session", id); + + pa_hashmap_put(u->sessions, session->id, session); + + pa_log_debug("Added new session %s", id); + +fail: + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); +} + +static void free_session(struct session *session) { + pa_assert(session); + + pa_log_debug("Removing session %s", session->id); + + pa_client_free(session->client); + pa_xfree(session->id); + pa_xfree(session); +} + +static void remove_session(struct userdata *u, const char *id) { + struct session *session; + + if (!(session = pa_hashmap_remove(u->sessions, id))) + return; + + free_session(session); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + struct userdata *u = userdata; + DBusError error; + const char *path; + + pa_assert(bus); + pa_assert(message); + pa_assert(u); + + dbus_error_init(&error); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(message), + dbus_message_get_path(message), + dbus_message_get_member(message)); + + if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionAdded")) { + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse SessionAdded message: %s: %s", error.name, error.message); + goto finish; + } + + add_session(u, path); + + } else if (dbus_message_is_signal(message, "org.freedesktop.ConsoleKit.Seat", "SessionRemoved")) { + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_STRING, &path, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse SessionRemoved message: %s: %s", error.name, error.message); + goto finish; + } + + remove_session(u, path); + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static int get_session_list(struct userdata *u) { + DBusError error; + DBusMessage *m = NULL, *reply = NULL; + uint32_t uid; + DBusMessageIter iter, sub; + int ret = -1; + + pa_assert(u); + + dbus_error_init(&error); + + if (!(m = dbus_message_new_method_call("org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "GetSessionsForUnixUser"))) { + pa_log("Failed to allocate GetSessionsForUnixUser() method call."); + goto fail; + } + + uid = (uint32_t) getuid(); + if (!(dbus_message_append_args(m, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID))) { + pa_log("Failed to append arguments to GetSessionsForUnixUser() method call."); + goto fail; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->connection), m, -1, &error))) { + pa_log("GetSessionsForUnixUser() call failed: %s: %s", error.name, error.message); + goto fail; + } + + dbus_message_iter_init(reply, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&iter) != DBUS_TYPE_OBJECT_PATH) { + pa_log("Failed to parse GetSessionsForUnixUser() result."); + goto fail; + } + + dbus_message_iter_recurse(&iter, &sub); + + for (;;) { + int at; + const char *id; + + if ((at = dbus_message_iter_get_arg_type(&sub)) == DBUS_TYPE_INVALID) + break; + + assert(at == DBUS_TYPE_OBJECT_PATH); + dbus_message_iter_get_basic(&sub, &id); + + add_session(u, id); + + dbus_message_iter_next(&sub); + } + + ret = 0; + +fail: + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + dbus_error_free(&error); + + return ret; +} + +int pa__init(pa_module*m) { + DBusError error; + pa_dbus_connection *connection; + struct userdata *u = NULL; + pa_modargs *ma; + + pa_assert(m); + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!(connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + + if (connection) + pa_dbus_connection_unref(connection); + + pa_log_error("Unable to contact D-Bus system bus: %s: %s", error.name, error.message); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->connection = connection; + u->sessions = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (!dbus_connection_add_filter(pa_dbus_connection_get(connection), filter_cb, u, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(connection), "type='signal',sender='org.freedesktop.ConsoleKit', interface='org.freedesktop.ConsoleKit.Seat'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to ConsoleKit signals: %s: %s", error.name, error.message); + goto fail; + } + + if (get_session_list(u) < 0) + goto fail; + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + pa__done(m); + + return -1; +} + + +void pa__done(pa_module *m) { + struct userdata *u; + struct session *session; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sessions) { + while ((session = pa_hashmap_steal_first(u->sessions))) + free_session(session); + + pa_hashmap_free(u->sessions, NULL, NULL); + } + + if (u->connection) + pa_dbus_connection_unref(u->connection); + + pa_xfree(u); +} diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c index b550ae78..2168ac71 100644 --- a/src/modules/module-default-device-restore.c +++ b/src/modules/module-default-device-restore.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2006 Lennart Poettering + Copyright 2006-2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -25,10 +23,16 @@ #include <config.h> #endif +#include <errno.h> +#include <stdio.h> + +#include <pulse/timeval.h> + #include <pulsecore/core-util.h> #include <pulsecore/module.h> #include <pulsecore/log.h> #include <pulsecore/namereg.h> +#include <pulsecore/core-error.h> #include "module-default-device-restore-symdef.h" @@ -39,15 +43,24 @@ PA_MODULE_LOAD_ONCE(TRUE); #define DEFAULT_SINK_FILE "default-sink" #define DEFAULT_SOURCE_FILE "default-source" +#define DEFAULT_SAVE_INTERVAL 5 -int pa__init(pa_module *m) { +struct userdata { + pa_core *core; + pa_subscription *subscription; + pa_time_event *time_event; + char *sink_filename, *source_filename; + pa_bool_t modified; +}; + +static void load(struct userdata *u) { FILE *f; /* We never overwrite manually configured settings */ - if (m->core->default_sink_name) + if (u->core->default_sink_name) pa_log_info("Manually configured default sink, not overwriting."); - else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) { + else if ((f = fopen(u->sink_filename, "r"))) { char ln[256] = ""; fgets(ln, sizeof(ln)-1, f); @@ -55,17 +68,19 @@ int pa__init(pa_module *m) { fclose(f); if (!ln[0]) - pa_log_debug("No previous default sink setting, ignoring."); - else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) { - pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK); - pa_log_debug("Restored default sink '%s'.", ln); + pa_log_info("No previous default sink setting, ignoring."); + else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SINK, TRUE)) { + pa_namereg_set_default(u->core, ln, PA_NAMEREG_SINK); + pa_log_info("Restored default sink '%s'.", ln); } else pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln); - } - if (m->core->default_source_name) + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); + + if (u->core->default_source_name) pa_log_info("Manually configured default source, not overwriting."); - else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) { + else if ((f = fopen(u->source_filename, "r"))) { char ln[256] = ""; fgets(ln, sizeof(ln)-1, f); @@ -73,29 +88,114 @@ int pa__init(pa_module *m) { fclose(f); if (!ln[0]) - pa_log_debug("No previous default source setting, ignoring."); - else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) { - pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE); - pa_log_debug("Restored default source '%s'.", ln); + pa_log_info("No previous default source setting, ignoring."); + else if (pa_namereg_get(u->core, ln, PA_NAMEREG_SOURCE, TRUE)) { + pa_namereg_set_default(u->core, ln, PA_NAMEREG_SOURCE); + pa_log_info("Restored default source '%s'.", ln); } else pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln); - } - return 0; + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); } -void pa__done(pa_module*m) { +static void save(struct userdata *u) { FILE *f; - if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) { - const char *n = pa_namereg_get_default_sink_name(m->core); - fprintf(f, "%s\n", n ? n : ""); - fclose(f); + if (!u->modified) + return; + + if (u->sink_filename) { + if ((f = fopen(u->sink_filename, "w"))) { + const char *n = pa_namereg_get_default_sink_name(u->core); + fprintf(f, "%s\n", pa_strempty(n)); + fclose(f); + } else + pa_log("Failed to save default sink: %s", pa_cstrerror(errno)); } - if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) { - const char *n = pa_namereg_get_default_source_name(m->core); - fprintf(f, "%s\n", n ? n : ""); - fclose(f); + if (u->source_filename) { + if ((f = fopen(u->source_filename, "w"))) { + const char *n = pa_namereg_get_default_source_name(u->core); + fprintf(f, "%s\n", pa_strempty(n)); + fclose(f); + } else + pa_log("Failed to save default source: %s", pa_cstrerror(errno)); + } + + u->modified = FALSE; +} + +static void time_cb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + save(u); + + if (u->time_event) { + u->core->mainloop->time_free(u->time_event); + u->time_event = NULL; } } + +static void subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + u->modified = TRUE; + + if (!u->time_event) { + struct timeval tv; + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEFAULT_SAVE_INTERVAL*PA_USEC_PER_SEC); + u->time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, time_cb, u); + } +} + +int pa__init(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + + if (!(u->sink_filename = pa_state_path(DEFAULT_SINK_FILE))) + goto fail; + + if (!(u->source_filename = pa_state_path(DEFAULT_SOURCE_FILE))) + goto fail; + + load(u); + + u->subscription = pa_subscription_new(u->core, PA_SUBSCRIPTION_MASK_SERVER, subscribe_cb, u); + + return 0; + +fail: + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + save(u); + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->time_event) + m->core->mainloop->time_free(u->time_event); + + pa_xfree(u->sink_filename); + pa_xfree(u->source_filename); + pa_xfree(u); +} diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 index a49e8329..64ce1928 100644 --- a/src/modules/module-defs.h.m4 +++ b/src/modules/module-defs.h.m4 @@ -1,4 +1,3 @@ -dnl $Id$ changecom(`/*', `*/')dnl define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index ee650dfd..13bcfcd1 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c new file mode 100644 index 00000000..b7a1e1b5 --- /dev/null +++ b/src/modules/module-device-restore.c @@ -0,0 +1,348 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + + 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 <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> +#include <gdbm.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> +#include <pulse/util.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> + +#include "module-device-restore-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the volume/mute state of devices"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SAVE_INTERVAL 10 + +static const char* const valid_modargs[] = { + NULL, +}; + +struct userdata { + pa_core *core; + pa_subscription *subscription; + pa_hook_slot *sink_fixate_hook_slot, *source_fixate_hook_slot; + pa_time_event *save_time_event; + GDBM_FILE gdbm_file; +}; + +struct entry { + pa_cvolume volume; + int muted; +}; + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(tv); + pa_assert(u); + + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + gdbm_sync(u->gdbm_file); + pa_log_info("Synced."); +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + struct entry entry; + char *name; + datum key, data; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) { + pa_sink *sink; + + if (!(sink = pa_idxset_get_by_index(c->sinks, idx))) + return; + + name = pa_sprintf_malloc("sink:%s", sink->name); + entry.volume = *pa_sink_get_volume(sink); + entry.muted = pa_sink_get_mute(sink); + + } else { + pa_source *source; + + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE); + + if (!(source = pa_idxset_get_by_index(c->sources, idx))) + return; + + name = pa_sprintf_malloc("source:%s", source->name); + entry.volume = *pa_source_get_volume(source); + entry.muted = pa_source_get_mute(source); + } + + key.dptr = name; + key.dsize = strlen(name); + + data = gdbm_fetch(u->gdbm_file, key); + + if (data.dptr) { + + if (data.dsize == sizeof(struct entry)) { + struct entry *old = (struct entry*) data.dptr; + + if (pa_cvolume_valid(&old->volume)) { + + if (pa_cvolume_equal(&old->volume, &entry.volume) && + !old->muted == !entry.muted) { + + pa_xfree(data.dptr); + pa_xfree(name); + return; + } + } else + pa_log_warn("Invalid volume stored in database for device %s", name); + + } else + pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + + pa_xfree(data.dptr); + } + + data.dptr = (void*) &entry; + data.dsize = sizeof(entry); + + pa_log_info("Storing volume/mute for device %s.", name); + + gdbm_store(u->gdbm_file, key, data, GDBM_REPLACE); + + if (!u->save_time_event) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec += SAVE_INTERVAL; + u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); + } + + pa_xfree(name); +} + +static struct entry* read_entry(struct userdata *u, char *name) { + datum key, data; + struct entry *e; + + pa_assert(u); + pa_assert(name); + + key.dptr = name; + key.dsize = strlen(name); + + data = gdbm_fetch(u->gdbm_file, key); + + if (!data.dptr) + goto fail; + + if (data.dsize != sizeof(struct entry)) { + pa_log_warn("Database contains entry for device %s of wrong size %lu != %lu", name, (unsigned long) data.dsize, (unsigned long) sizeof(struct entry)); + goto fail; + } + + e = (struct entry*) data.dptr; + + if (!(pa_cvolume_valid(&e->volume))) { + pa_log_warn("Invalid volume stored in database for device %s", name); + goto fail; + } + + return e; + +fail: + + pa_xfree(data.dptr); + return NULL; +} + + +static pa_hook_result_t sink_fixate_hook_callback(pa_core *c, pa_sink_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(new_data); + + name = pa_sprintf_malloc("sink:%s", new_data->name); + + if ((e = read_entry(u, name))) { + + if (e->volume.channels == new_data->sample_spec.channels) { + pa_log_info("Restoring volume for sink %s.", new_data->name); + pa_sink_new_data_set_volume(new_data, &e->volume); + } + + pa_log_info("Restoring mute state for sink %s.", new_data->name); + pa_sink_new_data_set_muted(new_data, e->muted); + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_fixate_hook_callback(pa_core *c, pa_source_new_data *new_data, struct userdata *u) { + char *name; + struct entry *e; + + pa_assert(new_data); + + name = pa_sprintf_malloc("source:%s", new_data->name); + + if ((e = read_entry(u, name))) { + + if (e->volume.channels == new_data->sample_spec.channels) { + pa_log_info("Restoring volume for source %s.", new_data->name); + pa_source_new_data_set_volume(new_data, &e->volume); + } + + pa_log_info("Restoring mute state for source %s.", new_data->name); + pa_source_new_data_set_muted(new_data, e->muted); + pa_xfree(e); + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + char *fname, *fn; + char hn[256]; + pa_sink *sink; + pa_source *source; + uint32_t idx; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->save_time_event = NULL; + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, subscribe_callback, u); + + u->sink_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_fixate_hook_callback, u); + u->source_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) source_fixate_hook_callback, u); + + m->userdata = u; + + if (!pa_get_host_name(hn, sizeof(hn))) + goto fail; + + fn = pa_sprintf_malloc("device-volumes.%s.gdbm", hn); + fname = pa_state_path(fn); + pa_xfree(fn); + + if (!fname) + goto fail; + + if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT, 0600, NULL))) { + pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno)); + pa_xfree(fname); + goto fail; + } + + pa_log_info("Sucessfully opened database file '%s'.", fname); + pa_xfree(fname); + + for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW, sink->index, u); + + for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx)) + subscribe_callback(m->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW, source->index, u); + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + + 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->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_fixate_hook_slot) + pa_hook_slot_free(u->sink_fixate_hook_slot); + if (u->source_fixate_hook_slot) + pa_hook_slot_free(u->source_fixate_hook_slot); + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + if (u->gdbm_file) + gdbm_close(u->gdbm_file); + + pa_xfree(u); +} diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c index 8321192b..8eb4985b 100644 --- a/src/modules/module-esound-compat-spawnfd.c +++ b/src/modules/module-esound-compat-spawnfd.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index f9bea63d..e189febd 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -143,7 +141,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); pa_smoother_pause(u->smoother, pa_rtclock_usec()); break; @@ -211,7 +209,7 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); /* Render some data and write it to the fifo */ - if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) { + if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && pollfd->revents) { pa_usec_t usec; int64_t n; @@ -294,7 +292,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; + pollfd->events = PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) @@ -502,12 +500,11 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo int pa__init(pa_module*m) { struct userdata *u = NULL; - const char *p; pa_sample_spec ss; pa_modargs *ma = NULL; - char *t; const char *espeaker; uint32_t key; + pa_sink_new_data data; pa_assert(m); @@ -533,13 +530,12 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; u->fd = -1; - u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE); + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); pa_memchunk_reset(&u->memchunk); u->offset = 0; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->rtpoll_item = NULL; u->format = @@ -554,30 +550,38 @@ int pa__init(pa_module*m) { u->state = STATE_AUTH; u->latency = 0; - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { + if (!(espeaker = getenv("ESPEAKER"))) + espeaker = ESD_UNIX_SOCKET_NAME; + + espeaker = pa_modargs_get_value(ma, "server", espeaker); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, espeaker); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Esound sink '%s'", espeaker); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); + pa_sink_new_data_done(&data); + + if (!u->sink) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK; - pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - if (!(espeaker = getenv("ESPEAKER"))) - espeaker = ESD_UNIX_SOCKET_NAME; - - if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) { + if (!(u->client = pa_socket_client_new_string(u->core->mainloop, espeaker, ESD_DEFAULT_PORT))) { pa_log("Failed to connect to server."); goto fail; } - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p)); - pa_xfree(t); - pa_socket_client_set_callback(u->client, on_connection, u); /* Prepare the initial request */ diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 832bc73e..19430a3d 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -372,7 +370,7 @@ static int hal_device_add_all(struct userdata *u, const char *capability) { pa_log_debug("Not loaded device %s", udis[i]); else { if (d->sink_name) - pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0); + pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL); count++; } } @@ -412,7 +410,7 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const s pa_log_debug("Not loaded device %s", td->udi); else { if (d->sink_name) - pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0); + pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL); } } } @@ -575,7 +573,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo if (prev_suspended && !suspend) { /* resume */ if (pa_sink_suspend(sink, 0) >= 0) - pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL); else d->acl_race_fix = 1; @@ -643,7 +641,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo if (prev_suspended) { /* resume */ if (pa_sink_suspend(sink, 0) >= 0) - pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, FALSE, PA_VOLUME_NORM, NULL, NULL); } } } diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c index a42aa9ef..c4d47f8e 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/module-jack-sink.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -53,7 +51,7 @@ /* General overview: * - * Because JACK has a very unflexible event loop management, which + * Because JACK has a very unflexible event loop management which * doesn't allow us to add our own event sources to the event thread * we cannot use the JACK real-time thread for dispatching our PA * work. Instead, we run an additional RT thread which does most of @@ -276,7 +274,7 @@ int pa__init(pa_module*m) { pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; - char *t; + pa_sink_new_data data; pa_assert(m); @@ -300,9 +298,8 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; u->saved_frame_time_valid = FALSE; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); /* The queue linking the JACK thread and our RT thread */ u->jack_msgq = pa_asyncmsgq_new(0); @@ -312,7 +309,7 @@ int pa__init(pa_module*m) { * all other drivers make: supplying the audio device with data is * the top priority -- and as long as that is possible we don't do * anything else */ - u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { pa_log("jack_client_open() failed."); @@ -333,8 +330,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); + pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA); if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { pa_log("Failed to parse channel_map= argument."); goto fail; @@ -355,20 +351,31 @@ int pa__init(pa_module*m) { } } - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("failed to create sink."); + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); + if (server_name) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack sink (%s)", jack_get_client_name(u->client)); + pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client))); - pa_xfree(t); jack_set_process_callback(u->client, jack_process, u); jack_on_shutdown(u->client, jack_shutdown, u); diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c index 4ee08bf1..03f9d15c 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/module-jack-source.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -253,7 +251,7 @@ int pa__init(pa_module*m) { pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; - char *t; + pa_source_new_data data; pa_assert(m); @@ -278,12 +276,11 @@ int pa__init(pa_module*m) { m->userdata = u; u->saved_frame_time_valid = FALSE; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->jack_msgq = pa_asyncmsgq_new(0); - u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { pa_log("jack_client_open() failed."); @@ -304,8 +301,7 @@ int pa__init(pa_module*m) { goto fail; } - pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); + pa_channel_map_init_extend(&map, channels, PA_CHANNEL_MAP_ALSA); if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { pa_log("failed to parse channel_map= argument."); goto fail; @@ -326,20 +322,31 @@ int pa__init(pa_module*m) { } } - if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { - pa_log("failed to create source."); + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "jack"); + if (server_name) + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server_name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Jack source (%s)", jack_get_client_name(u->client)); + pa_proplist_sets(data.proplist, "jack.client_name", jack_get_client_name(u->client)); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { + pa_log("Failed to create source."); goto fail; } u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - u->source->flags = PA_SOURCE_LATENCY; - pa_source_set_module(u->source, m); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client))); - pa_xfree(t); jack_set_process_callback(u->client, jack_process, u); jack_on_shutdown(u->client, jack_shutdown, u); diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index fcfeffd5..3e0babfa 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -41,6 +39,7 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/rtpoll.h> #include <pulsecore/sample-util.h> +#include <pulsecore/ltdl-helper.h> #include "module-ladspa-sink-symdef.h" #include "ladspa.h" @@ -60,6 +59,8 @@ PA_MODULE_USAGE( "label=<ladspa plugin label> " "control=<comma seperated list of input control values>"); +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) + struct userdata { pa_core *core; pa_module *module; @@ -79,7 +80,7 @@ struct userdata { about control out ports. We connect them all to this single buffer. */ LADSPA_Data control_out; - pa_memchunk memchunk; + pa_memblockq *memblockq; }; static const char* const valid_modargs[] = { @@ -104,10 +105,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t usec = 0; + /* Get the latency of the master sink */ if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) usec = 0; - *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + /* Add the latency internal to our sink input on top */ + usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + + *((pa_usec_t*) data) = usec; return 0; } } @@ -122,110 +127,170 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + if (PA_SINK_IS_LINKED(state) && + u->sink_input && + PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } /* Called from I/O thread context */ -static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK_INPUT(o)->userdata; +static void sink_request_rewind(pa_sink *s) { + struct userdata *u; - switch (code) { - case PA_SINK_INPUT_MESSAGE_GET_LATENCY: - *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); - /* Fall through, the default handler will add in the extra - * latency added by the resampler */ - break; - } + /* Just hand this one over to the master sink */ + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes + pa_memblockq_get_length(u->memblockq), TRUE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); - return pa_sink_input_process_msg(o, code, data, offset, chunk); + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); } /* Called from I/O thread context */ -static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct userdata *u; + float *src, *dst; + size_t fs; + unsigned n, c; + pa_memchunk tchunk; pa_sink_input_assert_ref(i); + pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->memchunk.memblock) { - pa_memchunk tchunk; - float *src, *dst; - size_t fs; - unsigned n, c; - - pa_sink_render(u->sink, length, &tchunk); + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return -1; - fs = pa_frame_size(&i->sample_spec); - n = tchunk.length / fs; + while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; - pa_assert(n > 0); - - u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length); - u->memchunk.index = 0; - u->memchunk.length = tchunk.length; + pa_sink_render(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } - src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); - dst = (float*) pa_memblock_acquire(u->memchunk.memblock); + tchunk.length = PA_MIN(nbytes, tchunk.length); + pa_assert(tchunk.length > 0); - for (c = 0; c < u->channels; c++) { - unsigned j; - float *p, *q; + fs = pa_frame_size(&i->sample_spec); + n = PA_MIN(tchunk.length, u->block_size) / fs; - p = src + c; - q = u->input; - for (j = 0; j < n; j++, p += u->channels, q++) - *q = PA_CLAMP_UNLIKELY(*p, -1.0, 1.0); + pa_assert(n > 0); - u->descriptor->run(u->handle[c], n); + chunk->index = 0; + chunk->length = n*fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - q = u->output; - p = dst + c; - for (j = 0; j < n; j++, q++, p += u->channels) - *p = PA_CLAMP_UNLIKELY(*q, -1.0, 1.0); - } + pa_memblockq_drop(u->memblockq, chunk->length); - pa_memblock_release(tchunk.memblock); - pa_memblock_release(u->memchunk.memblock); + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(chunk->memblock); - pa_memblock_unref(tchunk.memblock); + for (c = 0; c < u->channels; c++) { + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, u->input, sizeof(float), src+c, u->channels*sizeof(float), n); + u->descriptor->run(u->handle[c], n); + pa_sample_clamp(PA_SAMPLE_FLOAT32NE, dst+c, u->channels*sizeof(float), u->output, sizeof(float), n); } - pa_assert(u->memchunk.length > 0); - pa_assert(u->memchunk.memblock); + pa_memblock_release(tchunk.memblock); + pa_memblock_release(chunk->memblock); - *chunk = u->memchunk; - pa_memblock_ref(chunk->memblock); + pa_memblock_unref(tchunk.memblock); return 0; } /* Called from I/O thread context */ -static void sink_input_drop_cb(pa_sink_input *i, size_t length) { +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_assert(length > 0); + pa_assert(nbytes > 0); - if (u->memchunk.memblock) { + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return; - if (length < u->memchunk.length) { - u->memchunk.index += length; - u->memchunk.length -= length; - return; - } + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t max_rewrite, amount; + + max_rewrite = nbytes + pa_memblockq_get_length(u->memblockq); + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, max_rewrite); + u->sink->thread_info.rewind_nbytes = 0; - pa_memblock_unref(u->memchunk.memblock); - length -= u->memchunk.length; - pa_memchunk_reset(&u->memchunk); + if (amount > 0) { + unsigned c; + + pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE); + pa_sink_process_rewind(u->sink, amount); + + pa_log_debug("Resetting plugin"); + + /* Reset the plugin */ + if (u->descriptor->deactivate) + for (c = 0; c < u->channels; c++) + u->descriptor->deactivate(u->handle[c]); + if (u->descriptor->activate) + for (c = 0; c < u->channels; c++) + u->descriptor->activate(u->handle[c]); + } } - if (length > 0) - pa_sink_skip(u->sink, length); + pa_memblockq_rewind(u->memblockq, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_memblockq_set_maxrewind(u->memblockq, nbytes); + pa_sink_set_max_rewind(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_request(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ @@ -235,7 +300,12 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + pa_sink_detach_within_thread(u->sink); + pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } /* Called from I/O thread context */ @@ -245,10 +315,14 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); - pa_sink_attach_within_thread(u->sink); + + pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -258,25 +332,43 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + pa_sink_unlink(u->sink); pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - u->sink_input = NULL; - pa_sink_unlink(u->sink); pa_sink_unref(u->sink); u->sink = NULL; + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; pa_module_unload_request(u->module); } +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); + } +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; char *t; + const char *z; pa_sink *master; - pa_sink_input_new_data data; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; const char *plugin, *label; LADSPA_Descriptor_Function descriptor_func; const char *e, *cdata; @@ -284,7 +376,6 @@ int pa__init(pa_module*m) { unsigned long input_port, output_port, p, j, n_control; unsigned c; pa_bool_t *use_default = NULL; - char *default_sink_name = NULL; pa_assert(m); @@ -325,7 +416,9 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; u->master = master; - pa_memchunk_reset(&u->memchunk); + u->sink = NULL; + u->sink_input = NULL; + u->memblockq = pa_memblockq_new(0, MEMBLOCKQ_MAXLENGTH, 0, pa_frame_size(&ss), 1, 1, 0, NULL); if (!(e = getenv("LADSPA_PATH"))) e = LADSPA_PATH; @@ -342,7 +435,7 @@ int pa__init(pa_module*m) { goto fail; } - if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) { + if (!(descriptor_func = (LADSPA_Descriptor_Function) pa_load_sym(m->dl, NULL, "ladspa_descriptor"))) { pa_log("LADSPA module lacks ladspa_descriptor() symbol."); goto fail; } @@ -350,7 +443,7 @@ int pa__init(pa_module*m) { for (j = 0;; j++) { if (!(d = descriptor_func(j))) { - pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label); + pa_log("Failed to find plugin label '%s' in plugin '%s'.", label, plugin); goto fail; } @@ -442,7 +535,7 @@ int pa__init(pa_module*m) { p = 0; while ((k = pa_split(cdata, ",", &state)) && p < n_control) { - float f; + double f; if (*k == 0) { use_default[p++] = TRUE; @@ -450,7 +543,7 @@ int pa__init(pa_module*m) { continue; } - if (pa_atof(k, &f) < 0) { + if (pa_atod(k, &f) < 0) { pa_log("Failed to parse control value '%s'", k); pa_xfree(k); goto fail; @@ -582,43 +675,68 @@ int pa__init(pa_module*m) { for (c = 0; c < u->channels; c++) d->activate(u->handle[c]); - default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name); - /* Create sink */ - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) { + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.ladspa", master->name); + sink_data.namereg_fail = FALSE; + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &map); + z = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "LADSPA Plugin %s on %s", label, z ? z : master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + pa_proplist_sets(sink_data.proplist, "device.ladspa.module", plugin); + pa_proplist_sets(sink_data.proplist, "device.ladspa.label", d->Label); + pa_proplist_sets(sink_data.proplist, "device.ladspa.name", d->Name); + pa_proplist_sets(sink_data.proplist, "device.ladspa.maker", d->Maker); + pa_proplist_sets(sink_data.proplist, "device.ladspa.copyright", d->Copyright); + pa_proplist_setf(sink_data.proplist, "device.ladspa.unique_id", "%lu", (unsigned long) d->UniqueID); + + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->set_state = sink_set_state; + u->sink->update_requested_latency = sink_update_requested_latency; + u->sink->request_rewind = sink_request_rewind; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description)); - pa_xfree(t); pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ - pa_sink_input_new_data_init(&data); - data.sink = u->master; - data.driver = __FILE__; - data.name = "LADSPA Stream"; - pa_sink_input_new_data_set_sample_spec(&data, &ss); - pa_sink_input_new_data_set_channel_map(&data, &map); - data.module = m; - - if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = u->master; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "LADSPA Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &map); + + u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) goto fail; - u->sink_input->parent.process_msg = sink_input_process_msg; - u->sink_input->peek = sink_input_peek_cb; - u->sink_input->drop = sink_input_drop_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; + u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); @@ -627,7 +745,6 @@ int pa__init(pa_module*m) { pa_modargs_free(ma); pa_xfree(use_default); - pa_xfree(default_sink_name); return 0; @@ -636,7 +753,6 @@ fail: pa_modargs_free(ma); pa_xfree(use_default); - pa_xfree(default_sink_name); pa__done(m); @@ -652,18 +768,15 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink_input) { - pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - } - if (u->sink) { pa_sink_unlink(u->sink); pa_sink_unref(u->sink); } - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } for (c = 0; c < u->channels; c++) if (u->handle[c]) { @@ -675,6 +788,9 @@ void pa__done(pa_module*m) { if (u->output != u->input) pa_xfree(u->output); + if (u->memblockq) + pa_memblockq_free(u->memblockq); + pa_xfree(u->input); pa_xfree(u->control); diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index 24542172..0570a6a1 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-match.c b/src/modules/module-match.c index ed5f3076..769a6b59 100644 --- a/src/modules/module-match.c +++ b/src/modules/module-match.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -82,12 +80,14 @@ static int load_rules(struct userdata *u, const char *filename) { pa_assert(u); - f = filename ? - fopen(fn = pa_xstrdup(filename), "r") : - pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r"); + if (filename) + f = fopen(fn = pa_xstrdup(filename), "r"); + else + f = pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn); if (!f) { - pa_log("failed to open file '%s': %s", fn, pa_cstrerror(errno)); + pa_xfree(fn); + pa_log("Failed to open file config file: %s", pa_cstrerror(errno)); goto finish; } @@ -166,6 +166,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v struct userdata *u = userdata; pa_sink_input *si; struct rule *r; + const char *n; pa_assert(c); pa_assert(u); @@ -176,13 +177,13 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) return; - if (!si->name) + if (!(n = pa_proplist_gets(si->proplist, PA_PROP_MEDIA_NAME))) return; for (r = u->rules; r; r = r->next) { - if (!regexec(&r->regex, si->name, 0, NULL, 0)) { + if (!regexec(&r->regex, n, 0, NULL, 0)) { pa_cvolume cv; - pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume); + pa_log_debug("changing volume of sink input '%s' to 0x%03x", n, r->volume); pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); pa_sink_input_set_volume(si, &cv); } diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index 03c0e973..4388e49c 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c index 53f41896..1a6f5368 100644 --- a/src/modules/module-native-protocol-fd.c +++ b/src/modules/module-native-protocol-fd.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index de35fff9..604ab158 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -59,11 +57,12 @@ PA_MODULE_USAGE( "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name of sink>" - "channel_map=<channel map>" + "sink_name=<name of sink> " + "channel_map=<channel map> " "description=<description for the sink>"); #define DEFAULT_SINK_NAME "null" +#define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2) struct userdata { pa_core *core; @@ -76,7 +75,8 @@ struct userdata { size_t block_size; - struct timeval timestamp; + pa_usec_t block_usec; + pa_usec_t timestamp; }; static const char* const valid_modargs[] = { @@ -96,26 +96,93 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_SET_STATE: if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) - pa_rtclock_get(&u->timestamp); + u->timestamp = pa_rtclock_usec(); break; case PA_SINK_MESSAGE_GET_LATENCY: { - struct timeval now; + pa_usec_t now; - pa_rtclock_get(&now); + now = pa_rtclock_usec(); + *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0; - if (pa_timeval_cmp(&u->timestamp, &now) > 0) - *((pa_usec_t*) data) = 0; - else - *((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now); - break; + return 0; } } return pa_sink_process_msg(o, code, data, offset, chunk); } +static void sink_update_requested_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + u = s->userdata; + pa_assert(u); + + u->block_usec = pa_sink_get_requested_latency_within_thread(s); +} + +static void process_rewind(struct userdata *u, pa_usec_t now) { + size_t rewind_nbytes, in_buffer; + pa_usec_t delay; + + 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; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + if (u->timestamp <= now) + return; + + delay = u->timestamp - now; + in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); + + if (in_buffer <= 0) + return; + + if (rewind_nbytes > in_buffer) + rewind_nbytes = in_buffer; + + pa_sink_process_rewind(u->sink, rewind_nbytes); + u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); + + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); +} + +static void process_render(struct userdata *u, pa_usec_t now) { + size_t ate = 0; + + pa_assert(u); + + /* This is the configured latency. Sink inputs connected to us + might not have a single frame more than the maxrequest value + queed. Hence: at maximum read this many bytes from the sink + inputs. */ + + /* Fill the buffer up the the latency size */ + while (u->timestamp < now + u->block_usec) { + pa_memchunk chunk; + + pa_sink_render(u->sink, u->sink->thread_info.max_request, &chunk); + pa_memblock_unref(chunk.memblock); + +/* pa_log_debug("Ate %lu bytes.", (unsigned long) chunk.length); */ + u->timestamp += pa_bytes_to_usec(chunk.length, &u->sink->sample_spec); + + ate += chunk.length; + + if (ate >= u->sink->thread_info.max_request) + break; + } + +/* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, (unsigned long) nbytes); */ +} + static void thread_func(void *userdata) { struct userdata *u = userdata; @@ -126,28 +193,29 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); - pa_rtclock_get(&u->timestamp); + u->timestamp = pa_rtclock_usec(); for (;;) { int ret; /* Render some data and drop it immediately */ - if (u->sink->thread_info.state == PA_SINK_RUNNING) { - struct timeval now; + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + pa_usec_t now; + + now = pa_rtclock_usec(); - pa_rtclock_get(&now); + if (u->sink->thread_info.rewind_nbytes > 0) + process_rewind(u, now); - if (pa_timeval_cmp(&u->timestamp, &now) <= 0) { - pa_sink_skip(u->sink, u->block_size); - pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); - } + if (u->timestamp <= now) + process_render(u, now); - pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp); + pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp); } else pa_rtpoll_set_timer_disabled(u->rtpoll); /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; if (ret == 0) @@ -169,6 +237,7 @@ int pa__init(pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; + pa_sink_new_data data; pa_assert(m); @@ -183,31 +252,42 @@ int pa__init(pa_module*m) { goto fail; } - u = pa_xnew0(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; - m->userdata = u; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { - pa_log("Failed to create sink."); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + 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; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink")); - u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ - if (u->block_size <= 0) - u->block_size = pa_frame_size(&ss); + pa_sink_set_latency_range(u->sink, (pa_usec_t) -1, MAX_LATENCY_USEC); + u->block_usec = u->sink->thread_info.max_latency; + + u->sink->thread_info.max_rewind = + u->sink->thread_info.max_request = + pa_usec_to_bytes(u->block_usec, &u->sink->sample_spec); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index a7df8a0c..76b13ecc 100644 --- a/src/modules/module-oss.c +++ b/src/modules/module-oss.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -161,10 +159,10 @@ static void trigger(struct userdata *u, pa_bool_t quick) { pa_log_debug("trigger"); - if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) enable_bits |= PCM_ENABLE_INPUT; - if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) enable_bits |= PCM_ENABLE_OUTPUT; pa_log_debug("trigger: %i", enable_bits); @@ -202,7 +200,7 @@ static void trigger(struct userdata *u, pa_bool_t quick) { * register the fd as ready. */ - if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) { + 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); @@ -510,6 +508,9 @@ static int suspend(struct userdata *u) { 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; @@ -600,9 +601,9 @@ static int unsuspend(struct userdata *u) { build_pollfd(u); if (u->sink) - pa_sink_get_volume(u->sink); + sink_get_volume(u->sink); if (u->source) - pa_source_get_volume(u->source); + source_get_volume(u->source); pa_log_info("Resumed successfully..."); @@ -641,7 +642,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); if (!u->source || u->source_suspended) { if (suspend(u) < 0) @@ -658,7 +659,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse if (u->sink->thread_info.state == PA_SINK_INIT) { do_trigger = TRUE; - quick = u->source && PA_SOURCE_OPENED(u->source->thread_info.state); + quick = u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state); } if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { @@ -721,7 +722,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { case PA_SOURCE_SUSPENDED: - pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + pa_assert(PA_SOURCE_IS_OPENED(u->source->thread_info.state)); if (!u->sink || u->sink_suspended) { if (suspend(u) < 0) @@ -738,7 +739,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (u->source->thread_info.state == PA_SOURCE_INIT) { do_trigger = TRUE; - quick = u->sink && PA_SINK_OPENED(u->sink->thread_info.state); + quick = u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state); } if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { @@ -877,7 +878,7 @@ static void thread_func(void *userdata) { /* Render some data and write it to the dsp */ - if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { + if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { if (u->use_mmap) { @@ -985,7 +986,7 @@ static void thread_func(void *userdata) { /* Try to read some data and pass it on to the source driver. */ - if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { + if (u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { if (u->use_mmap) { @@ -1095,8 +1096,8 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->events = - ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | - ((u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0); + ((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 */ @@ -1143,9 +1144,11 @@ int pa__init(pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - char hwdesc[64], *t; + char hwdesc[64]; const char *name; - int namereg_fail; + pa_bool_t namereg_fail; + pa_sink_new_data sink_new_data; + pa_source_new_data source_new_data; pa_assert(m); @@ -1226,17 +1229,16 @@ int pa__init(pa_module*m) { m->userdata = u; u->fd = fd; u->mixer_fd = -1; - u->use_getospace = u->use_getispace = 1; - u->use_getodelay = 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 = u->nfrags = nfrags; u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size; u->use_mmap = use_mmap; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->rtpoll_item = NULL; build_pollfd(u); @@ -1244,14 +1246,14 @@ int pa__init(pa_module*m) { pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); u->in_fragment_size = info.fragsize; u->in_nfrags = info.fragstotal; - u->use_getispace = 1; + 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 = info.fragsize; u->out_nfrags = info.fragstotal; - u->use_getospace = 1; + u->use_getospace = TRUE; } u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size; @@ -1263,21 +1265,37 @@ int pa__init(pa_module*m) { 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 = 0; + 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 = 1; + namereg_fail = TRUE; else { name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); - namereg_fail = 0; + namereg_fail = FALSE; } - u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + 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; @@ -1286,18 +1304,8 @@ int pa__init(pa_module*m) { u->source->parent.process_msg = source_process_msg; u->source->userdata = u; - pa_source_set_module(u->source, m); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - pa_source_set_description(u->source, t = pa_sprintf_malloc( - "OSS PCM on %s%s%s%s%s", - dev, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "", - use_mmap ? " via DMA" : "")); - pa_xfree(t); - u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY; u->source->refresh_volume = TRUE; if (use_mmap) @@ -1315,7 +1323,7 @@ int pa__init(pa_module*m) { 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->use_mmap = use_mmap = FALSE; u->out_mmap = NULL; } } else { @@ -1325,14 +1333,30 @@ int pa__init(pa_module*m) { } if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = 1; + namereg_fail = TRUE; else { name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); - namereg_fail = 0; + namereg_fail = FALSE; } - u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + 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; @@ -1341,26 +1365,18 @@ int pa__init(pa_module*m) { u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc( - "OSS PCM on %s%s%s%s%s", - dev, - hwdesc[0] ? " (" : "", - hwdesc[0] ? hwdesc : "", - hwdesc[0] ? ")" : "", - use_mmap ? " via DMA" : "")); - pa_xfree(t); - u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY; 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) { - int do_close = 1; + pa_bool_t do_close = TRUE; u->mixer_devmask = 0; if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) @@ -1372,7 +1388,7 @@ int pa__init(pa_module*m) { u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; u->sink->get_volume = sink_get_volume; u->sink->set_volume = sink_set_volume; - do_close = 0; + do_close = FALSE; } if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) { @@ -1380,7 +1396,7 @@ int pa__init(pa_module*m) { u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; u->source->get_volume = source_get_volume; u->source->set_volume = source_set_volume; - do_close = 0; + do_close = FALSE; } } @@ -1402,10 +1418,25 @@ go_on: } /* Read mixer settings */ - if (u->sink && u->sink->get_volume) - sink_get_volume(u->sink); - if (u->source && u->source->get_volume) - source_get_volume(u->source); + 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); diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index e720c8ad..cd25b890 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -62,7 +60,7 @@ PA_MODULE_USAGE( "rate=<sample rate>" "channel_map=<channel map>"); -#define DEFAULT_FILE_NAME "/tmp/music.output" +#define DEFAULT_FILE_NAME "fifo_output" #define DEFAULT_SINK_NAME "fifo_output" struct userdata { @@ -80,6 +78,8 @@ struct userdata { pa_memchunk memchunk; pa_rtpoll_item *rtpoll_item; + + int write_type; }; static const char* const valid_modargs[] = { @@ -109,16 +109,64 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse n += u->memchunk.length; *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); - break; + return 0; } } return pa_sink_process_msg(o, code, data, offset, chunk); } +static void process_rewind(struct userdata *u) { + pa_assert(u); + + pa_log_debug("Rewind requested but not supported by pipe sink. Ignoring."); + u->sink->thread_info.rewind_nbytes = 0; +} + +static int process_render(struct userdata *u) { + pa_assert(u); + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + for (;;) { + ssize_t l; + void *p; + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &u->write_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + return -1; + } + + } else { + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + } + + return 0; + } +} + static void thread_func(void *userdata) { struct userdata *u = userdata; - int write_type = 0; pa_assert(u); @@ -134,39 +182,14 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); /* Render some data and write it to the fifo */ - if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { - ssize_t l; - void *p; - - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + if (u->sink->thread_info.state == PA_SINK_RUNNING) { - pa_assert(u->memchunk.length > 0); + if (u->sink->thread_info.rewind_nbytes > 0) + process_rewind(u); - p = pa_memblock_acquire(u->memchunk.memblock); - l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); - pa_memblock_release(u->memchunk.memblock); - - pa_assert(l != 0); - - if (l < 0) { - - if (errno == EINTR) - continue; - else if (errno != EAGAIN) { - pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + if (pollfd->revents) { + if (process_render(u) < 0) goto fail; - } - - } else { - - u->memchunk.index += l; - u->memchunk.length -= l; - - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } pollfd->revents = 0; } @@ -205,8 +228,8 @@ int pa__init(pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; - char *t; struct pollfd *pollfd; + pa_sink_new_data data; pa_assert(m); @@ -226,11 +249,11 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; pa_memchunk_reset(&u->memchunk); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->write_type = 0; - u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); + u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { @@ -251,20 +274,28 @@ int pa__init(pa_module*m) { goto fail; } - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO sink %s", u->filename); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + + if (!u->sink) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename)); - pa_xfree(t); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index 02935649..b0de34ca 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -153,13 +151,14 @@ static void thread_func(void *userdata) { /* Hmm, nothing to do. Let's sleep */ pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0; - if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; if (ret == 0) goto finish; pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + if (pollfd->revents & ~POLLIN) { pa_log("FIFO shutdown."); goto fail; @@ -182,8 +181,8 @@ int pa__init(pa_module*m) { pa_sample_spec ss; pa_channel_map map; pa_modargs *ma; - char *t; struct pollfd *pollfd; + pa_source_new_data data; pa_assert(m); @@ -203,11 +202,10 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; pa_memchunk_reset(&u->memchunk); - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); - u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); + u->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); mkfifo(u->filename, 0666); if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { @@ -228,19 +226,27 @@ int pa__init(pa_module*m) { goto fail; } - if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_source_new_data_set_name(&data, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->filename); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Unix FIFO source %s", u->filename); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + + u->source = pa_source_new(m->core, &data, 0); + pa_source_new_data_done(&data); + + if (!u->source) { pa_log("Failed to create source."); goto fail; } u->source->userdata = u; - u->source->flags = 0; - pa_source_set_module(u->source, m); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename)); - pa_xfree(t); u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); diff --git a/src/modules/module-position-event-sounds.c b/src/modules/module-position-event-sounds.c new file mode 100644 index 00000000..90e693a3 --- /dev/null +++ b/src/modules/module-position-event-sounds.c @@ -0,0 +1,165 @@ +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + + 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 <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/channelmap.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/sink-input.h> + +#include "module-position-event-sounds-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Position event sounds between L and R depending on the position on screen of the widget triggering them."); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL +}; + +struct userdata { + pa_core *core; + pa_hook_slot *sink_input_fixate_hook_slot; +}; + +static pa_bool_t is_left(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_FRONT_LEFT || + p == PA_CHANNEL_POSITION_REAR_LEFT || + p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_LEFT || + p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || + p == PA_CHANNEL_POSITION_TOP_REAR_LEFT; +} + +static pa_bool_t is_right(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_REAR_RIGHT|| + p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_RIGHT || + p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT; +} + +static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *data, struct userdata *u) { + const char *hpos; + double f; + unsigned c; + char t[PA_CVOLUME_SNPRINT_MAX]; + + pa_assert(data); + + if (!(hpos = pa_proplist_gets(data->proplist, PA_PROP_EVENT_MOUSE_HPOS))) + return PA_HOOK_OK; + + if (pa_atod(hpos, &f) < 0) { + pa_log_warn("Failed to parse "PA_PROP_EVENT_MOUSE_HPOS" property '%s'.", hpos); + return PA_HOOK_OK; + } + + if (f < 0.0 || f > 1.0) { + pa_log_warn("Property "PA_PROP_EVENT_MOUSE_HPOS" out of range %0.2f", f); + return PA_HOOK_OK; + } + + pa_log_debug("Positioning event sound '%s' at %0.2f.", pa_strnull(pa_proplist_gets(data->proplist, PA_PROP_EVENT_ID)), f); + + if (!data->volume_is_set) { + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + data->volume_is_set = TRUE; + } + + for (c = 0; c < data->sample_spec.channels; c++) { + + if (is_left(data->channel_map.map[c])) + data->volume.values[c] = + pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * (1.0 - f))); + + if (is_right(data->channel_map.map[c])) + data->volume.values[c] = + pa_sw_volume_multiply(data->volume.values[c], (pa_volume_t) (PA_VOLUME_NORM * f)); + } + + pa_log_debug("Final volume %s.", pa_cvolume_snprint(t, sizeof(t), &data->volume)); + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + 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_input_fixate_hook_slot) + pa_hook_slot_free(u->sink_input_fixate_hook_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index 600201b4..0c9529c3 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -215,15 +213,6 @@ int pa__init(pa_module*m) { #else pa_socket_server *s; int r; - char tmp[PATH_MAX]; - -#if defined(USE_PROTOCOL_ESOUND) -#if defined(USE_PER_USER_ESOUND_SOCKET) - char esdsocketpath[PATH_MAX]; -#else - const char esdsocketpath[] = "/tmp/.esd/socket"; -#endif -#endif #endif pa_assert(m); @@ -255,54 +244,57 @@ int pa__init(pa_module*m) { goto fail; if (s_ipv4) - if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma))) - pa_socket_server_unref(s_ipv4); - + u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma); if (s_ipv6) - if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma))) - pa_socket_server_unref(s_ipv6); + u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma); if (!u->protocol_ipv4 && !u->protocol_ipv6) goto fail; + if (s_ipv6) + pa_socket_server_unref(s_ipv6); + if (s_ipv6) + pa_socket_server_unref(s_ipv4); + #else #if defined(USE_PROTOCOL_ESOUND) #if defined(USE_PER_USER_ESOUND_SOCKET) - snprintf(esdsocketpath, sizeof(esdsocketpath), "/tmp/.esd-%lu/socket", (unsigned long) getuid()); + u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid()); +#else + u->socket_path = pa_xstrdup("/tmp/.esd/socket"); #endif - pa_runtime_path(pa_modargs_get_value(ma, "socket", esdsocketpath), tmp, sizeof(tmp)); - u->socket_path = pa_xstrdup(tmp); - /* This socket doesn't reside in our own runtime dir but in * /tmp/.esd/, hence we have to create the dir first */ - if (pa_make_secure_parent_dir(u->socket_path, m->core->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { + if (pa_make_secure_parent_dir(u->socket_path, pa_in_system_mode() ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno)); goto fail; } #else - pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp)); - u->socket_path = pa_xstrdup(tmp); -#endif - - if ((r = pa_unix_socket_remove_stale(tmp)) < 0) { - pa_log("Failed to remove stale UNIX socket '%s': %s", tmp, pa_cstrerror(errno)); + if (!(u->socket_path = pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET)))) { + pa_log("Failed to generate socket path."); goto fail; } +#endif - if (r) - pa_log("Removed stale UNIX socket '%s'.", tmp); + if ((r = pa_unix_socket_remove_stale(u->socket_path)) < 0) { + pa_log("Failed to remove stale UNIX socket '%s': %s", u->socket_path, pa_cstrerror(errno)); + goto fail; + } else if (r > 0) + pa_log_info("Removed stale UNIX socket '%s'.", u->socket_path); - if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp))) + if (!(s = pa_socket_server_new_unix(m->core->mainloop, u->socket_path))) goto fail; if (!(u->protocol_unix = protocol_new(m->core, s, m, ma))) goto fail; + pa_socket_server_unref(s); + #endif m->userdata = u; @@ -325,23 +317,21 @@ fail: #else if (u->protocol_unix) protocol_free(u->protocol_unix); - - if (u->socket_path) - pa_xfree(u->socket_path); + pa_xfree(u->socket_path); #endif pa_xfree(u); - } else { + } + #if defined(USE_TCP_SOCKETS) - if (s_ipv4) - pa_socket_server_unref(s_ipv4); - if (s_ipv6) - pa_socket_server_unref(s_ipv6); + if (s_ipv4) + pa_socket_server_unref(s_ipv4); + if (s_ipv6) + pa_socket_server_unref(s_ipv6); #else - if (s) - pa_socket_server_unref(s); + if (s) + pa_socket_server_unref(s); #endif - } goto finish; } @@ -362,7 +352,7 @@ void pa__done(pa_module*m) { if (u->protocol_unix) protocol_free(u->protocol_unix); -#if defined(USE_PROTOCOL_ESOUND) +#if defined(USE_PROTOCOL_ESOUND) && !defined(USE_PER_USER_ESOUND_SOCKET) if (u->socket_path) { char *p = pa_parent_dir(u->socket_path); rmdir(p); diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 39a9245d..c87b1ece 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering PulseAudio is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published @@ -51,7 +49,8 @@ PA_MODULE_USAGE( "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "channel_map=<channel map>"); + "channel_map=<channel map> " + "remix=<remix channels?>"); struct userdata { pa_core *core; @@ -59,8 +58,6 @@ struct userdata { pa_sink *sink, *master; pa_sink_input *sink_input; - - pa_memchunk memchunk; }; static const char* const valid_modargs[] = { @@ -71,6 +68,7 @@ static const char* const valid_modargs[] = { "format", "channels", "channel_map", + "remix", NULL }; @@ -83,10 +81,14 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t usec = 0; + /* Get the latency of the master sink */ if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) usec = 0; - *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + /* Add the latency internal to our sink input on top */ + usec += pa_bytes_to_usec(pa_memblockq_get_length(u->sink_input->thread_info.render_memblockq), &u->master->sample_spec); + + *((pa_usec_t*) data) = usec; return 0; } } @@ -101,67 +103,112 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); - if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + if (PA_SINK_IS_LINKED(state) && + u->sink_input && + PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); return 0; } /* Called from I/O thread context */ -static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { - struct userdata *u = PA_SINK_INPUT(o)->userdata; +static void sink_request_rewind(pa_sink *s) { + struct userdata *u; - switch (code) { - case PA_SINK_INPUT_MESSAGE_GET_LATENCY: - *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); - /* Fall through, the default handler will add in the extra - * latency added by the resampler */ - break; - } + pa_sink_input_request_rewind(u->sink_input, s->thread_info.rewind_nbytes, TRUE, FALSE); +} + +/* Called from I/O thread context */ +static void sink_update_requested_latency(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); - return pa_sink_input_process_msg(o, code, data, offset, chunk); + /* Just hand this one over to the master sink */ + pa_sink_input_set_requested_latency_within_thread( + u->sink_input, + pa_sink_get_requested_latency_within_thread(s)); } /* Called from I/O thread context */ -static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct userdata *u; pa_sink_input_assert_ref(i); + pa_assert(chunk); pa_assert_se(u = i->userdata); - if (!u->memchunk.memblock) - pa_sink_render(u->sink, length, &u->memchunk); + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return -1; - pa_assert(u->memchunk.memblock); - *chunk = u->memchunk; - pa_memblock_ref(chunk->memblock); + pa_sink_render(u->sink, nbytes, chunk); return 0; } /* Called from I/O thread context */ -static void sink_input_drop_cb(pa_sink_input *i, size_t length) { +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { struct userdata *u; pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); - pa_assert(length > 0); + pa_assert(nbytes > 0); - if (u->memchunk.memblock) { + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return; - if (length < u->memchunk.length) { - u->memchunk.index += length; - u->memchunk.length -= length; - return; - } + if (u->sink->thread_info.rewind_nbytes > 0) { + size_t amount; + + amount = PA_MIN(u->sink->thread_info.rewind_nbytes, nbytes); + u->sink->thread_info.rewind_nbytes = 0; - pa_memblock_unref(u->memchunk.memblock); - length -= u->memchunk.length; - pa_memchunk_reset(&u->memchunk); + if (amount > 0) + pa_sink_process_rewind(u->sink, amount); } +} + +/* Called from I/O thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_rewind(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_max_request_cb(pa_sink_input *i, size_t nbytes) { + struct userdata *u; - if (length > 0) - pa_sink_skip(u->sink, length); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_set_max_request(u->sink, nbytes); +} + +/* Called from I/O thread context */ +static void sink_input_update_sink_latency_range_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + + pa_sink_update_latency_range(u->sink, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); } /* Called from I/O thread context */ @@ -171,7 +218,12 @@ static void sink_input_detach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + pa_sink_detach_within_thread(u->sink); + pa_sink_set_asyncmsgq(u->sink, NULL); + pa_sink_set_rtpoll(u->sink, NULL); } /* Called from I/O thread context */ @@ -181,10 +233,14 @@ static void sink_input_attach_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + if (!u->sink || !PA_SINK_IS_LINKED(u->sink->thread_info.state)) + return; + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); - pa_sink_attach_within_thread(u->sink); + + pa_sink_update_latency_range(u->sink, u->master->thread_info.min_latency, u->master->thread_info.max_latency); } /* Called from main context */ @@ -194,26 +250,43 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_sink_input_assert_ref(i); pa_assert_se(u = i->userdata); + pa_sink_unlink(u->sink); pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - u->sink_input = NULL; - pa_sink_unlink(u->sink); pa_sink_unref(u->sink); u->sink = NULL; + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; pa_module_unload_request(u->module); } +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) { + pa_log_debug("Requesting rewind due to state change."); + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); + } +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; pa_channel_map sink_map, stream_map; pa_modargs *ma; - char *t; + const char *k; pa_sink *master; - pa_sink_input_new_data data; - char *default_sink_name = NULL; + pa_sink_input_new_data sink_input_data; + pa_sink_new_data sink_data; + pa_bool_t remix = TRUE; pa_assert(m); @@ -236,7 +309,7 @@ int pa__init(pa_module*m) { stream_map = sink_map; if (pa_modargs_get_channel_map(ma, "master_channel_map", &stream_map) < 0) { - pa_log("Invalid master hannel map"); + pa_log("Invalid master channel map"); goto fail; } @@ -245,57 +318,83 @@ int pa__init(pa_module*m) { goto fail; } + if (pa_channel_map_equal(&stream_map, &master->channel_map)) + pa_log_warn("No remapping configured, proceeding nonetheless!"); + + if (pa_modargs_get_value_boolean(ma, "remix", &remix) < 0) { + pa_log("Invalid boolean remix parameter"); + goto fail; + } + u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; m->userdata = u; u->master = master; - pa_memchunk_reset(&u->memchunk); - - default_sink_name = pa_sprintf_malloc("%s.remapped", master->name); + u->sink = NULL; + u->sink_input = NULL; /* Create sink */ - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) { + pa_sink_new_data_init(&sink_data); + sink_data.driver = __FILE__; + sink_data.module = m; + if (!(sink_data.name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + sink_data.name = pa_sprintf_malloc("%s.remapped", master->name); + pa_sink_new_data_set_sample_spec(&sink_data, &ss); + pa_sink_new_data_set_channel_map(&sink_data, &sink_map); + k = pa_proplist_gets(master->proplist, PA_PROP_DEVICE_DESCRIPTION); + pa_proplist_setf(sink_data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Remapped %s", k ? k : master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_MASTER_DEVICE, master->name); + pa_proplist_sets(sink_data.proplist, PA_PROP_DEVICE_CLASS, "filter"); + + u->sink = pa_sink_new(m->core, &sink_data, PA_SINK_LATENCY); + pa_sink_new_data_done(&sink_data); + + if (!u->sink) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->set_state = sink_set_state; + u->sink->update_requested_latency = sink_update_requested_latency; + u->sink->request_rewind = sink_request_rewind; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; - pa_sink_set_module(u->sink, m); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description)); - pa_xfree(t); pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); pa_sink_set_rtpoll(u->sink, master->rtpoll); /* Create sink input */ - pa_sink_input_new_data_init(&data); - data.sink = u->master; - data.driver = __FILE__; - data.name = "Remapped Stream"; - pa_sink_input_new_data_set_sample_spec(&data, &ss); - pa_sink_input_new_data_set_channel_map(&data, &stream_map); - data.module = m; - - if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + pa_sink_input_new_data_init(&sink_input_data); + sink_input_data.driver = __FILE__; + sink_input_data.module = m; + sink_input_data.sink = u->master; + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_NAME, "Remapped Stream"); + pa_proplist_sets(sink_input_data.proplist, PA_PROP_MEDIA_ROLE, "filter"); + pa_sink_input_new_data_set_sample_spec(&sink_input_data, &ss); + pa_sink_input_new_data_set_channel_map(&sink_input_data, &stream_map); + + u->sink_input = pa_sink_input_new(m->core, &sink_input_data, PA_SINK_INPUT_DONT_MOVE | (remix ? 0 : PA_SINK_INPUT_NO_REMIX)); + pa_sink_input_new_data_done(&sink_input_data); + + if (!u->sink_input) goto fail; - u->sink_input->parent.process_msg = sink_input_process_msg; - u->sink_input->peek = sink_input_peek_cb; - u->sink_input->drop = sink_input_drop_cb; - u->sink_input->kill = sink_input_kill_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; + u->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; + u->sink_input->update_max_request = sink_input_update_max_request_cb; + u->sink_input->update_sink_latency_range = sink_input_update_sink_latency_range_cb; u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); pa_sink_input_put(u->sink_input); pa_modargs_free(ma); - pa_xfree(default_sink_name); return 0; @@ -305,8 +404,6 @@ fail: pa__done(m); - pa_xfree(default_sink_name); - return -1; } @@ -318,18 +415,15 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; - if (u->sink_input) { - pa_sink_input_unlink(u->sink_input); - pa_sink_input_unref(u->sink_input); - } - if (u->sink) { pa_sink_unlink(u->sink); pa_sink_unref(u->sink); } - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } pa_xfree(u); } diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c index 12957c9d..cc6717cb 100644 --- a/src/modules/module-rescue-streams.c +++ b/src/modules/module-rescue-streams.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -75,12 +73,12 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user } while ((i = pa_idxset_first(sink->inputs, NULL))) { - if (pa_sink_input_move_to(i, target, 1) < 0) { - pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, i->name, target->name); + if (pa_sink_input_move_to(i, target) < 0) { + pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); return PA_HOOK_OK; } - pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, i->name, target->name); + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, pa_proplist_gets(i->proplist, PA_PROP_APPLICATION_NAME), target->name); } @@ -116,11 +114,11 @@ static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void while ((o = pa_idxset_first(source->outputs, NULL))) { if (pa_source_output_move_to(o, target) < 0) { - pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, o->name, target->name); + pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); return PA_HOOK_OK; } - pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, o->name, target->name); + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, pa_proplist_gets(o->proplist, PA_PROP_APPLICATION_NAME), target->name); } @@ -139,8 +137,8 @@ int pa__init(pa_module*m) { } m->userdata = u = pa_xnew(struct userdata, 1); - u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_hook_callback, NULL); - u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) source_hook_callback, NULL); + u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) sink_hook_callback, NULL); + u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) source_hook_callback, NULL); pa_modargs_free(ma); return 0; diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index 41d9a51c..38780f24 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -59,44 +57,43 @@ static const char* const valid_modargs[] = { NULL, }; -static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) { struct userdata *u; - pa_assert(i); - u = i->userdata; - pa_assert(u); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_assert(chunk); chunk->memblock = pa_memblock_ref(u->memblock); - chunk->index = u->peek_index; chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index; + chunk->index = u->peek_index; + + u->peek_index = 0; return 0; } -static void sink_input_drop_cb(pa_sink_input *i, size_t length) { - struct userdata *u; +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { size_t l; + struct userdata *u; - pa_assert(i); - u = i->userdata; - pa_assert(u); - pa_assert(length > 0); - - u->peek_index += length; + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); l = pa_memblock_get_length(u->memblock); + nbytes %= l; - while (u->peek_index >= l) - u->peek_index -= l; + if (u->peek_index >= nbytes) + u->peek_index -= nbytes; + else + u->peek_index = l + u->peek_index - nbytes; } static void sink_input_kill_cb(pa_sink_input *i) { struct userdata *u; - pa_assert(i); - u = i->userdata; - pa_assert(u); + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); pa_sink_input_unlink(u->sink_input); pa_sink_input_unref(u->sink_input); @@ -105,6 +102,20 @@ static void sink_input_kill_cb(pa_sink_input *i) { pa_module_unload_request(u->module); } +/* Called from IO thread context */ +static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t state) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + /* If we are added for the first time, ask for a rewinding so that + * we are heard right-away. */ + if (PA_SINK_INPUT_IS_LINKED(state) && + i->thread_info.state == PA_SINK_INPUT_INIT) + pa_sink_input_request_rewind(i, 0, FALSE, TRUE); +} + static void calc_sine(float *f, size_t l, float freq) { size_t i; @@ -120,7 +131,6 @@ int pa__init(pa_module*m) { pa_sink *sink; pa_sample_spec ss; uint32_t frequency; - char t[256]; void *p; pa_sink_input_new_data data; @@ -156,21 +166,25 @@ int pa__init(pa_module*m) { calc_sine(p, pa_memblock_get_length(u->memblock), frequency); pa_memblock_release(u->memblock); - pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency); - pa_sink_input_new_data_init(&data); data.sink = sink; data.driver = __FILE__; - data.name = t; + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, "%u Hz Sine", frequency); + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "abstract"); + pa_proplist_setf(data.proplist, "sine.hz", "%u", frequency); pa_sink_input_new_data_set_sample_spec(&data, &ss); data.module = m; - if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0))) + u->sink_input = pa_sink_input_new(m->core, &data, 0); + pa_sink_input_new_data_done(&data); + + if (!u->sink_input) goto fail; - u->sink_input->peek = sink_input_peek_cb; - u->sink_input->drop = sink_input_drop_cb; + u->sink_input->pop = sink_input_pop_cb; + u->sink_input->process_rewind = sink_input_process_rewind_cb; u->sink_input->kill = sink_input_kill_cb; + u->sink_input->state_change = sink_input_state_change_cb; u->sink_input->userdata = u; pa_sink_input_put(u->sink_input); diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index 4a5c88e4..6f50543a 100644 --- a/src/modules/module-solaris.c +++ b/src/modules/module-solaris.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 4c260d76..bc7c023c 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -317,7 +315,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s if (pa_sink_used_by(s) <= 0) { - if (PA_SINK_OPENED(state)) + if (PA_SINK_IS_OPENED(state)) restart(d); } @@ -328,7 +326,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s if (pa_source_used_by(s) <= 0) { - if (PA_SOURCE_OPENED(state)) + if (PA_SOURCE_IS_OPENED(state)) restart(d); } } @@ -367,21 +365,21 @@ int pa__init(pa_module*m) { for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx)) device_new_hook_cb(m->core, PA_OBJECT(source), u); - u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); - u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); - u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); - u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); - u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); - u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); - - u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_cb, u); - u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], (pa_hook_cb_t) source_output_fixate_hook_cb, u); - u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u); - u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u); - u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u); - u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u); - u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u); - u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u); + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) device_new_hook_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) device_new_hook_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) device_unlink_hook_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) device_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_state_changed_hook_cb, u); + u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) device_state_changed_hook_cb, u); + + u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_input_fixate_hook_cb, u); + u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], PA_HOOK_NORMAL, (pa_hook_cb_t) source_output_fixate_hook_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_input_unlink_hook_cb, u); + u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) source_output_unlink_hook_cb, u); + u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_input_move_hook_cb, u); + u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], PA_HOOK_NORMAL, (pa_hook_cb_t) source_output_move_hook_cb, u); + u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_input_state_changed_hook_cb, u); + u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_output_state_changed_hook_cb, u); pa_modargs_free(ma); return 0; diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 62dac5d3..86f30817 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -56,11 +54,16 @@ #include <pulsecore/thread-mq.h> #include <pulsecore/rtclock.h> #include <pulsecore/core-error.h> +#include <pulsecore/proplist-util.h> #ifdef TUNNEL_SINK #include "module-tunnel-sink-symdef.h" +#else +#include "module-tunnel-source-symdef.h" +#endif + +#ifdef TUNNEL_SINK PA_MODULE_DESCRIPTION("Tunnel module for sinks"); -PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "server=<address> " "sink=<remote sink name> " @@ -71,7 +74,6 @@ PA_MODULE_USAGE( "sink_name=<name for the local sink> " "channel_map=<channel map>"); #else -#include "module-tunnel-source-symdef.h" PA_MODULE_DESCRIPTION("Tunnel module for sources"); PA_MODULE_USAGE( "server=<address> " @@ -86,15 +88,7 @@ PA_MODULE_USAGE( PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_VERSION(PACKAGE_VERSION); - -#define DEFAULT_TLENGTH_MSEC 100 -#define DEFAULT_MINREQ_MSEC 10 -#define DEFAULT_MAXLENGTH_MSEC ((DEFAULT_TLENGTH_MSEC*3)/2) -#define DEFAULT_FRAGSIZE_MSEC 10 - -#define DEFAULT_TIMEOUT 5 - -#define LATENCY_INTERVAL 10 +PA_MODULE_LOAD_ONCE(FALSE); static const char* const valid_modargs[] = { "server", @@ -113,36 +107,58 @@ static const char* const valid_modargs[] = { NULL, }; -enum { - SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX -}; +#define DEFAULT_TIMEOUT 5 + +#define LATENCY_INTERVAL 10 + +#define MIN_NETWORK_LATENCY_USEC (8*PA_USEC_PER_MSEC) + +#ifdef TUNNEL_SINK enum { SINK_MESSAGE_REQUEST = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOTE_SUSPEND, + SINK_MESSAGE_UPDATE_LATENCY, SINK_MESSAGE_POST }; +#define DEFAULT_TLENGTH_MSEC 150 +#define DEFAULT_MINREQ_MSEC 25 + +#else + +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, + SOURCE_MESSAGE_REMOTE_SUSPEND, + SOURCE_MESSAGE_UPDATE_LATENCY +}; + +#define DEFAULT_FRAGSIZE_MSEC 25 + +#endif + #ifdef TUNNEL_SINK static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); #endif static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_overflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { #ifdef TUNNEL_SINK [PA_COMMAND_REQUEST] = command_request, + [PA_COMMAND_STARTED] = command_started, #endif [PA_COMMAND_SUBSCRIBE_EVENT] = command_subscribe_event, - [PA_COMMAND_OVERFLOW] = command_overflow, - [PA_COMMAND_UNDERFLOW] = command_underflow, + [PA_COMMAND_OVERFLOW] = command_overflow_or_underflow, + [PA_COMMAND_UNDERFLOW] = command_overflow_or_underflow, [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed, [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed, - [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspend, - [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspend, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspended, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspended, [PA_COMMAND_PLAYBACK_STREAM_MOVED] = command_moved, [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved, }; @@ -163,7 +179,7 @@ struct userdata { #ifdef TUNNEL_SINK char *sink_name; pa_sink *sink; - uint32_t requested_bytes; + int32_t requested_bytes; #else char *source_name; pa_source *source; @@ -178,6 +194,14 @@ struct userdata { int64_t counter, counter_delta; + pa_bool_t remote_corked:1; + pa_bool_t remote_suspended:1; + + pa_usec_t transport_usec; + pa_bool_t transport_usec_valid; + + uint32_t ignore_latency_before; + pa_time_event *time_event; pa_bool_t auth_cookie_in_property; @@ -198,7 +222,10 @@ struct userdata { #endif }; -static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +static void request_latency(struct userdata *u); + +/* Called from main context */ +static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_assert(pd); @@ -210,7 +237,8 @@ static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t comma pa_module_unload_request(u->module); } -static void command_overflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_assert(pd); @@ -218,21 +246,40 @@ static void command_overflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, P pa_assert(u); pa_assert(u->pdispatch == pd); - pa_log_warn("Server signalled buffer overrun."); + pa_log_info("Server signalled buffer overrun/underrun."); + request_latency(u); } -static void command_underflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; + uint32_t channel; + pa_bool_t suspended; pa_assert(pd); pa_assert(t); pa_assert(u); pa_assert(u->pdispatch == pd); - pa_log_warn("Server signalled buffer underrun."); + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || + !pa_tagstruct_eof(t)) { + pa_log("Invalid packet"); + pa_module_unload_request(u->module); + return; + } + +#ifdef TUNNEL_SINK + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); +#else + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); +#endif + + request_latency(u); } -static void command_suspend(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_assert(pd); @@ -240,28 +287,54 @@ static void command_suspend(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA pa_assert(u); pa_assert(u->pdispatch == pd); - pa_log_debug("Server reports a stream suspension."); + pa_log_debug("Server reports a stream move."); + request_latency(u); } -static void command_moved(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { - struct userdata *u = userdata; +#ifdef TUNNEL_SINK + +/* Called from main context */ +static void command_started(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; pa_assert(pd); pa_assert(t); pa_assert(u); pa_assert(u->pdispatch == pd); - pa_log_debug("Server reports a stream move."); + pa_log_debug("Server reports playback started."); + request_latency(u); } -static void stream_cork(struct userdata *u, pa_bool_t cork) { - pa_tagstruct *t; +#endif + +/* Called from IO thread context */ +static void stream_cork_within_thread(struct userdata *u, pa_bool_t cork) { + pa_usec_t x; pa_assert(u); - if (cork) - pa_smoother_pause(u->smoother, pa_rtclock_usec()); + if (u->remote_corked == cork) + return; + + u->remote_corked = cork; + x = pa_rtclock_usec(); + + /* Correct by the time this needs to travel to the other side. + * This is a valid thread-safe access, because the main thread is + * waiting for us */ + if (u->transport_usec_valid) + x += u->transport_usec; + + if (u->remote_suspended || u->remote_corked) + pa_smoother_pause(u->smoother, x); else - pa_smoother_resume(u->smoother, pa_rtclock_usec()); + pa_smoother_resume(u->smoother, x); +} + +/* Called from main context */ +static void stream_cork(struct userdata *u, pa_bool_t cork) { + pa_tagstruct *t; + pa_assert(u); if (!u->pstream) return; @@ -276,19 +349,50 @@ static void stream_cork(struct userdata *u, pa_bool_t cork) { pa_tagstruct_putu32(t, u->channel); pa_tagstruct_put_boolean(t, !!cork); pa_pstream_send_tagstruct(u->pstream, t); + + request_latency(u); +} + +/* Called from IO thread context */ +static void stream_suspend_within_thread(struct userdata *u, pa_bool_t suspend) { + pa_usec_t x; + pa_assert(u); + + if (u->remote_suspended == suspend) + return; + + u->remote_suspended = suspend; + + x = pa_rtclock_usec(); + + /* Correct by the time this needed to travel from the other side. + * This is a valid thread-safe access, because the main thread is + * waiting for us */ + if (u->transport_usec_valid) + x -= u->transport_usec; + + if (u->remote_suspended || u->remote_corked) + pa_smoother_pause(u->smoother, x); + else + pa_smoother_resume(u->smoother, x); } #ifdef TUNNEL_SINK +/* Called from IO thread context */ static void send_data(struct userdata *u) { pa_assert(u); while (u->requested_bytes > 0) { pa_memchunk memchunk; + pa_sink_render(u->sink, u->requested_bytes, &memchunk); pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_POST, NULL, 0, &memchunk, NULL); pa_memblock_unref(memchunk.memblock); + u->requested_bytes -= memchunk.length; + + u->counter += memchunk.length; } } @@ -302,23 +406,59 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse int r; /* First, change the state, because otherwide pa_sink_render() would fail */ - if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) - if (PA_SINK_OPENED((pa_sink_state_t) PA_PTR_TO_UINT(data))) + if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) { + + stream_cork_within_thread(u, u->sink->state == PA_SINK_SUSPENDED); + + if (PA_SINK_IS_OPENED(u->sink->state)) send_data(u); + } return r; } + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t yl, yr, *usec = data; + + yl = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); + + *usec = yl > yr ? yl - yr : 0; + return 0; + } + case SINK_MESSAGE_REQUEST: pa_assert(offset > 0); u->requested_bytes += (size_t) offset; - if (PA_SINK_OPENED(u->sink->thread_info.state)) + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) send_data(u); return 0; + + case SINK_MESSAGE_REMOTE_SUSPEND: + + stream_suspend_within_thread(u, !!PA_PTR_TO_UINT(data)); + return 0; + + + case SINK_MESSAGE_UPDATE_LATENCY: { + pa_usec_t y; + + y = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + + if (y > (pa_usec_t) offset || offset < 0) + y -= offset; + else + y = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + + return 0; + } + case SINK_MESSAGE_POST: /* OK, This might be a bit confusing. This message is @@ -327,14 +467,16 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse * dispatched. Yeah, ugly, but I am a lazy bastard. */ pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, chunk); - u->counter += chunk->length; + u->counter_delta += chunk->length; + return 0; } return pa_sink_process_msg(o, code, data, offset, chunk); } +/* Called from main context */ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { struct userdata *u; pa_sink_assert_ref(s); @@ -343,7 +485,7 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { switch ((pa_sink_state_t) state) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(s->state)); + pa_assert(PA_SINK_IS_OPENED(s->state)); stream_cork(u, TRUE); break; @@ -363,20 +505,65 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) { #else +/* This function is called from IO context -- except when it is not. */ 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_SINK_MESSAGE_SET_STATE: { + int r; + + if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) + stream_cork_within_thread(u, u->source->state == PA_SOURCE_SUSPENDED); + + return r; + } + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t yr, yl, *usec = data; + + yl = pa_bytes_to_usec(u->counter, &PA_SINK(o)->sample_spec); + yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); + + *usec = yr > yl ? yr - yl : 0; + return 0; + } + case SOURCE_MESSAGE_POST: - if (PA_SOURCE_OPENED(u->source->thread_info.state)) + if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) pa_source_post(u->source, chunk); + + u->counter += chunk->length; + return 0; + + case SOURCE_MESSAGE_REMOTE_SUSPEND: + + stream_suspend_within_thread(u, !!PA_PTR_TO_UINT(data)); + return 0; + + case SOURCE_MESSAGE_UPDATE_LATENCY: { + pa_usec_t y; + + y = pa_bytes_to_usec(u->counter, &u->source->sample_spec); + + if (offset >= 0 || y > (pa_usec_t) -offset) + y += offset; + else + y = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), y); + + return 0; + } } return pa_source_process_msg(o, code, data, offset, chunk); } +/* Called from main context */ static int source_set_state(pa_source *s, pa_source_state_t state) { struct userdata *u; pa_source_assert_ref(s); @@ -385,7 +572,7 @@ static int source_set_state(pa_source *s, pa_source_state_t state) { switch ((pa_source_state_t) state) { case PA_SOURCE_SUSPENDED: - pa_assert(PA_SOURCE_OPENED(s->state)); + pa_assert(PA_SOURCE_IS_OPENED(s->state)); stream_cork(u, TRUE); break; @@ -436,7 +623,8 @@ finish: } #ifdef TUNNEL_SINK -static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; uint32_t bytes, channel; @@ -457,7 +645,7 @@ static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED ui goto fail; } - pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL); + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, NULL); return; fail: @@ -466,12 +654,15 @@ fail: #endif -static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; - pa_usec_t sink_usec, source_usec, transport_usec, host_usec, k; - int playing; + pa_usec_t sink_usec, source_usec, transport_usec; + pa_bool_t playing; int64_t write_index, read_index; struct timeval local, remote, now; + pa_sample_spec *ss; + int64_t delay; pa_assert(pd); pa_assert(u); @@ -480,7 +671,7 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_G if (command == PA_COMMAND_ERROR) pa_log("Failed to get latency."); else - pa_log("Protocol error 1."); + pa_log("Protocol error."); goto fail; } @@ -491,52 +682,90 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_G pa_tagstruct_get_timeval(t, &remote) < 0 || pa_tagstruct_gets64(t, &write_index) < 0 || pa_tagstruct_gets64(t, &read_index) < 0) { - pa_log("Invalid reply. (latency)"); + pa_log("Invalid reply."); + goto fail; + } + +#ifdef TUNNEL_SINK + if (u->version >= 13) { + uint64_t underrun_for = 0, playing_for = 0; + + if (pa_tagstruct_getu64(t, &underrun_for) < 0 || + pa_tagstruct_getu64(t, &playing_for) < 0) { + pa_log("Invalid reply."); + goto fail; + } + } +#endif + + if (!pa_tagstruct_eof(t)) { + pa_log("Invalid reply."); goto fail; } + if (tag < u->ignore_latency_before) { + request_latency(u); + return; + } + pa_gettimeofday(&now); + /* Calculate transport usec */ if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) { /* local and remote seem to have synchronized clocks */ #ifdef TUNNEL_SINK - transport_usec = pa_timeval_diff(&remote, &local); + u->transport_usec = pa_timeval_diff(&remote, &local); #else - transport_usec = pa_timeval_diff(&now, &remote); + u->transport_usec = pa_timeval_diff(&now, &remote); #endif } else - transport_usec = pa_timeval_diff(&now, &local)/2; + u->transport_usec = pa_timeval_diff(&now, &local)/2; + u->transport_usec_valid = TRUE; + /* First, take the device's delay */ #ifdef TUNNEL_SINK - host_usec = sink_usec + transport_usec; + delay = (int64_t) sink_usec; + ss = &u->sink->sample_spec; #else - host_usec = source_usec + transport_usec; - if (host_usec > sink_usec) - host_usec -= sink_usec; - else - host_usec = 0; + delay = (int64_t) source_usec; + ss = &u->source->sample_spec; #endif + /* Add the length of our server-side buffer */ + if (write_index >= read_index) + delay += (int64_t) pa_bytes_to_usec(write_index-read_index, ss); + else + delay -= (int64_t) pa_bytes_to_usec(read_index-write_index, ss); + + /* Our measurements are already out of date, hence correct by the * + * transport latency */ #ifdef TUNNEL_SINK - k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->sink->sample_spec); + delay -= (int64_t) transport_usec; +#else + delay += (int64_t) transport_usec; +#endif - if (k > host_usec) - k -= host_usec; - else - k = 0; + /* Now correct by what we have have read/written since we requested the update */ +#ifdef TUNNEL_SINK + delay += (int64_t) pa_bytes_to_usec(u->counter_delta, ss); #else - k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->source->sample_spec); - k += host_usec; + delay -= (int64_t) pa_bytes_to_usec(u->counter_delta, ss); #endif - pa_smoother_put(u->smoother, pa_rtclock_usec(), k); +#ifdef TUNNEL_SINK + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_UPDATE_LATENCY, 0, delay, NULL); +#else + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_UPDATE_LATENCY, 0, delay, NULL); +#endif return; fail: + pa_module_unload_request(u->module); } +/* Called from main context */ static void request_latency(struct userdata *u) { pa_tagstruct *t; struct timeval now; @@ -558,10 +787,12 @@ static void request_latency(struct userdata *u) { pa_pstream_send_tagstruct(u->pstream, t); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u, NULL); + u->ignore_latency_before = tag; u->counter_delta = 0; } -static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { +/* Called from main context */ +static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, const struct timeval *tv, void *userdata) { struct userdata *u = userdata; struct timeval ntv; @@ -576,32 +807,7 @@ static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED m->time_restart(e, &ntv); } -#ifdef TUNNEL_SINK -static pa_usec_t sink_get_latency(pa_sink *s) { - pa_usec_t t, c; - struct userdata *u = s->userdata; - - pa_sink_assert_ref(s); - - c = pa_bytes_to_usec(u->counter, &s->sample_spec); - t = pa_smoother_get(u->smoother, pa_rtclock_usec()); - - return c > t ? c - t : 0; -} -#else -static pa_usec_t source_get_latency(pa_source *s) { - pa_usec_t t, c; - struct userdata *u = s->userdata; - - pa_source_assert_ref(s); - - c = pa_bytes_to_usec(u->counter, &s->sample_spec); - t = pa_smoother_get(u->smoother, pa_rtclock_usec()); - - return t > c ? t - c : 0; -} -#endif - +/* Called from main context */ static void update_description(struct userdata *u) { char *d; char un[128], hn[128]; @@ -616,8 +822,14 @@ static void update_description(struct userdata *u) { #ifdef TUNNEL_SINK pa_sink_set_description(u->sink, d); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.user", u->user_name); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.fqdn", u->server_fqdn); + pa_proplist_sets(u->sink->proplist, "tunnel.remote.description", u->device_description); #else pa_source_set_description(u->source, d); + pa_proplist_sets(u->source->proplist, "tunnel.remote.user", u->user_name); + pa_proplist_sets(u->source->proplist, "tunnel.remote.fqdn", u->server_fqdn); + pa_proplist_sets(u->source->proplist, "tunnel.remote.description", u->device_description); #endif pa_xfree(d); @@ -640,7 +852,8 @@ static void update_description(struct userdata *u) { pa_xfree(d); } -static void server_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void server_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_sample_spec ss; const char *server_name, *server_version, *user_name, *host_name, *default_sink_name, *default_source_name; @@ -653,7 +866,7 @@ static void server_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uin if (command == PA_COMMAND_ERROR) pa_log("Failed to get info."); else - pa_log("Protocol error 6."); + pa_log("Protocol error."); goto fail; } @@ -665,7 +878,13 @@ static void server_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uin pa_tagstruct_gets(t, &default_sink_name) < 0 || pa_tagstruct_gets(t, &default_source_name) < 0 || pa_tagstruct_getu32(t, &cookie) < 0) { - pa_log("Invalid reply. (get_server_info)"); + + pa_log("Parse failure"); + goto fail; + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); goto fail; } @@ -685,24 +904,28 @@ fail: #ifdef TUNNEL_SINK -static void sink_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void sink_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; uint32_t idx, owner_module, monitor_source, flags; const char *name, *description, *monitor_source_name, *driver; pa_sample_spec ss; pa_channel_map cm; pa_cvolume volume; - int mute; + pa_bool_t mute; pa_usec_t latency; + pa_proplist *pl; pa_assert(pd); pa_assert(u); + pl = pa_proplist_new(); + if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) pa_log("Failed to get info."); else - pa_log("Protocol error 5."); + pa_log("Protocol error."); goto fail; } @@ -719,10 +942,29 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint3 pa_tagstruct_get_usec(t, &latency) < 0 || pa_tagstruct_gets(t, &driver) < 0 || pa_tagstruct_getu32(t, &flags) < 0) { - pa_log("Invalid reply. (get_sink_info)"); + + pa_log("Parse failure"); goto fail; } + if (u->version >= 13) { + pa_usec_t configured_latency; + + if (pa_tagstruct_get_proplist(t, pl) < 0 || + pa_tagstruct_get_usec(t, &configured_latency) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_proplist_free(pl); + if (!u->sink_name || strcmp(name, u->sink_name)) return; @@ -735,26 +977,31 @@ static void sink_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint3 fail: pa_module_unload_request(u->module); + pa_proplist_free(pl); } -static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; uint32_t idx, owner_module, client, sink; pa_usec_t buffer_usec, sink_usec; const char *name, *driver, *resample_method; - int mute; + pa_bool_t mute; pa_sample_spec sample_spec; pa_channel_map channel_map; pa_cvolume volume; + pa_proplist *pl; pa_assert(pd); pa_assert(u); + pl = pa_proplist_new(); + if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) pa_log("Failed to get info."); else - pa_log("Protocol error 2."); + pa_log("Protocol error."); goto fail; } @@ -769,12 +1016,35 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED pa_tagstruct_get_usec(t, &buffer_usec) < 0 || pa_tagstruct_get_usec(t, &sink_usec) < 0 || pa_tagstruct_gets(t, &resample_method) < 0 || - pa_tagstruct_gets(t, &driver) < 0 || - (u->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0)) { - pa_log("Invalid reply. (get_info)"); + pa_tagstruct_gets(t, &driver) < 0) { + + pa_log("Parse failure"); + goto fail; + } + + if (u->version >= 11) { + if (pa_tagstruct_get_boolean(t, &mute) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (u->version >= 13) { + if (pa_tagstruct_get_proplist(t, pl) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); goto fail; } + pa_proplist_free(pl); + if (idx != u->device_index) return; @@ -794,28 +1064,33 @@ static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED fail: pa_module_unload_request(u->module); + pa_proplist_free(pl); } #else -static void source_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void source_info_cb(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; uint32_t idx, owner_module, monitor_of_sink, flags; const char *name, *description, *monitor_of_sink_name, *driver; pa_sample_spec ss; pa_channel_map cm; pa_cvolume volume; - int mute; - pa_usec_t latency; + pa_bool_t mute; + pa_usec_t latency, configured_latency; + pa_proplist *pl; pa_assert(pd); pa_assert(u); + pl = pa_proplist_new(); + if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) pa_log("Failed to get info."); else - pa_log("Protocol error 5."); + pa_log("Protocol error."); goto fail; } @@ -832,10 +1107,27 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uin pa_tagstruct_get_usec(t, &latency) < 0 || pa_tagstruct_gets(t, &driver) < 0 || pa_tagstruct_getu32(t, &flags) < 0) { - pa_log("Invalid reply. (get_source_info)"); + + pa_log("Parse failure"); goto fail; } + if (u->version >= 13) { + if (pa_tagstruct_get_proplist(t, pl) < 0 || + pa_tagstruct_get_usec(t, &configured_latency) < 0) { + + pa_log("Parse failure"); + goto fail; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_proplist_free(pl); + if (!u->source_name || strcmp(name, u->source_name)) return; @@ -848,10 +1140,12 @@ static void source_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uin fail: pa_module_unload_request(u->module); + pa_proplist_free(pl); } #endif +/* Called from main context */ static void request_info(struct userdata *u) { pa_tagstruct *t; uint32_t tag; @@ -871,25 +1165,30 @@ static void request_info(struct userdata *u) { pa_pstream_send_tagstruct(u->pstream, t); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_input_info_cb, u, NULL); - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO); - pa_tagstruct_putu32(t, tag = u->ctag++); - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->sink_name); - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_info_cb, u, NULL); + if (u->sink_name) { + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, u->sink_name); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_info_cb, u, NULL); + } #else - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO); - pa_tagstruct_putu32(t, tag = u->ctag++); - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->source_name); - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, source_info_cb, u, NULL); + if (u->source_name) { + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, u->source_name); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, source_info_cb, u, NULL); + } #endif } -static void command_subscribe_event(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_subscription_event_type_t e; uint32_t idx; @@ -919,6 +1218,7 @@ static void command_subscribe_event(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t com request_info(u); } +/* Called from main context */ static void start_subscribe(struct userdata *u) { pa_tagstruct *t; uint32_t tag; @@ -938,7 +1238,8 @@ static void start_subscribe(struct userdata *u) { pa_pstream_send_tagstruct(u->pstream, t); } -static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +/* Called from main context */ +static void create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; struct timeval ntv; #ifdef TUNNEL_SINK @@ -953,7 +1254,7 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN if (command == PA_COMMAND_ERROR) pa_log("Failed to create stream."); else - pa_log("Protocol error 3."); + pa_log("Protocol error."); goto fail; } @@ -967,22 +1268,57 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN if (u->version >= 9) { #ifdef TUNNEL_SINK - uint32_t maxlength, tlength, prebuf, minreq; + if (pa_tagstruct_getu32(t, &u->maxlength) < 0 || + pa_tagstruct_getu32(t, &u->tlength) < 0 || + pa_tagstruct_getu32(t, &u->prebuf) < 0 || + pa_tagstruct_getu32(t, &u->minreq) < 0) + goto parse_error; +#else + if (pa_tagstruct_getu32(t, &u->maxlength) < 0 || + pa_tagstruct_getu32(t, &u->fragsize) < 0) + goto parse_error; +#endif + } - if (pa_tagstruct_getu32(t, &maxlength) < 0 || - pa_tagstruct_getu32(t, &tlength) < 0 || - pa_tagstruct_getu32(t, &prebuf) < 0 || - pa_tagstruct_getu32(t, &minreq) < 0) + if (u->version >= 12) { + pa_sample_spec ss; + pa_channel_map cm; + uint32_t device_index; + const char *dn; + pa_bool_t suspended; + + if (pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &device_index) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) goto parse_error; + +#ifdef TUNNEL_SINK + pa_xfree(u->sink_name); + u->sink_name = pa_xstrdup(dn); #else - uint32_t maxlength, fragsize; + pa_xfree(u->source_name); + u->source_name = pa_xstrdup(dn); +#endif + } - if (pa_tagstruct_getu32(t, &maxlength) < 0 || - pa_tagstruct_getu32(t, &fragsize) < 0) + if (u->version >= 13) { + pa_usec_t usec; + + if (pa_tagstruct_get_usec(t, &usec) < 0) goto parse_error; + +#ifdef TUNNEL_SINK + pa_sink_set_latency_range(u->sink, usec + MIN_NETWORK_LATENCY_USEC, 0); +#else + pa_source_set_latency_range(u->source, usec + MIN_NETWORK_LATENCY_USEC, 0); #endif } + if (!pa_tagstruct_eof(t)) + goto parse_error; + start_subscribe(u); request_info(u); @@ -1006,8 +1342,10 @@ parse_error: fail: pa_module_unload_request(u->module); + } +/* Called from main context */ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; pa_tagstruct *reply; @@ -1021,11 +1359,13 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t pa_assert(u->pdispatch == pd); if (command != PA_COMMAND_REPLY || - pa_tagstruct_getu32(t, &u->version) < 0) { + pa_tagstruct_getu32(t, &u->version) < 0 || + !pa_tagstruct_eof(t)) { + if (command == PA_COMMAND_ERROR) pa_log("Failed to authenticate"); else - pa_log("Protocol error 4."); + pa_log("Protocol error."); goto fail; } @@ -1036,6 +1376,15 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t goto fail; } + /* Starting with protocol version 13 the MSB of the version tag + reflects if shm is enabled for this connection or not. We don't + support SHM here at all, so we just ignore this. */ + + if (u->version >= 13) + u->version &= 0x7FFFFFFFU; + + pa_log_debug("Protocol version: remote %u, local %u", u->version, PA_PROTOCOL_VERSION); + #ifdef TUNNEL_SINK pa_snprintf(name, sizeof(name), "%s for %s@%s", u->sink_name, @@ -1051,22 +1400,48 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t reply = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME); pa_tagstruct_putu32(reply, tag = u->ctag++); - pa_tagstruct_puts(reply, "PulseAudio"); + + if (u->version >= 13) { + pa_proplist *pl; + pl = pa_proplist_new(); + pa_init_proplist(pl); + pa_proplist_sets(pl, PA_PROP_APPLICATION_ID, "org.PulseAudio.PulseAudio"); + pa_proplist_sets(pl, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION); + pa_tagstruct_put_proplist(reply, pl); + pa_proplist_free(pl); + } else + pa_tagstruct_puts(reply, "PulseAudio"); + pa_pstream_send_tagstruct(u->pstream, reply); /* We ignore the server's reply here */ reply = pa_tagstruct_new(NULL, 0); + if (u->version < 13) + /* Only for older PA versions we need to fill in the maxlength */ + u->maxlength = 4*1024*1024; + +#ifdef TUNNEL_SINK + u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &u->sink->sample_spec); + u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &u->sink->sample_spec); + u->prebuf = u->tlength; +#else + u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &u->source->sample_spec); +#endif + #ifdef TUNNEL_SINK pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM); pa_tagstruct_putu32(reply, tag = u->ctag++); - pa_tagstruct_puts(reply, name); + + if (u->version < 13) + pa_tagstruct_puts(reply, name); + pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec); pa_tagstruct_put_channel_map(reply, &u->sink->channel_map); pa_tagstruct_putu32(reply, PA_INVALID_INDEX); pa_tagstruct_puts(reply, u->sink_name); pa_tagstruct_putu32(reply, u->maxlength); - pa_tagstruct_put_boolean(reply, !PA_SINK_OPENED(pa_sink_get_state(u->sink))); + pa_tagstruct_put_boolean(reply, !PA_SINK_IS_OPENED(pa_sink_get_state(u->sink))); pa_tagstruct_putu32(reply, u->tlength); pa_tagstruct_putu32(reply, u->prebuf); pa_tagstruct_putu32(reply, u->minreq); @@ -1076,26 +1451,44 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t #else pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM); pa_tagstruct_putu32(reply, tag = u->ctag++); - pa_tagstruct_puts(reply, name); + + if (u->version < 13) + pa_tagstruct_puts(reply, name); + pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec); pa_tagstruct_put_channel_map(reply, &u->source->channel_map); pa_tagstruct_putu32(reply, PA_INVALID_INDEX); pa_tagstruct_puts(reply, u->source_name); pa_tagstruct_putu32(reply, u->maxlength); - pa_tagstruct_put_boolean(reply, !PA_SOURCE_OPENED(pa_source_get_state(u->source))); + pa_tagstruct_put_boolean(reply, !PA_SOURCE_IS_OPENED(pa_source_get_state(u->source))); pa_tagstruct_putu32(reply, u->fragsize); #endif - /* New flags added in 0.9.8 */ if (u->version >= 12) { - /* TODO: set these to useful values */ - pa_tagstruct_put_boolean(reply, FALSE); /*no_remap*/ - pa_tagstruct_put_boolean(reply, FALSE); /*no_remix*/ - pa_tagstruct_put_boolean(reply, FALSE); /*fix_format*/ - pa_tagstruct_put_boolean(reply, FALSE); /*fix_rate*/ - pa_tagstruct_put_boolean(reply, FALSE); /*fix_channels*/ - pa_tagstruct_put_boolean(reply, FALSE); /*no_move*/ - pa_tagstruct_put_boolean(reply, FALSE); /*variable_rate*/ + pa_tagstruct_put_boolean(reply, FALSE); /* no_remap */ + pa_tagstruct_put_boolean(reply, FALSE); /* no_remix */ + pa_tagstruct_put_boolean(reply, FALSE); /* fix_format */ + pa_tagstruct_put_boolean(reply, FALSE); /* fix_rate */ + pa_tagstruct_put_boolean(reply, FALSE); /* fix_channels */ + pa_tagstruct_put_boolean(reply, TRUE); /* no_move */ + pa_tagstruct_put_boolean(reply, FALSE); /* variable_rate */ + } + + if (u->version >= 13) { + pa_proplist *pl; + + pa_tagstruct_put_boolean(reply, FALSE); /* start muted/peak detect*/ + pa_tagstruct_put_boolean(reply, TRUE); /* adjust_latency */ + + pl = pa_proplist_new(); + pa_proplist_sets(pl, PA_PROP_MEDIA_NAME, name); + pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "abstract"); + pa_tagstruct_put_proplist(reply, pl); + pa_proplist_free(pl); + +#ifndef TUNNEL_SINK + pa_tagstruct_putu32(reply, PA_INVALID_INDEX); /* direct on input */ +#endif } pa_pstream_send_tagstruct(u->pstream, reply); @@ -1109,6 +1502,7 @@ fail: pa_module_unload_request(u->module); } +/* Called from main context */ static void pstream_die_callback(pa_pstream *p, void *userdata) { struct userdata *u = userdata; @@ -1119,6 +1513,7 @@ static void pstream_die_callback(pa_pstream *p, void *userdata) { pa_module_unload_request(u->module); } +/* Called from main context */ static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) { struct userdata *u = userdata; @@ -1134,6 +1529,7 @@ static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_c } #ifndef TUNNEL_SINK +/* Called from main context */ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { struct userdata *u = userdata; @@ -1149,12 +1545,12 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk); - u->counter += chunk->length; u->counter_delta += chunk->length; } #endif +/* Called from main context */ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { struct userdata *u = userdata; pa_tagstruct *t; @@ -1211,10 +1607,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata #ifdef TUNNEL_SINK -static int sink_get_volume(pa_sink *sink) { - return 0; -} - +/* Called from main context */ static int sink_set_volume(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; @@ -1234,10 +1627,7 @@ static int sink_set_volume(pa_sink *sink) { return 0; } -static int sink_get_mute(pa_sink *sink) { - return 0; -} - +/* Called from main context */ static int sink_set_mute(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; @@ -1262,6 +1652,7 @@ static int sink_set_mute(pa_sink *sink) { #endif +/* Called from main context */ static int load_key(struct userdata *u, const char*fn) { pa_assert(u); @@ -1293,19 +1684,23 @@ int pa__init(pa_module*m) { struct userdata *u = NULL; pa_sample_spec ss; pa_channel_map map; - char *t, *dn = NULL; + char *dn = NULL; +#ifdef TUNNEL_SINK + pa_sink_new_data data; +#else + pa_source_new_data data; +#endif pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("failed to parse module arguments"); + pa_log("Failed to parse module arguments"); goto fail; } - u = pa_xnew0(struct userdata, 1); - m->userdata = u; - u->module = m; + m->userdata = u = pa_xnew0(struct userdata, 1); u->core = m->core; + u->module = m; u->client = NULL; u->pdispatch = NULL; u->pstream = NULL; @@ -1318,32 +1713,36 @@ int pa__init(pa_module*m) { u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));; u->source = NULL; #endif - u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE); + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); u->ctag = 1; u->device_index = u->channel = PA_INVALID_INDEX; u->auth_cookie_in_property = FALSE; u->time_event = NULL; + u->ignore_latency_before = 0; + u->transport_usec = 0; + u->transport_usec_valid = FALSE; + u->remote_suspended = u->remote_corked = FALSE; + u->counter = u->counter_delta = 0; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) goto fail; if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) { - pa_log("no server specified."); + pa_log("No server specified."); goto fail; } ss = m->core->default_sample_spec; if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log("invalid sample format specification"); + pa_log("Invalid sample format specification"); goto fail; } if (!(u->client = pa_socket_client_new_string(m->core->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { - pa_log("failed to connect to server '%s'", u->server_name); + pa_log("Failed to connect to server '%s'", u->server_name); goto fail; } @@ -1354,7 +1753,22 @@ int pa__init(pa_module*m) { if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) dn = pa_sprintf_malloc("tunnel.%s", u->server_name); - if (!(u->sink = pa_sink_new(m->core, __FILE__, dn, 1, &ss, &map))) { + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + data.namereg_fail = TRUE; + pa_sink_new_data_set_name(&data, dn); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_sink_new_data_set_channel_map(&data, &map); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "%s%s%s", pa_strempty(u->sink_name), u->sink_name ? " on " : "", u->server_name); + pa_proplist_sets(data.proplist, "tunnel.remote.server", u->server_name); + if (u->sink_name) + pa_proplist_sets(data.proplist, "tunnel.remote.sink", u->sink_name); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL); + pa_sink_new_data_done(&data); + + if (!u->sink) { pa_log("Failed to create sink."); goto fail; } @@ -1362,56 +1776,62 @@ int pa__init(pa_module*m) { u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; u->sink->set_state = sink_set_state; - u->sink->get_latency = sink_get_latency; - u->sink->get_volume = sink_get_volume; - u->sink->get_mute = sink_get_mute; u->sink->set_volume = sink_set_volume; u->sink->set_mute = sink_set_mute; - u->sink->flags = PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL; - pa_sink_set_module(u->sink, m); + u->sink->refresh_volume = u->sink->refresh_muted = FALSE; + + pa_sink_set_latency_range(u->sink, MIN_NETWORK_LATENCY_USEC, 0); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("%s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name)); - pa_xfree(t); #else if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) dn = pa_sprintf_malloc("tunnel.%s", u->server_name); - if (!(u->source = pa_source_new(m->core, __FILE__, dn, 1, &ss, &map))) { + pa_source_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + data.namereg_fail = TRUE; + pa_source_new_data_set_name(&data, dn); + pa_source_new_data_set_sample_spec(&data, &ss); + pa_source_new_data_set_channel_map(&data, &map); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "%s%s%s", pa_strempty(u->source_name), u->source_name ? " on " : "", u->server_name); + pa_proplist_sets(data.proplist, "tunnel.remote.server", u->server_name); + if (u->source_name) + pa_proplist_sets(data.proplist, "tunnel.remote.source", u->source_name); + + u->source = pa_source_new(m->core, &data, PA_SOURCE_NETWORK|PA_SOURCE_LATENCY); + pa_source_new_data_done(&data); + + if (!u->source) { pa_log("Failed to create source."); goto fail; } u->source->parent.process_msg = source_process_msg; - u->source->userdata = u; u->source->set_state = source_set_state; - u->source->get_latency = source_get_latency; - u->source->flags = PA_SOURCE_NETWORK|PA_SOURCE_LATENCY; + u->source->userdata = u; + + pa_source_set_latency_range(u->source, MIN_NETWORK_LATENCY_USEC, 0); - pa_source_set_module(u->source, m); pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); pa_source_set_rtpoll(u->source, u->rtpoll); - pa_source_set_description(u->source, t = pa_sprintf_malloc("%s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name)); - pa_xfree(t); #endif pa_xfree(dn); u->time_event = NULL; - u->maxlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MAXLENGTH_MSEC, &ss); + u->maxlength = 0; #ifdef TUNNEL_SINK - u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &ss); - u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &ss); - u->prebuf = u->tlength; + u->tlength = u->minreq = u->prebuf = 0; #else - u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &ss); + u->fragsize = 0; #endif - u->counter = u->counter_delta = 0; pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); if (!(u->thread = pa_thread_new(thread_func, u))) { diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 192a2a78..d862c203 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -102,7 +100,7 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { return NULL; k = strtol(s, &p, 0); - if (k <= 0 || k > PA_CHANNELS_MAX) + if (k <= 0 || k > (long) PA_CHANNELS_MAX) return NULL; v->channels = (unsigned) k; @@ -115,7 +113,7 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { k = strtol(p, &p, 0); - if (k < PA_VOLUME_MUTED) + if (k < (long) PA_VOLUME_MUTED) return NULL; v->values[i] = (pa_volume_t) k; @@ -134,16 +132,12 @@ static int load_rules(struct userdata *u) { char buf_name[256], buf_volume[256], buf_sink[256], buf_source[256]; char *ln = buf_name; - f = u->table_file ? - fopen(u->table_file, "r") : - pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r"); - - if (!f) { + if (!(f = fopen(u->table_file, "r"))) { if (errno == ENOENT) { - pa_log_info("starting with empty ruleset."); + pa_log_info("Starting with empty ruleset."); ret = 0; } else - pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); + pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); goto finish; } @@ -236,11 +230,7 @@ static int save_rules(struct userdata *u) { pa_log_info("Saving rules..."); - f = u->table_file ? - fopen(u->table_file, "w") : - pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w"); - - if (!f) { + if (!(f = fopen(u->table_file, "w"))) { pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); goto finish; } @@ -280,10 +270,10 @@ finish: static char* client_name(pa_client *c) { char *t, *e; - if (!c->name || !c->driver) + if (!pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME) || !c->driver) return NULL; - t = pa_sprintf_malloc("%s$%s", c->driver, c->name); + t = pa_sprintf_malloc("%s$%s", c->driver, pa_proplist_gets(c->proplist, PA_PROP_APPLICATION_NAME)); t[strcspn(t, "\n\r#")] = 0; if (!*t) { @@ -496,7 +486,6 @@ int pa__init(pa_module*m) { u = pa_xnew(struct userdata, 1); u->core = m->core; u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); - u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL)); u->modified = FALSE; u->subscription = NULL; u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL; @@ -504,6 +493,9 @@ int pa__init(pa_module*m) { m->userdata = u; + if (!(u->table_file = pa_state_path(pa_modargs_get_value(ma, "table", DEFAULT_VOLUME_TABLE_FILE)))) + goto fail; + if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 || pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) { pa_log("restore_volume= and restore_device= expect boolean arguments"); @@ -521,12 +513,12 @@ int pa__init(pa_module*m) { u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); if (restore_device) { - u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_new_hook_callback, u); - u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_new_hook_callback, u); + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_EARLY, (pa_hook_cb_t) source_output_new_hook_callback, u); } if (restore_volume) - u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); pa_modargs_free(ma); return 0; diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c index f8bae02f..b452c3bf 100644 --- a/src/modules/module-waveout.c +++ b/src/modules/module-waveout.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index 87c6849d..f7be48f7 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -45,14 +43,24 @@ #include "module-x11-bell-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("X11 Bell interceptor"); +PA_MODULE_DESCRIPTION("X11 bell interceptor"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>"); +static const char* const valid_modargs[] = { + "sink", + "sample", + "display", + NULL +}; + struct userdata { pa_core *core; + pa_module *module; + int xkb_event_base; + char *sink_name; char *scache_item; @@ -60,14 +68,7 @@ struct userdata { pa_x11_client *x11_client; }; -static const char* const valid_modargs[] = { - "sink", - "sample", - "display", - NULL -}; - -static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { +static int x11_event_cb(pa_x11_wrapper *w, XEvent *e, void *userdata) { XkbBellNotifyEvent *bne; struct userdata *u = userdata; @@ -81,7 +82,7 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { bne = (XkbBellNotifyEvent*) e; - if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) { + if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, TRUE, (bne->percent*PA_VOLUME_NORM)/100, NULL, NULL) < 0) { pa_log_info("Ringing bell failed, reverting to X11 device bell."); XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent); } @@ -89,6 +90,25 @@ static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { return 1; } +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { + struct userdata *u = userdata; + + pa_assert(w); + pa_assert(u); + pa_assert(u->x11_wrapper == w); + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + u->x11_client = NULL; + u->x11_wrapper = NULL; + + pa_module_unload_request(u->module); +} + int pa__init(pa_module*m) { struct userdata *u = NULL; @@ -105,7 +125,8 @@ int pa__init(pa_module*m) { m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; - u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell")); + u->module = m; + u->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "bell-window-system")); u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); u->x11_client = NULL; @@ -133,7 +154,7 @@ int pa__init(pa_module*m) { XkbSetAutoResetControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbAudibleBellMask, &auto_ctrls, &auto_values); XkbChangeEnabledControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbAudibleBellMask, 0); - u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u); + u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_cb, x11_kill_cb, u); pa_modargs_free(ma); @@ -153,11 +174,9 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!m->userdata) + if (!(u = m->userdata)) return; - u = m->userdata; - pa_xfree(u->scache_item); pa_xfree(u->sink_name); diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c index 429c2a69..705d90f4 100644 --- a/src/modules/module-x11-publish.c +++ b/src/modules/module-x11-publish.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -54,7 +52,7 @@ #include "module-x11-publish-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); -PA_MODULE_DESCRIPTION("X11 Credential Publisher"); +PA_MODULE_DESCRIPTION("X11 credential publisher"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE("display=<X11 display>"); @@ -69,16 +67,39 @@ static const char* const valid_modargs[] = { struct userdata { pa_core *core; - pa_x11_wrapper *x11_wrapper; + pa_module *module; + char *id; uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; - int auth_cookie_in_property; + pa_bool_t auth_cookie_in_property; + + pa_x11_wrapper *x11_wrapper; + pa_x11_client *x11_client; }; +static void x11_kill_cb(pa_x11_wrapper *w, void *userdata) { + struct userdata *u = userdata; + + pa_assert(w); + pa_assert(u); + pa_assert(u->x11_wrapper == w); + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + u->x11_client = NULL; + u->x11_wrapper = NULL; + + pa_module_unload_request(u->module); +} + static int load_key(struct userdata *u, const char*fn) { pa_assert(u); - u->auth_cookie_in_property = 0; + u->auth_cookie_in_property = FALSE; if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) { pa_log_debug("using already loaded auth cookie."); @@ -96,7 +117,7 @@ static int load_key(struct userdata *u, const char*fn) { pa_log_debug("Loading cookie from disk."); if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) - u->auth_cookie_in_property = 1; + u->auth_cookie_in_property = TRUE; return 0; } @@ -117,10 +138,13 @@ int pa__init(pa_module*m) { goto fail; } - m->userdata = u = pa_xmalloc(sizeof(struct userdata)); + m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; + u->module = m; u->id = NULL; - u->auth_cookie_in_property = 0; + u->auth_cookie_in_property = FALSE; + u->x11_client = NULL; + u->x11_wrapper = NULL; if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) goto fail; @@ -152,7 +176,10 @@ int pa__init(pa_module*m) { pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx))); + u->x11_client = pa_x11_client_new(u->x11_wrapper, NULL, x11_kill_cb, u); + pa_modargs_free(ma); + return 0; fail: @@ -160,6 +187,7 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } @@ -171,6 +199,9 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; + if (u->x11_client) + pa_x11_client_free(u->x11_client); + if (u->x11_wrapper) { char t[256]; @@ -185,10 +216,9 @@ void pa__done(pa_module*m) { pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE"); XSync(pa_x11_wrapper_get_display(u->x11_wrapper), False); } - } - if (u->x11_wrapper) pa_x11_wrapper_unref(u->x11_wrapper); + } if (u->auth_cookie_in_property) pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME); diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c index e9efa096..696826d8 100644 --- a/src/modules/module-x11-xsmp.c +++ b/src/modules/module-x11-xsmp.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -42,6 +40,7 @@ #include <pulsecore/namereg.h> #include <pulsecore/log.h> #include <pulsecore/core-util.h> +#include <pulsecore/x11wrap.h> #include "module-x11-xsmp-symdef.h" @@ -49,20 +48,36 @@ PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("X11 session management"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("session_manager=<session manager string> display=<X11 display>"); -static int ice_in_use = 0; +static pa_bool_t ice_in_use = FALSE; static const char* const valid_modargs[] = { + "session_manager", + "display", NULL }; +struct userdata { + pa_core *core; + pa_module *module; + pa_client *client; + SmcConn connection; + pa_x11_wrapper *x11; +}; + static void die_cb(SmcConn connection, SmPointer client_data){ - pa_core *c = PA_CORE(client_data); + struct userdata *u = client_data; + pa_assert(u); + + pa_log_debug("Got die message from XSMP."); - pa_log_debug("Got die message from XSM. Exiting..."); + pa_x11_wrapper_kill(u->x11); - pa_core_assert_ref(c); - c->mainloop->quit(c->mainloop, 0); + pa_x11_wrapper_unref(u->x11); + u->x11 = NULL; + + pa_module_unload_request(u->module); } static void save_complete_cb(SmcConn connection, SmPointer client_data) { @@ -86,12 +101,15 @@ static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_fla } static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) { - pa_core *c = client_data; - - pa_assert(c); + struct pa_core *c = client_data; if (opening) - *watch_data = c->mainloop->io_new(c->mainloop, IceConnectionNumber(connection), PA_IO_EVENT_INPUT, ice_io_cb, connection); + *watch_data = c->mainloop->io_new( + c->mainloop, + IceConnectionNumber(connection), + PA_IO_EVENT_INPUT, + ice_io_cb, + connection); else c->mainloop->io_free(*watch_data); } @@ -99,12 +117,13 @@ static void new_ice_connection(IceConn connection, IcePointer client_data, Bool int pa__init(pa_module*m) { pa_modargs *ma = NULL; - char t[256], *vendor, *client_id; + char t[256], *vendor, *client_id, *k; SmcCallbacks callbacks; SmProp prop_program, prop_user; SmProp *prop_list[2]; SmPropValue val_program, val_user; - SmcConn connection; + struct userdata *u; + const char *e; pa_assert(m); @@ -114,21 +133,33 @@ int pa__init(pa_module*m) { } IceAddConnectionWatch(new_ice_connection, m->core); - ice_in_use = 1; + ice_in_use = TRUE; + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->client = NULL; + u->connection = NULL; + u->x11 = NULL; if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; } - if (!getenv("SESSION_MANAGER")) { + if (!(u->x11 = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) + goto fail; + + e = pa_modargs_get_value(ma, "session_manager", NULL); + + if (!e && !getenv("SESSION_MANAGER")) { pa_log("X11 session manager not running."); goto fail; } memset(&callbacks, 0, sizeof(callbacks)); callbacks.die.callback = die_cb; - callbacks.die.client_data = m->core; + callbacks.die.client_data = u; callbacks.save_yourself.callback = save_yourself_cb; callbacks.save_yourself.client_data = m->core; callbacks.save_complete.callback = save_complete_cb; @@ -136,8 +167,8 @@ int pa__init(pa_module*m) { callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb; callbacks.shutdown_cancelled.client_data = m->core; - if (!(m->userdata = connection = SmcOpenConnection( - NULL, m->core, + if (!(u->connection = SmcOpenConnection( + (char*) e, m->core, SmProtoMajor, SmProtoMinor, SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, &callbacks, NULL, &client_id, @@ -164,9 +195,16 @@ int pa__init(pa_module*m) { prop_user.vals = &val_user; prop_list[1] = &prop_user; - SmcSetProperties(connection, PA_ELEMENTSOF(prop_list), prop_list); + SmcSetProperties(u->connection, PA_ELEMENTSOF(prop_list), prop_list); + + pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(u->connection), client_id); + k = pa_sprintf_malloc("XSMP Session on %s as %s", vendor, client_id); + u->client = pa_client_new(u->core, __FILE__, k); + pa_xfree(k); + + pa_proplist_sets(u->client->proplist, "xsmp.vendor", vendor); + pa_proplist_sets(u->client->proplist, "xsmp.client.id", client_id); - pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(connection), client_id); free(vendor); free(client_id); @@ -184,13 +222,26 @@ fail: } void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); - if (m->userdata) - SmcCloseConnection(m->userdata, 0, NULL); + if ((u = m->userdata)) { + + if (u->connection) + SmcCloseConnection(u->connection, 0, NULL); + + if (u->client) + pa_client_free(u->client); + + if (u->x11) + pa_x11_wrapper_unref(u->x11); + + pa_xfree(u); + } if (ice_in_use) { IceRemoveConnectionWatch(new_ice_connection, m->core); - ice_in_use = 0; + ice_in_use = FALSE; } } diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 4e76f448..2fc81370 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -164,8 +162,7 @@ static void resolver_cb( pa_module *m; ss = u->core->default_sample_spec; - pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); for (l = txt; l; l = l->next) { char *key, *value; @@ -190,10 +187,8 @@ static void resolver_cb( avahi_free(value); } - if (!channel_map_set && cm.channels != ss.channels) { - pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX)); - pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); - } + if (!channel_map_set && cm.channels != ss.channels) + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); if (!pa_sample_spec_valid(&ss)) { pa_log("Service '%s' contains an invalid sample specification.", name); @@ -237,7 +232,7 @@ static void resolver_cb( t, dname, pa_channel_map_snprint(cmt, sizeof(cmt), &cm)); - pa_log_debug("Loading module-tunnel-%s with arguments '%s'", module_name, args); + pa_log_debug("Loading %s with arguments '%s'", module_name, args); if ((m = pa_module_load(u->core, module_name, args))) { tnl->module_index = m->index; diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 46969a24..cb9c1285 100644 --- a/src/modules/module-zeroconf-publish.c +++ b/src/modules/module-zeroconf-publish.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -115,7 +113,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = sink->sample_spec; *ret_map = sink->channel_map; *ret_name = sink->name; - *ret_description = sink->description; + *ret_description = pa_strnull(pa_proplist_gets(sink->proplist, PA_PROP_DEVICE_DESCRIPTION)); *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; } else if (pa_source_isinstance(s->device)) { @@ -124,7 +122,7 @@ static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_chann *ret_ss = source->sample_spec; *ret_map = source->channel_map; *ret_name = source->name; - *ret_description = source->description; + *ret_description = pa_strnull(pa_proplist_gets(source->proplist, PA_PROP_DEVICE_DESCRIPTION)); *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); } else @@ -304,10 +302,10 @@ static struct service *get_service(struct userdata *u, pa_object *device) { s->device = device; if (pa_sink_isinstance(device)) { - if (!(n = PA_SINK(device)->description)) + if (!(n = pa_proplist_gets(PA_SINK(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) n = PA_SINK(device)->name; } else { - if (!(n = PA_SOURCE(device)->description)) + if (!(n = pa_proplist_gets(PA_SOURCE(device)->proplist, PA_PROP_DEVICE_DESCRIPTION))) n = PA_SOURCE(device)->name; } @@ -578,12 +576,12 @@ int pa__init(pa_module*m) { u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); - u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); - u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); - u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); - u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); - u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); - u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PUT], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_PROPLIST_CHANGED], PA_HOOK_LATE, (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], PA_HOOK_LATE, (pa_hook_cb_t) device_unlink_cb, u); u->main_entry_group = NULL; diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c index 9598feee..2791e165 100644 --- a/src/modules/oss-util.c +++ b/src/modules/oss-util.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -251,7 +249,7 @@ int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { return 0; } -int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) { +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; @@ -273,7 +271,7 @@ int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *v return 0; } -int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { +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; diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h index 259a622a..654f7bba 100644 --- a/src/modules/oss-util.h +++ b/src/modules/oss-util.h @@ -1,8 +1,6 @@ #ifndef fooossutilhfoo #define fooossutilhfoo -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -33,8 +31,8 @@ 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, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); -int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume); +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); diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index d8e7a781..cff5cf8b 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -51,6 +51,7 @@ #include <pulsecore/atomic.h> #include <pulsecore/rtclock.h> #include <pulsecore/atomic.h> +#include <pulsecore/time-smoother.h> #include "module-rtp-recv-symdef.h" @@ -69,9 +70,11 @@ PA_MODULE_USAGE( #define SAP_PORT 9875 #define DEFAULT_SAP_ADDRESS "224.0.0.56" -#define MEMBLOCKQ_MAXLENGTH (1024*170) +#define MEMBLOCKQ_MAXLENGTH (1024*1024*40) #define MAX_SESSIONS 16 #define DEATH_TIMEOUT 20 +#define RATE_UPDATE_INTERVAL (5*PA_USEC_PER_SEC) +#define LATENCY_USEC (500*PA_USEC_PER_MSEC) static const char* const valid_modargs[] = { "sink", @@ -97,6 +100,12 @@ struct session { pa_rtpoll_item *rtpoll_item; pa_atomic_t timestamp; + + pa_smoother *smoother; + pa_usec_t intended_latency; + pa_usec_t sink_latency; + + pa_usec_t last_rate_update; }; struct userdata { @@ -133,21 +142,37 @@ static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t } /* Called from I/O thread context */ -static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) { +static int sink_input_pop_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { struct session *s; pa_sink_input_assert_ref(i); pa_assert_se(s = i->userdata); - return pa_memblockq_peek(s->memblockq, chunk); + if (pa_memblockq_peek(s->memblockq, chunk) < 0) + return -1; + + pa_memblockq_drop(s->memblockq, chunk->length); + + return 0; } /* Called from I/O thread context */ -static void sink_input_drop(pa_sink_input *i, size_t length) { +static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { struct session *s; + pa_sink_input_assert_ref(i); pa_assert_se(s = i->userdata); - pa_memblockq_drop(s->memblockq, length); + pa_memblockq_rewind(s->memblockq, nbytes); +} + +/* Called from thread context */ +static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { + struct session *s; + + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_memblockq_set_maxrewind(s->memblockq, nbytes); } /* Called from main context */ @@ -215,20 +240,82 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE); + pa_rtclock_get(&now); + + pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec(pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); + if (pa_memblockq_push(s->memblockq, &chunk) < 0) { - /* queue overflow, let's flush it and try again */ - pa_memblockq_flush(s->memblockq); - pa_memblockq_push(s->memblockq, &chunk); + pa_log_warn("Queue overrun"); + pa_memblockq_seek(s->memblockq, chunk.length, PA_SEEK_RELATIVE); } - /* The next timestamp we expect */ - s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size); + pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq)); pa_memblock_unref(chunk.memblock); - pa_rtclock_get(&now); + /* The next timestamp we expect */ + s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size); + pa_atomic_store(&s->timestamp, now.tv_sec); + if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { + pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; + unsigned fix_samples; + + pa_log("Updating sample rate"); + + wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); + ri = pa_bytes_to_usec(pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); + + if (PA_MSGOBJECT(s->sink_input->sink)->process_msg(PA_MSGOBJECT(s->sink_input->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_delay, 0, NULL) < 0) + sink_delay = 0; + + render_delay = pa_bytes_to_usec(pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq), &s->sink_input->sink->sample_spec); + + if (ri > render_delay+sink_delay) + ri -= render_delay+sink_delay; + else + ri = 0; + + if (wi < ri) + latency = 0; + else + latency = wi - ri; + + pa_log_debug("Write index deviates by %0.2f ms, expected %0.2f ms", (double) latency/PA_USEC_PER_MSEC, (double) s->intended_latency/PA_USEC_PER_MSEC); + + /* Calculate deviation */ + if (latency < s->intended_latency) + fix = s->intended_latency - latency; + else + fix = latency - s->intended_latency; + + /* How many samples is this per second? */ + fix_samples = fix * s->sink_input->thread_info.sample_spec.rate / RATE_UPDATE_INTERVAL; + + /* Check if deviation is in bounds */ + if (fix_samples > s->sink_input->sample_spec.rate*.20) + pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples); + + /* Fix up rate */ + if (latency < s->intended_latency) + s->sink_input->sample_spec.rate -= fix_samples; + else + s->sink_input->sample_spec.rate += fix_samples; + + pa_resampler_set_input_rate(s->sink_input->thread_info.resampler, s->sink_input->sample_spec.rate); + + pa_log_debug("Updated sampling rate to %lu Hz.", (unsigned long) s->sink_input->sample_spec.rate); + + s->last_rate_update = pa_timeval_load(&now); + } + + if (pa_memblockq_is_readable(s->memblockq) && + s->sink_input->thread_info.underrun_for > 0) { + pa_log_debug("Requesting rewind due to end of underrun"); + pa_sink_input_request_rewind(s->sink_input, 0, FALSE, TRUE); + } + return 1; } @@ -314,10 +401,9 @@ fail: static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) { struct session *s = NULL; - char *c; pa_sink *sink; int fd = -1; - pa_memblock *silence; + pa_memchunk silence; pa_sink_input_new_data data; struct timeval now; @@ -329,37 +415,46 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in goto fail; } - if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) { + if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, TRUE))) { pa_log("Sink does not exist."); goto fail; } + pa_rtclock_get(&now); + s = pa_xnew0(struct session, 1); s->userdata = u; s->first_packet = FALSE; s->sdp_info = *sdp_info; s->rtpoll_item = NULL; - - pa_rtclock_get(&now); + s->intended_latency = LATENCY_USEC; + s->smoother = pa_smoother_new(PA_USEC_PER_SEC*5, PA_USEC_PER_SEC*2, TRUE, 10); + pa_smoother_set_time_offset(s->smoother, pa_timeval_load(&now)); + s->last_rate_update = pa_timeval_load(&now); pa_atomic_store(&s->timestamp, now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) goto fail; - c = pa_sprintf_malloc("RTP Stream%s%s%s", - sdp_info->session_name ? " (" : "", - sdp_info->session_name ? sdp_info->session_name : "", - sdp_info->session_name ? ")" : ""); - pa_sink_input_new_data_init(&data); data.sink = sink; data.driver = __FILE__; - data.name = c; + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_ROLE, "stream"); + pa_proplist_setf(data.proplist, PA_PROP_MEDIA_NAME, + "RTP Stream%s%s%s", + sdp_info->session_name ? " (" : "", + sdp_info->session_name ? sdp_info->session_name : "", + sdp_info->session_name ? ")" : ""); + + if (sdp_info->session_name) + pa_proplist_sets(data.proplist, "rtp.session", sdp_info->session_name); + pa_proplist_sets(data.proplist, "rtp.origin", sdp_info->origin); + pa_proplist_setf(data.proplist, "rtp.payload", "%u", (unsigned) sdp_info->payload); data.module = u->module; pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec); s->sink_input = pa_sink_input_new(u->module->core, &data, 0); - pa_xfree(c); + pa_sink_input_new_data_done(&data); if (!s->sink_input) { pa_log("Failed to create sink input."); @@ -369,27 +464,31 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->sink_input->userdata = s; s->sink_input->parent.process_msg = sink_input_process_msg; - s->sink_input->peek = sink_input_peek; - s->sink_input->drop = sink_input_drop; + s->sink_input->pop = sink_input_pop_cb; + s->sink_input->process_rewind = sink_input_process_rewind_cb; + s->sink_input->update_max_rewind = sink_input_update_max_rewind_cb; s->sink_input->kill = sink_input_kill; s->sink_input->attach = sink_input_attach; s->sink_input->detach = sink_input_detach; - silence = pa_silence_memblock_new( - s->userdata->module->core->mempool, - &s->sink_input->sample_spec, - pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec)); + pa_sink_input_get_silence(s->sink_input, &silence); + + s->sink_latency = pa_sink_input_set_requested_latency(s->sink_input, s->intended_latency/2); + + if (s->intended_latency < s->sink_latency*2) + s->intended_latency = s->sink_latency*2; s->memblockq = pa_memblockq_new( 0, MEMBLOCKQ_MAXLENGTH, MEMBLOCKQ_MAXLENGTH, pa_frame_size(&s->sink_input->sample_spec), - pa_bytes_per_second(&s->sink_input->sample_spec)/10+1, + pa_usec_to_bytes(s->intended_latency - s->sink_latency, &s->sink_input->sample_spec), + 0, 0, - silence); + &silence); - pa_memblock_unref(silence); + pa_memblock_unref(silence.memblock); pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec)); @@ -429,12 +528,14 @@ static void session_free(struct session *s) { pa_sdp_info_destroy(&s->sdp_info); pa_rtp_context_destroy(&s->rtp_context); + pa_smoother_free(s->smoother); + pa_xfree(s); } static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { struct userdata *u = userdata; - int goodbye; + pa_bool_t goodbye = FALSE; pa_sdp_info info; struct session *s; diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 95ff15de..d0d06c4d 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -288,14 +286,20 @@ int pa__init(pa_module*m) { pa_make_fd_cloexec(sap_fd); pa_source_output_new_data_init(&data); - data.name = "RTP Monitor Stream"; + pa_proplist_sets(data.proplist, PA_PROP_MEDIA_NAME, "RTP Monitor Stream"); + pa_proplist_sets(data.proplist, "rtp.destination", dest); + pa_proplist_setf(data.proplist, "rtp.mtu", "%lu", (unsigned long) mtu); + pa_proplist_setf(data.proplist, "rtp.port", "%lu", (unsigned long) port); data.driver = __FILE__; data.module = m; data.source = s; pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); - if (!(o = pa_source_output_new(m->core, &data, 0))) { + o = pa_source_output_new(m->core, &data, 0); + pa_source_output_new_data_done(&data); + + if (!o) { pa_log("failed to create source output."); goto fail; } @@ -318,6 +322,7 @@ int pa__init(pa_module*m) { pa_frame_size(&ss), 1, 0, + 0, NULL); u->mtu = mtu; diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 997fcc34..5a33ebc2 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -55,6 +53,8 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr c->payload = payload & 127; c->frame_size = frame_size; + pa_memchunk_reset(&c->memchunk); + return c; } @@ -152,6 +152,8 @@ pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame c->fd = fd; c->frame_size = frame_size; + + pa_memchunk_reset(&c->memchunk); return c; } @@ -173,12 +175,28 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { goto fail; } - if (!size) + if (size <= 0) return 0; - chunk->memblock = pa_memblock_new(pool, size); + if (c->memchunk.length < (unsigned) size) { + size_t l; + + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); + + l = PA_MAX((size_t) size, pa_mempool_block_size_max(pool)); + + c->memchunk.memblock = pa_memblock_new(pool, l); + c->memchunk.index = 0; + c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock); + } + + pa_assert(c->memchunk.length >= (size_t) size); + + chunk->memblock = pa_memblock_ref(c->memchunk.memblock); + chunk->index = c->memchunk.index; - iov.iov_base = pa_memblock_acquire(chunk->memblock); + iov.iov_base = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index; iov.iov_len = size; m.msg_name = NULL; @@ -236,14 +254,22 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { goto fail; } - chunk->index = 12 + cc*4; - chunk->length = size - chunk->index; + chunk->index += 12 + cc*4; + chunk->length = size - 12 + cc*4; if (chunk->length % c->frame_size != 0) { pa_log_warn("Bad RTP packet size."); goto fail; } + c->memchunk.index = chunk->index + chunk->length; + c->memchunk.length = pa_memblock_get_length(c->memchunk.memblock) - c->memchunk.index; + + if (c->memchunk.length <= 0) { + pa_memblock_unref(c->memchunk.memblock); + pa_memchunk_reset(&c->memchunk); + } + return 0; fail: @@ -329,7 +355,10 @@ int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { void pa_rtp_context_destroy(pa_rtp_context *c) { pa_assert(c); - pa_close(c->fd); + pa_assert_se(pa_close(c->fd) == 0); + + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); } const char* pa_rtp_format_to_string(pa_sample_format_t f) { @@ -361,4 +390,3 @@ pa_sample_format_t pa_rtp_string_to_format(const char *s) { else return PA_SAMPLE_INVALID; } - diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index ad7175ca..a2728f05 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -1,8 +1,6 @@ #ifndef foortphfoo #define foortphfoo -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -37,6 +35,8 @@ typedef struct pa_rtp_context { uint32_t ssrc; uint8_t payload; size_t frame_size; + + pa_memchunk memchunk; } pa_rtp_context; pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size); diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index ed7eb0be..5d9b58fa 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -71,7 +69,7 @@ void pa_sap_context_destroy(pa_sap_context *c) { pa_xfree(c->sdp_data); } -int pa_sap_send(pa_sap_context *c, int goodbye) { +int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) { uint32_t header; struct sockaddr_storage sa_buf; struct sockaddr *sa = (struct sockaddr*) &sa_buf; @@ -127,7 +125,7 @@ pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) { return c; } -int pa_sap_recv(pa_sap_context *c, int *goodbye) { +int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { struct msghdr m; struct iovec iov; int size, k; diff --git a/src/modules/rtp/sap.h b/src/modules/rtp/sap.h index f906a32b..69c757cb 100644 --- a/src/modules/rtp/sap.h +++ b/src/modules/rtp/sap.h @@ -1,8 +1,6 @@ #ifndef foosaphfoo #define foosaphfoo -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -40,9 +38,9 @@ typedef struct pa_sap_context { pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data); void pa_sap_context_destroy(pa_sap_context *c); -int pa_sap_send(pa_sap_context *c, int goodbye); +int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye); pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd); -int pa_sap_recv(pa_sap_context *c, int *goodbye); +int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye); #endif diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index 50ac157a..cef90433 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -117,7 +115,7 @@ static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { uint16_t port = 0; - int ss_valid = 0; + pa_bool_t ss_valid = FALSE; pa_assert(t); pa_assert(i); @@ -202,7 +200,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { i->payload = (uint8_t) _payload; if (pa_rtp_sample_spec_from_payload(i->payload, &i->sample_spec)) - ss_valid = 1; + ss_valid = TRUE; } } } else if (pa_startswith(t, "a=rtpmap:")) { @@ -222,7 +220,7 @@ pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { c[strcspn(c, "\n")] = 0; if (parse_sdp_sample_spec(&i->sample_spec, c)) - ss_valid = 1; + ss_valid = TRUE; } } } diff --git a/src/modules/rtp/sdp.h b/src/modules/rtp/sdp.h index 7c91fca6..933a602b 100644 --- a/src/modules/rtp/sdp.h +++ b/src/modules/rtp/sdp.h @@ -1,8 +1,6 @@ #ifndef foosdphfoo #define foosdphfoo -/* $Id$ */ - /*** This file is part of PulseAudio. |