6bc89ccdd669e36b38fb847318c7fcc8689742f4
[platform/upstream/gst-plugins-good.git] / sys / oss / gstossmixer.c
1 /* GStreamer OSS Mixer implementation
2  * Copyright (C) 2003 Ronald Bultje <rbultje@ronald.bitfreak.net>
3  *
4  * gstossmixer.c: mixer interface implementation for OSS
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <sys/ioctl.h>
33 #include <sys/soundcard.h>
34
35 #include "gstossmixer.h"
36
37 #define MASK_BIT_IS_SET(mask, bit) \
38   (mask & (1 << bit))
39
40 static void     gst_ossmixer_track_class_init (GstOssMixerTrackClass *klass);
41 static void     gst_ossmixer_track_init    (GstOssMixerTrack *track);
42
43 static gboolean gst_ossmixer_supported     (GstImplementsInterface *iface,
44                                             GType           iface_type);
45 static const GList *
46                 gst_ossmixer_list_tracks   (GstMixer       *ossmixer);
47
48 static void     gst_ossmixer_set_volume    (GstMixer       *ossmixer,
49                                             GstMixerTrack  *track,
50                                             gint           *volumes);
51 static void     gst_ossmixer_get_volume    (GstMixer       *ossmixer,
52                                             GstMixerTrack  *track,
53                                             gint           *volumes);
54
55 static void     gst_ossmixer_set_record    (GstMixer       *ossmixer,
56                                             GstMixerTrack  *track,
57                                             gboolean        record);
58 static void     gst_ossmixer_set_mute      (GstMixer       *ossmixer,
59                                             GstMixerTrack  *track,
60                                             gboolean        mute);
61
62 static const gchar **labels = NULL;
63 static GstMixerTrackClass *parent_class = NULL;
64
65 /* three functions: firstly, OSS has the nasty habit of inserting
66  * spaces in the labels, we want to get rid of them. Secondly,
67  * i18n is impossible with OSS' way of providing us with mixer
68  * labels, so we make a 'given' list of i18n'ed labels. Since
69  * i18n doesn't actually work, we fake it (FIXME). Thirdly, I
70  * personally don't like the "1337" names that OSS gives to their
71  * labels ("Vol", "Mic", "Rec"), I'd rather see full names. */
72 #define _(s) s
73
74 static void
75 fill_labels (void)
76 {
77   gint i, pos;
78   gchar *origs[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
79   struct {
80     gchar *given, *wanted;
81   } cases[] = {
82     /* Note: this list is simply ripped from soundcard.h. For
83      * some people, some values might be missing (3D surround,
84      * etc.) - feel free to add them. That's the reason why
85      * I'm doing this in such a horribly complicated way. */
86     { "Vol  ",    _("Volume")     },
87     { "Bass ",    _("Bass")       },
88     { "Trebl",    _("Treble")     },
89     { "Synth",    _("Synth")      },
90     { "Pcm  ",    _("PCM")        },
91     { "Spkr ",    _("Speaker")    },
92     { "Line ",    _("Line-in")    },
93     { "Mic  ",    _("Microphone") },
94     { "CD   ",    _("CD")         },
95     { "Mix  ",    _("Mixer")      },
96     { "Pcm2 ",    _("PCM-2")      },
97     { "Rec  ",    _("Record")     },
98     { "IGain",    _("In-gain")    },
99     { "OGain",    _("Out-gain")   },
100     { "Line1",    _("Line-1")     },
101     { "Line2",    _("Line-2")     },
102     { "Line3",    _("Line-3")     },
103     { "Digital1", _("Digital-1")  },
104     { "Digital2", _("Digital-2")  },
105     { "Digital3", _("Digital-3")  },
106     { "PhoneIn",  _("Phone-in")   },
107     { "PhoneOut", _("Phone-out")  },
108     { "Video",    _("Video")      },
109     { "Radio",    _("Radio")      },
110     { "Monitor",  _("Monitor")    },
111     { NULL, NULL }
112   };
113
114   labels = g_malloc (sizeof (gchar *) * SOUND_MIXER_NRDEVICES);
115
116   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
117     for (pos = 0; cases[pos].given != NULL; pos++) {
118       if (!strcmp (cases[pos].given, origs[i])) {
119         labels[i] = g_strdup (cases[pos].wanted);
120         break;
121       }
122     }
123     if (cases[pos].given == NULL)
124       labels[i] = g_strdup (origs[i]);
125   }
126 }
127
128 GType
129 gst_ossmixer_track_get_type (void)
130 {
131   static GType gst_ossmixer_track_type = 0;
132
133   if (!gst_ossmixer_track_type) {
134     static const GTypeInfo ossmixer_track_info = {
135       sizeof (GstOssMixerTrackClass),
136       NULL,
137       NULL,
138       (GClassInitFunc) gst_ossmixer_track_class_init,
139       NULL,
140       NULL,
141       sizeof (GstOssMixerTrack),
142       0,
143       (GInstanceInitFunc) gst_ossmixer_track_init,
144       NULL
145     };
146
147     gst_ossmixer_track_type =
148         g_type_register_static (GST_TYPE_MIXER_TRACK,
149                                 "GstOssMixerTrack",
150                                 &ossmixer_track_info, 0);
151   }
152
153   return gst_ossmixer_track_type;
154 }
155
156 static void
157 gst_ossmixer_track_class_init (GstOssMixerTrackClass *klass)
158 {
159   parent_class = g_type_class_ref (GST_TYPE_MIXER_TRACK);
160 }
161
162 static void
163 gst_ossmixer_track_init (GstOssMixerTrack *track)
164 {
165   track->lvol = track->rvol = 0;
166   track->track_num = 0;
167 }
168
169 GstMixerTrack *
170 gst_ossmixer_track_new (GstOssElement *oss,
171                         gint track_num,
172                         gint max_chans,
173                         gint flags)
174 {
175   GstOssMixerTrack *osstrack;
176   GstMixerTrack *track;
177   gint volume;
178
179   if (!labels)
180     fill_labels ();
181
182   osstrack = g_object_new (GST_TYPE_OSSMIXER_TRACK, NULL);
183   track = GST_MIXER_TRACK (osstrack);
184   track->label = g_strdup (labels[track_num]);
185   track->num_channels = max_chans;
186   track->flags = flags;
187   track->min_volume = 0;
188   track->max_volume = 100;
189   osstrack->track_num = track_num;
190
191   /* volume */
192   if (ioctl(oss->mixer_fd, MIXER_READ (osstrack->track_num), &volume) < 0) {
193     g_warning("Error getting device (%d) volume: %s",
194               osstrack->track_num, strerror(errno));
195     volume = 0;
196   }
197   osstrack->lvol = (volume & 0xff);
198   if (track->num_channels == 2) {
199     osstrack->rvol = ((volume >> 8) & 0xff);
200   }
201
202   return track;
203 }
204
205 void
206 gst_oss_interface_init (GstImplementsInterfaceClass *klass)
207 {
208   /* default virtual functions */
209   klass->supported = gst_ossmixer_supported;
210 }
211
212 void
213 gst_ossmixer_interface_init (GstMixerClass *klass)
214 {
215   GST_MIXER_TYPE (klass) = GST_MIXER_HARDWARE;
216   
217   /* default virtual functions */
218   klass->list_tracks = gst_ossmixer_list_tracks;
219   klass->set_volume = gst_ossmixer_set_volume;
220   klass->get_volume = gst_ossmixer_get_volume;
221   klass->set_mute = gst_ossmixer_set_mute;
222   klass->set_record = gst_ossmixer_set_record;
223 }
224
225 static gboolean
226 gst_ossmixer_supported (GstImplementsInterface *iface,
227                         GType                   iface_type)
228 {
229   g_assert (iface_type == GST_TYPE_MIXER);
230
231   return (GST_OSSELEMENT (iface)->mixer_fd != -1);
232 }
233
234 static gboolean
235 gst_ossmixer_contains_track (GstOssElement    *oss,
236                              GstOssMixerTrack *osstrack)
237 {
238   const GList *item;
239
240   for (item = oss->tracklist; item != NULL; item = item->next)
241     if (item->data == osstrack)
242       return TRUE;
243
244   return FALSE;
245 }
246
247 static const GList *
248 gst_ossmixer_list_tracks (GstMixer *mixer)
249 {
250   return (const GList *) GST_OSSELEMENT (mixer)->tracklist;
251 }
252
253 static void
254 gst_ossmixer_get_volume (GstMixer      *mixer,
255                          GstMixerTrack *track,
256                          gint          *volumes)
257 {
258   gint volume;
259   GstOssElement *oss = GST_OSSELEMENT (mixer);
260   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
261
262   /* assert that we're opened and that we're using a known item */
263   g_return_if_fail (oss->mixer_fd != -1);
264   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
265
266   if (track->flags & GST_MIXER_TRACK_MUTE) {
267     volumes[0] = osstrack->lvol;
268     if (track->num_channels == 2) {
269       volumes[1] = osstrack->rvol;
270     }
271   } else {
272     /* get */
273     if (ioctl(oss->mixer_fd, MIXER_READ (osstrack->track_num), &volume) < 0) {
274       g_warning("Error getting recording device (%d) volume: %s",
275                 osstrack->track_num, strerror(errno));
276       volume = 0;
277     }
278
279     osstrack->lvol = volumes[0] = (volume & 0xff);
280     if (track->num_channels == 2) {
281       osstrack->rvol = volumes[1] = ((volume >> 8) & 0xff);
282     }
283   }
284 }
285
286 static void
287 gst_ossmixer_set_volume (GstMixer      *mixer,
288                          GstMixerTrack *track,
289                          gint          *volumes)
290 {
291   gint volume;
292   GstOssElement *oss = GST_OSSELEMENT (mixer);
293   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
294
295   /* assert that we're opened and that we're using a known item */
296   g_return_if_fail (oss->mixer_fd != -1);
297   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
298
299   /* prepare the value for ioctl() */
300   if (!(track->flags & GST_MIXER_TRACK_MUTE)) {
301     volume = (volumes[0] & 0xff);
302     if (track->num_channels == 2) {
303       volume |= ((volumes[1] & 0xff) << 8);
304     }
305
306     /* set */
307     if (ioctl(oss->mixer_fd, MIXER_WRITE (osstrack->track_num), &volume) < 0) {
308       g_warning("Error setting recording device (%d) volume (0x%x): %s",
309                 osstrack->track_num, volume, strerror(errno));
310       return;
311     }
312   }
313
314   osstrack->lvol = volumes[0];
315   if (track->num_channels == 2) {
316     osstrack->rvol = volumes[1];
317   }
318 }
319
320 static void
321 gst_ossmixer_set_mute (GstMixer      *mixer,
322                        GstMixerTrack *track,
323                        gboolean       mute)
324 {
325   int volume;
326   GstOssElement *oss = GST_OSSELEMENT (mixer);
327   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
328
329   /* assert that we're opened and that we're using a known item */
330   g_return_if_fail (oss->mixer_fd != -1);
331   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
332
333   if (mute) {
334     volume = 0;
335   } else {
336     volume = (osstrack->lvol & 0xff);
337     if (MASK_BIT_IS_SET (oss->stereomask, osstrack->track_num)) {
338       volume |= ((osstrack->rvol & 0xff) << 8);
339     }
340   }
341
342   if (ioctl(oss->mixer_fd, MIXER_WRITE(osstrack->track_num), &volume) < 0) {
343     g_warning("Error setting mixer recording device volume (0x%x): %s",
344               volume, strerror(errno));
345     return;
346   }
347
348   if (mute) {
349     track->flags |= GST_MIXER_TRACK_MUTE;
350   } else {
351     track->flags &= ~GST_MIXER_TRACK_MUTE;
352   }
353 }
354
355 static void
356 gst_ossmixer_set_record (GstMixer      *mixer,
357                          GstMixerTrack *track,
358                          gboolean       record)
359 {
360   GstOssElement *oss = GST_OSSELEMENT (mixer);
361   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
362
363   /* assert that we're opened and that we're using a known item */
364   g_return_if_fail (oss->mixer_fd != -1);
365   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
366
367   /* if there's nothing to do... */
368   if ((record && GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)) ||
369       (!record && !GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)))
370     return;
371
372   /* if we're exclusive, then we need to unset the current one(s) */
373   if (oss->mixcaps & SOUND_CAP_EXCL_INPUT) {
374     GList *track;
375     for (track = oss->tracklist; track != NULL; track = track->next) {
376       GstMixerTrack *turn = (GstMixerTrack *) track->data;
377       turn->flags &= ~GST_MIXER_TRACK_RECORD;
378     }
379     oss->recdevs = 0;
380   }
381
382   /* set new record bit, if needed */
383   if (record) {
384     oss->recdevs |= (1 << osstrack->track_num);
385   } else {
386     oss->recdevs &= ~(1 << osstrack->track_num);
387   }
388
389   /* set it to the device */
390   if (ioctl(oss->mixer_fd, SOUND_MIXER_WRITE_RECSRC, &oss->recdevs) < 0) {
391     g_warning("Error setting mixer recording devices (0x%x): %s",
392               oss->recdevs, strerror(errno));
393     return;
394   }
395
396   if (record) {
397     track->flags |= GST_MIXER_TRACK_RECORD;
398   } else {
399     track->flags &= ~GST_MIXER_TRACK_RECORD;
400   }
401 }
402
403 void
404 gst_ossmixer_build_list (GstOssElement *oss)
405 {
406   gint i, devmask, master = -1;
407   const GList *pads = gst_element_get_pad_list (GST_ELEMENT (oss));
408   GstPadDirection dir = GST_PAD_UNKNOWN;
409 #ifdef SOUND_MIXER_INFO
410   struct mixer_info minfo;
411 #endif
412
413   g_return_if_fail (oss->mixer_fd == -1);
414
415   oss->mixer_fd = open (oss->mixer_dev, O_RDWR);
416   if (oss->mixer_fd == -1) {
417     /* this is valid. OSS devices don't need to expose a mixer */
418     GST_DEBUG ("Failed to open mixer device %s, mixing disabled: %s",
419                oss->mixer_dev, strerror (errno));
420     return;
421   }
422
423   /* get direction */
424   if (pads && g_list_length ((GList *) pads) == 1)
425     dir = GST_PAD_DIRECTION (GST_PAD (pads->data));
426
427   /* get masks */
428   if (ioctl (oss->mixer_fd, SOUND_MIXER_READ_RECMASK, &oss->recmask) < 0 ||
429       ioctl (oss->mixer_fd, SOUND_MIXER_READ_RECSRC, &oss->recdevs) < 0 ||
430       ioctl (oss->mixer_fd, SOUND_MIXER_READ_STEREODEVS, &oss->stereomask) < 0 ||
431       ioctl (oss->mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0 ||
432       ioctl (oss->mixer_fd, SOUND_MIXER_READ_CAPS, &oss->mixcaps) < 0) {
433     GST_DEBUG ("Failed to get device masks - disabling mixer");
434     close (oss->mixer_fd);
435     oss->mixer_fd = -1;
436     return;
437   }
438
439   /* get name */
440 #ifdef SOUND_MIXER_INFO
441   if (ioctl (oss->mixer_fd, SOUND_MIXER_INFO, &minfo) == 0) {
442     oss->device_name = g_strdup (minfo.name);
443   }
444 #else
445   oss->device_name = g_strdup ("Unknown");
446 #endif
447
448   /* find master volume */
449   if (devmask & SOUND_MASK_VOLUME)
450     master = SOUND_MIXER_VOLUME;
451   else if (devmask & SOUND_MASK_PCM)
452     master = SOUND_MIXER_PCM;
453   else if (devmask & SOUND_MASK_SPEAKER)
454     master = SOUND_MIXER_SPEAKER; /* doubtful... */
455   /* else: no master, so we won't set any */
456
457   /* build track list */
458   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
459     if (devmask & (1 << i)) {
460       GstMixerTrack *track;
461       gboolean input = FALSE, stereo = FALSE, record = FALSE;
462
463       /* track exists, make up capabilities */
464       if (MASK_BIT_IS_SET (oss->stereomask, i))
465         stereo = TRUE;
466       if (MASK_BIT_IS_SET (oss->recmask, i))
467         input = TRUE;
468       if (MASK_BIT_IS_SET (oss->recdevs, i))
469         record = TRUE;
470
471       /* do we want this in our list? */
472       if ((dir == GST_PAD_SRC && input == FALSE) ||
473           (dir == GST_PAD_SINK && i != SOUND_MIXER_PCM))
474         continue;
475
476       /* add track to list */
477       track = gst_ossmixer_track_new (oss, i, stereo ? 2 : 1,
478                                           (record ? GST_MIXER_TRACK_RECORD : 0) |
479                                           (input ? GST_MIXER_TRACK_INPUT :
480                                                    GST_MIXER_TRACK_OUTPUT) |
481                                           ((master != i) ? 0 :
482                                                    GST_MIXER_TRACK_MASTER));
483       oss->tracklist = g_list_append (oss->tracklist, track);
484     }
485   }
486 }
487
488 void
489 gst_ossmixer_free_list (GstOssElement *oss)
490 {
491   if (oss->mixer_fd == -1)
492     return;
493
494   g_list_foreach (oss->tracklist, (GFunc) g_object_unref, NULL);
495   g_list_free (oss->tracklist);
496   oss->tracklist = NULL;
497
498   if (oss->device_name) {
499     g_free (oss->device_name);
500     oss->device_name = NULL;
501   }
502
503   close (oss->mixer_fd);
504   oss->mixer_fd = -1;
505 }