Merge branch 'master' of git://0pointer.de/pulseaudio into dbus-work

Conflicts:
	src/Makefile.am
This commit is contained in:
Tanu Kaskinen 2009-08-24 14:43:11 +03:00
commit 2f3fc2f1d6
100 changed files with 4986 additions and 2224 deletions

View file

@ -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);

View file

@ -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 {
#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);
#endif
} else {
/* We got a dropout. What a mess! */
left_to_play = 0;
#ifdef DEBUG_TIMING
PA_DEBUG_TRAP;
#endif
if (!u->first && !u->after_rewind) {
if (!u->first && !u->after_rewind)
if (pa_log_ratelimit())
pa_log_info("Underrun!");
}
if (u->use_tsched)
adjust_after_underrun(u);
#ifdef DEBUG_TIMING
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;
/* 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_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)

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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(

View file

@ -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;

View file

@ -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);

View file

@ -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) {

View file

@ -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);

View file

@ -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);
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);
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);
pa_hashmap_put(u->devices, d->path, d);
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;
event = &buf.e;
while (r > 0) {
size_t len;
if ((buf.e.mask & (IN_DELETE_SELF|IN_MOVE_SELF)))
deleted = TRUE;
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);