2008-04-02 20:18:58 +00:00
|
|
|
/* 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.
|
|
|
|
*/
|
|
|
|
|
2012-02-27 13:47:25 +00:00
|
|
|
/* FIXME 0.11: suppress warnings for deprecated API such as GValueArray
|
|
|
|
* with newer GLib versions (>= 2.31.0) */
|
|
|
|
#define GLIB_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
|
2008-04-02 20:18:58 +00:00
|
|
|
#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);
|
|
|
|
}
|
|
|
|
|
2008-05-22 15:14:26 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2008-04-02 20:18:58 +00:00
|
|
|
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);
|
|
|
|
}
|