mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-11-09 13:29:59 -05:00
Merge branch 'master' of git://0pointer.de/pulseaudio into dbus-work
Conflicts: src/Makefile.am
This commit is contained in:
commit
2f3fc2f1d6
100 changed files with 4986 additions and 2224 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue