summaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/alsa/alsa-mixer.c16
-rw-r--r--src/modules/alsa/alsa-sink.c168
-rw-r--r--src/modules/alsa/alsa-source.c161
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf7
-rw-r--r--src/modules/alsa/mixer/paths/analog-output-mono.conf7
-rw-r--r--src/modules/alsa/mixer/paths/analog-output.conf7
-rw-r--r--src/modules/gconf/module-gconf.c9
-rw-r--r--src/modules/module-combine.c2
-rw-r--r--src/modules/module-ladspa-sink.c79
-rw-r--r--src/modules/module-remap-sink.c7
-rw-r--r--src/modules/module-solaris.c100
-rw-r--r--src/modules/module-udev-detect.c303
12 files changed, 677 insertions, 189 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index a4c2ee0f..61c92cd0 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -929,7 +929,7 @@ static int element_zero_volume(pa_alsa_element *e, snd_mixer_t *m) {
int pa_alsa_path_select(pa_alsa_path *p, snd_mixer_t *m) {
pa_alsa_element *e;
- int r;
+ int r = 0;
pa_assert(m);
pa_assert(p);
@@ -1849,7 +1849,12 @@ pa_alsa_path* pa_alsa_path_new(const char *fname, pa_alsa_direction_t direction)
items[1].data = &p->description;
items[2].data = &p->name;
- fn = pa_maybe_prefix_path(fname, PA_ALSA_PATHS_DIR);
+ fn = pa_maybe_prefix_path(fname,
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/paths/" :
+#endif
+ PA_ALSA_PATHS_DIR);
+
r = pa_config_parse(fn, NULL, items, p);
pa_xfree(fn);
@@ -3110,7 +3115,12 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel
if (!fname)
fname = "default.conf";
- fn = pa_maybe_prefix_path(fname, PA_ALSA_PROFILE_SETS_DIR);
+ fn = pa_maybe_prefix_path(fname,
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/modules/alsa/mixer/profile-sets/" :
+#endif
+ PA_ALSA_PROFILE_SETS_DIR);
+
r = pa_config_parse(fn, NULL, items, ps);
pa_xfree(fn);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index e3707ae7..07d53880 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -62,11 +62,21 @@
/* #define DEBUG_TIMING */
#define DEFAULT_DEVICE "default"
-#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
-#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
-#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
-#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
-#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s -- Overall buffer size */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms -- Fill up when only this much is left in the buffer */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- On underrun, increase watermark by this */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms -- When everything's great, decrease watermark by this */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s -- How long after a drop out recheck if things are good now */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (1*PA_USEC_PER_MSEC) /* 3ms -- If the buffer level ever below this theshold, increase the watermark */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms -- If the buffer level didn't drop below this theshold in the verification time, decrease the watermark */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms -- Sleep at least 10ms on each iteration */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms -- Wakeup at least this long before the buffer runs empty*/
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms -- min smoother update interval */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms -- max smoother update inteval */
#define VOLUME_ACCURACY (PA_VOLUME_NORM/100) /* don't require volume adjustments to be perfectly correct. don't necessarily extend granularity in software unless the differences get greater than this level */
@@ -96,7 +106,12 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
- watermark_step;
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold;
+
+ pa_usec_t watermark_dec_not_before;
unsigned nfragments;
pa_memchunk memchunk;
@@ -115,6 +130,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t write_count;
uint64_t since_start;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@@ -243,6 +260,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->sink->sample_spec);
@@ -257,6 +275,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@@ -267,7 +286,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
-static void adjust_after_underrun(struct userdata *u) {
+static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@@ -276,31 +295,64 @@ static void adjust_after_underrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
- u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
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);
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->sink->thread_info.min_latency;
- new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->sink->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
- pa_log_notice("Increasing minimal latency to %0.2f ms",
- (double) new_min_latency / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
pa_sink_set_latency_range_within_thread(u->sink, new_min_latency, u->sink->thread_info.max_latency);
- return;
}
/* When we reach this we're officialy fucked! */
}
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t usec, wm;
@@ -308,6 +360,7 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p
pa_assert(process_usec);
pa_assert(u);
+ pa_assert(u->use_tsched);
usec = pa_sink_get_requested_latency_within_thread(u->sink);
@@ -355,7 +408,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
-static size_t check_left_to_play(struct userdata *u, size_t n_bytes) {
+static size_t check_left_to_play(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_play;
/* We use <= instead of < for this check here because an underrun
@@ -363,34 +416,55 @@ static size_t check_left_to_play(struct userdata *u, size_t n_bytes) {
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
- if (n_bytes <= u->hwbuf_size) {
+ if (n_bytes <= u->hwbuf_size)
left_to_play = u->hwbuf_size - n_bytes;
+ else {
+
+ /* We got a dropout. What a mess! */
+ left_to_play = 0;
#ifdef DEBUG_TIMING
- 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);
+ PA_DEBUG_TRAP;
#endif
- } else {
- left_to_play = 0;
+ if (!u->first && !u->after_rewind)
+ if (pa_log_ratelimit())
+ pa_log_info("Underrun!");
+ }
#ifdef DEBUG_TIMING
- PA_DEBUG_TRAP;
+ pa_log_debug("%0.2f ms left to play; inc threshold = %0.2f ms; dec threshold = %0.2f ms",
+ (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_inc_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC,
+ (double) pa_bytes_to_usec(u->watermark_dec_threshold, &u->sink->sample_spec) / PA_USEC_PER_MSEC);
#endif
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
if (!u->first && !u->after_rewind) {
+ if (left_to_play < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_play > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
- if (pa_log_ratelimit())
- pa_log_info("Underrun!");
+ /* We decrease the watermark only if have actually
+ * been woken up by a timeout. If something else woke
+ * us up it's too easy to fulfill the deadlines... */
- if (u->use_tsched)
- adjust_after_underrun(u);
+ if (on_timeout)
+ decrease_watermark(u);
+ }
}
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
}
return left_to_play;
}
-static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = TRUE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@@ -425,7 +499,8 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
- left_to_play = check_left_to_play(u, n_bytes);
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
@@ -560,7 +635,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
return work_done ? 1 : 0;
}
-static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_play;
@@ -586,7 +661,8 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polle
}
n_bytes = (size_t) n * u->frame_size;
- left_to_play = check_left_to_play(u, n_bytes);
+ left_to_play = check_left_to_play(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
@@ -723,18 +799,27 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
+ /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
+ if (now1 <= 0)
+ now1 = pa_rtclock_now();
+
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) u->frame_size);
if (PA_UNLIKELY(position < 0))
position = 0;
- /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
- if (now1 <= 0)
- now1 = pa_rtclock_now();
-
now2 = pa_bytes_to_usec((uint64_t) position, &u->sink->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t sink_get_latency(struct userdata *u) {
@@ -906,11 +991,12 @@ static int unsuspend(struct userdata *u) {
u->write_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
u->first = TRUE;
u->since_start = 0;
-
pa_log_info("Resumed successfully...");
return 0;
@@ -1263,15 +1349,16 @@ static void thread_func(void *userdata) {
if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (PA_UNLIKELY(u->sink->thread_info.rewind_requested))
if (process_rewind(u) < 0)
goto fail;
if (u->use_mmap)
- work_done = mmap_write(u, &sleep_usec, revents & POLLOUT);
+ work_done = mmap_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
else
- work_done = unix_write(u, &sleep_usec, revents & POLLOUT);
+ work_done = unix_write(u, &sleep_usec, revents & POLLOUT, on_timeout);
if (work_done < 0)
goto fail;
@@ -1622,6 +1709,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
5,
pa_rtclock_now(),
TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@@ -1771,7 +1859,6 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
u->nfragments = nfrags;
u->hwbuf_size = u->fragment_size * nfrags;
- u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels);
pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
@@ -1782,7 +1869,13 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
pa_sink_set_max_rewind(u->sink, u->hwbuf_size);
if (u->use_tsched) {
- u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->sink->sample_spec);
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->sink->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->sink->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->sink->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->sink->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->sink->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
@@ -1796,6 +1889,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
} else
pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->hwbuf_size, &ss));
+
reserve_update(u);
if (update_sw_params(u) < 0)
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 7da37553..165b2e3b 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -59,11 +59,22 @@
/* #define DEBUG_TIMING */
#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_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
-#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
-#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
+
+#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */
+#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */
+
+#define TSCHED_WATERMARK_INC_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_WATERMARK_DEC_STEP_USEC (5*PA_USEC_PER_MSEC) /* 5ms */
+#define TSCHED_WATERMARK_VERIFY_AFTER_USEC (20*PA_USEC_PER_SEC) /* 20s */
+#define TSCHED_WATERMARK_INC_THRESHOLD_USEC (1*PA_USEC_PER_MSEC) /* 3ms */
+#define TSCHED_WATERMARK_DEC_THRESHOLD_USEC (100*PA_USEC_PER_MSEC) /* 100ms */
+#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
+
+#define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms */
+#define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC) /* 200ms */
#define VOLUME_ACCURACY (PA_VOLUME_NORM/100)
@@ -93,7 +104,12 @@ struct userdata {
hwbuf_unused,
min_sleep,
min_wakeup,
- watermark_step;
+ watermark_inc_step,
+ watermark_dec_step,
+ watermark_inc_threshold,
+ watermark_dec_threshold;
+
+ pa_usec_t watermark_dec_not_before;
unsigned nfragments;
@@ -108,6 +124,8 @@ struct userdata {
pa_smoother *smoother;
uint64_t read_count;
+ pa_usec_t smoother_interval;
+ pa_usec_t last_smoother_update;
pa_reserve_wrapper *reserve;
pa_hook_slot *reserve_slot;
@@ -236,6 +254,7 @@ static int reserve_monitor_init(struct userdata *u, const char *dname) {
static void fix_min_sleep_wakeup(struct userdata *u) {
size_t max_use, max_use_2;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec);
@@ -250,6 +269,7 @@ static void fix_min_sleep_wakeup(struct userdata *u) {
static void fix_tsched_watermark(struct userdata *u) {
size_t max_use;
pa_assert(u);
+ pa_assert(u->use_tsched);
max_use = u->hwbuf_size - u->hwbuf_unused;
@@ -260,7 +280,7 @@ static void fix_tsched_watermark(struct userdata *u) {
u->tsched_watermark = u->min_wakeup;
}
-static void adjust_after_overrun(struct userdata *u) {
+static void increase_watermark(struct userdata *u) {
size_t old_watermark;
pa_usec_t old_min_latency, new_min_latency;
@@ -269,36 +289,72 @@ static void adjust_after_overrun(struct userdata *u) {
/* First, just try to increase the watermark */
old_watermark = u->tsched_watermark;
- u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
-
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_inc_step);
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);
+ pa_log_info("Increasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
return;
}
/* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
old_min_latency = u->source->thread_info.min_latency;
- new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_INC_STEP_USEC);
new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
if (old_min_latency != new_min_latency) {
- pa_log_notice("Increasing minimal latency to %0.2f ms",
- (double) new_min_latency / PA_USEC_PER_MSEC);
+ pa_log_info("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
- return;
}
/* When we reach this we're officialy fucked! */
}
+static void decrease_watermark(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t now;
+
+ pa_assert(u);
+ pa_assert(u->use_tsched);
+
+ now = pa_rtclock_now();
+
+ if (u->watermark_dec_not_before <= 0)
+ goto restart;
+
+ if (u->watermark_dec_not_before > now)
+ return;
+
+ old_watermark = u->tsched_watermark;
+
+ if (u->tsched_watermark < u->watermark_dec_step)
+ u->tsched_watermark = u->tsched_watermark / 2;
+ else
+ u->tsched_watermark = PA_MAX(u->tsched_watermark / 2, u->tsched_watermark - u->watermark_dec_step);
+
+ fix_tsched_watermark(u);
+
+ if (old_watermark != u->tsched_watermark)
+ pa_log_info("Decreasing wakeup watermark to %0.2f ms",
+ (double) pa_bytes_to_usec(u->tsched_watermark, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+
+ /* We don't change the latency range*/
+
+restart:
+ u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
+}
+
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(sleep_usec);
+ pa_assert(process_usec);
+
pa_assert(u);
+ pa_assert(u->use_tsched);
usec = pa_source_get_requested_latency_within_thread(u->source);
@@ -347,7 +403,7 @@ static int try_recover(struct userdata *u, const char *call, int err) {
return 0;
}
-static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
+static size_t check_left_to_record(struct userdata *u, size_t n_bytes, pa_bool_t on_timeout) {
size_t left_to_record;
size_t rec_space = u->hwbuf_size - u->hwbuf_unused;
@@ -356,14 +412,11 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
* it is removed from the buffer. This is particularly important
* when block transfer is used. */
- if (n_bytes <= rec_space) {
+ if (n_bytes <= rec_space)
left_to_record = rec_space - n_bytes;
+ else {
-#ifdef DEBUG_TIMING
- 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);
-#endif
-
- } else {
+ /* We got a dropout. What a mess! */
left_to_record = 0;
#ifdef DEBUG_TIMING
@@ -372,15 +425,36 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
if (pa_log_ratelimit())
pa_log_info("Overrun!");
+ }
- if (u->use_tsched)
- adjust_after_overrun(u);
+#ifdef DEBUG_TIMING
+ 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);
+#endif
+
+ if (u->use_tsched) {
+ pa_bool_t reset_not_before = TRUE;
+
+ if (left_to_record < u->watermark_inc_threshold)
+ increase_watermark(u);
+ else if (left_to_record > u->watermark_dec_threshold) {
+ reset_not_before = FALSE;
+
+ /* We decrease the watermark only if have actually been
+ * woken up by a timeout. If something else woke us up
+ * it's too easy to fulfill the deadlines... */
+
+ if (on_timeout)
+ decrease_watermark(u);
+ }
+
+ if (reset_not_before)
+ u->watermark_dec_not_before = 0;
}
return left_to_record;
}
-static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@@ -412,7 +486,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
pa_log_debug("avail: %lu", (unsigned long) n_bytes);
#endif
- left_to_record = check_left_to_record(u, n_bytes);
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@@ -538,7 +613,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
return work_done ? 1 : 0;
}
-static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
+static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled, pa_bool_t on_timeout) {
int work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
@@ -565,7 +640,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled
}
n_bytes = (size_t) n * u->frame_size;
- left_to_record = check_left_to_record(u, n_bytes);
+ left_to_record = check_left_to_record(u, n_bytes, on_timeout);
+ on_timeout = FALSE;
if (u->use_tsched)
if (!polled &&
@@ -691,15 +767,23 @@ static void update_smoother(struct userdata *u) {
now1 = pa_timespec_load(&htstamp);
}
- position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
-
/* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
if (now1 <= 0)
now1 = pa_rtclock_now();
+ /* check if the time since the last update is bigger than the interval */
+ if (u->last_smoother_update > 0)
+ if (u->last_smoother_update + u->smoother_interval > now1)
+ return;
+
+ position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
now2 = pa_bytes_to_usec(position, &u->source->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
+
+ u->last_smoother_update = now1;
+ /* exponentially increase the update interval up to the MAX limit */
+ u->smoother_interval = PA_MIN (u->smoother_interval * 2, SMOOTHER_MAX_INTERVAL);
}
static pa_usec_t source_get_latency(struct userdata *u) {
@@ -862,6 +946,8 @@ static int unsuspend(struct userdata *u) {
u->read_count = 0;
pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
pa_log_info("Resumed successfully...");
@@ -1143,11 +1229,12 @@ static void thread_func(void *userdata) {
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
int work_done;
pa_usec_t sleep_usec = 0;
+ pa_bool_t on_timeout = pa_rtpoll_timer_elapsed(u->rtpoll);
if (u->use_mmap)
- work_done = mmap_read(u, &sleep_usec, revents & POLLIN);
+ work_done = mmap_read(u, &sleep_usec, revents & POLLIN, on_timeout);
else
- work_done = unix_read(u, &sleep_usec, revents & POLLIN);
+ work_done = unix_read(u, &sleep_usec, revents & POLLIN, on_timeout);
if (work_done < 0)
goto fail;
@@ -1469,6 +1556,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
5,
pa_rtclock_now(),
FALSE);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
dev_id = pa_modargs_get_value(
ma, "device_id",
@@ -1616,7 +1704,6 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size);
u->nfragments = nfrags;
u->hwbuf_size = u->fragment_size * nfrags;
- u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels);
pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms",
@@ -1624,7 +1711,13 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
if (u->use_tsched) {
- u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec);
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
+
+ u->watermark_inc_step = pa_usec_to_bytes(TSCHED_WATERMARK_INC_STEP_USEC, &u->source->sample_spec);
+ u->watermark_dec_step = pa_usec_to_bytes(TSCHED_WATERMARK_DEC_STEP_USEC, &u->source->sample_spec);
+
+ u->watermark_inc_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_INC_THRESHOLD_USEC, &u->source->sample_spec);
+ u->watermark_dec_threshold = pa_usec_to_bytes_round_up(TSCHED_WATERMARK_DEC_THRESHOLD_USEC, &u->source->sample_spec);
fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
diff --git a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
index 2db976a5..3457d4a2 100644
--- a/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-lfe-on-mono.conf
@@ -41,9 +41,12 @@ volume = merge
override-map.1 = lfe
override-map.2 = lfe,lfe
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
diff --git a/src/modules/alsa/mixer/paths/analog-output-mono.conf b/src/modules/alsa/mixer/paths/analog-output-mono.conf
index a58cc970..dc270cfe 100644
--- a/src/modules/alsa/mixer/paths/analog-output-mono.conf
+++ b/src/modules/alsa/mixer/paths/analog-output-mono.conf
@@ -38,9 +38,12 @@ volume = merge
override-map.1 = all
override-map.2 = all-left,all-right
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
diff --git a/src/modules/alsa/mixer/paths/analog-output.conf b/src/modules/alsa/mixer/paths/analog-output.conf
index b412a437..f71a05a1 100644
--- a/src/modules/alsa/mixer/paths/analog-output.conf
+++ b/src/modules/alsa/mixer/paths/analog-output.conf
@@ -37,9 +37,12 @@ override-map.2 = all-left,all-right
switch = off
volume = off
+; This profile path is intended to control the speaker, not the
+; headphones. But it should not hurt if we leave the headphone jack
+; enabled nonetheless.
[Element Headphone]
-switch = off
-volume = off
+switch = mute
+volume = zero
[Element Speaker]
switch = mute
diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c
index c01ebbf6..85523b39 100644
--- a/src/modules/gconf/module-gconf.c
+++ b/src/modules/gconf/module-gconf.c
@@ -52,9 +52,6 @@ PA_MODULE_LOAD_ONCE(TRUE);
#define MAX_MODULES 10
#define BUF_MAX 2048
-/* #undef PA_GCONF_HELPER */
-/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */
-
struct module_item {
char *name;
char *args;
@@ -343,7 +340,11 @@ int pa__init(pa_module*m) {
u->io_event = NULL;
u->buf_fill = 0;
- if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0)
+ if ((u->fd = pa_start_child_for_read(
+#if defined(__linux__) && !defined(__OPTIMIZE__)
+ pa_run_from_build_tree() ? PA_BUILDDIR "/.libs/gconf-helper" :
+#endif
+ PA_GCONF_HELPER, NULL, &u->pid)) < 0)
goto fail;
u->io_event = m->core->mainloop->io_new(
diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c
index 582cbce1..e90ef11c 100644
--- a/src/modules/module-combine.c
+++ b/src/modules/module-combine.c
@@ -1161,6 +1161,8 @@ int pa__init(pa_module*m) {
pa_channel_map slaves_map;
pa_bool_t is_first_slave = TRUE;
+ pa_sample_spec_init(&slaves_spec);
+
while ((n = pa_split(slaves, ",", &split_state))) {
pa_sink *slave_sink;
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index f2d53d00..aa28f7fd 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -99,7 +99,7 @@ static const char* const valid_modargs[] = {
};
/* Called from I/O thread context */
-static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+static int sink_process_msg_cb(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
struct userdata *u = PA_SINK(o)->userdata;
switch (code) {
@@ -130,7 +130,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
}
/* Called from main context */
-static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
+static int sink_set_state_cb(pa_sink *s, pa_sink_state_t state) {
struct userdata *u;
pa_sink_assert_ref(s);
@@ -145,7 +145,7 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
}
/* Called from I/O thread context */
-static void sink_request_rewind(pa_sink *s) {
+static void sink_request_rewind_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
@@ -160,7 +160,7 @@ static void sink_request_rewind(pa_sink *s) {
}
/* Called from I/O thread context */
-static void sink_update_requested_latency(pa_sink *s) {
+static void sink_update_requested_latency_cb(pa_sink *s) {
struct userdata *u;
pa_sink_assert_ref(s);
@@ -176,6 +176,34 @@ static void sink_update_requested_latency(pa_sink *s) {
pa_sink_get_requested_latency_within_thread(s));
}
+/* Called from main context */
+static void sink_set_volume_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_volume(u->sink_input, &s->real_volume, s->save_volume, TRUE);
+}
+
+/* Called from main context */
+static void sink_set_mute_cb(pa_sink *s) {
+ struct userdata *u;
+
+ pa_sink_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ if (!PA_SINK_IS_LINKED(pa_sink_get_state(s)) ||
+ !PA_SINK_INPUT_IS_LINKED(pa_sink_input_get_state(u->sink_input)))
+ return;
+
+ pa_sink_input_set_mute(u->sink_input, s->muted, s->save_muted);
+}
+
/* Called from I/O thread context */
static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk) {
struct userdata *u;
@@ -390,8 +418,31 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
- pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
+}
+
+/* Called from main context */
+static void sink_input_volume_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_volume_changed(u->sink, &i->volume);
+}
+
+/* Called from main context */
+static void sink_input_mute_changed_cb(pa_sink_input *i) {
+ struct userdata *u;
+
+ pa_sink_input_assert_ref(i);
+ pa_assert_se(u = i->userdata);
+
+ pa_sink_mute_changed(u->sink, i->muted);
}
int pa__init(pa_module*m) {
@@ -731,7 +782,9 @@ int pa__init(pa_module*m) {
goto fail;
}
- u->sink = pa_sink_new(m->core, &sink_data, master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY));
+ u->sink = pa_sink_new(m->core, &sink_data,
+ PA_SINK_HW_MUTE_CTRL|PA_SINK_HW_VOLUME_CTRL|PA_SINK_DECIBEL_VOLUME|
+ (master->flags & (PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY)));
pa_sink_new_data_done(&sink_data);
if (!u->sink) {
@@ -739,10 +792,12 @@ int pa__init(pa_module*m) {
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->parent.process_msg = sink_process_msg_cb;
+ u->sink->set_state = sink_set_state_cb;
+ u->sink->update_requested_latency = sink_update_requested_latency_cb;
+ u->sink->request_rewind = sink_request_rewind_cb;
+ u->sink->set_volume = sink_set_volume_cb;
+ u->sink->set_mute = sink_set_mute_cb;
u->sink->userdata = u;
pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq);
@@ -775,6 +830,8 @@ int pa__init(pa_module*m) {
u->sink_input->state_change = sink_input_state_change_cb;
u->sink_input->may_move_to = sink_input_may_move_to_cb;
u->sink_input->moving = sink_input_moving_cb;
+ u->sink_input->volume_changed = sink_input_volume_changed_cb;
+ u->sink_input->mute_changed = sink_input_mute_changed_cb;
u->sink_input->userdata = u;
pa_sink_put(u->sink);
diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c
index 0b4fdc9b..becff55c 100644
--- a/src/modules/module-remap-sink.c
+++ b/src/modules/module-remap-sink.c
@@ -302,8 +302,11 @@ static void sink_input_moving_cb(pa_sink_input *i, pa_sink *dest) {
pa_sink_input_assert_ref(i);
pa_assert_se(u = i->userdata);
- pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
- pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ if (dest) {
+ pa_sink_set_asyncmsgq(u->sink, dest->asyncmsgq);
+ pa_sink_update_flags(u->sink, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY, dest->flags);
+ } else
+ pa_sink_set_asyncmsgq(u->sink, NULL);
}
int pa__init(pa_module*m) {
diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
index 0920d25e..71f14071 100644
--- a/src/modules/module-solaris.c
+++ b/src/modules/module-solaris.c
@@ -60,6 +60,7 @@
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/thread.h>
+#include <pulsecore/time-smoother.h>
#include "module-solaris-symdef.h"
@@ -110,6 +111,8 @@ struct userdata {
uint32_t prev_playback_samples, prev_record_samples;
int32_t minimum_request;
+
+ pa_smoother *smoother;
};
static const char* const valid_modargs[] = {
@@ -133,6 +136,9 @@ static const char* const valid_modargs[] = {
#define MAX_RENDER_HZ (300)
/* This render rate limit imposes a minimum latency, but without it we waste too much CPU time. */
+#define MAX_BUFFER_SIZE (128 * 1024)
+/* An attempt to buffer more than 128 KB causes write() to fail with errno == EAGAIN. */
+
static uint64_t get_playback_buffered_bytes(struct userdata *u) {
audio_info_t info;
uint64_t played_bytes;
@@ -145,7 +151,12 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
/* Handle wrap-around of the device's sample counter, which is a uint_32. */
if (u->prev_playback_samples > info.play.samples) {
- /* Unfortunately info.play.samples can sometimes go backwards, even before it wraps! */
+ /*
+ * Unfortunately info.play.samples can sometimes go backwards, even before it wraps!
+ * The bug seems to be absent on Solaris x86 nv117 with audio810 driver, at least on this (UP) machine.
+ * The bug is present on a different (SMP) machine running Solaris x86 nv103 with audioens driver.
+ * An earlier revision of this file mentions the same bug independently (unknown configuration).
+ */
if (u->prev_playback_samples + info.play.samples < 240000) {
++u->play_samples_msw;
} else {
@@ -155,6 +166,8 @@ static uint64_t get_playback_buffered_bytes(struct userdata *u) {
u->prev_playback_samples = info.play.samples;
played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) * u->frame_size;
+ pa_smoother_put(u->smoother, pa_rtclock_now(), pa_bytes_to_usec(played_bytes, &u->sink->sample_spec));
+
return u->written_bytes - played_bytes;
}
@@ -387,6 +400,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state));
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
+
if (!u->source || u->source_suspended) {
if (suspend(u) < 0)
return -1;
@@ -398,6 +413,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
case PA_SINK_RUNNING:
if (u->sink->thread_info.state == PA_SINK_SUSPENDED) {
+ pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
+
if (!u->source || u->source_suspended) {
if (unsuspend(u) < 0)
return -1;
@@ -479,7 +496,7 @@ static void sink_set_volume(pa_sink *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
- info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ info.play.gain = pa_cvolume_max(&s->real_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@@ -501,8 +518,7 @@ static void sink_get_volume(pa_sink *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
- pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
- info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+ pa_cvolume_set(&s->real_volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@@ -515,7 +531,7 @@ static void source_set_volume(pa_source *s) {
if (u->fd >= 0) {
AUDIO_INITINFO(&info);
- info.play.gain = pa_cvolume_max(&s->virtual_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+ info.play.gain = pa_cvolume_max(&s->volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
assert(info.play.gain <= AUDIO_MAX_GAIN);
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
@@ -537,8 +553,7 @@ static void source_get_volume(pa_source *s) {
if (ioctl(u->fd, AUDIO_GETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
else
- pa_cvolume_set(&s->virtual_volume, s->sample_spec.channels,
- info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+ pa_cvolume_set(&s->volume, s->sample_spec.channels, info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
}
}
@@ -606,11 +621,13 @@ static void thread_func(void *userdata) {
pa_thread_mq_install(&u->thread_mq);
+ pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+
for (;;) {
/* Render some data and write it to the dsp */
if (u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
- pa_usec_t xtime0;
+ pa_usec_t xtime0, ysleep_interval, xsleep_interval;
uint64_t buffered_bytes;
if (u->sink->thread_info.rewind_requested)
@@ -629,12 +646,15 @@ static void thread_func(void *userdata) {
info.play.error = 0;
if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0)
pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
+
+ pa_smoother_reset(u->smoother, pa_rtclock_now(), TRUE);
}
for (;;) {
void *p;
ssize_t w;
size_t len;
+ int write_type = 1;
/*
* Since we cannot modify the size of the output buffer we fake it
@@ -652,38 +672,31 @@ static void thread_func(void *userdata) {
break;
if (u->memchunk.length < len)
- pa_sink_render(u->sink, u->sink->thread_info.max_request, &u->memchunk);
+ pa_sink_render(u->sink, len - u->memchunk.length, &u->memchunk);
+
+ len = PA_MIN(u->memchunk.length, len);
p = pa_memblock_acquire(u->memchunk.memblock);
- w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL);
+ w = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, len, &write_type);
pa_memblock_release(u->memchunk.memblock);
if (w <= 0) {
- switch (errno) {
- case EINTR:
- continue;
- case EAGAIN:
- /* If the buffer_size is too big, we get EAGAIN. Avoiding that limit by trial and error
- * is not ideal, but I don't know how to get the system to tell me what the limit is.
- */
- u->buffer_size = u->buffer_size * 18 / 25;
- u->buffer_size -= u->buffer_size % u->frame_size;
- u->buffer_size = PA_MAX(u->buffer_size, 2 * u->minimum_request);
- pa_sink_set_max_request_within_thread(u->sink, u->buffer_size);
- pa_sink_set_max_rewind_within_thread(u->sink, u->buffer_size);
- pa_log("EAGAIN. Buffer size is now %u bytes (%llu buffered)", u->buffer_size, buffered_bytes);
- break;
- default:
- pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
- goto fail;
+ if (errno == EINTR) {
+ continue;
+ } else if (errno == EAGAIN) {
+ /* We may have realtime priority so yield the CPU to ensure that fd can become writable again. */
+ pa_log_debug("EAGAIN with %llu bytes buffered.", buffered_bytes);
+ break;
+ } else {
+ pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno));
+ goto fail;
}
} else {
pa_assert(w % u->frame_size == 0);
u->written_bytes += w;
- u->memchunk.length -= w;
-
u->memchunk.index += w;
+ u->memchunk.length -= w;
if (u->memchunk.length <= 0) {
pa_memblock_unref(u->memchunk.memblock);
pa_memchunk_reset(&u->memchunk);
@@ -691,7 +704,9 @@ static void thread_func(void *userdata) {
}
}
- pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec));
+ ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, &u->sink->sample_spec);
+ xsleep_interval = pa_smoother_translate(u->smoother, xtime0, ysleep_interval);
+ pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + PA_MIN(xsleep_interval, ysleep_interval));
} else
pa_rtpoll_set_timer_disabled(u->rtpoll);
@@ -797,7 +812,7 @@ static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void
pa_log_debug("caught signal");
if (u->sink) {
- pa_sink_get_volume(u->sink, TRUE, FALSE);
+ pa_sink_get_volume(u->sink, TRUE);
pa_sink_get_mute(u->sink, TRUE);
}
@@ -812,7 +827,7 @@ int pa__init(pa_module *m) {
pa_channel_map map;
pa_modargs *ma = NULL;
uint32_t buffer_length_msec;
- int fd;
+ int fd = -1;
pa_sink_new_data sink_new_data;
pa_source_new_data source_new_data;
char const *name;
@@ -838,6 +853,9 @@ int pa__init(pa_module *m) {
u = pa_xnew0(struct userdata, 1);
+ if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, TRUE, TRUE, 10, pa_rtclock_now(), TRUE)))
+ goto fail;
+
/*
* For a process (or several processes) to use the same audio device for both
* record and playback at the same time, the device's mixer must be enabled.
@@ -861,7 +879,13 @@ int pa__init(pa_module *m) {
}
u->buffer_size = pa_usec_to_bytes(1000 * buffer_length_msec, &ss);
if (u->buffer_size < 2 * u->minimum_request) {
- pa_log("supplied buffer size argument is too small");
+ pa_log("buffer_length argument cannot be smaller than %u",
+ (unsigned)(pa_bytes_to_usec(2 * u->minimum_request, &ss) / 1000));
+ goto fail;
+ }
+ if (u->buffer_size > MAX_BUFFER_SIZE) {
+ pa_log("buffer_length argument cannot be greater than %u",
+ (unsigned)(pa_bytes_to_usec(MAX_BUFFER_SIZE, &ss) / 1000));
goto fail;
}
@@ -924,6 +948,7 @@ int pa__init(pa_module *m) {
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
pa_source_set_rtpoll(u->source, u->rtpoll);
+ pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->buffer_size, &u->source->sample_spec));
u->source->get_volume = source_get_volume;
u->source->set_volume = source_set_volume;
@@ -966,15 +991,15 @@ int pa__init(pa_module *m) {
pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
pa_sink_set_rtpoll(u->sink, u->rtpoll);
+ pa_sink_set_fixed_latency(u->sink, pa_bytes_to_usec(u->buffer_size, &u->sink->sample_spec));
+ pa_sink_set_max_request(u->sink, u->buffer_size);
+ pa_sink_set_max_rewind(u->sink, u->buffer_size);
u->sink->get_volume = sink_get_volume;
u->sink->set_volume = sink_set_volume;
u->sink->get_mute = sink_get_mute;
u->sink->set_mute = sink_set_mute;
u->sink->refresh_volume = u->sink->refresh_muted = TRUE;
-
- pa_sink_set_max_request(u->sink, u->buffer_size);
- pa_sink_set_max_rewind(u->sink, u->buffer_size);
} else
u->sink = NULL;
@@ -1075,6 +1100,9 @@ void pa__done(pa_module *m) {
if (u->fd >= 0)
close(u->fd);
+ if (u->smoother)
+ pa_smoother_free(u->smoother);
+
pa_xfree(u->device_name);
pa_xfree(u);
diff --git a/src/modules/module-udev-detect.c b/src/modules/module-udev-detect.c
index 0b30fd54..b41b9c0f 100644
--- a/src/modules/module-udev-detect.c
+++ b/src/modules/module-udev-detect.c
@@ -25,6 +25,7 @@
#include <errno.h>
#include <limits.h>
+#include <dirent.h>
#include <sys/inotify.h>
#include <libudev.h>
@@ -45,8 +46,9 @@ PA_MODULE_USAGE(
struct device {
char *path;
- pa_bool_t accessible;
+ pa_bool_t need_verify;
char *card_name;
+ char *args;
uint32_t module;
};
@@ -78,6 +80,7 @@ static void device_free(struct device *d) {
pa_xfree(d->path);
pa_xfree(d->card_name);
+ pa_xfree(d->args);
pa_xfree(d);
}
@@ -96,30 +99,166 @@ static const char *path_get_card_id(const char *path) {
return e + 5;
}
+static pa_bool_t is_card_busy(const char *id) {
+ char *card_path = NULL, *pcm_path = NULL, *sub_status = NULL;
+ DIR *card_dir = NULL, *pcm_dir = NULL;
+ FILE *status_file = NULL;
+ size_t len;
+ struct dirent *space = NULL, *de;
+ pa_bool_t busy = FALSE;
+ int r;
+
+ pa_assert(id);
+
+ card_path = pa_sprintf_malloc("/proc/asound/card%s", id);
+
+ if (!(card_dir = opendir(card_path))) {
+ pa_log_warn("Failed to open %s: %s", card_path, pa_cstrerror(errno));
+ goto fail;
+ }
+
+ len = offsetof(struct dirent, d_name) + fpathconf(dirfd(card_dir), _PC_NAME_MAX) + 1;
+ space = pa_xmalloc(len);
+
+ for (;;) {
+ de = NULL;
+
+ if ((r = readdir_r(card_dir, space, &de)) != 0) {
+ pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
+ goto fail;
+ }
+
+ if (!de)
+ break;
+
+ if (!pa_startswith(de->d_name, "pcm"))
+ continue;
+
+ pa_xfree(pcm_path);
+ pcm_path = pa_sprintf_malloc("%s/%s", card_path, de->d_name);
+
+ if (pcm_dir)
+ closedir(pcm_dir);
+
+ if (!(pcm_dir = opendir(pcm_path))) {
+ pa_log_warn("Failed to open %s: %s", pcm_path, pa_cstrerror(errno));
+ continue;
+ }
+
+ for (;;) {
+ char line[32];
+
+ if ((r = readdir_r(pcm_dir, space, &de)) != 0) {
+ pa_log_warn("readdir_r() failed: %s", pa_cstrerror(r));
+ goto fail;
+ }
+
+ if (!de)
+ break;
+
+ if (!pa_startswith(de->d_name, "sub"))
+ continue;
+
+ pa_xfree(sub_status);
+ sub_status = pa_sprintf_malloc("%s/%s/status", pcm_path, de->d_name);
+
+ if (status_file)
+ fclose(status_file);
+
+ if (!(status_file = fopen(sub_status, "r"))) {
+ pa_log_warn("Failed to open %s: %s", sub_status, pa_cstrerror(errno));
+ continue;
+ }
+
+ if (!(fgets(line, sizeof(line)-1, status_file))) {
+ pa_log_warn("Failed to read from %s: %s", sub_status, pa_cstrerror(errno));
+ continue;
+ }
+
+ if (!pa_streq(line, "closed\n")) {
+ busy = TRUE;
+ break;
+ }
+ }
+ }
+
+fail:
+
+ pa_xfree(card_path);
+ pa_xfree(pcm_path);
+ pa_xfree(sub_status);
+ pa_xfree(space);
+
+ if (card_dir)
+ closedir(card_dir);
+
+ if (pcm_dir)
+ closedir(pcm_dir);
+
+ if (status_file)
+ fclose(status_file);
+
+ return busy;
+}
+
static void verify_access(struct userdata *u, struct device *d) {
char *cd;
pa_card *card;
+ pa_bool_t accessible;
pa_assert(u);
pa_assert(d);
- if (!(card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD)))
- return;
-
cd = pa_sprintf_malloc("%s/snd/controlC%s", udev_get_dev_path(u->udev), path_get_card_id(d->path));
- d->accessible = access(cd, W_OK) >= 0;
- pa_log_info("%s is accessible: %s", cd, pa_yes_no(d->accessible));
+ accessible = access(cd, R_OK|W_OK) >= 0;
+ pa_log_debug("%s is accessible: %s", cd, pa_yes_no(accessible));
+
pa_xfree(cd);
- pa_card_suspend(card, !d->accessible, PA_SUSPEND_SESSION);
+ if (d->module == PA_INVALID_INDEX) {
+
+ /* If we are not loaded, try to load */
+
+ if (accessible) {
+ pa_module *m;
+ pa_bool_t busy;
+
+ /* Check if any of the PCM devices that belong to this
+ * card are currently busy. If they are, don't try to load
+ * right now, to make sure the probing phase can
+ * successfully complete. When the current user of the
+ * device closes it we will get another notification via
+ * inotify and can then recheck. */
+
+ busy = is_card_busy(path_get_card_id(d->path));
+ pa_log_debug("%s is busy: %s", d->path, pa_yes_no(busy));
+
+ if (!busy) {
+ pa_log_debug("Loading module-alsa-card with arguments '%s'", d->args);
+ m = pa_module_load(u->core, "module-alsa-card", d->args);
+
+ if (m) {
+ d->module = m->index;
+ pa_log_info("Card %s (%s) module loaded.", d->path, d->card_name);
+ } else
+ pa_log_info("Card %s (%s) failed to load module.", d->path, d->card_name);
+ }
+ }
+
+ } else {
+
+ /* If we are already loaded update suspend status with
+ * accessible boolean */
+
+ if ((card = pa_namereg_get(u->core, d->card_name, PA_NAMEREG_CARD)))
+ pa_card_suspend(card, !accessible, PA_SUSPEND_SESSION);
+ }
}
static void card_changed(struct userdata *u, struct udev_device *dev) {
struct device *d;
const char *path;
const char *t;
- char *card_name, *args;
- pa_module *m;
char *n;
pa_assert(u);
@@ -135,44 +274,33 @@ static void card_changed(struct userdata *u, struct udev_device *dev) {
return;
}
+ d = pa_xnew0(struct device, 1);
+ d->path = pa_xstrdup(path);
+ d->module = PA_INVALID_INDEX;
+
if (!(t = udev_device_get_property_value(dev, "PULSE_NAME")))
if (!(t = udev_device_get_property_value(dev, "ID_ID")))
if (!(t = udev_device_get_property_value(dev, "ID_PATH")))
t = path_get_card_id(path);
n = pa_namereg_make_valid_name(t);
+ d->card_name = pa_sprintf_malloc("alsa_card.%s", n);
+ d->args = pa_sprintf_malloc("device_id=\"%s\" "
+ "name=\"%s\" "
+ "card_name=\"%s\" "
+ "tsched=%s "
+ "ignore_dB=%s "
+ "card_properties=\"module-udev-detect.discovered=1\"",
+ path_get_card_id(path),
+ n,
+ d->card_name,
+ pa_yes_no(u->use_tsched),
+ pa_yes_no(u->ignore_dB));
+ pa_xfree(n);
- card_name = pa_sprintf_malloc("alsa_card.%s", n);
- args = pa_sprintf_malloc("device_id=\"%s\" "
- "name=\"%s\" "
- "card_name=\"%s\" "
- "tsched=%s "
- "ignore_dB=%s "
- "card_properties=\"module-udev-detect.discovered=1\"",
- path_get_card_id(path),
- n,
- card_name,
- pa_yes_no(u->use_tsched),
- pa_yes_no(u->ignore_dB));
-
- pa_log_debug("Loading module-alsa-card with arguments '%s'", args);
- m = pa_module_load(u->core, "module-alsa-card", args);
- pa_xfree(args);
-
- if (m) {
- pa_log_info("Card %s (%s) added.", path, n);
-
- d = pa_xnew(struct device, 1);
- d->path = pa_xstrdup(path);
- d->card_name = card_name;
- d->module = m->index;
- d->accessible = TRUE;
-
- pa_hashmap_put(u->devices, d->path, d);
- } else
- pa_xfree(card_name);
+ pa_hashmap_put(u->devices, d->path, d);
- pa_xfree(n);
+ verify_access(u, d);
}
static void remove_card(struct userdata *u, struct udev_device *dev) {
@@ -185,7 +313,10 @@ static void remove_card(struct userdata *u, struct udev_device *dev) {
return;
pa_log_info("Card %s removed.", d->path);
- pa_module_unload_request_by_index(u->core, d->module, TRUE);
+
+ if (d->module != PA_INVALID_INDEX)
+ pa_module_unload_request_by_index(u->core, d->module, TRUE);
+
device_free(d);
}
@@ -262,6 +393,34 @@ fail:
u->udev_io = NULL;
}
+static pa_bool_t pcm_node_belongs_to_device(
+ struct device *d,
+ const char *node) {
+
+ char *cd;
+ pa_bool_t b;
+
+ cd = pa_sprintf_malloc("pcmC%sD", path_get_card_id(d->path));
+ b = pa_startswith(node, cd);
+ pa_xfree(cd);
+
+ return b;
+}
+
+static pa_bool_t control_node_belongs_to_device(
+ struct device *d,
+ const char *node) {
+
+ char *cd;
+ pa_bool_t b;
+
+ cd = pa_sprintf_malloc("controlC%s", path_get_card_id(d->path));
+ b = pa_streq(node, cd);
+ pa_xfree(cd);
+
+ return b;
+}
+
static void inotify_cb(
pa_mainloop_api*a,
pa_io_event* e,
@@ -275,10 +434,13 @@ static void inotify_cb(
} buf;
struct userdata *u = userdata;
static int type = 0;
- pa_bool_t verify = FALSE, deleted = FALSE;
+ pa_bool_t deleted = FALSE;
+ struct device *d;
+ void *state;
for (;;) {
ssize_t r;
+ struct inotify_event *event;
pa_zero(buf);
if ((r = pa_read(fd, &buf, sizeof(buf), &type)) <= 0) {
@@ -290,22 +452,51 @@ static void inotify_cb(
goto fail;
}
- if ((buf.e.mask & IN_CLOSE_WRITE) && pa_startswith(buf.e.name, "pcmC"))
- verify = TRUE;
-
- if ((buf.e.mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
- deleted = TRUE;
+ event = &buf.e;
+ while (r > 0) {
+ size_t len;
+
+ if ((size_t) r < sizeof(struct inotify_event)) {
+ pa_log("read() too short.");
+ goto fail;
+ }
+
+ len = sizeof(struct inotify_event) + event->len;
+
+ if ((size_t) r < len) {
+ pa_log("Payload missing.");
+ goto fail;
+ }
+
+ /* From udev we get the guarantee that the control
+ * device's ACL is changed last. To avoid races when ACLs
+ * are changed we hence watch only the control device */
+ if (((event->mask & IN_ATTRIB) && pa_startswith(event->name, "controlC")))
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (control_node_belongs_to_device(d, event->name))
+ d->need_verify = TRUE;
+
+ /* ALSA doesn't really give us any guarantee on the closing
+ * order, so let's simply hope */
+ if (((event->mask & IN_CLOSE_WRITE) && pa_startswith(event->name, "pcmC")))
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (pcm_node_belongs_to_device(d, event->name))
+ d->need_verify = TRUE;
+
+ /* /dev/snd/ might have been removed */
+ if ((event->mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
+ deleted = TRUE;
+
+ event = (struct inotify_event*) ((uint8_t*) event + len);
+ r -= len;
+ }
}
- if (verify) {
- struct device *d;
- void *state;
-
- pa_log_debug("Verifying access.");
-
- PA_HASHMAP_FOREACH(d, u->devices, state)
+ PA_HASHMAP_FOREACH(d, u->devices, state)
+ if (d->need_verify) {
+ d->need_verify = FALSE;
verify_access(u, d);
- }
+ }
if (!deleted)
return;
@@ -335,7 +526,7 @@ static int setup_inotify(struct userdata *u) {
}
dev_snd = pa_sprintf_malloc("%s/snd", udev_get_dev_path(u->udev));
- r = inotify_add_watch(u->inotify_fd, dev_snd, IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
+ r = inotify_add_watch(u->inotify_fd, dev_snd, IN_ATTRIB|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF);
pa_xfree(dev_snd);
if (r < 0) {
@@ -449,7 +640,7 @@ int pa__init(pa_module *m) {
udev_enumerate_unref(enumerate);
- pa_log_info("Loaded %u modules.", pa_hashmap_size(u->devices));
+ pa_log_info("Found %u cards.", pa_hashmap_size(u->devices));
pa_modargs_free(ma);