[pulseaudio-discuss] [PATCH v2 04/10] card, sink, source: Introduce pa_device_prototype

Tanu Kaskinen tanu.kaskinen at linux.intel.com
Mon Dec 9 10:31:41 PST 2013


The active profile of a card determines which sinks and sources of the
card are active, and a sink or source being active means here to
exist. When switching the profile, sinks and sources that are not part
of the new profile are deleted.

It's not nice that the sinks and sources are deleted like that,
because sometimes it would be useful to have a pointer to a sink that
is not currently active (not part of the active profile). For example,
when switching profiles, it would be useful to be able to check if the
devices of the old profile are also part of the new profile. This is
not possible, because the new profile doesn't have any kind of
references to the sinks and sources that it contains. It can't have
those references, because the sinks and sources don't necessarily
exist yet.

Ideally the life cycle of sinks and sources of a card would be the
same as the life cycle of the card, instead of creating and deleting
the sinks/sources whenever the card profile changes. This would
require some kind of an "inactive" mode for sinks and sources, which
would likely imply many changes all around the code base, and probably
in clients too. That's why I chose an easier solution in this patch:
introduce a new kind of object, "device prototype", that represents
both existing and "potential" sinks and sources. Card profiles can
reference the device prototypes, which makes it possible to solve the
aforementioned problem of figuring out during profile switches whether
the new profile contains the same sinks and sources as the old
profile.

This patch only adds the device prototype infrastructure, the
prototypes aren't yet used in card back end implementations.
---
 src/pulsecore/card.c   | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++
 src/pulsecore/card.h   | 37 ++++++++++++++++++++++++++++++++-
 src/pulsecore/sink.c   | 14 +++++++++++++
 src/pulsecore/sink.h   |  4 ++++
 src/pulsecore/source.c | 14 +++++++++++++
 src/pulsecore/source.h |  4 ++++
 6 files changed, 128 insertions(+), 1 deletion(-)

diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c
index 92804d0..3da7c9a 100644
--- a/src/pulsecore/card.c
+++ b/src/pulsecore/card.c
@@ -38,6 +38,27 @@
 
 #include "card.h"
 
