| /* GStreamer |
| * Copyright (C) 2005 Wim Taymans <wim@fluendo.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:gstaudioringbuffer |
| * @title: GstAudioRingBuffer |
| * @short_description: Base class for audio ringbuffer implementations |
| * @see_also: #GstAudioBaseSink, #GstAudioSink |
| * |
| * This object is the base class for audio ringbuffers used by the base |
| * audio source and sink classes. |
| * |
| * The ringbuffer abstracts a circular buffer of data. One reader and |
| * one writer can operate on the data from different threads in a lockfree |
| * manner. The base class is sufficiently flexible to be used as an |
| * abstraction for DMA based ringbuffers as well as a pure software |
| * implementations. |
| * |
| */ |
| |
| #include <string.h> |
| |
| #include <gst/audio/audio.h> |
| #include "gstaudioringbuffer.h" |
| |
| GST_DEBUG_CATEGORY_STATIC (gst_audio_ring_buffer_debug); |
| #define GST_CAT_DEFAULT gst_audio_ring_buffer_debug |
| |
| static void gst_audio_ring_buffer_dispose (GObject * object); |
| static void gst_audio_ring_buffer_finalize (GObject * object); |
| |
| static gboolean gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf); |
| static void default_clear_all (GstAudioRingBuffer * buf); |
| static guint default_commit (GstAudioRingBuffer * buf, guint64 * sample, |
| guint8 * data, gint in_samples, gint out_samples, gint * accum); |
| |
| /* ringbuffer abstract base class */ |
| G_DEFINE_ABSTRACT_TYPE (GstAudioRingBuffer, gst_audio_ring_buffer, |
| GST_TYPE_OBJECT); |
| |
| static void |
| gst_audio_ring_buffer_class_init (GstAudioRingBufferClass * klass) |
| { |
| GObjectClass *gobject_class; |
| GstAudioRingBufferClass *gstaudioringbuffer_class; |
| |
| gobject_class = (GObjectClass *) klass; |
| gstaudioringbuffer_class = (GstAudioRingBufferClass *) klass; |
| |
| GST_DEBUG_CATEGORY_INIT (gst_audio_ring_buffer_debug, "ringbuffer", 0, |
| "ringbuffer class"); |
| |
| gobject_class->dispose = gst_audio_ring_buffer_dispose; |
| gobject_class->finalize = gst_audio_ring_buffer_finalize; |
| |
| gstaudioringbuffer_class->clear_all = GST_DEBUG_FUNCPTR (default_clear_all); |
| gstaudioringbuffer_class->commit = GST_DEBUG_FUNCPTR (default_commit); |
| } |
| |
| static void |
| gst_audio_ring_buffer_init (GstAudioRingBuffer * ringbuffer) |
| { |
| ringbuffer->open = FALSE; |
| ringbuffer->acquired = FALSE; |
| ringbuffer->state = GST_AUDIO_RING_BUFFER_STATE_STOPPED; |
| g_cond_init (&ringbuffer->cond); |
| ringbuffer->waiting = 0; |
| ringbuffer->empty_seg = NULL; |
| ringbuffer->flushing = TRUE; |
| ringbuffer->segbase = 0; |
| ringbuffer->segdone = 0; |
| } |
| |
| static void |
| gst_audio_ring_buffer_dispose (GObject * object) |
| { |
| GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER (object); |
| |
| gst_caps_replace (&ringbuffer->spec.caps, NULL); |
| |
| G_OBJECT_CLASS (gst_audio_ring_buffer_parent_class)->dispose (G_OBJECT |
| (ringbuffer)); |
| } |
| |
| static void |
| gst_audio_ring_buffer_finalize (GObject * object) |
| { |
| GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER (object); |
| |
| g_cond_clear (&ringbuffer->cond); |
| g_free (ringbuffer->empty_seg); |
| |
| if (ringbuffer->cb_data_notify != NULL) |
| ringbuffer->cb_data_notify (ringbuffer->cb_data); |
| |
| G_OBJECT_CLASS (gst_audio_ring_buffer_parent_class)->finalize (G_OBJECT |
| (ringbuffer)); |
| } |
| |
| #ifndef GST_DISABLE_GST_DEBUG |
| static const gchar *format_type_names[] = { |
| "raw", |
| "mu law", |
| "a law", |
| "ima adpcm", |
| "mpeg", |
| "gsm", |
| "iec958", |
| "ac3", |
| "eac3", |
| "dts", |
| "aac mpeg2", |
| "aac mpeg4", |
| "aac mpeg2 raw", |
| "aac mpeg4 raw", |
| "flac" |
| }; |
| #endif |
| |
| /** |
| * gst_audio_ring_buffer_debug_spec_caps: |
| * @spec: the spec to debug |
| * |
| * Print debug info about the parsed caps in @spec to the debug log. |
| */ |
| void |
| gst_audio_ring_buffer_debug_spec_caps (GstAudioRingBufferSpec * spec) |
| { |
| #if 0 |
| gint i, bytes; |
| #endif |
| |
| GST_DEBUG ("spec caps: %p %" GST_PTR_FORMAT, spec->caps, spec->caps); |
| GST_DEBUG ("parsed caps: type: %d, '%s'", spec->type, |
| format_type_names[spec->type]); |
| #if 0 |
| GST_DEBUG ("parsed caps: width: %d", spec->width); |
| GST_DEBUG ("parsed caps: sign: %d", spec->sign); |
| GST_DEBUG ("parsed caps: bigend: %d", spec->bigend); |
| GST_DEBUG ("parsed caps: rate: %d", spec->rate); |
| GST_DEBUG ("parsed caps: channels: %d", spec->channels); |
| GST_DEBUG ("parsed caps: sample bytes: %d", spec->bytes_per_sample); |
| bytes = (spec->width >> 3) * spec->channels; |
| for (i = 0; i < bytes; i++) { |
| GST_DEBUG ("silence byte %d: %02x", i, spec->silence_sample[i]); |
| } |
| #endif |
| } |
| |
| /** |
| * gst_audio_ring_buffer_debug_spec_buff: |
| * @spec: the spec to debug |
| * |
| * Print debug info about the buffer sized in @spec to the debug log. |
| */ |
| void |
| gst_audio_ring_buffer_debug_spec_buff (GstAudioRingBufferSpec * spec) |
| { |
| gint bpf = GST_AUDIO_INFO_BPF (&spec->info); |
| |
| GST_DEBUG ("acquire ringbuffer: buffer time: %" G_GINT64_FORMAT " usec", |
| spec->buffer_time); |
| GST_DEBUG ("acquire ringbuffer: latency time: %" G_GINT64_FORMAT " usec", |
| spec->latency_time); |
| GST_DEBUG ("acquire ringbuffer: total segments: %d", spec->segtotal); |
| GST_DEBUG ("acquire ringbuffer: latency segments: %d", spec->seglatency); |
| GST_DEBUG ("acquire ringbuffer: segment size: %d bytes = %d samples", |
| spec->segsize, (bpf != 0) ? (spec->segsize / bpf) : -1); |
| GST_DEBUG ("acquire ringbuffer: buffer size: %d bytes = %d samples", |
| spec->segsize * spec->segtotal, |
| (bpf != 0) ? (spec->segsize * spec->segtotal / bpf) : -1); |
| } |
| |
| /** |
| * gst_audio_ring_buffer_parse_caps: |
| * @spec: a spec |
| * @caps: a #GstCaps |
| * |
| * Parse @caps into @spec. |
| * |
| * Returns: TRUE if the caps could be parsed. |
| */ |
| gboolean |
| gst_audio_ring_buffer_parse_caps (GstAudioRingBufferSpec * spec, GstCaps * caps) |
| { |
| const gchar *mimetype; |
| GstStructure *structure; |
| gint i; |
| GstAudioInfo info; |
| |
| structure = gst_caps_get_structure (caps, 0); |
| gst_audio_info_init (&info); |
| |
| /* we have to differentiate between int and float formats */ |
| mimetype = gst_structure_get_name (structure); |
| |
| if (g_str_equal (mimetype, "audio/x-raw")) { |
| if (!gst_audio_info_from_caps (&info, caps)) |
| goto parse_error; |
| |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW; |
| } else if (g_str_equal (mimetype, "audio/x-alaw")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate) && |
| gst_structure_get_int (structure, "channels", &info.channels))) |
| goto parse_error; |
| |
| if (!(gst_audio_channel_positions_from_mask (info.channels, 0, |
| info.position))) |
| goto parse_error; |
| |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW; |
| info.bpf = info.channels; |
| } else if (g_str_equal (mimetype, "audio/x-mulaw")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate) && |
| gst_structure_get_int (structure, "channels", &info.channels))) |
| goto parse_error; |
| |
| if (!(gst_audio_channel_positions_from_mask (info.channels, 0, |
| info.position))) |
| goto parse_error; |
| |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MU_LAW; |
| info.bpf = info.channels; |
| } else if (g_str_equal (mimetype, "audio/x-iec958")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_IEC958; |
| info.bpf = 4; |
| } else if (g_str_equal (mimetype, "audio/x-ac3")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3; |
| info.bpf = 4; |
| } else if (g_str_equal (mimetype, "audio/x-eac3")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3; |
| info.bpf = 16; |
| } else if (g_str_equal (mimetype, "audio/x-dts")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS; |
| info.bpf = 4; |
| } else if (g_str_equal (mimetype, "audio/mpeg") && |
| gst_structure_get_int (structure, "mpegaudioversion", &i) && |
| (i == 1 || i == 2 || i == 3)) { |
| /* Now we know this is MPEG-1, MPEG-2 or MPEG-2.5 (non AAC) */ |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG; |
| info.bpf = 1; |
| } else if (g_str_equal (mimetype, "audio/mpeg") && |
| gst_structure_get_int (structure, "mpegversion", &i) && |
| (i == 2 || i == 4) && |
| (!g_strcmp0 (gst_structure_get_string (structure, "stream-format"), |
| "adts") |
| || !g_strcmp0 (gst_structure_get_string (structure, "stream-format"), |
| "raw"))) { |
| /* MPEG-2 AAC or MPEG-4 AAC */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| if (!g_strcmp0 (gst_structure_get_string (structure, "stream-format"), |
| "adts")) |
| spec->type = (i == 2) ? GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG2_AAC : |
| GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG4_AAC; |
| else |
| spec->type = (i == 2) ? |
| GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG2_AAC_RAW : |
| GST_AUDIO_RING_BUFFER_FORMAT_TYPE_MPEG4_AAC_RAW; |
| info.bpf = 1; |
| } else if (g_str_equal (mimetype, "audio/x-flac")) { |
| /* extract the needed information from the cap */ |
| if (!(gst_structure_get_int (structure, "rate", &info.rate))) |
| goto parse_error; |
| |
| gst_structure_get_int (structure, "channels", &info.channels); |
| spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_FLAC; |
| info.bpf = 1; |
| } else { |
| goto parse_error; |
| } |
| |
| gst_caps_replace (&spec->caps, caps); |
| |
| g_return_val_if_fail (spec->latency_time != 0, FALSE); |
| |
| /* calculate suggested segsize and segtotal. segsize should be one unit |
| * of 'latency_time' samples, scaling for the fact that latency_time is |
| * currently stored in microseconds (FIXME: in 0.11) */ |
| spec->segsize = gst_util_uint64_scale (info.rate * info.bpf, |
| spec->latency_time, GST_SECOND / GST_USECOND); |
| /* Round to an integer number of samples */ |
| spec->segsize -= spec->segsize % info.bpf; |
| |
| spec->segtotal = spec->buffer_time / spec->latency_time; |
| /* leave the latency undefined now, implementations can change it but if it's |
| * not changed, we assume the same value as segtotal */ |
| spec->seglatency = -1; |
| |
| spec->info = info; |
| |
| gst_audio_ring_buffer_debug_spec_caps (spec); |
| gst_audio_ring_buffer_debug_spec_buff (spec); |
| |
| return TRUE; |
| |
| /* ERRORS */ |
| parse_error: |
| { |
| GST_DEBUG ("could not parse caps"); |
| return FALSE; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_convert: |
| * @buf: the #GstAudioRingBuffer |
| * @src_fmt: the source format |
| * @src_val: the source value |
| * @dest_fmt: the destination format |
| * @dest_val: (out): a location to store the converted value |
| * |
| * Convert @src_val in @src_fmt to the equivalent value in @dest_fmt. The result |
| * will be put in @dest_val. |
| * |
| * Returns: TRUE if the conversion succeeded. |
| */ |
| gboolean |
| gst_audio_ring_buffer_convert (GstAudioRingBuffer * buf, |
| GstFormat src_fmt, gint64 src_val, GstFormat dest_fmt, gint64 * dest_val) |
| { |
| gboolean res; |
| |
| GST_OBJECT_LOCK (buf); |
| res = |
| gst_audio_info_convert (&buf->spec.info, src_fmt, src_val, dest_fmt, |
| dest_val); |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_set_callback: (skip) |
| * @buf: the #GstAudioRingBuffer to set the callback on |
| * @cb: (allow-none): the callback to set |
| * @user_data: user data passed to the callback |
| * |
| * Sets the given callback function on the buffer. This function |
| * will be called every time a segment has been written to a device. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_set_callback (GstAudioRingBuffer * buf, |
| GstAudioRingBufferCallback cb, gpointer user_data) |
| { |
| gst_audio_ring_buffer_set_callback_full (buf, cb, user_data, NULL); |
| } |
| |
| /** |
| * gst_audio_ring_buffer_set_callback_full: (rename-to gst_audio_ring_buffer_set_callback) |
| * @buf: the #GstAudioRingBuffer to set the callback on |
| * @cb: (allow-none): the callback to set |
| * @user_data: user data passed to the callback |
| * @notify: function to be called when @user_data is no longer needed |
| * |
| * Sets the given callback function on the buffer. This function |
| * will be called every time a segment has been written to a device. |
| * |
| * MT safe. |
| * |
| * Since: 1.12 |
| */ |
| void |
| gst_audio_ring_buffer_set_callback_full (GstAudioRingBuffer * buf, |
| GstAudioRingBufferCallback cb, gpointer user_data, GDestroyNotify notify) |
| { |
| gpointer old_data = NULL; |
| GDestroyNotify old_notify; |
| |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| GST_OBJECT_LOCK (buf); |
| old_notify = buf->cb_data_notify; |
| old_data = buf->cb_data; |
| |
| buf->callback = cb; |
| buf->cb_data = user_data; |
| buf->cb_data_notify = notify; |
| GST_OBJECT_UNLOCK (buf); |
| |
| if (old_notify) { |
| old_notify (old_data); |
| } |
| } |
| |
| |
| /** |
| * gst_audio_ring_buffer_open_device: |
| * @buf: the #GstAudioRingBuffer |
| * |
| * Open the audio device associated with the ring buffer. Does not perform any |
| * setup on the device. You must open the device before acquiring the ring |
| * buffer. |
| * |
| * Returns: TRUE if the device could be opened, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_open_device (GstAudioRingBuffer * buf) |
| { |
| gboolean res = TRUE; |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "opening device"); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (buf->open)) |
| goto was_opened; |
| |
| buf->open = TRUE; |
| |
| /* if this fails, something is wrong in this file */ |
| g_assert (!buf->acquired); |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->open_device)) |
| res = rclass->open_device (buf); |
| |
| if (G_UNLIKELY (!res)) |
| goto open_failed; |
| |
| GST_DEBUG_OBJECT (buf, "opened device"); |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| was_opened: |
| { |
| GST_DEBUG_OBJECT (buf, "Device for ring buffer already open"); |
| g_warning ("Device for ring buffer %p already open, fix your code", buf); |
| res = TRUE; |
| goto done; |
| } |
| open_failed: |
| { |
| buf->open = FALSE; |
| GST_DEBUG_OBJECT (buf, "failed opening device"); |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_close_device: |
| * @buf: the #GstAudioRingBuffer |
| * |
| * Close the audio device associated with the ring buffer. The ring buffer |
| * should already have been released via gst_audio_ring_buffer_release(). |
| * |
| * Returns: TRUE if the device could be closed, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_close_device (GstAudioRingBuffer * buf) |
| { |
| gboolean res = TRUE; |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "closing device"); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (!buf->open)) |
| goto was_closed; |
| |
| if (G_UNLIKELY (buf->acquired)) |
| goto was_acquired; |
| |
| buf->open = FALSE; |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->close_device)) |
| res = rclass->close_device (buf); |
| |
| if (G_UNLIKELY (!res)) |
| goto close_error; |
| |
| GST_DEBUG_OBJECT (buf, "closed device"); |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| was_closed: |
| { |
| GST_DEBUG_OBJECT (buf, "Device for ring buffer already closed"); |
| g_warning ("Device for ring buffer %p already closed, fix your code", buf); |
| res = TRUE; |
| goto done; |
| } |
| was_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "Resources for ring buffer still acquired"); |
| g_critical ("Resources for ring buffer %p still acquired", buf); |
| res = FALSE; |
| goto done; |
| } |
| close_error: |
| { |
| buf->open = TRUE; |
| GST_DEBUG_OBJECT (buf, "error closing device"); |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_device_is_open: |
| * @buf: the #GstAudioRingBuffer |
| * |
| * Checks the status of the device associated with the ring buffer. |
| * |
| * Returns: TRUE if the device was open, FALSE if it was closed. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_device_is_open (GstAudioRingBuffer * buf) |
| { |
| gboolean res = TRUE; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_OBJECT_LOCK (buf); |
| res = buf->open; |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_acquire: |
| * @buf: the #GstAudioRingBuffer to acquire |
| * @spec: the specs of the buffer |
| * |
| * Allocate the resources for the ringbuffer. This function fills |
| * in the data pointer of the ring buffer with a valid #GstBuffer |
| * to which samples can be written. |
| * |
| * Returns: TRUE if the device could be acquired, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_acquire (GstAudioRingBuffer * buf, |
| GstAudioRingBufferSpec * spec) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| gint segsize, bpf, i; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "acquiring device %p", buf); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (!buf->open)) |
| goto not_opened; |
| |
| if (G_UNLIKELY (buf->acquired)) |
| goto was_acquired; |
| |
| buf->acquired = TRUE; |
| buf->need_reorder = FALSE; |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->acquire)) |
| res = rclass->acquire (buf, spec); |
| |
| /* Only reorder for raw audio */ |
| buf->need_reorder = (buf->need_reorder |
| && buf->spec.type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW); |
| |
| if (G_UNLIKELY (!res)) |
| goto acquire_failed; |
| |
| GST_INFO_OBJECT (buf, "Allocating an array for %d timestamps", |
| spec->segtotal); |
| buf->timestamps = g_slice_alloc0 (sizeof (GstClockTime) * spec->segtotal); |
| /* initialize array with invalid timestamps */ |
| for (i = 0; i < spec->segtotal; i++) { |
| buf->timestamps[i] = GST_CLOCK_TIME_NONE; |
| } |
| |
| if (G_UNLIKELY ((bpf = buf->spec.info.bpf) == 0)) |
| goto invalid_bpf; |
| |
| /* if the seglatency was overwritten with something else than -1, use it, else |
| * assume segtotal as the latency */ |
| if (buf->spec.seglatency == -1) |
| buf->spec.seglatency = buf->spec.segtotal; |
| |
| segsize = buf->spec.segsize; |
| |
| buf->samples_per_seg = segsize / bpf; |
| |
| /* create an empty segment */ |
| g_free (buf->empty_seg); |
| buf->empty_seg = g_malloc (segsize); |
| |
| if (buf->spec.type == GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW) { |
| gst_audio_format_fill_silence (buf->spec.info.finfo, buf->empty_seg, |
| segsize); |
| } else { |
| /* FIXME, non-raw formats get 0 as the empty sample */ |
| memset (buf->empty_seg, 0, segsize); |
| } |
| GST_DEBUG_OBJECT (buf, "acquired device"); |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| not_opened: |
| { |
| GST_DEBUG_OBJECT (buf, "device not opened"); |
| g_critical ("Device for %p not opened", buf); |
| res = FALSE; |
| goto done; |
| } |
| was_acquired: |
| { |
| res = TRUE; |
| GST_DEBUG_OBJECT (buf, "device was acquired"); |
| goto done; |
| } |
| acquire_failed: |
| { |
| buf->acquired = FALSE; |
| GST_DEBUG_OBJECT (buf, "failed to acquire device"); |
| goto done; |
| } |
| invalid_bpf: |
| { |
| g_warning |
| ("invalid bytes_per_frame from acquire ringbuffer %p, fix the element", |
| buf); |
| buf->acquired = FALSE; |
| res = FALSE; |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_release: |
| * @buf: the #GstAudioRingBuffer to release |
| * |
| * Free the resources of the ringbuffer. |
| * |
| * Returns: TRUE if the device could be released, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_release (GstAudioRingBuffer * buf) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "releasing device"); |
| |
| gst_audio_ring_buffer_stop (buf); |
| |
| GST_OBJECT_LOCK (buf); |
| |
| if (G_LIKELY (buf->timestamps)) { |
| GST_INFO_OBJECT (buf, "Freeing timestamp buffer, %d entries", |
| buf->spec.segtotal); |
| g_slice_free1 (sizeof (GstClockTime) * buf->spec.segtotal, buf->timestamps); |
| buf->timestamps = NULL; |
| } |
| |
| if (G_UNLIKELY (!buf->acquired)) |
| goto was_released; |
| |
| buf->acquired = FALSE; |
| |
| /* if this fails, something is wrong in this file */ |
| g_assert (buf->open); |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->release)) |
| res = rclass->release (buf); |
| |
| /* signal any waiters */ |
| GST_DEBUG_OBJECT (buf, "signal waiter"); |
| GST_AUDIO_RING_BUFFER_SIGNAL (buf); |
| |
| if (G_UNLIKELY (!res)) |
| goto release_failed; |
| |
| g_atomic_int_set (&buf->segdone, 0); |
| buf->segbase = 0; |
| g_free (buf->empty_seg); |
| buf->empty_seg = NULL; |
| gst_caps_replace (&buf->spec.caps, NULL); |
| gst_audio_info_init (&buf->spec.info); |
| GST_DEBUG_OBJECT (buf, "released device"); |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| was_released: |
| { |
| res = TRUE; |
| GST_DEBUG_OBJECT (buf, "device was released"); |
| goto done; |
| } |
| release_failed: |
| { |
| buf->acquired = TRUE; |
| GST_DEBUG_OBJECT (buf, "failed to release device"); |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_is_acquired: |
| * @buf: the #GstAudioRingBuffer to check |
| * |
| * Check if the ringbuffer is acquired and ready to use. |
| * |
| * Returns: TRUE if the ringbuffer is acquired, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_is_acquired (GstAudioRingBuffer * buf) |
| { |
| gboolean res; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_OBJECT_LOCK (buf); |
| res = buf->acquired; |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_activate: |
| * @buf: the #GstAudioRingBuffer to activate |
| * @active: the new mode |
| * |
| * Activate @buf to start or stop pulling data. |
| * |
| * MT safe. |
| * |
| * Returns: TRUE if the device could be activated in the requested mode, |
| * FALSE on error. |
| */ |
| gboolean |
| gst_audio_ring_buffer_activate (GstAudioRingBuffer * buf, gboolean active) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "activate device"); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (active && !buf->acquired)) |
| goto not_acquired; |
| |
| if (G_UNLIKELY (buf->active == active)) |
| goto was_active; |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| /* if there is no activate function we assume it was started/released |
| * in the acquire method */ |
| if (G_LIKELY (rclass->activate)) |
| res = rclass->activate (buf, active); |
| else |
| res = TRUE; |
| |
| if (G_UNLIKELY (!res)) |
| goto activate_failed; |
| |
| buf->active = active; |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| not_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "device not acquired"); |
| g_critical ("Device for %p not acquired", buf); |
| res = FALSE; |
| goto done; |
| } |
| was_active: |
| { |
| res = TRUE; |
| GST_DEBUG_OBJECT (buf, "device was active in mode %d", active); |
| goto done; |
| } |
| activate_failed: |
| { |
| GST_DEBUG_OBJECT (buf, "failed to activate device"); |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_is_active: |
| * @buf: the #GstAudioRingBuffer |
| * |
| * Check if @buf is activated. |
| * |
| * MT safe. |
| * |
| * Returns: TRUE if the device is active. |
| */ |
| gboolean |
| gst_audio_ring_buffer_is_active (GstAudioRingBuffer * buf) |
| { |
| gboolean res; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_OBJECT_LOCK (buf); |
| res = buf->active; |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| |
| /** |
| * gst_audio_ring_buffer_set_flushing: |
| * @buf: the #GstAudioRingBuffer to flush |
| * @flushing: the new mode |
| * |
| * Set the ringbuffer to flushing mode or normal mode. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_set_flushing (GstAudioRingBuffer * buf, gboolean flushing) |
| { |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| GST_OBJECT_LOCK (buf); |
| buf->flushing = flushing; |
| |
| if (flushing) { |
| gst_audio_ring_buffer_pause_unlocked (buf); |
| } else { |
| gst_audio_ring_buffer_clear_all (buf); |
| } |
| GST_OBJECT_UNLOCK (buf); |
| } |
| |
| /** |
| * gst_audio_ring_buffer_is_flushing: |
| * @buf: the #GstAudioRingBuffer |
| * |
| * Check if @buf is flushing. |
| * |
| * MT safe. |
| * |
| * Returns: TRUE if the device is flushing. |
| */ |
| gboolean |
| gst_audio_ring_buffer_is_flushing (GstAudioRingBuffer * buf) |
| { |
| gboolean res; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), TRUE); |
| |
| GST_OBJECT_LOCK (buf); |
| res = buf->flushing; |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_start: |
| * @buf: the #GstAudioRingBuffer to start |
| * |
| * Start processing samples from the ringbuffer. |
| * |
| * Returns: TRUE if the device could be started, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_start (GstAudioRingBuffer * buf) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| gboolean resume = FALSE; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "starting ringbuffer"); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (buf->flushing)) |
| goto flushing; |
| |
| if (G_UNLIKELY (!buf->acquired)) |
| goto not_acquired; |
| |
| if (G_UNLIKELY (!g_atomic_int_get (&buf->may_start))) |
| goto may_not_start; |
| |
| /* if stopped, set to started */ |
| res = g_atomic_int_compare_and_exchange (&buf->state, |
| GST_AUDIO_RING_BUFFER_STATE_STOPPED, GST_AUDIO_RING_BUFFER_STATE_STARTED); |
| |
| if (!res) { |
| GST_DEBUG_OBJECT (buf, "was not stopped, try paused"); |
| /* was not stopped, try from paused */ |
| res = g_atomic_int_compare_and_exchange (&buf->state, |
| GST_AUDIO_RING_BUFFER_STATE_PAUSED, |
| GST_AUDIO_RING_BUFFER_STATE_STARTED); |
| if (!res) { |
| /* was not paused either, must be started then */ |
| res = TRUE; |
| GST_DEBUG_OBJECT (buf, "was not paused, must have been started"); |
| goto done; |
| } |
| resume = TRUE; |
| GST_DEBUG_OBJECT (buf, "resuming"); |
| } |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (resume) { |
| if (G_LIKELY (rclass->resume)) |
| res = rclass->resume (buf); |
| } else { |
| if (G_LIKELY (rclass->start)) |
| res = rclass->start (buf); |
| } |
| |
| if (G_UNLIKELY (!res)) { |
| buf->state = GST_AUDIO_RING_BUFFER_STATE_PAUSED; |
| GST_DEBUG_OBJECT (buf, "failed to start"); |
| } else { |
| GST_DEBUG_OBJECT (buf, "started"); |
| } |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| flushing: |
| { |
| GST_DEBUG_OBJECT (buf, "we are flushing"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| not_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "we are not acquired"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| may_not_start: |
| { |
| GST_DEBUG_OBJECT (buf, "we may not start"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| } |
| |
| static gboolean |
| gst_audio_ring_buffer_pause_unlocked (GstAudioRingBuffer * buf) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| |
| GST_DEBUG_OBJECT (buf, "pausing ringbuffer"); |
| |
| /* if started, set to paused */ |
| res = g_atomic_int_compare_and_exchange (&buf->state, |
| GST_AUDIO_RING_BUFFER_STATE_STARTED, GST_AUDIO_RING_BUFFER_STATE_PAUSED); |
| |
| if (!res) |
| goto not_started; |
| |
| /* signal any waiters */ |
| GST_DEBUG_OBJECT (buf, "signal waiter"); |
| GST_AUDIO_RING_BUFFER_SIGNAL (buf); |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->pause)) |
| res = rclass->pause (buf); |
| |
| if (G_UNLIKELY (!res)) { |
| buf->state = GST_AUDIO_RING_BUFFER_STATE_STARTED; |
| GST_DEBUG_OBJECT (buf, "failed to pause"); |
| } else { |
| GST_DEBUG_OBJECT (buf, "paused"); |
| } |
| |
| return res; |
| |
| not_started: |
| { |
| /* was not started */ |
| GST_DEBUG_OBJECT (buf, "was not started"); |
| return TRUE; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_pause: |
| * @buf: the #GstAudioRingBuffer to pause |
| * |
| * Pause processing samples from the ringbuffer. |
| * |
| * Returns: TRUE if the device could be paused, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_pause (GstAudioRingBuffer * buf) |
| { |
| gboolean res = FALSE; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (buf->flushing)) |
| goto flushing; |
| |
| if (G_UNLIKELY (!buf->acquired)) |
| goto not_acquired; |
| |
| res = gst_audio_ring_buffer_pause_unlocked (buf); |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| |
| /* ERRORS */ |
| flushing: |
| { |
| GST_DEBUG_OBJECT (buf, "we are flushing"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| not_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "not acquired"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_stop: |
| * @buf: the #GstAudioRingBuffer to stop |
| * |
| * Stop processing samples from the ringbuffer. |
| * |
| * Returns: TRUE if the device could be stopped, FALSE on error. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_stop (GstAudioRingBuffer * buf) |
| { |
| gboolean res = FALSE; |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| GST_DEBUG_OBJECT (buf, "stopping"); |
| |
| GST_OBJECT_LOCK (buf); |
| |
| /* if started, set to stopped */ |
| res = g_atomic_int_compare_and_exchange (&buf->state, |
| GST_AUDIO_RING_BUFFER_STATE_STARTED, GST_AUDIO_RING_BUFFER_STATE_STOPPED); |
| |
| if (!res) { |
| GST_DEBUG_OBJECT (buf, "was not started, try paused"); |
| /* was not started, try from paused */ |
| res = g_atomic_int_compare_and_exchange (&buf->state, |
| GST_AUDIO_RING_BUFFER_STATE_PAUSED, |
| GST_AUDIO_RING_BUFFER_STATE_STOPPED); |
| if (!res) { |
| /* was not paused either, must have been stopped then */ |
| res = TRUE; |
| GST_DEBUG_OBJECT (buf, "was not paused, must have been stopped"); |
| goto done; |
| } |
| } |
| |
| /* signal any waiters */ |
| GST_DEBUG_OBJECT (buf, "signal waiter"); |
| GST_AUDIO_RING_BUFFER_SIGNAL (buf); |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->stop)) |
| res = rclass->stop (buf); |
| |
| if (G_UNLIKELY (!res)) { |
| buf->state = GST_AUDIO_RING_BUFFER_STATE_STARTED; |
| GST_DEBUG_OBJECT (buf, "failed to stop"); |
| } else { |
| GST_DEBUG_OBJECT (buf, "stopped"); |
| } |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_delay: |
| * @buf: the #GstAudioRingBuffer to query |
| * |
| * Get the number of samples queued in the audio device. This is |
| * usually less than the segment size but can be bigger when the |
| * implementation uses another internal buffer between the audio |
| * device. |
| * |
| * For playback ringbuffers this is the amount of samples transfered from the |
| * ringbuffer to the device but still not played. |
| * |
| * For capture ringbuffers this is the amount of samples in the device that are |
| * not yet transfered to the ringbuffer. |
| * |
| * Returns: The number of samples queued in the audio device. |
| * |
| * MT safe. |
| */ |
| guint |
| gst_audio_ring_buffer_delay (GstAudioRingBuffer * buf) |
| { |
| GstAudioRingBufferClass *rclass; |
| guint res; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), 0); |
| |
| /* buffer must be acquired */ |
| if (G_UNLIKELY (!gst_audio_ring_buffer_is_acquired (buf))) |
| goto not_acquired; |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| if (G_LIKELY (rclass->delay)) |
| res = rclass->delay (buf); |
| else |
| res = 0; |
| |
| return res; |
| |
| not_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "not acquired"); |
| return 0; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_samples_done: |
| * @buf: the #GstAudioRingBuffer to query |
| * |
| * Get the number of samples that were processed by the ringbuffer |
| * since it was last started. This does not include the number of samples not |
| * yet processed (see gst_audio_ring_buffer_delay()). |
| * |
| * Returns: The number of samples processed by the ringbuffer. |
| * |
| * MT safe. |
| */ |
| guint64 |
| gst_audio_ring_buffer_samples_done (GstAudioRingBuffer * buf) |
| { |
| gint segdone; |
| guint64 samples; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), 0); |
| |
| /* get the amount of segments we processed */ |
| segdone = g_atomic_int_get (&buf->segdone); |
| |
| /* convert to samples */ |
| samples = ((guint64) segdone) * buf->samples_per_seg; |
| |
| return samples; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_set_sample: |
| * @buf: the #GstAudioRingBuffer to use |
| * @sample: the sample number to set |
| * |
| * Make sure that the next sample written to the device is |
| * accounted for as being the @sample sample written to the |
| * device. This value will be used in reporting the current |
| * sample position of the ringbuffer. |
| * |
| * This function will also clear the buffer with silence. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_set_sample (GstAudioRingBuffer * buf, guint64 sample) |
| { |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| if (sample == -1) |
| sample = 0; |
| |
| if (G_UNLIKELY (buf->samples_per_seg == 0)) |
| return; |
| |
| /* FIXME, we assume the ringbuffer can restart at a random |
| * position, round down to the beginning and keep track of |
| * offset when calculating the processed samples. */ |
| buf->segbase = buf->segdone - sample / buf->samples_per_seg; |
| |
| gst_audio_ring_buffer_clear_all (buf); |
| |
| GST_DEBUG_OBJECT (buf, "set sample to %" G_GUINT64_FORMAT ", segbase %d", |
| sample, buf->segbase); |
| } |
| |
| static void |
| default_clear_all (GstAudioRingBuffer * buf) |
| { |
| gint i; |
| |
| /* not fatal, we just are not negotiated yet */ |
| if (G_UNLIKELY (buf->spec.segtotal <= 0)) |
| return; |
| |
| GST_DEBUG_OBJECT (buf, "clear all segments"); |
| |
| for (i = 0; i < buf->spec.segtotal; i++) { |
| gst_audio_ring_buffer_clear (buf, i); |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_clear_all: |
| * @buf: the #GstAudioRingBuffer to clear |
| * |
| * Fill the ringbuffer with silence. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_clear_all (GstAudioRingBuffer * buf) |
| { |
| GstAudioRingBufferClass *rclass; |
| |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| |
| if (G_LIKELY (rclass->clear_all)) |
| rclass->clear_all (buf); |
| } |
| |
| |
| static gboolean |
| wait_segment (GstAudioRingBuffer * buf) |
| { |
| gint segments; |
| gboolean wait = TRUE; |
| |
| /* buffer must be started now or we deadlock since nobody is reading */ |
| if (G_UNLIKELY (g_atomic_int_get (&buf->state) != |
| GST_AUDIO_RING_BUFFER_STATE_STARTED)) { |
| /* see if we are allowed to start it */ |
| if (G_UNLIKELY (!g_atomic_int_get (&buf->may_start))) |
| goto no_start; |
| |
| GST_DEBUG_OBJECT (buf, "start!"); |
| segments = g_atomic_int_get (&buf->segdone); |
| gst_audio_ring_buffer_start (buf); |
| |
| /* After starting, the writer may have wrote segments already and then we |
| * don't need to wait anymore */ |
| if (G_LIKELY (g_atomic_int_get (&buf->segdone) != segments)) |
| wait = FALSE; |
| } |
| |
| /* take lock first, then update our waiting flag */ |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (buf->flushing)) |
| goto flushing; |
| |
| if (G_UNLIKELY (g_atomic_int_get (&buf->state) != |
| GST_AUDIO_RING_BUFFER_STATE_STARTED)) |
| goto not_started; |
| |
| if (G_LIKELY (wait)) { |
| if (g_atomic_int_compare_and_exchange (&buf->waiting, 0, 1)) { |
| GST_DEBUG_OBJECT (buf, "waiting.."); |
| GST_AUDIO_RING_BUFFER_WAIT (buf); |
| |
| if (G_UNLIKELY (buf->flushing)) |
| goto flushing; |
| |
| if (G_UNLIKELY (g_atomic_int_get (&buf->state) != |
| GST_AUDIO_RING_BUFFER_STATE_STARTED)) |
| goto not_started; |
| } |
| } |
| GST_OBJECT_UNLOCK (buf); |
| |
| return TRUE; |
| |
| /* ERROR */ |
| not_started: |
| { |
| g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0); |
| GST_DEBUG_OBJECT (buf, "stopped processing"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| flushing: |
| { |
| g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0); |
| GST_DEBUG_OBJECT (buf, "flushing"); |
| GST_OBJECT_UNLOCK (buf); |
| return FALSE; |
| } |
| no_start: |
| { |
| GST_DEBUG_OBJECT (buf, "not allowed to start"); |
| return FALSE; |
| } |
| } |
| |
| |
| |
| #define REORDER_SAMPLE(d, s, l) \ |
| G_STMT_START { \ |
| gint i; \ |
| for (i = 0; i < channels; i++) { \ |
| memcpy (d + reorder_map[i] * bps, s + i * bps, bps); \ |
| } \ |
| } G_STMT_END |
| |
| #define REORDER_SAMPLES(d, s, len) \ |
| G_STMT_START { \ |
| gint i, len_ = len / bpf; \ |
| guint8 *d_ = d, *s_ = s; \ |
| for (i = 0; i < len_; i++) { \ |
| REORDER_SAMPLE(d_, s_, bpf); \ |
| d_ += bpf; \ |
| s_ += bpf; \ |
| } \ |
| } G_STMT_END |
| |
| #define FWD_SAMPLES(s,se,d,de,F) \ |
| G_STMT_START { \ |
| /* no rate conversion */ \ |
| guint towrite = MIN (se + bpf - s, de - d); \ |
| /* simple copy */ \ |
| if (!skip) \ |
| F (d, s, towrite); \ |
| in_samples -= towrite / bpf; \ |
| out_samples -= towrite / bpf; \ |
| s += towrite; \ |
| GST_DEBUG ("copy %u bytes", towrite); \ |
| } G_STMT_END |
| |
| /* in_samples >= out_samples, rate > 1.0 */ |
| #define FWD_UP_SAMPLES(s,se,d,de,F) \ |
| G_STMT_START { \ |
| guint8 *sb = s, *db = d; \ |
| while (s <= se && d < de) { \ |
| if (!skip) \ |
| F (d, s, bpf); \ |
| s += bpf; \ |
| *accum += outr; \ |
| if ((*accum << 1) >= inr) { \ |
| *accum -= inr; \ |
| d += bpf; \ |
| } \ |
| } \ |
| in_samples -= (s - sb)/bpf; \ |
| out_samples -= (d - db)/bpf; \ |
| GST_DEBUG ("fwd_up end %d/%d",*accum,*toprocess); \ |
| } G_STMT_END |
| |
| /* out_samples > in_samples, for rates smaller than 1.0 */ |
| #define FWD_DOWN_SAMPLES(s,se,d,de,F) \ |
| G_STMT_START { \ |
| guint8 *sb = s, *db = d; \ |
| while (s <= se && d < de) { \ |
| if (!skip) \ |
| F (d, s, bpf); \ |
| d += bpf; \ |
| *accum += inr; \ |
| if ((*accum << 1) >= outr) { \ |
| *accum -= outr; \ |
| s += bpf; \ |
| } \ |
| } \ |
| in_samples -= (s - sb)/bpf; \ |
| out_samples -= (d - db)/bpf; \ |
| GST_DEBUG ("fwd_down end %d/%d",*accum,*toprocess); \ |
| } G_STMT_END |
| |
| #define REV_UP_SAMPLES(s,se,d,de,F) \ |
| G_STMT_START { \ |
| guint8 *sb = se, *db = d; \ |
| while (s <= se && d < de) { \ |
| if (!skip) \ |
| F (d, se, bpf); \ |
| se -= bpf; \ |
| *accum += outr; \ |
| while (d < de && (*accum << 1) >= inr) { \ |
| *accum -= inr; \ |
| d += bpf; \ |
| } \ |
| } \ |
| in_samples -= (sb - se)/bpf; \ |
| out_samples -= (d - db)/bpf; \ |
| GST_DEBUG ("rev_up end %d/%d",*accum,*toprocess); \ |
| } G_STMT_END |
| |
| #define REV_DOWN_SAMPLES(s,se,d,de,F) \ |
| G_STMT_START { \ |
| guint8 *sb = se, *db = d; \ |
| while (s <= se && d < de) { \ |
| if (!skip) \ |
| F (d, se, bpf); \ |
| d += bpf; \ |
| *accum += inr; \ |
| while (s <= se && (*accum << 1) >= outr) { \ |
| *accum -= outr; \ |
| se -= bpf; \ |
| } \ |
| } \ |
| in_samples -= (sb - se)/bpf; \ |
| out_samples -= (d - db)/bpf; \ |
| GST_DEBUG ("rev_down end %d/%d",*accum,*toprocess); \ |
| } G_STMT_END |
| |
| static guint |
| default_commit (GstAudioRingBuffer * buf, guint64 * sample, |
| guint8 * data, gint in_samples, gint out_samples, gint * accum) |
| { |
| gint segdone; |
| gint segsize, segtotal, channels, bps, bpf, sps; |
| guint8 *dest, *data_end; |
| gint writeseg, sampleoff; |
| gint *toprocess; |
| gint inr, outr; |
| gboolean reverse; |
| gboolean need_reorder; |
| |
| g_return_val_if_fail (buf->memory != NULL, -1); |
| g_return_val_if_fail (data != NULL, -1); |
| |
| need_reorder = buf->need_reorder; |
| |
| channels = buf->spec.info.channels; |
| dest = buf->memory; |
| segsize = buf->spec.segsize; |
| segtotal = buf->spec.segtotal; |
| bpf = buf->spec.info.bpf; |
| bps = bpf / channels; |
| sps = buf->samples_per_seg; |
| |
| reverse = out_samples < 0; |
| out_samples = ABS (out_samples); |
| |
| if (in_samples >= out_samples) |
| toprocess = &in_samples; |
| else |
| toprocess = &out_samples; |
| |
| inr = in_samples - 1; |
| outr = out_samples - 1; |
| |
| /* data_end points to the last sample we have to write, not past it. This is |
| * needed to properly handle reverse playback: it points to the last sample. */ |
| data_end = data + (bpf * inr); |
| |
| /* figure out the segment and the offset inside the segment where |
| * the first sample should be written. */ |
| writeseg = *sample / sps; |
| sampleoff = (*sample % sps) * bpf; |
| |
| GST_DEBUG_OBJECT (buf, "write %d : %d", in_samples, out_samples); |
| |
| /* write out all samples */ |
| while (*toprocess > 0) { |
| gint avail; |
| guint8 *d, *d_end; |
| gint ws; |
| gboolean skip; |
| |
| while (TRUE) { |
| gint diff; |
| |
| /* get the currently processed segment */ |
| segdone = g_atomic_int_get (&buf->segdone) - buf->segbase; |
| |
| /* see how far away it is from the write segment */ |
| diff = writeseg - segdone; |
| |
| GST_DEBUG_OBJECT (buf, |
| "pointer at %d, write to %d-%d, diff %d, segtotal %d, segsize %d, base %d", |
| segdone, writeseg, sampleoff, diff, segtotal, segsize, buf->segbase); |
| |
| /* segment too far ahead, writer too slow, we need to drop, hopefully UNLIKELY */ |
| if (G_UNLIKELY (diff < 0)) { |
| /* we need to drop one segment at a time, pretend we wrote a segment. */ |
| skip = TRUE; |
| break; |
| } |
| |
| /* write segment is within writable range, we can break the loop and |
| * start writing the data. */ |
| if (diff < segtotal) { |
| skip = FALSE; |
| break; |
| } |
| |
| /* else we need to wait for the segment to become writable. */ |
| if (!wait_segment (buf)) |
| goto not_started; |
| } |
| |
| /* we can write now */ |
| ws = writeseg % segtotal; |
| avail = MIN (segsize - sampleoff, bpf * out_samples); |
| |
| d = dest + (ws * segsize) + sampleoff; |
| d_end = d + avail; |
| *sample += avail / bpf; |
| |
| GST_DEBUG_OBJECT (buf, "write @%p seg %d, sps %d, off %d, avail %d", |
| dest + ws * segsize, ws, sps, sampleoff, avail); |
| |
| if (need_reorder) { |
| gint *reorder_map = buf->channel_reorder_map; |
| |
| if (G_LIKELY (inr == outr && !reverse)) { |
| /* no rate conversion, simply copy samples */ |
| FWD_SAMPLES (data, data_end, d, d_end, REORDER_SAMPLES); |
| } else if (!reverse) { |
| if (inr >= outr) |
| /* forward speed up */ |
| FWD_UP_SAMPLES (data, data_end, d, d_end, REORDER_SAMPLE); |
| else |
| /* forward slow down */ |
| FWD_DOWN_SAMPLES (data, data_end, d, d_end, REORDER_SAMPLE); |
| } else { |
| if (inr >= outr) |
| /* reverse speed up */ |
| REV_UP_SAMPLES (data, data_end, d, d_end, REORDER_SAMPLE); |
| else |
| /* reverse slow down */ |
| REV_DOWN_SAMPLES (data, data_end, d, d_end, REORDER_SAMPLE); |
| } |
| } else { |
| if (G_LIKELY (inr == outr && !reverse)) { |
| /* no rate conversion, simply copy samples */ |
| FWD_SAMPLES (data, data_end, d, d_end, memcpy); |
| } else if (!reverse) { |
| if (inr >= outr) |
| /* forward speed up */ |
| FWD_UP_SAMPLES (data, data_end, d, d_end, memcpy); |
| else |
| /* forward slow down */ |
| FWD_DOWN_SAMPLES (data, data_end, d, d_end, memcpy); |
| } else { |
| if (inr >= outr) |
| /* reverse speed up */ |
| REV_UP_SAMPLES (data, data_end, d, d_end, memcpy); |
| else |
| /* reverse slow down */ |
| REV_DOWN_SAMPLES (data, data_end, d, d_end, memcpy); |
| } |
| } |
| |
| /* for the next iteration we write to the next segment at the beginning. */ |
| writeseg++; |
| sampleoff = 0; |
| } |
| /* we consumed all samples here */ |
| data = data_end + bpf; |
| |
| done: |
| return inr - ((data_end - data) / bpf); |
| |
| /* ERRORS */ |
| not_started: |
| { |
| GST_DEBUG_OBJECT (buf, "stopped processing"); |
| goto done; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_commit: |
| * @buf: the #GstAudioRingBuffer to commit |
| * @sample: the sample position of the data |
| * @data: (array length=in_samples): the data to commit |
| * @in_samples: the number of samples in the data to commit |
| * @out_samples: the number of samples to write to the ringbuffer |
| * @accum: (inout): accumulator for rate conversion. |
| * |
| * Commit @in_samples samples pointed to by @data to the ringbuffer @buf. |
| * |
| * @in_samples and @out_samples define the rate conversion to perform on the |
| * samples in @data. For negative rates, @out_samples must be negative and |
| * @in_samples positive. |
| * |
| * When @out_samples is positive, the first sample will be written at position @sample |
| * in the ringbuffer. When @out_samples is negative, the last sample will be written to |
| * @sample in reverse order. |
| * |
| * @out_samples does not need to be a multiple of the segment size of the ringbuffer |
| * although it is recommended for optimal performance. |
| * |
| * @accum will hold a temporary accumulator used in rate conversion and should be |
| * set to 0 when this function is first called. In case the commit operation is |
| * interrupted, one can resume the processing by passing the previously returned |
| * @accum value back to this function. |
| * |
| * MT safe. |
| * |
| * Returns: The number of samples written to the ringbuffer or -1 on error. The |
| * number of samples written can be less than @out_samples when @buf was interrupted |
| * with a flush or stop. |
| */ |
| guint |
| gst_audio_ring_buffer_commit (GstAudioRingBuffer * buf, guint64 * sample, |
| guint8 * data, gint in_samples, gint out_samples, gint * accum) |
| { |
| GstAudioRingBufferClass *rclass; |
| guint res = -1; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), -1); |
| |
| if (G_UNLIKELY (in_samples == 0 || out_samples == 0)) |
| return in_samples; |
| |
| rclass = GST_AUDIO_RING_BUFFER_GET_CLASS (buf); |
| |
| if (G_LIKELY (rclass->commit)) |
| res = rclass->commit (buf, sample, data, in_samples, out_samples, accum); |
| |
| return res; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_read: |
| * @buf: the #GstAudioRingBuffer to read from |
| * @sample: the sample position of the data |
| * @data: (array length=len): where the data should be read |
| * @len: the number of samples in data to read |
| * @timestamp: (out): where the timestamp is returned |
| * |
| * Read @len samples from the ringbuffer into the memory pointed |
| * to by @data. |
| * The first sample should be read from position @sample in |
| * the ringbuffer. |
| * |
| * @len should not be a multiple of the segment size of the ringbuffer |
| * although it is recommended. |
| * |
| * @timestamp will return the timestamp associated with the data returned. |
| * |
| * Returns: The number of samples read from the ringbuffer or -1 on |
| * error. |
| * |
| * MT safe. |
| */ |
| guint |
| gst_audio_ring_buffer_read (GstAudioRingBuffer * buf, guint64 sample, |
| guint8 * data, guint len, GstClockTime * timestamp) |
| { |
| gint segdone; |
| gint segsize, segtotal, channels, bps, bpf, sps, readseg = 0; |
| guint8 *dest; |
| guint to_read; |
| gboolean need_reorder; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), -1); |
| g_return_val_if_fail (buf->memory != NULL, -1); |
| g_return_val_if_fail (data != NULL, -1); |
| |
| need_reorder = buf->need_reorder; |
| dest = buf->memory; |
| segsize = buf->spec.segsize; |
| segtotal = buf->spec.segtotal; |
| channels = buf->spec.info.channels; |
| bpf = buf->spec.info.bpf; |
| bps = bpf / channels; |
| sps = buf->samples_per_seg; |
| |
| to_read = len; |
| /* read enough samples */ |
| while (to_read > 0) { |
| gint sampleslen; |
| gint sampleoff; |
| |
| /* figure out the segment and the offset inside the segment where |
| * the sample should be read from. */ |
| readseg = sample / sps; |
| sampleoff = (sample % sps); |
| |
| while (TRUE) { |
| gint diff; |
| |
| /* get the currently processed segment */ |
| segdone = g_atomic_int_get (&buf->segdone) - buf->segbase; |
| |
| /* see how far away it is from the read segment, normally segdone (where |
| * the hardware is writing) is bigger than readseg (where software is |
| * reading) */ |
| diff = segdone - readseg; |
| |
| GST_DEBUG_OBJECT |
| (buf, "pointer at %d, sample %" G_GUINT64_FORMAT |
| ", read from %d-%d, to_read %d, diff %d, segtotal %d, segsize %d", |
| segdone, sample, readseg, sampleoff, to_read, diff, segtotal, |
| segsize); |
| |
| /* segment too far ahead, reader too slow */ |
| if (G_UNLIKELY (diff >= segtotal)) { |
| /* pretend we read an empty segment. */ |
| sampleslen = MIN (sps, to_read); |
| memcpy (data, buf->empty_seg, sampleslen * bpf); |
| goto next; |
| } |
| |
| /* read segment is within readable range, we can break the loop and |
| * start reading the data. */ |
| if (diff > 0) |
| break; |
| |
| /* else we need to wait for the segment to become readable. */ |
| if (!wait_segment (buf)) |
| goto not_started; |
| } |
| |
| /* we can read now */ |
| readseg = readseg % segtotal; |
| sampleslen = MIN (sps - sampleoff, to_read); |
| |
| GST_DEBUG_OBJECT (buf, "read @%p seg %d, off %d, sampleslen %d", |
| dest + readseg * segsize, readseg, sampleoff, sampleslen); |
| |
| if (need_reorder) { |
| guint8 *ptr = dest + (readseg * segsize) + (sampleoff * bpf); |
| gint i, j; |
| gint *reorder_map = buf->channel_reorder_map; |
| |
| /* Reorder from device order to GStreamer order */ |
| for (i = 0; i < sampleslen; i++) { |
| for (j = 0; j < channels; j++) { |
| memcpy (data + reorder_map[j] * bps, ptr + j * bps, bps); |
| } |
| ptr += bpf; |
| } |
| } else { |
| memcpy (data, dest + (readseg * segsize) + (sampleoff * bpf), |
| (sampleslen * bpf)); |
| } |
| |
| next: |
| to_read -= sampleslen; |
| sample += sampleslen; |
| data += sampleslen * bpf; |
| } |
| |
| if (buf->timestamps && timestamp) { |
| *timestamp = buf->timestamps[readseg % segtotal]; |
| GST_DEBUG_OBJECT (buf, "Retrieved timestamp %" GST_TIME_FORMAT |
| " @ %d", GST_TIME_ARGS (*timestamp), readseg % segtotal); |
| } |
| |
| return len - to_read; |
| |
| /* ERRORS */ |
| not_started: |
| { |
| GST_DEBUG_OBJECT (buf, "stopped processing"); |
| return len - to_read; |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_prepare_read: |
| * @buf: the #GstAudioRingBuffer to read from |
| * @segment: (out): the segment to read |
| * @readptr: (out) (array length=len): |
| * the pointer to the memory where samples can be read |
| * @len: (out): the number of bytes to read |
| * |
| * Returns a pointer to memory where the data from segment @segment |
| * can be found. This function is mostly used by subclasses. |
| * |
| * Returns: FALSE if the buffer is not started. |
| * |
| * MT safe. |
| */ |
| gboolean |
| gst_audio_ring_buffer_prepare_read (GstAudioRingBuffer * buf, gint * segment, |
| guint8 ** readptr, gint * len) |
| { |
| guint8 *data; |
| gint segdone; |
| |
| g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), FALSE); |
| |
| if (buf->callback == NULL) { |
| /* push mode, fail when nothing is started */ |
| if (g_atomic_int_get (&buf->state) != GST_AUDIO_RING_BUFFER_STATE_STARTED) |
| return FALSE; |
| } |
| |
| g_return_val_if_fail (buf->memory != NULL, FALSE); |
| g_return_val_if_fail (segment != NULL, FALSE); |
| g_return_val_if_fail (readptr != NULL, FALSE); |
| g_return_val_if_fail (len != NULL, FALSE); |
| |
| data = buf->memory; |
| |
| /* get the position of the pointer */ |
| segdone = g_atomic_int_get (&buf->segdone); |
| |
| *segment = segdone % buf->spec.segtotal; |
| *len = buf->spec.segsize; |
| *readptr = data + *segment * *len; |
| |
| GST_LOG_OBJECT (buf, "prepare read from segment %d (real %d) @%p", |
| *segment, segdone, *readptr); |
| |
| /* callback to fill the memory with data, for pull based |
| * scheduling. */ |
| if (buf->callback) |
| buf->callback (buf, *readptr, *len, buf->cb_data); |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_advance: |
| * @buf: the #GstAudioRingBuffer to advance |
| * @advance: the number of segments written |
| * |
| * Subclasses should call this function to notify the fact that |
| * @advance segments are now processed by the device. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_advance (GstAudioRingBuffer * buf, guint advance) |
| { |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| /* update counter */ |
| g_atomic_int_add (&buf->segdone, advance); |
| |
| /* the lock is already taken when the waiting flag is set, |
| * we grab the lock as well to make sure the waiter is actually |
| * waiting for the signal */ |
| if (g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0)) { |
| GST_OBJECT_LOCK (buf); |
| GST_DEBUG_OBJECT (buf, "signal waiter"); |
| GST_AUDIO_RING_BUFFER_SIGNAL (buf); |
| GST_OBJECT_UNLOCK (buf); |
| } |
| } |
| |
| /** |
| * gst_audio_ring_buffer_clear: |
| * @buf: the #GstAudioRingBuffer to clear |
| * @segment: the segment to clear |
| * |
| * Clear the given segment of the buffer with silence samples. |
| * This function is used by subclasses. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_clear (GstAudioRingBuffer * buf, gint segment) |
| { |
| guint8 *data; |
| |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| /* no data means it's already cleared */ |
| if (G_UNLIKELY (buf->memory == NULL)) |
| return; |
| |
| /* no empty_seg means it's not opened */ |
| if (G_UNLIKELY (buf->empty_seg == NULL)) |
| return; |
| |
| segment %= buf->spec.segtotal; |
| |
| data = buf->memory; |
| data += segment * buf->spec.segsize; |
| |
| GST_LOG_OBJECT (buf, "clear segment %d @%p", segment, data); |
| |
| memcpy (data, buf->empty_seg, buf->spec.segsize); |
| } |
| |
| /** |
| * gst_audio_ring_buffer_may_start: |
| * @buf: the #GstAudioRingBuffer |
| * @allowed: the new value |
| * |
| * Tell the ringbuffer that it is allowed to start playback when |
| * the ringbuffer is filled with samples. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_may_start (GstAudioRingBuffer * buf, gboolean allowed) |
| { |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| GST_LOG_OBJECT (buf, "may start: %d", allowed); |
| g_atomic_int_set (&buf->may_start, allowed); |
| } |
| |
| /* GST_AUDIO_CHANNEL_POSITION_NONE is used for position-less |
| * mutually exclusive channels. In this case we should not attempt |
| * to do any reordering. |
| */ |
| static gboolean |
| position_less_channels (const GstAudioChannelPosition * pos, guint channels) |
| { |
| guint i; |
| |
| for (i = 0; i < channels; i++) { |
| if (pos[i] != GST_AUDIO_CHANNEL_POSITION_NONE) |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /** |
| * gst_audio_ring_buffer_set_channel_positions: |
| * @buf: the #GstAudioRingBuffer |
| * @position: (array): the device channel positions |
| * |
| * Tell the ringbuffer about the device's channel positions. This must |
| * be called in when the ringbuffer is acquired. |
| */ |
| void |
| gst_audio_ring_buffer_set_channel_positions (GstAudioRingBuffer * buf, |
| const GstAudioChannelPosition * position) |
| { |
| const GstAudioChannelPosition *to; |
| gint channels; |
| gint i; |
| |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| g_return_if_fail (buf->acquired); |
| |
| channels = buf->spec.info.channels; |
| to = buf->spec.info.position; |
| |
| if (memcmp (position, to, channels * sizeof (to[0])) == 0) |
| return; |
| |
| if (position_less_channels (position, channels)) { |
| GST_LOG_OBJECT (buf, "position-less channels, no need to reorder"); |
| return; |
| } |
| |
| buf->need_reorder = FALSE; |
| if (!gst_audio_get_channel_reorder_map (channels, position, to, |
| buf->channel_reorder_map)) |
| g_return_if_reached (); |
| |
| for (i = 0; i < channels; i++) { |
| if (buf->channel_reorder_map[i] != i) { |
| #ifndef GST_DISABLE_GST_DEBUG |
| { |
| gchar *tmp1, *tmp2; |
| |
| tmp1 = gst_audio_channel_positions_to_string (position, channels); |
| tmp2 = gst_audio_channel_positions_to_string (to, channels); |
| GST_LOG_OBJECT (buf, "may have to reorder channels: %s -> %s", tmp1, |
| tmp2); |
| g_free (tmp1); |
| g_free (tmp2); |
| } |
| #endif /* GST_DISABLE_GST_DEBUG */ |
| |
| buf->need_reorder = TRUE; |
| break; |
| } |
| } |
| } |
| |
| /** |
| * gst_ring_buffer_set_timestamp: |
| * @buf: the #GstRingBuffer |
| * @readseg: the current data segment |
| * @timestamp: The new timestamp of the buffer. |
| * |
| * Set a new timestamp on the buffer. |
| * |
| * MT safe. |
| */ |
| void |
| gst_audio_ring_buffer_set_timestamp (GstAudioRingBuffer * buf, gint readseg, |
| GstClockTime timestamp) |
| { |
| g_return_if_fail (GST_IS_AUDIO_RING_BUFFER (buf)); |
| |
| GST_DEBUG_OBJECT (buf, "Storing timestamp %" GST_TIME_FORMAT |
| " @ %d", GST_TIME_ARGS (timestamp), readseg); |
| |
| GST_OBJECT_LOCK (buf); |
| if (G_UNLIKELY (!buf->acquired)) |
| goto not_acquired; |
| |
| buf->timestamps[readseg] = timestamp; |
| |
| done: |
| GST_OBJECT_UNLOCK (buf); |
| return; |
| |
| not_acquired: |
| { |
| GST_DEBUG_OBJECT (buf, "we are not acquired"); |
| goto done; |
| } |
| } |