Merge branch 'master' into 0.11
[platform/upstream/gst-plugins-good.git] / sys / oss4 / oss4-property-probe.c
1 /* GStreamer OSS4 audio property probe interface implementation
2  * Copyright (C) 2007-2008 Tim-Philipp Müller <tim centricular net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <gst/gst.h>
25
26 #define NO_LEGACY_MIXER
27 #include "oss4-audio.h"
28 #include "oss4-mixer.h"
29 #include "oss4-sink.h"
30 #include "oss4-source.h"
31 #include "oss4-soundcard.h"
32 #include "oss4-property-probe.h"
33
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/ioctl.h>
37 #include <fcntl.h>
38 #include <unistd.h>
39 #include <errno.h>
40 #include <string.h>
41
42 GST_DEBUG_CATEGORY_EXTERN (oss4_debug);
43 #define GST_CAT_DEFAULT oss4_debug
44
45 static const GList *
46 gst_oss4_property_probe_get_properties (GstPropertyProbe * probe)
47 {
48   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
49   GList *list;
50
51   GST_OBJECT_LOCK (GST_OBJECT (probe));
52
53   /* we create a new list and store it in the instance struct, since apparently
54    * we forgot to update the API for 0.10 (and why don't we mark probable
55    * properties with a flag instead anyway?). A bit hackish, but what can you
56    * do (can't really use a static variable since the pspec will be different
57    * for src and sink class); this isn't particularly pretty, but the best
58    * we can do given that we can't create a common base class (we could do
59    * fancy things with the interface, or use g_object_set_data instead, but
60    * it's not really going to make it much better) */
61   if (GST_IS_AUDIO_SINK_CLASS (klass)) {
62     list = GST_OSS4_SINK (probe)->property_probe_list;
63   } else if (GST_IS_AUDIO_SRC_CLASS (klass)) {
64     list = GST_OSS4_SOURCE (probe)->property_probe_list;
65   } else if (GST_IS_OSS4_MIXER_CLASS (klass)) {
66     list = GST_OSS4_MIXER (probe)->property_probe_list;
67   } else {
68     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
69     g_return_val_if_reached (NULL);
70   }
71
72   if (list == NULL) {
73     GParamSpec *pspec;
74
75     pspec = g_object_class_find_property (klass, "device");
76     list = g_list_prepend (NULL, pspec);
77
78     if (GST_IS_AUDIO_SINK_CLASS (klass)) {
79       GST_OSS4_SINK (probe)->property_probe_list = list;
80     } else if (GST_IS_AUDIO_SRC_CLASS (klass)) {
81       GST_OSS4_SOURCE (probe)->property_probe_list = list;
82     } else if (GST_IS_OSS4_MIXER_CLASS (klass)) {
83       GST_OSS4_MIXER (probe)->property_probe_list = list;
84     }
85   }
86
87   GST_OBJECT_UNLOCK (GST_OBJECT (probe));
88
89   return list;
90 }
91
92 static void
93 gst_oss4_property_probe_probe_property (GstPropertyProbe * probe,
94     guint prop_id, const GParamSpec * pspec)
95 {
96   if (!g_str_equal (pspec->name, "device")) {
97     G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
98   }
99 }
100
101 static gboolean
102 gst_oss4_property_probe_needs_probe (GstPropertyProbe * probe,
103     guint prop_id, const GParamSpec * pspec)
104 {
105   /* don't cache probed data */
106   return TRUE;
107 }
108
109 static gint
110 oss4_mixerinfo_priority_cmp (struct oss_mixerinfo *mi1,
111     struct oss_mixerinfo *mi2)
112 {
113   /* return negative vaue if mi1 comes before mi2 */
114   if (mi1->priority != mi2->priority)
115     return mi2->priority - mi1->priority;
116
117   return strcmp (mi1->devnode, mi2->devnode);
118 }
119
120 /* caller must ensure LOCK is taken (e.g. if ioctls need to be serialised) */
121 gboolean
122 gst_oss4_property_probe_find_device_name (GstObject * obj, int fd,
123     const gchar * device_handle, gchar ** device_name)
124 {
125   struct oss_sysinfo si = { {0,}, };
126   gchar *name = NULL;
127
128   if (ioctl (fd, SNDCTL_SYSINFO, &si) == 0) {
129     int i;
130
131     for (i = 0; i < si.numaudios; ++i) {
132       struct oss_audioinfo ai = { 0, };
133
134       ai.dev = i;
135       if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) {
136         GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i);
137         continue;
138       }
139       if (strcmp (ai.devnode, device_handle) == 0) {
140         name = g_strdup (ai.name);
141         break;
142       }
143     }
144   } else {
145     GST_WARNING_OBJECT (obj, "SYSINFO ioctl failed: %s", g_strerror (errno));
146   }
147
148   /* try ENGINEINFO as fallback (which is better than nothing) */
149   if (name == NULL) {
150     struct oss_audioinfo ai = { 0, };
151
152     GST_LOG_OBJECT (obj, "device %s not listed in AUDIOINFO", device_handle);
153     ai.dev = -1;
154     if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) == 0)
155       name = g_strdup (ai.name);
156   }
157
158   GST_DEBUG_OBJECT (obj, "Device name: %s", GST_STR_NULL (name));
159
160   if (name != NULL)
161     *device_name = name;
162
163   return (name != NULL);
164 }
165
166 gboolean
167 gst_oss4_property_probe_find_device_name_nofd (GstObject * obj,
168     const gchar * device_handle, gchar ** device_name)
169 {
170   gboolean res;
171   int fd;
172
173   fd = open ("/dev/mixer", O_RDONLY);
174   if (fd < 0)
175     return FALSE;
176
177   res = gst_oss4_property_probe_find_device_name (obj, fd, device_handle,
178       device_name);
179
180   close (fd);
181   return res;
182 }
183
184 static GList *
185 gst_oss4_property_probe_get_mixer_devices (GstObject * obj, int fd,
186     struct oss_sysinfo *si)
187 {
188   GList *m, *mixers = NULL;
189   GList *devices = NULL;
190
191   int i;
192
193   GST_LOG_OBJECT (obj, "%d mixer devices", si->nummixers);
194
195   /* first, find suitable mixer devices and sort by priority */
196   for (i = 0; i < si->nummixers; ++i) {
197     struct oss_mixerinfo mi = { 0, };
198
199     mi.dev = i;
200     if (ioctl (fd, SNDCTL_MIXERINFO, &mi) == -1) {
201       GST_DEBUG_OBJECT (obj, "MIXERINFO ioctl for device %d failed", i);
202       continue;
203     }
204
205     GST_INFO_OBJECT (obj, "mixer device %d:", i);
206     GST_INFO_OBJECT (obj, "  enabled  : %s", (mi.enabled) ? "yes" :
207         "no (powered off or unplugged)");
208     GST_INFO_OBJECT (obj, "  priority : %d", mi.priority);
209     GST_INFO_OBJECT (obj, "  devnode  : %s", mi.devnode);
210     GST_INFO_OBJECT (obj, "  handle   : %s", mi.handle);
211     GST_INFO_OBJECT (obj, "  caps     : 0x%02x", mi.caps);
212     GST_INFO_OBJECT (obj, "  name     : %s", mi.name);
213
214     if (!mi.enabled) {
215       GST_DEBUG_OBJECT (obj, "mixer device is not usable/enabled, skipping");
216       continue;
217     }
218
219     /* only want mixers that control hardware directly */
220     if ((mi.caps & MIXER_CAP_VIRTUAL)) {
221       GST_DEBUG_OBJECT (obj, "mixer device is a virtual device, skipping");
222       continue;
223     }
224
225     mixers = g_list_insert_sorted (mixers, g_memdup (&mi, sizeof (mi)),
226         (GCompareFunc) oss4_mixerinfo_priority_cmp);
227   }
228
229   /* then create device list according to priority */
230   for (m = mixers; m != NULL; m = m->next) {
231     struct oss_mixerinfo *mi = (struct oss_mixerinfo *) m->data;
232
233     GST_LOG_OBJECT (obj, "mixer device: '%s'", mi->devnode);
234     devices = g_list_prepend (devices, g_strdup (mi->devnode));
235     g_free (m->data);
236   }
237   g_list_free (mixers);
238   mixers = NULL;
239
240   return g_list_reverse (devices);
241 }
242
243 static GList *
244 gst_oss4_property_probe_get_audio_devices (GstObject * obj, int fd,
245     struct oss_sysinfo *si, int cap_mask)
246 {
247   GList *devices = NULL;
248   int i;
249
250   GST_LOG_OBJECT (obj, "%d audio/dsp devices", si->numaudios);
251
252   for (i = 0; i < si->numaudios; ++i) {
253     struct oss_audioinfo ai = { 0, };
254
255     ai.dev = i;
256     if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) {
257       GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i);
258       continue;
259     }
260
261     if ((ai.caps & cap_mask) == 0) {
262       GST_DEBUG_OBJECT (obj, "audio device %d is not an %s device", i,
263           (cap_mask == PCM_CAP_OUTPUT) ? "output" : "input");
264       continue;
265     }
266
267     if (!ai.enabled) {
268       GST_DEBUG_OBJECT (obj, "audio device %d is not usable/enabled", i);
269       continue;
270     }
271
272     GST_DEBUG_OBJECT (obj, "audio device %d looks ok: %s (\"%s\")", i,
273         ai.devnode, ai.name);
274
275     devices = g_list_prepend (devices, g_strdup (ai.devnode));
276   }
277
278   return g_list_reverse (devices);
279 }
280
281 static GValueArray *
282 gst_oss4_property_probe_get_values (GstPropertyProbe * probe,
283     guint prop_id, const GParamSpec * pspec)
284 {
285   struct oss_sysinfo si = { {0,}, };
286   GValueArray *array = NULL;
287   GstObject *obj;
288   GList *devices, *l;
289   int cap_mask, fd = -1;
290
291   if (!g_str_equal (pspec->name, "device")) {
292     G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
293     return NULL;
294   }
295
296   obj = GST_OBJECT (probe);
297
298   GST_OBJECT_LOCK (obj);
299
300   /* figure out whether the element is a source or sink */
301   if (GST_IS_OSS4_SINK (probe)) {
302     GST_DEBUG_OBJECT (probe, "probing available output devices");
303     cap_mask = PCM_CAP_OUTPUT;
304     fd = GST_OSS4_SINK (probe)->fd;
305   } else if (GST_IS_OSS4_SOURCE (probe)) {
306     GST_DEBUG_OBJECT (probe, "probing available input devices");
307     cap_mask = PCM_CAP_INPUT;
308     fd = GST_OSS4_SOURCE (probe)->fd;
309   } else if (GST_IS_OSS4_MIXER (probe)) {
310     fd = GST_OSS4_MIXER (probe)->fd;
311     cap_mask = 0;
312   } else {
313     GST_OBJECT_UNLOCK (obj);
314     g_return_val_if_reached (NULL);
315   }
316
317   /* copy fd if it's open, so we can just unconditionally close() later */
318   if (fd != -1)
319     fd = dup (fd);
320
321   /* this will also catch the unlikely case where the above dup() failed */
322   if (fd == -1) {
323     fd = open ("/dev/mixer", O_RDONLY | O_NONBLOCK, 0);
324     if (fd < 0)
325       goto open_failed;
326     else if (!gst_oss4_audio_check_version (GST_OBJECT (probe), fd))
327       goto legacy_oss;
328   }
329
330   if (ioctl (fd, SNDCTL_SYSINFO, &si) == -1)
331     goto no_sysinfo;
332
333   if (cap_mask != 0) {
334     devices =
335         gst_oss4_property_probe_get_audio_devices (obj, fd, &si, cap_mask);
336   } else {
337     devices = gst_oss4_property_probe_get_mixer_devices (obj, fd, &si);
338   }
339
340   if (devices == NULL) {
341     GST_DEBUG_OBJECT (obj, "No devices found");
342     goto done;
343   }
344
345   array = g_value_array_new (1);
346
347   for (l = devices; l != NULL; l = l->next) {
348     GValue val = { 0, };
349
350     g_value_init (&val, G_TYPE_STRING);
351     g_value_take_string (&val, (gchar *) l->data);
352     l->data = NULL;
353     g_value_array_append (array, &val);
354     g_value_unset (&val);
355   }
356
357   GST_OBJECT_UNLOCK (obj);
358
359   g_list_free (devices);
360
361 done:
362
363   close (fd);
364
365   return array;
366
367 /* ERRORS */
368 open_failed:
369   {
370     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
371     GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe "
372         "available devices: %s", g_strerror (errno));
373     return NULL;
374   }
375 legacy_oss:
376   {
377     close (fd);
378     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
379     GST_DEBUG_OBJECT (probe, "Legacy OSS (ie. not OSSv4), not supported");
380     return NULL;
381   }
382 no_sysinfo:
383   {
384     close (fd);
385     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
386     GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe "
387         "available devices: %s", g_strerror (errno));
388     return NULL;
389   }
390 }
391
392 static void
393 gst_oss4_property_probe_interface_init (GstPropertyProbeInterface * iface)
394 {
395   iface->get_properties = gst_oss4_property_probe_get_properties;
396   iface->probe_property = gst_oss4_property_probe_probe_property;
397   iface->needs_probe = gst_oss4_property_probe_needs_probe;
398   iface->get_values = gst_oss4_property_probe_get_values;
399 }
400
401 void
402 gst_oss4_add_property_probe_interface (GType type)
403 {
404   static const GInterfaceInfo probe_iface_info = {
405     (GInterfaceInitFunc) gst_oss4_property_probe_interface_init,
406     NULL,
407     NULL,
408   };
409
410   g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE,
411       &probe_iface_info);
412 }