From 5f20ab328add9442082277a57c23273a3a2125ed Mon Sep 17 00:00:00 2001 From: Michal Ratajsky Date: Mon, 18 Aug 2014 20:49:17 +0200 Subject: Global update --- backends/alsa/alsa-backend.c | 333 +++++++++++++++++++++++-------------------- 1 file changed, 182 insertions(+), 151 deletions(-) (limited to 'backends/alsa/alsa-backend.c') diff --git a/backends/alsa/alsa-backend.c b/backends/alsa/alsa-backend.c index 7a17b85..6bac691 100644 --- a/backends/alsa/alsa-backend.c +++ b/backends/alsa/alsa-backend.c @@ -27,12 +27,22 @@ #include "alsa-stream.h" #define BACKEND_NAME "ALSA" -#define BACKEND_PRIORITY 9 +#define BACKEND_PRIORITY 20 + +#define ALSA_DEVICE_GET_ID(d) \ + (g_object_get_data (G_OBJECT (d), "__matemixer_alsa_device_id")) + +#define ALSA_DEVICE_SET_ID(d,id) \ + (g_object_set_data_full (G_OBJECT (d), \ + "__matemixer_alsa_device_id", \ + g_strdup (id), \ + g_free)) struct _AlsaBackendPrivate { GSource *timeout_source; - GHashTable *devices; + GList *streams; + GList *devices; GHashTable *devices_ids; }; @@ -47,26 +57,38 @@ static void alsa_backend_finalize (GObject *object); G_DEFINE_DYNAMIC_TYPE (AlsaBackend, alsa_backend, MATE_MIXER_TYPE_BACKEND) #pragma clang diagnostic pop -static gboolean alsa_backend_open (MateMixerBackend *backend); -static void alsa_backend_close (MateMixerBackend *backend); -static GList * alsa_backend_list_devices (MateMixerBackend *backend); -static GList * alsa_backend_list_streams (MateMixerBackend *backend); +static gboolean alsa_backend_open (MateMixerBackend *backend); +static void alsa_backend_close (MateMixerBackend *backend); +static const GList *alsa_backend_list_devices (MateMixerBackend *backend); +static const GList *alsa_backend_list_streams (MateMixerBackend *backend); + +static gboolean read_devices (AlsaBackend *alsa); + +static gboolean read_device (AlsaBackend *alsa, + const gchar *card); + +static void add_device (AlsaBackend *alsa, + AlsaDevice *device); -static gboolean read_devices (AlsaBackend *alsa); +static void remove_device (AlsaBackend *alsa, + AlsaDevice *device); +static void remove_device_by_name (AlsaBackend *alsa, + const gchar *name); +static void remove_device_by_list_item (AlsaBackend *alsa, + GList *item); -static gboolean read_device (AlsaBackend *alsa, - const gchar *card); +static void remove_stream (AlsaBackend *alsa, + const gchar *name); -static void add_device (AlsaBackend *alsa, - AlsaDevice *device); +static void select_default_input_stream (AlsaBackend *alsa); +static void select_default_output_stream (AlsaBackend *alsa); -static void remove_device (AlsaBackend *alsa, - AlsaDevice *device); -static void remove_stream (AlsaBackend *alsa, - const gchar *name); +static void free_stream_list (AlsaBackend *alsa); -static void select_default_input_stream (AlsaBackend *alsa); -static void select_default_output_stream (AlsaBackend *alsa); +static gint compare_devices (gconstpointer a, + gconstpointer b); +static gint compare_device_name (gconstpointer a, + gconstpointer b); static MateMixerBackendInfo info; @@ -118,11 +140,6 @@ alsa_backend_init (AlsaBackend *alsa) ALSA_TYPE_BACKEND, AlsaBackendPrivate); - alsa->priv->devices = g_hash_table_new_full (g_str_hash, - g_str_equal, - g_free, - g_object_unref); - alsa->priv->devices_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, @@ -151,7 +168,6 @@ alsa_backend_finalize (GObject *object) alsa = ALSA_BACKEND (object); - g_hash_table_unref (alsa->priv->devices); g_hash_table_unref (alsa->priv->devices_ids); G_OBJECT_CLASS (alsa_backend_parent_class)->finalize (object); @@ -166,9 +182,8 @@ alsa_backend_open (MateMixerBackend *backend) alsa = ALSA_BACKEND (backend); - /* Poll ALSA for changes every 500 milliseconds, this actually only - * discovers added or changed sound cards, sound card related events - * are handled by AlsaDevices */ + /* Poll ALSA for changes every second, this only discovers added or removed + * sound cards, sound card related events are handled by AlsaDevices */ alsa->priv->timeout_source = g_timeout_source_new_seconds (1); g_source_set_callback (alsa->priv->timeout_source, (GSourceFunc) read_devices, @@ -197,61 +212,60 @@ alsa_backend_close (MateMixerBackend *backend) g_source_destroy (alsa->priv->timeout_source); - g_hash_table_remove_all (alsa->priv->devices); + if (alsa->priv->devices != NULL) { + g_list_free_full (alsa->priv->devices, g_object_unref); + alsa->priv->devices = NULL; + } + + free_stream_list (alsa); + g_hash_table_remove_all (alsa->priv->devices_ids); _mate_mixer_backend_set_state (backend, MATE_MIXER_STATE_IDLE); } -static GList * +static const GList * alsa_backend_list_devices (MateMixerBackend *backend) { - GList *list; - g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); - /* Convert the hash table to a linked list, this list is expected to be - * cached in the main library */ - list = g_hash_table_get_values (ALSA_BACKEND (backend)->priv->devices); - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return ALSA_BACKEND (backend)->priv->devices; } -static GList * +static const GList * alsa_backend_list_streams (MateMixerBackend *backend) { - AlsaBackend *alsa; - GHashTableIter iter; - gpointer value; - GList *list = NULL; + AlsaBackend *alsa; g_return_val_if_fail (ALSA_IS_BACKEND (backend), NULL); alsa = ALSA_BACKEND (backend); - /* We don't keep a list or hash table of all streams here, instead walk - * through the list of devices and create the list manually, each device - * has at most one input and one output stream */ - g_hash_table_iter_init (&iter, alsa->priv->devices); - - while (g_hash_table_iter_next (&iter, NULL, &value)) { - AlsaDevice *device = ALSA_DEVICE (value); - AlsaStream *stream; - - stream = alsa_device_get_output_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); - stream = alsa_device_get_input_stream (device); - if (stream != NULL) - list = g_list_prepend (list, stream); + if (alsa->priv->streams == NULL) { + GList *list; + + /* Walk through the list of devices and create the stream list, each + * device has at most one input and one output stream */ + list = alsa->priv->devices; + + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream; + + stream = alsa_device_get_input_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + stream = alsa_device_get_output_stream (device); + if (stream != NULL) { + alsa->priv->streams = + g_list_append (alsa->priv->streams, g_object_ref (stream)); + } + list = list->next; + } } - - if (list != NULL) - g_list_foreach (list, (GFunc) g_object_ref, NULL); - - return list; + return alsa->priv->streams; } static gboolean @@ -260,12 +274,12 @@ read_devices (AlsaBackend *alsa) gint num; gint ret; gchar card[16]; - gboolean changed = FALSE; + gboolean added = FALSE; /* Read the default device first, it will be either one of the hardware cards * that will be queried later, or a software mixer */ if (read_device (alsa, "default") == TRUE) - changed = TRUE; + added = TRUE; for (num = -1;;) { /* Read number of the next sound card */ @@ -277,12 +291,12 @@ read_devices (AlsaBackend *alsa) g_snprintf (card, sizeof (card), "hw:%d", num); if (read_device (alsa, card) == TRUE) - changed = TRUE; + added = TRUE; } /* If any card has been added, make sure we have the most suitable default * input and output streams */ - if (changed == TRUE) { + if (added == TRUE) { select_default_input_stream (alsa); select_default_output_stream (alsa); } @@ -300,15 +314,13 @@ read_device (AlsaBackend *alsa, const gchar *card) /* The device may be already known, remove it if it's known and fails * to be read, this happens for example when PulseAudio is killed */ - device = g_hash_table_lookup (alsa->priv->devices, card); - ret = snd_ctl_open (&ctl, card, 0); if (ret < 0) { g_warning ("Failed to open ALSA control for %s: %s", card, snd_strerror (ret)); - if (device != NULL) - remove_device (alsa, device); + + remove_device_by_name (alsa, card); return FALSE; } @@ -317,9 +329,8 @@ read_device (AlsaBackend *alsa, const gchar *card) ret = snd_ctl_card_info (ctl, info); if (ret < 0) { g_warning ("Failed to read card info: %s", snd_strerror (ret)); - if (device != NULL) - remove_device (alsa, device); + remove_device_by_name (alsa, card); snd_ctl_close (ctl); return FALSE; } @@ -342,11 +353,7 @@ read_device (AlsaBackend *alsa, const gchar *card) return FALSE; } - g_object_set_data_full (G_OBJECT (device), - "__matemixer_alsa_device_id", - g_strdup (id), - g_free); - + ALSA_DEVICE_SET_ID (device, id); add_device (alsa, device); snd_ctl_close (ctl); @@ -356,19 +363,13 @@ read_device (AlsaBackend *alsa, const gchar *card) static void add_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; - - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + alsa->priv->devices = g_list_insert_sorted_with_data (alsa->priv->devices, + device, + (GCompareDataFunc) compare_devices, + NULL); - g_hash_table_insert (alsa->priv->devices, - g_strdup (name), - g_object_ref (device)); - - /* Remember the device identifier, use a single string copy as we only free - * the hash table key */ - g_hash_table_add (alsa->priv->devices_ids, - g_strdup (g_object_get_data (G_OBJECT (device), - "__matemixer_alsa_device_id"))); + /* Keep track of device identifiers */ + g_hash_table_add (alsa->priv->devices_ids, g_strdup (ALSA_DEVICE_GET_ID (device))); g_signal_connect_swapped (G_OBJECT (device), "closed", @@ -379,7 +380,22 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) G_CALLBACK (remove_stream), alsa); - g_signal_emit_by_name (G_OBJECT (alsa), "device-added", name); + g_signal_connect_swapped (G_OBJECT (device), + "closed", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-added", + G_CALLBACK (free_stream_list), + alsa); + g_signal_connect_swapped (G_OBJECT (device), + "stream-removed", + G_CALLBACK (free_stream_list), + alsa); + + g_signal_emit_by_name (G_OBJECT (alsa), + "device-added", + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); /* Load the device elements after emitting device-added, because the load * function will most likely emit stream-added on the device and backend */ @@ -389,27 +405,48 @@ add_device (AlsaBackend *alsa, AlsaDevice *device) static void remove_device (AlsaBackend *alsa, AlsaDevice *device) { - const gchar *name; + GList *item; + + item = g_list_find (alsa->priv->devices, device); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_name (AlsaBackend *alsa, const gchar *name) +{ + GList *item; + + item = g_list_find_custom (alsa->priv->devices, name, compare_device_name); + if (item != NULL) + remove_device_by_list_item (alsa, item); +} + +static void +remove_device_by_list_item (AlsaBackend *alsa, GList *item) +{ + AlsaDevice *device; + + device = ALSA_DEVICE (item->data); - name = mate_mixer_device_get_name (MATE_MIXER_DEVICE (device)); + g_signal_handlers_disconnect_by_data (G_OBJECT (device), alsa); - g_signal_handlers_disconnect_by_func (G_OBJECT (device), - G_CALLBACK (remove_device), - alsa); - g_signal_handlers_disconnect_by_func (G_OBJECT (device), - G_CALLBACK (remove_stream), - alsa); + if (alsa_device_is_open (device) == TRUE) + alsa_device_close (device); + + alsa->priv->devices = g_list_delete_link (alsa->priv->devices, item); - /* Remove the device */ g_hash_table_remove (alsa->priv->devices_ids, - g_object_get_data (G_OBJECT (device), - "__matemixer_alsa_device_id")); + ALSA_DEVICE_GET_ID (device)); + + /* The list may and may not have been invalidate by device signals */ + free_stream_list (alsa); - // XXX close the device and make it remove streams - g_hash_table_remove (alsa->priv->devices, name); g_signal_emit_by_name (G_OBJECT (alsa), "device-removed", - name); + mate_mixer_device_get_name (MATE_MIXER_DEVICE (device))); + + g_object_unref (device); } static void @@ -419,7 +456,6 @@ remove_stream (AlsaBackend *alsa, const gchar *name) stream = mate_mixer_backend_get_default_input_stream (MATE_MIXER_BACKEND (alsa)); - // XXX see if the change happens after stream is removed or before if (stream != NULL && strcmp (mate_mixer_stream_get_name (stream), name) == 0) select_default_input_stream (alsa); @@ -432,36 +468,19 @@ remove_stream (AlsaBackend *alsa, const gchar *name) static void select_default_input_stream (AlsaBackend *alsa) { - AlsaDevice *device; - AlsaStream *stream; - gchar card[16]; - gint num; - - /* Always prefer stream in the "default" device */ - device = g_hash_table_lookup (alsa->priv->devices, "default"); - if (device != NULL) { - stream = alsa_device_get_input_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), - MATE_MIXER_STREAM (stream)); - return; - } - } + GList *list; - /* If there is no input stream in the default device, search the cards in - * the correct order */ - for (num = 0;; num++) { - g_snprintf (card, sizeof (card), "hw:%d", num); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_input_stream (device); - device = g_hash_table_lookup (alsa->priv->devices, card); - if (device == NULL) - break; - stream = alsa_device_get_input_stream (device); if (stream != NULL) { _mate_mixer_backend_set_default_input_stream (MATE_MIXER_BACKEND (alsa), MATE_MIXER_STREAM (stream)); return; } + list = list->next; } /* In the worst case unset the default stream */ @@ -471,38 +490,50 @@ select_default_input_stream (AlsaBackend *alsa) static void select_default_output_stream (AlsaBackend *alsa) { - AlsaDevice *device; - AlsaStream *stream; - gchar card[16]; - gint num; - - /* Always prefer stream in the "default" device */ - device = g_hash_table_lookup (alsa->priv->devices, "default"); - if (device != NULL) { - stream = alsa_device_get_output_stream (device); - if (stream != NULL) { - _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), - MATE_MIXER_STREAM (stream)); - return; - } - } + GList *list; - /* If there is no input stream in the default device, search the cards in - * the correct order */ - for (num = 0;; num++) { - g_snprintf (card, sizeof (card), "hw:%d", num); + list = alsa->priv->devices; + while (list != NULL) { + AlsaDevice *device = ALSA_DEVICE (list->data); + AlsaStream *stream = alsa_device_get_output_stream (device); - device = g_hash_table_lookup (alsa->priv->devices, card); - if (device == NULL) - break; - stream = alsa_device_get_output_stream (device); if (stream != NULL) { _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), MATE_MIXER_STREAM (stream)); return; } + list = list->next; } /* In the worst case unset the default stream */ _mate_mixer_backend_set_default_output_stream (MATE_MIXER_BACKEND (alsa), NULL); } + +static void +free_stream_list (AlsaBackend *alsa) +{ + if (alsa->priv->streams == NULL) + return; + + g_list_free_full (alsa->priv->streams, g_object_unref); + + alsa->priv->streams = NULL; +} + +static gint +compare_devices (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *d1 = MATE_MIXER_DEVICE (a); + MateMixerDevice *d2 = MATE_MIXER_DEVICE (b); + + return strcmp (mate_mixer_device_get_name (d1), mate_mixer_device_get_name (d2)); +} + +static gint +compare_device_name (gconstpointer a, gconstpointer b) +{ + MateMixerDevice *device = MATE_MIXER_DEVICE (a); + const gchar *name = (const gchar *) b; + + return strcmp (mate_mixer_device_get_name (device), name); +} -- cgit v1.2.1