plugnplay: card detection
authorJanos Kovacs <jankovac503@gmail.com>
Fri, 18 May 2012 08:50:00 +0000 (11:50 +0300)
committerJanos Kovacs <jankovac503@gmail.com>
Fri, 18 May 2012 08:50:00 +0000 (11:50 +0300)
src/Makefile.am
src/card-ext.c
src/dbusif.c
src/discover.c
src/discover.h
src/node.c [new file with mode: 0644]
src/node.h [new file with mode: 0644]

index c0001fa..26004fb 100644 (file)
@@ -13,6 +13,7 @@ module_genivi_mir_la_SOURCES = \
                        module-ext.c \
                        classify.c \
                        discover.c \
+                       node.c \
                        policy-group.c \
                        context.c \
                        audiomgr.c \
index 7cb9c79..cb6894f 100644 (file)
@@ -187,7 +187,7 @@ static void handle_new_card(struct userdata *u, struct pa_card *card)
 
         pa_policy_context_register(u, pa_policy_object_card, name, card);
 
-        pa_discover_new_card(u, card);
+        pa_discover_add_card(u, card);
 
         if (len <= 0)
             pa_log_debug("new card '%s' (idx=%d)", name, idx);
@@ -227,6 +227,8 @@ static void handle_removed_card(struct userdata *u, struct pa_card *card)
 
         pa_policy_context_unregister(u, pa_policy_object_card, name, card,idx);
 
