diff options
Diffstat (limited to 'sys/oss4/oss4-source.c')
-rwxr-xr-x | sys/oss4/oss4-source.c | 546 |
1 files changed, 546 insertions, 0 deletions
diff --git a/sys/oss4/oss4-source.c b/sys/oss4/oss4-source.c new file mode 100755 index 0000000..84b315d --- /dev/null +++ b/sys/oss4/oss4-source.c @@ -0,0 +1,546 @@ +/* GStreamer OSS4 audio source + * 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., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-oss4src + * + * This element lets you record sound using the Open Sound System (OSS) + * version 4. + * + * <refsect2> + * <title>Example pipelines</title> + * |[ + * gst-launch-1.0 -v oss4src ! queue ! audioconvert ! vorbisenc ! oggmux ! filesink location=mymusic.ogg + * ]| will record sound from your sound card using OSS4 and encode it to an + * Ogg/Vorbis file (this will only work if your mixer settings are right + * and the right inputs areenabled etc.) + * </refsect2> + */ + +/* FIXME: make sure we're not doing ioctls from the app thread (e.g. via the + * mixer interface) while recording */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#include <gst/gst-i18n-plugin.h> + +#define NO_LEGACY_MIXER +#include "oss4-audio.h" +#include "oss4-source.h" +#include "oss4-property-probe.h" +#include "oss4-soundcard.h" + +#define GST_OSS4_SOURCE_IS_OPEN(src) (GST_OSS4_SOURCE(src)->fd != -1) + +GST_DEBUG_CATEGORY_EXTERN (oss4src_debug); +#define GST_CAT_DEFAULT oss4src_debug + +#define DEFAULT_DEVICE NULL +#define DEFAULT_DEVICE_NAME NULL + +enum +{ + PROP_0, + PROP_DEVICE, + PROP_DEVICE_NAME +}; + +#define gst_oss4_source_parent_class parent_class +G_DEFINE_TYPE (GstOss4Source, gst_oss4_source, GST_TYPE_AUDIO_SRC); + +static void gst_oss4_source_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_oss4_source_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); + +static void gst_oss4_source_dispose (GObject * object); +static void gst_oss4_source_finalize (GstOss4Source * osssrc); + +static GstCaps *gst_oss4_source_getcaps (GstBaseSrc * bsrc, GstCaps * filter); + +static gboolean gst_oss4_source_open (GstAudioSrc * asrc, + gboolean silent_errors); +static gboolean gst_oss4_source_open_func (GstAudioSrc * asrc); +static gboolean gst_oss4_source_close (GstAudioSrc * asrc); +static gboolean gst_oss4_source_prepare (GstAudioSrc * asrc, + GstAudioRingBufferSpec * spec); +static gboolean gst_oss4_source_unprepare (GstAudioSrc * asrc); +static guint gst_oss4_source_read (GstAudioSrc * asrc, gpointer data, + guint length, GstClockTime * timestamp); +static guint gst_oss4_source_delay (GstAudioSrc * asrc); +static void gst_oss4_source_reset (GstAudioSrc * asrc); + +static void +gst_oss4_source_class_init (GstOss4SourceClass * klass) +{ + GObjectClass *gobject_class; + GstElementClass *gstelement_class; + GstBaseSrcClass *gstbasesrc_class; + GstAudioSrcClass *gstaudiosrc_class; + GstPadTemplate *templ; + + gobject_class = (GObjectClass *) klass; + gstelement_class = (GstElementClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstaudiosrc_class = (GstAudioSrcClass *) klass; + + gobject_class->dispose = gst_oss4_source_dispose; + gobject_class->finalize = (GObjectFinalizeFunc) gst_oss4_source_finalize; + gobject_class->get_property = gst_oss4_source_get_property; + gobject_class->set_property = gst_oss4_source_set_property; + + gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_oss4_source_getcaps); + + gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_oss4_source_open_func); + gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_oss4_source_prepare); + gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_oss4_source_unprepare); + gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_oss4_source_close); + gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_oss4_source_read); + gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_oss4_source_delay); + gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_oss4_source_reset); + + g_object_class_install_property (gobject_class, PROP_DEVICE, + g_param_spec_string ("device", "Device", + "OSS4 device (e.g. /dev/oss/hdaudio0/pcm0 or /dev/dspN) " + "(NULL = use first available device)", + DEFAULT_DEVICE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, + g_param_spec_string ("device-name", "Device name", + "Human-readable name of the sound device", DEFAULT_DEVICE_NAME, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_set_static_metadata (gstelement_class, + "OSS v4 Audio Source", "Source/Audio", + "Capture from a sound card via OSS version 4", + "Tim-Philipp Müller <tim centricular net>"); + + templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + gst_oss4_audio_get_template_caps ()); + gst_element_class_add_pad_template (gstelement_class, templ); +} + +static void +gst_oss4_source_init (GstOss4Source * osssrc) +{ + const gchar *device; + + device = g_getenv ("AUDIODEV"); + if (device == NULL) + device = DEFAULT_DEVICE; + + osssrc->fd = -1; + osssrc->device = g_strdup (device); + osssrc->device_name = g_strdup (DEFAULT_DEVICE_NAME); + osssrc->device_name = NULL; +} + +static void +gst_oss4_source_finalize (GstOss4Source * oss) +{ + g_free (oss->device); + oss->device = NULL; + + G_OBJECT_CLASS (parent_class)->finalize ((GObject *) (oss)); +} + +static void +gst_oss4_source_dispose (GObject * object) +{ + GstOss4Source *oss = GST_OSS4_SOURCE (object); + + if (oss->probed_caps) { + gst_caps_unref (oss->probed_caps); + oss->probed_caps = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_oss4_source_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + if (oss->fd == -1) { + g_free (oss->device); + oss->device = g_value_dup_string (value); + g_free (oss->device_name); + oss->device_name = NULL; + } else { + g_warning ("%s: can't change \"device\" property while audio source " + "is open", GST_OBJECT_NAME (oss)); + } + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_oss4_source_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (object); + + switch (prop_id) { + case PROP_DEVICE: + GST_OBJECT_LOCK (oss); + g_value_set_string (value, oss->device); + GST_OBJECT_UNLOCK (oss); + break; + case PROP_DEVICE_NAME: + GST_OBJECT_LOCK (oss); + /* If device is set, try to retrieve the name even if we're not open */ + if (oss->fd == -1 && oss->device != NULL) { + if (gst_oss4_source_open (GST_AUDIO_SRC (oss), TRUE)) { + g_value_set_string (value, oss->device_name); + gst_oss4_source_close (GST_AUDIO_SRC (oss)); + } else { + gchar *name = NULL; + + gst_oss4_property_probe_find_device_name_nofd (GST_OBJECT (oss), + oss->device, &name); + g_value_set_string (value, name); + g_free (name); + } + } else { + g_value_set_string (value, oss->device_name); + } + + GST_OBJECT_UNLOCK (oss); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstCaps * +gst_oss4_source_getcaps (GstBaseSrc * bsrc, GstCaps * filter) +{ + GstOss4Source *oss; + GstCaps *caps; + + oss = GST_OSS4_SOURCE (bsrc); + + if (oss->fd == -1) { + caps = gst_oss4_audio_get_template_caps (); + } else if (oss->probed_caps) { + caps = gst_caps_copy (oss->probed_caps); + } else { + caps = gst_oss4_audio_probe_caps (GST_OBJECT (oss), oss->fd); + if (caps != NULL && !gst_caps_is_empty (caps)) { + oss->probed_caps = gst_caps_copy (caps); + } + } + + if (filter && caps) { + GstCaps *intersection; + + intersection = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + return intersection; + } else { + return caps; + } +} + +/* note: we must not take the object lock here unless we fix up get_property */ +static gboolean +gst_oss4_source_open (GstAudioSrc * asrc, gboolean silent_errors) +{ + GstOss4Source *oss; + gchar *device; + int mode; + + oss = GST_OSS4_SOURCE (asrc); + + if (oss->device) + device = g_strdup (oss->device); + else + device = gst_oss4_audio_find_device (GST_OBJECT_CAST (oss)); + + /* desperate times, desperate measures */ + if (device == NULL) + device = g_strdup ("/dev/dsp0"); + + GST_INFO_OBJECT (oss, "Trying to open OSS4 device '%s'", device); + + /* we open in non-blocking mode even if we don't really want to do writes + * non-blocking because we can't be sure that this is really a genuine + * OSS4 device with well-behaved drivers etc. We really don't want to + * hang forever under any circumstances. */ + oss->fd = open (device, O_RDONLY | O_NONBLOCK, 0); + if (oss->fd == -1) { + switch (errno) { + case EBUSY: + goto busy; + case EACCES: + goto no_permission; + default: + goto open_failed; + } + } + + GST_INFO_OBJECT (oss, "Opened device"); + + /* Make sure it's OSS4. If it's old OSS, let osssink handle it */ + if (!gst_oss4_audio_check_version (GST_OBJECT_CAST (oss), oss->fd)) + goto legacy_oss; + + /* now remove the non-blocking flag. */ + mode = fcntl (oss->fd, F_GETFL); + mode &= ~O_NONBLOCK; + if (fcntl (oss->fd, F_SETFL, mode) < 0) { + /* some drivers do no support unsetting the non-blocking flag, try to + * close/open the device then. This is racy but we error out properly. */ + GST_WARNING_OBJECT (oss, "failed to unset O_NONBLOCK (buggy driver?), " + "will try to re-open device now"); + gst_oss4_source_close (asrc); + if ((oss->fd = open (device, O_RDONLY, 0)) == -1) + goto non_block; + } + + oss->open_device = device; + + /* not using ENGINEINFO here because it sometimes returns a different and + * less useful name than AUDIOINFO for the same device */ + if (!gst_oss4_property_probe_find_device_name (GST_OBJECT (oss), oss->fd, + oss->open_device, &oss->device_name)) { + oss->device_name = NULL; + } + + return TRUE; + + /* ERRORS */ +busy: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, BUSY, + (_("Could not open audio device for playback. " + "Device is being used by another application.")), (NULL)); + } + g_free (device); + return FALSE; + } +no_permission: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback. " + "You don't have permission to open the device.")), + GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +open_failed: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback.")), GST_ERROR_SYSTEM); + } + g_free (device); + return FALSE; + } +legacy_oss: + { + gst_oss4_source_close (asrc); + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, OPEN_READ, + (_("Could not open audio device for playback. " + "This version of the Open Sound System is not supported by this " + "element.")), ("Try the 'osssink' element instead")); + } + g_free (device); + return FALSE; + } +non_block: + { + if (!silent_errors) { + GST_ELEMENT_ERROR (oss, RESOURCE, SETTINGS, (NULL), + ("Unable to set device %s into non-blocking mode: %s", + oss->device, g_strerror (errno))); + } + g_free (device); + return FALSE; + } +} + +static gboolean +gst_oss4_source_open_func (GstAudioSrc * asrc) +{ + return gst_oss4_source_open (asrc, FALSE); +} + +static gboolean +gst_oss4_source_close (GstAudioSrc * asrc) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (asrc); + + if (oss->fd != -1) { + GST_DEBUG_OBJECT (oss, "closing device"); + close (oss->fd); + oss->fd = -1; + } + + oss->bytes_per_sample = 0; + + gst_caps_replace (&oss->probed_caps, NULL); + + g_free (oss->open_device); + oss->open_device = NULL; + + g_free (oss->device_name); + oss->device_name = NULL; + + return TRUE; +} + +static gboolean +gst_oss4_source_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec) +{ + GstOss4Source *oss; + + oss = GST_OSS4_SOURCE (asrc); + + if (!gst_oss4_audio_set_format (GST_OBJECT_CAST (oss), oss->fd, spec)) { + GST_WARNING_OBJECT (oss, "Couldn't set requested format %" GST_PTR_FORMAT, + spec->caps); + return FALSE; + } + + oss->bytes_per_sample = GST_AUDIO_INFO_BPF (&spec->info); + + return TRUE; +} + +static gboolean +gst_oss4_source_unprepare (GstAudioSrc * asrc) +{ + /* could do a SNDCTL_DSP_HALT, but the OSS manual recommends a close/open, + * since HALT won't properly reset some devices, apparently */ + + if (!gst_oss4_source_close (asrc)) + goto couldnt_close; + + if (!gst_oss4_source_open_func (asrc)) + goto couldnt_reopen; + + return TRUE; + + /* ERRORS */ +couldnt_close: + { + GST_DEBUG_OBJECT (asrc, "Couldn't close the audio device"); + return FALSE; + } +couldnt_reopen: + { + GST_DEBUG_OBJECT (asrc, "Couldn't reopen the audio device"); + return FALSE; + } +} + +static guint +gst_oss4_source_read (GstAudioSrc * asrc, gpointer data, guint length, + GstClockTime * timestamp) +{ + GstOss4Source *oss; + int n; + + oss = GST_OSS4_SOURCE_CAST (asrc); + + n = read (oss->fd, data, length); + GST_LOG_OBJECT (asrc, "%u bytes, %u samples", n, n / oss->bytes_per_sample); + + if (G_UNLIKELY (n < 0)) { + switch (errno) { + case ENOTSUP: + case EACCES:{ + /* This is the most likely cause, I think */ + GST_ELEMENT_ERROR (asrc, RESOURCE, READ, + (_("Recording is not supported by this audio device.")), + ("read: %s (device: %s) (maybe this is an output-only device?)", + g_strerror (errno), oss->open_device)); + break; + } + default:{ + GST_ELEMENT_ERROR (asrc, RESOURCE, READ, + (_("Error recording from audio device.")), + ("read: %s (device: %s)", g_strerror (errno), oss->open_device)); + break; + } + } + } + + return (guint) n; +} + +static guint +gst_oss4_source_delay (GstAudioSrc * asrc) +{ + audio_buf_info info = { 0, }; + GstOss4Source *oss; + guint delay; + + oss = GST_OSS4_SOURCE_CAST (asrc); + + if (ioctl (oss->fd, SNDCTL_DSP_GETISPACE, &info) == -1) { + GST_LOG_OBJECT (oss, "GETISPACE failed: %s", g_strerror (errno)); + return 0; + } + + delay = (info.fragstotal * info.fragsize) - info.bytes; + GST_LOG_OBJECT (oss, "fragstotal:%d, fragsize:%d, bytes:%d, delay:%d", + info.fragstotal, info.fragsize, info.bytes, delay); + return delay; +} + +static void +gst_oss4_source_reset (GstAudioSrc * asrc) +{ + /* There's nothing we can do here really: OSS can't handle access to the + * same device/fd from multiple threads and might deadlock or blow up in + * other ways if we try an ioctl SNDCTL_DSP_HALT or similar */ +} |