static guint gst_openal_sink_delay (GstAudioSink * asink) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); ALint queued, state, offset, delay; ALCcontext *old; if (!openal->context) return 0; GST_OPENAL_SINK_LOCK (openal); old = pushContext (openal->context); delay = 0; alGetSourcei (openal->sID, 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 (openal->sID, AL_BYTE_OFFSET, &offset); alGetSourcei (openal->sID, 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 * openal->bID_length) - offset) / openal->bytes_per_sample; popContext (old, openal->context); GST_OPENAL_SINK_UNLOCK (openal); return delay; }
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 gboolean gst_openal_sink_unprepare (GstAudioSink * asink) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); ALCcontext *old; if (!openal->context) return TRUE; old = pushContext (openal->context); alSourceStop (openal->sID); alSourcei (openal->sID, AL_BUFFER, 0); if (!openal->custom_sID) alDeleteSources (1, &openal->sID); openal->sID = 0; alDeleteBuffers (openal->bID_count, openal->bIDs); g_free (openal->bIDs); openal->bIDs = NULL; openal->bID_idx = 0; openal->bID_count = 0; openal->bID_length = 0; checkALError (); popContext (old, openal->context); if (!openal->custom_ctx) alcDestroyContext (openal->context); openal->context = NULL; return TRUE; }
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_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 gboolean gst_openal_sink_open (GstAudioSink * asink) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); if (openal->custom_dev) { ALCint val = -1; alcGetIntegerv (openal->custom_dev, ALC_ATTRIBUTES_SIZE, 1, &val); if (val > 0) { if (!openal->custom_ctx || alcGetContextsDevice (openal->custom_ctx) == openal->custom_dev) openal->device = openal->custom_dev; } } else if (openal->custom_ctx) openal->device = alcGetContextsDevice (openal->custom_ctx); else openal->device = alcOpenDevice (openal->devname); if (!openal->device) { GST_ELEMENT_ERROR (openal, RESOURCE, OPEN_WRITE, ("Could not open audio device for playback."), GST_ALC_ERROR (openal->device)); return FALSE; } return TRUE; }
static GstCaps * gst_openal_sink_getcaps (GstBaseSink * bsink) { GstOpenALSink *sink = GST_OPENAL_SINK (bsink); GstCaps *caps; if (sink->device == NULL) { GstPad *pad = GST_BASE_SINK_PAD (bsink); caps = gst_caps_copy (gst_pad_get_pad_template_caps (pad)); } else if (sink->probed_caps) caps = gst_caps_copy (sink->probed_caps); else { if (sink->context) caps = gst_openal_helper_probe_caps (sink->context); else if (sink->custom_ctx) caps = gst_openal_helper_probe_caps (sink->custom_ctx); else { ALCcontext *ctx = alcCreateContext (sink->device, NULL); if (ctx) { caps = gst_openal_helper_probe_caps (ctx); alcDestroyContext (ctx); } else { GST_ELEMENT_WARNING (sink, RESOURCE, FAILED, ("Could not create temporary context."), GST_ALC_ERROR (sink->device)); caps = NULL; } } if (caps && !gst_caps_is_empty (caps)) sink->probed_caps = gst_caps_copy (caps); } return caps; }
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_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 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 void gst_openal_sink_reset (GstAudioSink * asink) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); ALCcontext *old; GST_OPENAL_SINK_LOCK (openal); old = pushContext (openal->context); openal->write_reset = AL_TRUE; alSourceStop (openal->sID); alSourceRewind (openal->sID); alSourcei (openal->sID, AL_BUFFER, 0); checkALError (); popContext (old, openal->context); GST_OPENAL_SINK_UNLOCK (openal); }
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); }
static gboolean gst_openal_sink_close (GstAudioSink * asink) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); if (!openal->custom_dev && !openal->custom_ctx) { if (alcCloseDevice (openal->device) == ALC_FALSE) { GST_ELEMENT_ERROR (openal, RESOURCE, CLOSE, ("Could not close audio device."), GST_ALC_ERROR (openal->device)); return FALSE; } } openal->device = NULL; if (openal->probed_caps) gst_caps_unref (openal->probed_caps); openal->probed_caps = NULL; 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_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 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 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 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_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) { GstOpenALSink *openal = GST_OPENAL_SINK (asink); ALCcontext *ctx, *old; if (openal->context && !gst_openal_sink_unprepare (asink)) return FALSE; if (openal->custom_ctx) ctx = openal->custom_ctx; else { ALCint attribs[3] = { 0, 0, 0 }; /* Don't try to change the playback frequency of an app's device */ if (!openal->custom_dev) { attribs[0] = ALC_FREQUENCY; attribs[1] = spec->rate; attribs[2] = 0; } ctx = alcCreateContext (openal->device, attribs); if (!ctx) { GST_ELEMENT_ERROR (openal, RESOURCE, FAILED, ("Unable to prepare device."), GST_ALC_ERROR (openal->device)); return FALSE; } } old = pushContext (ctx); if (openal->custom_sID) { if (!openal->custom_ctx || !alIsSource (openal->custom_sID)) { GST_ELEMENT_ERROR (openal, RESOURCE, NOT_FOUND, (NULL), ("Invalid source ID specified for context")); goto fail; } openal->sID = openal->custom_sID; } else { ALuint sourceID; alGenSources (1, &sourceID); if (checkALError () != AL_NO_ERROR) { GST_ELEMENT_ERROR (openal, RESOURCE, NO_SPACE_LEFT, (NULL), ("Unable to generate source")); goto fail; } openal->sID = sourceID; } gst_openal_sink_parse_spec (openal, spec); if (openal->format == AL_NONE) { GST_ELEMENT_ERROR (openal, RESOURCE, SETTINGS, (NULL), ("Unable to get type %d, format %d, and %d channels", spec->type, spec->format, spec->channels)); goto fail; } openal->bIDs = g_malloc (openal->bID_count * sizeof (*openal->bIDs)); if (!openal->bIDs) { GST_ELEMENT_ERROR (openal, RESOURCE, FAILED, ("Out of memory."), ("Unable to allocate buffer IDs")); goto fail; } alGenBuffers (openal->bID_count, openal->bIDs); if (checkALError () != AL_NO_ERROR) { GST_ELEMENT_ERROR (openal, RESOURCE, NO_SPACE_LEFT, (NULL), ("Unable to generate %d buffers", openal->bID_count)); goto fail; } openal->bID_idx = 0; popContext (old, ctx); openal->context = ctx; return TRUE; fail: if (!openal->custom_sID && openal->sID) alDeleteSources (1, &openal->sID); openal->sID = 0; g_free (openal->bIDs); openal->bIDs = NULL; openal->bID_count = 0; openal->bID_length = 0; popContext (old, ctx); if (!openal->custom_ctx) alcDestroyContext (ctx); return FALSE; }