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., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
21  * with newer GLib versions (>= 2.31.0) */
22 #define GLIB_DISABLE_DEPRECATION_WARNINGS
23 
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27 
28 #include <gst/gst.h>
29 
30 
31 #define NO_LEGACY_MIXER
32 #include "oss4-audio.h"
33 #include "oss4-sink.h"
34 #include "oss4-source.h"
35 #include "oss4-soundcard.h"
36 #include "oss4-property-probe.h"
37 
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <sys/ioctl.h>
41 #include <fcntl.h>
42 #include <unistd.h>
43 #include <errno.h>
44 #include <string.h>
45 
46 #if 0
47 
48 GST_DEBUG_CATEGORY_EXTERN (oss4_debug);
49 #define GST_CAT_DEFAULT oss4_debug
50 
51 static const GList *
52 gst_oss4_property_probe_get_properties (GstPropertyProbe * probe)
53 {
54   GObjectClass *klass = G_OBJECT_GET_CLASS (probe);
55   GList *list;
56 
57   GST_OBJECT_LOCK (GST_OBJECT (probe));
58 
59   /* we create a new list and store it in the instance struct, since apparently
60    * we forgot to update the API for 0.10 (and why don't we mark probable
61    * properties with a flag instead anyway?). A bit hackish, but what can you
62    * do (can't really use a static variable since the pspec will be different
63    * for src and sink class); this isn't particularly pretty, but the best
64    * we can do given that we can't create a common base class (we could do
65    * fancy things with the interface, or use g_object_set_data instead, but
66    * it's not really going to make it much better) */
67   if (GST_IS_AUDIO_SINK_CLASS (klass)) {
68     list = GST_OSS4_SINK (probe)->property_probe_list;
69   } else if (GST_IS_AUDIO_SRC_CLASS (klass)) {
70     list = GST_OSS4_SOURCE (probe)->property_probe_list;
71   } else if (GST_IS_OSS4_MIXER_CLASS (klass)) {
72     list = GST_OSS4_MIXER (probe)->property_probe_list;
73   } else {
74     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
75     g_return_val_if_reached (NULL);
76   }
77 
78   if (list == NULL) {
79     GParamSpec *pspec;
80 
81     pspec = g_object_class_find_property (klass, "device");
82     list = g_list_prepend (NULL, pspec);
83 
84     if (GST_IS_AUDIO_SINK_CLASS (klass)) {
85       GST_OSS4_SINK (probe)->property_probe_list = list;
86     } else if (GST_IS_AUDIO_SRC_CLASS (klass)) {
87       GST_OSS4_SOURCE (probe)->property_probe_list = list;
88     } else if (GST_IS_OSS4_MIXER_CLASS (klass)) {
89       GST_OSS4_MIXER (probe)->property_probe_list = list;
90     }
91   }
92 
93   GST_OBJECT_UNLOCK (GST_OBJECT (probe));
94 
95   return list;
96 }
97 
98 static void
99 gst_oss4_property_probe_probe_property (GstPropertyProbe * probe,
100     guint prop_id, const GParamSpec * pspec)
101 {
102   if (!g_str_equal (pspec->name, "device")) {
103     G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
104   }
105 }
106 
107 static gboolean
108 gst_oss4_property_probe_needs_probe (GstPropertyProbe * probe,
109     guint prop_id, const GParamSpec * pspec)
110 {
111   /* don't cache probed data */
112   return TRUE;
113 }
114 
115 #endif
116 
117 /* caller must ensure LOCK is taken (e.g. if ioctls need to be serialised) */
118 gboolean
gst_oss4_property_probe_find_device_name(GstObject * obj,int fd,const gchar * device_handle,gchar ** device_name)119 gst_oss4_property_probe_find_device_name (GstObject * obj, int fd,
120     const gchar * device_handle, gchar ** device_name)
121 {
122   struct oss_sysinfo si = { {0,}, };
123   gchar *name = NULL;
124 
125   if (ioctl (fd, SNDCTL_SYSINFO, &si) == 0) {
126     int i;
127 
128     for (i = 0; i < si.numaudios; ++i) {
129       struct oss_audioinfo ai = { 0, };
130 
131       ai.dev = i;
132       if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) {
133         GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i);
134         continue;
135       }
136       if (strcmp (ai.devnode, device_handle) == 0) {
137         name = g_strdup (ai.name);
138         break;
139       }
140     }
141   } else {
142     GST_WARNING_OBJECT (obj, "SYSINFO ioctl failed: %s", g_strerror (errno));
143   }
144 
145   /* try ENGINEINFO as fallback (which is better than nothing) */
146   if (name == NULL) {
147     struct oss_audioinfo ai = { 0, };
148 
149     GST_LOG_OBJECT (obj, "device %s not listed in AUDIOINFO", device_handle);
150     ai.dev = -1;
151     if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) == 0)
152       name = g_strdup (ai.name);
153   }
154 
155   GST_DEBUG_OBJECT (obj, "Device name: %s", GST_STR_NULL (name));
156 
157   if (name != NULL)
158     *device_name = name;
159 
160   return (name != NULL);
161 }
162 
163 gboolean
gst_oss4_property_probe_find_device_name_nofd(GstObject * obj,const gchar * device_handle,gchar ** device_name)164 gst_oss4_property_probe_find_device_name_nofd (GstObject * obj,
165     const gchar * device_handle, gchar ** device_name)
166 {
167   gboolean res;
168   int fd;
169 
170   fd = open ("/dev/mixer", O_RDONLY);
171   if (fd < 0)
172     return FALSE;
173 
174   res = gst_oss4_property_probe_find_device_name (obj, fd, device_handle,
175       device_name);
176 
177   close (fd);
178   return res;
179 }
180 
181 static GList *
gst_oss4_property_probe_get_audio_devices(GstObject * obj,int fd,struct oss_sysinfo * si,int cap_mask)182 gst_oss4_property_probe_get_audio_devices (GstObject * obj, int fd,
183     struct oss_sysinfo *si, int cap_mask)
184 {
185   GList *devices = NULL;
186   int i;
187 
188   GST_LOG_OBJECT (obj, "%d audio/dsp devices", si->numaudios);
189 
190   for (i = 0; i < si->numaudios; ++i) {
191     struct oss_audioinfo ai = { 0, };
192 
193     ai.dev = i;
194     if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) {
195       GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i);
196       continue;
197     }
198 
199     if ((ai.caps & cap_mask) == 0) {
200       GST_DEBUG_OBJECT (obj, "audio device %d is not an %s device", i,
201           (cap_mask == PCM_CAP_OUTPUT) ? "output" : "input");
202       continue;
203     }
204 
205     if (!ai.enabled) {
206       GST_DEBUG_OBJECT (obj, "audio device %d is not usable/enabled", i);
207       continue;
208     }
209 
210     GST_DEBUG_OBJECT (obj, "audio device %d looks ok: %s (\"%s\")", i,
211         ai.devnode, ai.name);
212 
213     devices = g_list_prepend (devices, g_strdup (ai.devnode));
214   }
215 
216   return g_list_reverse (devices);
217 }
218 
219 GValueArray *
gst_oss4_property_probe_get_values(GstObject * probe,const gchar * pname)220 gst_oss4_property_probe_get_values (GstObject * probe, const gchar * pname)
221 {
222   struct oss_sysinfo si = { {0,}, };
223   GValueArray *array = NULL;
224   GstObject *obj;
225   GList *devices, *l;
226   int cap_mask, fd = -1;
227 
228   if (!g_str_equal (pname, "device")) {
229     GST_WARNING_OBJECT (probe, "invalid property");
230     return NULL;
231   }
232 
233   obj = GST_OBJECT (probe);
234 
235   GST_OBJECT_LOCK (obj);
236 
237   /* figure out whether the element is a source or sink */
238   if (GST_IS_OSS4_SINK (probe)) {
239     GST_DEBUG_OBJECT (probe, "probing available output devices");
240     cap_mask = PCM_CAP_OUTPUT;
241     fd = GST_OSS4_SINK (probe)->fd;
242   } else if (GST_IS_OSS4_SOURCE (probe)) {
243     GST_DEBUG_OBJECT (probe, "probing available input devices");
244     cap_mask = PCM_CAP_INPUT;
245     fd = GST_OSS4_SOURCE (probe)->fd;
246   } else {
247     GST_OBJECT_UNLOCK (obj);
248     g_assert_not_reached ();
249     return NULL;
250   }
251 
252   /* copy fd if it's open, so we can just unconditionally close() later */
253   if (fd != -1)
254     fd = dup (fd);
255 
256   /* this will also catch the unlikely case where the above dup() failed */
257   if (fd == -1) {
258     fd = open ("/dev/mixer", O_RDONLY | O_NONBLOCK, 0);
259     if (fd < 0)
260       goto open_failed;
261     else if (!gst_oss4_audio_check_version (GST_OBJECT (probe), fd))
262       goto legacy_oss;
263   }
264 
265   if (ioctl (fd, SNDCTL_SYSINFO, &si) == -1)
266     goto no_sysinfo;
267 
268   devices = gst_oss4_property_probe_get_audio_devices (obj, fd, &si, cap_mask);
269 
270   if (devices == NULL) {
271     GST_OBJECT_UNLOCK (obj);
272     GST_DEBUG_OBJECT (obj, "No devices found");
273     goto done;
274   }
275 
276   array = g_value_array_new (1);
277 
278   for (l = devices; l != NULL; l = l->next) {
279     GValue val = { 0, };
280 
281     g_value_init (&val, G_TYPE_STRING);
282     g_value_take_string (&val, (gchar *) l->data);
283     l->data = NULL;
284     g_value_array_append (array, &val);
285     g_value_unset (&val);
286   }
287 
288   GST_OBJECT_UNLOCK (obj);
289 
290   g_list_free (devices);
291 
292 done:
293 
294   close (fd);
295 
296   return array;
297 
298 /* ERRORS */
299 open_failed:
300   {
301     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
302     GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe "
303         "available devices: %s", g_strerror (errno));
304     return NULL;
305   }
306 legacy_oss:
307   {
308     close (fd);
309     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
310     GST_DEBUG_OBJECT (probe, "Legacy OSS (ie. not OSSv4), not supported");
311     return NULL;
312   }
313 no_sysinfo:
314   {
315     close (fd);
316     GST_OBJECT_UNLOCK (GST_OBJECT (probe));
317     GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe "
318         "available devices: %s", g_strerror (errno));
319     return NULL;
320   }
321 }
322 
323 #if 0
324 static void
325 gst_oss4_property_probe_interface_init (GstPropertyProbeInterface * iface)
326 {
327   iface->get_properties = gst_oss4_property_probe_get_properties;
328   iface->probe_property = gst_oss4_property_probe_probe_property;
329   iface->needs_probe = gst_oss4_property_probe_needs_probe;
330   iface->get_values = gst_oss4_property_probe_get_values;
331 }
332 
333 void
334 gst_oss4_add_property_probe_interface (GType type)
335 {
336   static const GInterfaceInfo probe_iface_info = {
337     (GInterfaceInitFunc) gst_oss4_property_probe_interface_init,
338     NULL,
339     NULL,
340   };
341 
342   g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE,
343       &probe_iface_info);
344 }
345 #endif
346