blob: 1fa1c65742e97d02b1555ca179f199b1e0d56a5d [file] [log] [blame]
/*
* 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
* @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.
*
* <refsect2>
* <title>Example pipelines</title>
* |[
* gst-launch audiotestsrc ! audioconvert ! volume volume=0.5 ! openalsink
* ]| will play a sine wave (continuous beep sound) through OpenAL.
* |[
* gst-launch filesrc location=stream.wav ! decodebin ! audioconvert ! openalsink
* ]| will play a wav audio file through OpenAL.
* |[
* gst-launch openalsrc ! "audio/x-raw,format=S16LE,rate=44100" ! audioconvert ! volume volume=0.25 ! openalsink
* ]| will capture and play audio through OpenAL.
* </refsect2>
*/
/*
* 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_pad_template (gstelement_class,
gst_static_pad_template_get (&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);
}