+pa_device_prototype *pa_device_prototype_new(pa_direction_t direction) {
+    pa_device_prototype *prototype;
+
+    prototype = pa_xnew0(pa_device_prototype, 1);
+    prototype->direction = direction;
+
+    return prototype;
+}
+
+void pa_device_prototype_free(pa_device_prototype *prototype) {
+    pa_assert(prototype);
+
+    pa_xfree(prototype);
+}
+
+void pa_device_prototype_set_device(pa_device_prototype *prototype, void *device) {
+    pa_assert(prototype);
+
+    prototype->device = device;
+}
+
 pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra) {
     pa_card_profile *c;
 
@@ -49,6 +70,8 @@ pa_card_profile *pa_card_profile_new(const char *name, const char *description,
     c->description = pa_xstrdup(description);
 
     c->priority = 0;
+    c->sink_prototypes = pa_hashmap_new(NULL, NULL);
+    c->source_prototypes = pa_hashmap_new(NULL, NULL);
     c->n_sinks = c->n_sources = 0;
     c->max_sink_channels = c->max_source_channels = 0;
     c->available = PA_AVAILABLE_UNKNOWN;
@@ -59,6 +82,12 @@ pa_card_profile *pa_card_profile_new(const char *name, const char *description,
 void pa_card_profile_free(pa_card_profile *c) {
     pa_assert(c);
 
+    if (c->source_prototypes)
+        pa_hashmap_free(c->source_prototypes);
+
+    if (c->sink_prototypes)
+        pa_hashmap_free(c->sink_prototypes);
+
     pa_xfree(c->name);
     pa_xfree(c->description);
     pa_xfree(c);
@@ -89,6 +118,7 @@ pa_card_new_data* pa_card_new_data_init(pa_card_new_data *data) {
 
     memset(data, 0, sizeof(*data));
     data->proplist = pa_proplist_new();
+    data->device_prototypes = pa_hashmap_new_full(NULL, NULL, NULL, (pa_free_cb_t) pa_device_prototype_free);
     data->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_card_profile_free);
     data->recreate_devices_on_profile_switch = true;
     data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref);
@@ -103,10 +133,19 @@ void pa_card_new_data_set_name(pa_card_new_data *data, const char *name) {
 }
 
 void pa_card_new_data_add_profile(pa_card_new_data *data, pa_card_profile *profile) {
+    pa_device_prototype *prototype;
+    void *state;
+
     pa_assert(data);
     pa_assert(profile);
 
     pa_assert_se(pa_hashmap_put(data->profiles, profile->name, profile) >= 0);
+
+    PA_HASHMAP_FOREACH(prototype, profile->sink_prototypes, state)
+        pa_hashmap_put(data->device_prototypes, prototype, prototype);
+
+    PA_HASHMAP_FOREACH(prototype, profile->source_prototypes, state)
+        pa_hashmap_put(data->device_prototypes, prototype, prototype);
 }
 
 void pa_card_new_data_set_profile(pa_card_new_data *data, const char *profile) {
@@ -134,6 +173,9 @@ void pa_card_new_data_done(pa_card_new_data *data) {
     if (data->ports)
         pa_hashmap_free(data->ports);
 
+    if (data->device_prototypes)
+        pa_hashmap_free(data->device_prototypes);
+
     pa_xfree(data->name);
     pa_xfree(data->active_profile);
 }
@@ -172,6 +214,8 @@ pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {
     c->driver = pa_xstrdup(pa_path_get_filename(data->driver));
     c->module = data->module;
 
+    c->device_prototypes = data->device_prototypes;
+    data->device_prototypes = NULL;
     c->sinks = pa_idxset_new(NULL, NULL);
     c->sources = pa_idxset_new(NULL, NULL);
 
@@ -241,6 +285,9 @@ void pa_card_free(pa_card *c) {
     pa_assert(pa_idxset_isempty(c->sources));
     pa_idxset_free(c->sources, NULL);
 
+    if (c->device_prototypes)
+        pa_hashmap_free(c->device_prototypes);
+
     pa_hashmap_free(c->ports);
 
     if (c->profiles)
@@ -253,6 +300,9 @@ void pa_card_free(pa_card *c) {
 }
 
 void pa_card_add_profile(pa_card *c, pa_card_profile *profile) {
+    pa_device_prototype *prototype;
+    void *state;
+
     pa_assert(c);
     pa_assert(profile);
 
@@ -260,6 +310,12 @@ void pa_card_add_profile(pa_card *c, pa_card_profile *profile) {
     pa_assert_se(pa_hashmap_put(c->profiles, profile->name, profile) >= 0);
     profile->card = c;
 
+    PA_HASHMAP_FOREACH(prototype, profile->sink_prototypes, state)
+        pa_hashmap_put(c->device_prototypes, prototype, prototype);
+
+    PA_HASHMAP_FOREACH(prototype, profile->source_prototypes, state)
+        pa_hashmap_put(c->device_prototypes, prototype, prototype);
+
     pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CARD|PA_SUBSCRIPTION_EVENT_CHANGE, c->index);
 
     pa_hook_fire(&c->core->hooks[PA_CORE_HOOK_CARD_PROFILE_ADDED], profile);
diff --git a/src/pulsecore/card.h b/src/pulsecore/card.h
index 8fa0623..2f3dd06 100644
--- a/src/pulsecore/card.h
+++ b/src/pulsecore/card.h
@@ -37,6 +37,25 @@ typedef enum pa_available {
     PA_AVAILABLE_YES = 2,
 } pa_available_t;
 
+/* A card contains and owns a number of device prototypes. A device prototype
+ * represents one existing or potential sink or source of the card. A potential
+ * sink or source means a sink/source that is not part of the currently active
+ * card profile, but can be activated by selecting some other profile. One
+ * device prototype can be part of multiple profiles.
+ *
+ * When the represented sink or source exists, then the device pointer is set
+ * to point to that sink or source. When the represented sink or source does
+ * not exist, the device pointer is NULL. The device pointer is managed by the
+ * core, so card back end implementations don't need to worry about that. The
+ * back ends need to make sure to call pa_sink/source_new_data_set_prototype(),
+ * though, when creating sinks and sources that correspond to the prototypes
+ * (it's annoying that this is needed, bug filed:
+ * https://bugs.freedesktop.org/show_bug.cgi?id=72523 ). */
+typedef struct pa_device_prototype {
+    pa_direction_t direction;
+    void *device; /* Either pa_sink or pa_source, depending on the direction. */
+} pa_device_prototype;
+
 typedef struct pa_card_profile {
     pa_card *card;
     char *name;
@@ -45,7 +64,13 @@ typedef struct pa_card_profile {
     unsigned priority;
     pa_available_t available; /* PA_AVAILABLE_UNKNOWN, PA_AVAILABLE_NO or PA_AVAILABLE_YES */
 
-    /* We probably want to have different properties later on here */
+    /* When populating these hashmaps, note that you initially own the device
+     * prototype objects that you add, but once you call
+     * pa_card_new_data_add_profile(), the prototype ownership moves to
+     * pa_card_new_data (and later to pa_card). */
+    pa_hashmap *sink_prototypes; /* pa_device_prototype -> pa_device_prototype (hashmap-as-a-set) */
+    pa_hashmap *source_prototypes; /* pa_device_prototype -> pa_device_prototype (hashmap-as-a-set) */
+
     unsigned n_sinks;
     unsigned n_sources;
 
@@ -67,6 +92,7 @@ struct pa_card {
     pa_module *module;
     char *driver;
 
+    pa_hashmap *device_prototypes; /* pa_device_prototype -> pa_device_prototype (hashmap-as-a-set) */
     pa_idxset *sinks;
     pa_idxset *sources;
 
@@ -96,6 +122,10 @@ typedef struct pa_card_new_data {
     const char *driver;
     pa_module *module;
 
+    /* Don't populate this manually. pa_card_new_data_add_profile() will
+     * automatically add the prototypes from the added profile. */
+    pa_hashmap *device_prototypes; /* pa_device_prototype -> pa_device_prototype (hashmap-as-a-set) */
+
     pa_hashmap *profiles;
     char *active_profile;
     bool recreate_devices_on_profile_switch;
@@ -116,6 +146,11 @@ typedef struct {
     bool ret_valid;
 } pa_card_set_profile_hook_data;
 
+pa_device_prototype *pa_device_prototype_new(pa_direction_t direction);
+void pa_device_prototype_free(pa_device_prototype *prototype);
+
+void pa_device_prototype_set_device(pa_device_prototype *prototype, void *device);
+
 pa_card_profile *pa_card_profile_new(const char *name, const char *description, size_t extra);
 void pa_card_profile_free(pa_card_profile *c);
 
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 1783c95..8bd9544 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -139,6 +139,12 @@ void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port) {
     data->active_port = pa_xstrdup(port);
 }
 
+void pa_sink_new_data_set_prototype(pa_sink_new_data *data, pa_device_prototype *prototype) {
+    pa_assert(data);
+
+    data->prototype = prototype;
+}
+
 void pa_sink_new_data_done(pa_sink_new_data *data) {
     pa_assert(data);
 
@@ -313,6 +319,8 @@ pa_sink* pa_sink_new(
             &s->sample_spec,
             0);
 
+    s->prototype = data->prototype;
+
     s->thread_info.rtpoll = NULL;
     s->thread_info.inputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
                                                 (pa_free_cb_t) pa_sink_input_unref);
@@ -640,6 +648,9 @@ void pa_sink_put(pa_sink* s) {
          * place where he is supposed to place his changes.  */
         s->reference_volume = s->real_volume;
 
+    if (s->prototype)
+        pa_device_prototype_set_device(s->prototype, s);
+
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
     pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
@@ -694,6 +705,9 @@ void pa_sink_unlink(pa_sink* s) {
     if (s->node)
         pa_node_unlink(s->node);
 
+    if (s->prototype)
+        pa_device_prototype_set_device(s->prototype, NULL);
+
     if (s->state != PA_SINK_UNLINKED && s->name)
         pa_namereg_unregister(s->core, s->name);
 
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index 0a4b25e..b9f59c1 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -119,6 +119,7 @@ struct pa_sink {
     int64_t latency_offset;
 
     unsigned priority;
+    pa_device_prototype *prototype;
 
     /* Called when the main loop requests a state change. Called from
      * main loop context. If returns -1 the state change will be
@@ -360,6 +361,8 @@ typedef struct pa_sink_new_data {
     bool save_volume:1;
     bool save_muted:1;
 
+    pa_device_prototype *prototype;
+
     pa_node_new_data node_data;
 } pa_sink_new_data;
 
@@ -371,6 +374,7 @@ void pa_sink_new_data_set_alternate_sample_rate(pa_sink_new_data *data, const ui
 void pa_sink_new_data_set_volume(pa_sink_new_data *data, const pa_cvolume *volume);
 void pa_sink_new_data_set_muted(pa_sink_new_data *data, bool mute);
 void pa_sink_new_data_set_port(pa_sink_new_data *data, const char *port);
+void pa_sink_new_data_set_prototype(pa_sink_new_data *data, pa_device_prototype *prototype);
 void pa_sink_new_data_done(pa_sink_new_data *data);
 
 /*** To be called exclusively by the sink driver, from main context */
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 0fc3e81..25b7a78 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -136,6 +136,12 @@ void pa_source_new_data_set_port(pa_source_new_data *data, const char *port) {
     data->active_port = pa_xstrdup(port);
 }
 
+void pa_source_new_data_set_prototype(pa_source_new_data *data, pa_device_prototype *prototype) {
+    pa_assert(data);
+
+    data->prototype = prototype;
+}
+
 void pa_source_new_data_done(pa_source_new_data *data) {
     pa_assert(data);
 
@@ -307,6 +313,8 @@ pa_source* pa_source_new(
             &s->sample_spec,
             0);
 
+    s->prototype = data->prototype;
+
     s->thread_info.rtpoll = NULL;
     s->thread_info.outputs = pa_hashmap_new_full(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func, NULL,
                                                  (pa_free_cb_t) pa_source_output_unref);
@@ -602,6 +610,9 @@ void pa_source_put(pa_source *s) {
          * place where he is supposed to place his changes.  */
         s->reference_volume = s->real_volume;
 
+    if (s->prototype)
+        pa_device_prototype_set_device(s->prototype, s);
+
     s->thread_info.soft_volume = s->soft_volume;
     s->thread_info.soft_muted = s->muted;
     pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
@@ -643,6 +654,9 @@ void pa_source_unlink(pa_source *s) {
     if (s->node)
         pa_node_unlink(s->node);
 
+    if (s->prototype)
+        pa_device_prototype_set_device(s->prototype, NULL);
+
     if (s->state != PA_SOURCE_UNLINKED && s->name)
         pa_namereg_unregister(s->core, s->name);
 
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 3c43df8..8734202 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -119,6 +119,7 @@ struct pa_source {
     int64_t latency_offset;
 
     unsigned priority;
+    pa_device_prototype *prototype;
 
     /* Called when the main loop requests a state change. Called from
      * main loop context. If returns -1 the state change will be
@@ -298,6 +299,8 @@ typedef struct pa_source_new_data {
     bool save_volume:1;
     bool save_muted:1;
 
+    pa_device_prototype *prototype;
+
     pa_node_new_data node_data;
 } pa_source_new_data;
 
@@ -310,6 +313,7 @@ void pa_source_new_data_set_monitor_of(pa_source_new_data *data, pa_sink *monito
 void pa_source_new_data_set_volume(pa_source_new_data *data, const pa_cvolume *volume);
 void pa_source_new_data_set_muted(pa_source_new_data *data, bool mute);
 void pa_source_new_data_set_port(pa_source_new_data *data, const char *port);
+void pa_source_new_data_set_prototype(pa_source_new_data *data, pa_device_prototype *prototype);
 void pa_source_new_data_done(pa_source_new_data *data);
 
 /*** To be called exclusively by the source driver, from main context */
-- 
1.8.3.1



More information about the pulseaudio-discuss mailing list