upload tizen1.0 source
[framework/multimedia/gst-plugins-good0.10.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 | G_PARAM_STATIC_STRINGS));
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 | G_PARAM_STATIC_STRINGS));
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     /* TRANSLATORS: "Very Low" is a quality setting here */
793   "very-low", N_("Very Low")}, {
794     /* TRANSLATORS: "Low" is a quality setting here */
795   "low", N_("Low")}, {
796     /* TRANSLATORS: "Medium" is a quality setting here */
797   "medium", N_("Medium")}, {
798     /* TRANSLATORS: "High" is a quality setting here */
799   "high", N_("High")}, {
800     /* TRANSLATORS: "Very High" is a quality setting here */
801   "very-high", N_("Very High")}, {
802   "high+", N_("Very High")}, {
803     /* TRANSLATORS: "Production" is a quality setting here */
804   "production", N_("Production")}, {
805   "fp-mic", N_("Front Panel Microphone")}, {
806   "fp-linein", N_("Front Panel Line In")}, {
807   "fp-headphones", N_("Front Panel Headphones")}, {
808   "fp-lineout", N_("Front Panel Line Out")}, {
809   "green", N_("Green Connector")}, {
810   "pink", N_("Pink Connector")}, {
811   "blue", N_("Blue Connector")}, {
812   "white", N_("White Connector")}, {
813   "black", N_("Black Connector")}, {
814   "gray", N_("Gray Connector")}, {
815   "orange", N_("Orange Connector")}, {
816   "red", N_("Red Connector")}, {
817   "yellow", N_("Yellow Connector")}, {
818   "fp-green", N_("Green Front Panel Connector")}, {
819   "fp-pink", N_("Pink Front Panel Connector")}, {
820   "fp-blue", N_("Blue Front Panel Connector")}, {
821   "fp-white", N_("White Front Panel Connector")}, {
822   "fp-black", N_("Black Front Panel Connector")}, {
823   "fp-gray", N_("Gray Front Panel Connector")}, {
824   "fp-orange", N_("Orange Front Panel Connector")}, {
825   "fp-red", N_("Red Front Panel Connector")}, {
826   "fp-yellow", N_("Yellow Front Panel Connector")}, {
827   "spread", N_("Spread Output")}, {
828   "downmix", N_("Downmix")},
829       /* FIXME translate Audigy NX USB labels) */
830 /*
831   { "rec.src", N_("Record Source") },
832   { "output.mute", N_("Mute output") }
833  headph (Controller group)
834    headph.src (Enumeration control)
835    headph.mute (On/Off switch)
836  digital2 (Controller group)
837    digital2.src (Enumeration control)
838    digital2.mute (On/Off switch)
839  digital (Controller group)
840    digital.mute1 (On/Off switch)
841    digital.vol (Controller group)
842      digital.vol.front (Stereo slider (0-255))
843      digital.vol.surr (Stereo slider (0-255))
844      digital.vol.c/l (Stereo slider (0-255))
845      digital.vol.center (Stereo slider (0-255))
846    digital.mute2 (On/Off switch)
847    digital.vol (Stereo slider (0-255))
848  line (Controller group)
849    line.mute (On/Off switch)
850    line.vol (Stereo slider (0-255))
851  play-altset (Enumeration control)
852  rec-altset (Enumeration control)
853 */
854 };
855
856 /* Decent i18n is pretty much impossible with OSS's way of providing us with
857  * mixer labels (and the fact that they are pretty much random), but that
858  * doesn't mean we shouldn't at least try. */
859 static gchar *
860 gst_oss4_mixer_control_get_translated_name (GstOss4MixerControl * mc)
861 {
862   gchar name[128] = { 0, };
863   gchar vmix_str[32] = { '\0', };
864   gchar *ptr;
865   int dummy, i;
866   int num = -1;
867   gboolean function_suffix = FALSE;
868
869   /* main virtual mixer controls (we hide the stream volumes) */
870   if (sscanf (mc->mixext.extname, "vmix%d-%32c", &dummy, vmix_str) == 2) {
871     if (strcmp (vmix_str, "src") == 0)
872       return g_strdup (_("Virtual Mixer Input"));
873     else if (strcmp (vmix_str, "vol") == 0)
874       return g_strdup (_("Virtual Mixer Output"));
875     else if (strcmp (vmix_str, "channels") == 0)
876       return g_strdup (_("Virtual Mixer Channels"));
877   }
878
879   g_strlcpy (name, mc->mixext.extname, sizeof (name));
880
881   /* we deal with either "connector." or "jack." */
882   if ((g_str_has_prefix (name, "connector.")) ||
883       (g_str_has_prefix (name, "jack."))) {
884     ptr = strchr (mc->mixext.extname, '.');
885     ptr++;
886     g_strlcpy (name, ptr, sizeof (name));
887   }
888
889   /* special handling for jack retasking suffixes */
890   if (g_str_has_suffix (name, ".function") || g_str_has_suffix (name, ".mode")) {
891     function_suffix = TRUE;
892     ptr = strrchr (name, '.');
893     *ptr = 0;
894   }
895
896   /* parse off trailing numbers */
897   i = strlen (name);
898   while ((i > 0) && (g_ascii_isdigit (name[i - 1]))) {
899     i--;
900   }
901   /* the check catches the case where the control name is just a number */
902   if ((i > 0) && (name[i] != '\0')) {
903     num = atoi (name + i);
904     name[i] = '\0';
905   }
906
907   /* look for a match, progressively skipping '.' delimited prefixes as we go */
908   ptr = name;
909   do {
910     if (*ptr == '.')
911       ptr++;
912     for (i = 0; i < G_N_ELEMENTS (labels); ++i) {
913       if (g_ascii_strcasecmp (ptr, labels[i].oss_name) == 0) {
914         g_strlcpy (name, _(labels[i].label), sizeof (name));
915         goto append_suffixes;
916       }
917     }
918   } while ((ptr = strchr (ptr, '.')) != NULL);
919
920   /* failing that, just replace periods with spaces */
921   g_strdelimit (name, ".", ' ');
922
923 append_suffixes:
924   if (num > -1) {
925     if (function_suffix) {
926       /* TRANSLATORS: name + number of a volume mixer control */
927       return g_strdup_printf (_("%s %d Function"), name, num);
928     } else {
929       return g_strdup_printf ("%s %d", name, num);
930     }
931   } else {
932     if (function_suffix) {
933       /* TRANSLATORS: name of a volume mixer control */
934       return g_strdup_printf (_("%s Function"), name);
935     } else {
936       return g_strdup (name);
937     }
938   }
939 }
940
941 static const gchar *
942 gst_oss4_mixer_control_get_translated_option (const gchar * name)
943 {
944   int i;
945   for (i = 0; i < G_N_ELEMENTS (labels); ++i) {
946     if (g_ascii_strcasecmp (name, labels[i].oss_name) == 0) {
947       return _(labels[i].label);
948     }
949   }
950   return (name);
951 }
952
953 #ifndef GST_DISABLE_GST_DEBUG
954 static const gchar *
955 mixer_ext_type_get_name (gint type)
956 {
957   switch (type) {
958     case MIXT_DEVROOT:
959       return "Device root entry";
960     case MIXT_GROUP:
961       return "Controller group";
962     case MIXT_ONOFF:
963       return "On/Off switch";
964     case MIXT_ENUM:
965       return "Enumeration control";
966     case MIXT_MONOSLIDER:
967       return "Mono slider (0-255)";
968     case MIXT_STEREOSLIDER:
969       return "Stereo slider (0-255)";
970     case MIXT_MESSAGE:
971       return "Textual message"; /* whatever this is */
972     case MIXT_MONOVU:
973       return "Mono VU meter value";
974     case MIXT_STEREOVU:
975       return "Stereo VU meter value";
976     case MIXT_MONOPEAK:
977       return "Mono VU meter peak value";
978     case MIXT_STEREOPEAK:
979       return "Stereo VU meter peak value";
980     case MIXT_RADIOGROUP:
981       return "Radio button group";
982     case MIXT_MARKER:          /* Separator between normal and extension entries */
983       return "Separator";
984     case MIXT_VALUE:
985       return "Decimal value entry";
986     case MIXT_HEXVALUE:
987       return "Hex value entry";
988     case MIXT_SLIDER:
989       return "Mono slider (31-bit value range)";
990     case MIXT_3D:
991       return "3D";              /* what's this? */
992     case MIXT_MONOSLIDER16:
993       return "Mono slider (0-32767)";
994     case MIXT_STEREOSLIDER16:
995       return "Stereo slider (0-32767)";
996     case MIXT_MUTE:
997       return "Mute switch";
998     default:
999       break;
1000   }
1001   return "unknown";
1002 }
1003 #endif /* GST_DISABLE_GST_DEBUG */
1004
1005 #ifndef GST_DISABLE_GST_DEBUG
1006 static const gchar *
1007 mixer_ext_flags_get_string (gint flags)
1008 {
1009   struct
1010   {
1011     gint flag;
1012     gchar nick[16];
1013   } all_flags[] = {
1014     /* first the important ones */
1015     {
1016     MIXF_MAINVOL, "MAINVOL"}, {
1017     MIXF_PCMVOL, "PCMVOL"}, {
1018     MIXF_RECVOL, "RECVOL"}, {
1019     MIXF_MONVOL, "MONVOL"}, {
1020     MIXF_DESCR, "DESCR"},
1021         /* now the rest in the right order */
1022     {
1023     MIXF_READABLE, "READABLE"}, {
1024     MIXF_WRITEABLE, "WRITABLE"}, {
1025     MIXF_POLL, "POLL"}, {
1026     MIXF_HZ, "HZ"}, {
1027     MIXF_STRING, "STRING"}, {
1028     MIXF_DYNAMIC, "DYNAMIC"}, {
1029     MIXF_OKFAIL, "OKFAIL"}, {
1030     MIXF_FLAT, "FLAT"}, {
1031     MIXF_LEGACY, "LEGACY"}, {
1032     MIXF_CENTIBEL, "CENTIBEL"}, {
1033     MIXF_DECIBEL, "DECIBEL"}, {
1034     MIXF_WIDE, "WIDE"}
1035   };
1036   GString *s;
1037   GQuark q;
1038   gint i;
1039
1040   if (flags == 0)
1041     return "None";
1042
1043   s = g_string_new ("");
1044   for (i = 0; i < G_N_ELEMENTS (all_flags); ++i) {
1045     if ((flags & all_flags[i].flag)) {
1046       if (s->len > 0)
1047         g_string_append (s, " | ");
1048       g_string_append (s, all_flags[i].nick);
1049       flags &= ~all_flags[i].flag;      /* unset */
1050     }
1051   }
1052
1053   /* unknown flags? */
1054   if (flags != 0) {
1055     if (s->len > 0)
1056       g_string_append (s, " | ");
1057     g_string_append (s, "???");
1058   }
1059
1060   /* we'll just put it into the global quark table (cheeky, eh?) */
1061   q = g_quark_from_string (s->str);
1062   g_string_free (s, TRUE);
1063
1064   return g_quark_to_string (q);
1065 }
1066 #endif /* GST_DISABLE_GST_DEBUG */
1067
1068 #ifndef GST_DISABLE_GST_DEBUG
1069 static void
1070 gst_oss4_mixer_control_dump_tree (GstOss4MixerControl * mc, gint depth)
1071 {
1072   GList *c;
1073   gchar spaces[64];
1074   gint i;
1075
1076   depth = MIN (sizeof (spaces) - 1, depth);
1077   for (i = 0; i < depth; ++i)
1078     spaces[i] = ' ';
1079   spaces[i] = '\0';
1080
1081   GST_LOG ("%s%s (%s)", spaces, mc->mixext.extname,
1082       mixer_ext_type_get_name (mc->mixext.type));
1083   for (c = mc->children; c != NULL; c = c->next) {
1084     GstOss4MixerControl *child_mc = (GstOss4MixerControl *) c->data;
1085
1086     gst_oss4_mixer_control_dump_tree (child_mc, depth + 2);
1087   }
1088 }
1089 #endif /* GST_DISABLE_GST_DEBUG */
1090
1091 static GList *
1092 gst_oss4_mixer_get_controls (GstOss4Mixer * mixer)
1093 {
1094   GstOss4MixerControl *root_mc = NULL;
1095   oss_mixerinfo mi = { 0, };
1096   GList *controls = NULL;
1097   GList *l;
1098   guint i;
1099
1100   /* Get info for currently open fd */
1101   mi.dev = -1;
1102   if (ioctl (mixer->fd, SNDCTL_MIXERINFO, &mi) == -1)
1103     goto no_mixerinfo;
1104
1105   if (mi.nrext <= 0) {
1106     GST_DEBUG ("mixer %s has no controls", mi.id);
1107     return NULL;
1108   }
1109
1110   GST_INFO ("Reading controls for mixer %s", mi.id);
1111
1112   for (i = 0; i < mi.nrext; ++i) {
1113     GstOss4MixerControl *mc;
1114     oss_mixext mix_ext = { 0, };
1115
1116     mix_ext.dev = mi.dev;
1117     mix_ext.ctrl = i;
1118
1119     if (ioctl (mixer->fd, SNDCTL_MIX_EXTINFO, &mix_ext) == -1) {
1120       GST_DEBUG ("SNDCTL_MIX_EXTINFO failed on mixer %s, control %d: %s",
1121           mi.id, i, g_strerror (errno));
1122       continue;
1123     }
1124
1125     /* if this is the last one, save it for is-interface-up-to-date checking */
1126     if (i == mi.nrext)
1127       mixer->last_mixext = mix_ext;
1128
1129     mc = g_new0 (GstOss4MixerControl, 1);
1130     mc->mixext = mix_ext;
1131
1132     /* both control_no and desc fields are pretty useless, ie. not always set,
1133      * if ever, so not listed here */
1134     GST_INFO ("Control %d", mix_ext.ctrl);
1135     GST_INFO ("  name   : %s", mix_ext.extname);
1136     GST_INFO ("  type   : %s (%d)", mixer_ext_type_get_name (mix_ext.type),
1137         mix_ext.type);
1138     GST_INFO ("  flags  : %s (0x%04x)",
1139         mixer_ext_flags_get_string (mix_ext.flags), mix_ext.flags);
1140     GST_INFO ("  parent : %d", mix_ext.parent);
1141
1142     if (!MIXEXT_IS_ROOT (mix_ext)) {
1143       /* find parent (we assume it comes in the list before the child) */
1144       for (l = controls; l != NULL; l = l->next) {
1145         GstOss4MixerControl *parent_mc = (GstOss4MixerControl *) l->data;
1146
1147         if (parent_mc->mixext.ctrl == mix_ext.parent) {
1148           mc->parent = parent_mc;
1149           parent_mc->children = g_list_append (parent_mc->children, mc);
1150           break;
1151         }
1152       }
1153       if (mc->parent == NULL) {
1154         GST_ERROR_OBJECT (mixer, "couldn't find parent for control %d", i);
1155         g_free (mc);
1156         continue;
1157       }
1158     } else if (root_mc == NULL) {
1159       root_mc = mc;
1160     } else {
1161       GST_WARNING_OBJECT (mixer, "two root controls?!");
1162     }
1163
1164     controls = g_list_prepend (controls, mc);
1165   }
1166
1167 #ifndef GST_DISABLE_GST_DEBUG
1168   gst_oss4_mixer_control_dump_tree (root_mc, 0);
1169 #endif
1170
1171   return g_list_reverse (controls);
1172
1173 /* ERRORS */
1174 no_mixerinfo:
1175   {
1176     GST_WARNING ("SNDCTL_MIXERINFO failed on mixer device %s: %s", mi.id,
1177         g_strerror (errno));
1178     return NULL;
1179   }
1180 }
1181
1182 static void
1183 gst_oss4_mixer_controls_guess_master (GstOss4Mixer * mixer,
1184     const GList * controls)
1185 {
1186   GstOss4MixerControl *master_mc = NULL;
1187   const GList *l;
1188
1189   for (l = controls; l != NULL; l = l->next) {
1190     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1191
1192     /* do we need to check if it's a slider type here? */
1193     if ((mc->mixext.flags & MIXF_PCMVOL)) {
1194       GST_INFO_OBJECT (mixer, "First PCM control: %s", mc->mixext.extname);
1195       master_mc = mc;
1196       break;
1197     }
1198
1199     if (((mc->mixext.flags & MIXF_MAINVOL)) && master_mc == NULL) {
1200       GST_INFO_OBJECT (mixer, "First main volume control: %s",
1201           mc->mixext.extname);
1202       master_mc = mc;
1203     }
1204   }
1205
1206   if (master_mc != NULL)
1207     master_mc->is_master = TRUE;
1208 }
1209
1210 /* type: -1 for all types, otherwise just return siblings with requested type */
1211 static GList *
1212 gst_oss4_mixer_control_get_siblings (GstOss4MixerControl * mc, gint type)
1213 {
1214   GList *s, *siblings = NULL;
1215
1216   if (mc->parent == NULL)
1217     return NULL;
1218
1219   for (s = mc->parent->children; s != NULL; s = s->next) {
1220     GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data;
1221
1222     if (mc != sibling && (type < 0 || sibling->mixext.type == type))
1223       siblings = g_list_append (siblings, sibling);
1224   }
1225
1226   return siblings;
1227 }
1228
1229 static void
1230 gst_oss4_mixer_controls_find_sliders (GstOss4Mixer * mixer,
1231     const GList * controls)
1232 {
1233   const GList *l;
1234
1235   for (l = controls; l != NULL; l = l->next) {
1236     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1237     GList *s, *siblings;
1238
1239     if (!MIXEXT_IS_SLIDER (mc->mixext) || mc->parent == NULL || mc->used)
1240       continue;
1241
1242     mc->is_slider = TRUE;
1243     mc->used = TRUE;
1244
1245     siblings = gst_oss4_mixer_control_get_siblings (mc, -1);
1246
1247     /* Note: the names can be misleading and may not reflect the actual
1248      * hierarchy of the controls, e.g. it's possible that a slider is called
1249      * connector.green and the mute control then connector.green.mute, but
1250      * both controls are in fact siblings and both children of the group
1251      * 'green' instead of mute being a child of connector.green as the naming
1252      * would seem to suggest */
1253     GST_LOG ("Slider: %s, parent=%s, %d siblings", mc->mixext.extname,
1254         mc->parent->mixext.extname, g_list_length (siblings));
1255
1256     for (s = siblings; s != NULL; s = s->next) {
1257       GstOss4MixerControl *sibling = (GstOss4MixerControl *) s->data;
1258
1259       GST_LOG ("    %s (%s)", sibling->mixext.extname,
1260           mixer_ext_type_get_name (sibling->mixext.type));
1261
1262       if (sibling->mixext.type == MIXT_MUTE ||
1263           g_str_has_suffix (sibling->mixext.extname, ".mute")) {
1264         /* simple case: slider with single mute sibling. We assume the .mute
1265          * suffix in the name won't change - can't really do much else anyway */
1266         if (sibling->mixext.type == MIXT_ONOFF ||
1267             sibling->mixext.type == MIXT_MUTE) {
1268           GST_LOG ("    mute control for %s is %s", mc->mixext.extname,
1269               sibling->mixext.extname);
1270           mc->mute = sibling;
1271           sibling->used = TRUE;
1272         }
1273         /* a group of .mute controls. We assume they are all switches here */
1274         if (sibling->mixext.type == MIXT_GROUP) {
1275           GList *m;
1276
1277           for (m = sibling->children; m != NULL; m = m->next) {
1278             GstOss4MixerControl *grouped_sibling = m->data;
1279
1280             if (grouped_sibling->mixext.type == MIXT_ONOFF ||
1281                 grouped_sibling->mixext.type == MIXT_MUTE) {
1282               GST_LOG ("    %s is grouped mute control for %s",
1283                   grouped_sibling->mixext.extname, mc->mixext.extname);
1284               mc->mute_group = g_list_append (mc->mute_group, grouped_sibling);
1285             }
1286           }
1287
1288           GST_LOG ("    %s has a group of %d mute controls",
1289               mc->mixext.extname, g_list_length (mc->mute_group));
1290
1291           /* we don't mark the individual mute controls as used, only the
1292            * group control, as we still want individual switches for the
1293            * individual controls */
1294           sibling->used = TRUE;
1295         }
1296       }
1297     }
1298     g_list_free (siblings);
1299   }
1300 }
1301
1302 /* should be called with the mixer object lock held because of the ioctl;
1303  * returns TRUE if the list was read the first time or modified */
1304 static gboolean
1305 gst_oss4_mixer_enum_control_update_enum_list (GstOss4Mixer * mixer,
1306     GstOss4MixerControl * mc)
1307 {
1308   oss_mixer_enuminfo ei = { 0, };
1309   guint num_existing = 0;
1310   int i;
1311
1312   /* count and existing values */
1313   while (mc->enum_vals != NULL && mc->enum_vals[num_existing] != 0)
1314     ++num_existing;
1315
1316   ei.dev = mc->mixext.dev;
1317   ei.ctrl = mc->mixext.ctrl;
1318
1319   /* if we have create a generic list with numeric IDs already and the
1320    * number of values hasn't changed, then there's not much to do here */
1321   if (mc->no_list && mc->enum_vals != NULL &&
1322       num_existing == mc->mixext.maxvalue) {
1323     return FALSE;
1324   }
1325
1326   /* if we have a list and it doesn't change, there's nothing to do either */
1327   if (mc->enum_vals != NULL && mc->enum_version == 0)
1328     return FALSE;
1329
1330   if (ioctl (mixer->fd, SNDCTL_MIX_ENUMINFO, &ei) == -1) {
1331     g_free (mc->enum_vals);
1332     mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1);
1333
1334     GST_DEBUG ("no enum info available, creating numeric values from 0-%d",
1335         mc->mixext.maxvalue - 1);
1336
1337     /* "It is possible that some enum controls don't have any name list
1338      * available. In this case the application should automatically generate
1339      * list of numbers (0 to N-1)" */
1340     for (i = 0; i < mc->mixext.maxvalue; ++i) {
1341       gchar num_str[8];
1342
1343       g_snprintf (num_str, sizeof (num_str), "%d", i);
1344       mc->enum_vals[i] = g_quark_from_string (num_str);
1345     }
1346     mc->enum_version = 0;       /* the only way to change is via maxvalue */
1347   } else {
1348     /* old list same as current list? */
1349     if (mc->enum_vals != NULL && mc->enum_version == ei.version)
1350       return FALSE;
1351
1352     /* no list yet, or the list has changed */
1353     GST_LOG ("%s", (mc->enum_vals) ? "enum list has changed" : "reading list");
1354     if (ei.nvalues != mc->mixext.maxvalue) {
1355       GST_WARNING_OBJECT (mixer, "Enum: %s, nvalues %d != maxvalue %d",
1356           mc->mixext.extname, ei.nvalues, mc->mixext.maxvalue);
1357       mc->mixext.maxvalue = MIN (ei.nvalues, mc->mixext.maxvalue);
1358     }
1359
1360     mc->mixext.maxvalue = MIN (mc->mixext.maxvalue, OSS_ENUM_MAXVALUE);
1361
1362     g_free (mc->enum_vals);
1363     mc->enum_vals = g_new0 (GQuark, mc->mixext.maxvalue + 1);
1364     for (i = 0; i < mc->mixext.maxvalue; ++i) {
1365       GST_LOG ("  %s", ei.strings + ei.strindex[i]);
1366       mc->enum_vals[i] =
1367           g_quark_from_string (gst_oss4_mixer_control_get_translated_option
1368           (ei.strings + ei.strindex[i]));
1369     }
1370   }
1371
1372   return TRUE;
1373 }
1374
1375 static void
1376 gst_oss4_mixer_controls_find_enums (GstOss4Mixer * mixer,
1377     const GList * controls)
1378 {
1379   const GList *l;
1380
1381   for (l = controls; l != NULL; l = l->next) {
1382     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1383
1384     if (mc->mixext.type != MIXT_ENUM || mc->used)
1385       continue;
1386
1387     mc->is_enum = TRUE;
1388     mc->used = TRUE;
1389
1390     /* Note: enums are special: for most controls, the maxvalue is inclusive,
1391      * but for enum controls it's actually exclusive (boggle), so that
1392      * mixext.maxvalue = num_values */
1393
1394     GST_LOG ("Enum: %s, parent=%s, num_enums=%d", mc->mixext.extname,
1395         mc->parent->mixext.extname, mc->mixext.maxvalue);
1396
1397     gst_oss4_mixer_enum_control_update_enum_list (mixer, mc);
1398   }
1399 }
1400
1401 static void
1402 gst_oss4_mixer_controls_find_switches (GstOss4Mixer * mixer,
1403     const GList * controls)
1404 {
1405   const GList *l;
1406
1407   for (l = controls; l != NULL; l = l->next) {
1408     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1409
1410     if (mc->used)
1411       continue;
1412
1413     if (mc->mixext.type != MIXT_ONOFF && mc->mixext.type != MIXT_MUTE)
1414       continue;
1415
1416     mc->is_switch = TRUE;
1417     mc->used = TRUE;
1418
1419     GST_LOG ("Switch: %s, parent=%s", mc->mixext.extname,
1420         mc->parent->mixext.extname);
1421   }
1422 }
1423
1424 static void
1425 gst_oss4_mixer_controls_find_virtual (GstOss4Mixer * mixer,
1426     const GList * controls)
1427 {
1428   const GList *l;
1429
1430   for (l = controls; l != NULL; l = l->next) {
1431     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1432
1433     /* or sscanf (mc->mixext.extname, "vmix%d-out.", &n) == 1 ? */
1434     /* for now we just flag all virtual controls with managed labels, those
1435      * are really more appropriate for a pavucontrol-type control thing than
1436      * the (more hardware-oriented) mixer interface */
1437     if (mc->mixext.id[0] == '@') {
1438       mc->is_virtual = TRUE;
1439       GST_LOG ("%s is virtual control with managed label", mc->mixext.extname);
1440     }
1441   }
1442 }
1443
1444 static void
1445 gst_oss4_mixer_controls_dump_unused (GstOss4Mixer * mixer,
1446     const GList * controls)
1447 {
1448   const GList *l;
1449
1450   for (l = controls; l != NULL; l = l->next) {
1451     GstOss4MixerControl *mc = (GstOss4MixerControl *) l->data;
1452
1453     if (mc->used)
1454       continue;
1455
1456     switch (mc->mixext.type) {
1457       case MIXT_DEVROOT:
1458       case MIXT_GROUP:
1459       case MIXT_MESSAGE:
1460       case MIXT_MONOVU:
1461       case MIXT_STEREOVU:
1462       case MIXT_MONOPEAK:
1463       case MIXT_STEREOPEAK:
1464       case MIXT_MARKER:
1465         continue;               /* not interested in these types of controls */
1466       case MIXT_MONODB:
1467       case MIXT_STEREODB:
1468         GST_DEBUG ("obsolete control type %d", mc->mixext.type);
1469         continue;
1470       case MIXT_MONOSLIDER:
1471       case MIXT_STEREOSLIDER:
1472       case MIXT_SLIDER:
1473       case MIXT_MONOSLIDER16:
1474       case MIXT_STEREOSLIDER16:
1475         /* this shouldn't happen */
1476         GST_ERROR ("unused slider control?!");
1477         continue;
1478       case MIXT_VALUE:
1479       case MIXT_HEXVALUE:
1480         /* value entry, not sure what to do with that, skip for now */
1481         continue;
1482       case MIXT_ONOFF:
1483       case MIXT_MUTE:
1484       case MIXT_ENUM:
1485       case MIXT_3D:
1486       case MIXT_RADIOGROUP:
1487         GST_DEBUG ("FIXME: handle %s %s",
1488             mixer_ext_type_get_name (mc->mixext.type), mc->mixext.extname);
1489         break;
1490       default:
1491         GST_WARNING ("unknown control type %d", mc->mixext.type);
1492         continue;
1493     }
1494   }
1495 }
1496
1497 static GList *
1498 gst_oss4_mixer_create_tracks (GstOss4Mixer * mixer, const GList * controls)
1499 {
1500   const GList *c;
1501   GList *tracks = NULL;
1502
1503   for (c = controls; c != NULL; c = c->next) {
1504     GstOss4MixerControl *mc = (GstOss4MixerControl *) c->data;
1505     GstMixerTrack *track = NULL;
1506
1507     if (mc->is_virtual)
1508       continue;
1509
1510     if (mc->is_slider) {
1511       track = gst_oss4_mixer_slider_new (mixer, mc);
1512     } else if (mc->is_enum) {
1513       track = gst_oss4_mixer_enum_new (mixer, mc);
1514     } else if (mc->is_switch) {
1515       track = gst_oss4_mixer_switch_new (mixer, mc);
1516     }
1517
1518     if (track == NULL)
1519       continue;
1520
1521     track->label = gst_oss4_mixer_control_get_translated_name (mc);
1522     track->flags = 0;
1523
1524     GST_LOG ("translated label: %s [%s] = %s", track->label, mc->mixext.id,
1525         track->label);
1526
1527     /* This whole 'a track is either INPUT or OUTPUT' model is just flawed,
1528      * esp. if a slider's role can be changed on the fly, like when you change
1529      * function of a connector. What should we do in that case? Change the flag
1530      * and make the app rebuild the interface? Ignore it? */
1531     if (mc->mixext.flags & (MIXF_MAINVOL | MIXF_PCMVOL)) {
1532       track->flags = GST_MIXER_TRACK_OUTPUT | GST_MIXER_TRACK_WHITELIST;
1533
1534     } else if (mc->mixext.flags & MIXF_RECVOL) {
1535       /* record gain whitelisted by default */
1536       track->flags = GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD |
1537           GST_MIXER_TRACK_WHITELIST;
1538
1539     } else if (mc->mixext.flags & MIXF_MONVOL) {
1540       /* monitor sources not whitelisted by default */
1541       track->flags = GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD;
1542     }
1543
1544     /*
1545      * The kernel may give us better clues about the scope of a control.
1546      * If so, try to honor it.
1547      */
1548     switch (mc->mixext.desc & MIXEXT_SCOPE_MASK) {
1549       case MIXEXT_SCOPE_INPUT:
1550       case MIXEXT_SCOPE_RECSWITCH:
1551         track->flags |= GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD |
1552             GST_MIXER_TRACK_WHITELIST;
1553         break;
1554       case MIXEXT_SCOPE_MONITOR:
1555         /* don't whitelist monitor tracks by default */
1556         track->flags |= GST_MIXER_TRACK_INPUT | GST_MIXER_TRACK_NO_RECORD;
1557         break;
1558       case MIXEXT_SCOPE_OUTPUT:
1559         track->flags = GST_MIXER_TRACK_OUTPUT | GST_MIXER_TRACK_WHITELIST;
1560         break;
1561     }
1562
1563     if (mc->is_master) {
1564       track->flags |= GST_MIXER_TRACK_OUTPUT;
1565     }
1566
1567     if (mc->is_master)
1568       track->flags |= GST_MIXER_TRACK_MASTER;
1569
1570     tracks = g_list_append (tracks, track);
1571   }
1572
1573   return tracks;
1574 }
1575
1576 static void
1577 gst_oss4_mixer_update_tracks (GstOss4Mixer * mixer)
1578 {
1579   GList *controls, *tracks;
1580
1581   /* read and process controls */
1582   controls = gst_oss4_mixer_get_controls (mixer);
1583
1584   gst_oss4_mixer_controls_guess_master (mixer, controls);
1585
1586   gst_oss4_mixer_controls_find_sliders (mixer, controls);
1587
1588   gst_oss4_mixer_controls_find_enums (mixer, controls);
1589
1590   gst_oss4_mixer_controls_find_switches (mixer, controls);
1591
1592   gst_oss4_mixer_controls_find_virtual (mixer, controls);
1593
1594   gst_oss4_mixer_controls_dump_unused (mixer, controls);
1595
1596   tracks = gst_oss4_mixer_create_tracks (mixer, controls);
1597
1598   /* free old tracks and controls */
1599   gst_oss4_mixer_free_tracks (mixer);
1600
1601   /* replace old with new */
1602   mixer->tracks = tracks;
1603   mixer->controls = controls;
1604 }
1605
1606 static const GList *
1607 gst_oss4_mixer_list_tracks (GstMixer * mixer_iface)
1608 {
1609   GstOss4Mixer *mixer = GST_OSS4_MIXER (mixer_iface);
1610
1611   g_return_val_if_fail (mixer != NULL, NULL);
1612   g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL);
1613
1614   GST_OBJECT_LOCK (mixer);
1615
1616   /* Do a read on the last control to check if the interface has changed */
1617   if (!mixer->need_update && mixer->last_mixext.ctrl > 0) {
1618     GstOss4MixerControl mc = { {0,}
1619     ,
1620     };
1621     int val;
1622
1623     mc.mixext = mixer->last_mixext;
1624     gst_oss4_mixer_get_control_val (mixer, &mc, &val);
1625   }
1626
1627   if (mixer->need_update || mixer->tracks == NULL) {
1628     gst_oss4_mixer_update_tracks (mixer);
1629     mixer->need_update = FALSE;
1630   }
1631
1632   GST_OBJECT_UNLOCK (mixer);
1633
1634   return (const GList *) mixer->tracks;
1635 }
1636
1637 static void
1638 gst_oss4_mixer_set_volume (GstMixer * mixer, GstMixerTrack * track,
1639     gint * volumes)
1640 {
1641   GstOss4Mixer *oss;
1642
1643   g_return_if_fail (mixer != NULL);
1644   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1645   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1646   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1647   g_return_if_fail (volumes != NULL);
1648
1649   oss = GST_OSS4_MIXER (mixer);
1650
1651   GST_OBJECT_LOCK (oss);
1652
1653   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1654     gst_oss4_mixer_slider_set_volume (GST_OSS4_MIXER_SLIDER (track), volumes);
1655   }
1656
1657   GST_OBJECT_UNLOCK (oss);
1658 }
1659
1660 static void
1661 gst_oss4_mixer_get_volume (GstMixer * mixer, GstMixerTrack * track,
1662     gint * volumes)
1663 {
1664   GstOss4Mixer *oss;
1665
1666   g_return_if_fail (mixer != NULL);
1667   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1668   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1669   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1670   g_return_if_fail (volumes != NULL);
1671
1672   oss = GST_OSS4_MIXER (mixer);
1673
1674   GST_OBJECT_LOCK (oss);
1675
1676   memset (volumes, 0, track->num_channels * sizeof (gint));
1677
1678   if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1679     gboolean enabled = FALSE;
1680     gst_oss4_mixer_switch_get (GST_OSS4_MIXER_SWITCH (track), &enabled);
1681   }
1682   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1683     gst_oss4_mixer_slider_get_volume (GST_OSS4_MIXER_SLIDER (track), volumes);
1684   }
1685
1686   GST_OBJECT_UNLOCK (oss);
1687 }
1688
1689 static void
1690 gst_oss4_mixer_set_record (GstMixer * mixer, GstMixerTrack * track,
1691     gboolean record)
1692 {
1693   GstOss4Mixer *oss;
1694
1695   g_return_if_fail (mixer != NULL);
1696   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1697   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1698   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1699
1700   oss = GST_OSS4_MIXER (mixer);
1701
1702   GST_OBJECT_LOCK (oss);
1703
1704   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1705     gst_oss4_mixer_slider_set_record (GST_OSS4_MIXER_SLIDER (track), record);
1706   } else if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1707     if ((track->flags & GST_MIXER_TRACK_INPUT)) {
1708       gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), record);
1709     } else {
1710       GST_WARNING_OBJECT (track, "set_record called on non-INPUT track");
1711     }
1712   }
1713
1714   GST_OBJECT_UNLOCK (oss);
1715 }
1716
1717 static void
1718 gst_oss4_mixer_set_mute (GstMixer * mixer, GstMixerTrack * track, gboolean mute)
1719 {
1720   GstOss4Mixer *oss;
1721
1722   g_return_if_fail (mixer != NULL);
1723   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1724   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1725   g_return_if_fail (gst_oss4_mixer_contains_track (mixer, track));
1726
1727   oss = GST_OSS4_MIXER (mixer);
1728
1729   GST_OBJECT_LOCK (oss);
1730
1731   if (GST_IS_OSS4_MIXER_SLIDER (track)) {
1732     gst_oss4_mixer_slider_set_mute (GST_OSS4_MIXER_SLIDER (track), mute);
1733   } else if (GST_IS_OSS4_MIXER_SWITCH (track)) {
1734     gst_oss4_mixer_switch_set (GST_OSS4_MIXER_SWITCH (track), mute);
1735   }
1736
1737   GST_OBJECT_UNLOCK (oss);
1738 }
1739
1740 static void
1741 gst_oss4_mixer_set_option (GstMixer * mixer, GstMixerOptions * options,
1742     gchar * value)
1743 {
1744   GstOss4Mixer *oss;
1745
1746   g_return_if_fail (mixer != NULL);
1747   g_return_if_fail (value != NULL);
1748   g_return_if_fail (GST_IS_OSS4_MIXER (mixer));
1749   g_return_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer));
1750   g_return_if_fail (GST_IS_OSS4_MIXER_ENUM (options));
1751   g_return_if_fail (gst_oss4_mixer_contains_options (mixer, options));
1752
1753   oss = GST_OSS4_MIXER (mixer);
1754
1755   GST_OBJECT_LOCK (oss);
1756
1757   if (!gst_oss4_mixer_enum_set_option (GST_OSS4_MIXER_ENUM (options), value)) {
1758     /* not much we can do here but wake up the watch thread early, so it
1759      * can do its thing and post messages if anything has changed */
1760     gst_oss4_mixer_wake_up_watch_task (oss);
1761   }
1762
1763   GST_OBJECT_UNLOCK (oss);
1764 }
1765
1766 static const gchar *
1767 gst_oss4_mixer_get_option (GstMixer * mixer, GstMixerOptions * options)
1768 {
1769   GstOss4Mixer *oss;
1770   const gchar *current_val;
1771
1772   g_return_val_if_fail (mixer != NULL, NULL);
1773   g_return_val_if_fail (GST_IS_OSS4_MIXER (mixer), NULL);
1774   g_return_val_if_fail (GST_OSS4_MIXER_IS_OPEN (mixer), NULL);
1775   g_return_val_if_fail (GST_IS_OSS4_MIXER_ENUM (options), NULL);
1776   g_return_val_if_fail (gst_oss4_mixer_contains_options (mixer, options), NULL);
1777
1778   oss = GST_OSS4_MIXER (mixer);
1779
1780   GST_OBJECT_LOCK (oss);
1781
1782   current_val = gst_oss4_mixer_enum_get_option (GST_OSS4_MIXER_ENUM (options));
1783
1784   if (current_val == NULL) {
1785     /* not much we can do here but wake up the watch thread early, so it
1786      * can do its thing and post messages if anything has changed */
1787     gst_oss4_mixer_wake_up_watch_task (oss);
1788   }
1789
1790   GST_OBJECT_UNLOCK (oss);
1791
1792   return current_val;
1793 }
1794
1795 static GstMixerFlags
1796 gst_oss4_mixer_get_mixer_flags (GstMixer * mixer)
1797 {
1798   return GST_MIXER_FLAG_AUTO_NOTIFICATIONS | GST_MIXER_FLAG_HAS_WHITELIST |
1799       GST_MIXER_FLAG_GROUPING;
1800 }
1801
1802 static void
1803 gst_oss4_mixer_interface_init (GstMixerClass * klass)
1804 {
1805   GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE;
1806
1807   klass->list_tracks = gst_oss4_mixer_list_tracks;
1808   klass->set_volume = gst_oss4_mixer_set_volume;
1809   klass->get_volume = gst_oss4_mixer_get_volume;
1810   klass->set_mute = gst_oss4_mixer_set_mute;
1811   klass->set_record = gst_oss4_mixer_set_record;
1812   klass->set_option = gst_oss4_mixer_set_option;
1813   klass->get_option = gst_oss4_mixer_get_option;
1814   klass->get_mixer_flags = gst_oss4_mixer_get_mixer_flags;
1815 }
1816
1817 /* Implement the horror that is GstImplementsInterface */
1818
1819 static gboolean
1820 gst_oss4_mixer_supported (GstImplementsInterface * iface, GType iface_type)
1821 {
1822   GstOss4Mixer *mixer;
1823
1824   g_return_val_if_fail (iface_type == GST_TYPE_MIXER, FALSE);
1825
1826   mixer = GST_OSS4_MIXER (iface);
1827
1828   return GST_OSS4_MIXER_IS_OPEN (mixer);
1829 }
1830
1831 static void
1832 gst_oss4_mixer_implements_interface_init (GstImplementsInterfaceClass * klass)
1833 {
1834   klass->supported = gst_oss4_mixer_supported;
1835 }
1836
1837 static void
1838 gst_oss4_mixer_init_interfaces (GType type)
1839 {
1840   static const GInterfaceInfo implements_iface_info = {
1841     (GInterfaceInitFunc) gst_oss4_mixer_implements_interface_init,
1842     NULL,
1843     NULL,
1844   };
1845   static const GInterfaceInfo mixer_iface_info = {
1846     (GInterfaceInitFunc) gst_oss4_mixer_interface_init,
1847     NULL,
1848     NULL,
1849   };
1850
1851   g_type_add_interface_static (type, GST_TYPE_IMPLEMENTS_INTERFACE,
1852       &implements_iface_info);
1853   g_type_add_interface_static (type, GST_TYPE_MIXER, &mixer_iface_info);
1854
1855   gst_oss4_add_property_probe_interface (type);
1856 }