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