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