gstreamer/sys/oss4/oss4-property-probe.c

416 lines
12 KiB
C

/* 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-mixer.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>
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;
}
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);
}
static GValueArray *
gst_oss4_property_probe_get_values (GstPropertyProbe * probe,
guint prop_id, const GParamSpec * pspec)
{
struct oss_sysinfo si = { {0,}, };
GValueArray *array = NULL;
GstObject *obj;
GList *devices, *l;
int cap_mask, fd = -1;
if (!g_str_equal (pspec->name, "device")) {
G_OBJECT_WARN_INVALID_PROPERTY_ID (probe, prop_id, pspec);
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 if (GST_IS_OSS4_MIXER (probe)) {
fd = GST_OSS4_MIXER (probe)->fd;
cap_mask = 0;
} 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;
}
}
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);
}