Er, right, this is a typo
[platform/upstream/gstreamer.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   /* default virtual functions */
216   klass->list_tracks = gst_ossmixer_list_tracks;
217   klass->set_volume = gst_ossmixer_set_volume;
218   klass->get_volume = gst_ossmixer_get_volume;
219   klass->set_mute = gst_ossmixer_set_mute;
220   klass->set_record = gst_ossmixer_set_record;
221 }
222
223 static gboolean
224 gst_ossmixer_supported (GstImplementsInterface *iface,
225                         GType                   iface_type)
226 {
227   g_assert (iface_type == GST_TYPE_MIXER);
228
229   return (GST_OSSELEMENT (iface)->mixer_fd != -1);
230 }
231
232 static gboolean
233 gst_ossmixer_contains_track (GstOssElement    *oss,
234                              GstOssMixerTrack *osstrack)
235 {
236   const GList *item;
237
238   for (item = oss->tracklist; item != NULL; item = item->next)
239     if (item->data == osstrack)
240       return TRUE;
241
242   return FALSE;
243 }
244
245 static const GList *
246 gst_ossmixer_list_tracks (GstMixer *mixer)
247 {
248   return (const GList *) GST_OSSELEMENT (mixer)->tracklist;
249 }
250
251 static void
252 gst_ossmixer_get_volume (GstMixer      *mixer,
253                          GstMixerTrack *track,
254                          gint          *volumes)
255 {
256   gint volume;
257   GstOssElement *oss = GST_OSSELEMENT (mixer);
258   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
259
260   /* assert that we're opened and that we're using a known item */
261   g_return_if_fail (oss->mixer_fd != -1);
262   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
263
264   if (track->flags & GST_MIXER_TRACK_MUTE) {
265     volumes[0] = osstrack->lvol;
266     if (track->num_channels == 2) {
267       volumes[1] = osstrack->rvol;
268     }
269   } else {
270     /* get */
271     if (ioctl(oss->mixer_fd, MIXER_READ (osstrack->track_num), &volume) < 0) {
272       g_warning("Error getting recording device (%d) volume: %s",
273                 osstrack->track_num, strerror(errno));
274       volume = 0;
275     }
276
277     osstrack->lvol = volumes[0] = (volume & 0xff);
278     if (track->num_channels == 2) {
279       osstrack->rvol = volumes[1] = ((volume >> 8) & 0xff);
280     }
281   }
282 }
283
284 static void
285 gst_ossmixer_set_volume (GstMixer      *mixer,
286                          GstMixerTrack *track,
287                          gint          *volumes)
288 {
289   gint volume;
290   GstOssElement *oss = GST_OSSELEMENT (mixer);
291   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
292
293   /* assert that we're opened and that we're using a known item */
294   g_return_if_fail (oss->mixer_fd != -1);
295   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
296
297   /* prepare the value for ioctl() */
298   if (!(track->flags & GST_MIXER_TRACK_MUTE)) {
299     volume = (volumes[0] & 0xff);
300     if (track->num_channels == 2) {
301       volume |= ((volumes[1] & 0xff) << 8);
302     }
303
304     /* set */
305     if (ioctl(oss->mixer_fd, MIXER_WRITE (osstrack->track_num), &volume) < 0) {
306       g_warning("Error setting recording device (%d) volume (0x%x): %s",
307                 osstrack->track_num, volume, strerror(errno));
308       return;
309     }
310   }
311
312   osstrack->lvol = volumes[0];
313   if (track->num_channels == 2) {
314     osstrack->rvol = volumes[1];
315   }
316 }
317
318 static void
319 gst_ossmixer_set_mute (GstMixer      *mixer,
320                        GstMixerTrack *track,
321                        gboolean       mute)
322 {
323   int volume;
324   GstOssElement *oss = GST_OSSELEMENT (mixer);
325   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
326
327   /* assert that we're opened and that we're using a known item */
328   g_return_if_fail (oss->mixer_fd != -1);
329   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
330
331   if (mute) {
332     volume = 0;
333   } else {
334     volume = (osstrack->lvol & 0xff);
335     if (MASK_BIT_IS_SET (oss->stereomask, osstrack->track_num)) {
336       volume |= ((osstrack->rvol & 0xff) << 8);
337     }
338   }
339
340   if (ioctl(oss->mixer_fd, MIXER_WRITE(osstrack->track_num), &volume) < 0) {
341     g_warning("Error setting mixer recording device volume (0x%x): %s",
342               volume, strerror(errno));
343     return;
344   }
345
346   if (mute) {
347     track->flags |= GST_MIXER_TRACK_MUTE;
348   } else {
349     track->flags &= ~GST_MIXER_TRACK_MUTE;
350   }
351 }
352
353 static void
354 gst_ossmixer_set_record (GstMixer      *mixer,
355                          GstMixerTrack *track,
356                          gboolean       record)
357 {
358   GstOssElement *oss = GST_OSSELEMENT (mixer);
359   GstOssMixerTrack *osstrack = GST_OSSMIXER_TRACK (track);
360
361   /* assert that we're opened and that we're using a known item */
362   g_return_if_fail (oss->mixer_fd != -1);
363   g_return_if_fail (gst_ossmixer_contains_track (oss, osstrack));
364
365   /* if there's nothing to do... */
366   if ((record && GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)) ||
367       (!record && !GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_RECORD)))
368     return;
369
370   /* if we're exclusive, then we need to unset the current one(s) */
371   if (oss->mixcaps & SOUND_CAP_EXCL_INPUT) {
372     GList *track;
373     for (track = oss->tracklist; track != NULL; track = track->next) {
374       GstMixerTrack *turn = (GstMixerTrack *) track->data;
375       turn->flags &= ~GST_MIXER_TRACK_RECORD;
376     }
377     oss->recdevs = 0;
378   }
379
380   /* set new record bit, if needed */
381   if (record) {
382     oss->recdevs |= (1 << osstrack->track_num);
383   } else {
384     oss->recdevs &= ~(1 << osstrack->track_num);
385   }
386
387   /* set it to the device */
388   if (ioctl(oss->mixer_fd, SOUND_MIXER_WRITE_RECSRC, &oss->recdevs) < 0) {
389     g_warning("Error setting mixer recording devices (0x%x): %s",
390               oss->recdevs, strerror(errno));
391     return;
392   }
393
394   if (record) {
395     track->flags |= GST_MIXER_TRACK_RECORD;
396   } else {
397     track->flags &= ~GST_MIXER_TRACK_RECORD;
398   }
399 }
400
401 void
402 gst_ossmixer_build_list (GstOssElement *oss)
403 {
404   gint i, devmask;
405   const GList *pads = gst_element_get_pad_list (GST_ELEMENT (oss));
406   GstPadDirection dir = GST_PAD_UNKNOWN;
407 #ifdef SOUND_MIXER_INFO
408   struct mixer_info minfo;
409 #endif
410
411   g_return_if_fail (oss->mixer_fd == -1);
412
413   oss->mixer_fd = open (oss->mixer_dev, O_RDWR);
414   if (oss->mixer_fd == -1) {
415     /* this is valid. OSS devices don't need to expose a mixer */
416     GST_DEBUG ("Failed to open mixer device %s, mixing disabled: %s",
417                oss->mixer_dev, strerror (errno));
418     return;
419   }
420
421   /* get direction */
422   if (pads && g_list_length ((GList *) pads) == 1)
423     dir = GST_PAD_DIRECTION (GST_PAD (pads->data));
424
425   /* get masks */
426   if (ioctl (oss->mixer_fd, SOUND_MIXER_READ_RECMASK, &oss->recmask) < 0 ||
427       ioctl (oss->mixer_fd, SOUND_MIXER_READ_RECSRC, &oss->recdevs) < 0 ||
428       ioctl (oss->mixer_fd, SOUND_MIXER_READ_STEREODEVS, &oss->stereomask) < 0 ||
429       ioctl (oss->mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0 ||
430       ioctl (oss->mixer_fd, SOUND_MIXER_READ_CAPS, &oss->mixcaps) < 0) {
431     GST_DEBUG ("Failed to get device masks - disabling mixer");
432     close (oss->mixer_fd);
433     oss->mixer_fd = -1;
434     return;
435   }
436
437   /* get name */
438 #ifdef SOUND_MIXER_INFO
439   if (ioctl (oss->mixer_fd, SOUND_MIXER_INFO, &minfo) == 0) {
440     oss->device_name = g_strdup (minfo.name);
441   }
442 #else
443   oss->device_name = g_strdup ("Unknown");
444 #endif
445
446   /* build track list */
447   for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
448     if (devmask & (1 << i)) {
449       GstMixerTrack *track;
450       gboolean input = FALSE, stereo = FALSE, record = FALSE;
451
452       /* track exists, make up capabilities */
453       if (MASK_BIT_IS_SET (oss->stereomask, i))
454         stereo = TRUE;
455       if (MASK_BIT_IS_SET (oss->recmask, i))
456         input = TRUE;
457       if (MASK_BIT_IS_SET (oss->recdevs, i))
458         record = TRUE;
459
460       /* do we want this in our list? */
461       if ((dir == GST_PAD_SRC && input == FALSE) ||
462           (dir == GST_PAD_SINK && i != SOUND_MIXER_PCM))
463         continue;
464
465       /* add track to list */
466       track = gst_ossmixer_track_new (oss, i, stereo ? 2 : 1,
467                                           (record ? GST_MIXER_TRACK_RECORD : 0) |
468                                           (input ? GST_MIXER_TRACK_INPUT :
469                                                    GST_MIXER_TRACK_OUTPUT));
470       oss->tracklist = g_list_append (oss->tracklist, track);
471     }
472   }
473 }
474
475 void
476 gst_ossmixer_free_list (GstOssElement *oss)
477 {
478   if (oss->mixer_fd == -1)
479     return;
480
481   g_list_foreach (oss->tracklist, (GFunc) g_object_unref, NULL);
482   g_list_free (oss->tracklist);
483   oss->tracklist = NULL;
484
485   if (oss->device_name) {
486     g_free (oss->device_name);
487     oss->device_name = NULL;
488   }
489
490   close (oss->mixer_fd);
491   oss->mixer_fd = -1;
492 }