2018-03-01 12:57:09 +00:00
|
|
|
From 94fc586c011537536cfb434376354699357af785 Mon Sep 17 00:00:00 2001
|
|
|
|
From: Tanu Kaskinen <tanuk@iki.fi>
|
|
|
|
Date: Thu, 28 Dec 2017 12:09:17 +0200
|
|
|
|
Subject: [PATCH] alsa: fix infinite loop with Intel HDMI LPE
|
|
|
|
|
|
|
|
The Intel HDMI LPE driver works in a peculiar way when the HDMI cable is
|
|
|
|
not plugged in: any written audio is immediately discarded and underrun
|
|
|
|
is reported. That resulted in an infinite loop, because PulseAudio tried
|
|
|
|
to keep the buffer filled, which was futile since the written audio was
|
|
|
|
immediately consumed/discarded.
|
|
|
|
|
|
|
|
This patch adds special handling for the LPE driver: if the active port
|
|
|
|
of the sink is unavailable, the sink suspends itself. A new suspend
|
|
|
|
cause is added: PA_SUSPEND_UNAVAILABLE.
|
|
|
|
|
|
|
|
BugLink: https://bugs.freedesktop.org/show_bug.cgi?id=100488
|
|
|
|
---
|
|
|
|
src/modules/alsa/alsa-mixer.h | 1 +
|
2018-03-06 15:13:51 +00:00
|
|
|
src/modules/alsa/alsa-sink.c | 20 ++++++++++++++++++++
|
2018-03-01 12:57:09 +00:00
|
|
|
src/modules/alsa/module-alsa-card.c | 34 ++++++++++++++++++++++++++++++++++
|
|
|
|
src/pulsecore/core.h | 1 +
|
2018-03-06 15:13:51 +00:00
|
|
|
4 files changed, 56 insertions(+)
|
2018-03-01 12:57:09 +00:00
|
|
|
|
|
|
|
--- a/src/modules/alsa/alsa-mixer.h
|
|
|
|
+++ b/src/modules/alsa/alsa-mixer.h
|
|
|
|
@@ -364,6 +364,7 @@ int pa_alsa_set_mixer_rtpoll(struct pa_a
|
|
|
|
struct pa_alsa_port_data {
|
|
|
|
pa_alsa_path *path;
|
|
|
|
pa_alsa_setting *setting;
|
|
|
|
+ bool suspend_when_unavailable;
|
|
|
|
};
|
|
|
|
|
|
|
|
void pa_alsa_add_ports(void *sink_or_source_new_data, pa_alsa_path_set *ps, pa_card *card);
|
|
|
|
--- a/src/modules/alsa/alsa-sink.c
|
|
|
|
+++ b/src/modules/alsa/alsa-sink.c
|
2018-03-06 15:13:51 +00:00
|
|
|
@@ -1513,6 +1513,9 @@ static int sink_set_port_cb(pa_sink *s,
|
2018-03-01 12:57:09 +00:00
|
|
|
s->set_volume(s);
|
|
|
|
}
|
|
|
|
|
2018-03-06 15:13:51 +00:00
|
|
|
+ if (data->suspend_when_unavailable)
|
|
|
|
+ pa_sink_suspend(s, p->available == PA_AVAILABLE_NO, PA_SUSPEND_UNAVAILABLE);
|
2018-03-01 12:57:09 +00:00
|
|
|
+
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-06 15:13:51 +00:00
|
|
|
@@ -2455,6 +2458,23 @@ pa_sink *pa_alsa_sink_new(pa_module *m,
|
2018-03-01 12:57:09 +00:00
|
|
|
if (profile_set)
|
|
|
|
pa_alsa_profile_set_free(profile_set);
|
|
|
|
|
|
|
|
+ /* Suspend if necessary. FIXME: It would be better to start suspended, but
|
|
|
|
+ * that would require some core changes. It's possible to set
|
|
|
|
+ * pa_sink_new_data.suspend_cause, but that has to be done before the
|
|
|
|
+ * pa_sink_new() call, and we know if we need to suspend only after the
|
|
|
|
+ * pa_sink_new() call when the initial port has been chosen. Calling
|
|
|
|
+ * pa_sink_suspend() between pa_sink_new() and pa_sink_put() would
|
|
|
|
+ * otherwise work, but currently pa_sink_suspend() will crash if
|
|
|
|
+ * pa_sink_put() hasn't been called. */
|
|
|
|
+ if (u->sink->active_port) {
|
|
|
|
+ pa_alsa_port_data *port_data;
|
|
|
|
+
|
|
|
|
+ port_data = PA_DEVICE_PORT_DATA(u->sink->active_port);
|
|
|
|
+
|
|
|
|
+ if (port_data->suspend_when_unavailable && u->sink->active_port->available == PA_AVAILABLE_NO)
|
|
|
|
+ pa_sink_suspend(u->sink, true, PA_SUSPEND_UNAVAILABLE);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
return u->sink;
|
|
|
|
|
|
|
|
fail:
|
|
|
|
--- a/src/modules/alsa/module-alsa-card.c
|
|
|
|
+++ b/src/modules/alsa/module-alsa-card.c
|
|
|
|
@@ -427,6 +427,22 @@ static int report_jack_state(snd_mixer_e
|
|
|
|
if (tp->avail == PA_AVAILABLE_NO)
|
|
|
|
pa_device_port_set_available(tp->port, tp->avail);
|
|
|
|
|
|
|
|
+ for (tp = tports; tp->port; tp++) {
|
|
|
|
+ pa_alsa_port_data *data;
|
|
|
|
+ pa_sink *sink;
|
|
|
|
+ uint32_t idx;
|
|
|
|
+
|
|
|
|
+ data = PA_DEVICE_PORT_DATA(tp->port);
|
|
|
|
+
|
|
|
|
+ if (!data->suspend_when_unavailable)
|
|
|
|
+ continue;
|
|
|
|
+
|
|
|
|
+ PA_IDXSET_FOREACH(sink, u->core->sinks, idx) {
|
|
|
|
+ if (sink->active_port == tp->port)
|
|
|
|
+ pa_sink_suspend(sink, tp->avail == PA_AVAILABLE_NO, PA_SUSPEND_UNAVAILABLE);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
/* Update profile availabilities. The logic could be improved; for now we
|
|
|
|
* only set obviously unavailable profiles (those that contain only
|
|
|
|
* unavailable ports) to PA_AVAILABLE_NO and all others to
|
|
|
|
@@ -837,6 +853,24 @@ int pa__init(pa_module *m) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
+ /* The Intel HDMI LPE driver needs some special handling. When the HDMI
|
|
|
|
+ * cable is not plugged in, trying to play audio doesn't work. Any written
|
|
|
|
+ * audio is immediately discarded and an underrun is reported, and that
|
|
|
|
+ * results in an infinite loop of "fill buffer, handle underrun". To work
|
|
|
|
+ * around this issue, the suspend_when_unavailable flag is used to stop
|
|
|
|
+ * playback when the HDMI cable is unplugged. */
|
|
|
|
+ if (pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) {
|
|
|
|
+ pa_device_port *port;
|
|
|
|
+ void *state;
|
|
|
|
+
|
|
|
|
+ PA_HASHMAP_FOREACH(port, data.ports, state) {
|
|
|
|
+ pa_alsa_port_data *port_data;
|
|
|
|
+
|
|
|
|
+ port_data = PA_DEVICE_PORT_DATA(port);
|
|
|
|
+ port_data->suspend_when_unavailable = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
u->card = pa_card_new(m->core, &data);
|
|
|
|
pa_card_new_data_done(&data);
|
|
|
|
|
|
|
|
--- a/src/pulsecore/core.h
|
|
|
|
+++ b/src/pulsecore/core.h
|
|
|
|
@@ -34,6 +34,7 @@ typedef enum pa_suspend_cause {
|
|
|
|
PA_SUSPEND_SESSION = 8, /* Used by module-hal for mark inactive sessions */
|
|
|
|
PA_SUSPEND_PASSTHROUGH = 16, /* Used to suspend monitor sources when the sink is in passthrough mode */
|
|
|
|
PA_SUSPEND_INTERNAL = 32, /* This is used for short period server-internal suspends, such as for sample rate updates */
|
|
|
|
+ PA_SUSPEND_UNAVAILABLE = 64, /* Used by device implementations that have to suspend when the device is unavailable */
|
|
|
|
PA_SUSPEND_ALL = 0xFFFF /* Magic cause that can be used to resume forcibly */
|
|
|
|
} pa_suspend_cause_t;
|
|
|
|
|