/* GStreamer OSS4 audio property probe interface implementation * Copyright (C) 2007-2008 Tim-Philipp Müller <tim centricular net> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* FIXME 0.11: suppress warnings for deprecated API such as GValueArray * with newer GLib versions (>= 2.31.0) */ #define GLIB_DISABLE_DEPRECATION_WARNINGS #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <gst/gst.h> #define NO_LEGACY_MIXER #include "oss4-audio.h" #include "oss4-sink.h" #include "oss4-source.h" #include "oss4-soundcard.h" #include "oss4-property-probe.h" #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #if 0 GST_DEBUG_CATEGORY_EXTERN (oss4_debug); #define GST_CAT_DEFAULT oss4_debug static const GList * gst_oss4_property_probe_get_properties (GstPropertyProbe * probe) { GObjectClass *klass = G_OBJECT_GET_CLASS (probe); GList *list; GST_OBJECT_LOCK (GST_OBJECT (probe)); /* we create a new list and store it in the instance struct, since apparently * we forgot to update the API for 0.10 (and why don't we mark probable * properties with a flag instead anyway?). A bit hackish, but what can you * do (can't really use a static variable since the pspec will be different * for src and sink class); this isn't particularly pretty, but the best * we can do given that we can't create a common base class (we could do * fancy things with the interface, or use g_object_set_data instead, but * it's not really going to make it much better) */ if (GST_IS_AUDIO_SINK_CLASS (klass)) { list = GST_OSS4_SINK (probe)->property_probe_list; } else if (GST_IS_AUDIO_SRC_CLASS (klass)) { list = GST_OSS4_SOURCE (probe)->property_probe_list; } else if (GST_IS_OSS4_MIXER_CLASS (klass)) { list = GST_OSS4_MIXER (probe)->property_probe_list; } else { GST_OBJECT_UNLOCK (GST_OBJECT (probe)); g_return_val_if_reached (NULL); } if (list == NULL) { GParamSpec *pspec; pspec = g_object_class_find_property (klass, "device"); list = g_list_prepend (NULL, pspec); if (GST_IS_AUDIO_SINK_CLASS (klass)) { GST_OSS4_SINK (probe)->property_probe_list = list; } else if (GST_IS_AUDIO_SRC_CLASS (klass)) { GST_OSS4_SOURCE (probe)->property_probe_list = list; } else if (GST_IS_OSS4_MIXER_CLASS (klass)) { GST_OSS4_MIXER (probe)->property_probe_list = list; } } GST_OBJECT_UNLOCK (GST_OBJECT (probe)); return list; } static void gst_oss4_property_probe_probe_property (GstPropertyProbe * probe, guint prop_id, const GParamSpec * pspec) { if (!g_str_equal (pspec->name, "device")) { G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec); } } static gboolean gst_oss4_property_probe_needs_probe (GstPropertyProbe * probe, guint prop_id, const GParamSpec * pspec) { /* don't cache probed data */ return TRUE; } #endif static gint oss4_mixerinfo_priority_cmp (struct oss_mixerinfo *mi1, struct oss_mixerinfo *mi2) { /* return negative vaue if mi1 comes before mi2 */ if (mi1->priority != mi2->priority) return mi2->priority - mi1->priority; return strcmp (mi1->devnode, mi2->devnode); } /* caller must ensure LOCK is taken (e.g. if ioctls need to be serialised) */ gboolean gst_oss4_property_probe_find_device_name (GstObject * obj, int fd, const gchar * device_handle, gchar ** device_name) { struct oss_sysinfo si = { {0,}, }; gchar *name = NULL; if (ioctl (fd, SNDCTL_SYSINFO, &si) == 0) { int i; for (i = 0; i < si.numaudios; ++i) { struct oss_audioinfo ai = { 0, }; ai.dev = i; if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) { GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i); continue; } if (strcmp (ai.devnode, device_handle) == 0) { name = g_strdup (ai.name); break; } } } else { GST_WARNING_OBJECT (obj, "SYSINFO ioctl failed: %s", g_strerror (errno)); } /* try ENGINEINFO as fallback (which is better than nothing) */ if (name == NULL) { struct oss_audioinfo ai = { 0, }; GST_LOG_OBJECT (obj, "device %s not listed in AUDIOINFO", device_handle); ai.dev = -1; if (ioctl (fd, SNDCTL_ENGINEINFO, &ai) == 0) name = g_strdup (ai.name); } GST_DEBUG_OBJECT (obj, "Device name: %s", GST_STR_NULL (name)); if (name != NULL) *device_name = name; return (name != NULL); } gboolean gst_oss4_property_probe_find_device_name_nofd (GstObject * obj, const gchar * device_handle, gchar ** device_name) { gboolean res; int fd; fd = open ("/dev/mixer", O_RDONLY); if (fd < 0) return FALSE; res = gst_oss4_property_probe_find_device_name (obj, fd, device_handle, device_name); close (fd); return res; } static GList * gst_oss4_property_probe_get_mixer_devices (GstObject * obj, int fd, struct oss_sysinfo *si) { GList *m, *mixers = NULL; GList *devices = NULL; int i; GST_LOG_OBJECT (obj, "%d mixer devices", si->nummixers); /* first, find suitable mixer devices and sort by priority */ for (i = 0; i < si->nummixers; ++i) { struct oss_mixerinfo mi = { 0, }; mi.dev = i; if (ioctl (fd, SNDCTL_MIXERINFO, &mi) == -1) { GST_DEBUG_OBJECT (obj, "MIXERINFO ioctl for device %d failed", i); continue; } GST_INFO_OBJECT (obj, "mixer device %d:", i); GST_INFO_OBJECT (obj, " enabled : %s", (mi.enabled) ? "yes" : "no (powered off or unplugged)"); GST_INFO_OBJECT (obj, " priority : %d", mi.priority); GST_INFO_OBJECT (obj, " devnode : %s", mi.devnode); GST_INFO_OBJECT (obj, " handle : %s", mi.handle); GST_INFO_OBJECT (obj, " caps : 0x%02x", mi.caps); GST_INFO_OBJECT (obj, " name : %s", mi.name); if (!mi.enabled) { GST_DEBUG_OBJECT (obj, "mixer device is not usable/enabled, skipping"); continue; } /* only want mixers that control hardware directly */ if ((mi.caps & MIXER_CAP_VIRTUAL)) { GST_DEBUG_OBJECT (obj, "mixer device is a virtual device, skipping"); continue; } mixers = g_list_insert_sorted (mixers, g_memdup (&mi, sizeof (mi)), (GCompareFunc) oss4_mixerinfo_priority_cmp); } /* then create device list according to priority */ for (m = mixers; m != NULL; m = m->next) { struct oss_mixerinfo *mi = (struct oss_mixerinfo *) m->data; GST_LOG_OBJECT (obj, "mixer device: '%s'", mi->devnode); devices = g_list_prepend (devices, g_strdup (mi->devnode)); g_free (m->data); } g_list_free (mixers); mixers = NULL; return g_list_reverse (devices); } static GList * gst_oss4_property_probe_get_audio_devices (GstObject * obj, int fd, struct oss_sysinfo *si, int cap_mask) { GList *devices = NULL; int i; GST_LOG_OBJECT (obj, "%d audio/dsp devices", si->numaudios); for (i = 0; i < si->numaudios; ++i) { struct oss_audioinfo ai = { 0, }; ai.dev = i; if (ioctl (fd, SNDCTL_AUDIOINFO, &ai) == -1) { GST_DEBUG_OBJECT (obj, "AUDIOINFO ioctl for device %d failed", i); continue; } if ((ai.caps & cap_mask) == 0) { GST_DEBUG_OBJECT (obj, "audio device %d is not an %s device", i, (cap_mask == PCM_CAP_OUTPUT) ? "output" : "input"); continue; } if (!ai.enabled) { GST_DEBUG_OBJECT (obj, "audio device %d is not usable/enabled", i); continue; } GST_DEBUG_OBJECT (obj, "audio device %d looks ok: %s (\"%s\")", i, ai.devnode, ai.name); devices = g_list_prepend (devices, g_strdup (ai.devnode)); } return g_list_reverse (devices); } GValueArray * gst_oss4_property_probe_get_values (GstObject * probe, const gchar * pname) { struct oss_sysinfo si = { {0,}, }; GValueArray *array = NULL; GstObject *obj; GList *devices, *l; int cap_mask, fd = -1; if (!g_str_equal (pname, "device")) { GST_WARNING_OBJECT (probe, "invalid property"); return NULL; } obj = GST_OBJECT (probe); GST_OBJECT_LOCK (obj); /* figure out whether the element is a source or sink */ if (GST_IS_OSS4_SINK (probe)) { GST_DEBUG_OBJECT (probe, "probing available output devices"); cap_mask = PCM_CAP_OUTPUT; fd = GST_OSS4_SINK (probe)->fd; } else if (GST_IS_OSS4_SOURCE (probe)) { GST_DEBUG_OBJECT (probe, "probing available input devices"); cap_mask = PCM_CAP_INPUT; fd = GST_OSS4_SOURCE (probe)->fd; } else { GST_OBJECT_UNLOCK (obj); g_return_val_if_reached (NULL); } /* copy fd if it's open, so we can just unconditionally close() later */ if (fd != -1) fd = dup (fd); /* this will also catch the unlikely case where the above dup() failed */ if (fd == -1) { fd = open ("/dev/mixer", O_RDONLY | O_NONBLOCK, 0); if (fd < 0) goto open_failed; else if (!gst_oss4_audio_check_version (GST_OBJECT (probe), fd)) goto legacy_oss; } if (ioctl (fd, SNDCTL_SYSINFO, &si) == -1) goto no_sysinfo; if (cap_mask != 0) { devices = gst_oss4_property_probe_get_audio_devices (obj, fd, &si, cap_mask); } else { devices = gst_oss4_property_probe_get_mixer_devices (obj, fd, &si); } if (devices == NULL) { GST_DEBUG_OBJECT (obj, "No devices found"); goto done; } array = g_value_array_new (1); for (l = devices; l != NULL; l = l->next) { GValue val = { 0, }; g_value_init (&val, G_TYPE_STRING); g_value_take_string (&val, (gchar *) l->data); l->data = NULL; g_value_array_append (array, &val); g_value_unset (&val); } GST_OBJECT_UNLOCK (obj); g_list_free (devices); done: close (fd); return array; /* ERRORS */ open_failed: { GST_OBJECT_UNLOCK (GST_OBJECT (probe)); GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe " "available devices: %s", g_strerror (errno)); return NULL; } legacy_oss: { close (fd); GST_OBJECT_UNLOCK (GST_OBJECT (probe)); GST_DEBUG_OBJECT (probe, "Legacy OSS (ie. not OSSv4), not supported"); return NULL; } no_sysinfo: { close (fd); GST_OBJECT_UNLOCK (GST_OBJECT (probe)); GST_WARNING_OBJECT (probe, "Can't open file descriptor to probe " "available devices: %s", g_strerror (errno)); return NULL; } } #if 0 static void gst_oss4_property_probe_interface_init (GstPropertyProbeInterface * iface) { iface->get_properties = gst_oss4_property_probe_get_properties; iface->probe_property = gst_oss4_property_probe_probe_property; iface->needs_probe = gst_oss4_property_probe_needs_probe; iface->get_values = gst_oss4_property_probe_get_values; } void gst_oss4_add_property_probe_interface (GType type) { static const GInterfaceInfo probe_iface_info = { (GInterfaceInitFunc) gst_oss4_property_probe_interface_init, NULL, NULL, }; g_type_add_interface_static (type, GST_TYPE_PROPERTY_PROBE, &probe_iface_info); } #endif