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