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