/* * GStreamer * * Copyright (C) 2005 Thomas Vander Stichele <thomas@apestaart.org> * Copyright (C) 2005 Ronald S. Bultje <rbultje@ronald.bitfreak.net> * Copyright (C) 2008 Victor Lin <bornstub@gmail.com> * Copyright (C) 2013 Juan Manuel Borges Caño <juanmabcmail@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. * * Alternatively, the contents of this file may be used under the * GNU Lesser General Public License Version 2.1 (the "LGPL"), in * which case the following provisions apply instead of the ones * mentioned above: * * Copyright (C) 2013 Juan Manuel Borges Caño <juanmabcmail@gmail.com> * * 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-openalsrc * @title: openalsrc * @see_also: openalsink * @short_description: capture raw audio samples through OpenAL * * This element captures raw audio samples through OpenAL. * * ## Example pipelines * |[ * gst-launch-1.0 -v openalsrc ! audioconvert ! wavenc ! filesink location=stream.wav * ]| * will capture sound through OpenAL and encode it to a wav file. * |[ * gst-launch-1.0 openalsrc ! "audio/x-raw,format=S16LE,rate=44100" ! audioconvert ! volume volume=0.25 ! openalsink * ]| will capture and play audio through OpenAL. * */ /* * DEV: * To get better timing/delay information you may also be interested in this: * http://kcat.strangesoft.net/openal-extensions/SOFT_source_latency.txt */ #ifdef HAVE_CONFIG_H #include <config.h> #endif #include <gst/gst.h> #include <gst/gsterror.h> GST_DEBUG_CATEGORY_EXTERN (openal_debug); #define GST_CAT_DEFAULT openal_debug #include "gstopenalsrc.h" static void gst_openal_src_dispose (GObject * object); static void gst_openal_src_finalize (GObject * object); static void gst_openal_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_openal_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstCaps *gst_openal_src_getcaps (GstBaseSrc * basesrc, GstCaps * filter); static gboolean gst_openal_src_open (GstAudioSrc * audiosrc); static gboolean gst_openal_src_prepare (GstAudioSrc * audiosrc, GstAudioRingBufferSpec * spec); static gboolean gst_openal_src_unprepare (GstAudioSrc * audiosrc); static gboolean gst_openal_src_close (GstAudioSrc * audiosrc); static guint gst_openal_src_read (GstAudioSrc * audiosrc, gpointer data, guint length, GstClockTime * timestamp); static guint gst_openal_src_delay (GstAudioSrc * audiosrc); static void gst_openal_src_reset (GstAudioSrc * audiosrc); #define OPENAL_DEFAULT_DEVICE_NAME NULL #define OPENAL_DEFAULT_DEVICE NULL #define OPENAL_MIN_RATE 8000 #define OPENAL_MAX_RATE 192000 enum { PROP_0, PROP_DEVICE, PROP_DEVICE_NAME }; static GstStaticPadTemplate openalsrc_factory = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS ( /* These caps do not work on my card */ // "audio/x-adpcm, " "layout = (string) ima, " // "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; " // "audio/x-alaw, " "rate = (int) [ 1, MAX ], " // "channels = (int) 1; " // "audio/x-mulaw, " "rate = (int) [ 1, MAX ], " // "channels = (int) 1; " // "audio/x-raw, " "format = (string) " GST_AUDIO_NE (F64) ", " // "rate = (int) [ 1, MAX ], " "channels = (int) 1; " // "audio/x-raw, " "format = (string) " GST_AUDIO_NE (F32) ", " // "rate = (int) [ 1, MAX ], " "channels = (int) 1; " "audio/x-raw, " "format = (string) " GST_AUDIO_NE (S16) ", " "rate = (int) [ 1, MAX ], " "channels = (int) 1; " /* These caps work wrongly on my card */ // "audio/x-raw, " "format = (string) " GST_AUDIO_NE (U16) ", " // "rate = (int) [ 1, MAX ], " "channels = (int) 1; " // "audio/x-raw, " "format = (string) " G_STRINGIFY (S8) ", " // "rate = (int) [ 1, MAX ], " "channels = (int) 1")); "audio/x-raw, " "format = (string) " G_STRINGIFY (U8) ", " "rate = (int) [ 1, MAX ], " "channels = (int) 1") ); G_DEFINE_TYPE (GstOpenalSrc, gst_openal_src, GST_TYPE_AUDIO_SRC); static void gst_openal_src_dispose (GObject * object) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (object); if (openalsrc->probed_caps) gst_caps_unref (openalsrc->probed_caps); openalsrc->probed_caps = NULL; G_OBJECT_CLASS (gst_openal_src_parent_class)->dispose (object); } static void gst_openal_src_class_init (GstOpenalSrcClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *gstelement_class = (GstElementClass *) klass; GstBaseSrcClass *gstbasesrc_class = (GstBaseSrcClass *) klass; GstAudioSrcClass *gstaudiosrc_class = (GstAudioSrcClass *) (klass); gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_openal_src_dispose); gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_openal_src_finalize); gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_openal_src_set_property); gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_openal_src_get_property); gst_openal_src_parent_class = g_type_class_peek_parent (klass); gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_openal_src_getcaps); gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_openal_src_open); gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_openal_src_prepare); gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_openal_src_unprepare); gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_openal_src_close); gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_openal_src_read); gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_openal_src_delay); gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_openal_src_reset); g_object_class_install_property (gobject_class, PROP_DEVICE, g_param_spec_string ("device", "ALCdevice", "User device, default device if NULL", OPENAL_DEFAULT_DEVICE, G_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_DEVICE_NAME, g_param_spec_string ("device-name", "Device name", "Human-readable name of the device", OPENAL_DEFAULT_DEVICE_NAME, G_PARAM_READABLE)); gst_element_class_set_static_metadata (gstelement_class, "OpenAL Audio Source", "Source/Audio", "Input audio through OpenAL", "Juan Manuel Borges Caño <juanmabcmail@gmail.com>"); gst_element_class_add_static_pad_template (gstelement_class, &openalsrc_factory); } static void gst_openal_src_init (GstOpenalSrc * openalsrc) { GST_DEBUG_OBJECT (openalsrc, "initializing"); openalsrc->default_device_name = g_strdup (OPENAL_DEFAULT_DEVICE_NAME); openalsrc->default_device = OPENAL_DEFAULT_DEVICE; openalsrc->device = NULL; openalsrc->buffer_length = 0; openalsrc->probed_caps = NULL; } static void gst_openal_src_finalize (GObject * object) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (object); g_free (openalsrc->default_device_name); g_free (openalsrc->default_device); G_OBJECT_CLASS (gst_openal_src_parent_class)->finalize (object); } static void gst_openal_src_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (object); switch (prop_id) { case PROP_DEVICE: openalsrc->default_device = g_value_dup_string (value); break; case PROP_DEVICE_NAME: openalsrc->default_device_name = g_value_dup_string (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_openal_src_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (object); switch (prop_id) { case PROP_DEVICE: g_value_set_string (value, openalsrc->default_device); break; case PROP_DEVICE_NAME: g_value_set_string (value, openalsrc->default_device_name); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstCaps * gst_openal_helper_probe_caps (ALCcontext * context) { GstStructure *structure; GstCaps *caps; // ALCcontext *old; // old = pushContext(context); caps = gst_caps_new_empty (); if (alIsExtensionPresent ("AL_EXT_DOUBLE")) { structure = gst_structure_new ("audio/x-raw", "format", G_TYPE_STRING, GST_AUDIO_NE (F64), "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); } if (alIsExtensionPresent ("AL_EXT_FLOAT32")) { structure = gst_structure_new ("audio/x-raw", "format", G_TYPE_STRING, GST_AUDIO_NE (F32), "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); } structure = gst_structure_new ("audio/x-raw", "format", G_TYPE_STRING, GST_AUDIO_NE (S16), "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); structure = gst_structure_new ("audio/x-raw", "format", G_TYPE_STRING, G_STRINGIFY (U8), "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); if (alIsExtensionPresent ("AL_EXT_IMA4")) { structure = gst_structure_new ("audio/x-adpcm", "layout", G_TYPE_STRING, "ima", "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); } if (alIsExtensionPresent ("AL_EXT_ALAW")) { structure = gst_structure_new ("audio/x-alaw", "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); } if (alIsExtensionPresent ("AL_EXT_MULAW")) { structure = gst_structure_new ("audio/x-mulaw", "rate", GST_TYPE_INT_RANGE, OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT, 1, NULL); gst_caps_append_structure (caps, structure); } // popContext(old, context); return caps; } static GstCaps * gst_openal_src_getcaps (GstBaseSrc * basesrc, GstCaps * filter) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (basesrc); GstCaps *caps; ALCdevice *device; device = alcOpenDevice (NULL); if (device == NULL) { GstPad *pad = GST_BASE_SRC_PAD (basesrc); GstCaps *tcaps = gst_pad_get_pad_template_caps (pad); GST_ELEMENT_WARNING (openalsrc, RESOURCE, OPEN_WRITE, ("Could not open temporary device."), GST_ALC_ERROR (device)); caps = gst_caps_copy (tcaps); gst_caps_unref (tcaps); } else if (openalsrc->probed_caps) caps = gst_caps_copy (openalsrc->probed_caps); else { ALCcontext *context = alcCreateContext (device, NULL); if (context) { caps = gst_openal_helper_probe_caps (context); alcDestroyContext (context); } else { GST_ELEMENT_WARNING (openalsrc, RESOURCE, FAILED, ("Could not create temporary context."), GST_ALC_ERROR (device)); caps = NULL; } if (caps && !gst_caps_is_empty (caps)) openalsrc->probed_caps = gst_caps_copy (caps); } if (device != NULL) { if (alcCloseDevice (device) == ALC_FALSE) { GST_ELEMENT_WARNING (openalsrc, RESOURCE, CLOSE, ("Could not close temporary device."), GST_ALC_ERROR (device)); } } if (filter) { GstCaps *intersection; intersection = gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); return intersection; } else { return caps; } } static gboolean gst_openal_src_open (GstAudioSrc * audiosrc) { return TRUE; } static void gst_openal_src_parse_spec (GstOpenalSrc * openalsrc, const GstAudioRingBufferSpec * spec) { ALuint format = AL_NONE; GST_DEBUG_OBJECT (openalsrc, "looking up format for type %d, gst-format %d, and %d channels", spec->type, GST_AUDIO_INFO_FORMAT (&spec->info), GST_AUDIO_INFO_CHANNELS (&spec->info)); switch (spec->type) { case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW: switch (GST_AUDIO_INFO_FORMAT (&spec->info)) { case GST_AUDIO_FORMAT_U8: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO8; break; default: break; } break; case GST_AUDIO_FORMAT_U16: case GST_AUDIO_FORMAT_S16: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO16; break; default: break; } break; case GST_AUDIO_FORMAT_F32: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO_FLOAT32; break; default: break; } break; case GST_AUDIO_FORMAT_F64: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO_DOUBLE_EXT; break; default: break; } break; default: break; } break; case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_IMA_ADPCM: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO_IMA4; break; default: break; } break; case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO_ALAW_EXT; break; default: break; } break; case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MU_LAW: switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) { case 1: format = AL_FORMAT_MONO_MULAW; break; default: break; } break; default: break; } openalsrc->bytes_per_sample = GST_AUDIO_INFO_BPS (&spec->info); openalsrc->rate = GST_AUDIO_INFO_RATE (&spec->info); openalsrc->buffer_length = spec->segsize; openalsrc->format = format; } static gboolean gst_openal_src_prepare (GstAudioSrc * audiosrc, GstAudioRingBufferSpec * spec) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (audiosrc); gst_openal_src_parse_spec (openalsrc, spec); if (openalsrc->format == AL_NONE) { GST_ELEMENT_ERROR (openalsrc, RESOURCE, SETTINGS, (NULL), ("Unable to get type %d, format %d, and %d channels", spec->type, GST_AUDIO_INFO_FORMAT (&spec->info), GST_AUDIO_INFO_CHANNELS (&spec->info))); return FALSE; } openalsrc->device = alcCaptureOpenDevice (openalsrc->default_device, openalsrc->rate, openalsrc->format, openalsrc->buffer_length); if (!openalsrc->device) { GST_ELEMENT_ERROR (openalsrc, RESOURCE, OPEN_READ, ("Could not open device."), GST_ALC_ERROR (openalsrc->device)); return FALSE; } openalsrc->default_device_name = g_strdup (alcGetString (openalsrc->device, ALC_DEVICE_SPECIFIER)); alcCaptureStart (openalsrc->device); return TRUE; } static gboolean gst_openal_src_unprepare (GstAudioSrc * audiosrc) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (audiosrc); if (openalsrc->device) { alcCaptureStop (openalsrc->device); if (alcCaptureCloseDevice (openalsrc->device) == ALC_FALSE) { GST_ELEMENT_ERROR (openalsrc, RESOURCE, CLOSE, ("Could not close device."), GST_ALC_ERROR (openalsrc->device)); return FALSE; } } return TRUE; } static gboolean gst_openal_src_close (GstAudioSrc * audiosrc) { return TRUE; } static guint gst_openal_src_read (GstAudioSrc * audiosrc, gpointer data, guint length, GstClockTime * timestamp) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (audiosrc); gint samples; alcGetIntegerv (openalsrc->device, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); if (samples * openalsrc->bytes_per_sample > length) { samples = length / openalsrc->bytes_per_sample; } if (samples) { GST_DEBUG_OBJECT (openalsrc, "read samples : %d", samples); alcCaptureSamples (openalsrc->device, data, samples); } return samples * openalsrc->bytes_per_sample; } static guint gst_openal_src_delay (GstAudioSrc * audiosrc) { GstOpenalSrc *openalsrc = GST_OPENAL_SRC (audiosrc); ALint samples; alcGetIntegerv (openalsrc->device, ALC_CAPTURE_SAMPLES, sizeof (samples), &samples); if (G_UNLIKELY (samples < 0)) { /* make sure we never return a negative delay */ GST_WARNING_OBJECT (openal_debug, "negative delay"); samples = 0; } return samples; } static void gst_openal_src_reset (GstAudioSrc * audiosrc) { }