diff options
Diffstat (limited to 'src/modules')
60 files changed, 8092 insertions, 2633 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 906de58d..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> @@ -34,6 +33,8 @@ #include <pulsecore/log.h> #include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/atomic.h> #include "alsa-util.h" @@ -227,15 +228,19 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, + [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE, + [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE, }; static const pa_sample_format_t try_order[] = { - PA_SAMPLE_S16NE, - PA_SAMPLE_S16RE, PA_SAMPLE_FLOAT32NE, PA_SAMPLE_FLOAT32RE, - PA_SAMPLE_ULAW, + PA_SAMPLE_S32NE, + PA_SAMPLE_S32RE, + PA_SAMPLE_S16NE, + PA_SAMPLE_S16RE, PA_SAMPLE_ALAW, + PA_SAMPLE_ULAW, PA_SAMPLE_U8, PA_SAMPLE_INVALID }; @@ -256,6 +261,10 @@ static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_s *f = PA_SAMPLE_S16LE; else if (*f == PA_SAMPLE_S16LE) *f = PA_SAMPLE_S16BE; + else if (*f == PA_SAMPLE_S32BE) + *f = PA_SAMPLE_S32LE; + else if (*f == PA_SAMPLE_S32LE) + *f = PA_SAMPLE_S32BE; else goto try_auto; @@ -276,13 +285,27 @@ try_auto: /* Set the hardware parameters of the given ALSA device. Returns the * selected fragment settings in *period and *period_size */ -int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap) { +int pa_alsa_set_hw_params( + snd_pcm_t *pcm_handle, + pa_sample_spec *ss, + uint32_t *periods, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number) { + int ret = -1; + snd_pcm_uframes_t _period_size = *period_size; + unsigned int _periods = *periods; snd_pcm_uframes_t buffer_size; unsigned int r = ss->rate; unsigned int c = ss->channels; pa_sample_format_t f = ss->format; snd_pcm_hw_params_t *hwparams; + pa_bool_t _use_mmap = use_mmap && *use_mmap; + pa_bool_t _use_tsched = use_tsched && *use_tsched; + int dir; pa_assert(pcm_handle); pa_assert(ss); @@ -291,13 +314,13 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p snd_pcm_hw_params_alloca(&hwparams); - buffer_size = *periods * *period_size; + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) + goto finish; - if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0 || - (ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) goto finish; - if (use_mmap && *use_mmap) { + if (_use_mmap) { if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { /* mmap() didn't work, fall back to interleaved */ @@ -305,58 +328,95 @@ int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *p if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) goto finish; - if (use_mmap) - *use_mmap = 0; + _use_mmap = FALSE; } } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) goto finish; + if (!_use_mmap) + _use_tsched = FALSE; + if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) goto finish; if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) goto finish; - if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) - goto finish; + /* Adjust the buffer sizes, if we didn't get the rate we were asking for */ + _period_size = (snd_pcm_uframes_t) (((uint64_t) _period_size * r) / ss->rate); + tsched_size = (snd_pcm_uframes_t) (((uint64_t) tsched_size * r) / ss->rate); + + if (require_exact_channel_number) { + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) + goto finish; + } else { + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + goto finish; + } + + if (_use_tsched) { + _period_size = tsched_size; + _periods = 1; + + pa_assert_se(snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size) == 0); + pa_log_debug("Maximum hw buffer size is %u ms", (unsigned) buffer_size * 1000 / r); + } + + buffer_size = _periods * _period_size; - if ((*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 ((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; - if (ss->rate != r) { + if (ss->rate != r) pa_log_warn("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r); - /* If the sample rate deviates too much, we need to resample */ - if (r < ss->rate*.95 || r > ss->rate*1.05) - ss->rate = r; - } - - if (ss->channels != c) { + if (ss->channels != c) pa_log_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); - ss->channels = c; - } - if (ss->format != f) { + if (ss->format != f) pa_log_warn("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); - ss->format = f; - } 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; - pa_assert(buffer_size > 0); - pa_assert(*period_size > 0); - *periods = buffer_size / *period_size; - pa_assert(*periods > 0); + /* If the sample rate deviates too much, we need to resample */ + if (r < ss->rate*.95 || r > ss->rate*1.05) + ss->rate = r; + ss->channels = c; + ss->format = f; + + pa_assert(_periods > 0); + pa_assert(_period_size > 0); + + *periods = _periods; + *period_size = _period_size; + + if (use_mmap) + *use_mmap = _use_mmap; + + if (use_tsched) + *use_tsched = _use_tsched; ret = 0; @@ -365,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; @@ -388,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; @@ -396,6 +461,220 @@ int pa_alsa_set_sw_params(snd_pcm_t *pcm) { return 0; } +struct device_info { + pa_channel_map map; + const char *name; +}; + +static const struct device_info device_table[] = { + {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, "front" }, + + {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, "surround40" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE }}, "surround41" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER }}, "surround50" }, + + {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, "surround51" }, + + {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }} , "surround71" }, + + {{ 0, { 0 }}, NULL } +}; + +static pa_bool_t channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { + pa_bool_t in_a[PA_CHANNEL_POSITION_MAX]; + unsigned i; + + pa_assert(a); + pa_assert(b); + + memset(in_a, 0, sizeof(in_a)); + + for (i = 0; i < a->channels; i++) + in_a[a->map[i]] = TRUE; + + for (i = 0; i < b->channels; i++) + if (!in_a[b->map[i]]) + return FALSE; + + return TRUE; +} + +snd_pcm_t *pa_alsa_open_by_device_id( + const char *dev_id, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { + + int i; + int direction = 1; + int err; + char *d; + snd_pcm_t *pcm_handle; + + pa_assert(dev_id); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + /* First we try to find a device string with a superset of the + * requested channel map and open it without the plug: prefix. We + * iterate through our device table from top to bottom and take + * the first that matches. If we didn't find a working device that + * way, we iterate backwards, and check all devices that do not + * provide a superset of the requested channel map.*/ + + for (i = 0;; i += direction) { + pa_sample_spec try_ss; + + if (i < 0) { + pa_assert(direction == -1); + + /* OK, so we iterated backwards, and now are at the + * beginning of our list. */ + + break; + + } else if (!device_table[i].name) { + pa_assert(direction == 1); + + /* OK, so we are at the end of our list. at iterated + * forwards. */ + + i--; + direction = -1; + } + + if ((direction > 0) == !channel_map_superset(&device_table[i].map, map)) + continue; + + d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); + pa_log_debug("Trying %s...", d); + + 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; + } + + try_ss.channels = device_table[i].map.channels; + try_ss.rate = ss->rate; + try_ss.format = ss->format; + + if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { + pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + continue; + } + + *ss = try_ss; + *map = device_table[i].map; + pa_assert(map->channels == ss->channels); + *dev = d; + return pcm_handle; + } + + /* OK, we didn't find any good device, so let's try the raw plughw: stuff */ + + 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, tsched_size, use_mmap, use_tsched); + pa_xfree(d); + + return pcm_handle; +} + +snd_pcm_t *pa_alsa_open_by_device_string( + const char *device, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched) { + + int err; + char *d; + snd_pcm_t *pcm_handle; + + pa_assert(device); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + d = pa_xstrdup(device); + + for (;;) { + + 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, 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 */ + + if (pa_startswith(d, "hw:")) { + char *t = pa_sprintf_malloc("plughw:%s", d+3); + pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", d, t); + pa_xfree(d); + d = t; + + snd_pcm_close(pcm_handle); + continue; + } + + pa_log("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + return NULL; + } + } + + *dev = d; + + if (ss->channels != map->channels) + pa_channel_map_init_extend(map, ss->channels, PA_CHANNEL_MAP_ALSA); + + return pcm_handle; + } +} + int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { int err; @@ -403,7 +682,7 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { pa_assert(dev); if ((err = snd_mixer_attach(mixer, dev)) < 0) { - pa_log_warn("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); + pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); return -1; } @@ -417,6 +696,8 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { return -1; } + pa_log_info("Successfully attached to mixer '%s'", dev); + return 0; } @@ -432,7 +713,7 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const snd_mixer_selem_id_set_name(sid, name); if (!(elem = snd_mixer_find_selem(mixer, sid))) { - pa_log_warn("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); + pa_log_info("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); if (fallback) { snd_mixer_selem_id_set_name(sid, fallback); @@ -447,3 +728,391 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const return elem; } + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + + [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, + [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { + unsigned i; + pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; + pa_bool_t mono_used = FALSE; + + pa_assert(elem); + pa_assert(channel_map); + pa_assert(mixer_map); + + memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); + + if (channel_map->channels > 1 && + ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || + (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { + pa_log_info("ALSA device lacks independant volume controls for each channel, falling back to software volume control."); + return -1; + } + + for (i = 0; i < channel_map->channels; i++) { + snd_mixer_selem_channel_id_t id; + pa_bool_t is_mono; + + is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; + id = alsa_channel_ids[channel_map->map[i]]; + + if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { + pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer. Falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { + pa_log_info("Channel map has duplicate channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || + (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { + + pa_log_info("ALSA device lacks separate volumes control for channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if (is_mono) { + mixer_map[i] = SND_MIXER_SCHN_MONO; + mono_used = TRUE; + } else { + mixer_map[i] = id; + alsa_channel_used[id] = TRUE; + } + } + + pa_log_info("All %u channels can be mapped to mixer channels.", channel_map->channels); + + return 0; +} + +void pa_alsa_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 6f1f927e..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; @@ -38,10 +38,60 @@ struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); -int pa_alsa_set_hw_params(snd_pcm_t *pcm_handle, pa_sample_spec *ss, uint32_t *periods, snd_pcm_uframes_t *period_size, int *use_mmap); -int pa_alsa_set_sw_params(snd_pcm_t *pcm); +int pa_alsa_set_hw_params( + snd_pcm_t *pcm_handle, + pa_sample_spec *ss, + uint32_t *periods, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched, + pa_bool_t require_exact_channel_number); + +int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min); int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback); +snd_pcm_t *pa_alsa_open_by_device_id( + const char *dev_id, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched); + +snd_pcm_t *pa_alsa_open_by_device_string( + const char *device, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + snd_pcm_uframes_t tsched_size, + pa_bool_t *use_mmap, + pa_bool_t *use_tsched); + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); + +void pa_alsa_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 new file mode 100644 index 00000000..3767f01c --- /dev/null +++ b/src/modules/bt-proximity-helper.c @@ -0,0 +1,202 @@ +/* + * Small SUID helper that allows us to ping a BT device. Borrows + * heavily from bluez-utils' l2ping, which is licensed as GPL2+ + * and comes with a copyright like this: + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2007 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#undef NDEBUG + +#include <assert.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/select.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> + +#define PING_STRING "PulseAudio" +#define IDENT 200 +#define TIMEOUT 4 +#define INTERVAL 2 + +static void update_status(int found) { + static int status = -1; + + if (!found && status != 0) + printf("-"); + if (found && status <= 0) + printf("+"); + + fflush(stdout); + status = !!found; +} + +int main(int argc, char *argv[]) { + struct sockaddr_l2 addr; + union { + l2cap_cmd_hdr hdr; + uint8_t buf[L2CAP_CMD_HDR_SIZE + sizeof(PING_STRING)]; + } packet; + int fd = -1; + uint8_t id = IDENT; + int connected = 0; + + assert(argc == 2); + + for (;;) { + fd_set fds; + struct timeval end; + ssize_t r; + + if (!connected) { + + if (fd >= 0) + close(fd); + + if ((fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) { + fprintf(stderr, "socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP) failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, BDADDR_ANY); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "bind() failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(argv[1], &addr.l2_bdaddr); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "connect() failed: %s", strerror(errno)); + goto finish; + } + + connected = 1; + } + + assert(connected); + + memset(&packet, 0, sizeof(packet)); + strcpy((char*) packet.buf + L2CAP_CMD_HDR_SIZE, PING_STRING); + packet.hdr.ident = id; + packet.hdr.len = htobs(sizeof(PING_STRING)); + packet.hdr.code = L2CAP_ECHO_REQ; + + if ((r = send(fd, &packet, sizeof(packet), 0)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "send() failed: %s", strerror(errno)); + goto finish; + } + + assert(r == sizeof(packet)); + + gettimeofday(&end, NULL); + end.tv_sec += TIMEOUT; + + for (;;) { + struct timeval now, delta; + + gettimeofday(&now, NULL); + + if (timercmp(&end, &now, <=)) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + timersub(&end, &now, &delta); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (select(fd+1, &fds, NULL, NULL, &delta) < 0) { + fprintf(stderr, "select() failed: %s", strerror(errno)); + goto finish; + } + + if ((r = recv(fd, &packet, sizeof(packet), 0)) <= 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + fprintf(stderr, "send() failed: %s", r == 0 ? "EOF" : strerror(errno)); + goto finish; + } + + assert(r >= L2CAP_CMD_HDR_SIZE); + + if (packet.hdr.ident != id) + continue; + + if (packet.hdr.code == L2CAP_ECHO_RSP || packet.hdr.code == L2CAP_COMMAND_REJ) { + + if (++id >= 0xFF) + id = IDENT; + + update_status(1); + sleep(INTERVAL); + break; + } + } + } + +finish: + + if (fd >= 0) + close(fd); + + return 1; +} diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c index ccc658b7..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. @@ -35,7 +33,7 @@ struct pa_dbus_connection { PA_REFCNT_DECLARE; - + pa_core *core; DBusConnection *connection; const char *property_name; @@ -44,7 +42,7 @@ struct pa_dbus_connection { static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) { DBusConnection *conn = userdata; - + if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) { /* no more data to process, disable the deferred */ ea->defer_enable(ev, 0); @@ -56,13 +54,13 @@ static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, voi pa_dbus_connection *c = userdata; pa_assert(c); - + switch(status) { - + case DBUS_DISPATCH_COMPLETE: c->core->mainloop->defer_enable(c->dispatch_event, 0); break; - + case DBUS_DISPATCH_DATA_REMAINS: case DBUS_DISPATCH_NEED_MEMORY: default: @@ -76,9 +74,9 @@ static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { pa_io_event_flags_t events = 0; pa_assert(watch); - + flags = dbus_watch_get_flags(watch); - + /* no watch flags for disabled watches */ if (!dbus_watch_get_enabled(watch)) return PA_IO_EVENT_NULL; @@ -142,14 +140,14 @@ static dbus_bool_t add_watch(DBusWatch *watch, void *data) { pa_assert(c); ev = c->mainloop->io_new( - c->mainloop, + c->mainloop, #if HAVE_DBUS_WATCH_GET_UNIX_FD - dbus_watch_get_unix_fd(watch), + dbus_watch_get_unix_fd(watch), #else - dbus_watch_get_fd(watch), + dbus_watch_get_fd(watch), #endif get_watch_flags(watch), handle_io_event, watch); - + dbus_watch_set_data(watch, ev, NULL); return TRUE; @@ -162,7 +160,7 @@ static void remove_watch(DBusWatch *watch, void *data) { pa_assert(watch); pa_assert(c); - + if ((ev = dbus_watch_get_data(watch))) c->mainloop->io_free(ev); } @@ -174,7 +172,7 @@ static void toggle_watch(DBusWatch *watch, void *data) { pa_assert(watch); pa_core_assert_ref(c); - + pa_assert_se(ev = dbus_watch_get_data(watch)); /* get_watch_flags() checks if the watch is enabled */ @@ -210,7 +208,7 @@ static void remove_timeout(DBusTimeout *timeout, void *data) { pa_assert(timeout); pa_assert(c); - + if ((ev = dbus_timeout_get_data(timeout))) c->mainloop->time_free(ev); } @@ -222,15 +220,15 @@ static void toggle_timeout(DBusTimeout *timeout, void *data) { pa_assert(timeout); pa_assert(c); - + pa_assert_se(ev = dbus_timeout_get_data(timeout)); if (dbus_timeout_get_enabled(timeout)) { struct timeval tv; - + pa_gettimeofday(&tv); pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); - + c->mainloop->time_restart(ev, &tv); } else c->mainloop->time_restart(ev, NULL); @@ -240,7 +238,7 @@ static void wakeup_main(void *userdata) { pa_dbus_connection *c = userdata; pa_assert(c); - + /* this will wakeup the mainloop and dispatch events, although * it may not be the cleanest way of accomplishing it */ c->core->mainloop->defer_enable(c->dispatch_event, 1); @@ -265,7 +263,7 @@ DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ pa_assert(c); pa_assert(PA_REFCNT_VALUE(c) > 0); pa_assert(c->connection); - + return c->connection; } @@ -282,7 +280,7 @@ void pa_dbus_connection_unref(pa_dbus_connection *c) { * both unload and shutdown */ while (dbus_connection_read_write_dispatch(c->connection, -1)); } - + /* already disconnected, just free */ pa_property_remove(c->core, c->property_name); c->core->mainloop->defer_free(c->dispatch_event); @@ -300,7 +298,7 @@ pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { } pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) { - + static const char *const prop_name[] = { [DBUS_BUS_SESSION] = "dbus-connection-session", [DBUS_BUS_SYSTEM] = "dbus-connection-system", 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 24f60b1e..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. @@ -33,30 +31,23 @@ #include <sys/types.h> #include <sys/wait.h> #include <fcntl.h> -#include <dirent.h> - -#ifdef HAVE_SYS_PRCTL_H -#include <sys/prctl.h> -#endif -#ifdef HAVE_SYS_RESOURCE_H -#include <sys/resource.h> -#endif +#include <pulse/xmalloc.h> #include <pulsecore/module.h> #include <pulsecore/core.h> #include <pulsecore/llist.h> #include <pulsecore/core-util.h> #include <pulsecore/log.h> #include <pulse/mainloop-api.h> -#include <pulse/xmalloc.h> #include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> #include "module-gconf-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("GConf Adapter") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("GConf Adapter"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); #define MAX_MODULES 10 #define BUF_MAX 2048 @@ -225,8 +216,11 @@ static int handle_event(struct userdata *u) { int ret = 0; do { - if ((opcode = read_byte(u)) < 0) + if ((opcode = read_byte(u)) < 0){ + if (errno == EINTR || errno == EAGAIN) + break; goto fail; + } switch (opcode) { case '!': @@ -334,117 +328,6 @@ static void io_event_cb( } } -static int start_client(const char *n, pid_t *pid) { - pid_t child; - int pipe_fds[2] = { -1, -1 }; - - if (pipe(pipe_fds) < 0) { - pa_log("pipe() failed: %s", pa_cstrerror(errno)); - goto fail; - } - - if ((child = fork()) == (pid_t) -1) { - pa_log("fork() failed: %s", pa_cstrerror(errno)); - goto fail; - } else if (child != 0) { - - /* Parent */ - close(pipe_fds[1]); - - if (pid) - *pid = child; - - return pipe_fds[0]; - } else { -#ifdef __linux__ - DIR* d; -#endif - int max_fd, i; - /* child */ - - close(pipe_fds[0]); - dup2(pipe_fds[1], 1); - - if (pipe_fds[1] != 1) - close(pipe_fds[1]); - - close(0); - open("/dev/null", O_RDONLY); - - close(2); - open("/dev/null", O_WRONLY); - -#ifdef __linux__ - - if ((d = opendir("/proc/self/fd/"))) { - - struct dirent *de; - - while ((de = readdir(d))) { - char *e = NULL; - int fd; - - if (de->d_name[0] == '.') - continue; - - errno = 0; - fd = strtol(de->d_name, &e, 10); - pa_assert(errno == 0 && e && *e == 0); - - if (fd >= 3 && dirfd(d) != fd) - close(fd); - } - - closedir(d); - } else { - -#endif - - max_fd = 1024; - -#ifdef HAVE_SYS_RESOURCE_H - { - struct rlimit r; - if (getrlimit(RLIMIT_NOFILE, &r) == 0) - max_fd = r.rlim_max; - } -#endif - - for (i = 3; i < max_fd; i++) - close(i); -# -#ifdef __linux__ - } -#endif - -#ifdef PR_SET_PDEATHSIG - /* On Linux we can use PR_SET_PDEATHSIG to have the helper - process killed when the daemon dies abnormally. On non-Linux - machines the client will die as soon as it writes data to - stdout again (SIGPIPE) */ - - prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); -#endif - -#ifdef SIGPIPE - /* Make sure that SIGPIPE kills the child process */ - signal(SIGPIPE, SIG_DFL); -#endif - - execl(n, n, NULL); - _exit(1); - } - -fail: - if (pipe_fds[0] >= 0) - close(pipe_fds[0]); - - if (pipe_fds[1] >= 0) - close(pipe_fds[1]); - - return -1; -} - int pa__init(pa_module*m) { struct userdata *u; int r; @@ -460,7 +343,7 @@ int pa__init(pa_module*m) { u->io_event = NULL; u->buf_fill = 0; - if ((u->fd = start_client(PA_GCONF_HELPER, &u->pid)) < 0) + if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0) goto fail; u->io_event = m->core->mainloop->io_new( @@ -493,20 +376,20 @@ void pa__done(pa_module*m) { if (!(u = m->userdata)) return; + if (u->pid != (pid_t) -1) { + kill(u->pid, SIGTERM); + waitpid(u->pid, NULL, 0); + } + if (u->io_event) m->core->mainloop->io_free(u->io_event); if (u->fd >= 0) - close(u->fd); + pa_close(u->fd); - if (u->pid != (pid_t) -1) { - kill(u->pid, SIGTERM); - waitpid(u->pid, NULL, 0); - } if (u->module_infos) pa_hashmap_free(u->module_infos, module_info_free, u); pa_xfree(u); } - diff --git a/src/modules/ladspa.h b/src/modules/ladspa.h index 5c30a8a4..b1a9c4e5 100644 --- a/src/modules/ladspa.h +++ b/src/modules/ladspa.h @@ -3,17 +3,17 @@ Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, Stefan Westerfeld. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. - + This library 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 Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 @@ -32,7 +32,7 @@ extern "C" { /*****************************************************************************/ -/* Overview: +/* Overview: There is a large number of synthesis packages in use or development on the Linux platform at this time. This API (`The Linux Audio @@ -76,7 +76,7 @@ extern "C" { /* Fundamental data type passed in and out of plugin. This data type is used to communicate audio samples and control values. It is assumed that the plugin will work sensibly given any numeric input - value although it may have a preferred range (see hints below). + value although it may have a preferred range (see hints below). For audio it is generally assumed that 1.0f is the `0dB' reference amplitude and is a `normal' signal level. */ @@ -85,8 +85,8 @@ typedef float LADSPA_Data; /*****************************************************************************/ -/* Special Plugin Properties: - +/* Special Plugin Properties: + Optional features of the plugin type are encapsulated in the LADSPA_Properties type. This is assembled by ORing individual properties together. */ @@ -122,7 +122,7 @@ typedef int LADSPA_Properties; (3) The plugin will not access files, devices, pipes, sockets, IPC or any other mechanism that might result in process or thread blocking. - + (4) The plugin will take an amount of time to execute a run() or run_adding() call approximately of form (A+B*SampleCount) where A and B depend on the machine and host in use. This amount of time @@ -137,7 +137,7 @@ typedef int LADSPA_Properties; /*****************************************************************************/ -/* Plugin Ports: +/* Plugin Ports: Plugins have `ports' that are inputs or outputs for audio or data. Ports can communicate arrays of LADSPA_Data (for audio @@ -172,23 +172,23 @@ typedef int LADSPA_PortDescriptor; /*****************************************************************************/ -/* Plugin Port Range Hints: +/* Plugin Port Range Hints: The host may wish to provide a representation of data entering or leaving a plugin (e.g. to generate a GUI automatically). To make this more meaningful, the plugin should provide `hints' to the host describing the usual values taken by the data. - + Note that these are only hints. The host may ignore them and the plugin must not assume that data supplied to it is meaningful. If the plugin receives invalid input data it is expected to continue to run without failure and, where possible, produce a sensible output (e.g. a high-pass filter given a negative cutoff frequency might switch to an all-pass mode). - + Hints are meaningful for all input and output ports but hints for input control ports are expected to be particularly useful. - + More hint information is encapsulated in the LADSPA_PortRangeHintDescriptor type which is assembled by ORing individual hint types together. Hints may require further @@ -353,7 +353,7 @@ typedef struct _LADSPA_PortRangeHint { /*****************************************************************************/ -/* Plugin Handles: +/* Plugin Handles: This plugin handle indicates a particular instance of the plugin concerned. It is valid to compare this to NULL (0 for C++) but @@ -364,13 +364,13 @@ typedef void * LADSPA_Handle; /*****************************************************************************/ -/* Descriptor for a Type of Plugin: +/* Descriptor for a Type of Plugin: This structure is used to describe a plugin type. It provides a number of functions to examine the type, instantiate it, link it to buffers and workspaces and to run it. */ -typedef struct _LADSPA_Descriptor { +typedef struct _LADSPA_Descriptor { /* This numeric identifier indicates the plugin type uniquely. Plugin programmers may reserve ranges of IDs from a @@ -430,7 +430,7 @@ typedef struct _LADSPA_Descriptor { instantiation function accepts a sample rate as a parameter. The plugin descriptor from which this instantiate function was found must also be passed. This function must return NULL if - instantiation fails. + instantiation fails. Note that instance initialisation should generally occur in activate() rather than here. */ @@ -551,7 +551,7 @@ typedef struct _LADSPA_Descriptor { /* Once an instance of a plugin has been finished with it can be deleted using the following function. The instance handle passed ceases to be valid after this call. - + If activate() was called for a plugin instance then a corresponding call to deactivate() must be made before cleanup() is called. */ @@ -589,7 +589,7 @@ typedef struct _LADSPA_Descriptor { const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); /* Datatype corresponding to the ladspa_descriptor() function. */ -typedef const LADSPA_Descriptor * +typedef const LADSPA_Descriptor * (*LADSPA_Descriptor_Function)(unsigned long Index); /**********************************************************************/ diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index a09247fe..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,25 +45,55 @@ #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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ALSA Sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "device=<ALSA device> " + "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; @@ -81,224 +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; - int use_mmap; + pa_bool_t use_mmap, use_tsched; - int first; + pa_bool_t first, after_rewind; pa_rtpoll_item *alsa_rtpoll_item; -}; -static const char* const valid_modargs[] = { - "device", - "sink_name", - "format", - "channels", - "rate", - "fragments", - "fragment_size", - "channel_map", - "mmap", - NULL + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; + uint64_t since_start; + + snd_pcm_sframes_t hwbuf_unused_frames; }; -static 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 = 1; - } + /* 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 = 1; - } + 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 = 1; - } + 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); - work_done = 1; + if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - if (frames >= (snd_pcm_uframes_t) n) - return work_done; + if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + continue; + + return r; + } -/* pa_log("wrote %i samples", (int) frames); */ + work_done = 1; + + u->frame_index += frames; + u->since_start += frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= (snd_pcm_uframes_t) n) + break; + + n -= 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; - if (t * u->frame_size >= (unsigned) l) - return work_done; + u->frame_index += frames; + u->since_start += frames * u->frame_size; + +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ + + if (frames >= n) + break; + + n -= frames; + } } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; } -static 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 (frames > 0) - r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); +/* 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); + + 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); @@ -307,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; } @@ -337,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); @@ -352,9 +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, b; + int err; + pa_bool_t b, d; unsigned nfrags; snd_pcm_uframes_t period_size; @@ -373,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)) < 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; } @@ -394,17 +649,16 @@ 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; /* FIXME: We need to reload the volume somehow */ - u->first = 1; + u->first = TRUE; + u->since_start = 0; pa_log_info("Resumed successfully..."); @@ -440,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; @@ -499,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, i)); + 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, i, &vol)) < 0) - goto fail; + if (u->hw_dB_supported) { + + 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; + } - set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + u->hw_dB_supported = FALSE; + } - /* 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)); + 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; @@ -518,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; } @@ -535,17 +793,34 @@ static int sink_set_volume_cb(pa_sink *s) { long alsa_vol; pa_volume_t vol; - pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, i)); + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + + vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); - vol = s->volume.values[i]; + 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_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + + 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, i, alsa_vol)) < 0) + 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; @@ -553,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; } @@ -567,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; } @@ -587,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; + } + + 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); + } +} - s->get_mute = NULL; - s->set_mute = NULL; +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; } @@ -603,8 +951,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); @@ -612,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 = 0; - 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) @@ -640,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; @@ -654,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); } } @@ -708,24 +1080,27 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u = NULL; - char *dev; + 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; - int use_mmap = 1, 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; @@ -740,117 +1115,180 @@ 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->first = 1; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->use_tsched = use_tsched; + u->first = TRUE; + u->since_start = 0; + u->after_rewind = FALSE; u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->alsa_rtpoll_item = NULL; - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - snd_config_update_free_global(); - - dev = pa_xstrdup(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + 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); - for (;;) { + snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, dev, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); - pa_xfree(dev); - goto fail; - } + b = use_mmap; + d = use_tsched; - b = use_mmap; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { - if (err == -EPERM) { - /* Hmm, some hw is very exotic, so we retry with plughw, if hw didn't work */ + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_frames, tsched_frames, + &b, &d))) - if (pa_startswith(dev, "hw:")) { - char *d = pa_sprintf_malloc("plughw:%s", dev+3); - pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", dev, d); - pa_xfree(dev); - dev = d; + goto fail; - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - continue; - } - } + } else { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); - pa_xfree(dev); + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; - } - break; } - u->device_name = dev; + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = 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); - if (ss.channels != map.channels) - /* Seems ALSA didn't like the channel number, so let's fix the channel map */ - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) pa_log_warn("Error opening mixer: %s", snd_strerror(err)); else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + snd_pcm_info_t *info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; - if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) { + if ((card = snd_pcm_info_get_card(info)) >= 0) { + md = pa_sprintf_malloc("hw:%i", card); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + pa_xfree(md); + } + } + } + + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) + found = FALSE; + + if (!found) { snd_mixer_close(u->mixer_handle); u->mixer_handle = NULL; } } if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) - namereg_fail = 1; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("alsa_output.%s", dev); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); + 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) { @@ -859,59 +1297,124 @@ 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", - dev, - snd_pcm_info_get_name(pcm_info), - use_mmap ? " via DMA" : "")); - pa_xfree(t); - - u->sink->flags = PA_SINK_HARDWARE|PA_SINK_HW_VOLUME_CTRL|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); + + 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); - 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; pa_memchunk_reset(&u->memchunk); if (u->mixer_handle) { - /* Initialize mixer code */ - pa_assert(u->mixer_elem); - if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { - int i; + if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) - for (i = 0; i < ss.channels; i++) - if (!snd_mixer_selem_has_playback_channel(u->mixer_elem, i)) - break; + 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) { - if (i == ss.channels) { - pa_log_debug("ALSA device has separate volumes controls for all %u channels.", ss.channels); - 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); - } else - pa_log_info("ALSA device lacks separate volumes controls for all %u channels (%u available), falling back to software volume control.", ss.channels, i+1); - } + 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_MUTE_CTRL; } u->mixer_fdl = pa_alsa_fdlist_new(); if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { - pa_log("failed to initialise file descriptor monitoring"); + pa_log("Failed to initialize file descriptor monitoring"); goto fail; } @@ -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 d840cac3..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,25 +46,55 @@ #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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ALSA Source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source_name=<name for the source> " "device=<ALSA device> " + "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; @@ -82,241 +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; - int use_mmap; + pa_bool_t use_mmap, use_tsched; pa_rtpoll_item *alsa_rtpoll_item; -}; -static const char* const valid_modargs[] = { - "device", - "source_name", - "channels", - "rate", - "format", - "fragments", - "fragment_size", - "channel_map", - "mmap", - NULL + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; + + pa_smoother *smoother; + int64_t frame_index; + + snd_pcm_sframes_t hwbuf_unused_frames; }; -static 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; - work_done = 1; +/* pa_log_debug("read %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_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; } @@ -325,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; @@ -339,9 +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, b; + int err; + pa_bool_t b, d; unsigned nfrags; snd_pcm_uframes_t period_size; @@ -360,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)) < 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; } @@ -381,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; @@ -427,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; @@ -488,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, i)); + 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, i, &vol)) < 0) - goto fail; + if (u->hw_dB_supported) { + + 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; + } - set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + 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; - /* 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)); + 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; @@ -507,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; } @@ -524,17 +736,34 @@ static int source_set_volume_cb(pa_source *s) { long alsa_vol; pa_volume_t vol; - pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, i)); + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[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); - vol = s->volume.values[i]; - 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, i, alsa_vol)) < 0) + 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; @@ -542,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; } @@ -556,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; } @@ -576,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; @@ -592,8 +823,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); @@ -601,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) @@ -622,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; @@ -636,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); } } @@ -690,24 +921,26 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u = NULL; - char *dev; + const char *dev_id; pa_sample_spec ss; pa_channel_map map; - unsigned 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; - int use_mmap = 1, 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; @@ -722,115 +955,173 @@ 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(); - dev = pa_xstrdup(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE)); + b = use_mmap; + d = use_tsched; - for (;;) { + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { - if ((err = snd_pcm_open(&u->pcm_handle, dev, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { - pa_log("Error opening PCM device %s: %s", dev, snd_strerror(err)); - pa_xfree(dev); + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; - } - b = use_mmap; - if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b)) < 0) { - - if (err == -EPERM) { - /* Hmm, some hw is very exotic, so we retry with plughw, if hw didn't work */ - - if (pa_startswith(dev, "hw:")) { - char *d = pa_sprintf_malloc("plughw:%s", dev+3); - pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", dev, d); - pa_xfree(dev); - dev = d; - - snd_pcm_close(u->pcm_handle); - u->pcm_handle = NULL; - continue; - } - } + } else { - pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); - pa_xfree(dev); + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_frames, tsched_frames, + &b, &d))) goto fail; - } - - break; } - u->device_name = dev; + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); if (use_mmap && !b) { pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); - u->use_mmap = use_mmap = 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); - if (ss.channels != map.channels) - /* Seems ALSA didn't like the channel number, so let's fix the channel map */ - pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_ALSA); - if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) pa_log("Error opening mixer: %s", snd_strerror(err)); else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + snd_pcm_info_t* info; + + snd_pcm_info_alloca(&info); + + if (snd_pcm_info(u->pcm_handle, info) >= 0) { + char *md; + int card; + + if ((card = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + pa_xfree(md); + } + } + } - if ((pa_alsa_prepare_mixer(u->mixer_handle, dev) < 0) || - !(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", NULL))) { + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic"))) + found = FALSE; + + if (!found) { snd_mixer_close(u->mixer_handle); u->mixer_handle = NULL; } } if ((name = pa_modargs_get_value(ma, "source_name", NULL))) - namereg_fail = 1; + namereg_fail = TRUE; else { - name = name_buf = pa_sprintf_malloc("alsa_input.%s", dev); - namereg_fail = 0; + name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); + 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) { @@ -839,54 +1130,110 @@ 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", - dev, - snd_pcm_info_get_name(pcm_info), - use_mmap ? " via DMA" : "")); - pa_xfree(t); - - u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL; 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); + + 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); - pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + 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)) { - int i; + 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 && + snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { - for (i = 0;i < ss.channels;i++) { - if (!snd_mixer_selem_has_capture_channel(u->mixer_elem, i)) - break; - } + 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 (i == ss.channels) { - 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); } - } + if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { u->source->get_mute = source_get_mute_cb; u->source->set_mute = source_set_mute_cb; + u->source->flags |= PA_SOURCE_HW_MUTE_CTRL; } u->mixer_fdl = pa_alsa_fdlist_new(); if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { - pa_log("failed to initialise file descriptor monitoring"); + pa_log("Failed to initialize file descriptor monitoring"); goto fail; } @@ -895,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); @@ -926,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); @@ -959,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 new file mode 100644 index 00000000..77b95868 --- /dev/null +++ b/src/modules/module-bt-proximity.c @@ -0,0 +1,490 @@ +/*** + This file is part of PulseAudio. + + Copyright 2005-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 <stdio.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> + +#include "dbus-util.h" +#include "module-bt-proximity-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "sink=<sink name> " + "hci=<hci device> " +); + +#define DEFAULT_HCI "hci0" + +static const char* const valid_modargs[] = { + "sink", + "rssi", + "hci", + NULL, +}; + +struct bonding { + struct userdata *userdata; + char address[18]; + + pid_t pid; + int fd; + + pa_io_event *io_event; + + enum { + UNKNOWN, + FOUND, + NOT_FOUND + } state; +}; + +struct userdata { + pa_module *module; + pa_dbus_connection *dbus_connection; + + char *sink_name; + char *hci, *hci_path; + + pa_hashmap *bondings; + + unsigned n_found; + unsigned n_unknown; + + pa_bool_t muted; +}; + +static void update_volume(struct userdata *u) { + pa_assert(u); + + if (u->muted && u->n_found > 0) { + pa_sink *s; + + u->muted = FALSE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("Found %u BT devices, unmuting.", u->n_found); + pa_sink_set_mute(s, FALSE); + + } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) { + pa_sink *s; + + u->muted = TRUE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("No BT devices found, muting."); + pa_sink_set_mute(s, TRUE); + + } else + pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); +} + +static void bonding_free(struct bonding *b) { + pa_assert(b); + + if (b->state == FOUND) + pa_assert_se(b->userdata->n_found-- >= 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (b->pid != (pid_t) -1) { + kill(b->pid, SIGTERM); + waitpid(b->pid, NULL, 0); + } + + if (b->fd >= 0) + pa_close(b->fd); + + if (b->io_event) + b->userdata->module->core->mainloop->io_free(b->io_event); + + pa_xfree(b); +} + +static void io_event_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct bonding *b = userdata; + char x; + ssize_t r; + + pa_assert(b); + + if ((r = read(fd, &x, 1)) <= 0) { + pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno)); + + pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b); + bonding_free(b); + return; + } + + pa_assert_se(r == 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (x == '+') { + pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND); + + b->state = FOUND; + b->userdata->n_found++; + + pa_log_info("Device '%s' is alive.", b->address); + + } else { + pa_assert(x == '-'); + pa_assert(b->state == UNKNOWN || b->state == FOUND); + + if (b->state == FOUND) + b->userdata->n_found--; + + b->state = NOT_FOUND; + + pa_log_info("Device '%s' is dead.", b->address); + } + + update_volume(b->userdata); +} + +static struct bonding* bonding_new(struct userdata *u, const char *a) { + struct bonding *b = NULL; + DBusMessage *m = NULL, *r = NULL; + DBusError e; + const char *class; + + pa_assert(u); + pa_assert(a); + + pa_return_val_if_fail(strlen(a) == 17, NULL); + pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL); + + dbus_error_init(&e); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e); + + if (!r) { + pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message); + goto fail; + } + + if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message); + goto fail; + } + + if (strcmp(class, "phone")) { + pa_log_info("Found device '%s' of class '%s', ignoring.", a, class); + goto fail; + } + + b = pa_xnew(struct bonding, 1); + b->userdata = u; + pa_strlcpy(b->address, a, sizeof(b->address)); + b->pid = (pid_t) -1; + b->fd = -1; + b->io_event = NULL; + b->state = UNKNOWN; + u->n_unknown ++; + + pa_log_info("Watching device '%s' of class '%s'.", b->address, class); + + if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) { + pa_log("Failed to start helper tool."); + goto fail; + } + + b->io_event = u->module->core->mainloop->io_new( + u->module->core->mainloop, + b->fd, + PA_IO_EVENT_INPUT, + io_event_cb, + b); + + dbus_message_unref(m); + dbus_message_unref(r); + + pa_hashmap_put(u->bondings, b->address, b); + + return b; + +fail: + if (m) + dbus_message_unref(m); + if (r) + dbus_message_unref(r); + + if (b) + bonding_free(b); + + dbus_error_free(&e); + return NULL; +} + +static void bonding_remove(struct userdata *u, const char *a) { + struct bonding *b; + pa_assert(u); + + pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a))); + + pa_log_info("No longer watching device '%s'", b->address); + bonding_free(b); +} + +static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusError e; + + dbus_error_init(&e); + + if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) { + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message); + goto finish; + } + + bonding_new(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) { + + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message); + goto finish; + } + + bonding_remove(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + } + +finish: + + dbus_error_free(&e); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_matches(struct userdata *u, pa_bool_t add) { + char *filter1, *filter2; + DBusError e; + int r = -1; + + pa_assert(u); + dbus_error_init(&e); + + filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path); + filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path); + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message); + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + + if (add) + pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL)); + else + dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u); + + r = 0; + +finish: + pa_xfree(filter1); + pa_xfree(filter2); + dbus_error_free(&e); + + return r; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + DBusError e; + DBusMessage *msg = NULL, *r = NULL; + DBusMessageIter iter, sub; + + pa_assert(m); + dbus_error_init(&e); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI)); + u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci); + u->n_found = u->n_unknown = 0; + u->muted = FALSE; + + u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) { + pa_log("Failed to get D-Bus connection: %s", e.message); + goto fail; + } + + if (add_matches(u, TRUE) < 0) + goto fail; + + pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings")); + + if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) { + pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message); + goto fail; + } + + dbus_message_iter_init(r, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + pa_log("Malformed reply to org.bluez.Adapter.ListBondings."); + goto fail; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *a = NULL; + + dbus_message_iter_get_basic(&sub, &a); + bonding_new(u, a); + + dbus_message_iter_next(&sub); + } + + dbus_message_unref(r); + dbus_message_unref(msg); + + pa_modargs_free(ma); + + if (pa_hashmap_size(u->bondings) == 0) + pa_log_warn("Warning: no phone device bonded."); + + update_volume(u); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + dbus_error_free(&e); + + if (msg) + dbus_message_unref(msg); + + if (r) + dbus_message_unref(r); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->bondings) { + struct bonding *b; + + while ((b = pa_hashmap_steal_first(u->bondings))) + bonding_free(b); + + pa_hashmap_free(u->bondings, NULL, NULL); + } + + if (u->dbus_connection) { + add_matches(u, FALSE); + pa_dbus_connection_unref(u->dbus_connection); + } + + pa_xfree(u->sink_name); + pa_xfree(u->hci_path); + pa_xfree(u->hci); + pa_xfree(u); +} diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c index 84125214..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. @@ -38,10 +36,11 @@ #include "module-cli-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Command line interface") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("exit_on_eof=<exit daemon after EOF?>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Command line interface"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("exit_on_eof=<exit daemon after EOF?>"); static const char* const valid_modargs[] = { "exit_on_eof", @@ -69,7 +68,7 @@ static void eof_and_exit_cb(pa_cli*c, void *userdata) { int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; - int exit_on_eof = 0; + pa_bool_t exit_on_eof = FALSE; pa_assert(m); diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 665bf9dd..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,31 +45,34 @@ #include <pulsecore/rtpoll.h> #include <pulsecore/rtclock.h> #include <pulsecore/core-error.h> +#include <pulsecore/time-smoother.h> #include "module-combine-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Combine multiple sinks to one") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Combine multiple sinks to one"); +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> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "channel_map=<channel map>") + "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", @@ -90,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); }; @@ -112,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); @@ -158,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) @@ -202,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) { @@ -226,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; @@ -233,36 +274,29 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+1); 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; @@ -294,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 */ @@ -314,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) @@ -353,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; +} - return pa_memblockq_peek(o->memblockq, chunk); +/* 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_drop_cb(pa_sink_input *i, size_t length) { +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(length > 0); pa_assert_se(o = i->userdata); - pa_memblockq_drop(o->memblockq, length); + pa_memblockq_set_maxrewind(o->memblockq, nbytes); +} + +/* Called from I/O thread context */ +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_se(o = i->userdata); + + 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 */ @@ -384,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 */ @@ -399,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 */ @@ -415,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; @@ -433,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); @@ -451,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 */ @@ -471,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); } } @@ -487,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..."); } @@ -504,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..."); } @@ -525,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; @@ -546,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; @@ -554,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; } @@ -597,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; @@ -664,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; @@ -677,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; @@ -735,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); @@ -763,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( @@ -774,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; @@ -828,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); @@ -836,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); @@ -849,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; + + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); - if (!o) + if (!(o = find_output(u, s))) return PA_HOOK_OK; pa_log_info("Unconfiguring sink: %s", s->name); @@ -881,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; } @@ -912,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); @@ -934,109 +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_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; @@ -1053,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)) { @@ -1072,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."); @@ -1116,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); @@ -1151,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); @@ -1186,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 71e19e63..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,79 +23,179 @@ #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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Automatically restore the default sink and source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the default sink and source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +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); pa_strip_nl(ln); 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); pa_strip_nl(ln); 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); + + } else if (errno != ENOENT) + pa_log("Failed to load default sink: %s", pa_cstrerror(errno)); +} + +static void save(struct userdata *u) { + FILE *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 (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) { - FILE *f; + struct userdata *u; - 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); - } + pa_assert(m); - 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 = 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 5bff748e..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 @@ -10,6 +9,7 @@ define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl #include <pulsecore/core.h> #include <pulsecore/module.h> +#include <pulsecore/macro.h> gen_symbol(pa__init) gen_symbol(pa__done) @@ -17,6 +17,7 @@ gen_symbol(pa__get_author) gen_symbol(pa__get_description) gen_symbol(pa__get_usage) gen_symbol(pa__get_version) +gen_symbol(pa__load_once) int pa__init(pa_module*m); void pa__done(pa_module*m); @@ -25,5 +26,6 @@ const char* pa__get_author(void); const char* pa__get_description(void); const char* pa__get_usage(void); const char* pa__get_version(void); +pa_bool_t pa__load_once(void); #endif diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 7dc25243..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. @@ -47,10 +45,11 @@ #include "module-detect-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("just-one=<boolean>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("just-one=<boolean>"); static const char* const valid_modargs[] = { "just-one", @@ -221,7 +220,8 @@ static int detect_waveout(pa_core *c, int just_one) { #endif int pa__init(pa_module*m) { - int just_one = 0, n = 0; + pa_bool_t just_one = FALSE; + int n = 0; pa_modargs *ma; pa_assert(m); 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 49dcf3be..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. @@ -37,10 +35,11 @@ #include "module-esound-compat-spawnfd-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation") -PA_MODULE_USAGE("fd=<file descriptor>") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE("fd=<file descriptor>"); static const char* const valid_modargs[] = { "fd", @@ -51,13 +50,13 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1, fd = -1; char x = 1; - + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || pa_modargs_get_value_s32(ma, "fd", &fd) < 0 || fd < 0) { - + pa_log("Failed to parse module arguments"); goto finish; } diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c index 6ad7db7b..67f0a231 100644 --- a/src/modules/module-esound-compat-spawnpid.c +++ b/src/modules/module-esound-compat-spawnpid.c @@ -37,10 +37,11 @@ #include "module-esound-compat-spawnpid-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation") -PA_MODULE_USAGE("pid=<process id>") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("pid=<process id>"); static const char* const valid_modargs[] = { "pid", @@ -51,7 +52,7 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; int ret = -1; uint32_t pid = 0; - + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index 8b46637e..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. @@ -64,15 +62,16 @@ #include "module-esound-sink-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("ESOUND Sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "server=<address> cookie=<filename> " "format=<sample format> " "channels=<number of channels> " - "rate=<sample rate>") + "rate=<sample rate>"); #define DEFAULT_SINK_NAME "esound_out" @@ -142,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; @@ -210,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; @@ -293,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) @@ -362,7 +361,7 @@ static int do_write(struct userdata *u) { pa_make_tcp_socket_low_delay(u->fd); - pa_log_info("Connection authenticated, handing fd to IO thread..."); + pa_log_debug("Connection authenticated, handing fd to IO thread..."); pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); u->state = STATE_RUNNING; @@ -496,16 +495,16 @@ static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, vo u->io = io; pa_iochannel_set_callback(u->io, io_callback, u); - pa_log_info("Connection established, authenticating ..."); + pa_log_debug("Connection established, authenticating ..."); } 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); @@ -531,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 = @@ -552,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))) { - pa_log("failed to create sink."); + 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_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 */ @@ -584,7 +590,9 @@ int pa__init(pa_module*m) { pa_log("Failed to load cookie"); goto fail; } - *(int32_t*) ((uint8_t*) u->write_data + ESD_KEY_LEN) = ESD_ENDIAN_KEY; + + key = ESD_ENDIAN_KEY; + memcpy((uint8_t*) u->write_data + ESD_KEY_LEN, &key, sizeof(key)); /* Reserve space for the response */ u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t)); diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index 441ebfed..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. @@ -53,15 +51,16 @@ #include "dbus-util.h" #include "module-hal-detect-symdef.h" -PA_MODULE_AUTHOR("Shahms King") -PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Shahms King"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); #if defined(HAVE_ALSA) && defined(HAVE_OSS) -PA_MODULE_USAGE("api=<alsa or oss>") +PA_MODULE_USAGE("api=<alsa or oss>"); #elif defined(HAVE_ALSA) -PA_MODULE_USAGE("api=<alsa>") +PA_MODULE_USAGE("api=<alsa>"); #elif defined(HAVE_OSS) -PA_MODULE_USAGE("api=<oss>") +PA_MODULE_USAGE("api=<oss>"); #endif struct device { @@ -94,7 +93,7 @@ static const char* const valid_modargs[] = { static void hal_device_free(struct device* d) { pa_assert(d); - + pa_xfree(d->udi); pa_xfree(d->sink_name); pa_xfree(d->source_name); @@ -107,7 +106,7 @@ static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) { static const char *strip_udi(const char *udi) { const char *slash; - + if ((slash = strrchr(udi, '/'))) return slash+1; @@ -130,11 +129,11 @@ static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char * if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error))) return ALSA_TYPE_OTHER; - if (!strcmp(type, "playback")) + if (!strcmp(type, "playback")) t = ALSA_TYPE_SINK; else if (!strcmp(type, "capture")) t = ALSA_TYPE_SOURCE; - else + else t = ALSA_TYPE_OTHER; libhal_free_string(type); @@ -145,13 +144,13 @@ static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char * static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) { char *class; int r; - + if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error))) return 0; r = strcmp(class, "modem") == 0; pa_xfree(class); - + return r; } @@ -162,7 +161,7 @@ static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char const char *module_name; DBusError error; pa_module *m; - + dbus_error_init(&error); pa_assert(u); @@ -170,7 +169,7 @@ static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char pa_assert(source_name); *sink_name = *source_name = NULL; - + type = hal_alsa_device_get_type(u->context, udi, &error); if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER) goto fail; @@ -188,14 +187,14 @@ static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char if (type == ALSA_TYPE_SINK) { *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi)); - + module_name = "module-alsa-sink"; - args = pa_sprintf_malloc("device=hw:%u sink_name=%s", card, *sink_name); + args = pa_sprintf_malloc("device_id=%u sink_name=%s", card, *sink_name); } else { *source_name = pa_sprintf_malloc("alsa_input.%s", strip_udi(udi)); - + module_name = "module-alsa-source"; - args = pa_sprintf_malloc("device=hw:%u source_name=%s", card, *source_name); + args = pa_sprintf_malloc("device_id=%u source_name=%s", card, *source_name); } pa_log_debug("Loading %s with arguments '%s'", module_name, args); @@ -209,7 +208,7 @@ static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char pa_xfree(*source_name); *sink_name = *source_name = NULL; } - + return m; fail: @@ -255,7 +254,7 @@ finish: libhal_free_string(class); libhal_free_string(dev); - + return r; } @@ -264,7 +263,7 @@ static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char char* device; DBusError error; pa_module *m; - + dbus_error_init(&error); pa_assert(u); @@ -282,7 +281,7 @@ static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char *sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi)); *source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi)); - + args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, *sink_name, *source_name); libhal_free_string(device); @@ -316,7 +315,7 @@ static struct device* hal_device_add(struct userdata *u, const char *udi) { pa_assert(u); pa_assert(u->capability); pa_assert(!pa_hashmap_get(u->devices, udi)); - + #ifdef HAVE_ALSA if (strcmp(u->capability, CAPABILITY_ALSA) == 0) m = hal_device_load_alsa(u, udi, &sink_name, &source_name); @@ -346,12 +345,12 @@ static int hal_device_add_all(struct userdata *u, const char *capability) { char** udis; pa_assert(u); - + dbus_error_init(&error); if (u->capability && strcmp(u->capability, capability) != 0) return 0; - + pa_log_info("Trying capability %s", capability); udis = libhal_find_device_by_capability(u->context, capability, &n, &error); @@ -363,15 +362,15 @@ static int hal_device_add_all(struct userdata *u, const char *capability) { if (n > 0) { u->capability = capability; - + for (i = 0; i < n; i++) { struct device *d; - + if (!(d = hal_device_add(u, udis[i]))) 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++; } } @@ -383,7 +382,7 @@ static int hal_device_add_all(struct userdata *u, const char *capability) { static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){ dbus_bool_t has_prop; - + has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error); if (!has_prop || dbus_error_is_set(error)) return FALSE; @@ -400,18 +399,18 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const s if (!pa_hashmap_get(td->u->devices, td->udi)) { int b; struct device *d; - + b = libhal_device_exists(td->u->context, td->udi, &error); - + if (dbus_error_is_set(&error)) { pa_log_error("Error adding device: %s: %s", error.name, error.message); dbus_error_free(&error); } else if (b) { - if (!(d = hal_device_add(td->u, td->udi))) + if (!(d = hal_device_add(td->u, td->udi))) 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); } } } @@ -432,13 +431,13 @@ static void device_added_cb(LibHalContext *context, const char *udi) { if (pa_hashmap_get(u->devices, udi)) return; - + pa_log_debug("HAL Device added: %s", udi); dbus_error_init(&error); if (u->capability) { - + good = device_has_capability(context, udi, u->capability, &error); if (dbus_error_is_set(&error)) { @@ -446,7 +445,7 @@ static void device_added_cb(LibHalContext *context, const char *udi) { dbus_error_free(&error); return; } - + } else { #ifdef HAVE_ALSA @@ -463,10 +462,10 @@ static void device_added_cb(LibHalContext *context, const char *udi) { #endif #if defined(HAVE_OSS) && defined(HAVE_ALSA) if (!good) { -#endif +#endif #ifdef HAS_OSS good = device_has_capability(context, udi, CAPABILITY_OSS, &error); - + if (dbus_error_is_set(&error)) { pa_log_error("Error getting capability: %s: %s", error.name, error.message); dbus_error_free(&error); @@ -481,7 +480,7 @@ static void device_added_cb(LibHalContext *context, const char *udi) { } #endif } - + if (!good) return; @@ -502,7 +501,7 @@ static void device_removed_cb(LibHalContext* context, const char *udi) { pa_assert_se(u = libhal_ctx_get_user_data(context)); pa_log_debug("Device removed: %s", udi); - + if ((d = pa_hashmap_remove(u->devices, udi))) { pa_module_unload_by_index(u->core, d->index); hal_device_free(d); @@ -536,7 +535,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_assert(bus); pa_assert(message); pa_assert(u); - + dbus_error_init(&error); pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", @@ -548,7 +547,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) { uint32_t uid; int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0; - + if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message); goto finish; @@ -559,25 +558,25 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo const char *udi; udi = dbus_message_get_path(message); - + if ((d = pa_hashmap_get(u->devices, udi))) { int send_acl_race_fix_message = 0; - d->acl_race_fix = 0; + d->acl_race_fix = 0; if (d->sink_name) { pa_sink *sink; - + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; 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; - + } else if (!prev_suspended && suspend) { /* suspend */ if (pa_sink_suspend(sink, 1) >= 0) @@ -611,11 +610,11 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL); dbus_message_unref(msg); } - + } else if (!suspend) device_added_cb(u->context, udi); } - + } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) { /* We use this message to avoid a dirty race condition when we get an ACLAdded message before the previously owning PA @@ -626,15 +625,15 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo struct device *d; udi = dbus_message_get_path(message); - + if ((d = pa_hashmap_get(u->devices, udi)) && d->acl_race_fix) { pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi); d->acl_race_fix = 0; - + if (d->sink_name) { pa_sink *sink; - + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; @@ -642,14 +641,14 @@ 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); } } } - + if (d->source_name) { pa_source *source; - + if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) { int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED; @@ -658,15 +657,15 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo pa_source_suspend(source, 0); } } - - } else + + } else /* Yes, we don't check the UDI for validity, but hopefully HAL will */ device_added_cb(u->context, udi); } finish: dbus_error_free(&error); - + return DBUS_HANDLER_RESULT_HANDLED; } @@ -674,7 +673,7 @@ static void hal_context_free(LibHalContext* hal_context) { DBusError error; dbus_error_init(&error); - + libhal_ctx_shutdown(hal_context, &error); libhal_ctx_free(hal_context); @@ -686,7 +685,7 @@ static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) { LibHalContext *hal_context = NULL; dbus_error_init(&error); - + if (!(hal_context = libhal_ctx_new())) { pa_log_error("libhal_ctx_new() failed"); goto fail; @@ -721,7 +720,7 @@ int pa__init(pa_module*m) { int n = 0; pa_modargs *ma; const char *api; - + pa_assert(m); dbus_error_init(&error); @@ -738,7 +737,7 @@ int pa__init(pa_module*m) { if (strcmp(api, CAPABILITY_ALSA) == 0) { good = 1; api = CAPABILITY_ALSA; - } + } #endif #ifdef HAVE_OSS if (strcmp(api, CAPABILITY_OSS) == 0) { @@ -746,13 +745,13 @@ int pa__init(pa_module*m) { api = CAPABILITY_OSS; } #endif - + if (!good) { pa_log_error("Invalid API specification."); goto fail; } } - + if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { if (conn) pa_dbus_connection_unref(conn); @@ -779,7 +778,7 @@ int pa__init(pa_module*m) { #endif #if defined(HAVE_ALSA) && defined(HAVE_OSS) if (n <= 0) -#endif +#endif #ifdef HAVE_OSS n += hal_device_add_all(u, CAPABILITY_OSS); #endif @@ -811,17 +810,17 @@ int pa__init(pa_module*m) { pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message); goto fail; } - + pa_log_info("Loaded %i modules.", n); pa_modargs_free(ma); - + return 0; fail: if (ma) pa_modargs_free(ma); - + dbus_error_free(&error); pa__done(m); @@ -831,7 +830,7 @@ fail: void pa__done(pa_module *m) { struct userdata *u; - + pa_assert(m); if (!(u = m->userdata)) @@ -845,6 +844,6 @@ void pa__done(pa_module *m) { if (u->connection) pa_dbus_connection_unref(u->connection); - + pa_xfree(u); } diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c index 5019d656..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. @@ -28,7 +26,6 @@ #include <stdlib.h> #include <sys/stat.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> @@ -54,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 @@ -65,16 +62,17 @@ * source support in JACK. */ -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("JACK Sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Sink"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_USAGE( "sink_name=<name of sink> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " "connect=<connect ports?> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #define DEFAULT_SINK_NAME "jack_out" @@ -215,8 +213,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); @@ -254,8 +252,8 @@ static void jack_init(void *arg) { pa_log_info("JACK thread starting up."); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+4); } static void jack_shutdown(void* arg) { @@ -273,10 +271,10 @@ int pa__init(pa_module*m) { jack_status_t status; const char *server_name, *client_name; uint32_t channels = 0; - int do_connect = 1; + 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,7 +330,7 @@ int pa__init(pa_module*m) { goto fail; } - 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; @@ -354,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 b62ebe7a..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. @@ -54,16 +52,17 @@ /* See module-jack-sink for a few comments how this module basically * works */ -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("JACK Source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); PA_MODULE_USAGE( "source_name=<name of source> " "server_name=<jack server name> " "client_name=<jack client name> " "channels=<number of channels> " "connect=<connect ports?>" - "channel_map=<channel map>") + "channel_map=<channel map>"); #define DEFAULT_SOURCE_NAME "jack_in" @@ -191,8 +190,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); @@ -230,8 +229,8 @@ static void jack_init(void *arg) { pa_log_info("JACK thread starting up."); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+4); } static void jack_shutdown(void* arg) { @@ -249,10 +248,10 @@ int pa__init(pa_module*m) { jack_status_t status; const char *server_name, *client_name; uint32_t channels = 0; - int do_connect = 1; + pa_bool_t do_connect = TRUE; unsigned i; const char **ports = NULL, **p; - char *t; + pa_source_new_data data; pa_assert(m); @@ -277,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."); @@ -303,7 +301,7 @@ int pa__init(pa_module*m) { goto fail; } - 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; @@ -324,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 0265d971..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,13 +39,15 @@ #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" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Virtual LADSPA sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Virtual LADSPA sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "master=<name of sink to remap> " @@ -57,7 +57,9 @@ PA_MODULE_USAGE( "channel_map=<channel map> " "plugin=<ladspa plugin name> " "label=<ladspa plugin label> " - "control=<comma seperated list of input control values>") + "control=<comma seperated list of input control values>"); + +#define MEMBLOCKQ_MAXLENGTH (16*1024*1024) struct userdata { pa_core *core; @@ -78,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[] = { @@ -103,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; } } @@ -121,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; + if (!u->sink || !PA_SINK_IS_OPENED(u->sink->thread_info.state)) + return -1; - pa_sink_render(u->sink, length, &tchunk); + while (pa_memblockq_peek(u->memblockq, &tchunk) < 0) { + pa_memchunk nchunk; - fs = pa_frame_size(&i->sample_spec); - n = tchunk.length / fs; - - pa_assert(n > 0); + pa_sink_render(u->sink, nbytes, &nchunk); + pa_memblockq_push(u->memblockq, &nchunk); + pa_memblock_unref(nchunk.memblock); + } - u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length); - u->memchunk.index = 0; - u->memchunk.length = tchunk.length; + tchunk.length = PA_MIN(nbytes, tchunk.length); + pa_assert(tchunk.length > 0); - src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); - dst = (float*) pa_memblock_acquire(u->memchunk.memblock); + fs = pa_frame_size(&i->sample_spec); + n = PA_MIN(tchunk.length, u->block_size) / fs; - for (c = 0; c < u->channels; c++) { - unsigned j; - float *p, *q; + pa_assert(n > 0); - p = src + c; - q = u->input; - for (j = 0; j < n; j++, p += u->channels, q++) - *q = CLAMP(*p, -1.0, 1.0); + chunk->index = 0; + chunk->length = n*fs; + chunk->memblock = pa_memblock_new(i->sink->core->mempool, chunk->length); - u->descriptor->run(u->handle[c], n); + pa_memblockq_drop(u->memblockq, chunk->length); - q = u->output; - p = dst + c; - for (j = 0; j < n; j++, q++, p += u->channels) - *p = CLAMP(*q, -1.0, 1.0); - } + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(chunk->memblock); - pa_memblock_release(tchunk.memblock); - pa_memblock_release(u->memchunk.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; + + if (amount > 0) { + unsigned c; + + pa_memblockq_seek(u->memblockq, - (int64_t) amount, PA_SEEK_RELATIVE); + pa_sink_process_rewind(u->sink, amount); - pa_memblock_unref(u->memchunk.memblock); - length -= u->memchunk.length; - pa_memchunk_reset(&u->memchunk); + 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 */ @@ -234,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 */ @@ -244,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 */ @@ -257,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; @@ -283,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); @@ -324,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; @@ -341,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; } @@ -349,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; } @@ -395,7 +489,7 @@ int pa__init(pa_module*m) { n_control++; else { pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])); - pa_log_info("Ignored port \"%s\", because we ignore all control out ports.", d->PortNames[p]); + pa_log_debug("Ignored control output port \"%s\".", d->PortNames[p]); } } @@ -440,8 +534,8 @@ int pa__init(pa_module*m) { use_default = pa_xnew(pa_bool_t, n_control); p = 0; - while ((k = pa_split(cdata, ",", &state))) { - float f; + while ((k = pa_split(cdata, ",", &state)) && p < n_control) { + double f; if (*k == 0) { use_default[p++] = TRUE; @@ -449,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; @@ -457,15 +551,24 @@ int pa__init(pa_module*m) { pa_xfree(k); - if (p >= n_control) { - pa_log("Too many control values passed, %lu expected.", n_control); - goto fail; - } - use_default[p] = FALSE; u->control[p++] = f; } + /* The previous loop doesn't take the last control value into account + if it is left empty, so we do it here. */ + if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') { + if (p < n_control) + use_default[p] = TRUE; + p++; + } + + if (p > n_control || k) { + pa_log("Too many control values passed, %lu expected.", n_control); + pa_xfree(k); + goto fail; + } + if (p < n_control) { pa_log("Not enough control values passed, %lu expected, %lu passed.", n_control, p); goto fail; @@ -572,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); @@ -617,7 +745,6 @@ int pa__init(pa_module*m) { pa_modargs_free(ma); pa_xfree(use_default); - pa_xfree(default_sink_name); return 0; @@ -626,7 +753,6 @@ fail: pa_modargs_free(ma); pa_xfree(use_default); - pa_xfree(default_sink_name); pa__done(m); @@ -642,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]) { @@ -665,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 401763b7..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. @@ -43,10 +41,11 @@ #include "module-lirc-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("LIRC volume control") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("LIRC volume control"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>"); static const char* const valid_modargs[] = { "config", @@ -187,7 +186,7 @@ fail: int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - + pa_assert(m); if (lirc_in_use) { diff --git a/src/modules/module-match.c b/src/modules/module-match.c index 5a922966..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. @@ -46,10 +44,11 @@ #include "module-match-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Playback stream expression matching module") -PA_MODULE_USAGE("table=<filename>") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Playback stream expression matching module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("table=<filename>"); #define WHITESPACE "\n\r \t" @@ -81,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; } @@ -165,7 +166,8 @@ 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); @@ -175,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); } @@ -191,7 +193,7 @@ static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, v 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))) { @@ -223,7 +225,7 @@ fail: void pa__done(pa_module*m) { struct userdata* u; struct rule *r, *n; - + pa_assert(m); if (!(u = m->userdata)) @@ -241,5 +243,3 @@ void pa__done(pa_module*m) { pa_xfree(u); } - - diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index dc0b1c1a..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. @@ -46,10 +44,11 @@ #include "module-mmkbd-evdev-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("device=<evdev device> sink=<sink name>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("device=<evdev device> sink=<sink name>"); #define DEFAULT_DEVICE "/dev/input/event0" @@ -79,7 +78,7 @@ struct userdata { static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { struct userdata *u = userdata; - + pa_assert(io); pa_assert(u); @@ -166,7 +165,7 @@ fail: #define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) int pa__init(pa_module*m) { - + pa_modargs *ma = NULL; struct userdata *u; int version; diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c index b0a9ebb0..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. @@ -37,9 +35,10 @@ #include "module-native-protocol-fd-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Native protocol autospawn helper") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Native protocol autospawn helper"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); static const char* const valid_modargs[] = { "fd", @@ -52,7 +51,7 @@ int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; int fd, r = -1; - + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index d993988e..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 @@ -51,18 +49,20 @@ #include "module-null-sink-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Clocked NULL sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Clocked NULL sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "sink_name=<name of sink>" - "channel_map=<channel map>" - "description=<description for the sink>") + "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; @@ -74,8 +74,9 @@ struct userdata { pa_rtpoll *rtpoll; size_t block_size; - - struct timeval timestamp; + + pa_usec_t block_usec; + pa_usec_t timestamp; }; static const char* const valid_modargs[] = { @@ -95,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_rtclock_get(&now); - - 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; + pa_usec_t now; + + now = pa_rtclock_usec(); + *((pa_usec_t*) data) = u->timestamp > now ? u->timestamp - now : 0; + + 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; @@ -125,30 +193,31 @@ 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; - - pa_rtclock_get(&now); + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + pa_usec_t now; + + now = pa_rtclock_usec(); - 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->sink->thread_info.rewind_nbytes > 0) + process_rewind(u, now); - pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp); + if (u->timestamp <= now) + process_render(u, now); + + 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) goto finish; } @@ -168,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); @@ -182,39 +252,50 @@ 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."); goto fail; } pa_sink_put(u->sink); - + pa_modargs_free(ma); return 0; @@ -251,6 +332,6 @@ void pa__done(pa_module*m) { if (u->rtpoll) pa_rtpoll_free(u->rtpoll); - + pa_xfree(u); } diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index 19dceef2..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. @@ -76,9 +74,10 @@ #include "oss-util.h" #include "module-oss-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("OSS Sink/Source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("OSS Sink/Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "source_name=<name for the source> " @@ -91,7 +90,7 @@ PA_MODULE_USAGE( "fragments=<number of fragments> " "fragment_size=<fragment size> " "channel_map=<channel map> " - "mmap=<enable memory mapping?>") + "mmap=<enable memory mapping?>"); #define DEFAULT_DEVICE "/dev/dsp" @@ -160,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); @@ -201,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); @@ -509,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; @@ -599,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..."); @@ -640,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) @@ -657,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) { @@ -720,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) @@ -737,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) { @@ -863,8 +865,8 @@ static void thread_func(void *userdata) { pa_log_debug("Thread starting up"); - if (u->core->high_priority) - pa_make_realtime(); + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); @@ -876,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) { @@ -984,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) { @@ -1094,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 */ @@ -1138,13 +1140,15 @@ int pa__init(pa_module*m) { int fd = -1; int nfrags, frag_size; int mode, caps; - int record = 1, playback = 1, use_mmap = 1; + pa_bool_t record = TRUE, playback = TRUE, use_mmap = TRUE; pa_sample_spec ss; pa_channel_map map; pa_modargs *ma = NULL; - char hwdesc[64], *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); @@ -1154,7 +1158,7 @@ int pa__init(pa_module*m) { } if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { - pa_log("record= and playback= expect numeric argument."); + pa_log("record= and playback= expect boolean argument."); goto fail; } @@ -1225,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); @@ -1243,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; @@ -1262,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; @@ -1285,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) @@ -1314,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 { @@ -1324,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; @@ -1340,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) @@ -1371,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))) { @@ -1379,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; } } @@ -1401,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 75748474..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. @@ -50,18 +48,19 @@ #include "module-pipe-sink-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("UNIX pipe sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UNIX pipe sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "file=<path of the FIFO> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate>" - "channel_map=<channel map>") + "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 { @@ -79,6 +78,8 @@ struct userdata { pa_memchunk memchunk; pa_rtpoll_item *rtpoll_item; + + int write_type; }; static const char* const valid_modargs[] = { @@ -108,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); @@ -133,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; } @@ -204,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); @@ -225,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) { @@ -250,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 2313df67..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. @@ -49,16 +47,17 @@ #include "module-pipe-source-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("UNIX pipe source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UNIX pipe source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source_name=<name for the source> " "file=<path of the FIFO> " "format=<sample format> " "channels=<number of channels> " "rate=<sample rate> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #define DEFAULT_FILE_NAME "/tmp/music.input" #define DEFAULT_SOURCE_NAME "fifo_input" @@ -100,13 +99,13 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); - + for (;;) { int ret; struct pollfd *pollfd; pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - + /* Try to read some data and pass it on to the source driver */ if (u->source->thread_info.state == PA_SOURCE_RUNNING && pollfd->revents) { ssize_t l; @@ -152,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; @@ -181,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); @@ -202,12 +202,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->filename = pa_runtime_path(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); - u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); - mkfifo(u->filename, 0666); if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); @@ -227,25 +226,33 @@ 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); pollfd->fd = u->fd; pollfd->events = pollfd->revents = 0; - + if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); goto fail; @@ -268,7 +275,7 @@ fail: void pa__done(pa_module*m) { struct userdata *u; - + pa_assert(m); if (!(u = m->userdata)) @@ -286,16 +293,16 @@ void pa__done(pa_module*m) { if (u->source) pa_source_unref(u->source); - + if (u->memchunk.memblock) pa_memblock_unref(u->memchunk.memblock); if (u->rtpoll_item) pa_rtpoll_item_free(u->rtpoll_item); - + if (u->rtpoll) pa_rtpoll_free(u->rtpoll); - + if (u->filename) { unlink(u->filename); pa_xfree(u->filename); 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 fb7cf22d..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. @@ -76,7 +74,7 @@ #else #include "module-simple-protocol-unix-symdef.h" #endif - PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION) +PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("rate=<sample rate> " "format=<sample format> " "channels=<number of channels> " @@ -84,7 +82,7 @@ "source=<source to connect to> " "playback=<enable playback?> " "record=<enable record?> " - SOCKET_USAGE) + SOCKET_USAGE); #elif defined(USE_PROTOCOL_CLI) #include <pulsecore/protocol-cli.h> #define protocol_new pa_protocol_cli_new @@ -98,8 +96,8 @@ #else #include "module-cli-protocol-unix-symdef.h" #endif - PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION) - PA_MODULE_USAGE(SOCKET_USAGE) + PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION); + PA_MODULE_USAGE(SOCKET_USAGE); #elif defined(USE_PROTOCOL_HTTP) #include <pulsecore/protocol-http.h> #define protocol_new pa_protocol_http_new @@ -113,8 +111,8 @@ #else #include "module-http-protocol-unix-symdef.h" #endif - PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION) - PA_MODULE_USAGE(SOCKET_USAGE) + PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION); + PA_MODULE_USAGE(SOCKET_USAGE); #elif defined(USE_PROTOCOL_NATIVE) #include <pulsecore/protocol-native.h> #define protocol_new pa_protocol_native_new @@ -140,11 +138,11 @@ #define AUTH_USAGE #endif - PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION) + PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("auth-anonymous=<don't check for cookies?> " "cookie=<path to cookie file> " AUTH_USAGE - SOCKET_USAGE) + SOCKET_USAGE); #elif defined(USE_PROTOCOL_ESOUND) #include <pulsecore/protocol-esound.h> #include <pulsecore/esound.h> @@ -167,19 +165,20 @@ #define AUTH_USAGE #endif - PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION) + PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION); PA_MODULE_USAGE("sink=<sink to connect to> " "source=<source to connect to> " "auth-anonymous=<don't verify cookies?> " "cookie=<path to cookie file> " AUTH_USAGE - SOCKET_USAGE) + SOCKET_USAGE); #else #error "Broken build system" #endif -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); static const char* const valid_modargs[] = { MODULE_ARGUMENTS @@ -214,11 +213,6 @@ int pa__init(pa_module*m) { #else pa_socket_server *s; int r; - char tmp[PATH_MAX]; - -#if defined(USE_PROTOCOL_ESOUND) - char tmp2[PATH_MAX]; -#endif #endif pa_assert(m); @@ -250,51 +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) - snprintf(tmp2, sizeof(tmp2), "/tmp/.esd-%lu/socket", (unsigned long) getuid()); - pa_runtime_path(pa_modargs_get_value(ma, "socket", tmp2), tmp, sizeof(tmp)); - u->socket_path = pa_xstrdup(tmp); - +#if defined(USE_PER_USER_ESOUND_SOCKET) + u->socket_path = pa_sprintf_malloc("/tmp/.esd-%lu/socket", (unsigned long) getuid()); +#else + u->socket_path = pa_xstrdup("/tmp/.esd/socket"); +#endif + /* 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; @@ -317,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; } @@ -354,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 e863c0c3..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 @@ -40,9 +38,10 @@ #include "module-remap-sink-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Virtual channel remapping sink") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Virtual channel remapping sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name=<name for the sink> " "master=<name of sink to remap> " @@ -50,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; @@ -58,8 +58,6 @@ struct userdata { pa_sink *sink, *master; pa_sink_input *sink_input; - - pa_memchunk memchunk; }; static const char* const valid_modargs[] = { @@ -70,6 +68,7 @@ static const char* const valid_modargs[] = { "format", "channels", "channel_map", + "remix", NULL }; @@ -82,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; } } @@ -100,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 */ @@ -170,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 */ @@ -180,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 */ @@ -193,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); @@ -235,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; } @@ -244,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; @@ -304,8 +404,6 @@ fail: pa__done(m); - pa_xfree(default_sink_name); - return -1; } @@ -317,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 24077dc2..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. @@ -36,9 +34,10 @@ #include "module-rescue-streams-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("When a sink/source is removed, try to move their streams to the default sink/source") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("When a sink/source is removed, try to move their streams to the default sink/source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); static const char* const valid_modargs[] = { NULL, @@ -62,7 +61,7 @@ static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* user if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0)) || target == sink) { uint32_t idx; - + for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) if (target != sink) break; @@ -74,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); } @@ -115,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); } @@ -138,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 5684f94c..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. @@ -39,10 +37,11 @@ #include "module-sine-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Sine wave generator") -PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Sine wave generator"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>"); struct userdata { pa_core *core; @@ -58,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; - - pa_assert(i); - u = i->userdata; - pa_assert(u); - pa_assert(length > 0); + struct userdata *u; - u->peek_index += length; + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); l = pa_memblock_get_length(u->memblock); - - while (u->peek_index >= l) - u->peek_index -= l; + nbytes %= 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); @@ -104,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; @@ -119,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; @@ -155,25 +166,29 @@ 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); - + pa_modargs_free(ma); return 0; @@ -187,7 +202,7 @@ fail: void pa__done(pa_module*m) { struct userdata *u; - + pa_assert(m); if (!(u = m->userdata)) @@ -200,7 +215,6 @@ void pa__done(pa_module*m) { if (u->memblock) pa_memblock_unref(u->memblock); - + pa_xfree(u); } - diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c index a8a94712..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. @@ -28,7 +26,6 @@ #include <stdlib.h> #include <stdio.h> -#include <assert.h> #include <errno.h> #include <string.h> #include <fcntl.h> diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index 5a711390..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. @@ -37,9 +35,10 @@ #include "module-suspend-on-idle-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); static const char* const valid_modargs[] = { "timeout", @@ -65,8 +64,6 @@ struct userdata { *source_output_unlink_slot, *sink_input_move_slot, *source_output_move_slot, - *sink_input_move_post_slot, - *source_output_move_post_slot, *sink_input_state_changed_slot, *source_output_state_changed_slot; }; @@ -130,27 +127,27 @@ static void resume(struct device_info *d) { } } -static pa_hook_result_t sink_input_new_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { +static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { struct device_info *d; pa_assert(c); - pa_sink_input_assert_ref(s); + pa_assert(data); pa_assert(u); - if ((d = pa_hashmap_get(u->device_infos, s->sink))) + if ((d = pa_hashmap_get(u->device_infos, data->sink))) resume(d); return PA_HOOK_OK; } -static pa_hook_result_t source_output_new_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { +static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { struct device_info *d; pa_assert(c); - pa_source_output_assert_ref(s); + pa_assert(data); pa_assert(u); - if ((d = pa_hashmap_get(u->device_infos, s->source))) + if ((d = pa_hashmap_get(u->device_infos, data->source))) resume(d); return PA_HOOK_OK; @@ -184,56 +181,37 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu return PA_HOOK_OK; } -static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { - pa_assert(c); - pa_sink_input_assert_ref(s); - pa_assert(u); - - if (pa_sink_used_by(s->sink) <= 1) { - struct device_info *d; - if ((d = pa_hashmap_get(u->device_infos, s->sink))) - restart(d); - } - - return PA_HOOK_OK; -} - -static pa_hook_result_t sink_input_move_post_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { +static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input_move_hook_data *data, struct userdata *u) { struct device_info *d; - pa_assert(c); - pa_sink_input_assert_ref(s); - pa_assert(u); - - if ((d = pa_hashmap_get(u->device_infos, s->sink))) - resume(d); - - return PA_HOOK_OK; -} -static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { pa_assert(c); - pa_source_output_assert_ref(s); + pa_assert(data); pa_assert(u); - if (pa_source_used_by(s->source) <= 1) { - struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, data->destination))) + resume(d); - if ((d = pa_hashmap_get(u->device_infos, s->source))) + if (pa_sink_used_by(data->sink_input->sink) <= 1) + if ((d = pa_hashmap_get(u->device_infos, data->sink_input->sink))) restart(d); - } return PA_HOOK_OK; } -static pa_hook_result_t source_output_move_post_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { +static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output_move_hook_data *data, struct userdata *u) { struct device_info *d; + pa_assert(c); - pa_source_output_assert_ref(s); + pa_assert(data); pa_assert(u); - if ((d = pa_hashmap_get(u->device_infos, s->source))) + if ((d = pa_hashmap_get(u->device_infos, data->destination))) resume(d); + if (pa_source_used_by(data->source_output->source) <= 1) + if ((d = pa_hashmap_get(u->device_infos, data->source_output->source))) + restart(d); + return PA_HOOK_OK; } @@ -337,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); } @@ -348,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); } } @@ -387,24 +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_PUT], (pa_hook_cb_t) sink_input_new_hook_cb, u); - u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], (pa_hook_cb_t) source_output_new_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_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], (pa_hook_cb_t) sink_input_move_post_hook_cb, u); - u->source_output_move_post_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], (pa_hook_cb_t) source_output_move_post_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; @@ -448,8 +423,6 @@ void pa__done(pa_module*m) { pa_hook_slot_free(u->sink_input_unlink_slot); if (u->sink_input_move_slot) pa_hook_slot_free(u->sink_input_move_slot); - if (u->sink_input_move_post_slot) - pa_hook_slot_free(u->sink_input_move_post_slot); if (u->sink_input_state_changed_slot) pa_hook_slot_free(u->sink_input_state_changed_slot); @@ -459,8 +432,6 @@ void pa__done(pa_module*m) { pa_hook_slot_free(u->source_output_unlink_slot); if (u->source_output_move_slot) pa_hook_slot_free(u->source_output_move_slot); - if (u->source_output_move_post_slot) - pa_hook_slot_free(u->source_output_move_post_slot); if (u->source_output_state_changed_slot) pa_hook_slot_free(u->source_output_state_changed_slot); diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index b96d46b3..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. @@ -27,7 +25,6 @@ #endif #include <unistd.h> -#include <assert.h> #include <string.h> #include <errno.h> #include <sys/types.h> @@ -52,10 +49,21 @@ #include <pulsecore/socket-client.h> #include <pulsecore/socket-util.h> #include <pulsecore/authkey-prop.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/thread.h> +#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" -PA_MODULE_DESCRIPTION("Tunnel module for sinks") +#else +#include "module-tunnel-source-symdef.h" +#endif + +#ifdef TUNNEL_SINK +PA_MODULE_DESCRIPTION("Tunnel module for sinks"); PA_MODULE_USAGE( "server=<address> " "sink=<remote sink name> " @@ -64,10 +72,9 @@ PA_MODULE_USAGE( "channels=<number of channels> " "rate=<sample rate> " "sink_name=<name for the local sink> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #else -#include "module-tunnel-source-symdef.h" -PA_MODULE_DESCRIPTION("Tunnel module for sources") +PA_MODULE_DESCRIPTION("Tunnel module for sources"); PA_MODULE_USAGE( "server=<address> " "source=<remote source name> " @@ -76,21 +83,12 @@ PA_MODULE_USAGE( "channels=<number of channels> " "rate=<sample rate> " "source_name=<name for the local source> " - "channel_map=<channel map>") + "channel_map=<channel map>"); #endif -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_VERSION(PACKAGE_VERSION) - -#define DEFAULT_TLENGTH (44100*2*2/10) //(10240*8) -#define DEFAULT_MAXLENGTH ((DEFAULT_TLENGTH*3)/2) -#define DEFAULT_MINREQ 512 -#define DEFAULT_PREBUF (DEFAULT_TLENGTH-DEFAULT_MINREQ) -#define DEFAULT_FRAGSIZE 1024 - -#define DEFAULT_TIMEOUT 5 - -#define LATENCY_INTERVAL 10 +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); static const char* const valid_modargs[] = { "server", @@ -109,23 +107,70 @@ static const char* const valid_modargs[] = { NULL, }; -static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); -static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +#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_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_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_SUBSCRIBE_EVENT] = command_subscribe_event, + [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, }; struct userdata { + pa_core *core; + pa_module *module; + + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_thread *thread; + pa_socket_client *client; pa_pstream *pstream; pa_pdispatch *pdispatch; @@ -134,15 +179,12 @@ 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; #endif - pa_module *module; - pa_core *core; - uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; uint32_t version; @@ -150,174 +192,487 @@ struct userdata { uint32_t device_index; uint32_t channel; - pa_usec_t host_latency; + int64_t counter, counter_delta; - pa_time_event *time_event; + pa_bool_t remote_corked:1; + pa_bool_t remote_suspended:1; - int auth_cookie_in_property; -}; + pa_usec_t transport_usec; + pa_bool_t transport_usec_valid; -static void close_stuff(struct userdata *u) { - assert(u); + uint32_t ignore_latency_before; - if (u->pstream) { - pa_pstream_close(u->pstream); - pa_pstream_unref(u->pstream); - u->pstream = NULL; - } + pa_time_event *time_event; - if (u->pdispatch) { - pa_pdispatch_unref(u->pdispatch); - u->pdispatch = NULL; - } + pa_bool_t auth_cookie_in_property; - if (u->client) { - pa_socket_client_unref(u->client); - u->client = NULL; - } + pa_smoother *smoother; + char *device_description; + char *server_fqdn; + char *user_name; + + uint32_t maxlength; #ifdef TUNNEL_SINK - if (u->sink) { - pa_sink_disconnect(u->sink); - pa_sink_unref(u->sink); - u->sink = NULL; - } + uint32_t tlength; + uint32_t minreq; + uint32_t prebuf; #else - if (u->source) { - pa_source_disconnect(u->source); - pa_source_unref(u->source); - u->source = NULL; - } + uint32_t fragsize; #endif +}; - if (u->time_event) { - u->core->mainloop->time_free(u->time_event); - u->time_event = NULL; - } -} +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); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); -static void die(struct userdata *u) { - assert(u); - close_stuff(u); + pa_log_warn("Stream killed"); pa_module_unload_request(u->module); } -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) { +/* 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; - assert(pd && t && u && u->pdispatch == pd); - pa_log("stream killed"); - die(u); -} + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); -static void request_info(struct userdata *u); + pa_log_info("Server signalled buffer overrun/underrun."); + request_latency(u); +} -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_suspended(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; + uint32_t channel; + pa_bool_t suspended; - assert(pd && t && u); - assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); - if (pa_tagstruct_getu32(t, &e) < 0 || - pa_tagstruct_getu32(t, &idx) < 0 || + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || !pa_tagstruct_eof(t)) { - pa_log("invalid protocol reply"); - die(u); + pa_log("Invalid packet"); + pa_module_unload_request(u->module); return; } #ifdef TUNNEL_SINK - if (e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE)) - return; + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); #else - if (e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE)) - return; + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_REMOTE_SUSPEND, PA_UINT32_TO_PTR(!!suspended), 0, NULL); #endif - request_info(u); + request_latency(u); +} + +/* 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); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_debug("Server reports a stream move."); + request_latency(u); } #ifdef TUNNEL_SINK -static void send_prebuf_request(struct userdata *u) { + +/* 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 playback started."); + request_latency(u); +} + +#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 (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, 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; t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_PREBUF_PLAYBACK_STREAM); +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(t, PA_COMMAND_CORK_PLAYBACK_STREAM); +#else + pa_tagstruct_putu32(t, PA_COMMAND_CORK_RECORD_STREAM); +#endif pa_tagstruct_putu32(t, u->ctag++); pa_tagstruct_putu32(t, u->channel); + pa_tagstruct_put_boolean(t, !!cork); pa_pstream_send_tagstruct(u->pstream, t); + + request_latency(u); } -static void send_bytes(struct userdata *u) { - assert(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->pstream) + 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 chunk; - if (pa_sink_render(u->sink, u->requested_bytes, &chunk) < 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; + } +} + +/* This function is called from IO context -- except when it is not. */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: { + 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) { + + stream_cork_within_thread(u, u->sink->state == PA_SINK_SUSPENDED); - if (u->requested_bytes >= DEFAULT_TLENGTH-DEFAULT_PREBUF) - send_prebuf_request(u); + if (PA_SINK_IS_OPENED(u->sink->state)) + send_data(u); + } - return; + return r; } - pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, &chunk); - pa_memblock_unref(chunk.memblock); + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t yl, yr, *usec = data; -/* pa_log("sent %lu", (unsigned long) chunk.length); */ + yl = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); - if (chunk.length > u->requested_bytes) - u->requested_bytes = 0; - else - u->requested_bytes -= chunk.length; + *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_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 + * delivered to us from the main context -- NOT from the + * IO thread context where the rest of the messages are + * dispatched. Yeah, ugly, but I am a lazy bastard. */ + + pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, chunk); + + 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); + u = s->userdata; + + switch ((pa_sink_state_t) state) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(s->state)); + stream_cork(u, TRUE); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + if (s->state == PA_SINK_SUSPENDED) + stream_cork(u, FALSE); + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + return 0; +} + +#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_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); + u = s->userdata; + + switch ((pa_source_state_t) state) { + + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_IS_OPENED(s->state)); + stream_cork(u, TRUE); + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + if (s->state == PA_SOURCE_SUSPENDED) + stream_cork(u, FALSE); + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; } + + return 0; +} + +#endif + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); } -static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { +#ifdef TUNNEL_SINK +/* 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; - assert(pd && command == PA_COMMAND_REQUEST && t && u && u->pdispatch == pd); + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REQUEST); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); if (pa_tagstruct_getu32(t, &channel) < 0 || - pa_tagstruct_getu32(t, &bytes) < 0 || - !pa_tagstruct_eof(t)) { - pa_log("invalid protocol reply"); - die(u); - return; + pa_tagstruct_getu32(t, &bytes) < 0) { + pa_log("Invalid protocol reply"); + goto fail; } if (channel != u->channel) { - pa_log("recieved data for invalid channel"); - die(u); - return; + pa_log("Recieved data for invalid channel"); + goto fail; } - u->requested_bytes += bytes; - send_bytes(u); + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, NULL); + return; + +fail: + pa_module_unload_request(u->module); } #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; - int playing; + pa_bool_t playing; int64_t write_index, read_index; struct timeval local, remote, now; - assert(pd && u); + pa_sample_spec *ss; + int64_t delay; + + pa_assert(pd); + pa_assert(u); if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) - pa_log("failed to get latency."); + pa_log("Failed to get latency."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + goto fail; } if (pa_tagstruct_get_usec(t, &sink_usec) < 0 || @@ -326,45 +681,96 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_G pa_tagstruct_get_timeval(t, &local) < 0 || pa_tagstruct_get_timeval(t, &remote) < 0 || pa_tagstruct_gets64(t, &write_index) < 0 || - pa_tagstruct_gets64(t, &read_index) < 0 || - !pa_tagstruct_eof(t)) { - pa_log("invalid reply. (latency)"); - die(u); + pa_tagstruct_gets64(t, &read_index) < 0) { + 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); - /* FIXME! This could use some serious love. */ - + /* 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 - u->host_latency = sink_usec + transport_usec; + delay = (int64_t) sink_usec; + ss = &u->sink->sample_spec; #else - u->host_latency = source_usec + transport_usec; - if (u->host_latency > sink_usec) - u->host_latency -= sink_usec; + 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 - u->host_latency = 0; + 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 + delay -= (int64_t) transport_usec; +#else + delay += (int64_t) transport_usec; +#endif + + /* 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 + delay -= (int64_t) pa_bytes_to_usec(u->counter_delta, ss); +#endif + +#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 -/* pa_log("estimated host latency: %0.0f usec", (double) u->host_latency); */ + 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; uint32_t tag; - assert(u); + pa_assert(u); t = pa_tagstruct_new(NULL, 0); #ifdef TUNNEL_SINK @@ -380,34 +786,154 @@ 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 stream_get_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, 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; - uint32_t idx, owner_module, monitor_source; - pa_usec_t latency; + struct timeval ntv; + + pa_assert(m); + pa_assert(e); + pa_assert(u); + + request_latency(u); + + pa_gettimeofday(&ntv); + ntv.tv_sec += LATENCY_INTERVAL; + m->time_restart(e, &ntv); +} + +/* Called from main context */ +static void update_description(struct userdata *u) { + char *d; + char un[128], hn[128]; + pa_tagstruct *t; + + pa_assert(u); + + if (!u->server_fqdn || !u->user_name || !u->device_description) + return; + + d = pa_sprintf_malloc("%s on %s@%s", u->device_description, u->user_name, u->server_fqdn); + +#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); + + d = pa_sprintf_malloc("%s for %s@%s", u->device_description, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); + + t = pa_tagstruct_new(NULL, 0); +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(t, PA_COMMAND_SET_PLAYBACK_STREAM_NAME); +#else + pa_tagstruct_putu32(t, PA_COMMAND_SET_RECORD_STREAM_NAME); +#endif + pa_tagstruct_putu32(t, u->ctag++); + pa_tagstruct_putu32(t, u->channel); + pa_tagstruct_puts(t, d); + pa_pstream_send_tagstruct(u->pstream, t); + + pa_xfree(d); +} + +/* 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; + uint32_t cookie; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error."); + goto fail; + } + + if (pa_tagstruct_gets(t, &server_name) < 0 || + pa_tagstruct_gets(t, &server_version) < 0 || + pa_tagstruct_gets(t, &user_name) < 0 || + pa_tagstruct_gets(t, &host_name) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_gets(t, &default_sink_name) < 0 || + pa_tagstruct_gets(t, &default_source_name) < 0 || + pa_tagstruct_getu32(t, &cookie) < 0) { + + pa_log("Parse failure"); + goto fail; + } + + if (!pa_tagstruct_eof(t)) { + pa_log("Packet too long"); + goto fail; + } + + pa_xfree(u->server_fqdn); + u->server_fqdn = pa_xstrdup(host_name); + + pa_xfree(u->user_name); + u->user_name = pa_xstrdup(user_name); + + update_description(u); + + return; + +fail: + pa_module_unload_request(u->module); +} + +#ifdef TUNNEL_SINK + +/* 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; - int mute; - uint32_t flags; - pa_sample_spec sample_spec; - pa_channel_map channel_map; + pa_sample_spec ss; + pa_channel_map cm; pa_cvolume volume; - assert(pd && u); + 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."); + pa_log("Failed to get info."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + goto fail; } if (pa_tagstruct_getu32(t, &idx) < 0 || pa_tagstruct_gets(t, &name) < 0 || pa_tagstruct_gets(t, &description) < 0 || - pa_tagstruct_get_sample_spec(t, &sample_spec) < 0 || - pa_tagstruct_get_channel_map(t, &channel_map) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || pa_tagstruct_getu32(t, &owner_module) < 0 || pa_tagstruct_get_cvolume(t, &volume) < 0 || pa_tagstruct_get_boolean(t, &mute) < 0 || @@ -415,133 +941,378 @@ static void stream_get_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_ pa_tagstruct_gets(t, &monitor_source_name) < 0 || pa_tagstruct_get_usec(t, &latency) < 0 || pa_tagstruct_gets(t, &driver) < 0 || - pa_tagstruct_getu32(t, &flags) < 0 || - !pa_tagstruct_eof(t)) { - pa_log("invalid reply. (get_info)"); - die(u); + pa_tagstruct_getu32(t, &flags) < 0) { + + 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; + + pa_xfree(u->device_description); + u->device_description = pa_xstrdup(description); + + update_description(u); + + return; + +fail: + pa_module_unload_request(u->module); + pa_proplist_free(pl); +} + +/* 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; + 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."); + goto fail; } -#ifdef TUNNEL_SINK - assert(u->sink); - if ((!!mute == !!u->sink->hw_muted) && - pa_cvolume_equal(&volume, &u->sink->hw_volume)) + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_getu32(t, &owner_module) < 0 || + pa_tagstruct_getu32(t, &client) < 0 || + pa_tagstruct_getu32(t, &sink) < 0 || + pa_tagstruct_get_sample_spec(t, &sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + 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) { + + 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; -#else - assert(u->source); - if ((!!mute == !!u->source->hw_muted) && - pa_cvolume_equal(&volume, &u->source->hw_volume)) + + pa_assert(u->sink); + + if ((u->version < 11 || !!mute == !!u->sink->muted) && + pa_cvolume_equal(&volume, &u->sink->volume)) return; -#endif -#ifdef TUNNEL_SINK - memcpy(&u->sink->hw_volume, &volume, sizeof(pa_cvolume)); - u->sink->hw_muted = !!mute; + memcpy(&u->sink->volume, &volume, sizeof(pa_cvolume)); + + if (u->version >= 11) + u->sink->muted = !!mute; + + pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index); + return; + +fail: + pa_module_unload_request(u->module); + pa_proplist_free(pl); +} - pa_subscription_post(u->sink->core, - PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, - u->sink->index); #else - memcpy(&u->source->hw_volume, &volume, sizeof(pa_cvolume)); - u->source->hw_muted = !!mute; - pa_subscription_post(u->source->core, - PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, - u->source->index); -#endif +/* 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; + 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."); + goto fail; + } + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &description) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_getu32(t, &monitor_of_sink) < 0 || + pa_tagstruct_gets(t, &monitor_of_sink_name) < 0 || + pa_tagstruct_get_usec(t, &latency) < 0 || + pa_tagstruct_gets(t, &driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0) { + + 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; + + pa_xfree(u->device_description); + u->device_description = pa_xstrdup(description); + + update_description(u); + + return; + +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; - assert(u); + pa_assert(u); t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SERVER_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, server_info_cb, u, NULL); + #ifdef TUNNEL_SINK - pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO); + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INPUT_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->device_index); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_input_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 - pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO); + 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 - pa_tagstruct_putu32(t, tag = u->ctag++); +} + +/* 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; - pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + + if (pa_tagstruct_getu32(t, &e) < 0 || + pa_tagstruct_getu32(t, &idx) < 0) { + pa_log("Invalid protocol reply"); + pa_module_unload_request(u->module); + return; + } + + if (e != (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE) && #ifdef TUNNEL_SINK - pa_tagstruct_puts(t, u->sink_name); + e != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) #else - pa_tagstruct_puts(t, u->source_name); + e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) #endif + ) + return; - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_info_callback, u, NULL); + request_info(u); } +/* Called from main context */ static void start_subscribe(struct userdata *u) { pa_tagstruct *t; uint32_t tag; - assert(u); + pa_assert(u); t = pa_tagstruct_new(NULL, 0); pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE); pa_tagstruct_putu32(t, tag = u->ctag++); - + pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER| #ifdef TUNNEL_SINK - pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SINK); + PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK #else - pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SOURCE); + PA_SUBSCRIPTION_MASK_SOURCE #endif + ); pa_pstream_send_tagstruct(u->pstream, t); } -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 create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { struct userdata *u = userdata; struct timeval ntv; - assert(m && e && u); - - request_latency(u); - - pa_gettimeofday(&ntv); - ntv.tv_sec += LATENCY_INTERVAL; - m->time_restart(e, &ntv); -} +#ifdef TUNNEL_SINK + uint32_t bytes; +#endif -static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { - struct userdata *u = userdata; - struct timeval ntv; - assert(pd && u && u->pdispatch == pd); + pa_assert(pd); + pa_assert(u); + pa_assert(u->pdispatch == pd); if (command != PA_COMMAND_REPLY) { if (command == PA_COMMAND_ERROR) - pa_log("failed to create stream."); + pa_log("Failed to create stream."); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + goto fail; } if (pa_tagstruct_getu32(t, &u->channel) < 0 || pa_tagstruct_getu32(t, &u->device_index) < 0 #ifdef TUNNEL_SINK - || pa_tagstruct_getu32(t, &u->requested_bytes) < 0 + || pa_tagstruct_getu32(t, &bytes) < 0 #endif ) goto parse_error; 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 (u->version >= 13) { + pa_usec_t usec; - if (pa_tagstruct_getu32(t, &maxlength) < 0 || - pa_tagstruct_getu32(t, &fragsize) < 0) + 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 } @@ -551,23 +1322,30 @@ static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UN start_subscribe(u); request_info(u); - assert(!u->time_event); + pa_assert(!u->time_event); pa_gettimeofday(&ntv); ntv.tv_sec += LATENCY_INTERVAL; u->time_event = u->core->mainloop->time_new(u->core->mainloop, &ntv, timeout_callback, u); request_latency(u); + + pa_log_debug("Stream created."); + #ifdef TUNNEL_SINK - send_bytes(u); + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, NULL); #endif return; parse_error: - pa_log("invalid reply. (create stream)"); - die(u); + pa_log("Invalid reply. (Create stream)"); + +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; @@ -575,123 +1353,218 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t #ifdef TUNNEL_SINK pa_cvolume volume; #endif - assert(pd && u && u->pdispatch == pd); + + pa_assert(pd); + pa_assert(u); + pa_assert(u->pdispatch == pd); if (command != PA_COMMAND_REPLY || pa_tagstruct_getu32(t, &u->version) < 0 || !pa_tagstruct_eof(t)) { + if (command == PA_COMMAND_ERROR) - pa_log("failed to authenticate"); + pa_log("Failed to authenticate"); else - pa_log("protocol error."); - die(u); - return; + pa_log("Protocol error."); + + goto fail; } /* Minimum supported protocol version */ if (u->version < 8) { - pa_log("incompatible protocol version"); - die(u); - return; + pa_log("Incompatible protocol version"); + 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), "Tunnel from host %s, user %s, sink %s", - pa_get_host_name(hn, sizeof(hn)), - pa_get_user_name(un, sizeof(un)), - u->sink->name); + pa_snprintf(name, sizeof(name), "%s for %s@%s", + u->sink_name, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); #else - pa_snprintf(name, sizeof(name), "Tunnel from host %s, user %s, source %s", - pa_get_host_name(hn, sizeof(hn)), - pa_get_user_name(un, sizeof(un)), - u->source->name); + pa_snprintf(name, sizeof(name), "%s for %s@%s", + u->source_name, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); #endif 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, name); + + 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, DEFAULT_MAXLENGTH); - pa_tagstruct_put_boolean(reply, 0); - pa_tagstruct_putu32(reply, DEFAULT_TLENGTH); - pa_tagstruct_putu32(reply, DEFAULT_PREBUF); - pa_tagstruct_putu32(reply, DEFAULT_MINREQ); + pa_tagstruct_putu32(reply, u->maxlength); + 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); pa_tagstruct_putu32(reply, 0); pa_cvolume_reset(&volume, u->sink->sample_spec.channels); pa_tagstruct_put_cvolume(reply, &volume); #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, DEFAULT_MAXLENGTH); - pa_tagstruct_put_boolean(reply, 0); - pa_tagstruct_putu32(reply, DEFAULT_FRAGSIZE); + pa_tagstruct_putu32(reply, u->maxlength); + pa_tagstruct_put_boolean(reply, !PA_SOURCE_IS_OPENED(pa_source_get_state(u->source))); + pa_tagstruct_putu32(reply, u->fragsize); +#endif + + if (u->version >= 12) { + 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); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); + + pa_log_debug("Connection authenticated, creating stream ..."); + + return; + +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; - assert(p && u); - pa_log("stream died."); - die(u); + pa_assert(p); + pa_assert(u); + + pa_log_warn("Stream died."); + 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; - assert(p && packet && u); + + pa_assert(p); + pa_assert(packet); + pa_assert(u); if (pa_pdispatch_run(u->pdispatch, packet, creds, u) < 0) { - pa_log("invalid packet"); - die(u); + pa_log("Invalid packet"); + pa_module_unload_request(u->module); + return; } } #ifndef TUNNEL_SINK -static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, PA_GCC_UNUSED int64_t offset, PA_GCC_UNUSED pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { +/* 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; - assert(p && chunk && u); + + pa_assert(p); + pa_assert(chunk); + pa_assert(u); if (channel != u->channel) { - pa_log("recieved memory block on bad channel."); - die(u); + pa_log("Recieved memory block on bad channel."); + pa_module_unload_request(u->module); return; } - pa_source_post(u->source, chunk); + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk); + + 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; uint32_t tag; - assert(sc && u && u->client == sc); + + pa_assert(sc); + pa_assert(u); + pa_assert(u->client == sc); pa_socket_client_unref(u->client); u->client = NULL; if (!io) { - pa_log("connection failed."); + pa_log("Connection failed: %s", pa_cstrerror(errno)); pa_module_unload_request(u->module); return; } @@ -710,164 +1583,83 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_tagstruct_putu32(t, tag = u->ctag++); pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION); pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie)); - pa_pstream_send_tagstruct(u->pstream, t); - pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL); -} +#ifdef HAVE_CREDS +{ + pa_creds ucred; -#ifdef TUNNEL_SINK -static void sink_notify(pa_sink*sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); - send_bytes(u); -} + ucred.uid = getuid(); + ucred.gid = getgid(); -static pa_usec_t sink_get_latency(pa_sink *sink) { - struct userdata *u; - uint32_t l; - pa_usec_t usec = 0; - assert(sink && sink->userdata); - u = sink->userdata; - - l = DEFAULT_TLENGTH; - - if (l > u->requested_bytes) { - l -= u->requested_bytes; - usec += pa_bytes_to_usec(l, &u->sink->sample_spec); - } - - usec += u->host_latency; - - return usec; + pa_pstream_send_tagstruct_with_creds(u->pstream, t, &ucred); } - -static int sink_get_hw_volume(pa_sink *sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; - - return 0; -} - -static int sink_set_hw_volume(pa_sink *sink) { - struct userdata *u; - pa_tagstruct *t; - uint32_t tag; - assert(sink && sink->userdata); - u = sink->userdata; - - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->sink_name); - pa_tagstruct_put_cvolume(t, &sink->hw_volume); +#else pa_pstream_send_tagstruct(u->pstream, t); +#endif - return 0; -} - -static int sink_get_hw_mute(pa_sink *sink) { - struct userdata *u; - assert(sink && sink->userdata); - u = sink->userdata; + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL); - return 0; + pa_log_debug("Connection established, authenticating ..."); } -static int sink_set_hw_mute(pa_sink *sink) { +#ifdef TUNNEL_SINK + +/* Called from main context */ +static int sink_set_volume(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; uint32_t tag; - assert(sink && sink->userdata); + + pa_assert(sink); u = sink->userdata; + pa_assert(u); t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_MUTE); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME); pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->sink_name); - pa_tagstruct_put_boolean(t, !!sink->hw_muted); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_cvolume(t, &sink->volume); pa_pstream_send_tagstruct(u->pstream, t); return 0; } -#else -static pa_usec_t source_get_latency(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - - return u->host_latency; -} - -static int source_get_hw_volume(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - - return 0; -} - -static int source_set_hw_volume(pa_source *source) { +/* Called from main context */ +static int sink_set_mute(pa_sink *sink) { struct userdata *u; pa_tagstruct *t; uint32_t tag; - assert(source && source->userdata); - u = source->userdata; - - t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_VOLUME); - pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->source_name); - pa_tagstruct_put_cvolume(t, &source->hw_volume); - pa_pstream_send_tagstruct(u->pstream, t); - - return 0; -} - -static int source_get_hw_mute(pa_source *source) { - struct userdata *u; - assert(source && source->userdata); - u = source->userdata; - return 0; -} + pa_assert(sink); + u = sink->userdata; + pa_assert(u); -static int source_set_hw_mute(pa_source *source) { - struct userdata *u; - pa_tagstruct *t; - uint32_t tag; - assert(source && source->userdata); - u = source->userdata; + if (u->version < 11) + return -1; t = pa_tagstruct_new(NULL, 0); - pa_tagstruct_putu32(t, PA_COMMAND_SET_SOURCE_MUTE); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE); pa_tagstruct_putu32(t, tag = u->ctag++); - - pa_tagstruct_putu32(t, PA_INVALID_INDEX); - pa_tagstruct_puts(t, u->source_name); - pa_tagstruct_put_boolean(t, !!source->hw_muted); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_boolean(t, !!sink->muted); pa_pstream_send_tagstruct(u->pstream, t); return 0; } + #endif +/* Called from main context */ static int load_key(struct userdata *u, const char*fn) { - assert(u); + 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."); + pa_log_debug("Using already loaded auth cookie."); pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME); u->auth_cookie_in_property = 1; return 0; @@ -879,32 +1671,36 @@ static int load_key(struct userdata *u, const char*fn) { if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0) return -1; - pa_log_debug("loading cookie from disk."); + 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; } -int pa__init(pa_core *c, pa_module*m) { +int pa__init(pa_module*m) { pa_modargs *ma = NULL; 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 - assert(c && m); + 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_xmalloc(sizeof(struct userdata)); - m->userdata = u; + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; u->module = m; - u->core = c; u->client = NULL; u->pdispatch = NULL; u->pstream = NULL; @@ -917,34 +1713,39 @@ int pa__init(pa_core *c, 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, 10); u->ctag = 1; u->device_index = u->channel = PA_INVALID_INDEX; - u->host_latency = 0; - u->auth_cookie_in_property = 0; + 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; + + u->rtpoll = pa_rtpoll_new(); + 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 = c->default_sample_spec; + 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(c->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { - pa_log("failed to connect to server '%s'", u->server_name); + 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); goto fail; } - if (!u->client) - goto fail; - pa_socket_client_set_callback(u->client, on_connection, u); #ifdef TUNNEL_SINK @@ -952,55 +1753,104 @@ int pa__init(pa_core *c, 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(c, __FILE__, dn, 0, &ss, &map))) { - pa_log("failed to create sink."); + 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; } - u->sink->get_latency = sink_get_latency; - u->sink->get_hw_volume = sink_get_hw_volume; - u->sink->set_hw_volume = sink_set_hw_volume; - u->sink->get_hw_mute = sink_get_hw_mute; - u->sink->set_hw_mute = sink_set_hw_mute; - u->sink->notify = sink_notify; + u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Tunnel to %s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name)); - pa_xfree(t); + u->sink->set_state = sink_set_state; + u->sink->set_volume = sink_set_volume; + u->sink->set_mute = sink_set_mute; + + 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_owner(u->sink, m); #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(c, __FILE__, dn, 0, &ss, &map))) { - pa_log("failed to create source."); + 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->get_latency = source_get_latency; - u->source->get_hw_volume = source_get_hw_volume; - u->source->set_hw_volume = source_set_hw_volume; - u->source->get_hw_mute = source_get_hw_mute; - u->source->set_hw_mute = source_set_hw_mute; + u->source->parent.process_msg = source_process_msg; + u->source->set_state = source_set_state; u->source->userdata = u; - pa_source_set_description(u->source, t = pa_sprintf_malloc("Tunnel to %s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name)); - pa_xfree(t); + pa_source_set_latency_range(u->source, MIN_NETWORK_LATENCY_USEC, 0); - pa_source_set_owner(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); #endif pa_xfree(dn); u->time_event = NULL; + u->maxlength = 0; +#ifdef TUNNEL_SINK + u->tlength = u->minreq = u->prebuf = 0; +#else + u->fragsize = 0; +#endif + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + +#ifdef TUNNEL_SINK + pa_sink_put(u->sink); +#else + pa_source_put(u->source); +#endif + pa_modargs_free(ma); return 0; fail: - pa__done(c, m); + pa__done(m); if (ma) pa_modargs_free(ma); @@ -1010,17 +1860,59 @@ fail: return -1; } -void pa__done(pa_core *c, pa_module*m) { +void pa__done(pa_module*m) { struct userdata* u; - assert(c && m); + + pa_assert(m); if (!(u = m->userdata)) return; - close_stuff(u); +#ifdef TUNNEL_SINK + if (u->sink) + pa_sink_unlink(u->sink); +#else + if (u->source) + pa_source_unlink(u->source); +#endif + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + +#ifdef TUNNEL_SINK + if (u->sink) + pa_sink_unref(u->sink); +#else + if (u->source) + pa_source_unref(u->source); +#endif + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pstream) { + pa_pstream_unlink(u->pstream); + pa_pstream_unref(u->pstream); + } + + if (u->pdispatch) + pa_pdispatch_unref(u->pdispatch); + + if (u->client) + pa_socket_client_unref(u->client); if (u->auth_cookie_in_property) - pa_authkey_prop_unref(c, PA_NATIVE_COOKIE_PROPERTY_NAME); + pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + + if (u->smoother) + pa_smoother_free(u->smoother); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); #ifdef TUNNEL_SINK pa_xfree(u->sink_name); @@ -1029,7 +1921,9 @@ void pa__done(pa_core *c, pa_module*m) { #endif pa_xfree(u->server_name); + pa_xfree(u->device_description); + pa_xfree(u->server_fqdn); + pa_xfree(u->user_name); + pa_xfree(u); } - - diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 77e6174f..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. @@ -35,6 +33,7 @@ #include <pulse/xmalloc.h> #include <pulse/volume.h> +#include <pulse/timeval.h> #include <pulsecore/core-error.h> #include <pulsecore/module.h> @@ -48,34 +47,45 @@ #include "module-volume-restore-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams") -PA_MODULE_USAGE("table=<filename>") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "table=<filename> " + "restore_device=<Restore the device for each stream?> " + "restore_volume=<Restore the volume for each stream?>" +); #define WHITESPACE "\n\r \t" - #define DEFAULT_VOLUME_TABLE_FILE "volume-restore.table" +#define SAVE_INTERVAL 10 static const char* const valid_modargs[] = { "table", + "restore_device", + "restore_volume", NULL, }; struct rule { char* name; - int volume_is_set; + pa_bool_t volume_is_set; pa_cvolume volume; - char *sink; - char *source; + char *sink, *source; }; struct userdata { + pa_core *core; pa_hashmap *hashmap; pa_subscription *subscription; - pa_hook_slot *sink_input_hook_slot, *source_output_hook_slot; - int modified; + pa_hook_slot + *sink_input_new_hook_slot, + *sink_input_fixate_hook_slot, + *source_output_new_hook_slot; + pa_bool_t modified; char *table_file; + pa_time_event *save_time_event; }; static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { @@ -90,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; @@ -103,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; @@ -122,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; } @@ -141,7 +147,7 @@ static int load_rules(struct userdata *u) { while (!feof(f)) { struct rule *rule; pa_cvolume v; - int v_is_set; + pa_bool_t v_is_set; if (!fgets(ln, sizeof(buf_name), f)) break; @@ -176,9 +182,9 @@ static int load_rules(struct userdata *u) { goto finish; } - v_is_set = 1; + v_is_set = TRUE; } else - v_is_set = 0; + v_is_set = FALSE; ln = buf_name; @@ -219,12 +225,13 @@ static int save_rules(struct userdata *u) { void *state = NULL; struct rule *rule; - f = u->table_file ? - fopen(u->table_file, "w") : - pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w"); + if (!u->modified) + return 0; + + pa_log_info("Saving rules..."); - if (!f) { - pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); + if (!(f = fopen(u->table_file, "w"))) { + pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); goto finish; } @@ -248,6 +255,8 @@ static int save_rules(struct userdata *u) { } ret = 0; + u->modified = FALSE; + pa_log_debug("Successfully saved rules..."); finish: if (f) { @@ -261,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) { @@ -288,6 +297,21 @@ static char* client_name(pa_client *c) { return t; } +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; + + save_rules(u); +} + static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { struct userdata *u = userdata; pa_sink_input *si = NULL; @@ -328,15 +352,15 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (!r->volume_is_set || !pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) { pa_log_info("Saving volume for <%s>", r->name); r->volume = *pa_sink_input_get_volume(si); - r->volume_is_set = 1; - u->modified = 1; + r->volume_is_set = TRUE; + u->modified = TRUE; } if (!r->sink || strcmp(si->sink->name, r->sink) != 0) { pa_log_info("Saving sink for <%s>", r->name); pa_xfree(r->sink); r->sink = pa_xstrdup(si->sink->name); - u->modified = 1; + u->modified = TRUE; } } else { pa_assert(so); @@ -345,7 +369,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 pa_log_info("Saving source for <%s>", r->name); pa_xfree(r->source); r->source = pa_xstrdup(so->source->name); - u->modified = 1; + u->modified = TRUE; } } @@ -357,27 +381,61 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 if (si) { r->volume = *pa_sink_input_get_volume(si); - r->volume_is_set = 1; + r->volume_is_set = TRUE; r->sink = pa_xstrdup(si->sink->name); r->source = NULL; } else { pa_assert(so); - r->volume_is_set = 0; + r->volume_is_set = FALSE; r->sink = NULL; r->source = pa_xstrdup(so->source->name); } pa_hashmap_put(u->hashmap, r->name, r); - u->modified = 1; + u->modified = TRUE; + } + + if (u->modified && !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); } } -static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { struct rule *r; char *name; pa_assert(data); + /* In the NEW hook we only adjust the device. Adjusting the volume + * is left for the FIXATE hook */ + + if (!data->client || !(name = client_name(data->client))) + return PA_HOOK_OK; + + if ((r = pa_hashmap_get(u->hashmap, name))) { + if (!data->sink && r->sink) { + if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1))) + pa_log_info("Restoring sink for <%s>", r->name); + } + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { + struct rule *r; + char *name; + + pa_assert(data); + + /* In the FIXATE hook we only adjust the volum. Adjusting the device + * is left for the NEW hook */ + if (!data->client || !(name = client_name(data->client))) return PA_HOOK_OK; @@ -387,11 +445,6 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d pa_log_info("Restoring volume for <%s>", r->name); pa_sink_input_new_data_set_volume(data, &r->volume); } - - if (!data->sink && r->sink) { - if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1))) - pa_log_info("Restoring sink for <%s>", r->name); - } } pa_xfree(name); @@ -399,7 +452,7 @@ static pa_hook_result_t sink_input_hook_callback(pa_core *c, pa_sink_input_new_d return PA_HOOK_OK; } -static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { struct rule *r; char *name; @@ -421,6 +474,7 @@ static pa_hook_result_t source_output_hook_callback(pa_core *c, pa_source_output int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; + pa_bool_t restore_device = TRUE, restore_volume = TRUE; pa_assert(m); @@ -430,20 +484,41 @@ 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->modified = FALSE; u->subscription = NULL; - u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL)); - u->modified = 0; - u->sink_input_hook_slot = u->source_output_hook_slot = NULL; + u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL; + u->save_time_event = NULL; 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"); + goto fail; + } + + if (!(restore_device || restore_volume)) { + pa_log("Both restrong the volume and restoring the device are disabled. There's no point in using this module at all then, failing."); + goto fail; + } + if (load_rules(u) < 0) goto fail; u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); - u->sink_input_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_hook_callback, u); - u->source_output_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_hook_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_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_EARLY, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); pa_modargs_free(ma); return 0; @@ -477,21 +552,21 @@ void pa__done(pa_module*m) { if (u->subscription) pa_subscription_free(u->subscription); - if (u->sink_input_hook_slot) - pa_hook_slot_free(u->sink_input_hook_slot); - if (u->source_output_hook_slot) - pa_hook_slot_free(u->source_output_hook_slot); + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->sink_input_fixate_hook_slot) + pa_hook_slot_free(u->sink_input_fixate_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); if (u->hashmap) { - - if (u->modified) - save_rules(u); - + save_rules(u); pa_hashmap_free(u->hashmap, free_func, NULL); } + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + pa_xfree(u->table_file); pa_xfree(u); } - - diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c index ad3645fc..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. @@ -28,7 +26,6 @@ #include <windows.h> #include <mmsystem.h> -#include <assert.h> #include <pulse/mainloop-api.h> diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index e59db83c..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. @@ -44,14 +42,25 @@ #include "module-x11-bell-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("X11 Bell interceptor") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>") +PA_MODULE_AUTHOR("Lennart Poettering"); +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; @@ -59,17 +68,10 @@ 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; - + pa_assert(w); pa_assert(e); pa_assert(u); @@ -80,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); } @@ -88,13 +90,32 @@ 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; pa_modargs *ma = NULL; int major, minor; unsigned int auto_ctrls, auto_values; - + pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { @@ -104,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; @@ -132,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); @@ -143,20 +165,18 @@ fail: pa_modargs_free(ma); pa__done(m); - + return -1; } void pa__done(pa_module*m) { struct userdata *u; - + 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 7da9cc2d..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. @@ -53,10 +51,11 @@ #include "module-x11-publish-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("X11 Credential Publisher") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("display=<X11 display>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 credential publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("display=<X11 display>"); static const char* const valid_modargs[] = { "display", @@ -68,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."); @@ -95,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; } @@ -110,16 +132,19 @@ int pa__init(pa_module*m) { pa_strlist *l; 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_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; @@ -130,7 +155,10 @@ int pa__init(pa_module*m) { if (!(l = pa_property_get(m->core, PA_NATIVE_SERVER_PROPERTY_NAME))) goto fail; + l = pa_strlist_reverse(l); s = pa_strlist_tostring(l); + l = pa_strlist_reverse(l); + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s); pa_xfree(s); @@ -148,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: @@ -156,17 +187,21 @@ fail: pa_modargs_free(ma); pa__done(m); + return -1; } void pa__done(pa_module*m) { struct userdata*u; - + pa_assert(m); if (!(u = m->userdata)) return; + if (u->x11_client) + pa_x11_client_free(u->x11_client); + if (u->x11_wrapper) { char t[256]; @@ -181,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); @@ -192,4 +226,3 @@ void pa__done(pa_module*m) { pa_xfree(u->id); pa_xfree(u); } - diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c index 4ef437a1..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,26 +40,44 @@ #include <pulsecore/namereg.h> #include <pulsecore/log.h> #include <pulsecore/core-util.h> +#include <pulsecore/x11wrap.h> #include "module-x11-xsmp-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("X11 session management") -PA_MODULE_VERSION(PACKAGE_VERSION) +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_x11_wrapper_kill(u->x11); + + pa_x11_wrapper_unref(u->x11); + u->x11 = NULL; - pa_log_debug("Got die message from XSM. Exiting..."); - - pa_core_assert_ref(c); - c->mainloop->quit(c->mainloop, 0); + pa_module_unload_request(u->module); } static void save_complete_cb(SmcConn connection, SmPointer client_data) { @@ -85,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; + struct pa_core *c = client_data; - pa_assert(c); - 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); } @@ -98,50 +117,63 @@ 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); if (ice_in_use) { pa_log("module-x11-xsmp may no be loaded twice."); return -1; } - + 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; callbacks.save_complete.client_data = m->core; 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, sizeof(t), t))) { - + pa_log("Failed to open connection to session manager: %s", t); goto fail; } @@ -163,12 +195,19 @@ 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); - + pa_modargs_free(ma); return 0; @@ -176,20 +215,33 @@ int pa__init(pa_module*m) { fail: if (ma) pa_modargs_free(ma); - + pa__done(m); - + return -1; } 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 new file mode 100644 index 00000000..2fc81370 --- /dev/null +++ b/src/modules/module-zeroconf-discover.c @@ -0,0 +1,438 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/alternative.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/native-common.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/avahi-wrap.h> + +#include "module-zeroconf-discover-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_non-monitor._sub._pulse-source._tcp" + +static const char* const valid_modargs[] = { + NULL +}; + +struct tunnel { + AvahiIfIndex interface; + AvahiProtocol protocol; + char *name, *type, *domain; + uint32_t module_index; +}; + +struct userdata { + pa_core *core; + pa_module *module; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *source_browser, *sink_browser; + + pa_hashmap *tunnels; +}; + +static unsigned tunnel_hash(const void *p) { + const struct tunnel *t = p; + + return + (unsigned) t->interface + + (unsigned) t->protocol + + pa_idxset_string_hash_func(t->name) + + pa_idxset_string_hash_func(t->type) + + pa_idxset_string_hash_func(t->domain); +} + +static int tunnel_compare(const void *a, const void *b) { + const struct tunnel *ta = a, *tb = b; + int r; + + if (ta->interface != tb->interface) + return 1; + if (ta->protocol != tb->protocol) + return 1; + if ((r = strcmp(ta->name, tb->name))) + return r; + if ((r = strcmp(ta->type, tb->type))) + return r; + if ((r = strcmp(ta->domain, tb->domain))) + return r; + + return 0; +} + +static struct tunnel *tunnel_new( + AvahiIfIndex interface, AvahiProtocol protocol, + const char *name, const char *type, const char *domain) { + + struct tunnel *t; + t = pa_xnew(struct tunnel, 1); + t->interface = interface; + t->protocol = protocol; + t->name = pa_xstrdup(name); + t->type = pa_xstrdup(type); + t->domain = pa_xstrdup(domain); + t->module_index = PA_IDXSET_INVALID; + return t; +} + +static void tunnel_free(struct tunnel *t) { + pa_assert(t); + pa_xfree(t->name); + pa_xfree(t->type); + pa_xfree(t->domain); + pa_xfree(t); +} + +static void resolver_cb( + AvahiServiceResolver *r, + AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *a, uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata) { + + struct userdata *u = userdata; + struct tunnel *tnl; + + pa_assert(u); + + tnl = tunnel_new(interface, protocol, name, type, domain); + + if (event != AVAHI_RESOLVER_FOUND) + pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); + else { + char *device = NULL, *dname, *module_name, *args; + const char *t; + char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_sample_spec ss; + pa_channel_map cm; + AvahiStringList *l; + pa_bool_t channel_map_set = FALSE; + pa_module *m; + + ss = u->core->default_sample_spec; + pa_channel_map_init_extend(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); + + for (l = txt; l; l = l->next) { + char *key, *value; + pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); + + if (strcmp(key, "device") == 0) { + pa_xfree(device); + device = value; + value = NULL; + } else if (strcmp(key, "rate") == 0) + ss.rate = atoi(value); + else if (strcmp(key, "channels") == 0) + ss.channels = atoi(value); + else if (strcmp(key, "format") == 0) + ss.format = pa_parse_sample_format(value); + else if (strcmp(key, "channel_map") == 0) { + pa_channel_map_parse(&cm, value); + channel_map_set = TRUE; + } + + avahi_free(key); + avahi_free(value); + } + + 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); + avahi_free(device); + goto finish; + } + + if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) { + pa_log("Service '%s' contains an invalid channel map.", name); + avahi_free(device); + goto finish; + } + + if (device) + dname = pa_sprintf_malloc("tunnel.%s.%s", host_name, device); + else + dname = pa_sprintf_malloc("tunnel.%s", host_name); + + if (!pa_namereg_is_valid_name(dname)) { + pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); + avahi_free(device); + pa_xfree(dname); + goto finish; + } + + t = strstr(type, "sink") ? "sink" : "source"; + + module_name = pa_sprintf_malloc("module-tunnel-%s", t); + args = pa_sprintf_malloc("server=[%s]:%u " + "%s=%s " + "format=%s " + "channels=%u " + "rate=%u " + "%s_name=%s " + "channel_map=%s", + avahi_address_snprint(at, sizeof(at), a), port, + t, device, + pa_sample_format_to_string(ss.format), + ss.channels, + ss.rate, + t, dname, + pa_channel_map_snprint(cmt, sizeof(cmt), &cm)); + + 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; + pa_hashmap_put(u->tunnels, tnl, tnl); + tnl = NULL; + } + + pa_xfree(module_name); + pa_xfree(dname); + pa_xfree(args); + avahi_free(device); + } + +finish: + + avahi_service_resolver_free(r); + + if (tnl) + tunnel_free(tnl); +} + +static void browser_cb( + AvahiServiceBrowser *b, + AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, + void *userdata) { + + struct userdata *u = userdata; + struct tunnel *t; + + pa_assert(u); + + if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + return; + + t = tunnel_new(interface, protocol, name, type, domain); + + if (event == AVAHI_BROWSER_NEW) { + + if (!pa_hashmap_get(u->tunnels, t)) + if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u))) + pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + + /* We ignore the returned resolver object here, since the we don't + * need to attach any special data to it, and we can still destory + * it from the callback */ + + } else if (event == AVAHI_BROWSER_REMOVE) { + struct tunnel *t2; + + if ((t2 = pa_hashmap_get(u->tunnels, t))) { + pa_module_unload_by_index(u->core, t2->module_index); + pa_hashmap_remove(u->tunnels, t2); + tunnel_free(t2); + } + } + + tunnel_free(t); +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(u); + + u->client = c; + + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + + if (!u->sink_browser) { + + if (!(u->sink_browser = avahi_service_browser_new( + c, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SINK, + NULL, + 0, + browser_cb, u))) { + + pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); + pa_module_unload_request(u->module); + } + } + + if (!u->source_browser) { + + if (!(u->source_browser = avahi_service_browser_new( + c, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SOURCE, + NULL, + 0, + browser_cb, u))) { + + pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); + pa_module_unload_request(u->module); + } + } + + break; + + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + int error; + + pa_log_debug("Avahi daemon disconnected."); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); + pa_module_unload_request(u->module); + } + } + + /* Fall through */ + + case AVAHI_CLIENT_CONNECTING: + + if (u->sink_browser) { + avahi_service_browser_free(u->sink_browser); + u->sink_browser = NULL; + } + + if (u->source_browser) { + avahi_service_browser_free(u->source_browser); + u->source_browser = NULL; + } + + break; + + default: ; + } +} + +int pa__init(pa_module*m) { + + struct userdata *u; + pa_modargs *ma = NULL; + int error; + + 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->module = m; + u->sink_browser = u->source_browser = NULL; + + u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); + + u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + goto fail; + } + + 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->client) + avahi_client_free(u->client); + + if (u->avahi_poll) + pa_avahi_poll_free(u->avahi_poll); + + if (u->tunnels) { + struct tunnel *t; + + while ((t = pa_hashmap_steal_first(u->tunnels))) { + pa_module_unload_by_index(u->core, t->module_index); + tunnel_free(t); + } + + pa_hashmap_free(u->tunnels, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c index 113686cf..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. @@ -52,29 +50,44 @@ #include "module-zeroconf-publish-symdef.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher") -PA_MODULE_VERSION(PACKAGE_VERSION) -PA_MODULE_USAGE("port=<IP port number>") +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("port=<IP port number>"); #define SERVICE_TYPE_SINK "_pulse-sink._tcp" #define SERVICE_TYPE_SOURCE "_pulse-source._tcp" #define SERVICE_TYPE_SERVER "_pulse-server._tcp" +#define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK +#define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK +#define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE static const char* const valid_modargs[] = { "port", NULL }; +enum service_subtype { + SUBTYPE_HARDWARE, + SUBTYPE_VIRTUAL, + SUBTYPE_MONITOR +}; + struct service { struct userdata *userdata; AvahiEntryGroup *entry_group; char *service_name; pa_object *device; + enum service_subtype subtype; }; struct userdata { pa_core *core; + pa_module *module; AvahiPoll *avahi_poll; AvahiClient *client; @@ -88,10 +101,11 @@ struct userdata { pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; }; -static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description) { +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description, enum service_subtype *ret_subtype) { pa_assert(s); pa_assert(ret_ss); pa_assert(ret_description); + pa_assert(ret_subtype); if (pa_sink_isinstance(s->device)) { pa_sink *sink = PA_SINK(s->device); @@ -99,7 +113,8 @@ 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)) { pa_source *source = PA_SOURCE(s->device); @@ -107,7 +122,9 @@ 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 pa_assert_not_reached(); } @@ -174,6 +191,13 @@ static int publish_service(struct service *s) { pa_sample_spec ss; pa_channel_map map; char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + enum service_subtype subtype; + + const char * const subtype_text[] = { + [SUBTYPE_HARDWARE] = "hardware", + [SUBTYPE_VIRTUAL] = "virtual", + [SUBTYPE_MONITOR] = "monitor" + }; pa_assert(s); @@ -190,12 +214,13 @@ static int publish_service(struct service *s) { txt = txt_record_server_data(s->userdata->core, txt); - get_service_data(s, &ss, &map, &name, &description); + get_service_data(s, &ss, &map, &name, &description, &subtype); txt = avahi_string_list_add_pair(txt, "device", name); txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate); txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format)); txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map)); + txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]); if (avahi_entry_group_add_service_strlst( s->entry_group, @@ -212,6 +237,35 @@ static int publish_service(struct service *s) { goto finish; } + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, + pa_sink_isinstance(s->device) ? (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) : + (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) { + + pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + + if (pa_source_isinstance(s->device) && subtype != SUBTYPE_MONITOR) { + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + SERVICE_TYPE_SOURCE, + NULL, + SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) { + + pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + } + if (avahi_entry_group_commit(s->entry_group) < 0) { pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); goto finish; @@ -248,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; } @@ -280,11 +334,24 @@ static void service_free(struct service *s) { pa_xfree(s); } +static pa_bool_t shall_ignore(pa_object *o) { + pa_object_assert_ref(o); + + if (pa_sink_isinstance(o)) + return !!(PA_SINK(o)->flags & PA_SINK_NETWORK); + + if (pa_source_isinstance(o)) + return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK); + + pa_assert_not_reached(); +} + static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) { pa_assert(c); pa_object_assert_ref(o); - publish_service(get_service(u, o)); + if (!shall_ignore(o)) + publish_service(get_service(u, o)); return PA_HOOK_OK; } @@ -394,10 +461,12 @@ static int publish_all_services(struct userdata *u) { pa_log_debug("Publishing services in Zeroconf"); for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx))) - publish_service(get_service(u, PA_OBJECT(sink))); + if (!shall_ignore(PA_OBJECT(sink))) + publish_service(get_service(u, PA_OBJECT(sink))); for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx))) - publish_service(get_service(u, PA_OBJECT(source))); + if (!shall_ignore(PA_OBJECT(source))) + publish_service(get_service(u, PA_OBJECT(source))); if (publish_main_service(u) < 0) goto fail; @@ -468,8 +537,10 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda unpublish_all_services(u, TRUE); avahi_client_free(u->client); - if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) - pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); + pa_module_unload_request(u->module); + } } break; @@ -487,36 +558,37 @@ int pa__init(pa_module*m) { int error; 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; } if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { - pa_log("invalid port specified."); + pa_log("Invalid port specified."); goto fail; } m->userdata = u = pa_xnew(struct userdata, 1); u->core = m->core; + u->module = m; u->port = (uint16_t) port; u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); 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; u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { - pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); goto fail; } diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c index 25e45a35..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. @@ -164,6 +162,8 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { [PA_SAMPLE_S16BE] = AFMT_S16_BE, [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */ [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */ }; pa_assert(fd >= 0); @@ -249,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; @@ -271,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; @@ -293,29 +293,39 @@ int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvo } static int get_device_number(const char *dev) { - char buf[PATH_MAX]; const char *p, *e; + char *rp = NULL; + int r; - if (readlink(dev, buf, sizeof(buf)) < 0) { - if (errno != EINVAL && errno != ENOLINK) - return -1; + if (!(p = rp = pa_readlink(dev))) { + if (errno != EINVAL && errno != ENOLINK) { + r = -1; + goto finish; + } p = dev; - } else - p = buf; + } if ((e = strrchr(p, '/'))) p = e+1; - if (p == 0) - return 0; + if (p == 0) { + r = 0; + goto finish; + } p = strchr(p, 0) -1; - if (*p >= '0' && *p <= '9') - return *p - '0'; + if (*p >= '0' && *p <= '9') { + r = *p - '0'; + goto finish; + } - return -1; + r = -1; + +finish: + pa_xfree(rp); + return r; } int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { 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 6c018931..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" @@ -58,19 +59,22 @@ #include "sdp.h" #include "sap.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink=<name of the sink> " "sap_address=<multicast address to listen on> " -) +); #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", @@ -96,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 { @@ -132,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 */ @@ -214,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; } @@ -313,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; @@ -328,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."); @@ -368,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)); @@ -428,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 f79867c2..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. @@ -57,9 +55,10 @@ #include "sdp.h" #include "sap.h" -PA_MODULE_AUTHOR("Lennart Poettering") -PA_MODULE_DESCRIPTION("Read data from source and send it to the network via RTP/SAP/SDP") -PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Read data from source and send it to the network via RTP/SAP/SDP"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "source=<name of the source> " "format=<sample format> " @@ -69,7 +68,7 @@ PA_MODULE_USAGE( "port=<port number> " "mtu=<maximum transfer unit> " "loop=<loopback to local host?>" -) +); #define DEFAULT_PORT 46000 #define SAP_PORT 9875 @@ -111,7 +110,7 @@ static int source_output_process_msg(pa_msgobject *o, int code, void *data, int6 switch (code) { case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->source_output->sample_spec); - + /* Fall through, the default handler will add in the extra * latency added by the resampler */ break; @@ -178,11 +177,11 @@ int pa__init(pa_module*m) { pa_source_output *o = NULL; uint8_t payload; char *p; - int r; + int r, j; socklen_t k; struct timeval tv; char hn[128], *n; - int loop = 0; + pa_bool_t loop = FALSE; pa_source_output_new_data data; pa_assert(m); @@ -273,8 +272,9 @@ int pa__init(pa_module*m) { goto fail; } - if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0 || - setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0) { + j = !!loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0 || + setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0) { pa_log("IP_MULTICAST_LOOP failed: %s", pa_cstrerror(errno)); goto fail; } @@ -286,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; } @@ -316,6 +322,7 @@ int pa__init(pa_module*m) { pa_frame_size(&ss), 1, 0, + 0, NULL); u->mtu = mtu; @@ -345,7 +352,7 @@ int pa__init(pa_module*m) { u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u); pa_source_output_put(u->source_output); - + pa_modargs_free(ma); return 0; diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 60df7274..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; } @@ -78,24 +78,24 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { pa_memchunk chunk; pa_memchunk_reset(&chunk); - + if ((r = pa_memblockq_peek(q, &chunk)) >= 0) { size_t k = n + chunk.length > size ? size - n : chunk.length; pa_assert(chunk.memblock); - + iov[iov_idx].iov_base = ((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index); iov[iov_idx].iov_len = k; mb[iov_idx] = chunk.memblock; iov_idx ++; - + n += k; pa_memblockq_drop(q, k); } pa_assert(n % c->frame_size == 0); - + if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) { uint32_t header[3]; struct msghdr m; @@ -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; @@ -191,11 +209,11 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { r = recvmsg(c->fd, &m, 0); pa_memblock_release(chunk->memblock); - + if (r != size) { if (r < 0 && errno != EAGAIN && errno != EINTR) pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); - + goto fail; } @@ -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. |