[pulseaudio-commits] 3 commits - src/modules src/pulsecore

Tanu Kaskinen tanuk at kemper.freedesktop.org
Wed Jan 3 14:36:17 UTC 2018


 src/modules/alsa/alsa-mixer.h       |    1 
 src/modules/alsa/alsa-sink.c        |   22 ++++++++++++++
 src/modules/alsa/module-alsa-card.c |   34 ++++++++++++++++++++++
 src/pulsecore/cli-text.c            |   54 +++++-------------------------------
 src/pulsecore/core.c                |   41 +++++++++++++++++++++++++++
 src/pulsecore/core.h                |   13 ++++++++
 src/pulsecore/sink.c                |   27 ++++++++++++++++--
 src/pulsecore/sink.h                |    2 +
 src/pulsecore/source.c              |   27 ++++++++++++++++--
 src/pulsecore/source.h              |    2 +
 10 files changed, 172 insertions(+), 51 deletions(-)

New commits:
commit f17644318194f213e30bc396e041680154b217d4
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Dec 28 12:09:19 2017 +0200

    sink, source: improve suspend cause logging
    
    Previously the suspend cause was logged as a hexadecimal number, now
    it's logged as a human-friendly string.
    
    Also, the command line interface handled only a subset of causes when
    printing them, now all suspend causes are printed.

diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index 794f51c1..474f3367 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -212,6 +212,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             v[PA_VOLUME_SNPRINT_VERBOSE_MAX],
             cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
         const char *cmn;
+        char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
 
         cmn = pa_channel_map_to_pretty_name(&sink->channel_map);
 
@@ -222,7 +223,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             "\tdriver: <%s>\n"
             "\tflags: %s%s%s%s%s%s%s%s\n"
             "\tstate: %s\n"
-            "\tsuspend cause: %s%s%s%s\n"
+            "\tsuspend cause: %s\n"
             "\tpriority: %u\n"
             "\tvolume: %s\n"
             "\t        balance %0.2f\n"
@@ -250,10 +251,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "",
             sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
             pa_sink_state_to_string(pa_sink_get_state(sink)),
