aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGeorg Chini <georg@chini.tk>2019-03-09 20:19:34 +0100
committerArun Raghavan <arun@arunraghavan.net>2019-03-25 05:02:29 +0000
commitf7b3537bbf9a6916ee3fd72a82025519b4c346f5 (patch)
treeebae49d1bf9992035f79d612f277018fe514c7ef
parente794d0a21af24ae3d2afae000ad67b17ef56ada2 (diff)
downloadpulseaudio-f7b3537bbf9a6916ee3fd72a82025519b4c346f5.tar.gz
alsa: Improve resume logic after alsa suspend
Currently, when a system is waking up from suspend, the resume process of the ALSA sink and source is unstable. Sometimes the device needs to be restarted multiple times and when the system was suspended between snd_pcm_mmap_begin() and snd_pcm_mmap_commit(), pulseaudio crashes on resume. Additionally, variables are not reset after the resume, so that sink/source report wrong latencies. This patch fixes the issues by closing and re-opening the PCM if recovery from an error condition is not possible. Additionally, the variables are reset, so that latencies are reported correctly.
-rw-r--r--src/modules/alsa/alsa-sink.c171
-rw-r--r--src/modules/alsa/alsa-source.c174
-rw-r--r--src/modules/alsa/alsa-util.c25
3 files changed, 237 insertions, 133 deletions
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 3585336e..28143402 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -177,6 +177,7 @@ enum {
};
static void userdata_free(struct userdata *u);
+static int unsuspend(struct userdata *u, bool recovering);
/* FIXME: Is there a better way to do this than device names? */
static bool is_iec958(struct userdata *u) {
@@ -412,6 +413,39 @@ restart:
u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
}
+/* Called from IO Context on unsuspend or from main thread when creating sink */
+static void reset_watermark(struct userdata *u, size_t tsched_watermark, pa_sample_spec *ss,
+ bool in_thread) {
+ u->tsched_watermark = pa_convert_size(tsched_watermark, 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);
+
+ if (in_thread)
+ pa_sink_set_latency_range_within_thread(u->sink,
+ u->min_latency_ref,
+ pa_bytes_to_usec(u->hwbuf_size, ss));
+ else {
+ pa_sink_set_latency_range(u->sink,
+ 0,
+ pa_bytes_to_usec(u->hwbuf_size, ss));
+
+ /* work-around assert in pa_sink_set_latency_within_thead,
+ keep track of min_latency and reuse it when
+ this routine is called from IO context */
+ u->min_latency_ref = u->sink->thread_info.min_latency;
+ }
+
+ pa_log_info("Time scheduling watermark is %0.2fms",
+ (double) u->tsched_watermark_usec / PA_USEC_PER_MSEC);
+}
+
static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t usec, wm;
@@ -442,6 +476,31 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p
#endif
}
+/* Reset smoother and counters */
+static void reset_vars(struct userdata *u) {
+
+ 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;
+ u->write_count = 0;
+}
+
+/* Called from IO context */
+static void close_pcm(struct userdata *u) {
+ /* Let's suspend -- we don't call snd_pcm_drain() here since that might
+ * take awfully long with our long buffer sizes today. */
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+}
+
static int try_recover(struct userdata *u, const char *call, int err) {
pa_assert(u);
pa_assert(call);
@@ -458,12 +517,17 @@ static int try_recover(struct userdata *u, const char *call, int err) {
pa_log_debug("%s: System suspended!", call);
if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
- pa_log("%s: %s", call, pa_alsa_strerror(err));
- return -1;
+ pa_log("%s: %s, trying to restart PCM", call, pa_alsa_strerror(err));
+
+ /* As a last measure, restart the PCM and inform the caller about it. */
+ close_pcm(u);
+ if (unsuspend(u, true) < 0)
+ return -1;
+
+ return 1;
}
- u->first = true;
- u->since_start = 0;
+ reset_vars(u);
return 0;
}
@@ -548,7 +612,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) >= 0)
continue;
return r;
@@ -639,6 +703,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo
if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -677,6 +744,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo
if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -736,7 +806,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) >= 0)
continue;
return r;
@@ -825,6 +895,9 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bo
if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -956,19 +1029,16 @@ static int build_pollfd(struct userdata *u) {
/* Called from IO context */
static void suspend(struct userdata *u) {
pa_assert(u);
- pa_assert(u->pcm_handle);
+
+ /* Handle may have been invalidated due to a device failure.
+ * In that case there is nothing to do. */
+ if (!u->pcm_handle)
+ return;
pa_smoother_pause(u->smoother, pa_rtclock_now());
- /* Let's suspend -- we don't call snd_pcm_drain() here since that might
- * take awfully long with our long buffer sizes today. */
- snd_pcm_close(u->pcm_handle);
- u->pcm_handle = NULL;
-
- if (u->alsa_rtpoll_item) {
- pa_rtpoll_item_free(u->alsa_rtpoll_item);
- u->alsa_rtpoll_item = NULL;
- }
+ /* Close PCM device */
+ close_pcm(u);
/* We reset max_rewind/max_request here to make sure that while we
* are suspended the old max_request/max_rewind values set before
@@ -1057,39 +1127,6 @@ static int update_sw_params(struct userdata *u, bool may_need_rewind) {
return 0;
}
-/* Called from IO Context on unsuspend or from main thread when creating sink */
-static void reset_watermark(struct userdata *u, size_t tsched_watermark, pa_sample_spec *ss,
- bool in_thread) {
- u->tsched_watermark = pa_convert_size(tsched_watermark, 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);
-
- if (in_thread)
- pa_sink_set_latency_range_within_thread(u->sink,
- u->min_latency_ref,
- pa_bytes_to_usec(u->hwbuf_size, ss));
- else {
- pa_sink_set_latency_range(u->sink,
- 0,
- pa_bytes_to_usec(u->hwbuf_size, ss));
-
- /* work-around assert in pa_sink_set_latency_within_thead,
- keep track of min_latency and reuse it when
- this routine is called from IO context */
- u->min_latency_ref = u->sink->thread_info.min_latency;
- }
-
- pa_log_info("Time scheduling watermark is %0.2fms",
- (double) u->tsched_watermark_usec / PA_USEC_PER_MSEC);
-}
-
/* Called from IO Context on unsuspend */
static void update_size(struct userdata *u, pa_sample_spec *ss) {
pa_assert(u);
@@ -1112,7 +1149,7 @@ static void update_size(struct userdata *u, pa_sample_spec *ss) {
}
/* Called from IO context */
-static int unsuspend(struct userdata *u) {
+static int unsuspend(struct userdata *u, bool recovering) {
pa_sample_spec ss;
int err;
bool b, d;
@@ -1190,16 +1227,10 @@ static int unsuspend(struct userdata *u) {
if (build_pollfd(u) < 0)
goto fail;
- 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;
+ reset_vars(u);
/* reset the watermark to the value defined when sink was created */
- if (u->use_tsched)
+ if (u->use_tsched && !recovering)
reset_watermark(u, u->tsched_watermark_ref, &u->sink->sample_spec, true);
pa_log_info("Resumed successfully...");
@@ -1359,7 +1390,7 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, pa_sink_state_t new_state,
}
if (s->thread_info.state == PA_SINK_SUSPENDED) {
- if ((r = unsuspend(u)) < 0)
+ if ((r = unsuspend(u, false)) < 0)
return r;
}
@@ -1756,6 +1787,7 @@ static void sink_reconfigure_cb(pa_sink *s, pa_sample_spec *spec, bool passthrou
static int process_rewind(struct userdata *u) {
snd_pcm_sframes_t unused;
size_t rewind_nbytes, unused_nbytes, limit_nbytes;
+ int err;
pa_assert(u);
if (!PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
@@ -1769,10 +1801,12 @@ static int process_rewind(struct userdata *u) {
pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes);
if (PA_UNLIKELY((unused = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) {
- if (try_recover(u, "snd_pcm_avail", (int) unused) < 0) {
+ if ((err = try_recover(u, "snd_pcm_avail", (int) unused)) < 0) {
pa_log_warn("Trying to recover from underrun failed during rewind");
return -1;
}
+ if (err == 1)
+ goto rewind_done;
}
unused_nbytes = (size_t) unused * u->frame_size;
@@ -1797,8 +1831,10 @@ static int process_rewind(struct userdata *u) {
pa_log_debug("before: %lu", (unsigned long) in_frames);
if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) {
pa_log("snd_pcm_rewind() failed: %s", pa_alsa_strerror((int) out_frames));
- if (try_recover(u, "process_rewind", out_frames) < 0)
+ if ((err = try_recover(u, "process_rewind", out_frames)) < 0)
return -1;
+ if (err == 1)
+ goto rewind_done;
out_frames = 0;
}
@@ -1819,6 +1855,7 @@ static int process_rewind(struct userdata *u) {
} else
pa_log_debug("Mhmm, actually there is nothing to rewind.");
+rewind_done:
pa_sink_process_rewind(u->sink, 0);
return 0;
}
@@ -1974,11 +2011,17 @@ static void thread_func(void *userdata) {
}
if (revents & ~POLLOUT) {
- if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+ if ((err = pa_alsa_recover_from_poll(u->pcm_handle, revents)) < 0)
goto fail;
- u->first = true;
- u->since_start = 0;
+ /* Stream needs to be restarted */
+ if (err == 1) {
+ close_pcm(u);
+ if (unsuspend(u, true) < 0)
+ goto fail;
+ } else
+ reset_vars(u);
+
revents = 0;
} else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG))
pa_log_debug("Wakeup from ALSA!");
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index 34f1614a..8129220b 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -158,6 +158,7 @@ enum {
};
static void userdata_free(struct userdata *u);
+static int unsuspend(struct userdata *u, bool recovering);
static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
pa_assert(r);
@@ -384,6 +385,39 @@ restart:
u->watermark_dec_not_before = now + TSCHED_WATERMARK_VERIFY_AFTER_USEC;
}
+/* Called from IO Context on unsuspend or from main thread when creating source */
+static void reset_watermark(struct userdata *u, size_t tsched_watermark, pa_sample_spec *ss,
+ bool in_thread) {
+ u->tsched_watermark = pa_convert_size(tsched_watermark, 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);
+
+ if (in_thread)
+ pa_source_set_latency_range_within_thread(u->source,
+ u->min_latency_ref,
+ pa_bytes_to_usec(u->hwbuf_size, ss));
+ else {
+ pa_source_set_latency_range(u->source,
+ 0,
+ pa_bytes_to_usec(u->hwbuf_size, ss));
+
+ /* work-around assert in pa_source_set_latency_within_thead,
+ keep track of min_latency and reuse it when
+ this routine is called from IO context */
+ u->min_latency_ref = u->source->thread_info.min_latency;
+ }
+
+ pa_log_info("Time scheduling watermark is %0.2fms",
+ (double) u->tsched_watermark_usec / PA_USEC_PER_MSEC);
+}
+
static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
pa_usec_t wm, usec;
@@ -414,6 +448,31 @@ static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*p
#endif
}
+/* Reset smoother and counters */
+static void reset_vars(struct userdata *u) {
+
+ pa_smoother_reset(u->smoother, pa_rtclock_now(), true);
+ u->smoother_interval = SMOOTHER_MIN_INTERVAL;
+ u->last_smoother_update = 0;
+
+ u->read_count = 0;
+ u->first = true;
+}
+
+/* Called from IO context */
+static void close_pcm(struct userdata *u) {
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
+
+ /* Let's suspend */
+ snd_pcm_close(u->pcm_handle);
+ u->pcm_handle = NULL;
+
+ if (u->alsa_rtpoll_item) {
+ pa_rtpoll_item_free(u->alsa_rtpoll_item);
+ u->alsa_rtpoll_item = NULL;
+ }
+}
+
static int try_recover(struct userdata *u, const char *call, int err) {
pa_assert(u);
pa_assert(call);
@@ -430,11 +489,17 @@ static int try_recover(struct userdata *u, const char *call, int err) {
pa_log_debug("%s: System suspended!", call);
if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
- pa_log("%s: %s", call, pa_alsa_strerror(err));
- return -1;
+ pa_log("%s: %s, trying to restart PCM", call, pa_alsa_strerror(err));
+
+ /* As a last measure, restart the PCM and inform the caller about it. */
+ close_pcm(u);
+ if (unsuspend(u, true) < 0)
+ return -1;
+
+ return 1;
}
- u->first = true;
+ reset_vars(u);
return 0;
}
@@ -493,6 +558,7 @@ static size_t check_left_to_record(struct userdata *u, size_t n_bytes, bool on_t
static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bool on_timeout) {
bool work_done = false;
+ bool recovery_done = false;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
unsigned j = 0;
@@ -511,7 +577,8 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ recovery_done = true;
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) >= 0)
continue;
return r;
@@ -583,9 +650,13 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
if (!after_avail && err == -EAGAIN)
break;
+ recovery_done = true;
if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -617,9 +688,13 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) {
+ recovery_done = true;
if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -646,6 +721,11 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
*sleep_usec -= process_usec;
else
*sleep_usec = 0;
+
+ /* If the PCM was recovered, it may need restarting. Reduce the sleep time
+ * to 0 to ensure immediate restart. */
+ if (recovery_done)
+ *sleep_usec = 0;
}
return work_done ? 1 : 0;
@@ -653,6 +733,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, bool on_timeout) {
int work_done = false;
+ bool recovery_done = false;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
unsigned j = 0;
@@ -671,7 +752,8 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
+ recovery_done = true;
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) >= 0)
continue;
return r;
@@ -735,9 +817,13 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
if (!after_avail && (int) frames == -EAGAIN)
break;
+ recovery_done = true;
if ((r = try_recover(u, "snd_pcm_readi", (int) frames)) == 0)
continue;
+ if (r == 1)
+ break;
+
return r;
}
@@ -776,6 +862,11 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, bool polled, boo
*sleep_usec -= process_usec;
else
*sleep_usec = 0;
+
+ /* If the PCM was recovered, it may need restarting. Reduce the sleep time
+ * to 0 to ensure immediate restart. */
+ if (recovery_done)
+ *sleep_usec = 0;
}
return work_done ? 1 : 0;
@@ -853,18 +944,14 @@ static int build_pollfd(struct userdata *u) {
/* Called from IO context */
static void suspend(struct userdata *u) {
pa_assert(u);
- pa_assert(u->pcm_handle);
- pa_smoother_pause(u->smoother, pa_rtclock_now());
+ /* PCM may have been invalidated due to device failure.
+ * In that case, there is nothing to do. */
+ if (!u->pcm_handle)
+ return;
- /* Let's suspend */
- snd_pcm_close(u->pcm_handle);
- u->pcm_handle = NULL;
-
- if (u->alsa_rtpoll_item) {
- pa_rtpoll_item_free(u->alsa_rtpoll_item);
- u->alsa_rtpoll_item = NULL;
- }
+ /* Close PCM device */
+ close_pcm(u);
pa_log_info("Device suspended...");
}
@@ -922,39 +1009,6 @@ static int update_sw_params(struct userdata *u) {
return 0;
}
-/* Called from IO Context on unsuspend or from main thread when creating source */
-static void reset_watermark(struct userdata *u, size_t tsched_watermark, pa_sample_spec *ss,
- bool in_thread) {
- u->tsched_watermark = pa_convert_size(tsched_watermark, 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);
-
- if (in_thread)
- pa_source_set_latency_range_within_thread(u->source,
- u->min_latency_ref,
- pa_bytes_to_usec(u->hwbuf_size, ss));
- else {
- pa_source_set_latency_range(u->source,
- 0,
- pa_bytes_to_usec(u->hwbuf_size, ss));
-
- /* work-around assert in pa_source_set_latency_within_thead,
- keep track of min_latency and reuse it when
- this routine is called from IO context */
- u->min_latency_ref = u->source->thread_info.min_latency;
- }
-
- pa_log_info("Time scheduling watermark is %0.2fms",
- (double) u->tsched_watermark_usec / PA_USEC_PER_MSEC);
-}
-
/* Called from IO Context on unsuspend */
static void update_size(struct userdata *u, pa_sample_spec *ss) {
pa_assert(u);
@@ -976,7 +1030,7 @@ static void update_size(struct userdata *u, pa_sample_spec *ss) {
}
/* Called from IO context */
-static int unsuspend(struct userdata *u) {
+static int unsuspend(struct userdata *u, bool recovering) {
pa_sample_spec ss;
int err;
bool b, d;
@@ -1047,15 +1101,10 @@ static int unsuspend(struct userdata *u) {
/* FIXME: We need to reload the volume somehow */
- u->read_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;
+ reset_vars(u);
/* reset the watermark to the value defined when source was created */
- if (u->use_tsched)
+ if (u->use_tsched && !recovering)
reset_watermark(u, u->tsched_watermark_ref, &u->source->sample_spec, true);
pa_log_info("Resumed successfully...");
@@ -1212,7 +1261,7 @@ static int source_set_state_in_io_thread_cb(pa_source *s, pa_source_state_t new_
}
if (s->thread_info.state == PA_SOURCE_SUSPENDED) {
- if ((r = unsuspend(u)) < 0)
+ if ((r = unsuspend(u, false)) < 0)
return r;
}
@@ -1668,10 +1717,17 @@ static void thread_func(void *userdata) {
}
if (revents & ~POLLIN) {
- if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
+ if ((err = pa_alsa_recover_from_poll(u->pcm_handle, revents)) < 0)
goto fail;
- u->first = true;
+ /* Stream needs to be restarted */
+ if (err == 1) {
+ close_pcm(u);
+ if (unsuspend(u, true) < 0)
+ goto fail;
+ } else
+ reset_vars(u);
+
revents = 0;
} else if (revents && u->use_tsched && pa_log_ratelimit(PA_LOG_DEBUG))
pa_log_debug("Wakeup from ALSA!");
diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c
index c5f6188e..e8d712e7 100644
--- a/src/modules/alsa/alsa-util.c
+++ b/src/modules/alsa/alsa-util.c
@@ -1090,6 +1090,11 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
switch (state) {
+ case SND_PCM_STATE_DISCONNECTED:
+ /* Do not try to recover */
+ pa_log_info("Device disconnected.");
+ return -1;
+
case SND_PCM_STATE_XRUN:
if ((err = snd_pcm_recover(pcm, -EPIPE, 1)) != 0) {
pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", pa_alsa_strerror(err));
@@ -1098,21 +1103,21 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
break;
case SND_PCM_STATE_SUSPENDED:
- if ((err = snd_pcm_recover(pcm, -ESTRPIPE, 1)) != 0) {
- pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", pa_alsa_strerror(err));
- return -1;
+ /* Retry resume 3 times before giving up, then fallback to restarting the stream. */
+ for (int i = 0; i < 3; i++) {
+ if ((err = snd_pcm_resume(pcm)) == 0)
+ return 0;
+ if (err != -EAGAIN)
+ break;
+ pa_msleep(25);
}
- break;
+ pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM");
+ /* Fall through */
default:
snd_pcm_drop(pcm);
-
- if ((err = snd_pcm_prepare(pcm)) < 0) {
- pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", pa_alsa_strerror(err));
- return -1;
- }
- break;
+ return 1;
}
return 0;