--- configure.ac.in | 18 libxfce4mixer/Makefile.am | 25 libxfce4mixer/audio.h | 8 libxfce4mixer/libxfce4mixer.c | 40 + libxfce4mixer/libxfce4mixer.h | 2 libxfce4mixer/xfce-mixer-preferences.h | 3 libxfce4mixer/xfce-mixer-track-combo.c | 3 libxfce4mixer/xfce-mixer-track-combo.h | 3 libxfce4mixer/xfce-mixer-track-type.h | 2 libxfce4mixer/xfce4-mixer-alsa.c | 974 +++++++++++++++++++++++++++++++ libxfce4mixer/xfce4-mixer-alsa.h | 192 ++++++ panel-plugin/Makefile.am | 15 panel-plugin/xfce-plugin-dialog.c | 3 panel-plugin/xfce-plugin-dialog.h | 3 xfce4-mixer/Makefile.am | 11 xfce4-mixer/xfce-mixer-container.c | 3 xfce4-mixer/xfce-mixer-controls-dialog.c | 3 xfce4-mixer/xfce-mixer-option.c | 3 xfce4-mixer/xfce-mixer-switch.c | 3 xfce4-mixer/xfce-mixer-track.c | 3 xfce4-mixer/xfce-mixer-window.c | 4 21 files changed, 1278 insertions(+), 43 deletions(-) --- a/libxfce4mixer/libxfce4mixer.c +++ b/libxfce4mixer/libxfce4mixer.c @@ -31,8 +31,7 @@ #include -#include -#include +#include "audio.h" #include @@ -40,8 +39,10 @@ +#ifndef XFCE4_MIXER_ALSA static gboolean _xfce_mixer_filter_mixer (GstMixer *mixer, gpointer user_data); +#endif static void _xfce_mixer_add_track_labels (gpointer data, gpointer user_data); static void _xfce_mixer_init_mixer (gpointer data, @@ -88,7 +89,11 @@ xfce_mixer_init (void) gtk_icon_theme_append_search_path (icon_theme, MIXER_DATADIR G_DIR_SEPARATOR_S "icons"); /* Get list of all available mixer devices */ +#ifdef XFCE4_MIXER_ALSA + mixers = gst_mixer_probe_devices (); +#else mixers = gst_audio_default_registry_mixer_filter (_xfce_mixer_filter_mixer, FALSE, &counter); +#endif /* Create a GstBus for notifications */ bus = gst_bus_new (); @@ -396,6 +401,9 @@ xfce_mixer_get_max_volume (gint *volumes +static void set_mixer_name (GstMixer *mixer, const gchar *name); + +#ifndef XFCE4_MIXER_ALSA static gboolean _xfce_mixer_filter_mixer (GstMixer *mixer, gpointer user_data) @@ -427,8 +435,24 @@ _xfce_mixer_filter_mixer (GstMixer *mixe /* Free device name */ g_free (device_name); + set_mixer_name (mixer, name); + g_free (name); + + /* Keep the mixer (we want all devices to be visible) */ + return TRUE; +} +#endif /* !XFCE4_MIXER_ALSA */ + +static void +set_mixer_name (GstMixer *mixer, const gchar *name) +{ + gint length; + const gchar *p; + gchar *internal_name; + /* Set name to be used by xfce4-mixer */ - g_object_set_data_full (G_OBJECT (mixer), "xfce-mixer-name", name, (GDestroyNotify) g_free); + g_object_set_data_full (G_OBJECT (mixer), "xfce-mixer-name", + g_strdup (name), (GDestroyNotify) g_free); /* Count alpha-numeric characters in the name */ for (length = 0, p = name; *p != '\0'; ++p) @@ -442,15 +466,11 @@ _xfce_mixer_filter_mixer (GstMixer *mixe internal_name[length++] = *p; internal_name[length] = '\0'; - /* Remember name for use by xfce4-mixer */ + /* Set name to be used by xfce4-mixer */ g_object_set_data_full (G_OBJECT (mixer), "xfce-mixer-internal-name", internal_name, (GDestroyNotify) g_free); - - /* Keep the mixer (we want all devices to be visible) */ - return TRUE; } - static void _xfce_mixer_add_track_labels (gpointer data, gpointer user_data) @@ -492,6 +512,10 @@ _xfce_mixer_init_mixer (gpointer data, { GstMixer *card = GST_MIXER (data); +#ifdef XFCE4_MIXER_ALSA + set_mixer_name (card, gst_mixer_get_card_name (card)); +#endif + /* Add custom labels to all tracks */ _xfce_mixer_add_track_labels (card, NULL); --- a/libxfce4mixer/libxfce4mixer.h +++ b/libxfce4mixer/libxfce4mixer.h @@ -26,7 +26,7 @@ #include -#include +#include "audio.h" #include "xfce-mixer-preferences.h" #include "xfce-mixer-card-combo.h" --- /dev/null +++ b/libxfce4mixer/xfce4-mixer-alsa.h @@ -0,0 +1,192 @@ +/* + * Simple alternative GstMixer implementation with ALSA-native API + */ + +#ifndef __XFCE4_MIXER_ALSA_H +#define __XFCE4_MIXER_ALSA_H + +G_BEGIN_DECLS + +/* + * GstMixer + */ + +GType gst_mixer_get_type (void); + +#define GST_TYPE_MIXER \ + (gst_mixer_get_type ()) +#define GST_MIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MIXER, GstMixer)) +#define GST_MIXER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MIXER, GstMixerClass)) +#define GST_IS_MIXER(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MIXER)) +#define GST_IS_MIXER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MIXER)) +#define GST_MIXER_GET_CLASS(inst) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_MIXER, GstMixerClass)) + +typedef struct _GstMixer GstMixer; +typedef struct _GstMixerClass GstMixerClass; + +typedef enum { + GST_MIXER_FLAG_NONE = 0, + GST_MIXER_FLAG_AUTO_NOTIFICATIONS = (1<<0), + GST_MIXER_FLAG_HAS_WHITELIST = (1<<1), + GST_MIXER_FLAG_GROUPING = (1<<2), +} GstMixerFlags; + +typedef enum { + GST_MIXER_MESSAGE_INVALID, + GST_MIXER_MESSAGE_MUTE_TOGGLED, + GST_MIXER_MESSAGE_RECORD_TOGGLED, + GST_MIXER_MESSAGE_VOLUME_CHANGED, + GST_MIXER_MESSAGE_OPTION_CHANGED, + GST_MIXER_MESSAGE_OPTIONS_LIST_CHANGED, + GST_MIXER_MESSAGE_MIXER_CHANGED +} GstMixerMessageType; + +struct _GstMixer { + GstElement element; + GList *tracklist; + void *handle; /* snd_mixer_t */ + const char *name; + const gchar *card_name; + GSource *src; +}; + +struct _GstMixerClass { + GstElementClass parent_class; +}; + +const GList *gst_mixer_list_tracks (GstMixer *mixer); + +static inline GstMixerFlags +gst_mixer_get_mixer_flags (GstMixer * mixer) +{ + return GST_MIXER_FLAG_AUTO_NOTIFICATIONS; +} + +GstMixerMessageType gst_mixer_message_get_type (GstMessage *message); + +int gst_mixer_new (const char *name, GstMixer **mixer_ret); +GList *gst_mixer_probe_devices (void); +const gchar *gst_mixer_get_card_name (GstMixer *mixer); + +/* + * GstMixerTrack + */ + +GType gst_mixer_track_get_type (void); + +#define GST_TYPE_MIXER_TRACK \ + (gst_mixer_track_get_type ()) +#define GST_MIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MIXER_TRACK, \ + GstMixerTrack)) +#define GST_MIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MIXER_TRACK, \ + GstMixerTrackClass)) +#define GST_IS_MIXER_TRACK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MIXER_TRACK)) +#define GST_IS_MIXER_TRACK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MIXER_TRACK)) + +typedef struct _GstMixerTrack GstMixerTrack; +typedef struct _GstMixerTrackClass GstMixerTrackClass; + +typedef enum { + GST_MIXER_TRACK_INPUT = (1<<0), + GST_MIXER_TRACK_OUTPUT = (1<<1), + GST_MIXER_TRACK_MUTE = (1<<2), + GST_MIXER_TRACK_RECORD = (1<<3), + GST_MIXER_TRACK_MASTER = (1<<4), + GST_MIXER_TRACK_SOFTWARE = (1<<5), + GST_MIXER_TRACK_NO_RECORD = (1<<6), + GST_MIXER_TRACK_NO_MUTE = (1<<7), + GST_MIXER_TRACK_WHITELIST = (1<<8), + GST_MIXER_TRACK_READONLY = (1<<9), + GST_MIXER_TRACK_WRITEONLY = (1<<10) +} GstMixerTrackFlags; + +struct _GstMixerTrack { + GObject parent; + void *element; + gchar *label; + gchar *untranslated_label; + guint index; + GstMixerTrackFlags flags; + gint num_channels; + gint *volumes; + gint min_volume; + gint max_volume; + GstMixerTrack *shared_mute; + gboolean has_volume; + gboolean has_switch; +}; + +struct _GstMixerTrackClass { + GObjectClass parent; +}; + +#define GST_MIXER_TRACK_HAS_FLAG(track, flag) ((track)->flags & (flag)) + +void gst_mixer_get_volume (GstMixer *mixer, GstMixerTrack *track, gint *volumes); +void gst_mixer_set_volume (GstMixer *mixer, GstMixerTrack *track, gint *volumes); +void gst_mixer_set_mute (GstMixer *mixer, GstMixerTrack *track, gboolean mute); +void gst_mixer_set_record (GstMixer *mixer, GstMixerTrack *track, gboolean record); + +void gst_mixer_message_parse_mute_toggled (GstMessage *message, + GstMixerTrack **track, + gboolean *mute); +void gst_mixer_message_parse_record_toggled (GstMessage *message, + GstMixerTrack **track, + gboolean *record); +void gst_mixer_message_parse_volume_changed (GstMessage *message, + GstMixerTrack **track, + gint **volumes, + gint *num_channels); + +/* + * GstMixerOptions + */ + +GType gst_mixer_options_get_type (void); + +#define GST_TYPE_MIXER_OPTIONS \ + (gst_mixer_options_get_type ()) +#define GST_MIXER_OPTIONS(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_MIXER_OPTIONS, GstMixerOptions)) +#define GST_MIXER_OPTIONS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_MIXER_OPTIONS, GstMixerOptionsClass)) +#define GST_IS_MIXER_OPTIONS(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_MIXER_OPTIONS)) +#define GST_IS_MIXER_OPTIONS_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_MIXER_OPTIONS)) +#define GST_MIXER_OPTIONS_GET_CLASS(inst) \ + (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GST_TYPE_MIXER_OPTIONS, GstMixerOptionsClass)) + +typedef struct _GstMixerOptions GstMixerOptions; +typedef struct _GstMixerOptionsClass GstMixerOptionsClass; + +struct _GstMixerOptions { + GstMixerTrack parent; + GList *values; +}; + +struct _GstMixerOptionsClass { + GstMixerTrackClass parent; +}; + +void gst_mixer_set_option (GstMixer * mixer, GstMixerOptions * opts, gchar * value); +const gchar * gst_mixer_get_option (GstMixer * mixer, GstMixerOptions * opts); +GList * gst_mixer_options_get_values (GstMixerOptions *mixer_options); +void gst_mixer_message_parse_option_changed (GstMessage *message, + GstMixerOptions ** options, + const gchar **value); +void gst_mixer_message_parse_options_list_changed (GstMessage *message, + GstMixerOptions **options); + +G_END_DECLS + +#endif /* __XFCE4_MIXER_ALSA_H */ --- a/libxfce4mixer/xfce-mixer-preferences.h +++ b/libxfce4mixer/xfce-mixer-preferences.h @@ -24,8 +24,7 @@ #include -#include -#include +#include "audio.h" G_BEGIN_DECLS --- a/libxfce4mixer/xfce-mixer-track-combo.c +++ b/libxfce4mixer/xfce-mixer-track-combo.c @@ -27,8 +27,7 @@ #include -#include -#include +#include "audio.h" #include "libxfce4mixer.h" #include "xfce-mixer-track-type.h" --- a/libxfce4mixer/xfce-mixer-track-combo.h +++ b/libxfce4mixer/xfce-mixer-track-combo.h @@ -24,8 +24,7 @@ #include -#include -#include +#include "audio.h" G_BEGIN_DECLS --- a/libxfce4mixer/xfce-mixer-track-type.h +++ b/libxfce4mixer/xfce-mixer-track-type.h @@ -22,7 +22,7 @@ #define __XFCE_TRACK_TYPE_H__ #include -#include +#include "audio.h" G_BEGIN_DECLS --- /dev/null +++ b/libxfce4mixer/xfce4-mixer-alsa.c @@ -0,0 +1,974 @@ +/* + * Simple alternative GstMixer implementation with ALSA-native API + */ + +#include "config.h" +#include "audio.h" + +#include + +#define GST_MIXER_MESSAGE_NAME "gst-mixer-message" + +/* + * GstMixer + */ + +G_DEFINE_TYPE (GstMixer, gst_mixer, GST_TYPE_ELEMENT); + +static void gst_mixer_init (GstMixer *mixer) +{ +} + +static void gst_mixer_dispose (GObject * object) +{ + GstMixer *mixer = GST_MIXER (object); + + if (mixer->src) { + g_source_destroy (mixer->src); + mixer->src = NULL; + } + + if (mixer->handle) { + snd_mixer_close (mixer->handle); + mixer->handle = NULL; + } + + g_list_free_full (mixer->tracklist, g_object_unref); + mixer->tracklist = NULL; + + g_free ((gpointer *) mixer->name); + mixer->name = NULL; + + g_free ((gpointer *) mixer->card_name); + mixer->card_name = NULL; + + G_OBJECT_CLASS (gst_mixer_parent_class)->dispose (object); +} + +static void gst_mixer_class_init (GstMixerClass *klass) +{ + GstElementClass *element_klass = GST_ELEMENT_CLASS (klass); + GObjectClass *object_klass = G_OBJECT_CLASS (klass); + + gst_element_class_set_static_metadata (element_klass, + "ALSA mixer", "Generic/Audio", + "Control audio mixer via ALSA API", + "Takashi Iwai "); + + object_klass->dispose = gst_mixer_dispose; +} + +/* + * GstMixerTrack + */ + +G_DEFINE_TYPE (GstMixerTrack, gst_mixer_track, G_TYPE_OBJECT); + +static void gst_mixer_track_init (GstMixerTrack *track) +{ +} + +static void notify_mute_change (GstMixer *mixer, GstMixerTrack *track, + gboolean mute) +{ + GstStructure *s; + GstMessage *m; + + s = gst_structure_new (GST_MIXER_MESSAGE_NAME, + "type", G_TYPE_STRING, "mute-toggled", + "track", GST_TYPE_MIXER_TRACK, track, + "mute", G_TYPE_BOOLEAN, mute, + NULL); + m = gst_message_new_element (GST_OBJECT (mixer), s); + gst_element_post_message (GST_ELEMENT (mixer), m); +} + +static void update_mute (GstMixer *mixer, GstMixerTrack *track, gboolean mute) +{ + int old_flag = track->flags & GST_MIXER_TRACK_MUTE; + + if (mute) { + track->flags |= GST_MIXER_TRACK_MUTE; + if (track->shared_mute) + track->shared_mute->flags |= GST_MIXER_TRACK_MUTE; + } else { + track->flags &= ~GST_MIXER_TRACK_MUTE; + if (track->shared_mute) + track->shared_mute->flags &= ~GST_MIXER_TRACK_MUTE; + } + + if ((track->flags & GST_MIXER_TRACK_MUTE) != old_flag) + notify_mute_change (mixer, track, mute); +} + +static void notify_recording_change (GstMixer *mixer, GstMixerTrack *track, + gboolean recording) +{ + GstStructure *s; + GstMessage *m; + + s = gst_structure_new (GST_MIXER_MESSAGE_NAME, + "type", G_TYPE_STRING, "record-toggled", + "track", GST_TYPE_MIXER_TRACK, track, + "record", G_TYPE_BOOLEAN, recording, + NULL); + m = gst_message_new_element (GST_OBJECT (mixer), s); + gst_element_post_message (GST_ELEMENT (mixer), m); +} + +static void update_recording (GstMixer *mixer, GstMixerTrack *track, + gboolean recording) +{ + int old_flag = track->flags & GST_MIXER_TRACK_RECORD; + + if (recording) + track->flags |= GST_MIXER_TRACK_RECORD; + else + track->flags &= ~GST_MIXER_TRACK_RECORD; + + if ((track->flags & GST_MIXER_TRACK_RECORD) != old_flag) + notify_recording_change (mixer, track, recording); +} + +static void notify_volume_change (GstMixer *mixer, GstMixerTrack *track) +{ + GstStructure *s; + GstMessage *m; + GValue l = { 0, }; + GValue v = { 0, }; + int i; + + s = gst_structure_new (GST_MIXER_MESSAGE_NAME, + "type", G_TYPE_STRING, "volume-changed", + "track", GST_TYPE_MIXER_TRACK, track, + NULL); + g_value_init (&l, GST_TYPE_ARRAY); + g_value_init (&v, G_TYPE_INT); + + for (i = 0; i < track->num_channels; i++) { + g_value_set_int (&v, track->volumes[i]); + gst_value_array_append_value (&l, &v); + } + + gst_structure_set_value (s, "volumes", &l); + g_value_unset (&v); + g_value_unset (&l); + + m = gst_message_new_element (GST_OBJECT (mixer), s); + gst_element_post_message (GST_ELEMENT (mixer), m); +} + +static void track_update (GstMixer *mixer, GstMixerTrack *track) +{ + gboolean vol_changed = FALSE; + int i; + + if (track->flags & GST_MIXER_TRACK_OUTPUT) { + int audible = 0; + if (track->has_switch) { + for (i = 0; i < track->num_channels; i++) { + int v = 0; + snd_mixer_selem_get_playback_switch (track->element, i, &v); + if (v) + audible = 1; + } + } + + if (track->has_volume) { + for (i = 0; i < track->num_channels; i++) { + long vol = 0; + snd_mixer_selem_get_playback_volume (track->element, i, &vol); + if (track->volumes[i] != vol) + vol_changed = TRUE; + track->volumes[i] = vol; + if (!track->has_switch && + vol > track->min_volume) + audible = 1; + } + } + + update_mute (mixer, track, !audible); + } + + if (track->flags & GST_MIXER_TRACK_INPUT) { + int recording = 0; + if (track->has_switch) { + for (i = 0; i < track->num_channels; i++) { + int v = 0; + snd_mixer_selem_get_capture_switch (track->element, i, &v); + if (v) + recording = 1; + } + } + + if (track->has_volume) { + for (i = 0; i < track->num_channels; i++) { + long vol = 0; + snd_mixer_selem_get_capture_volume (track->element, i, &vol); + if (track->volumes[i] != vol) + vol_changed = TRUE; + track->volumes[i] = vol; + if (!track->has_switch && + vol > track->min_volume) + recording = 1; + } + } + + update_recording (mixer, track, recording); + } + + if (vol_changed) + notify_volume_change (mixer, track); +} + +static GstMixerTrack *track_new (snd_mixer_elem_t *element, int num, + int flags, gboolean append_capture) +{ + GstMixerTrack *track; + const char *name; + + track = (GstMixerTrack *) g_object_new (GST_TYPE_MIXER_TRACK, NULL); + track->index = snd_mixer_selem_get_index (element); + track->element = element; + track->flags = flags; + + if (flags & GST_MIXER_TRACK_OUTPUT) { + while (snd_mixer_selem_has_playback_channel (element, + track->num_channels)) + track->num_channels++; + } else if (flags & GST_MIXER_TRACK_INPUT) { + while (snd_mixer_selem_has_capture_channel (element, + track->num_channels)) + track->num_channels++; + } + + track->volumes = g_new (gint, track->num_channels); + + name = snd_mixer_selem_get_name (element); + track->untranslated_label = g_strdup (name); + + if (!num) + track->label = g_strdup_printf ("%s%s", name, + append_capture ? " Capture" : ""); + else + track->label = g_strdup_printf ("%s%s %d", name, + append_capture ? " Capture" : "", + num); + + return track; +} + +enum { + ARG_0, + ARG_LABEL, + ARG_UNTRANSLATED_LABEL, + ARG_INDEX, +}; + +static void gst_mixer_track_get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + GstMixerTrack *track = GST_MIXER_TRACK (object); + + switch (prop_id) { + case ARG_LABEL: + g_value_set_string (value, track->label); + break; + case ARG_UNTRANSLATED_LABEL: + g_value_set_string (value, track->untranslated_label); + break; + case ARG_INDEX: + g_value_set_uint (value, track->index); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void gst_mixer_track_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec * pspec) +{ + GstMixerTrack *track; + + track = GST_MIXER_TRACK (object); + + switch (prop_id) { + case ARG_LABEL: + g_free (track->label); + track->label = g_value_dup_string (value); + break; + case ARG_UNTRANSLATED_LABEL: + g_free (track->untranslated_label); + track->untranslated_label = g_value_dup_string (value); + break; + case ARG_INDEX: + track->index = g_value_get_uint (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void gst_mixer_track_dispose (GObject * object) +{ + GstMixerTrack *track = GST_MIXER_TRACK (object); + + if (track->label) { + g_free (track->label); + track->label = NULL; + } + + if (track->untranslated_label) { + g_free (track->untranslated_label); + track->untranslated_label = NULL; + } + + G_OBJECT_CLASS (gst_mixer_track_parent_class)->dispose (object); +} + +static void gst_mixer_track_class_init (GstMixerTrackClass * klass) +{ + GObjectClass *object_klass = G_OBJECT_CLASS (klass); + + object_klass->get_property = gst_mixer_track_get_property; + object_klass->set_property = gst_mixer_track_set_property; + + g_object_class_install_property (object_klass, ARG_UNTRANSLATED_LABEL, + g_param_spec_string ("untranslated-label", + "Untranslated track label", + "The untranslated label assigned to the track (since 0.10.13)", + NULL, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_klass, ARG_LABEL, + g_param_spec_string ("label", "Track label", + "The label assigned to the track (may be translated)", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_klass, ARG_INDEX, + g_param_spec_uint ("index", "Index", + "Track index", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + object_klass->dispose = gst_mixer_track_dispose; +} + +static void get_playback_min_max (GstMixerTrack *track) +{ + if (track->has_volume) { + long min = 0, max = 0; + snd_mixer_selem_get_playback_volume_range (track->element, &min, &max); + track->min_volume = min; + track->max_volume = max; + } +} + +static void get_capture_min_max (GstMixerTrack *track) +{ + if (track->has_volume) { + long min = 0, max = 0; + snd_mixer_selem_get_capture_volume_range (track->element, &min, &max); + track->min_volume = min; + track->max_volume = max; + } +} + +static GstMixerTrack *get_named_playback_track (GstMixer *mixer, + const char *name) +{ + GList *item; + GstMixerTrack *track; + + for (item = mixer->tracklist; item; item = item->next) { + track = GST_MIXER_TRACK (item->data); + if (! (track->flags & GST_MIXER_TRACK_OUTPUT)) + continue; + if (!strcmp (track->label, name)) + return track; + } + return NULL; +} + +static void mark_master_track (GstMixer *mixer) +{ + GList *item; + GstMixerTrack *track; + + if ((track = get_named_playback_track (mixer, "Master")) || + (track = get_named_playback_track (mixer, "Front")) || + (track = get_named_playback_track (mixer, "PCM")) || + (track = get_named_playback_track (mixer, "Speaker"))) + goto found; + + /* If not found, take a mono track with both volume and switch */ + for (item = mixer->tracklist; item; item = item->next) { + track = GST_MIXER_TRACK (item->data); + if (! (track->flags & GST_MIXER_TRACK_OUTPUT)) + continue; + if (track->has_volume && track->has_switch && + track->num_channels == 1) + goto found; + } + + /* If not found, take any track with both volume and switch */ + for (item = mixer->tracklist; item; item = item->next) { + track = GST_MIXER_TRACK (item->data); + if (! (track->flags & GST_MIXER_TRACK_OUTPUT)) + continue; + if (track->has_volume && track->has_switch) + goto found; + } + + /* If not found, take any track with volume */ + for (item = mixer->tracklist; item; item = item->next) { + track = GST_MIXER_TRACK (item->data); + if (! (track->flags & GST_MIXER_TRACK_OUTPUT)) + continue; + if (track->has_volume) + goto found; + } + + return; + + found: + track->flags |= GST_MIXER_TRACK_MASTER; + return; +} + +static int mixer_elem_callback (snd_mixer_elem_t *elem, unsigned int mask) +{ + GstMixer *mixer = snd_mixer_elem_get_callback_private (elem); + GList *item; + + for (item = mixer->tracklist; item; item = item->next) { + GstMixerTrack *track = GST_MIXER_TRACK (item->data); + if (track->element == elem) + track_update (mixer, track); + } + + return 0; +} + +static int mixer_callback (snd_mixer_t *ctl, unsigned int mask, + snd_mixer_elem_t *elem) +{ + GstMixer *mixer = snd_mixer_get_callback_private (ctl); + + snd_mixer_handle_events (mixer->handle); + return 0; +} + +const GList *gst_mixer_list_tracks (GstMixer *mixer) +{ + return mixer->tracklist; +} + +static gboolean same_volumes (gint num_channels, const gint *volumes) +{ + gint i; + + for (i = 1; i < num_channels; i++) { + if (volumes[0] != volumes[i]) + return FALSE; + } + + return TRUE; +} + +void gst_mixer_set_volume (GstMixer *mixer, GstMixerTrack *track, gint *volumes) +{ + gint i; + + track_update (mixer, track); + + if (!track->has_volume) + return; + + for (i = 0; i < track->num_channels; i++) + track->volumes[i] = volumes[i]; + + if (track->flags & GST_MIXER_TRACK_OUTPUT) { + if (!track->has_switch && (track->flags & GST_MIXER_TRACK_MUTE)) + return; + if (same_volumes (track->num_channels, volumes)) { + snd_mixer_selem_set_playback_volume_all (track->element, + volumes[0]); + } else { + for (i = 0; i < track->num_channels; i++) + snd_mixer_selem_set_playback_volume (track->element, i, + volumes[i]); + } + } else { + if (!track->has_switch && ! (track->flags & GST_MIXER_TRACK_RECORD)) + return; + if (same_volumes (track->num_channels, volumes)) { + snd_mixer_selem_set_capture_volume_all (track->element, + volumes[0]); + } else { + for (i = 0; i < track->num_channels; i++) + snd_mixer_selem_set_capture_volume (track->element, i, + volumes[i]); + } + } +} + +void gst_mixer_get_volume (GstMixer *mixer, GstMixerTrack *track, gint *volumes) +{ + int i; + + if (!track->has_volume) + return; + + track_update (mixer, track); + for (i = 0; i < track->num_channels; i++) + volumes[i] = track->volumes[i]; +} + +void gst_mixer_set_mute (GstMixer *mixer, GstMixerTrack *track, gboolean mute) +{ + int i; + + if (track->flags & GST_MIXER_TRACK_INPUT) { + if (track->shared_mute) + track = track->shared_mute; + else + return; + } + + track_update (mixer, track); + + mute = !!mute; + if (mute == !! (track->flags & GST_MIXER_TRACK_MUTE)) + return; + + update_mute (mixer, track, mute); + + if (track->has_switch) { + snd_mixer_selem_set_playback_switch_all (track->element, !mute); + } else { + for (i = 0; i < track->num_channels; i++) { + long vol = mute ? track->min_volume : track->volumes[i]; + snd_mixer_selem_set_playback_volume (track->element, i, vol); + } + } +} + +void gst_mixer_set_record (GstMixer * mixer, GstMixerTrack *track, gboolean record) +{ + int i; + + if (! (track->flags & GST_MIXER_TRACK_INPUT)) + return; + + track_update (mixer, track); + + record = !!record; + if (record == !! (track->flags & GST_MIXER_TRACK_RECORD)) + return; + + if (record) + track->flags |= GST_MIXER_TRACK_RECORD; + else + track->flags &= ~GST_MIXER_TRACK_RECORD; + + if (track->has_switch) { + snd_mixer_selem_set_capture_switch_all (track->element, record); + } else { + for (i = 0; i < track->num_channels; i++) { + long vol = record ? track->volumes[i] : track->min_volume; + snd_mixer_selem_set_capture_volume (track->element, i, vol); + } + } +} + +GstMixerMessageType +gst_mixer_message_get_type (GstMessage * message) +{ + const GstStructure *s; + const gchar *m_type; + + s = gst_message_get_structure (message); + m_type = gst_structure_get_string (s, "type"); + if (!m_type) + return GST_MIXER_MESSAGE_INVALID; + + if (g_str_equal (m_type, "mute-toggled")) + return GST_MIXER_MESSAGE_MUTE_TOGGLED; + else if (g_str_equal (m_type, "record-toggled")) + return GST_MIXER_MESSAGE_RECORD_TOGGLED; + else if (g_str_equal (m_type, "volume-changed")) + return GST_MIXER_MESSAGE_VOLUME_CHANGED; + else if (g_str_equal (m_type, "option-changed")) + return GST_MIXER_MESSAGE_OPTION_CHANGED; + else if (g_str_equal (m_type, "options-list-changed")) + return GST_MIXER_MESSAGE_OPTIONS_LIST_CHANGED; + else if (g_str_equal (m_type, "mixer-changed")) + return GST_MIXER_MESSAGE_MIXER_CHANGED; + + return GST_MIXER_MESSAGE_INVALID; +} + +static void message_parse_track (const GstStructure *s, GstMixerTrack **track) +{ + if (track) { + const GValue *v = gst_structure_get_value (s, "track"); + *track = (GstMixerTrack *)g_value_get_object (v); + } +} + +void gst_mixer_message_parse_mute_toggled (GstMessage *message, + GstMixerTrack **track, + gboolean *mute) +{ + const GstStructure *s = gst_message_get_structure (message); + + message_parse_track (s, track); + if (mute) + gst_structure_get_boolean (s, "mute", mute); +} + +void gst_mixer_message_parse_record_toggled (GstMessage *message, + GstMixerTrack **track, + gboolean *record) +{ + const GstStructure *s = gst_message_get_structure (message); + + message_parse_track (s, track); + if (record) + gst_structure_get_boolean (s, "record", record); +} + +void gst_mixer_message_parse_volume_changed (GstMessage *message, + GstMixerTrack **track, + gint **volumes, + gint *num_channels) +{ + const GstStructure *s = gst_message_get_structure (message); + + message_parse_track (s, track); + if (volumes || num_channels) { + gint n_chans, i; + const GValue *v = gst_structure_get_value (s, "volumes"); + + n_chans = gst_value_array_get_size (v); + if (num_channels) + *num_channels = n_chans; + + if (volumes) { + *volumes = g_new (gint, n_chans); + for (i = 0; i < n_chans; i++) { + const GValue *e = gst_value_array_get_value (v, i); + + (*volumes)[i] = g_value_get_int (e); + } + } + } +} + +/* + * GstMixerOptions + */ + +G_DEFINE_TYPE (GstMixerOptions, gst_mixer_options, GST_TYPE_MIXER_TRACK); + +static GstMixerOptions * +mixer_options_new (snd_mixer_elem_t *element, int num) +{ + GstMixerOptions *opt; + GstMixerTrack *track; + const char *label; + int i; + + label = snd_mixer_selem_get_name (element); + opt = g_object_new (GST_TYPE_MIXER_OPTIONS, + "untranslated-label", label, + "index", snd_mixer_selem_get_index (element), + NULL); + track = GST_MIXER_TRACK (opt); + track->element = element; + if (!num) + track->label = g_strdup (label); + else + track->label = g_strdup_printf ("%s %d", label, num); + + num = snd_mixer_selem_get_enum_items (element); + for (i = 0; i < num; i++) { + char str[256]; + if (snd_mixer_selem_get_enum_item_name (element, i, sizeof(str), str) < 0) + break; + opt->values = g_list_append (opt->values, g_strdup (str)); + } + + return opt; +} + +static void gst_mixer_options_dispose (GObject * object) +{ + GstMixerOptions *opt = GST_MIXER_OPTIONS (object); + + g_list_free_full (opt->values, g_free); + opt->values = NULL; + + G_OBJECT_CLASS (gst_mixer_options_parent_class)->dispose (object); +} + +static void gst_mixer_options_init (GstMixerOptions *opt) +{ +} + +static void gst_mixer_options_class_init (GstMixerOptionsClass * klass) +{ + GObjectClass *object_klass = G_OBJECT_CLASS (klass); + + object_klass->dispose = gst_mixer_options_dispose; +} + +const gchar *gst_mixer_get_option (GstMixer *mixer, GstMixerOptions *opt) +{ + unsigned int idx; + + if (snd_mixer_selem_get_enum_item (opt->parent.element, 0, &idx) < 0) + return "error"; + return g_list_nth_data (opt->values, idx); +} + +void gst_mixer_set_option (GstMixer *mixer, GstMixerOptions *opt, + gchar *value) +{ + int n = 0; + GList *item; + + for (item = opt->values; item; item = item->next, n++) { + if (!strcmp (item->data, value)) { + snd_mixer_selem_set_enum_item (opt->parent.element, 0, n); + break; + } + } +} + +GList *gst_mixer_options_get_values (GstMixerOptions *opt) +{ + return opt->values; +} + +static void message_parse_options (const GstStructure *s, + GstMixerOptions ** options) +{ + if (options) { + const GValue *v = gst_structure_get_value (s, "options"); + *options = (GstMixerOptions *) g_value_get_object (v); + } +} + +void gst_mixer_message_parse_option_changed (GstMessage *message, + GstMixerOptions ** options, + const gchar **value) +{ + const GstStructure *s = gst_message_get_structure (message); + + message_parse_options (s, options); + if (value) + *value = gst_structure_get_string (s, "value"); +} + +void gst_mixer_message_parse_options_list_changed (GstMessage *message, + GstMixerOptions **options) +{ + const GstStructure *s = gst_message_get_structure (message); + + message_parse_options (s, options); +} + +/* + */ + +static void create_track_list (GstMixer *mixer) +{ + snd_mixer_elem_t *element, *temp; + GList *item; + + if (mixer->tracklist) + return; + + for (element = snd_mixer_first_elem (mixer->handle); element; + element = snd_mixer_elem_next (element)) { + GstMixerTrack *play_track = NULL; + GstMixerTrack *cap_track = NULL; + const gchar *name = snd_mixer_selem_get_name (element); + int index = 0; + int has_volume, has_switch; + + for (item = mixer->tracklist; item; item = item->next) { + temp = GST_MIXER_TRACK (item->data)->element; + if (strcmp (name, snd_mixer_selem_get_name (temp)) == 0) + index++; + } + + has_volume = snd_mixer_selem_has_playback_volume (element); + has_switch = snd_mixer_selem_has_playback_switch (element); + if (has_volume || has_switch) { + play_track = track_new (element, index, + GST_MIXER_TRACK_OUTPUT, FALSE); + play_track->has_volume = has_volume; + play_track->has_switch = has_switch; + get_playback_min_max (play_track); + } + + has_volume = snd_mixer_selem_has_capture_volume (element); + has_switch = snd_mixer_selem_has_capture_switch (element); + if (play_track && snd_mixer_selem_has_common_volume (element)) + has_volume = 0; + if (play_track && snd_mixer_selem_has_common_switch (element)) + has_switch = 0; + if (has_volume || has_switch) { + cap_track = track_new (element, index, + GST_MIXER_TRACK_INPUT, + play_track != NULL); + cap_track->has_volume = has_volume; + cap_track->has_switch = has_switch; + get_capture_min_max (cap_track); + } + + if (play_track && cap_track) { + play_track->shared_mute = cap_track; + cap_track->shared_mute = play_track; + } + + if (play_track) { + track_update (mixer, play_track); + mixer->tracklist = g_list_append (mixer->tracklist, play_track); + } + + if (cap_track) { + track_update (mixer, cap_track); + mixer->tracklist = g_list_append (mixer->tracklist, cap_track); + } + + if (snd_mixer_selem_is_enumerated (element)) { + mixer->tracklist = g_list_append (mixer->tracklist, + mixer_options_new (element, index)); + } + + snd_mixer_elem_set_callback_private (element, mixer); + snd_mixer_elem_set_callback (element, mixer_elem_callback); + } + + mark_master_track (mixer); +} + +static gboolean mixer_src_callback (gpointer user_data) +{ + GstMixer *mixer = (GstMixer *)user_data; + + snd_mixer_handle_events (mixer->handle); + return TRUE; +} + +static gboolean mixer_src_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + return callback (user_data); +} + +static void mixer_src_attach (GstMixer *mixer) +{ + static GSourceFuncs func = { + .dispatch = mixer_src_dispatch, + }; + struct pollfd pfd; + + if (snd_mixer_poll_descriptors (mixer->handle, &pfd, 1) != 1) + return; + + mixer->src = g_source_new (&func, sizeof (*mixer->src)); + g_source_add_unix_fd (mixer->src, pfd.fd, G_IO_IN | G_IO_ERR); + g_source_set_callback (mixer->src, mixer_src_callback, mixer, NULL); + g_source_attach (mixer->src, g_main_context_default ()); +} + +/* + * These are new functions that didn't exist in the original gstreamer API; + * instead of lengthy probing using factory, just provide a simpler method + */ + +int gst_mixer_new (const char *name, GstMixer **mixer_ret) +{ + GstMixer *mixer; + snd_hctl_t *hctl; + int err; + + mixer = (GstMixer *) g_object_new (GST_TYPE_MIXER, NULL); + mixer->name = g_strdup (name); + + err = snd_mixer_open ((snd_mixer_t **) &mixer->handle, 0); + if (err < 0) + return err; + + err = snd_mixer_attach (mixer->handle, name); + if (err < 0) + goto error; + + err = snd_mixer_selem_register (mixer->handle, NULL, NULL); + if (err < 0) + goto error; + + err = snd_mixer_load (mixer->handle); + if (err < 0) + goto error; + + snd_mixer_get_hctl (mixer->handle, name, &hctl); + { + snd_ctl_card_info_t *info; + + snd_ctl_card_info_alloca (&info); + snd_ctl_card_info (snd_hctl_ctl (hctl), info); + mixer->card_name = g_strdup_printf ("%s (Alsa mixer)", + snd_ctl_card_info_get_name (info)); + } + + snd_mixer_set_callback_private (mixer->handle, mixer); + snd_mixer_set_callback (mixer->handle, mixer_callback); + + create_track_list (mixer); + + mixer_src_attach (mixer); + + *mixer_ret = mixer; + return 0; + + error: + gst_object_unref (mixer); + return err; +} + +GList *gst_mixer_probe_devices (void) +{ + int card = -1; + GList *card_list = NULL; + + while (snd_card_next(&card) >= 0 && card >= 0) { + GstMixer *mixer; + char name [16]; + int err; + + sprintf (name, "hw:%d", card); + err = gst_mixer_new (name, &mixer); + if (err < 0) + continue; + card_list = g_list_append (card_list, mixer); + } + + return card_list; +} + +const gchar *gst_mixer_get_card_name (GstMixer *mixer) +{ + return mixer->card_name; +} --- a/panel-plugin/xfce-plugin-dialog.c +++ b/panel-plugin/xfce-plugin-dialog.c @@ -25,8 +25,7 @@ #include -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/panel-plugin/xfce-plugin-dialog.h +++ b/panel-plugin/xfce-plugin-dialog.h @@ -24,8 +24,7 @@ #include -#include -#include +#include "../libxfce4mixer/audio.h" G_BEGIN_DECLS --- a/xfce4-mixer/xfce-mixer-container.c +++ b/xfce4-mixer/xfce-mixer-container.c @@ -23,8 +23,7 @@ #include #endif -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/xfce4-mixer/xfce-mixer-controls-dialog.c +++ b/xfce4-mixer/xfce-mixer-controls-dialog.c @@ -23,8 +23,7 @@ #include #endif -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/xfce4-mixer/xfce-mixer-option.c +++ b/xfce4-mixer/xfce-mixer-option.c @@ -23,8 +23,7 @@ #include #endif -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/xfce4-mixer/xfce-mixer-switch.c +++ b/xfce4-mixer/xfce-mixer-switch.c @@ -23,8 +23,7 @@ #include #endif -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/xfce4-mixer/xfce-mixer-track.c +++ b/xfce4-mixer/xfce-mixer-track.c @@ -27,8 +27,7 @@ #include #endif -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/xfce4-mixer/xfce-mixer-window.c +++ b/xfce4-mixer/xfce-mixer-window.c @@ -23,9 +23,7 @@ #include #endif -#include -#include -#include +#include "../libxfce4mixer/audio.h" #include #include --- a/libxfce4mixer/Makefile.am +++ b/libxfce4mixer/Makefile.am @@ -17,7 +17,14 @@ libxfce4mixer_la_SOURCES = \ xfce-mixer-preferences.h \ xfce-mixer-preferences.c \ xfce-mixer-debug.h \ - xfce-mixer-debug.c + xfce-mixer-debug.c \ + audio.h + +if XFCE4_MIXER_ALSA +libxfce4mixer_la_SOURCES += \ + xfce4-mixer-alsa.h \ + xfce4-mixer-alsa.c +endif libxfce4mixer_la_CPPFLAGS = \ -I$(top_builddir) \ @@ -35,7 +42,12 @@ libxfce4mixer_la_CFLAGS = \ $(LIBXFCE4UI_CFLAGS) \ $(XFCONF_CFLAGS) \ $(DBUS_GLIB_CFLAGS) \ - $(GST_PLUGINS_BASE_CFLAGS) + $(GST_CFLAGS) + +if XFCE4_MIXER_ALSA +libxfce4mixer_la_CFLAGS += \ + $(ALSA_CFLAGS) +endif libxfce4mixer_la_LDFLAGS = \ -no-undefined @@ -48,6 +60,13 @@ libxfce4mixer_la_LIBADD = \ $(LIBXFCE4UI_LIBS) \ $(XFCONF_LIBS) \ $(DBUS_GLIB_LIBS) \ - $(GST_PLUGINS_BASE_LIBS) \ + $(GST_LIBS) + +if XFCE4_MIXER_ALSA +libxfce4mixer_la_LIBADD += \ + $(ALSA_LIBS) +else +libxfce4mixer_la_LIBADD += \ -lgstaudio-0.10 \ -lgstinterfaces-0.10 +endif --- a/panel-plugin/Makefile.am +++ b/panel-plugin/Makefile.am @@ -27,7 +27,7 @@ libmixer_la_CFLAGS = \ $(LIBXFCE4UI_CFLAGS) \ $(LIBXFCE4PANEL_CFLAGS) \ $(XFCONF_CFLAGS) \ - $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_CFLAGS) \ $(KEYBINDER_CFLAGS) libmixer_la_DEPENDENCIES = \ @@ -48,11 +48,18 @@ libmixer_la_LIBADD = \ $(LIBXFCE4UI_LIBS) \ $(LIBXFCE4PANEL_LIBS) \ $(XFCONF_LIBS) \ - $(GST_PLUGINS_BASE_LIBS) \ - -lgstaudio-0.10 \ - -lgstinterfaces-0.10 \ + $(GST_LIBS) \ $(KEYBINDER_LIBS) +if XFCE4_MIXER_ALSA +libmixer_la_LIBADD += \ + $(ALSA_LIBS) +else +libmixer_la_LIBADD += \ + -lgstaudio-0.10 \ + -lgstinterfaces-0.10 +endif + desktopdir = $(datadir)/xfce4/panel/plugins desktop_in_files = mixer.desktop.in --- a/xfce4-mixer/Makefile.am +++ b/xfce4-mixer/Makefile.am @@ -34,7 +34,7 @@ xfce4_mixer_CFLAGS = \ $(LIBXFCE4UTIL_CFLAGS) \ $(LIBXFCE4UI_CFLAGS) \ $(XFCONF_CFLAGS) \ - $(GST_PLUGINS_BASE_CFLAGS) + $(GST_CFLAGS) xfce4_mixer_DEPENDENCIES = \ $(top_builddir)/libxfce4mixer/libxfce4mixer.la @@ -47,9 +47,16 @@ xfce4_mixer_LDFLAGS = \ $(LIBXFCE4UTIL_LIBS) \ $(LIBXFCE4UI_LIBS) \ $(XFCONF_LIBS) \ - $(GST_PLUGINS_BASE_LIBS) \ + $(GST_LIBS) + +if XFCE4_MIXER_ALSA +xfce4_mixer_LDFLAGS += \ + $(ALSA_LIBS) +else +xfce4_mixer_LDFLAGS += \ -lgstaudio-0.10 \ -lgstinterfaces-0.10 +endif dist_man_MANS = xfce4-mixer.1 --- /dev/null +++ b/libxfce4mixer/audio.h @@ -0,0 +1,8 @@ +#ifdef XFCE4_MIXER_ALSA +#include +#include "xfce4-mixer-alsa.h" +#else +#include +#include +#include +#endif --- a/configure.ac.in +++ b/configure.ac.in @@ -96,13 +96,29 @@ dnl *********************************** XDT_CHECK_PACKAGE([GLIB], [glib-2.0], [2.42.0]) XDT_CHECK_PACKAGE([GTHREAD], [gthread-2.0], [2.42.0]) XDT_CHECK_PACKAGE([DBUS_GLIB], [dbus-glib-1], [0.84]) -XDT_CHECK_PACKAGE([GST_PLUGINS_BASE], [gstreamer-plugins-base-0.10], [0.10.25]) XDT_CHECK_PACKAGE([GTK], [gtk+-3.0], [3.14.0]) XDT_CHECK_PACKAGE([LIBXFCE4UTIL], [libxfce4util-1.0], [4.12.0]) XDT_CHECK_PACKAGE([LIBXFCE4UI], [libxfce4ui-2], [4.12.0]) XDT_CHECK_PACKAGE([LIBXFCE4PANEL], [libxfce4panel-2.0], [4.12.0]) XDT_CHECK_PACKAGE([XFCONF], [libxfconf-0], [4.12.0]) +AC_MSG_CHECKING(for audio engine) +AC_ARG_WITH(audio, + AS_HELP_STRING([--with-audio], + [audio engine, either gstmixer or alsa (default)]), + audio="$withval", audio="alsa") + +if test "$audio" = "gstmixer"; then + AC_MSG_RESULT(gstreamer-0.10 native mixer) + XDT_CHECK_PACKAGE([GST], [gstreamer-plugins-base-0.10], [0.10.25]) +else + AC_MSG_RESULT(gstreamer-1.0 ALSA mixer) + XDT_CHECK_PACKAGE([GST], [gstreamer-1.0], [1.0]) + XDT_CHECK_PACKAGE([ALSA], [alsa], [0.9]) + AC_DEFINE([XFCE4_MIXER_ALSA], 1, [Built-in ALSA-based gstreamer mixer i/f]) +fi +AM_CONDITIONAL([XFCE4_MIXER_ALSA], [test x"$audio" != x"gstmixer"]) + dnl *********************************** dnl *** Check for optional packages *** dnl