-            sink->suspend_cause & PA_SUSPEND_USER ? "USER " : "",
-            sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "",
-            sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "",
-            sink->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "",
+            pa_suspend_cause_to_string(sink->suspend_cause, suspend_cause_buf),
             sink->priority,
             pa_cvolume_snprint_verbose(cv,
                                        sizeof(cv),
@@ -328,6 +326,7 @@ char *pa_source_list_to_string(pa_core *c) {
             v[PA_VOLUME_SNPRINT_VERBOSE_MAX],
             cm[PA_CHANNEL_MAP_SNPRINT_MAX], *t;
         const char *cmn;
+        char suspend_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
 
         cmn = pa_channel_map_to_pretty_name(&source->channel_map);
 
@@ -338,7 +337,7 @@ char *pa_source_list_to_string(pa_core *c) {
             "\tdriver: <%s>\n"
             "\tflags: %s%s%s%s%s%s%s\n"
             "\tstate: %s\n"
-            "\tsuspend cause: %s%s%s%s\n"
+            "\tsuspend cause: %s\n"
             "\tpriority: %u\n"
             "\tvolume: %s\n"
             "\t        balance %0.2f\n"
@@ -363,10 +362,7 @@ char *pa_source_list_to_string(pa_core *c) {
             source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
             source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
             pa_source_state_to_string(pa_source_get_state(source)),
-            source->suspend_cause & PA_SUSPEND_USER ? "USER " : "",
-            source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "",
-            source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "",
-            source->suspend_cause & PA_SUSPEND_SESSION ? "SESSION" : "",
+            pa_suspend_cause_to_string(source->suspend_cause, suspend_cause_buf),
             source->priority,
             pa_cvolume_snprint_verbose(cv,
                                        sizeof(cv),
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index 454c5668..79abbc04 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -502,3 +502,44 @@ void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec) {
 
     c->mainloop->time_restart(e, pa_timeval_rtstore(&tv, usec, true));
 }
+
+/* Helper macro to reduce repetition in pa_suspend_cause_to_string().
+ * Parameters:
+ *   char *p: the current position in the write buffer
+ *   bool first: is cause_to_check the first cause to be written?
+ *   pa_suspend_cause_t cause_bitfield: the causes given to pa_suspend_cause_to_string()
+ *   pa_suspend_cause_t cause_to_check: the cause whose presence in cause_bitfield is to be checked
+ */
+#define CHECK_CAUSE(p, first, cause_bitfield, cause_to_check) \
+    if (cause_bitfield & PA_SUSPEND_##cause_to_check) {       \
+        size_t len = sizeof(#cause_to_check) - 1;             \
+        if (!first) {                                         \
+            *p = '|';                                         \
+            p++;                                              \
+        }                                                     \
+        first = false;                                        \
+        memcpy(p, #cause_to_check, len);                      \
+        p += len;                                             \
+    }
+
+const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause_bitfield, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]) {
+    char *p = buf;
+    bool first = true;
+
+    CHECK_CAUSE(p, first, cause_bitfield, USER);
+    CHECK_CAUSE(p, first, cause_bitfield, APPLICATION);
+    CHECK_CAUSE(p, first, cause_bitfield, IDLE);
+    CHECK_CAUSE(p, first, cause_bitfield, SESSION);
+    CHECK_CAUSE(p, first, cause_bitfield, PASSTHROUGH);
+    CHECK_CAUSE(p, first, cause_bitfield, INTERNAL);
+    CHECK_CAUSE(p, first, cause_bitfield, UNAVAILABLE);
+
+    if (p == buf) {
+        memcpy(p, "(none)", 6);
+        p += 6;
+    }
+
+    *p = 0;
+
+    return buf;
+}
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index afe6c25e..213964ce 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -26,7 +26,10 @@
 #include <pulsecore/cpu.h>
 
 /* This is a bitmask that encodes the cause why a sink/source is
- * suspended. */
+ * suspended.
+ *
+ * When adding new causes, remember to update pa_suspend_cause_to_string() and
+ * PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE! */
 typedef enum pa_suspend_cause {
     PA_SUSPEND_USER = 1,         /* Exposed to the user via some protocol */
     PA_SUSPEND_APPLICATION = 2,  /* Used by the device reservation logic */
@@ -266,4 +269,11 @@ void pa_core_maybe_vacuum(pa_core *c);
 pa_time_event* pa_core_rttime_new(pa_core *c, pa_usec_t usec, pa_time_event_cb_t cb, void *userdata);
 void pa_core_rttime_restart(pa_core *c, pa_time_event *e, pa_usec_t usec);
 
+static const size_t PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE =
+    sizeof("USER|APPLICATION|IDLE|SESSION|PASSTHROUGH|INTERNAL|UNAVAILABLE");
+
+/* Converts the given suspend cause to a string. The string is written to the
+ * provided buffer. The same buffer is the return value of this function. */
+const char *pa_suspend_cause_to_string(pa_suspend_cause_t cause, char buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE]);
+
 #endif
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 35f189ba..7f5c37f4 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -840,11 +840,17 @@ void pa_sink_set_mixer_dirty(pa_sink *s, bool is_dirty) {
 
 /* Called from main context */
 int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
+    pa_suspend_cause_t old_cause;
+    char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+    char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
     pa_sink_assert_ref(s);
     pa_assert_ctl_context();
     pa_assert(PA_SINK_IS_LINKED(s->state));
     pa_assert(cause != 0);
 
+    old_cause = s->suspend_cause;
+
     if (suspend) {
         s->suspend_cause |= cause;
         s->monitor_source->suspend_cause |= cause;
@@ -853,6 +859,11 @@ int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
         s->monitor_source->suspend_cause &= ~cause;
     }
 
+    if (s->suspend_cause != old_cause) {
+        pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(old_cause, old_cause_buf),
+                     pa_suspend_cause_to_string(s->suspend_cause, new_cause_buf));
+    }
+
     if (!(s->suspend_cause & PA_SUSPEND_SESSION) && (pa_atomic_load(&s->mixer_dirty) != 0)) {
         /* This might look racy but isn't: If somebody sets mixer_dirty exactly here,
            it'll be handled just fine. */
@@ -877,8 +888,6 @@ int pa_sink_suspend(pa_sink *s, bool suspend, pa_suspend_cause_t cause) {
     if ((pa_sink_get_state(s) == PA_SINK_SUSPENDED) == !!s->suspend_cause)
         return 0;
 
-    pa_log_debug("Suspend cause of sink %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming");
-
     if (s->suspend_cause)
         return sink_set_state(s, PA_SINK_SUSPENDED);
     else
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index a0ab8e96..2a6b1f11 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -761,6 +761,10 @@ void pa_source_set_mixer_dirty(pa_source *s, bool is_dirty) {
 
 /* Called from main context */
 int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) {
+    pa_suspend_cause_t old_cause;
+    char old_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+    char new_cause_buf[PA_SUSPEND_CAUSE_TO_STRING_BUF_SIZE];
+
     pa_source_assert_ref(s);
     pa_assert_ctl_context();
     pa_assert(PA_SOURCE_IS_LINKED(s->state));
@@ -769,11 +773,18 @@ int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) {
     if (s->monitor_of && cause != PA_SUSPEND_PASSTHROUGH)
         return -PA_ERR_NOTSUPPORTED;
 
+    old_cause = s->suspend_cause;
+
     if (suspend)
         s->suspend_cause |= cause;
     else
         s->suspend_cause &= ~cause;
 
+    if (s->suspend_cause != old_cause) {
+        pa_log_debug("%s: suspend_cause: %s -> %s", s->name, pa_suspend_cause_to_string(old_cause, old_cause_buf),
+                     pa_suspend_cause_to_string(s->suspend_cause, new_cause_buf));
+    }
+
     if (!(s->suspend_cause & PA_SUSPEND_SESSION) && (pa_atomic_load(&s->mixer_dirty) != 0)) {
         /* This might look racy but isn't: If somebody sets mixer_dirty exactly here,
            it'll be handled just fine. */
@@ -798,8 +809,6 @@ int pa_source_suspend(pa_source *s, bool suspend, pa_suspend_cause_t cause) {
     if ((pa_source_get_state(s) == PA_SOURCE_SUSPENDED) == !!s->suspend_cause)
         return 0;
 
-    pa_log_debug("Suspend cause of source %s is 0x%04x, %s", s->name, s->suspend_cause, s->suspend_cause ? "suspending" : "resuming");
-
     if (s->suspend_cause)
         return source_set_state(s, PA_SOURCE_SUSPENDED);
     else

commit eeee5664fad6d08c2757bc741db0955b0387b3b7
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Dec 28 12:09:18 2017 +0200

    sink, source: improve state change logging
    
    Now the old and new state is logged every time when the sink or source
    state changes.

diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c
index ded82f6e..794f51c1 100644
--- a/src/pulsecore/cli-text.c
+++ b/src/pulsecore/cli-text.c
@@ -196,40 +196,6 @@ char *pa_card_list_to_string(pa_core *c) {
     return pa_strbuf_to_string_free(s);
 }
 
-static const char *sink_state_to_string(pa_sink_state_t state) {
-    switch (state) {
-        case PA_SINK_INIT:
-            return "INIT";
-        case PA_SINK_RUNNING:
-            return "RUNNING";
-        case PA_SINK_SUSPENDED:
-            return "SUSPENDED";
-        case PA_SINK_IDLE:
-            return "IDLE";
-        case PA_SINK_UNLINKED:
-            return "UNLINKED";
-        default:
-            return "INVALID";
-    }
-}
-
-static const char *source_state_to_string(pa_source_state_t state) {
-    switch (state) {
-        case PA_SOURCE_INIT:
-            return "INIT";
-        case PA_SOURCE_RUNNING:
-            return "RUNNING";
-        case PA_SOURCE_SUSPENDED:
-            return "SUSPENDED";
-        case PA_SOURCE_IDLE:
-            return "IDLE";
-        case PA_SOURCE_UNLINKED:
-            return "UNLINKED";
-        default:
-            return "INVALID";
-    }
-}
-
 char *pa_sink_list_to_string(pa_core *c) {
     pa_strbuf *s;
     pa_sink *sink;
@@ -283,7 +249,7 @@ char *pa_sink_list_to_string(pa_core *c) {
             sink->flags & PA_SINK_LATENCY ? "LATENCY " : "",
             sink->flags & PA_SINK_FLAT_VOLUME ? "FLAT_VOLUME " : "",
             sink->flags & PA_SINK_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
-            sink_state_to_string(pa_sink_get_state(sink)),
+            pa_sink_state_to_string(pa_sink_get_state(sink)),
             sink->suspend_cause & PA_SUSPEND_USER ? "USER " : "",
             sink->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "",
             sink->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "",
@@ -396,7 +362,7 @@ char *pa_source_list_to_string(pa_core *c) {
             source->flags & PA_SOURCE_DECIBEL_VOLUME ? "DECIBEL_VOLUME " : "",
             source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
             source->flags & PA_SOURCE_DYNAMIC_LATENCY ? "DYNAMIC_LATENCY" : "",
-            source_state_to_string(pa_source_get_state(source)),
+            pa_source_state_to_string(pa_source_get_state(source)),
             source->suspend_cause & PA_SUSPEND_USER ? "USER " : "",
             source->suspend_cause & PA_SUSPEND_APPLICATION ? "APPLICATION " : "",
             source->suspend_cause & PA_SUSPEND_IDLE ? "IDLE " : "",
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 39bf18f1..35f189ba 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -427,6 +427,7 @@ static int sink_set_state(pa_sink *s, pa_sink_state_t state) {
             return ret;
         }
 
+    pa_log_debug("%s: state: %s -> %s", s->name, pa_sink_state_to_string(s->state), pa_sink_state_to_string(state));
     s->state = state;
 
     if (state != PA_SINK_UNLINKED) { /* if we enter UNLINKED state pa_sink_unlink() will fire the appropriate events */
@@ -2462,6 +2463,19 @@ unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_sourc
     return ret;
 }
 
+const char *pa_sink_state_to_string(pa_sink_state_t state) {
+    switch (state) {
+        case PA_SINK_INIT:          return "INIT";
+        case PA_SINK_IDLE:          return "IDLE";
+        case PA_SINK_RUNNING:       return "RUNNING";
+        case PA_SINK_SUSPENDED:     return "SUSPENDED";
+        case PA_SINK_UNLINKED:      return "UNLINKED";
+        case PA_SINK_INVALID_STATE: return "INVALID_STATE";
+    }
+
+    pa_assert_not_reached();
+}
+
 /* Called from the IO thread */
 static void sync_input_volumes_within_thread(pa_sink *s) {
     pa_sink_input *i;
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index cae5e517..3fb23012 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -476,6 +476,8 @@ unsigned pa_sink_check_suspend(pa_sink *s, pa_sink_input *ignore_input, pa_sourc
 
 #define pa_sink_get_state(s) ((s)->state)
 
+const char *pa_sink_state_to_string(pa_sink_state_t state);
+
 /* Moves all inputs away, and stores them in pa_queue */
 pa_queue *pa_sink_move_all_start(pa_sink *s, pa_queue *q);
 void pa_sink_move_all_finish(pa_sink *s, pa_queue *q, bool save);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 6099c10d..a0ab8e96 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -381,6 +381,7 @@ static int source_set_state(pa_source *s, pa_source_state_t state) {
             return ret;
         }
 
+    pa_log_debug("%s: state: %s -> %s", s->name, pa_source_state_to_string(s->state), pa_source_state_to_string(state));
     s->state = state;
 
     if (state != PA_SOURCE_UNLINKED) { /* if we enter UNLINKED state pa_source_unlink() will fire the appropriate events */
@@ -2014,6 +2015,19 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore) {
     return ret;
 }
 
+const char *pa_source_state_to_string(pa_source_state_t state) {
+    switch (state) {
+        case PA_SOURCE_INIT:          return "INIT";
+        case PA_SOURCE_IDLE:          return "IDLE";
+        case PA_SOURCE_RUNNING:       return "RUNNING";
+        case PA_SOURCE_SUSPENDED:     return "SUSPENDED";
+        case PA_SOURCE_UNLINKED:      return "UNLINKED";
+        case PA_SOURCE_INVALID_STATE: return "INVALID_STATE";
+    }
+
+    pa_assert_not_reached();
+}
+
 /* Called from the IO thread */
 static void sync_output_volumes_within_thread(pa_source *s) {
     pa_source_output *o;
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 7fb4a79e..75ce241f 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -407,6 +407,8 @@ unsigned pa_source_check_suspend(pa_source *s, pa_source_output *ignore);
 
 #define pa_source_get_state(s) ((pa_source_state_t) (s)->state)
 
+const char *pa_source_state_to_string(pa_source_state_t state);
+
 /* Moves all inputs away, and stores them in pa_queue */
 pa_queue *pa_source_move_all_start(pa_source *s, pa_queue *q);
 void pa_source_move_all_finish(pa_source *s, pa_queue *q, bool save);

commit 94fc586c011537536cfb434376354699357af785
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Dec 28 12:09:17 2017 +0200

    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

diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 4ebf1922..3577f435 100644
--- 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_alsa_mixer_pdata *pd, snd_mixer_t *mixer,
 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);
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 7936cfac..a80caab2 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1527,6 +1527,11 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) {
             s->set_volume(s);
     }
 
+    if (data->suspend_when_unavailable && p->available == PA_AVAILABLE_NO)
+        pa_sink_suspend(s, true, PA_SUSPEND_UNAVAILABLE);
+    else
+        pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE);
+
     return 0;
 }
 
@@ -2460,6 +2465,23 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
     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:
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 804b4f87..b193d40c 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -426,6 +426,22 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) {
         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
@@ -836,6 +852,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);
 
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 79a095d2..afe6c25e 100644
--- 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;
 



More information about the pulseaudio-commits mailing list