mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-27 02:30:35 +00:00
1096 lines
33 KiB
C
1096 lines
33 KiB
C
/*
|
|
* GStreamer
|
|
*
|
|
* Copyright (C) 2005 Wim Taymans <wim@fluendo.com>
|
|
* Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
|
|
* Copyright (C) 2009-2010 Chris Robinson <chris.kcat@gmail.com>
|
|
* 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-openalsink
|
|
* @title: openalsink
|
|
* @see_also: openalsrc
|
|
* @short_description: capture raw audio samples through OpenAL
|
|
*
|
|
* This element plays raw audio samples through OpenAL.
|
|
*
|
|
* Unfortunately the capture API doesn't have a format enumeration/check. all you can do is try opening it and see if it works.
|
|
*
|
|
* ## Example pipelines
|
|
* |[
|
|
* gst-launch-1.0 audiotestsrc ! audioconvert ! volume volume=0.5 ! openalsink
|
|
* ]| will play a sine wave (continuous beep sound) through OpenAL.
|
|
* |[
|
|
* gst-launch-1.0 filesrc location=stream.wav ! decodebin ! audioconvert ! openalsink
|
|
* ]| will play a wav audio file through OpenAL.
|
|
* |[
|
|
* 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 "gstopenalsink.h"
|
|
|
|
static void gst_openal_sink_dispose (GObject * object);
|
|
static void gst_openal_sink_finalize (GObject * object);
|
|
|
|
static void gst_openal_sink_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
static void gst_openal_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static GstCaps *gst_openal_sink_getcaps (GstBaseSink * basesink,
|
|
GstCaps * filter);
|
|
static gboolean gst_openal_sink_open (GstAudioSink * audiosink);
|
|
static gboolean gst_openal_sink_close (GstAudioSink * audiosink);
|
|
static gboolean gst_openal_sink_prepare (GstAudioSink * audiosink,
|
|
GstAudioRingBufferSpec * spec);
|
|
static gboolean gst_openal_sink_unprepare (GstAudioSink * audiosink);
|
|
static gint gst_openal_sink_write (GstAudioSink * audiosink, gpointer data,
|
|
guint length);
|
|
static guint gst_openal_sink_delay (GstAudioSink * audiosink);
|
|
static void gst_openal_sink_reset (GstAudioSink * audiosink);
|
|
|
|
#define OPENAL_DEFAULT_DEVICE NULL
|
|
|
|
#define OPENAL_MIN_RATE 8000
|
|
#define OPENAL_MAX_RATE 192000
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_DEVICE,
|
|
PROP_DEVICE_NAME,
|
|
|
|
PROP_USER_DEVICE,
|
|
PROP_USER_CONTEXT,
|
|
PROP_USER_SOURCE
|
|
};
|
|
|
|
static GstStaticPadTemplate openalsink_factory =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("audio/x-raw, " "format = (string) " GST_AUDIO_NE (F64)
|
|
", " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, 2 ]; "
|
|
"audio/x-raw, " "format = (string) " GST_AUDIO_NE (F32) ", "
|
|
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; "
|
|
"audio/x-raw, " "format = (string) " GST_AUDIO_NE (S16) ", "
|
|
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; "
|
|
"audio/x-raw, " "format = (string) " G_STRINGIFY (U8) ", "
|
|
"rate = (int) [ 1, MAX ], " "channels = (int) [ 1, MAX ]; "
|
|
/* 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, 2 ]; "
|
|
// "audio/x-mulaw, " "rate = (int) [ 1, MAX ], "
|
|
// "channels = (int) [ 1, MAX ]"
|
|
)
|
|
);
|
|
|
|
static PFNALCSETTHREADCONTEXTPROC palcSetThreadContext;
|
|
static PFNALCGETTHREADCONTEXTPROC palcGetThreadContext;
|
|
|
|
static inline ALCcontext *
|
|
pushContext (ALCcontext * context)
|
|
{
|
|
ALCcontext *old;
|
|
if (!palcGetThreadContext || !palcSetThreadContext)
|
|
return NULL;
|
|
|
|
old = palcGetThreadContext ();
|
|
if (old != context)
|
|
palcSetThreadContext (context);
|
|
return old;
|
|
}
|
|
|
|
static inline void
|
|
popContext (ALCcontext * old, ALCcontext * context)
|
|
{
|
|
if (!palcGetThreadContext || !palcSetThreadContext)
|
|
return;
|
|
|
|
if (old != context)
|
|
palcSetThreadContext (old);
|
|
}
|
|
|
|
static inline ALenum
|
|
checkALError (const char *fname, unsigned int fline)
|
|
{
|
|
ALenum err = alGetError ();
|
|
if (err != AL_NO_ERROR)
|
|
g_warning ("%s:%u: context error: %s", fname, fline, alGetString (err));
|
|
return err;
|
|
}
|
|
|
|
#define checkALError() checkALError(__FILE__, __LINE__)
|
|
|
|
G_DEFINE_TYPE (GstOpenALSink, gst_openal_sink, GST_TYPE_AUDIO_SINK);
|
|
|
|
static void
|
|
gst_openal_sink_dispose (GObject * object)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (object);
|
|
|
|
if (sink->probed_caps)
|
|
gst_caps_unref (sink->probed_caps);
|
|
sink->probed_caps = NULL;
|
|
|
|
G_OBJECT_CLASS (gst_openal_sink_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_class_init (GstOpenALSinkClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstElementClass *gstelement_class = (GstElementClass *) klass;
|
|
GstBaseSinkClass *gstbasesink_class = (GstBaseSinkClass *) klass;
|
|
GstAudioSinkClass *gstaudiosink_class = (GstAudioSinkClass *) klass;
|
|
|
|
if (alcIsExtensionPresent (NULL, "ALC_EXT_thread_local_context")) {
|
|
palcSetThreadContext = alcGetProcAddress (NULL, "alcSetThreadContext");
|
|
palcGetThreadContext = alcGetProcAddress (NULL, "alcGetThreadContext");
|
|
}
|
|
|
|
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_openal_sink_dispose);
|
|
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_openal_sink_finalize);
|
|
gobject_class->set_property =
|
|
GST_DEBUG_FUNCPTR (gst_openal_sink_set_property);
|
|
gobject_class->get_property =
|
|
GST_DEBUG_FUNCPTR (gst_openal_sink_get_property);
|
|
|
|
gst_openal_sink_parent_class = g_type_class_peek_parent (klass);
|
|
|
|
gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_openal_sink_getcaps);
|
|
|
|
gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_openal_sink_open);
|
|
gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_openal_sink_close);
|
|
gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_openal_sink_prepare);
|
|
gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_openal_sink_unprepare);
|
|
gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_openal_sink_write);
|
|
gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_openal_sink_delay);
|
|
gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_openal_sink_reset);
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE_NAME,
|
|
g_param_spec_string ("device-name", "Device name",
|
|
"Human-readable name of the opened device", "", G_PARAM_READABLE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DEVICE,
|
|
g_param_spec_string ("device", "Device",
|
|
"Human-readable name of the device", OPENAL_DEFAULT_DEVICE,
|
|
G_PARAM_READWRITE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_USER_DEVICE,
|
|
g_param_spec_pointer ("user-device", "ALCdevice", "User device",
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_USER_CONTEXT,
|
|
g_param_spec_pointer ("user-context", "ALCcontext", "User context",
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_USER_SOURCE,
|
|
g_param_spec_uint ("user-source", "ALsource", "User source", 0, UINT_MAX,
|
|
0, G_PARAM_READWRITE));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "OpenAL Audio Sink",
|
|
"Sink/Audio", "Output audio through OpenAL",
|
|
"Juan Manuel Borges Caño <juanmabcmail@gmail.com>");
|
|
|
|
gst_element_class_add_static_pad_template (gstelement_class,
|
|
&openalsink_factory);
|
|
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_init (GstOpenALSink * sink)
|
|
{
|
|
GST_DEBUG_OBJECT (sink, "initializing");
|
|
|
|
sink->device_name = g_strdup (OPENAL_DEFAULT_DEVICE);
|
|
|
|
sink->user_device = NULL;
|
|
sink->user_context = NULL;
|
|
sink->user_source = 0;
|
|
|
|
sink->default_device = NULL;
|
|
sink->default_context = NULL;
|
|
sink->default_source = 0;
|
|
|
|
sink->buffer_idx = 0;
|
|
sink->buffer_count = 0;
|
|
sink->buffers = NULL;
|
|
sink->buffer_length = 0;
|
|
|
|
sink->write_reset = AL_FALSE;
|
|
sink->probed_caps = NULL;
|
|
|
|
g_mutex_init (&sink->openal_lock);
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_finalize (GObject * object)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (object);
|
|
|
|
g_free (sink->device_name);
|
|
sink->device_name = NULL;
|
|
g_mutex_clear (&sink->openal_lock);
|
|
|
|
G_OBJECT_CLASS (gst_openal_sink_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE:
|
|
g_free (sink->device_name);
|
|
sink->device_name = g_value_dup_string (value);
|
|
if (sink->probed_caps)
|
|
gst_caps_unref (sink->probed_caps);
|
|
sink->probed_caps = NULL;
|
|
break;
|
|
case PROP_USER_DEVICE:
|
|
if (!sink->default_device)
|
|
sink->user_device = g_value_get_pointer (value);
|
|
break;
|
|
case PROP_USER_CONTEXT:
|
|
if (!sink->default_device)
|
|
sink->user_context = g_value_get_pointer (value);
|
|
break;
|
|
case PROP_USER_SOURCE:
|
|
if (!sink->default_device)
|
|
sink->user_source = g_value_get_uint (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (object);
|
|
const ALCchar *device_name = sink->device_name;
|
|
ALCdevice *device = sink->default_device;
|
|
ALCcontext *context = sink->default_context;
|
|
ALuint source = sink->default_source;
|
|
|
|
switch (prop_id) {
|
|
case PROP_DEVICE_NAME:
|
|
device_name = "";
|
|
if (device)
|
|
device_name = alcGetString (device, ALC_DEVICE_SPECIFIER);
|
|
/* fall-through */
|
|
case PROP_DEVICE:
|
|
g_value_set_string (value, device_name);
|
|
break;
|
|
case PROP_USER_DEVICE:
|
|
if (!device)
|
|
device = sink->user_device;
|
|
g_value_set_pointer (value, device);
|
|
break;
|
|
case PROP_USER_CONTEXT:
|
|
if (!context)
|
|
context = sink->user_context;
|
|
g_value_set_pointer (value, context);
|
|
break;
|
|
case PROP_USER_SOURCE:
|
|
if (!source)
|
|
source = sink->user_source;
|
|
g_value_set_uint (value, source);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_openal_helper_probe_caps (ALCcontext * context)
|
|
{
|
|
static const struct
|
|
{
|
|
gint count;
|
|
GstAudioChannelPosition positions[8];
|
|
} chans[] = {
|
|
{
|
|
1, {
|
|
GST_AUDIO_CHANNEL_POSITION_MONO}
|
|
}, {
|
|
2, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT}
|
|
}, {
|
|
4, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}
|
|
}, {
|
|
6, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT}
|
|
}, {
|
|
7, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
|
|
}, {
|
|
8, {
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER,
|
|
GST_AUDIO_CHANNEL_POSITION_LFE1,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT,
|
|
GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT}
|
|
},};
|
|
GstStructure *structure;
|
|
guint64 channel_mask;
|
|
GstCaps *caps;
|
|
ALCcontext *old;
|
|
|
|
old = pushContext (context);
|
|
|
|
caps = gst_caps_new_empty ();
|
|
|
|
if (alIsExtensionPresent ("AL_EXT_MCFORMATS")) {
|
|
const char *fmt32[] = {
|
|
"AL_FORMAT_MONO_FLOAT32",
|
|
"AL_FORMAT_STEREO_FLOAT32",
|
|
"AL_FORMAT_QUAD32",
|
|
"AL_FORMAT_51CHN32",
|
|
"AL_FORMAT_61CHN32",
|
|
"AL_FORMAT_71CHN32",
|
|
NULL
|
|
}, *fmt16[] = {
|
|
"AL_FORMAT_MONO16",
|
|
"AL_FORMAT_STEREO16",
|
|
"AL_FORMAT_QUAD16",
|
|
"AL_FORMAT_51CHN16",
|
|
"AL_FORMAT_61CHN16", "AL_FORMAT_71CHN16", NULL}, *fmt8[] = {
|
|
"AL_FORMAT_MONO8",
|
|
"AL_FORMAT_STEREO8",
|
|
"AL_FORMAT_QUAD8",
|
|
"AL_FORMAT_51CHN8", "AL_FORMAT_61CHN8", "AL_FORMAT_71CHN8", NULL};
|
|
int i;
|
|
|
|
if (alIsExtensionPresent ("AL_EXT_FLOAT32")) {
|
|
for (i = 0; fmt32[i]; i++) {
|
|
ALenum value = alGetEnumValue (fmt32[i]);
|
|
if (checkALError () != AL_NO_ERROR || value == 0 || value == -1)
|
|
continue;
|
|
|
|
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, chans[i].count, NULL);
|
|
if (chans[i].count > 2) {
|
|
gst_audio_channel_positions_to_mask (chans[i].positions,
|
|
chans[i].count, FALSE, &channel_mask);
|
|
gst_structure_set (structure, "channel-mask", GST_TYPE_BITMASK,
|
|
channel_mask, NULL);
|
|
}
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
}
|
|
|
|
for (i = 0; fmt16[i]; i++) {
|
|
ALenum value = alGetEnumValue (fmt16[i]);
|
|
if (checkALError () != AL_NO_ERROR || value == 0 || value == -1)
|
|
continue;
|
|
|
|
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, chans[i].count, NULL);
|
|
if (chans[i].count > 2) {
|
|
gst_audio_channel_positions_to_mask (chans[i].positions, chans[i].count,
|
|
FALSE, &channel_mask);
|
|
gst_structure_set (structure, "channel-mask", GST_TYPE_BITMASK,
|
|
channel_mask, NULL);
|
|
}
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
for (i = 0; fmt8[i]; i++) {
|
|
ALenum value = alGetEnumValue (fmt8[i]);
|
|
if (checkALError () != AL_NO_ERROR || value == 0 || value == -1)
|
|
continue;
|
|
|
|
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, chans[i].count, NULL);
|
|
if (chans[i].count > 2) {
|
|
gst_audio_channel_positions_to_mask (chans[i].positions, chans[i].count,
|
|
FALSE, &channel_mask);
|
|
gst_structure_set (structure, "channel-mask", GST_TYPE_BITMASK,
|
|
channel_mask, NULL);
|
|
}
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
} else {
|
|
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", GST_TYPE_INT_RANGE, 1, 2, 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", GST_TYPE_INT_RANGE, 1, 2, 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", GST_TYPE_INT_RANGE, 1, 2, NULL);
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
|
|
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", GST_TYPE_INT_RANGE, 1, 2, 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", GST_TYPE_INT_RANGE, 1, 2, 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", GST_TYPE_INT_RANGE, 1, 2,
|
|
NULL);
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
|
|
if (alIsExtensionPresent ("AL_EXT_MULAW_MCFORMATS")) {
|
|
const char *fmtmulaw[] = {
|
|
"AL_FORMAT_MONO_MULAW",
|
|
"AL_FORMAT_STEREO_MULAW",
|
|
"AL_FORMAT_QUAD_MULAW",
|
|
"AL_FORMAT_51CHN_MULAW",
|
|
"AL_FORMAT_61CHN_MULAW",
|
|
"AL_FORMAT_71CHN_MULAW",
|
|
NULL
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; fmtmulaw[i]; i++) {
|
|
ALenum value = alGetEnumValue (fmtmulaw[i]);
|
|
if (checkALError () != AL_NO_ERROR || value == 0 || value == -1)
|
|
continue;
|
|
|
|
structure =
|
|
gst_structure_new ("audio/x-mulaw", "rate", GST_TYPE_INT_RANGE,
|
|
OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", G_TYPE_INT,
|
|
chans[i].count, NULL);
|
|
if (chans[i].count > 2) {
|
|
gst_audio_channel_positions_to_mask (chans[i].positions, chans[i].count,
|
|
FALSE, &channel_mask);
|
|
gst_structure_set (structure, "channel-mask", GST_TYPE_BITMASK,
|
|
channel_mask, NULL);
|
|
}
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
} else if (alIsExtensionPresent ("AL_EXT_MULAW")) {
|
|
structure =
|
|
gst_structure_new ("audio/x-mulaw", "rate", GST_TYPE_INT_RANGE,
|
|
OPENAL_MIN_RATE, OPENAL_MAX_RATE, "channels", GST_TYPE_INT_RANGE, 1, 2,
|
|
NULL);
|
|
gst_caps_append_structure (caps, structure);
|
|
}
|
|
|
|
popContext (old, context);
|
|
|
|
return caps;
|
|
}
|
|
|
|
static GstCaps *
|
|
gst_openal_sink_getcaps (GstBaseSink * basesink, GstCaps * filter)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (basesink);
|
|
GstCaps *caps;
|
|
|
|
if (sink->default_device == NULL) {
|
|
GstPad *pad = GST_BASE_SINK_PAD (basesink);
|
|
GstCaps *tcaps = gst_pad_get_pad_template_caps (pad);
|
|
caps = gst_caps_copy (tcaps);
|
|
gst_caps_unref (tcaps);
|
|
} else if (sink->probed_caps)
|
|
caps = gst_caps_copy (sink->probed_caps);
|
|
else {
|
|
if (sink->default_context)
|
|
caps = gst_openal_helper_probe_caps (sink->default_context);
|
|
else if (sink->user_context)
|
|
caps = gst_openal_helper_probe_caps (sink->user_context);
|
|
else {
|
|
ALCcontext *context = alcCreateContext (sink->default_device, NULL);
|
|
if (context) {
|
|
caps = gst_openal_helper_probe_caps (context);
|
|
alcDestroyContext (context);
|
|
} else {
|
|
GST_ELEMENT_WARNING (sink, RESOURCE, FAILED,
|
|
("Could not create temporary context."),
|
|
GST_ALC_ERROR (sink->default_device));
|
|
caps = NULL;
|
|
}
|
|
}
|
|
|
|
if (caps && !gst_caps_is_empty (caps))
|
|
sink->probed_caps = gst_caps_copy (caps);
|
|
}
|
|
|
|
if (filter) {
|
|
GstCaps *intersection;
|
|
|
|
intersection =
|
|
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
|
|
return intersection;
|
|
} else {
|
|
return caps;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_openal_sink_open (GstAudioSink * audiosink)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
|
|
if (sink->user_device) {
|
|
ALCint value = -1;
|
|
alcGetIntegerv (sink->user_device, ALC_ATTRIBUTES_SIZE, 1, &value);
|
|
if (value > 0) {
|
|
if (!sink->user_context
|
|
|| alcGetContextsDevice (sink->user_context) == sink->user_device)
|
|
sink->default_device = sink->user_device;
|
|
}
|
|
} else if (sink->user_context)
|
|
sink->default_device = alcGetContextsDevice (sink->user_context);
|
|
else
|
|
sink->default_device = alcOpenDevice (sink->device_name);
|
|
if (!sink->default_device) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, OPEN_WRITE,
|
|
("Could not open device."), GST_ALC_ERROR (sink->default_device));
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_openal_sink_close (GstAudioSink * audiosink)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
|
|
if (!sink->user_device && !sink->user_context) {
|
|
if (alcCloseDevice (sink->default_device) == ALC_FALSE) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, CLOSE,
|
|
("Could not close device."), GST_ALC_ERROR (sink->default_device));
|
|
return FALSE;
|
|
}
|
|
}
|
|
sink->default_device = NULL;
|
|
|
|
if (sink->probed_caps)
|
|
gst_caps_unref (sink->probed_caps);
|
|
sink->probed_caps = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_parse_spec (GstOpenALSink * sink,
|
|
const GstAudioRingBufferSpec * spec)
|
|
{
|
|
ALuint format = AL_NONE;
|
|
|
|
GST_DEBUG_OBJECT (sink,
|
|
"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));
|
|
|
|
/* Don't need to verify supported formats, since the probed caps will only
|
|
* report what was detected and we shouldn't get anything different */
|
|
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;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO8;
|
|
break;
|
|
case 4:
|
|
format = AL_FORMAT_QUAD8;
|
|
break;
|
|
case 6:
|
|
format = AL_FORMAT_51CHN8;
|
|
break;
|
|
case 7:
|
|
format = AL_FORMAT_61CHN8;
|
|
break;
|
|
case 8:
|
|
format = AL_FORMAT_71CHN8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case GST_AUDIO_FORMAT_S16:
|
|
switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) {
|
|
case 1:
|
|
format = AL_FORMAT_MONO16;
|
|
break;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO16;
|
|
break;
|
|
case 4:
|
|
format = AL_FORMAT_QUAD16;
|
|
break;
|
|
case 6:
|
|
format = AL_FORMAT_51CHN16;
|
|
break;
|
|
case 7:
|
|
format = AL_FORMAT_61CHN16;
|
|
break;
|
|
case 8:
|
|
format = AL_FORMAT_71CHN16;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case GST_AUDIO_FORMAT_F32:
|
|
switch (GST_AUDIO_INFO_CHANNELS (&spec->info)) {
|
|
case 1:
|
|
format = AL_FORMAT_MONO_FLOAT32;
|
|
break;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO_FLOAT32;
|
|
break;
|
|
case 4:
|
|
format = AL_FORMAT_QUAD32;
|
|
break;
|
|
case 6:
|
|
format = AL_FORMAT_51CHN32;
|
|
break;
|
|
case 7:
|
|
format = AL_FORMAT_61CHN32;
|
|
break;
|
|
case 8:
|
|
format = AL_FORMAT_71CHN32;
|
|
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;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO_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;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO_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;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO_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;
|
|
case 2:
|
|
format = AL_FORMAT_STEREO_MULAW;
|
|
break;
|
|
case 4:
|
|
format = AL_FORMAT_QUAD_MULAW;
|
|
break;
|
|
case 6:
|
|
format = AL_FORMAT_51CHN_MULAW;
|
|
break;
|
|
case 7:
|
|
format = AL_FORMAT_61CHN_MULAW;
|
|
break;
|
|
case 8:
|
|
format = AL_FORMAT_71CHN_MULAW;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
sink->bytes_per_sample = GST_AUDIO_INFO_BPS (&spec->info);
|
|
sink->rate = GST_AUDIO_INFO_RATE (&spec->info);
|
|
sink->channels = GST_AUDIO_INFO_CHANNELS (&spec->info);
|
|
sink->format = format;
|
|
sink->buffer_count = spec->segtotal;
|
|
sink->buffer_length = spec->segsize;
|
|
}
|
|
|
|
static gboolean
|
|
gst_openal_sink_prepare (GstAudioSink * audiosink,
|
|
GstAudioRingBufferSpec * spec)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
ALCcontext *context, *old;
|
|
|
|
if (sink->default_context && !gst_openal_sink_unprepare (audiosink))
|
|
return FALSE;
|
|
|
|
if (sink->user_context)
|
|
context = sink->user_context;
|
|
else {
|
|
ALCint attribs[3] = { 0, 0, 0 };
|
|
|
|
/* Don't try to change the playback frequency of an app's device */
|
|
if (!sink->user_device) {
|
|
attribs[0] = ALC_FREQUENCY;
|
|
attribs[1] = GST_AUDIO_INFO_RATE (&spec->info);
|
|
attribs[2] = 0;
|
|
}
|
|
|
|
context = alcCreateContext (sink->default_device, attribs);
|
|
if (!context) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, FAILED,
|
|
("Unable to prepare device."), GST_ALC_ERROR (sink->default_device));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
old = pushContext (context);
|
|
|
|
if (sink->user_source) {
|
|
if (!sink->user_context || !alIsSource (sink->user_source)) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, NOT_FOUND, (NULL),
|
|
("Invalid source specified for context"));
|
|
goto fail;
|
|
}
|
|
sink->default_source = sink->user_source;
|
|
} else {
|
|
ALuint source;
|
|
|
|
alGenSources (1, &source);
|
|
if (checkALError () != AL_NO_ERROR) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, NO_SPACE_LEFT, (NULL),
|
|
("Unable to generate source"));
|
|
goto fail;
|
|
}
|
|
sink->default_source = source;
|
|
}
|
|
|
|
gst_openal_sink_parse_spec (sink, spec);
|
|
if (sink->format == AL_NONE) {
|
|
GST_ELEMENT_ERROR (sink, 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)));
|
|
goto fail;
|
|
}
|
|
|
|
sink->buffers = g_malloc (sink->buffer_count * sizeof (*sink->buffers));
|
|
if (!sink->buffers) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, FAILED, ("Out of memory."),
|
|
("Unable to allocate buffers"));
|
|
goto fail;
|
|
}
|
|
|
|
alGenBuffers (sink->buffer_count, sink->buffers);
|
|
if (checkALError () != AL_NO_ERROR) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, NO_SPACE_LEFT, (NULL),
|
|
("Unable to generate %d buffers", sink->buffer_count));
|
|
goto fail;
|
|
}
|
|
sink->buffer_idx = 0;
|
|
|
|
popContext (old, context);
|
|
sink->default_context = context;
|
|
return TRUE;
|
|
|
|
fail:
|
|
if (!sink->user_source && sink->default_source)
|
|
alDeleteSources (1, &sink->default_source);
|
|
sink->default_source = 0;
|
|
|
|
g_free (sink->buffers);
|
|
sink->buffers = NULL;
|
|
sink->buffer_count = 0;
|
|
sink->buffer_length = 0;
|
|
|
|
popContext (old, context);
|
|
if (!sink->user_context)
|
|
alcDestroyContext (context);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_openal_sink_unprepare (GstAudioSink * audiosink)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
ALCcontext *old;
|
|
|
|
if (!sink->default_context)
|
|
return TRUE;
|
|
|
|
old = pushContext (sink->default_context);
|
|
|
|
alSourceStop (sink->default_source);
|
|
alSourcei (sink->default_source, AL_BUFFER, 0);
|
|
|
|
if (!sink->user_source)
|
|
alDeleteSources (1, &sink->default_source);
|
|
sink->default_source = 0;
|
|
|
|
alDeleteBuffers (sink->buffer_count, sink->buffers);
|
|
g_free (sink->buffers);
|
|
sink->buffers = NULL;
|
|
sink->buffer_idx = 0;
|
|
sink->buffer_count = 0;
|
|
sink->buffer_length = 0;
|
|
|
|
checkALError ();
|
|
popContext (old, sink->default_context);
|
|
if (!sink->user_context)
|
|
alcDestroyContext (sink->default_context);
|
|
sink->default_context = NULL;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
gst_openal_sink_write (GstAudioSink * audiosink, gpointer data, guint length)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
ALint processed, queued, state;
|
|
ALCcontext *old;
|
|
gulong rest_us;
|
|
|
|
g_assert (length == sink->buffer_length);
|
|
|
|
old = pushContext (sink->default_context);
|
|
|
|
rest_us =
|
|
(guint64) (sink->buffer_length / sink->bytes_per_sample) *
|
|
G_USEC_PER_SEC / sink->rate / sink->channels;
|
|
do {
|
|
alGetSourcei (sink->default_source, AL_SOURCE_STATE, &state);
|
|
alGetSourcei (sink->default_source, AL_BUFFERS_QUEUED, &queued);
|
|
alGetSourcei (sink->default_source, AL_BUFFERS_PROCESSED, &processed);
|
|
if (checkALError () != AL_NO_ERROR) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
|
|
("Source state error detected"));
|
|
length = 0;
|
|
goto out_nolock;
|
|
}
|
|
|
|
if (processed > 0 || queued < sink->buffer_count)
|
|
break;
|
|
if (state != AL_PLAYING)
|
|
alSourcePlay (sink->default_source);
|
|
g_usleep (rest_us);
|
|
}
|
|
while (1);
|
|
|
|
GST_OPENAL_SINK_LOCK (sink);
|
|
if (sink->write_reset != AL_FALSE) {
|
|
sink->write_reset = AL_FALSE;
|
|
length = 0;
|
|
goto out;
|
|
}
|
|
|
|
queued -= processed;
|
|
while (processed-- > 0) {
|
|
ALuint bid;
|
|
alSourceUnqueueBuffers (sink->default_source, 1, &bid);
|
|
}
|
|
if (state == AL_STOPPED) {
|
|
/* "Restore" from underruns (not actually needed, but it keeps delay
|
|
* calculations correct while rebuffering) */
|
|
alSourceRewind (sink->default_source);
|
|
}
|
|
|
|
alBufferData (sink->buffers[sink->buffer_idx], sink->format,
|
|
data, sink->buffer_length, sink->rate);
|
|
alSourceQueueBuffers (sink->default_source, 1,
|
|
&sink->buffers[sink->buffer_idx]);
|
|
sink->buffer_idx = (sink->buffer_idx + 1) % sink->buffer_count;
|
|
queued++;
|
|
|
|
if (state != AL_PLAYING && queued == sink->buffer_count)
|
|
alSourcePlay (sink->default_source);
|
|
|
|
if (checkALError () != AL_NO_ERROR) {
|
|
GST_ELEMENT_ERROR (sink, RESOURCE, WRITE, (NULL),
|
|
("Source queue error detected"));
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
GST_OPENAL_SINK_UNLOCK (sink);
|
|
out_nolock:
|
|
popContext (old, sink->default_context);
|
|
return length;
|
|
}
|
|
|
|
static guint
|
|
gst_openal_sink_delay (GstAudioSink * audiosink)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
ALint queued, state, offset, delay;
|
|
ALCcontext *old;
|
|
|
|
if (!sink->default_context)
|
|
return 0;
|
|
|
|
GST_OPENAL_SINK_LOCK (sink);
|
|
old = pushContext (sink->default_context);
|
|
|
|
delay = 0;
|
|
alGetSourcei (sink->default_source, AL_BUFFERS_QUEUED, &queued);
|
|
/* Order here is important. If the offset is queried after the state and an
|
|
* underrun occurs in between the two calls, it can end up with a 0 offset
|
|
* in a playing state, incorrectly reporting a len*queued/bps delay. */
|
|
alGetSourcei (sink->default_source, AL_BYTE_OFFSET, &offset);
|
|
alGetSourcei (sink->default_source, AL_SOURCE_STATE, &state);
|
|
|
|
/* Note: state=stopped is an underrun, meaning all buffers are processed
|
|
* and there's no delay when writing the next buffer. Pre-buffering is
|
|
* state=initial, which will introduce a delay while writing. */
|
|
if (checkALError () == AL_NO_ERROR && state != AL_STOPPED)
|
|
delay =
|
|
((queued * sink->buffer_length) -
|
|
offset) / sink->bytes_per_sample / sink->channels / GST_MSECOND;
|
|
|
|
popContext (old, sink->default_context);
|
|
GST_OPENAL_SINK_UNLOCK (sink);
|
|
|
|
if (G_UNLIKELY (delay < 0)) {
|
|
/* make sure we never return a negative delay */
|
|
GST_WARNING_OBJECT (openal_debug, "negative delay");
|
|
delay = 0;
|
|
}
|
|
|
|
return delay;
|
|
}
|
|
|
|
static void
|
|
gst_openal_sink_reset (GstAudioSink * audiosink)
|
|
{
|
|
GstOpenALSink *sink = GST_OPENAL_SINK (audiosink);
|
|
ALCcontext *old;
|
|
|
|
GST_OPENAL_SINK_LOCK (sink);
|
|
old = pushContext (sink->default_context);
|
|
|
|
sink->write_reset = AL_TRUE;
|
|
alSourceStop (sink->default_source);
|
|
alSourceRewind (sink->default_source);
|
|
alSourcei (sink->default_source, AL_BUFFER, 0);
|
|
checkALError ();
|
|
|
|
popContext (old, sink->default_context);
|
|
GST_OPENAL_SINK_UNLOCK (sink);
|
|
}
|