diff -Nru pulseaudio-11.1/debian/changelog pulseaudio-11.1/debian/changelog --- pulseaudio-11.1/debian/changelog 2019-11-05 17:16:25.000000000 +0800 +++ pulseaudio-11.1/debian/changelog 2019-12-10 23:13:12.000000000 +0800 @@ -1,3 +1,11 @@ +pulseaudio (1:11.1-1ubuntu7.6) bionic; urgency=medium + + * Add 0910-alsa-Improve-resume-logic-after-alsa-suspend.patch to make PCM + suspend/resume more reliable to fix Dell WD19TB Headset Mic doesn't work + after resume. (LP: #1855893) + + -- Kai-Heng Feng Tue, 10 Dec 2019 23:13:12 +0800 + pulseaudio (1:11.1-1ubuntu7.5) bionic; urgency=medium * Update snap policy to make access to audio recording conditional on diff -Nru pulseaudio-11.1/debian/patches/0910-alsa-Improve-resume-logic-after-alsa-suspend.patch pulseaudio-11.1/debian/patches/0910-alsa-Improve-resume-logic-after-alsa-suspend.patch --- pulseaudio-11.1/debian/patches/0910-alsa-Improve-resume-logic-after-alsa-suspend.patch 1970-01-01 08:00:00.000000000 +0800 +++ pulseaudio-11.1/debian/patches/0910-alsa-Improve-resume-logic-after-alsa-suspend.patch 2019-12-10 23:12:54.000000000 +0800 @@ -0,0 +1,545 @@ +Index: pulseaudio-sru/src/modules/alsa/alsa-sink.c +=================================================================== +--- pulseaudio-sru.orig/src/modules/alsa/alsa-sink.c ++++ pulseaudio-sru/src/modules/alsa/alsa-sink.c +@@ -162,6 +162,7 @@ struct userdata { + }; + + 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) { +@@ -427,6 +428,31 @@ static void hw_sleep_time(struct userdat + #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); +@@ -443,12 +469,17 @@ static int try_recover(struct userdata * + 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; + } + +@@ -533,7 +564,7 @@ static int mmap_write(struct userdata *u + + 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; +@@ -624,6 +655,9 @@ static int mmap_write(struct userdata *u + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; + ++ if (r == 1) ++ break; ++ + return r; + } + +@@ -662,6 +696,9 @@ static int mmap_write(struct userdata *u + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) + continue; + ++ if (r == 1) ++ break; ++ + return r; + } + +@@ -721,7 +758,7 @@ static int unix_write(struct userdata *u + + 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; +@@ -810,6 +847,9 @@ static int unix_write(struct userdata *u + if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) + continue; + ++ if (r == 1) ++ break; ++ + return r; + } + +@@ -941,19 +981,16 @@ static int build_pollfd(struct userdata + /* Called from IO context */ + static int 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 +@@ -1064,7 +1101,7 @@ static void reset_watermark(struct userd + } + + /* 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; +@@ -1128,16 +1165,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..."); +@@ -1198,7 +1229,7 @@ static int sink_process_msg(pa_msgobject + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { +- if ((r = unsuspend(u)) < 0) ++ if ((r = unsuspend(u, false)) < 0) + return r; + } + +@@ -1235,7 +1266,6 @@ static int sink_set_state_cb(pa_sink *s, + + return 0; + } +- + static int ctl_mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + +@@ -1627,6 +1657,7 @@ static int sink_update_rate_cb(pa_sink * + 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)) { +@@ -1640,10 +1671,12 @@ static int process_rewind(struct userdat + 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; +@@ -1668,8 +1701,10 @@ static int process_rewind(struct userdat + 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; + } + +@@ -1690,6 +1725,7 @@ static int process_rewind(struct userdat + } else + pa_log_debug("Mhmm, actually there is nothing to rewind."); + ++rewind_done: + pa_sink_process_rewind(u->sink, 0); + return 0; + } +@@ -1845,11 +1881,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!"); +Index: pulseaudio-sru/src/modules/alsa/alsa-source.c +=================================================================== +--- pulseaudio-sru.orig/src/modules/alsa/alsa-source.c ++++ pulseaudio-sru/src/modules/alsa/alsa-source.c +@@ -144,6 +144,7 @@ struct userdata { + }; + + 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); +@@ -400,6 +401,31 @@ static void hw_sleep_time(struct userdat + #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); +@@ -416,11 +442,17 @@ static int try_recover(struct userdata * + 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; + } + +@@ -479,6 +511,7 @@ static size_t check_left_to_record(struc + + 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; +@@ -497,7 +530,8 @@ static int mmap_read(struct userdata *u, + + 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; +@@ -569,9 +603,13 @@ static int mmap_read(struct userdata *u, + 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; + } + +@@ -603,9 +641,13 @@ static int mmap_read(struct userdata *u, + + 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; + } + +@@ -632,6 +674,11 @@ static int mmap_read(struct userdata *u, + *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; +@@ -639,6 +686,7 @@ static int mmap_read(struct userdata *u, + + 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; +@@ -657,7 +705,8 @@ static int unix_read(struct userdata *u, + + 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; +@@ -721,9 +770,13 @@ static int unix_read(struct userdata *u, + 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; + } + +@@ -762,6 +815,11 @@ static int unix_read(struct userdata *u, + *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; +@@ -839,18 +897,14 @@ static int build_pollfd(struct userdata + /* Called from IO context */ + static int suspend(struct userdata *u) { + pa_assert(u); +- pa_assert(u->pcm_handle); +- +- pa_smoother_pause(u->smoother, pa_rtclock_now()); + +- /* Let's suspend */ +- snd_pcm_close(u->pcm_handle); +- u->pcm_handle = NULL; ++ /* PCM may have been invalidated due to device failure. ++ * In that case, there is nothing to do. */ ++ if (!u->pcm_handle) ++ return; + +- 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..."); + +@@ -944,7 +998,7 @@ static void reset_watermark(struct userd + } + + /* 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; +@@ -1001,15 +1055,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..."); +@@ -1067,7 +1116,7 @@ static int source_process_msg(pa_msgobje + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { +- if ((r = unsuspend(u)) < 0) ++ if ((r = unsuspend(u, false)) < 0) + return r; + } + +@@ -1549,10 +1598,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!"); +Index: pulseaudio-sru/src/modules/alsa/alsa-util.c +=================================================================== +--- pulseaudio-sru.orig/src/modules/alsa/alsa-util.c ++++ pulseaudio-sru/src/modules/alsa/alsa-util.c +@@ -1090,6 +1090,11 @@ int pa_alsa_recover_from_poll(snd_pcm_t + + 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 + 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; diff -Nru pulseaudio-11.1/debian/patches/series pulseaudio-11.1/debian/patches/series --- pulseaudio-11.1/debian/patches/series 2019-11-05 17:16:25.000000000 +0800 +++ pulseaudio-11.1/debian/patches/series 2019-12-10 23:12:54.000000000 +0800 @@ -30,3 +30,4 @@ steelseries.5.3454c19f3.patch 0900-stream-restore-Don-t-restore-if-the-active_port-is-P.patch +0910-alsa-Improve-resume-logic-after-alsa-suspend.patch