+        pa_discover_remove_card(u, card);
+
         if (len <= 0)
             pa_log_debug("remove card '%s' (idx=%d)", name, idx);
         else {
index 50fd76f..6f5469b 100644 (file)
@@ -947,7 +947,7 @@ static DBusHandlerResult audiomgr_method_handler(DBusConnection *conn,
     struct dispatch         *d;
     const char              *name;
     method_t                 method;
-    uint32_t                 serial;
+    //uint32_t                 serial;
     dbus_int16_t             errcod;
     DBusMessage             *reply;
     pa_bool_t                success;
@@ -958,8 +958,8 @@ static DBusHandlerResult audiomgr_method_handler(DBusConnection *conn,
 
     if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL) {
 
-        name   = dbus_message_get_member(msg);
-        serial = dbus_message_get_serial(msg);
+        name = dbus_message_get_member(msg);
+        // serial = dbus_message_get_serial(msg);
 
         pa_assert(name);
 
index 1eff7e9..edb4aad 100644 (file)
 #include <pulsecore/source-output.h>
 #include <pulsecore/strbuf.h>
 
+
 #include "discover.h"
+#include "node.h"
+#include "card-ext.h"
+
+#define MAX_CARD_TARGET   4
+#define MAX_NAME_LENGTH   256
+
+
+static void add_alsa_card(struct userdata *, pa_card *);
+static void add_bluetooth_card(struct userdata *, pa_card *);
+
+static void handle_add_udev_loaded_card(struct userdata *, pa_card *,
+                                        mir_node *, char *);
+static void handle_card_ports(struct userdata *, mir_node *,
+                              pa_card *, pa_card_profile *);
+
+static mir_node *create_node(struct userdata *, mir_node *, pa_bool_t *);
+static void destroy_node(struct userdata *, mir_node *);
+
+static void parse_profile_name(pa_card_profile *,
+                               char **, char **, char *, int);
+
+static void classify_node_by_card(mir_node *, pa_card *,
+                                  pa_card_profile *, pa_device_port *);
+static void guess_node_type_and_name(mir_node *, const char *, const char *);
+
 
-struct pa_discover {
-    pa_hashmap *targets;
-};
 
 struct pa_discover *pa_discover_init(struct userdata *u)
 {
-    struct pa_discover *dsc = pa_xnew0(struct pa_discover, 1);
+    pa_discover *dsc = pa_xnew0(pa_discover, 1);
 
-    dsc->targets = pa_hashmap_new(pa_idxset_string_hash_func,
-                                  pa_idxset_string_compare_func);
+    dsc->chmin = 1;
+    dsc->chmax = 2;
+    dsc->nodes.pahash = pa_hashmap_new(pa_idxset_string_hash_func,
+                                       pa_idxset_string_compare_func);
     return dsc;
 }
 
-void pa_discover_new_card(struct userdata *u, struct pa_card *card)
+void pa_discover_add_card(struct userdata *u, pa_card *card)
+{
+    const char *bus;
+
+    pa_assert(u);
+    pa_assert(card);
+
+    if ((bus = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS)) == NULL) {
+        pa_log_debug("ignoring card '%s' due to lack of %s property",
+                     pa_card_ext_get_name(card), PA_PROP_DEVICE_BUS);
+        return;
+    }
+
+    if (pa_streq(bus, "pci") || pa_streq(bus, "usb")) {
+        add_alsa_card(u, card);
+        return;
+    }
+    else if (pa_streq(bus, "bluetooth")) {
+        add_bluetooth_card(u, card);
+        return;
+    }
+
+    pa_log_debug("ignoring card '%s' due to unsupported bus type '%s'",
+                 pa_card_ext_get_name(card), bus);
+}
+
+void pa_discover_remove_card(struct userdata *u, pa_card *card)
+{
+    pa_discover *discover;
+    mir_node    *node;
+    void        *state;
+
+    pa_assert(u);
+    pa_assert(card);
+    pa_assert_se((discover = u->discover));
+
+    PA_HASHMAP_FOREACH(node, discover->nodes.pahash, state) {
+        if (node->implement == mir_device &&
+            node->pacard.index == card->index)
+        {
+            destroy_node(u, node);
+        }
+    }
+}
+
+static void add_alsa_card(struct userdata *u, pa_card *card)
+{
+    pa_discover     *discover;
+    mir_node         data;
+    const char      *udd;
+    char            *cnam;
+    char            *cid;
+
+    pa_assert_se((discover = u->discover));
+
+    memset(&data, 0, sizeof(data));
+    data.visible = TRUE;
+    data.amid = AM_ID_INVALID;
+    data.implement = mir_device;
+    data.paidx = PA_IDXSET_INVALID;
+
+    cnam = pa_card_ext_get_name(card);
+    udd  = pa_proplist_gets(card->proplist, "module-udev-detect.discovered");
+
+    if (udd && pa_streq(udd, "1")) {
+        /* udev loaded alsa card */
+        if (!strncmp(cnam, "alsa_card.", 10)) {
+            cid = cnam + 10;
+            handle_add_udev_loaded_card(u, card, &data, cid);
+            return;
+        }
+    }
+    else {
+        /* manually loaded pci or usb card */
+    }
+
+    pa_log_debug("ignoring unrecognized pci card '%s'", cnam);
+}
+
+static void add_bluetooth_card(struct userdata *u, pa_card *card)
+{
+    pa_discover     *discover;
+    pa_card_profile *prof;
+    mir_node         data;
+    char            *cnam;
+    char            *cid;
+    const char      *cdescr;
+    void            *state;
+    char             paname[MAX_NAME_LENGTH+1];
+    char             amname[MAX_NAME_LENGTH+1];
+    char             key[MAX_NAME_LENGTH+1];
+
+    pa_assert_se((discover = u->discover));
+
+    cdescr = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_DESCRIPTION);
+
+
+    memset(paname, 0, sizeof(paname));
+    memset(amname, 0, sizeof(amname));
+    memset(key   , 0, sizeof(key)   );
+
+    memset(&data, 0, sizeof(data));
+    data.key = key;
+    data.visible = TRUE;
+    data.amid = AM_ID_INVALID;
+    data.implement = mir_device;
+    data.paidx = PA_IDXSET_INVALID;
+    data.paname = paname;
+    data.amname = amname;
+    data.amdescr = (char *)cdescr;
+    data.pacard.index = card->index;
+
+    cnam = pa_card_ext_get_name(card);
+
+    if (!strncmp(cnam, "bluez_card.", 11)) { 
+        cid = cnam + 11;
+
+        PA_HASHMAP_FOREACH(prof, card->profiles, state) {
+            data.available = (prof == card->active_profile);
+            data.pacard.profile = prof->name;
+
+            if (prof->n_sinks > 0) {
+                data.direction = mir_output;
+                data.channels = prof->max_sink_channels; 
+                data.amname = amname;
+                amname[0] = '\0';
+                snprintf(paname, sizeof(paname), "bluez_sink.%s", cid);
+                snprintf(key, sizeof(key), "%s@%s", paname, prof->name);
+                classify_node_by_card(&data, card, prof, NULL);
+                create_node(u, &data, NULL);
+            }
+
+            if (prof->n_sources > 0) {
+                data.direction = mir_input;
+                data.channels = prof->max_source_channels; 
+                data.amname = amname;
+                amname[0] = '\0';
+                snprintf(paname, sizeof(paname), "bluez_source.%s", cid);
+                snprintf(key, sizeof(key), "%s@%s", paname, prof->name);
+                classify_node_by_card(&data, card, prof, NULL);
+                create_node(u, &data, NULL);
+            }
+        }
+    }
+}
+
+static void handle_add_udev_loaded_card(struct userdata *u, pa_card *card,
+                                        mir_node *data, char *cardid)
+{
+    pa_discover      *discover;
+    pa_card_profile  *prof;
+    pa_card_profile  *active;
+    void             *state;
+    const char       *alsanam;
+    char             *sid;
+    char             *sinks[MAX_CARD_TARGET+1];
+    char             *sources[MAX_CARD_TARGET+1];
+    char              buf[MAX_NAME_LENGTH+1];
+    char              paname[MAX_NAME_LENGTH+1];
+    char              amname[MAX_NAME_LENGTH+1];
+    int               i;
+
+    pa_assert(card);
+    pa_assert(card->profiles);
+    pa_assert_se((discover = u->discover));
+
+    alsanam = pa_proplist_gets(card->proplist, "alsa.card_name");
+
+    memset(amname, 0, sizeof(amname));
+
+    data->paname  = paname;
+    data->amname  = amname;
+    data->amdescr = (char *)alsanam;
+
+    data->pacard.index = card->index;
+
+    active = card->active_profile;
+
+    PA_HASHMAP_FOREACH(prof, card->profiles, state) {
+        if (active && prof != active)
+            continue;
+
+        if (!prof->n_sinks && !prof->n_sources)
+            continue;
+
+        if (prof->n_sinks && 
+            (prof->max_sink_channels < discover->chmin ||
+             prof->max_sink_channels  > discover->chmax  ))
+            continue;
+        
+        if (prof->n_sources &&
+            (prof->max_source_channels <  discover->chmin ||
+             prof->max_source_channels >  discover->chmax   ))
+            continue;
+
+        data->pacard.profile = prof->name;
+
+        parse_profile_name(prof, sinks,sources, buf,sizeof(buf));
+        
+        data->direction = mir_output;
+        data->channels = prof->max_sink_channels;
+        for (i = 0;  (sid = sinks[i]);  i++) {
+            snprintf(paname, sizeof(paname), "alsa_output.%s.%s", cardid, sid);
+            handle_card_ports(u, data, card, prof);
+        }
+
+        data->direction = mir_input;
+        data->channels = prof->max_source_channels;
+        for (i = 0;  (sid = sources[i]);  i++) {
+            snprintf(paname, sizeof(paname), "alsa_input.%s.%s", cardid, sid);
+            handle_card_ports(u, data, card, prof);
+        }        
+    }
+}
+
+
+static void handle_card_ports(struct userdata *u, mir_node *data,
+                              pa_card *card, pa_card_profile *prof)
+{
+    mir_node       *node = NULL;
+    pa_bool_t       have_ports = FALSE;
+    char           *amname = data->amname;
+    pa_device_port *port;
+    void           *state;
+    pa_bool_t       created;
+    char            key[MAX_NAME_LENGTH+1];
+
+    pa_assert(u);
+    pa_assert(data);
+    pa_assert(card);
+    pa_assert(prof);
+
+    if (card->ports) {        
+        PA_HASHMAP_FOREACH(port, card->ports, state) {
+            /*
+             * if this port did not belong to any profile 
+             * (ie. prof->profiles == NULL) we assume that this port
+             * does works with all the profiles
+             */
+            if (port->profiles && pa_hashmap_get(port->profiles, prof->name) &&
+                ((port->is_input && data->direction == mir_input)||
+                 (port->is_output && data->direction == mir_output)))
+            {
+                have_ports = TRUE;
+
+                /* make constrain if node != NULL and add node to it */
+
+                amname[0] = '\0';
+                snprintf(key, sizeof(key), "%s@%s", data->paname, port->name);
+
+                data->key       = key;
+                data->available = (port->available == PA_PORT_AVAILABLE_YES);
+                data->type      = 0;
+                data->amname    = amname;
+                data->paport    = port->name;
+
+                classify_node_by_card(data, card, prof, port);
+
+                node = create_node(u, data, &created);
+
+                /* if constrain != NULL add the node to it */
+            }
+        }
+    }
+    
+    if (!have_ports) {
+        data->key = data->paname;
+        data->available = TRUE;
+        classify_node_by_card(data, card, prof, NULL);
+        create_node(u, data, NULL);
+    }
+
+    data->amname = amname;
+    *amname = '\0';
+}
+
+
+static mir_node *create_node(struct userdata *u, mir_node *data, 
+                             pa_bool_t *created_ret)
+{
+    pa_discover *discover;
+    mir_node    *node;
+    pa_bool_t    created;
+    char         buf[2048];
+
+    pa_assert(u);
+    pa_assert(data);
+    pa_assert(data->key);
+    pa_assert(data->paname);
+    pa_assert_se((discover = u->discover));
+    
+    if ((node = pa_hashmap_get(discover->nodes.pahash, data->key)))
+        created = FALSE;
+    else {
+        created = TRUE;
+
+        node = mir_node_create(u, data);
+        pa_hashmap_put(discover->nodes.pahash, node->key, node);
+
+        mir_node_print(node, buf, sizeof(buf));
+        pa_log_debug("new node:\n%s", buf);
+    }
+
+    if (created_ret)
+        *created_ret = created;
+    
+    return node;
+}
+
+static void destroy_node(struct userdata *u, mir_node *node)
+{
+    pa_discover *discover;
+    mir_node    *removed;
+
+    pa_assert(u);
+    pa_assert_se((discover = u->discover));
+
+    if (node) {
+        removed = pa_hashmap_remove(discover->nodes.pahash, node->key);
+
+        if (node != removed) {
+            if (removed) {
+                pa_log("%s: confused with data structures: key mismatch. "
+                       " attempted to destroy '%s'; actually destroyed '%s'",
+                       __FILE__, node->key, removed->key);
+            }
+            else {
+                pa_log("%s: confused with data structures: node '%s' "
+                       "is not in the has table", __FILE__, node->key);
+            }
+            return;
+        }
+    }
+
+    pa_log_debug("destroying node: %s / '%s'", node->key, node->amname);
+
+    mir_node_destroy(u, node);
+}
+
+
+
+static char *get_name(char **string_ptr, int offs)
+{
+    char c, *name, *end;
+
+    name = *string_ptr + offs;
+
+    for (end = name;  (c = *end);   end++) {
+        if (c == '+') {
+            *end++ = '\0';
+            break;
+        }
+    }
+
+    *string_ptr = end;
+
+    return name;
+} 
+
+static void parse_profile_name(pa_card_profile *prof,
+                               char           **sinks,
+                               char           **sources,
+                               char            *buf,
+                               int              buflen)
+{
+    char *p = buf;
+    int   i = 0;
+    int   j = 0;
+
+    pa_assert(prof->name);
+
+    strncpy(buf, prof->name, buflen);
+    buf[buflen-1] = '\0';
+
+    memset(sinks, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
+    memset(sources, 0, sizeof(char *) * (MAX_CARD_TARGET+1));
+
+    do {
+        if (!strncmp(p, "output:", 7)) {
+            if (i >= MAX_CARD_TARGET) {
+                pa_log_debug("number of outputs exeeds the maximum %d in "
+                             "profile name '%s'", MAX_CARD_TARGET, prof->name);
+                return;
+            }
+            sinks[i++] = get_name(&p, 7);
+        } 
+        else if (!strncmp(p, "input:", 6)) {
+            if (j >= MAX_CARD_TARGET) {
+                pa_log_debug("number of inputs exeeds the maximum %d in "
+                             "profile name '%s'", MAX_CARD_TARGET, prof->name);
+                return;
+            }
+            sources[j++] = get_name(&p, 6);            
+        }
+        else {
+            pa_log("%s: failed to parse profile name '%s'",
+                   __FILE__, prof->name);
+            return;
+        }
+    } while (*p);
+}
+
+
+static void classify_node_by_card(mir_node *data, pa_card *card,
+                                  pa_card_profile *prof, pa_device_port *port)
+{
+    const char *bus;
+    const char *form;
+    
+    pa_assert(data);
+    pa_assert(card);
+
+    bus  = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_BUS);
+    form = pa_proplist_gets(card->proplist, PA_PROP_DEVICE_FORM_FACTOR);
+
+    if (form) {
+        if (!strcasecmp(form, "internal")) {
+            data->location = mir_external;
+            if (port && !strcasecmp(bus, "pci"))
+                guess_node_type_and_name(data, port->name, port->description);
+        }
+        else if (!strcasecmp(form, "speaker") || !strcasecmp(form, "car")) {
+            if (data->direction == mir_output) {
+                data->location = mir_internal;
+                data->type = mir_speakers;
+            }
+        }
+        else if (!strcasecmp(form, "handset")) {
+            data->location = mir_external;
+            data->type = mir_phone;
+            data->privacy = mir_private;
+        }
+        else if (!strcasecmp(form, "headset")) {
+            data->location = mir_external;
+            if (bus) {
+                if (!strcasecmp(bus,"usb")) {
+                    data->type = mir_usb_headset;
+                }
+                else if (!strcasecmp(bus,"bluetooth")) {
+                    if (prof && !strcmp(prof->name, "a2dp"))
+                        data->type = mir_bluetooth_a2dp;
+                    else 
+                        data->type = mir_bluetooth_sco;
+                }
+                else {
+                    data->type = mir_wired_headset;
+                }
+            }
+        }
+        else if (!strcasecmp(form, "headphone")) {
+            if (data->direction == mir_output) {
+                data->location = mir_external;
+                if (bus) {
+                    if (!strcasecmp(bus,"usb"))
+                        data->type = mir_usb_headphone;
+                    else if (strcasecmp(bus,"bluetooth"))
+                        data->type = mir_wired_headphone;
+                }
+            }
+        }
+        else if (!strcasecmp(form, "microphone")) {
+            if (data->direction == mir_input) {
+                data->location = mir_external;
+                data->type = mir_microphone;
+            }
+        }
+    }
+    else {
+        if (port && !strcasecmp(bus, "pci"))
+            guess_node_type_and_name(data, port->name, port->description);
+    }
+
+    if (!data->amname[0]) {
+        if (data->type != mir_node_type_unknown)
+            data->amname = (char *)mir_node_type_str(data->type);
+        else if (port && port->description)
+            data->amname = port->description;
+        else if (port && port->name)
+            data->amname = port->name;
+        else
+            data->amname = data->paname;
+    }
+
+
+    if (data->direction == mir_input)
+        data->privacy = mir_privacy_unknown;
+    else {
+        switch (data->type) {
+            /* public */
+        default:
+        case mir_speakers:
+        case mir_front_speakers:
+        case mir_rear_speakers:
+            data->privacy = mir_public;
+            break;
+            
+            /* private */
+        case mir_phone:
+        case mir_wired_headset:
+        case mir_wired_headphone:
+        case mir_usb_headset:
+        case mir_usb_headphone:
+        case mir_bluetooth_sco:
+        case mir_bluetooth_a2dp:
+            data->privacy = mir_private;
+            break;
+            
+            /* unknown */
+        case mir_null:
+        case mir_jack:
+        case mir_spdif:
+        case mir_hdmi:
+            data->privacy = mir_privacy_unknown;
+            break;
+        } /* switch */
+    }
+}
+
+
+/* data->direction must be set */
+static void guess_node_type_and_name(mir_node *data, const char *pnam,
+                                     const char *pdesc)
 {
+    if (data->direction == mir_output && strcasestr(pnam, "headphone")) {
+        data->type = mir_wired_headphone;
+        data->amname = (char *)pdesc;
+    }
+    else if (strcasestr(pnam, "headset")) {
+        data->type = mir_wired_headset;
+        data->amname = (char *)pdesc;
+    }
+    else if (strcasestr(pnam, "line")) {
+        data->type = mir_jack;
+        data->amname = (char *)pdesc;
+    }
+    else if (strcasestr(pnam, "spdif")) {
+        data->type = mir_spdif;
+        data->amname = (char *)pdesc;
+    }
+    else if (strcasestr(pnam, "hdmi")) {
+        data->type = mir_hdmi;
+        data->amname = (char *)pdesc;
+    }
+    else if (data->direction == mir_input && strcasestr(pnam, "microphone")) {
+        data->type = mir_microphone;
+        data->amname = (char *)pdesc;
+    }
+    else if (data->direction == mir_output && strcasestr(pnam, "analog-output"))
+        data->type = mir_speakers;
+    else if (data->direction == mir_input && strcasestr(pnam, "analog-input"))
+        data->type = mir_jack;
+    else {
+        data->type = mir_node_type_unknown;
+    }
 }
 
+
                                   
 /*
  * Local Variables:
index 2dc1930..1258b8e 100644 (file)
@@ -9,30 +9,45 @@
 struct pa_card;
 
 
-/*
- * generally in PA a routing target is a
- * card/profile + sink/port combination
- * the struct below represent such entity
- */
-#define AM_ID_INVALID  (~((uint16_t)0))
+#define PA_BIT(a)      (1UL << (a))
 
-struct pa_routing_target {
-    char *name;          /**< internal  name */
-    char *descr;         /**< UI description */
-    uint16_t id;         /**< link to audiomanager, if any */
-    struct {
-        uint32_t index;
-        char *profile;
-    } card;
-    struct {
-        uint32_t index;  /**< PA_IDXSET_INVALID if the sink is not loaded  */
-        char *name;      /**< sink name */
-        char *port;      /**< port name for the target  */
-    } sink;
+enum pa_bus_type {
+    pa_bus_unknown = 0,
+    pa_bus_pci,
+    pa_bus_usb,
+    pa_bus_bluetooth,
 };
 
+enum pa_form_factor {
+    pa_form_factor_unknown,
+    pa_internal,
+    pa_speaker,
+    pa_handset,
+    pa_tv,
+    pa_webcam,
+    pa_microphone,
+    pa_headset,
+    pa_headphone,
+    pa_hands_free,
+    pa_car,
+    pa_hifi,
+    pa_computer,
+    pa_portable
+};
+
+typedef struct pa_discover {
+    unsigned                chmin;
+    unsigned                chmax;
+    struct {
+        pa_hashmap *pahash;
+        pa_hashmap *amhash;
+    }                       nodes;    
+} pa_discover;
+
+
 struct pa_discover *pa_discover_init(struct userdata *);
-void pa_discover_new_card(struct userdata *, struct pa_card *);
+void pa_discover_add_card(struct userdata *, struct pa_card *);
+void pa_discover_remove_card(struct userdata *, pa_card *);
 
 #endif
 
diff --git a/src/node.c b/src/node.c
new file mode 100644 (file)
index 0000000..268b4d0
--- /dev/null
@@ -0,0 +1,177 @@
+#include <stdio.h>
+
+#include <pulsecore/pulsecore-config.h>
+
+#include <pulsecore/hashmap.h>
+#include <pulsecore/idxset.h>
+#include <pulsecore/strbuf.h>
+
+
+#include "node.h"
+#include "discover.h"
+
+
+mir_node *mir_node_create(struct userdata *u, mir_node *data)
+{
+    mir_node *node;
+
+    pa_assert(u);
+    pa_assert(data);
+    pa_assert(data->key);
+    pa_assert(data->paname);
+    
+    node = pa_xnew0(mir_node, 1);
+
+    node->key       = pa_xstrdup(data->key);
+    node->direction = data->direction;
+    node->implement = data->implement;
+    node->channels  = data->channels;
+    node->location  = data->location;
+    node->privacy   = data->privacy;
+    node->type      = data->type;
+    node->visible   = data->visible;
+    node->available = data->available;
+    node->amname    = pa_xstrdup(data->amname ? data->amname : data->paname);
+    node->amdescr   = pa_xstrdup(data->amdescr ? data->amdescr : "");
+    node->amid      = data->amid;
+    node->paname    = pa_xstrdup(data->paname);
+    node->paidx     = data->paidx;
+    
+    if (node->implement == mir_device) {
+        node->pacard.index   = data->pacard.index;
+        if (data->pacard.profile)
+            node->pacard.profile = pa_xstrdup(data->pacard.profile);
+        if (data->paport)
+            node->paport = pa_xstrdup(data->paport);
+    }
+    
+    return node;
+}
+
+void mir_node_destroy(struct userdata *u, mir_node *node)
+{
+    pa_assert(u);
+
+    if (node) {
+
+        pa_xfree(node->key);
+        pa_xfree(node->amname);
+        pa_xfree(node->amdescr);
+        pa_xfree(node->paname);
+        pa_xfree(node->pacard.profile);
+        pa_xfree(node->paport);
+
+        pa_xfree(node);
+    }
+}
+
+int mir_node_print(mir_node *node, char *buf, int len)
+{
+    char *p, *e;
+
+    pa_assert(node);
+    pa_assert(buf);
+    pa_assert(len > 0);
+
+    e = (p = buf) + len;
+
+#define PRINT(f,v) if (p < e) p += snprintf(p, e-p, f "\n", v)
+
+    PRINT("   key           : '%s'",  node->key ? node->key : "");
+    PRINT("   direction     : %s"  ,  mir_direction_str(node->direction));
+    PRINT("   implement     : %s"  ,  mir_implement_str(node->implement));
+    PRINT("   channels      : %u"  ,  node->channels);
+    PRINT("   location      : %s"  ,  mir_location_str(node->location));
+    PRINT("   privacy       : %s"  ,  mir_privacy_str(node->privacy));
+    PRINT("   type          : %s"  ,  mir_node_type_str(node->type));
+    PRINT("   visible       : %s"  ,  node->visible ? "yes" : "no");
+    PRINT("   available     : %s"  ,  node->available ? "yes" : "no");
+    PRINT("   amname        : '%s'",  node->amname ? node->amname : "");
+    PRINT("   amdescr       : '%s'",  node->amdescr ? node->amdescr : "");
+    PRINT("   amid          : %u"  ,  node->amid);
+    PRINT("   paname        : '%s'",  node->paname ? node->paname : "");
+    PRINT("   paidx         : %u"  ,  node->paidx);
+    PRINT("   pacard.index  : %u"  ,  node->pacard.index);
+    PRINT("   pacard.profile: '%s'",  node->pacard.profile ?
+                                      node->pacard.profile : "");
+    PRINT("   paport        : '%s'",  node->paport ? node->paport : "");
+
+#undef PRINT
+
+    return p - buf;
+}
+
+const char *mir_direction_str(mir_direction direction)
+{
+    switch (direction) {
+    case mir_direction_unknown:  return "unknown";
+    case mir_input:              return "input";
+    case mir_output:             return "output";
+    default:                     return "< ??? >";
+    }
+}
+
+const char *mir_implement_str(mir_implement implement)
+{
+    switch (implement) {
+    case mir_implementation_unknown:  return "unknown";
+    case mir_device:                  return "device";
+    case mir_stream:                  return "stream";
+    default:                          return "< ??? >";
+    }
+}
+
+const char *mir_location_str(mir_location location)
+{
+    switch (location) {
+    case mir_location_unknown:  return "unknown";
+    case mir_internal:          return "internal";
+    case mir_external:          return "external";
+    default:                    return "< ??? >";
+    }
+} 
+
+
+const char *mir_node_type_str(mir_node_type type)
+{
+    switch (type) {
+    case mir_node_type_unknown:   return "Unknown";
+    case mir_radio:               return "Radio";
+    case mir_player:              return "Player";
+    case mir_navigator:           return "Navigator";
+    case mir_game:                return "Game";
+    case mir_browser:             return "Browser";
+    case mir_phone:               return "Phone";
+    case mir_speakers:            return "Speakers";
+    case mir_microphone:          return "Microphone";
+    case mir_jack:                return "Line";
+    case mir_spdif:               return "SPDIF";
+    case mir_hdmi:                return "HDMI";
+    case mir_wired_headset:       return "Wired Headset";
+    case mir_wired_headphone:     return "Wired Headphone";
+    case mir_usb_headset:         return "USB Headset";
+    case mir_bluetooth_sco:       return "Bluetooth Handsfree";
+    case mir_bluetooth_a2dp:      return "Bluetooth Stereo";
+    default:                      return "<user defined>";
+    }
+}
+
+
+const char *mir_privacy_str(mir_privacy privacy)
+{
+    switch (privacy) {
+    case mir_privacy_unknown:  return "<unknown>";
+    case mir_public:           return "public";
+    case mir_private:          return "private";
+    default:                   return "< ??? >";
+    }
+}
+
+                                  
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */
diff --git a/src/node.h b/src/node.h
new file mode 100644 (file)
index 0000000..273dd34
--- /dev/null
@@ -0,0 +1,122 @@
+#ifndef foomirnodefoo
+#define foomirnodefoo
+
+#include <sys/types.h>
+
+#include "userdata.h"
+
+
+#define AM_ID_INVALID  (~((uint16_t)0))
+
+typedef enum {
+    mir_direction_unknown,
+    mir_input,
+    mir_output
+} mir_direction;
+
+typedef enum {
+    mir_implementation_unknown = 0,
+    mir_device,
+    mir_stream
+} mir_implement;
+
+typedef enum {
+    mir_location_unknown = 0,
+    mir_internal,
+    mir_external
+} mir_location;
+
+typedef enum {
+    mir_node_type_unknown = 0,
+    /* application classes */
+    mir_radio,
+    mir_player,
+    mir_navigator,
+    mir_game,
+    mir_browser,
+    mir_phone,
+    /* device types */
+    mir_null = 128,
+    mir_speakers,
+    mir_front_speakers,
+    mir_rear_speakers,
+    mir_microphone,
+    mir_jack,
+    mir_spdif,
+    mir_hdmi,
+    mir_wired_headset,
+    mir_wired_headphone,
+    mir_usb_headset,
+    mir_usb_headphone,
+    mir_bluetooth_sco,
+    mir_bluetooth_a2dp,
+    /* extensions */
+    mir_user_defined_start = 256
+} mir_node_type;
+
+typedef enum {
+    mir_privacy_unknown = 0,
+    mir_public,
+    mir_private
+} mir_privacy;
+
+
+typedef struct {
+    uint32_t  index;
+    char     *profile;
+} pa_node_card;
+
+
+/**
+ * @brief routing endpoint
+ *
+ * @details node is a routing endpoint in the GenIVI audio model.
+ *          In pulseaudio terminology a routing endpoint is one of
+ *          the following
+ * @li      node is a pulseaudio sink or source. Such node is a
+ *          combination of pulseudio card/profile + sink/port
+ * @li      node is a pulseaudio stream. Such node in pulseaudio
+ *          is either a sink_input or a source_output
+ */
+typedef struct mir_node {
+    char           *key;      /**< hash key  */
+    mir_direction   direction;/**< mir_input | mir_output */
+    mir_implement   implement;/**< mir_device | mir_stream */
+    uint32_t        channels; /**< number of channels (eg. 1=mono, 2=stereo) */
+    mir_location    location; /**< mir_internal | mir_external */
+    mir_privacy     privacy;  /**< mir_public | mir_private */
+    mir_node_type   type;     /**< mir_speakers | mir_headset | ...  */
+    pa_bool_t       visible;  /**< internal or can appear on UI  */
+    pa_bool_t       available;/**< eg. is the headset connected?  */
+    char           *amname;   /**< audiomanager name */
+    char           *amdescr;  /**< UI description */
+    uint16_t        amid;     /**< handle to audiomanager, if any */
+    char           *paname;   /**< sink|source|sink_input|source_output name */
+    uint32_t        paidx;    /**< sink|source|sink_input|source_output index*/
+    pa_node_card    pacard;   /**< pulse card related data, if any  */
+    char           *paport;   /**< sink or source port if applies */
+} mir_node;
+
+
+mir_node *mir_node_create(struct userdata *, mir_node *);
+void mir_node_destroy(struct userdata *, mir_node *);
+
+int mir_node_print(mir_node *, char *, int);
+
+const char *mir_direction_str(mir_direction);
+const char *mir_implement_str(mir_implement);
+const char *mir_location_str(mir_location);
+const char *mir_node_type_str(mir_node_type);
+const char *mir_node_type_str(mir_node_type);
+const char *mir_privacy_str(mir_privacy);
+
+#endif
+
+
+/*
+ * Local Variables:
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ *
+ */