cabd6b73ab7c1755971a08ecd79ddecee1a9cd3e
[platform/upstream/gstreamer.git] / sys / oss4 / oss4-mixer.c
1 /* GStreamer OSS4 mixer implementation
2  * Copyright (C) 2007-2008 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with mixer library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /**
21  * SECTION:element-oss4mixer
22  *
23  * This element lets you adjust sound input and output levels with the
24  * Open Sound System (OSS) version 4. It supports the GstMixer interface, which
25  * can be used to obtain a list of available mixer tracks. Set the mixer
26  * element to READY state before using the GstMixer interface on it.
27  * 
28  * <refsect2>
29  * <title>Example pipelines</title>
30  * <para>
31  * oss4mixer can&apos;t be used in a sensible way in gst-launch.
32  * </para>
33  * </refsect2>
34  *
35  * Since: 0.10.7
36  */
37
38 /* Note: ioctl calls on the same open mixer device are serialised via
39  * the object lock to make sure we don't do concurrent ioctls from two
40  * different threads (e.g. app thread and mixer watch thread), since that
41  * will probably confuse OSS. */
42
43 #ifdef HAVE_CONFIG_H
44 #include "config.h"
45 #endif
46
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <fcntl.h>
50 #include <unistd.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <sys/ioctl.h>
54
55 #include <gst/interfaces/mixer.h>
56 #include <gst/gst-i18n-plugin.h>
57
58 #include <glib/gprintf.h>
59
60 #define NO_LEGACY_MIXER
61
62 #include "oss4-audio.h"
63 #include "oss4-mixer.h"
64 #include "oss4-mixer-enum.h"
65 #include "oss4-mixer-slider.h"
66 #include "oss4-mixer-switch.h"
67 #include "oss4-property-probe.h"
68 #include "oss4-soundcard.h"
69
70 #define GST_OSS4_MIXER_WATCH_INTERVAL   500     /* in millisecs, ie. 0.5s */
71
72 GST_DEBUG_CATEGORY_EXTERN (oss4mixer_debug);
73 #define GST_CAT_DEFAULT oss4mixer_debug
74
75 #define DEFAULT_DEVICE       NULL
76 #define DEFAULT_DEVICE_NAME  NULL
77
78 enum
79 {
80   PROP_0,
81   PROP_DEVICE,
82   PROP_DEVICE_NAME
83 };
84
85 static void gst_oss4_mixer_init_interfaces (GType type);
86
87 GST_BOILERPLATE_FULL (GstOss4Mixer, gst_oss4_mixer, GstElement,
88     GST_TYPE_ELEMENT, gst_oss4_mixer_init_interfaces);
89
90 static GstStateChangeReturn gst_oss4_mixer_change_state (GstElement *
91     element, GstStateChange transition);
92
93 static void gst_oss4_mixer_set_property (GObject * object, guint prop_id,
94     const GValue * value, GParamSpec * pspec);
95 static void gst_oss4_mixer_get_property (GObject * object, guint prop_id,
96     GValue * value, GParamSpec * pspec);
97 static void gst_oss4_mixer_finalize (GObject * object);
98
99 static gboolean gst_oss4_mixer_open (GstOss4Mixer * mixer,
100     gboolean silent_errors);
101 static void gst_oss4_mixer_close (GstOss4Mixer * mixer);
102
103 static gboolean gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * m,
104     GstOss4MixerControl * mc);
105
106 #ifndef GST_DISABLE_GST_DEBUG
107 static const gchar *mixer_ext_type_get_name (gint type);
108 static const gchar *mixer_ext_flags_get_string (gint flags);
109 #endif
110
111 static void
112 gst_oss4_mixer_base_init (gpointer klass)
113 {
114   gst_element_class_set_details_simple (GST_ELEMENT_CLASS (klass),
115       "OSS v4 Audio Mixer", "Generic/Audio",
116       "Control sound input and output levels with OSS4",
117       "Tim-Philipp Müller <tim centricular net>");
118 }
119
120 static void
121 gst_oss4_mixer_class_init (GstOss4MixerClass * klass)
122 {
123   GstElementClass *element_class;
124   GObjectClass *gobject_class;
125
126   element_class = (GstElementClass *) klass;
127   gobject_class = (GObjectClass *) klass;
128
129   gobject_class->finalize = gst_oss4_mixer_finalize;
130   gobject_class->set_property = gst_oss4_mixer_set_property;
131   gobject_class->get_property = gst_oss4_mixer_get_property;
132
133   /**
134    * GstOss4Mixer:device
135    *
136    * OSS4 mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN)
137    *
138    **/
139   g_object_class_install_property (gobject_class, PROP_DEVICE,
140       g_param_spec_string ("device", "Device",
141           "OSS mixer device (e.g. /dev/oss/hdaudio0/mix0 or /dev/mixerN) "
142           "(NULL = use first mixer device found)", DEFAULT_DEVICE,
143           G_PARAM_READWRITE));
144
145   /**
146    * GstOss4Mixer:device-name
147    *
148    * Human-readable name of the sound device. May be NULL if the device is
149    * not open (ie. when the mixer is in NULL state)
150    *
151    **/
152   g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
153       g_param_spec_string ("device-name", "Device name",
154           "Human-readable name of the sound device", DEFAULT_DEVICE_NAME,
155           G_PARAM_READABLE));
156
157   element_class->change_state = GST_DEBUG_FUNCPTR (gst_oss4_mixer_change_state);
158 }
159
160 static void
161 gst_oss4_mixer_finalize (GObject * obj)
162 {
163   GstOss4Mixer *mixer = GST_OSS4_MIXER (obj);
164
165   g_free (mixer->device);
166
167   G_OBJECT_CLASS (parent_class)->finalize (obj);
168 }
169
170 static void
171 gst_oss4_mixer_reset (GstOss4Mixer * mixer)
172 {
173   mixer->fd = -1;
174   mixer->need_update = TRUE;
175   memset (&mixer->last_mixext, 0, sizeof (oss_mixext));
176 }
177
178 static void
179 gst_oss4_mixer_init (GstOss4Mixer * mixer, GstOss4MixerClass * g_class)
180 {
181   mixer->device = g_strdup (DEFAULT_DEVICE);
182   mixer->device_name = NULL;
183
184   gst_oss4_mixer_reset (mixer);
185 }
186
187 static void
188 gst_oss4_mixer_set_property (GObject * object, guint prop_id,
189     const GValue * value, GParamSpec * pspec)
190 {
191   GstOss4Mixer *mixer = GST_OSS4_MIXER (object);
192
193   switch (prop_id) {
194     case PROP_DEVICE:
195       GST_OBJECT_LOCK (mixer);
196       if (!GST_OSS4_MIXER_IS_OPEN (mixer)) {
197         g_free (mixer->device);
198         mixer->device = g_value_dup_string (value);
199         /* unset any cached device-name */
200         g_free (mixer->device_name);
201         mixer->device_name = NULL;
202       } else {
203         g_warning ("%s: can't change \"device\" property while mixer is open",
204             GST_OBJECT_NAME (mixer));
205       }
206       GST_OBJECT_UNLOCK (mixer);
207       break;
208     default:
209       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
210       break;
211   }
212 }
213
214 static void
215 gst_oss4_mixer_get_property (GObject * object, guint prop_id,
216     GValue * value, GParamSpec * pspec)
217 {
218   GstOss4Mixer *mixer = GST_OSS4_MIXER (object);
219
220   switch (prop_id) {
221     case PROP_DEVICE:
222       GST_OBJECT_LOCK (mixer);
223       g_value_set_string (value, mixer->device);
224       GST_OBJECT_UNLOCK (mixer);
225       break;
226     case PROP_DEVICE_NAME:
227       GST_OBJECT_LOCK (mixer);
228       /* If device is set, try to retrieve the name even if we're not open */
229       if (mixer->fd == -1 && mixer->device != NULL) {
230         if (gst_oss4_mixer_open (mixer, TRUE)) {
231           g_value_set_string (value, mixer->device_name);
232           gst_oss4_mixer_close (mixer);
233         } else {
234           g_value_set_string (value, mixer->device_name);
235         }
236       } else {
237         g_value_set_string (value, mixer->device_name);
238       }
239       GST_OBJECT_UNLOCK (mixer);
240       break;
241     default:
242       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
243       break;
244   }
245 }
246
247 static gboolean
248 gst_oss4_mixer_open (GstOss4Mixer * mixer, gboolean silent_errors)
249 {
250   struct oss_mixerinfo mi = { 0, };
251   gchar *device;
252
253   g_return_val_if_fail (!GST_OSS4_MIXER_IS_OPEN (mixer), FALSE);
254
255   if (mixer->device)
256     device = g_strdup (mixer->device);
257   else
258     device = gst_oss4_audio_find_device (GST_OBJECT_CAST (mixer));
259
260   /* desperate times, desperate measures */
261   if (device == NULL)
262     device = g_strdup ("/dev/mixer");
263
264   GST_INFO_OBJECT (mixer, "Trying to open OSS4 mixer device '%s'", device);
265
266   mixer->fd = open (device, O_RDWR, 0);
267   if (mixer->fd < 0)
268     goto open_failed;
269
270   /* Make sure it's OSS4. If it's old OSS, let the old ossmixer handle it */
271   if (!gst_oss4_audio_check_version (GST_OBJECT (mixer), mixer->fd))
272     goto legacy_oss;
273
274   GST_INFO_OBJECT (mixer, "Opened mixer device '%s', which is mixer %d",
275       device, mi.dev);
276
277   /* Get device name for currently open fd */
278   mi.dev = -1;
279   if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) {
280     mixer->modify_counter = mi.modify_counter;
281     if (mi.name[0] != '\0') {
282       mixer->device_name = g_strdup (mi.name);
283     }
284   } else {
285     mixer->modify_counter = 0;
286   }
287
288   if (mixer->device_name == NULL) {
289     mixer->device_name = g_strdup ("Unknown");
290   }
291   GST_INFO_OBJECT (mixer, "device name = '%s'", mixer->device_name);
292
293   mixer->open_device = device;
294
295   return TRUE;
296
297   /* ERRORS */
298 open_failed:
299   {
300     if (!silent_errors) {
301       GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE,
302           (_("Could not open audio device for mixer control handling.")),
303           GST_ERROR_SYSTEM);
304     } else {
305       GST_DEBUG_OBJECT (mixer, "open failed: %s (ignoring errors)",
306           g_strerror (errno));
307     }
308     g_free (device);
309     return FALSE;
310   }
311 legacy_oss:
312   {
313     gst_oss4_mixer_close (mixer);
314     if (!silent_errors) {
315       GST_ELEMENT_ERROR (mixer, RESOURCE, OPEN_READ_WRITE,
316           (_("Could not open audio device for mixer control handling. "
317                   "This version of the Open Sound System is not supported by this "
318                   "element.")), ("Try the 'ossmixer' element instead"));
319     } else {
320       GST_DEBUG_OBJECT (mixer, "open failed: legacy oss (ignoring errors)");
321     }
322     g_free (device);
323     return FALSE;
324   }
325 }
326
327 static void
328 gst_oss4_mixer_control_free (GstOss4MixerControl * mc)
329 {
330   g_list_free (mc->children);
331   g_list_free (mc->mute_group);
332   g_free (mc->enum_vals);
333   memset (mc, 0, sizeof (GstOss4MixerControl));
334   g_free (mc);
335 }
336
337 static void
338 gst_oss4_mixer_free_tracks (GstOss4Mixer * mixer)
339 {
340   g_list_foreach (mixer->tracks, (GFunc) g_object_unref, NULL);
341   g_list_free (mixer->tracks);
342   mixer->tracks = NULL;
343
344   g_list_foreach (mixer->controls, (GFunc) gst_oss4_mixer_control_free, NULL);
345   g_list_free (mixer->controls);
346   mixer->controls = NULL;
347 }
348
349 static void
350 gst_oss4_mixer_close (GstOss4Mixer * mixer)
351 {
352   g_free (mixer->device_name);
353   mixer->device_name = NULL;
354
355   g_free (mixer->open_device);
356   mixer->open_device = NULL;
357
358   gst_oss4_mixer_free_tracks (mixer);
359
360   if (mixer->fd != -1) {
361     close (mixer->fd);
362     mixer->fd = -1;
363   }
364
365   gst_oss4_mixer_reset (mixer);
366 }
367
368 static void
369 gst_oss4_mixer_watch_process_changes (GstOss4Mixer * mixer)
370 {
371   GList *c, *t, *tracks = NULL;
372
373   GST_INFO_OBJECT (mixer, "mixer interface or control changed");
374
375   /* this is all with the mixer object lock held */
376
377   /* we go through the list backwards so we can bail out faster when the entire
378    * interface needs to be rebuilt */
379   for (c = g_list_last (mixer->controls); c != NULL; c = c->prev) {
380     GstOss4MixerControl *mc = c->data;
381     oss_mixer_value ossval = { 0, };
382
383     mc->changed = FALSE;
384     mc->list_changed = FALSE;
385
386     /* not interested in controls we don't expose in the mixer interface */
387     if (!mc->used)
388       continue;
389
390     /* don't try to read a value from controls that don't have one */
391     if (mc->mixext.type == MIXT_DEVROOT || mc->mixext.type == MIXT_GROUP)
392       continue;
393
394     /* is this an enum control whose list may change? */
395     if (mc->mixext.type == MIXT_ENUM && mc->enum_version != 0) {
396       if (gst_oss4_mixer_enum_control_update_enum_list (mixer, mc))
397         mc->list_changed = TRUE;
398     }
399
400     ossval.dev = mc->mixext.dev;
401     ossval.ctrl = mc->mixext.ctrl;
402     ossval.timestamp = mc->mixext.timestamp;
403
404     if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) {
405       if (errno == EIDRM || errno == EFAULT) {
406         GST_DEBUG ("%s has disappeared", mc->mixext.extname);
407         goto mixer_changed;
408       }
409       GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno));
410       /* just ignore, move on to next one */
411       continue;
412     }
413
414     if (ossval.value == mc->last_val) { /* no change */
415       /* GST_LOG_OBJECT (mixer, "%s hasn't changed", mc->mixext.extname); */
416       continue;
417     }
418
419     mc->last_val = ossval.value;
420     GST_LOG_OBJECT (mixer, "%s changed value to %u 0x%08x",
421         mc->mixext.extname, ossval.value, ossval.value);
422     mc->changed = TRUE;
423   }
424
425   /* copy list and take track refs, so we can safely drop the object lock,
426    * which we need to do to be able to post messages on the bus */
427   tracks = g_list_copy (mixer->tracks);
428   g_list_foreach (tracks, (GFunc) g_object_ref, NULL);
429
430   GST_OBJECT_UNLOCK (mixer);
431
432   /* since we don't know (or want to know exactly) which controls belong to
433    * which track, we just go through the tracks one-by-one now and make them
434    * check themselves if any of their controls have changed and which messages
435    * to post on the bus as a result */
436   for (t = tracks; t != NULL; t = t->next) {
437     GstMixerTrack *track = t->data;
438
439     if (GST_IS_OSS4_MIXER_SLIDER (track)) {
440       gst_oss4_mixer_slider_process_change_unlocked (track);
441     } else if (GST_IS_OSS4_MIXER_SWITCH (track)) {
442       gst_oss4_mixer_switch_process_change_unlocked (track);
443     } else if (GST_IS_OSS4_MIXER_ENUM (track)) {
444       gst_oss4_mixer_enum_process_change_unlocked (track);
445     }
446
447     g_object_unref (track);
448   }
449   g_list_free (tracks);
450
451   GST_OBJECT_LOCK (mixer);
452   return;
453
454 mixer_changed:
455   {
456     GST_OBJECT_UNLOCK (mixer);
457     gst_mixer_mixer_changed (GST_MIXER (mixer));
458     GST_OBJECT_LOCK (mixer);
459     return;
460   }
461 }
462
463 /* This thread watches the mixer for changes in a somewhat inefficient way
464  * (running an ioctl every half second or so). This is still better and
465  * cheaper than apps polling all tracks for changes a few times a second
466  * though. Needs more thought. There's probably (hopefully) a way to get
467  * notifications via the fd directly somehow. */
468 static gpointer
469 gst_oss4_mixer_watch_thread (gpointer thread_data)
470 {
471   GstOss4Mixer *mixer = GST_OSS4_MIXER_CAST (thread_data);
472
473   GST_DEBUG_OBJECT (mixer, "watch thread running");
474
475   GST_OBJECT_LOCK (mixer);
476   while (!mixer->watch_shutdown) {
477     oss_mixerinfo mi = { 0, };
478     GTimeVal tv;
479
480     mi.dev = -1;
481     if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == 0) {
482       if (mixer->modify_counter != mi.modify_counter) {
483         /* GST_LOG ("processing changes"); */
484         gst_oss4_mixer_watch_process_changes (mixer);
485         mixer->modify_counter = mi.modify_counter;
486       } else {
487         /* GST_LOG ("no changes"); */
488       }
489     } else {
490       GST_WARNING_OBJECT (mixer, "MIXERINFO failed: %s", g_strerror (errno));
491     }
492
493     /* we could move the _get_current_time out of the loop and just do the
494      * add in ever iteration, which would be less exact, but who cares */
495     g_get_current_time (&tv);
496     g_time_val_add (&tv, GST_OSS4_MIXER_WATCH_INTERVAL * 1000);
497     (void) g_cond_timed_wait (mixer->watch_cond, GST_OBJECT_GET_LOCK (mixer),
498         &tv);
499   }
500   GST_OBJECT_UNLOCK (mixer);
501
502   GST_DEBUG_OBJECT (mixer, "watch thread done");
503   gst_object_unref (mixer);
504   return NULL;
505 }
506
507 /* call with object lock held */
508 static void
509 gst_oss4_mixer_wake_up_watch_task (GstOss4Mixer * mixer)
510 {
511   GST_LOG_OBJECT (mixer, "signalling watch thread to wake up");
512   g_cond_signal (mixer->watch_cond);
513 }
514
515 static void
516 gst_oss4_mixer_stop_watch_task (GstOss4Mixer * mixer)
517 {
518   if (mixer->watch_thread) {
519     GST_OBJECT_LOCK (mixer);
520     mixer->watch_shutdown = TRUE;
521     GST_LOG_OBJECT (mixer, "signalling watch thread to stop");
522     g_cond_signal (mixer->watch_cond);
523     GST_OBJECT_UNLOCK (mixer);
524     GST_LOG_OBJECT (mixer, "waiting for watch thread to join");
525     g_thread_join (mixer->watch_thread);
526     GST_DEBUG_OBJECT (mixer, "watch thread stopped");
527     mixer->watch_thread = NULL;
528   }
529
530   if (mixer->watch_cond) {
531     g_cond_free (mixer->watch_cond);
532     mixer->watch_cond = NULL;
533   }
534 }
535
536 static void
537 gst_oss4_mixer_start_watch_task (GstOss4Mixer * mixer)
538 {
539   GError *err = NULL;
540
541   mixer->watch_cond = g_cond_new ();
542   mixer->watch_shutdown = FALSE;
543
544   mixer->watch_thread = g_thread_create (gst_oss4_mixer_watch_thread,
545       gst_object_ref (mixer), TRUE, &err);
546
547   if (mixer->watch_thread == NULL) {
548     GST_ERROR_OBJECT (mixer, "Could not create watch thread: %s", err->message);
549     g_cond_free (mixer->watch_cond);
550     mixer->watch_cond = NULL;
551     g_error_free (err);
552   }
553 }
554
555 static GstStateChangeReturn
556 gst_oss4_mixer_change_state (GstElement * element, GstStateChange transition)
557 {
558   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
559   GstOss4Mixer *mixer = GST_OSS4_MIXER (element);
560
561   switch (transition) {
562     case GST_STATE_CHANGE_NULL_TO_READY:
563       if (!gst_oss4_mixer_open (mixer, FALSE))
564         return GST_STATE_CHANGE_FAILURE;
565       gst_oss4_mixer_start_watch_task (mixer);
566       break;
567     default:
568       break;
569   }
570
571   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
572   if (ret == GST_STATE_CHANGE_FAILURE)
573     return ret;
574
575   switch (transition) {
576     case GST_STATE_CHANGE_READY_TO_NULL:
577       gst_oss4_mixer_stop_watch_task (mixer);
578       gst_oss4_mixer_close (mixer);
579       break;
580     default:
581       break;
582   }
583
584   return ret;
585 }
586
587 /* === GstMixer interface === */
588
589 static inline gboolean
590 gst_oss4_mixer_contains_track (GstMixer * mixer, GstMixerTrack * track)
591 {
592   return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, track) != NULL);
593 }
594
595 static inline gboolean
596 gst_oss4_mixer_contains_options (GstMixer * mixer, GstMixerOptions * options)
597 {
598   return (g_list_find (GST_OSS4_MIXER (mixer)->tracks, options) != NULL);
599 }
600
601 static void
602 gst_oss4_mixer_post_mixer_changed_msg (GstOss4Mixer * mixer)
603 {
604   /* only post mixer-changed message once */
605   if (!mixer->need_update) {
606     gst_mixer_mixer_changed (GST_MIXER (mixer));
607     mixer->need_update = TRUE;
608   }
609 }
610
611 /* call with mixer object lock held to serialise ioctl */
612 gboolean
613 gst_oss4_mixer_get_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc,
614     int *val)
615 {
616   oss_mixer_value ossval = { 0, };
617
618   if (GST_OBJECT_TRYLOCK (mixer)) {
619     GST_ERROR ("must be called with mixer lock held");
620     GST_OBJECT_UNLOCK (mixer);
621   }
622
623   ossval.dev = mc->mixext.dev;
624   ossval.ctrl = mc->mixext.ctrl;
625   ossval.timestamp = mc->mixext.timestamp;
626
627   if (ioctl (mixer->fd, SNDCTL_MIX_READ, &ossval) == -1) {
628     if (errno == EIDRM) {
629       GST_DEBUG_OBJECT (mixer, "MIX_READ failed: mixer interface has changed");
630       gst_oss4_mixer_post_mixer_changed_msg (mixer);
631     } else {
632       GST_WARNING_OBJECT (mixer, "MIX_READ failed: %s", g_strerror (errno));
633     }
634     *val = 0;
635     mc->last_val = 0;
636     return FALSE;
637   }
638
639   *val = ossval.value;
640   mc->last_val = ossval.value;
641   GST_LOG_OBJECT (mixer, "got value 0x%08x from %s)", *val, mc->mixext.extname);
642   return TRUE;
643 }
644
645 /* call with mixer object lock held to serialise ioctl */
646 gboolean
647 gst_oss4_mixer_set_control_val (GstOss4Mixer * mixer, GstOss4MixerControl * mc,
648     int val)
649 {
650   oss_mixer_value ossval = { 0, };
651
652   ossval.dev = mc->mixext.dev;
653   ossval.ctrl = mc->mixext.ctrl;
654   ossval.timestamp = mc->mixext.timestamp;
655   ossval.value = val;
656
657   if (GST_OBJECT_TRYLOCK (mixer)) {
658     GST_ERROR ("must be called with mixer lock held");
659     GST_OBJECT_UNLOCK (mixer);
660   }
661
662   if (ioctl (mixer->fd, SNDCTL_MIX_WRITE, &ossval) == -1) {
663     if (errno == EIDRM) {
664       GST_LOG_OBJECT (mixer, "MIX_WRITE failed: mixer interface has changed");
665       gst_oss4_mixer_post_mixer_changed_msg (mixer);
666     } else {
667       GST_WARNING_OBJECT (mixer, "MIX_WRITE failed: %s", g_strerror (errno));
668     }
669     return FALSE;
670   }
671
672   mc->last_val = val;
673   GST_LOG_OBJECT (mixer, "set value 0x%08x on %s", val, mc->mixext.extname);
674   return TRUE;
675 }
676
677 #if 0
678 static gchar *
679 gst_oss4_mixer_control_get_pretty_name (GstOss4MixerControl * mc)
680 {
681   gchar *name;
682
683   const gchar *name, *u;
684
685   /* "The id field is the original name given by the driver when it called
686    * mixer_ext_create_control. This name can be used by fully featured GUI
687    * mixers. However this name should be downshifted and cut before the last
688    * underscore ("_") to get the proper name. For example mixer control name
689    * created as "MYDRV_MAINVOL" will become just "mainvol" after this
690    * transformation." */
691   name = mc->mixext.extname;
692   u = MAX (strrchr (name, '_'), strrchr (name, '.'));
693   if (u != NULL)
694     name = u + 1;
695
696   /* maybe capitalize the first letter? */
697   return g_ascii_strdown (name, -1);
698   /* the .id thing doesn't really seem to work right, ie. for some sliders
699    * it's just '-' so you have to use the name of the parent control etc.
700    * let's not use it for now, much too painful. */
701   if (g_str_has_prefix (mc->mixext.extname, "misc."))
702     name = g_strdup (mc->mixext.extname + 5);
703   else
704     name = g_strdup (mc->mixext.extname);
705   /* chop off trailing digit (only one for now) */
706   if (strlen (name) > 0 && g_ascii_isdigit (name[strlen (name) - 1]))
707     name[strlen (name) - 1] = '\0';
708   g_strdelimit (name, ".", ' ');
709   return name;
710 }
711 #endif
712
713 /* these translations are a bit ad-hoc and horribly incomplete; it is not
714  * really going to work this way with all the different chipsets and drivers.
715  * We also use these for translating option values. */
716 static struct
717 {
718   const gchar oss_name[32];
719   const gchar *label;
720 } labels[] = {
721   {
722   "volume", N_("Volume")}, {
723   "master", N_("Master")}, {
724   "front", N_("Front")}, {
725   "rear", N_("Rear")}, {
726   "headphones", N_("Headphones")}, {
727   "center", N_("Center")}, {
728   "lfe", N_("LFE")}, {
729   "surround", N_("Surround")}, {
730   "side", N_("Side")}, {
731   "speaker", N_("Built-in Speaker")}, {
732   "aux1-out", N_("AUX 1 Out")}, {
733   "aux2-out", N_("AUX 2 Out")}, {
734   "aux-out", N_("AUX Out")}, {
735   "bass", N_("Bass")}, {
736   "treble", N_("Treble")}, {
737   "3d-depth", N_("3D Depth")}, {
738   "3d-center", N_("3D Center")}, {
739   "3d-enhance", N_("3D Enhance")}, {
740   "phone", N_("Telephone")}, {
741   "mic", N_("Microphone")}, {
742   "line-out", N_("Line Out")}, {
743   "line-in", N_("Line In")}, {
744   "linein", N_("Line In")}, {
745   "cd", N_("Internal CD")}, {
746   "video", N_("Video In")}, {
747   "aux1-in", N_("AUX 1 In")}, {
748   "aux2-in", N_("AUX 2 In")}, {
749   "aux-in", N_("AUX In")}, {
750   "pcm", N_("PCM")}, {
751   "record-gain", N_("Record Gain")}, {
752   "igain", N_("Record Gain")}, {
753   "ogain", N_("Output Gain")}, {
754   "micboost", N_("Microphone Boost")}, {
755   "loopback", N_("Loopback")}, {
756   "diag", N_("Diagnostic")}, {
757   "loudness", N_("Bass Boost")}, {
758   "outputs", N_("Playback Ports")}, {
759   "input", N_("Input")}, {
760   "inputs", N_("Record Source")}, {
761   "record-source", N_("Record Source")}, {
762   "monitor-source", N_("Monitor Source")}, {
763   "beep", N_("Keyboard Beep")}, {
764   "monitor-gain", N_("Monitor")}, {
765   "stereo-simulate", N_("Simulate Stereo")}, {
766   "stereo", N_("Stereo")}, {
767   "multich", N_("Surround Sound")}, {
768   "mic-gain", N_("Microphone Gain")}, {
769   "speaker-source", N_("Speaker Source")}, {
770   "mic-source", N_("Microphone Source")}, {
771   "jack", N_("Jack")}, {
772   "center/lfe", N_("Center / LFE")}, {
773   "stereo-mix", N_("Stereo Mix")}, {
774   "mono-mix", N_("Mono Mix")}, {
775   "input-mix", N_("Input Mix")}, {
776   "spdif-in", N_("SPDIF In")}, {
777   "spdif-out", N_("SPDIF Out")}, {
778   "mic1", N_("Microphone 1")}, {
779   "mic2", N_("Microphone 2")}, {
780   "digital-out", N_("Digital Out")}, {
781   "digital-in", N_("Digital In")}, {
782   "hdmi", N_("HDMI")}, {
783   "modem", N_("Modem")}, {
784   "handset", N_("Handset")}, {
785   "other", N_("Other")}, {
786   "stereo", N_("Stereo")}, {
787   "none", N_("None")}, {
788   "on", N_("On")}, {
789   "off", N_("Off")}, {
790   "mute", N_("Mute")}, {
791   "fast", N_("Fast")}, {
792   "very-low", N_("Very Low")}, {
793   "low", N_("Low")}, {
794   "medium", N_("Medium")}, {
795   "high", N_("High")}, {
796   "very-high", N_("Very High")}, {
797   "high+", N_("Very High")}, {
798   "production", N_("Production")}, {
799   "fp-mic", N_("Front Panel Microphone")}, {
800   "fp-linein", N_("Front Panel Line In")}, {
801   "fp-headphones", N_("Front Panel Headphones")}, {
802   "fp-lineout", N_("Front Panel Line Out")}, {
803   "green", N_("Green Connector")}, {
804   "pink", N_("Pink Connector")}, {
805   "blue", N_("Blue Connector")}, {
806   "white", N_("White Connector")}, {
807   "black", N_("Black Connector")}, {
808   "gray", N_("Gray Connector")}, {
809   "orange", N_("Orange Connector")}, {
810   "red", N_("Red Connector")}, {
811   "yellow", N_("Yellow Connector")}, {
812   "fp-green", N_("Green Front Panel Connector")}, {
813   "fp-pink", N_("Pink Front Panel Connector")}, {
814   "fp-blue", N_("Blue Front Panel Connector")}, {
815   "fp-white", N_("White Front Panel Connector")}, {
816   "fp-black", N_("Black Front Panel Connector")}, {
817   "fp-gray", N_("Gray Front Panel Connector")}, {
818   "fp-orange", N_("Orange Front Panel Connector")}, {
819   "fp-red", N_("Red Front Panel Connector")}, {
820   "fp-yellow", N_("Yellow Front Panel Connector")}, {
821   "spread", N_("Spread Output")}, {
822   "downmix", N_("Downmix")},
823       /* FIXME translate Audigy NX USB labels) */
824 /*
825   { "rec.src", N_("Record Source") },
826   { "output.mute", N_("Mute output") }
827  headph (Controller group)
828    headph.src (Enumeration control)
829    headph.mute (On/Off switch)
830  digital2 (Controller group)
831    digital2.src (Enumeration control)
832    digital2.mute (On/Off switch)
833  digital (Controller group)
834    digital.mute1 (On/Off switch)
835    digital.vol (Controller group)
836      digital.vol.front (Stereo slider (0-255))
837      digital.vol.surr (Stereo slider (0-255))
838      digital.vol.c/l (Stereo slider (0-255))
839      digital.vol.center (Stereo slider (0-255))
840    digital.mute2 (On/Off switch)
841    digital.vol (Stereo slider (0-255))
842  line (Controller group)
843    line.mute (On/Off switch)
844    line.vol (Stereo slider (0-255))
845  play-altset (Enumeration control)
846  rec-altset (Enumeration control)
847 */
848 };
849
850 /* Decent i18n is pretty much impossible with OSS's way of providing us with
851  * mixer labels (and the fact that they are pretty much random), but that
852  * doesn't mean we shouldn't at least try. */
853 static gchar *
854 gst_oss4_mixer_control_get_translated_name (GstOss4MixerControl * mc)
855 {
856   gchar name[128] = { 0, };
857   gchar scratch[128] = { 0, };
858   gchar fmtbuf[128] = { 0, };
859   gchar vmix_str[32] = { '\0', };
860   gchar *ptr;
861   int dummy, i;
862   int num = -1;
863
864   g_strlcpy (fmtbuf, "%s", sizeof (fmtbuf));
865
866   /* main virtual mixer controls (we hide the stream volumes) */
867   if (sscanf (mc->mixext.extname, "vmix%d-%32c", &dummy, vmix_str) == 2) {
868     if (strcmp (vmix_str, "src") == 0)
869       return g_strdup (_("Virtual Mixer Input"));
870     else if (strcmp (vmix_str, "vol") == 0)
871       return g_strdup (_("Virtual Mixer Output"));
872     else if (strcmp (vmix_str, "channels") == 0)
873       return g_strdup (_("Virtual Mixer Channels"));
874   }
875
876   g_strlcpy (name, mc->mixext.extname, sizeof (name));
877
878   /* we deal with either "connector." or "jack." */
879   if ((g_str_has_prefix (name, "connector.")) ||
880       (g_str_has_prefix (name, "jack."))) {
881     ptr = strchr (mc->mixext.extname, '.');
882     ptr++;
883     g_strlcpy (scratch, ptr, sizeof (scratch));
884     g_strlcpy (name, scratch, sizeof (name));
885   }
886
887   /* special handling for jack retasking suffixes */
888   if (g_str_has_suffix (name, ".function") || g_str_has_suffix (name, ".mode")) {
889     g_strlcpy (fmtbuf, _("%s Function"), sizeof (fmtbuf));
890     ptr = strrchr (name, '.');
891     *ptr = 0;
892   }
893
894   /* parse off trailing numbers */
895   i = strlen (name);
896   while ((i > 0) && (g_ascii_isdigit (name[i - 1]))) {
897     i--;
898   }
899   /* the check catches the case where the control name is just a number */
900   if ((i > 0) && (name[i] != '\0')) {
901     num = atoi (name + i);
902     name[i] = '\0';
903     /* format appends a number to the base, but preserves any surrounding
904        format */
905     g_snprintf (scratch, sizeof (scratch), fmtbuf, _("%s %d"));
906     g_strlcpy (fmtbuf, scratch, sizeof (fmtbuf));
907   }
908
909   /* look for a match, progressively skipping '.' delimited prefixes as we go */
910   ptr = name;
911   do {
912     if (*ptr == '.')
913       ptr++;
914     for (i = 0; i < G_N_ELEMENTS (labels); ++i) {
915       if (g_strcasecmp (ptr, labels[i].oss_name) == 0) {
916         g_snprintf (name, sizeof (name), fmtbuf, _(labels[i].label), num);
917         return g_strdup (name);
918       }
919     }
920   } while ((ptr = strchr (ptr, '.')) != NULL);
921
922   /* failing that, just replace periods with spaces */
923   g_strdelimit (name, ".", ' ');
924   g_snprintf (scratch, sizeof (scratch), fmtbuf, name);
925   return g_strdup (scratch);
926 }
927
928 static const gchar *
929 gst_oss4_mixer_control_get_translated_option (const gchar * name)
930 {
931   int i;
932   for (i = 0; i < G_N_ELEMENTS (labels); ++i) {
933     if (g_strcasecmp (name, labels[i].oss_name) == 0) {
934       return _(labels[i].label);
935     }
936   }
937   return (name);
938 }
939
940 #ifndef GST_DISABLE_GST_DEBUG
941 static const gchar *
942 mixer_ext_type_get_name (gint type)
943 {
944   switch (type) {
945     case MIXT_DEVROOT:
946       return "Device root entry";
947     case MIXT_GROUP:
948       return "Controller group";
949     case MIXT_ONOFF:
950       return "On/Off switch";
951     case MIXT_ENUM:
952       return "Enumeration control";
953     case MIXT_MONOSLIDER:
954       return "Mono slider (0-255)";
955     case MIXT_STEREOSLIDER:
956       return "Stereo slider (0-255)";
957     case MIXT_MESSAGE:
958       return "Textual message"; /* whatever this is */
959     case MIXT_MONOVU:
960       return "Mono VU meter value";
961     case MIXT_STEREOVU:
962       return "Stereo VU meter value";
963     case MIXT_MONOPEAK:
964       return "Mono VU meter peak value";
965     case MIXT_STEREOPEAK:
966       return "Stereo VU meter peak value";
967     case MIXT_RADIOGROUP:
968       return "Radio button group";
969     case MIXT_MARKER:          /* Separator between normal and extension entries */
970       return "Separator";
971     case MIXT_VALUE:
972       return "Decimal value entry";
973     case MIXT_HEXVALUE:
974       return "Hex value entry";
975     case MIXT_SLIDER:
976       return "Mono slider (31-bit value range)";
977     case MIXT_3D:
978       return "3D";              /* what's this? */
979     case MIXT_MONOSLIDER16:
980       return "Mono slider (0-32767)";
981     case MIXT_STEREOSLIDER16:
982       return "Stereo slider (0-32767)";
983     case MIXT_MUTE:
984       return "Mute switch";
985     default:
986       break;
987   }
988   return "unknown";
989 }
990 #endif /* GST_DISABLE_GST_DEBUG */
991
992 #ifndef GST_DISABLE_GST_DEBUG
993 static const gchar *
994 mixer_ext_flags_get_string (gint flags)
995 {
996   struct
997   {
998     gint flag;
999     gchar nick[16];
1000   } all_flags[] = {
1001     /* first the important ones */
1002     {
1003     MIXF_MAINVOL, "MAINVOL"}, {
1004     MIXF_PCMVOL, "PCMVOL"}, {
1005     MIXF_RECVOL, "RECVOL"}, {
1006     MIXF_MONVOL, "MONVOL"}, {
1007     MIXF_DESCR, "DESCR"},
1008         /* now the rest in the right order */
1009     {
1010     MIXF_READABLE, "READABLE"}, {
1011     MIXF_WRITEABLE, "WRITABLE"}, {
1012     MIXF_POLL, "POLL"}, {
1013     MIXF_HZ, "HZ"}, {
1014     MIXF_STRING, "STRING"}, {
1015     MIXF_DYNAMIC, "DYNAMIC"}, {
1016     MIXF_OKFAIL, "OKFAIL"}, {
1017     MIXF_FLAT, "FLAT"}, {
1018     MIXF_LEGACY, "LEGACY"}, {
1019     MIXF_CENTIBEL, "CENTIBEL"}, {
1020     MIXF_DECIBEL, "DECIBEL"}, {
1021     MIXF_WIDE, "WIDE"}
1022   };
1023   GString *s;
1024   GQuark q;
1025   gint i;
1026
1027   if (flags == 0)
1028     return "None";
1029
1030   s = g_string_new ("");
1031   for (i = 0; i < G_N_ELEMENTS (all_flags); ++i) {
1032     if ((flags & all_flags[i].flag)) {
1033       if (s->len > 0)
1034         g_string_append (s, " | ");
1035       g_string_append (s, all_flags[i].nick);
1036       flags &= ~all_flags[i].flag;      /* unset */
1037     }
1038   }
1039
1040   /* unknown flags? */
1041   if (flags != 0) {
1042     if (s->len > 0)
1043       g_string_append (s, " | ");
1044     g_string_append (s, "???");
1045   }
1046
1047   /* we'll just put it into the global quark table (cheeky, eh?) */
1048   q = g_quark_from_string (s->str);
1049   g_string_free (s, TRUE);
1050
1051   return g_quark_to_string (q);
1052 }
1053 #endif /* GST_DISABLE_GST_DEBUG */
1054
1055 #ifndef GST_DISABLE_GST_DEBUG
1056 static void
1057 gst_oss4_mixer_control_dump_tree (GstOss4MixerControl * mc, gint depth)
1058 {
1059   GList *c;
1060   gchar spaces[64];
1061   gint i;
1062
1063   depth = MIN (sizeof (spaces) - 1, depth);
1064   for (i = 0; i < depth; ++i)
1065     spaces[i] = ' ';
1066   spaces[i] = '\0';
1067
1068   GST_LOG ("%s%s (%s)", spaces, mc->mixext.extname,
1069       mixer_ext_type_get_name (mc->mixext.type));
1070   for (c = mc->children; c != NULL; c = c->next) {
1071     GstOss4MixerControl *child_mc = (GstOss4MixerControl *) c->data;
1072
1073     gst_oss4_mixer_control_dump_tree (child_mc, depth + 2);
1074   }
1075 }
1076 #endif /* GST_DISABLE_GST_DEBUG */
1077
1078 static GList *
1079 gst_oss4_mixer_get_controls (GstOss4Mixer * mixer)
1080 {
1081   GstOss4MixerControl *root_mc = NULL;
1082   oss_mixerinfo mi = { 0, };
1083   GList *controls = NULL;
1084   GList *l;
1085   guint i;
1086
1087   /* Get info for currently open fd */
1088   mi.dev = -1;
1089   if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == -1)
1090     goto no_mixerinfo;
1091
1092   if (mi.nrext <= 0) {
1093     GST_DEBUG ("mixer %s has no controls", mi.id);
1094     return NULL;
1095   }
1096
1097   GST_INFO ("Reading controls for mixer %s", mi.id);
1098
1099   for (i = 0; i < mi.nrext; ++i) {
1100     GstOss4MixerControl *mc;
1101     oss_mixext mix_ext = { 0, };
1102
1103     mix_ext.dev = mi.dev;
1104     mix_ext.ctrl = i;
1105
1106     if (ioctl (mixer->fd, SNDCTL_MIX_EXTINFO, &mix_ext) == -1) {
1107       GST_DEBUG ("SNDCTL_MIX_EXTINFO failed on mixer %s, control %d: %s",
1108           mi.id, i, g_strerror (errno));
1109       continue;
1110     }
1111
1112     /* if this is the last one, save it for is-interface-up-to-date checking */
1113     if (i == mi.nrext)
1114       mixer->last_mixext = mix_ext;
1115
1116     mc = g_new0 (GstOss4MixerControl, 1);
1117     mc->mixext = mix_ext;
1118
1119     /* both control_no and desc fields are pretty useless, ie. not always set,
1120      * if ever, so not listed here */
1121     GST_INFO ("Control %d", mix_ext.ctrl);
1122     GST_INFO ("  name   : %s", mix_ext.extname);
1123     GST_INFO ("  type   : %s (%d)", mixer_ext_type_get_name (mix_ext.type),
1124         mix_ext.type);
1125     GST_INFO ("  flags  : %s (0x%04x)",
1126         mixer_ext_flags_get_string (mix_ext.flags), mix_ext.flags);
1127     GST_INFO ("  parent : %d", mix_ext.parent);
1128
1129     if (!MIXEXT_IS_ROOT (mix_ext)) {
1130       /* find parent (we assume it comes in the list before the child) */
1131       for (l = controls; l != NULL; l = l->next) {
1132         GstOss4MixerControl *parent_mc = (GstOss4MixerControl *) l->data;
1133
1134         if (parent_mc->mixext.ctrl == mix_ext.parent) {
1135           mc->parent = parent_mc;
1136           parent_mc->children = g_list_append (parent_mc->children, mc);
1137           break;
1138         }
1139       }
1140       if (mc->parent == NULL) {
1141         GST_ERROR_OBJECT (mixer, "couldn't find parent for control %d", i);
1142         g_free (mc);
1143         continue;
1144       }
1145     } else if (root_mc == NULL) {
1146       root_mc = mc;
1147     } else {
1148       GST_WARNING_OBJECT (mixer, "two root controls?!");
1149     }
1150
1151     controls = g_list_prepend (controls, mc);
1152   }
1153
1154 #ifndef GST_DISABLE_GST_DEBUG
1155   gst_oss4_mixer_control_dump_tree (root_mc, 0);
1156 #endif
1157
1158   return g_list_reverse (controls);
1159
1160 /* ERRORS */
1161 no_mixerinfo:
1162   {
1163     GST_WARNING ("SNDCTL_MIXERINFO failed on mixer device %s: %s", mi.id,
1164         g_strerror (errno));
1165     return NULL;
1166   }
1167 }
1168
1169 static void
1170 gst_oss4_mixer_controls_guess_master (GstOss4Mixer * mixer,
1171     const GList * controls)
1172 {
1173   GstOss4MixerControl *master_mc = NULL;
1174   const GList *l;
1175
1176   for (l = controls; l != NULL; l = l->next) {
1177     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1178
1179     /* do we need to check if it's a slider type here? */
1180     if ((mc->mixext.flags & MIXF_PCMVOL)) {
1181       GST_INFO_OBJECT (mixer, "First PCM control: %s", mc->mixext.extname);
1182       master_mc = mc;
1183       break;
1184     }
1185
1186     if (((mc->mixext.flags & MIXF_MAINVOL)) && master_mc == NULL) {
1187       GST_INFO_OBJECT (mixer, "First main volume control: %s",
1188           mc->mixext.extname);
1189       master_mc = mc;
1190     }
1191   }
1192
1193   if (master_mc != NULL)
1194     master_mc->is_master = TRUE;
1195 }
1196
1197 /* type: -1 for all types, otherwise just return siblings with requested type */
1198 static GList *
1199 gst_oss4_mixer_control_get_siblings (GstOss4MixerControl * mc, gint type)
1200 {
1201   GList *s, *siblings = NULL;
1202
1203   if (mc->parent == NULL)
1204     return NULL;
1205
1206   for (s = mc->parent->children; s != NULL; s = s->next) {
1207     GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data;
1208
1209     if (mc != sibling && (type < 0 || sibling->mixext.type == type))
1210       siblings = g_list_append (siblings, sibling);
1211   }
1212
1213   return siblings;
1214 }
1215
1216 static void
1217 gst_oss4_mixer_controls_find_sliders (GstOss4Mixer * mixer,
1218     const GList * controls)
1219 {
1220   const GList *l;
1221
1222   for (l = controls; l != NULL; l = l->next) {
1223     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1224     GList *s, *siblings;
1225
1226     if (!MIXEXT_IS_SLIDER (mc->mixext) || mc->parent == NULL || mc->used)
1227       continue;
1228
1229     mc->is_slider = TRUE;
1230     mc->used = TRUE;
1231
1232     siblings = gst_oss4_mixer_control_get_siblings (mc, -1);
1233
1234     /* Note: the names can be misleading and may not reflect the actual
1235      * hierarchy of the controls, e.g. it's possible that a slider is called
1236      * connector.green and the mute control then connector.green.mute, but
1237      * both controls are in fact siblings and both children of the group
1238      * 'green' instead of mute being a child of connector.green as the naming
1239      * would seem to suggest */
1240     GST_LOG ("Slider: %s, parent=%s, %d siblings", mc->mixext.extname,
1241         mc->parent->mixext.extname, g_list_length (siblings));
1242
1243     for (s = siblings; s != NULL; s = s->next) {
1244       GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data;
1245
1246       GST_LOG ("    %s (%s)", sibling->mixext.extname,
1247           mixer_ext_type_get_name (sibling->mixext.type));
1248
1249       if (sibling->mixext.type == MIXT_MUTE ||
1250           g_str_has_suffix (sibling->mixext.extname, ".mute")) {
1251         /* simple case: slider with single mute sibling. We assume the .mute
1252          * suffix in the name won't change - can't really do much else anyway */
1253         if (sibling->mixext.type == MIXT_ONOFF ||
1254             sibling->mixext.type == MIXT_MUTE) {
1255           GST_LOG ("    mute control for %s is %s", mc->mixext.extname,
1256               sibling->mixext.extname);
1257           mc->mute = sibling;
1258           sibling->used = TRUE;
1259         }
1260         /* a group of .mute controls. We assume they are all switches here */
1261         if (sibling->mixext.type == MIXT_GROUP) {
1262           GList *m;
1263
1264           for (m = sibling->children; m != NULL; m = m->next) {
1265             GstOss4MixerControl *grouped_sibling = m->data;
1266
1267             if (grouped_sibling->mixext.type == MIXT_ONOFF ||
1268                 grouped_sibling->mixext.type == MIXT_MUTE) {
1269               GST_LOG ("    %s is grouped mute control for %s",
1270                   grouped_sibling->mixext.extname, mc->mixext.extname);
1271               mc->mute_group = g_list_append (mc->mute_group, grouped_sibling);
1272             }
1273           }
1274
1275           GST_LOG ("    %s has a group of %d mute controls",
1276               mc->mixext.extname, g_list_length (mc->mute_group));
1277
1278           /* we don't mark the individual mute controls as used, only the
1279            * group control, as we still want individual switches for the
1280            * individual controls */
1281           sibling->used = TRUE;
1282         }
1283       }
1284     }
1285     g_list_free (siblings);
1286   }
1287 }
1288
1289 /* should be called with the mixer object lock held because of the ioctl;
1290  * returns TRUE if the list was read the first time or modified */
1291 static gboolean
1292 gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * mixer,
1293     GstOss4MixerControl * mc)
1294 {
1295   oss_mixer_enuminfo ei = { 0, };
1296   guint num_existing = 0;
1297   int i;
1298
1299   /* count and existing values */
1300   while (mc->enum_vals != NULL && mc->enum_vals[num_existing] != 0)
1301     ++num_existing;
1302
1303   ei.dev = mc->mixext.dev;
1304   ei.ctrl = mc->mixext.ctrl;
1305
1306   /* if we have create a generic list with numeric IDs already and the
1307    * number of values hasn't changed, then there's not much to do here */
1308   if (mc->no_list && mc->enum_vals != NULL &&
1309       num_existing == mc->mixext.maxvalue) {
1310     return FALSE;
1311   }
1312
1313   /* if we have a list and it doesn't change, there's nothing to do either */
1314   if (mc->enum_vals != NULL && mc->enum_version == 0)
1315     return FALSE;
1316
1317   if (ioctl (mixer->fd, SNDCTL_MIX_ENUMINFO, &ei) == -1) {
1318     g_free (mc->enum_vals);
1319     mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1);
1320
1321     GST_DEBUG ("no enum info available, creating numeric values from 0-%d",
1322         mc->mixext.maxvalue - 1);
1323
1324     /* "It is possible that some enum controls don't have any name list
1325      * available. In this case the application should automatically generate
1326      * list of numbers (0 to N-1)" */
1327     for (i = 0; i < mc->mixext.maxvalue; ++i) {
1328       gchar num_str[8];
1329
1330       g_snprintf (num_str, sizeof (num_str), "%d", i);
1331       mc->enum_vals[i] = g_quark_from_string (num_str);
1332     }
1333     mc->enum_version = 0;       /* the only way to change is via maxvalue */
1334   } else {
1335     /* old list same as current list? */
1336     if (mc->enum_vals != NULL && mc->enum_version == ei.version)
1337       return FALSE;
1338
1339     /* no list yet, or the list has changed */
1340     GST_LOG ("%s", (mc->enum_vals) ? "enum list has changed" : "reading list");
1341     if (ei.nvalues != mc->mixext.maxvalue) {
1342       GST_WARNING_OBJECT (mixer, "Enum: %s, nvalues %d != maxvalue %d",
1343           mc->mixext.extname, ei.nvalues, mc->mixext.maxvalue);
1344       mc->mixext.maxvalue = MIN (ei.nvalues, mc->mixext.maxvalue);
1345     }
1346
1347     mc->mixext.maxvalue = MIN (mc->mixext.maxvalue, OSS_ENUM_MAXVALUE);
1348
1349     g_free (mc->enum_vals);
1350     mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1);
1351     for (i = 0; i < mc->mixext.maxvalue; ++i) {
1352       GST_LOG ("  %s", ei.strings + ei.strindex[i]);
1353       mc->enum_vals[i] =
1354           g_quark_from_string (gst_oss4_mixer_control_get_translated_option
1355           (ei.strings + ei.strindex[i]));
1356     }
1357   }
1358
1359   return TRUE;
1360 }
1361
1362 static void
1363 gst_oss4_mixer_controls_find_enums (GstOss4Mixer * mixer,
1364     const GList * controls)
1365 {
1366   const GList *l;
1367
1368   for (l = controls; l != NULL; l = l->next) {
1369     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1370
1371     if (mc->mixext.type != MIXT_ENUM || mc->used)
1372       continue;
1373
1374     mc->is_enum = TRUE;
1375     mc->used = TRUE;
1376
1377     /* Note: enums are special: for most controls, the maxvalue is inclusive,
1378      * but for enum controls it's actually exclusive (boggle), so that
1379      * mixext.maxvalue = num_values */
1380
1381     GST_LOG ("Enum: %s, parent=%s, num_enums=%d", mc->mixext.extname,
1382         mc->parent->mixext.extname, mc->mixext.maxvalue);
1383
1384     gst_oss4_mixer_enum_control_update_enum_list (mixer, mc);
1385   }
1386 }
1387
1388 static void
1389 gst_oss4_mixer_controls_find_switches (GstOss4Mixer * mixer,
1390     const GList * controls)
1391 {
1392   const GList *l;
1393
1394   for (l = controls; l != NULL; l = l->next) {
1395     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1396
1397     if (mc->used)
1398       continue;
1399
1400     if (mc->mixext.type != MIXT_ONOFF && mc->mixext.type != MIXT_MUTE)
1401       continue;
1402
1403     mc->is_switch = TRUE;
1404     mc->used = TRUE;
1405
1406     GST_LOG ("Switch: %s, parent=%s", mc->mixext.extname,
1407         mc->parent->mixext.extname);
1408   }
1409 }
1410
1411 static void
1412 gst_oss4_mixer_controls_find_virtual (GstOss4Mixer * mixer,
1413     const GList * controls)
1414 {
1415   const GList *l;
1416
1417   for (l = controls; l != NULL; l = l->next) {
1418     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1419
1420     /* or sscanf (mc->mixext.extname, "vmix%d-out.", &n) == 1 ? */
1421     /* for now we just flag all virtual controls with managed labels, those
1422      * are really more appropriate for a pavucontrol-type control thing than
1423      * the (more hardware-oriented) mixer interface */
1424     if (mc->mixext.id[0] == '@') {
1425       mc->is_virtual = TRUE;
1426       GST_LOG ("%s is virtual control with managed label", mc->mixext.extname);
1427     }
1428   }
1429 }
1430
1431 static void
1432 gst_oss4_mixer_controls_dump_unused (GstOss4Mixer * mixer,
1433     const GList * controls)
1434 {
1435   const GList *l;
1436
1437   for (l = controls; l != NULL; l = l->next) {
1438     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1439
1440     if (mc->used)
1441       continue;
1442
1443     switch (mc->mixext.type) {
1444       case MIXT_DEVROOT:
1445       case MIXT_GROUP:
1446       case MIXT_MESSAGE:
1447       case MIXT_MONOVU:
1448       case MIXT_STEREOVU:
1449       case MIXT_MONOPEAK:
1450       case MIXT_STEREOPEAK:
1451       case MIXT_MARKER:
1452         continue;               /* not interested in these types of controls */
1453       case MIXT_MONODB:
1454       case MIXT_STEREODB:
1455         GST_DEBUG ("obsolete control type %d", mc->mixext.type);
1456         continue;
1457       case MIXT_MONOSLIDER:
1458       case MIXT_STEREOSLIDER:
1459       case MIXT_SLIDER:
1460       case MIXT_MONOSLIDER16:
1461       case MIXT_STEREOSLIDER16:
1462         /* this shouldn't happen */
1463         GST_ERROR ("unused slider control?!");
1464         continue;
1465       case MIXT_VALUE:
1466       case MIXT_HEXVALUE:
1467         /* value entry, not sure what to do with that, skip for now */
1468         continue;
1469       case MIXT_ONOFF:
1470       case MIXT_MUTE:
1471       case MIXT_ENUM:
1472       case MIXT_3D:
1473       case MIXT_RADIOGROUP:
1474         GST_DEBUG ("FIXME: handle %s %s",
1475             mixer_ext_type_get_name (mc->mixext.type), mc->mixext.extname);
1476         break;
1477       default:
1478         GST_WARNING ("unknown control type %d", mc->mixext.type);
1479         continue;
1480     }
1481   }
1482 }
1483
1484 static GList *
1485 gst_oss4_mixer_create_tracks (GstOss4Mixer * mixer, const GList * controls)
1486 {
1487   const GList *c;
1488   GList *tracks = NULL;
1489
1490   for (c = controls; c != NULL; c = c->next) {
1491     GstOss4MixerControl *mc = (GstOss4MixerControl *) c->data;
1492     GstMixerTrack *track = NULL;
1493
1494     if (mc->is_virtual)
1495       continue;
1496
1497     if (mc->is_slider) {
1498       track = gst_oss4_mixer_slider_new (mixer, mc);
1499     } else if (mc->is_enum) {
1500       track = gst_oss4_mixer_enum_new (mixer, mc);
1501     } else if (mc->is_switch) {
1502       track = gst_oss4_mixer_switch_new (mixer, mc);
1503     }
1504
1505     if (track == NULL)
1506       continue;
1507
1508     track->label = gst_oss4_mixer_control_get_translated_name (mc);
1509     track->flags = 0;
1510
1511     GST_LOG ("translated label: %s [%s] = %s", track->label, mc->mixext.id,
1512         track->label);
1513
1514     /* This whole 'a track is either INPUT or OUTPUT' model is just flawed,
1515      * esp. if a slider's role can be changed on the fly, like when you change
1516      * function of a connector. What should we do in that case? Change the flag
1517      * and make the app rebuild the interface? Ignore it? */
1518     if (mc->mixext.flags & (MIXF_MAINVOL | MIXF_PCMVOL)) {
1519       track->flags = GST_MIXER_TRACK_OUTPUT | GST_MIXER_TRACK_WHITELIST;
1520
1521     } else if (mc->mixext.flags & MIXF_RECVOL) {
1522       /* record gain whitelisted by default */
1523       track->flags = GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD |
1524           GST_MIXER_TRACK_WHITELIST;
1525
1526     } else if (mc->mixext.flags & MIXF_MONVOL) {
1527       /* monitor sources not whitelisted by default */
1528       track->flags = GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD;
1529     }
1530
1531     /*
1532      * The kernel may give us better clues about the scope of a control.
1533      * If so, try to honor it.
1534      */
1535     switch (mc->mixext.desc & MIXEXT_SCOPE_MASK) {
1536       case MIXEXT_SCOPE_INPUT:
1537       case MIXEXT_SCOPE_RECSWITCH:
1538         track->flags |= GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD |
1539             GST_MIXER_TRACK_WHITELIST;
1540         break;
1541       case MIXEXT_SCOPE_MONITOR:
1542         /* don't whitelist monitor tracks by default */
1543         track->flags |= GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD;
1544         break;
1545       case MIXEXT_SCOPE_OUTPUT:
1546         track->flags = GST_MIXER_TRACK_OUTPUT | GST_MIXER_TRACK_WHITELIST;
1547         break;
1548     }
1549
1550     if (mc->is_master) {
1551       track->flags |= GST_MIXER_TRACK_OUTPUT;
1552     }
1553
1554     if (mc->is_master)
1555       track->flags |= GST_MIXER_TRACK_MASTER;
1556
1557     tracks = g_list_append (tracks, track);
1558   }
1559
1560   return tracks;
1561 }
1562
1563 static void
1564 gst_oss4_mixer_update_tracks (GstOss4Mixer * mixer)
1565 {
1566   GList *controls, *tracks;
1567
1568   /* read and process controls */
1569   controls = gst_oss4_mixer_get_controls (mixer);
1570
1571   gst_oss4_mixer_controls_guess_master (mixer, controls);
1572
1573   gst_oss4_mixer_controls_find_sliders (mixer, controls);
1574
1575   gst_oss4_mixer_controls_find_enums (mixer, controls);
1576
1577   gst_oss4_mixer_controls_find_switches (mixer, controls);
1578
1579   gst_oss4_mixer_controls_find_virtual (mixer, controls);
1580
1581   gst_oss4_mixer_controls_dump_unused (mixer, controls);
1582
1583   tracks = gst_oss4_mixer_create_tracks (mixer, controls);
1584
1585   /* free old tracks and controls */
1586   gst_oss4_mixer_free_tracks (mixer);
1587
1588   /* replace old with new */
1589   mixer->tracks = tracks;
1590   mixer->controls = controls;
1591 }
1592
1593 static const GList *
1594 gst_oss4_mixer_list_tracks (GstMixer * mixer_iface)
1595 {
1596   GstOss4Mixer *mixer = GST_OSS4_MIXER (mixer_iface);
1597
1598   g_return_val_if_fail (mixer != NULL, NULL);
1599   g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL);
1600
1601   GST_OBJECT_LOCK (mixer);
1602
1603   /* Do a read on the last control to check if the interface has changed */
1604   if (!mixer->need_update && mixer->last_mixext.ctrl > 0) {
1605     GstOss4MixerControl mc = { {0,}
1606     ,
1607     };
1608     int val;
1609
1610     mc.mixext = mixer->last_mixext;
1611     gst_oss4_mixer_get_control_val (mixer, &mc, &val);
1612   }
1613
1614   if (mixer->need_update || mixer->tracks == NULL) {
1615     gst_oss4_mixer_update_tracks (mixer);
1616     mixer->need_update = FALSE;
1617   }
1618
1619   GST_OBJECT_UNLOCK (mixer);
1620
1621   return (const GList *) mixer->tracks;
1622 }
1623
1624 static void
1625 gst_oss4_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
1626     gint * volumes)
1627 {
1628   GstOss4Mixer *oss;
1629
1630   g_return_if_fail (mixer != NULL);
1631   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1632   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1633   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1634   g_return_if_fail (volumes != NULL);
1635
1636   oss = GST_OSS4_MIXER (mixer);
1637
1638   GST_OBJECT_LOCK (oss);
1639
1640   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1641     gst_oss4_mixer_slider_set_volume (GST_OSS4_MIXER_SLIDER (track), volumes);
1642   }
1643
1644   GST_OBJECT_UNLOCK (oss);
1645 }
1646
1647 static void
1648 gst_oss4_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
1649     gint * volumes)
1650 {
1651   GstOss4Mixer *oss;
1652
1653   g_return_if_fail (mixer != NULL);
1654   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1655   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1656   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1657   g_return_if_fail (volumes != NULL);
1658
1659   oss = GST_OSS4_MIXER (mixer);
1660
1661   GST_OBJECT_LOCK (oss);
1662
1663   memset (volumes, 0, track->num_channels * sizeof (gint));
1664
1665   if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1666     gboolean enabled = FALSE;
1667     gst_oss4_mixer_switch_get (GST_OSS4_MIXER_SWITCH (track), &enabled);
1668   }
1669   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1670     gst_oss4_mixer_slider_get_volume (GST_OSS4_MIXER_SLIDER (track), volumes);
1671   }
1672
1673   GST_OBJECT_UNLOCK (oss);
1674 }
1675
1676 static void
1677 gst_oss4_mixer_set_record (GstMixer * mixer, GstMixerTrack * track,
1678     gboolean record)
1679 {
1680   GstOss4Mixer *oss;
1681
1682   g_return_if_fail (mixer != NULL);
1683   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1684   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1685   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1686
1687   oss = GST_OSS4_MIXER (mixer);
1688
1689   GST_OBJECT_LOCK (oss);
1690
1691   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1692     gst_oss4_mixer_slider_set_record (GST_OSS4_MIXER_SLIDER (track), record);
1693   } else if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1694     if ((track->flags & GST_MIXER_TRACK_INPUT)) {
1695       gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), record);
1696     } else {
1697       GST_WARNING_OBJECT (track, "set_record called on non-INPUT track");
1698     }
1699   }
1700
1701   GST_OBJECT_UNLOCK (oss);
1702 }
1703
1704 static void
1705 gst_oss4_mixer_set_mute (GstMixer * mixer, GstMixerTrack * track, gboolean mute)
1706 {
1707   GstOss4Mixer *oss;
1708
1709   g_return_if_fail (mixer != NULL);
1710   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1711   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1712   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1713
1714   oss = GST_OSS4_MIXER (mixer);
1715
1716   GST_OBJECT_LOCK (oss);
1717
1718   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1719     gst_oss4_mixer_slider_set_mute (GST_OSS4_MIXER_SLIDER (track), mute);
1720   } else if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1721     gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), mute);
1722   }
1723
1724   GST_OBJECT_UNLOCK (oss);
1725 }
1726
1727 static void
1728 gst_oss4_mixer_set_option (GstMixer * mixer, GstMixerOptions * options,
1729     gchar * value)
1730 {
1731   GstOss4Mixer *oss;
1732
1733   g_return_if_fail (mixer != NULL);
1734   g_return_if_fail (value != NULL);
1735   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1736   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1737   g_return_if_fail (GST_IS_OSS4_MIXER_ENUM (options));
1738   g_return_if_fail (gst_oss4_mixer_contains_options (mixer, options));
1739
1740   oss = GST_OSS4_MIXER (mixer);
1741
1742   GST_OBJECT_LOCK (oss);
1743
1744   if (!gst_oss4_mixer_enum_set_option (GST_OSS4_MIXER_ENUM (options), value)) {
1745     /* not much we can do here but wake up the watch thread early, so it
1746      * can do its thing and post messages if anything has changed */
1747     gst_oss4_mixer_wake_up_watch_task (oss);
1748   }
1749
1750   GST_OBJECT_UNLOCK (oss);
1751 }
1752
1753 static const gchar *
1754 gst_oss4_mixer_get_option (GstMixer * mixer, GstMixerOptions * options)
1755 {
1756   GstOss4Mixer *oss;
1757   const gchar *current_val;
1758
1759   g_return_val_if_fail (mixer != NULL, NULL);
1760   g_return_val_if_fail (GST_IS_OSS4_MIXER (mixer), NULL);
1761   g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL);
1762   g_return_val_if_fail (GST_IS_OSS4_MIXER_ENUM (options), NULL);
1763   g_return_val_if_fail (gst_oss4_mixer_contains_options (mixer, options), NULL);
1764
1765   oss = GST_OSS4_MIXER (mixer);
1766
1767   GST_OBJECT_LOCK (oss);
1768
1769   current_val = gst_oss4_mixer_enum_get_option (GST_OSS4_MIXER_ENUM (options));
1770
1771   if (current_val == NULL) {
1772     /* not much we can do here but wake up the watch thread early, so it
1773      * can do its thing and post messages if anything has changed */
1774     gst_oss4_mixer_wake_up_watch_task (oss);
1775   }
1776
1777   GST_OBJECT_UNLOCK (oss);
1778
1779   return current_val;
1780 }
1781
1782 static GstMixerFlags
1783 gst_oss4_mixer_get_mixer_flags (GstMixer * mixer)
1784 {
1785   return GST_MIXER_FLAG_AUTO_NOTIFICATIONS | GST_MIXER_FLAG_HAS_WHITELIST |
1786       GST_MIXER_FLAG_GROUPING;
1787 }
1788
1789 static void
1790 gst_oss4_mixer_interface_init (GstMixerClass * klass)
1791 {
1792   GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE;
1793
1794   klass->list_tracks = gst_oss4_mixer_list_tracks;
1795   klass->set_volume = gst_oss4_mixer_set_volume;
1796   klass->get_volume = gst_oss4_mixer_get_volume;
1797   klass->set_mute = gst_oss4_mixer_set_mute;
1798   klass->set_record = gst_oss4_mixer_set_record;
1799   klass->set_option = gst_oss4_mixer_set_option;
1800   klass->get_option = gst_oss4_mixer_get_option;
1801   klass->get_mixer_flags = gst_oss4_mixer_get_mixer_flags;
1802 }
1803
1804 /* Implement the horror that is GstImplementsInterface */
1805
1806 static gboolean
1807 gst_oss4_mixer_supported (GstImplementsInterface * iface, GType iface_type)
1808 {
1809   GstOss4Mixer *mixer;
1810
1811   g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
1812
1813   mixer = GST_OSS4_MIXER (iface);
1814
1815   return GST_OSS4_MIXER_IS_OPEN (mixer);
1816 }
1817
1818 static void
1819 gst_oss4_mixer_implements_interface_init (GstImplementsInterfaceClass * klass)
1820 {
1821   klass->supported = gst_oss4_mixer_supported;
1822 }
1823
1824 static void
1825 gst_oss4_mixer_init_interfaces (GType type)
1826 {
1827   static const GInterfaceInfo implements_iface_info = {
1828     (GInterfaceInitFunc) gst_oss4_mixer_implements_interface_init,
1829     NULL,
1830     NULL,
1831   };
1832   static const GInterfaceInfo mixer_iface_info = {
1833     (GInterfaceInitFunc) gst_oss4_mixer_interface_init,
1834     NULL,
1835     NULL,
1836   };
1837
1838   g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
1839       &implements_iface_info);
1840   g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info);
1841
1842   gst_oss4_add_property_probe_interface (type);
1843 }