X-Git-Url: http://vcs.maemo.org/git/?a=blobdiff_plain;f=gst-plugins-base-subtitles0.10%2Fgst-libs%2Fgst%2Faudio%2Fgstringbuffer.c;fp=gst-plugins-base-subtitles0.10%2Fgst-libs%2Fgst%2Faudio%2Fgstringbuffer.c;h=87d1ce2fb403f5153dde6f0c66edd0bfd0979a9d;hb=57ba96e291a055f69dbfd4ae9f1ae2390e36986e;hp=0000000000000000000000000000000000000000;hpb=be2c98fb83895d10ac44af7b9a9c3e00ca54bf49;p=mafwsubrenderer diff --git a/gst-plugins-base-subtitles0.10/gst-libs/gst/audio/gstringbuffer.c b/gst-plugins-base-subtitles0.10/gst-libs/gst/audio/gstringbuffer.c new file mode 100644 index 0000000..87d1ce2 --- /dev/null +++ b/gst-plugins-base-subtitles0.10/gst-libs/gst/audio/gstringbuffer.c @@ -0,0 +1,2060 @@ +/* GStreamer + * Copyright (C) 2005 Wim Taymans + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * SECTION:gstringbuffer + * @short_description: Base class for audio ringbuffer implementations + * @see_also: #GstBaseAudioSink, #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. + * + * + * + * Last reviewed on 2006-02-02 (0.10.4) + */ + +#include + +#include "gstringbuffer.h" + +GST_DEBUG_CATEGORY_STATIC (gst_ring_buffer_debug); +#define GST_CAT_DEFAULT gst_ring_buffer_debug + +static void gst_ring_buffer_dispose (GObject * object); +static void gst_ring_buffer_finalize (GObject * object); + +static gboolean gst_ring_buffer_pause_unlocked (GstRingBuffer * buf); +static void default_clear_all (GstRingBuffer * buf); +static guint default_commit (GstRingBuffer * buf, guint64 * sample, + guchar * data, gint in_samples, gint out_samples, gint * accum); + +/* ringbuffer abstract base class */ +G_DEFINE_ABSTRACT_TYPE (GstRingBuffer, gst_ring_buffer, GST_TYPE_OBJECT); + +static void +gst_ring_buffer_class_init (GstRingBufferClass * klass) +{ + GObjectClass *gobject_class; + GstRingBufferClass *gstringbuffer_class; + + gobject_class = (GObjectClass *) klass; + gstringbuffer_class = (GstRingBufferClass *) klass; + + GST_DEBUG_CATEGORY_INIT (gst_ring_buffer_debug, "ringbuffer", 0, + "ringbuffer class"); + + gobject_class->dispose = gst_ring_buffer_dispose; + gobject_class->finalize = gst_ring_buffer_finalize; + + gstringbuffer_class->clear_all = GST_DEBUG_FUNCPTR (default_clear_all); + gstringbuffer_class->commit = GST_DEBUG_FUNCPTR (default_commit); +} + +static void +gst_ring_buffer_init (GstRingBuffer * ringbuffer) +{ + ringbuffer->open = FALSE; + ringbuffer->acquired = FALSE; + ringbuffer->state = GST_RING_BUFFER_STATE_STOPPED; + ringbuffer->cond = g_cond_new (); + ringbuffer->waiting = 0; + ringbuffer->empty_seg = NULL; + ringbuffer->abidata.ABI.flushing = TRUE; +} + +static void +gst_ring_buffer_dispose (GObject * object) +{ + GstRingBuffer *ringbuffer = GST_RING_BUFFER (object); + + gst_caps_replace (&ringbuffer->spec.caps, NULL); + + G_OBJECT_CLASS (gst_ring_buffer_parent_class)->dispose (G_OBJECT + (ringbuffer)); +} + +static void +gst_ring_buffer_finalize (GObject * object) +{ + GstRingBuffer *ringbuffer = GST_RING_BUFFER (object); + + g_cond_free (ringbuffer->cond); + g_free (ringbuffer->empty_seg); + + G_OBJECT_CLASS (gst_ring_buffer_parent_class)->finalize (G_OBJECT + (ringbuffer)); +} + +typedef struct +{ + const GstBufferFormat format; + const guint8 silence[4]; +} FormatDef; + +static const FormatDef linear_defs[4 * 2 * 2] = { + {GST_S8, {0x00, 0x00, 0x00, 0x00}}, + {GST_S8, {0x00, 0x00, 0x00, 0x00}}, + {GST_U8, {0x80, 0x80, 0x80, 0x80}}, + {GST_U8, {0x80, 0x80, 0x80, 0x80}}, + {GST_S16_LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S16_BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U16_LE, {0x00, 0x80, 0x00, 0x80}}, + {GST_U16_BE, {0x80, 0x00, 0x80, 0x00}}, + {GST_S24_LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S24_BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U24_LE, {0x00, 0x00, 0x80, 0x00}}, + {GST_U24_BE, {0x80, 0x00, 0x00, 0x00}}, + {GST_S32_LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S32_BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U32_LE, {0x00, 0x00, 0x00, 0x80}}, + {GST_U32_BE, {0x80, 0x00, 0x00, 0x00}} +}; + +static const FormatDef linear24_defs[3 * 2 * 2] = { + {GST_S24_3LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S24_3BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U24_3LE, {0x00, 0x00, 0x80, 0x00}}, + {GST_U24_3BE, {0x80, 0x00, 0x00, 0x00}}, + {GST_S20_3LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S20_3BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U20_3LE, {0x00, 0x00, 0x08, 0x00}}, + {GST_U20_3BE, {0x08, 0x00, 0x00, 0x00}}, + {GST_S18_3LE, {0x00, 0x00, 0x00, 0x00}}, + {GST_S18_3BE, {0x00, 0x00, 0x00, 0x00}}, + {GST_U18_3LE, {0x00, 0x00, 0x02, 0x00}}, + {GST_U18_3BE, {0x02, 0x00, 0x00, 0x00}} +}; + +static const FormatDef * +build_linear_format (int depth, int width, int unsignd, int big_endian) +{ + const FormatDef *formats; + + if (width == 24) { + switch (depth) { + case 24: + formats = &linear24_defs[0]; + break; + case 20: + formats = &linear24_defs[4]; + break; + case 18: + formats = &linear24_defs[8]; + break; + default: + return NULL; + } + } else { + switch (depth) { + case 8: + formats = &linear_defs[0]; + break; + case 16: + formats = &linear_defs[4]; + break; + case 24: + formats = &linear_defs[8]; + break; + case 32: + formats = &linear_defs[12]; + break; + default: + return NULL; + } + } + if (unsignd) + formats += 2; + if (big_endian) + formats += 1; + + return formats; +} + +#ifndef GST_DISABLE_GST_DEBUG +static const gchar *format_type_names[] = { + "linear", + "float", + "mu law", + "a law", + "ima adpcm", + "mpeg", + "gsm", + "iec958", + "ac3", + "eac3", + "dts" +}; + +static const gchar *format_names[] = { + "unknown", + "s8", + "u8", + "s16_le", + "s16_be", + "u16_le", + "u16_be", + "s24_le", + "s24_be", + "u24_le", + "u24_be", + "s32_le", + "s32_be", + "u32_le", + "u32_be", + "s24_3le", + "s24_3be", + "u24_3le", + "u24_3be", + "s20_3le", + "s20_3be", + "u20_3le", + "u20_3be", + "s18_3le", + "s18_3be", + "u18_3le", + "u18_3be", + "float32_le", + "float32_be", + "float64_le", + "float64_be", + "mu_law", + "a_law", + "ima_adpcm", + "mpeg", + "gsm", + "iec958", + "ac3", + "eac3", + "dts" +}; +#endif + +/** + * gst_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_ring_buffer_debug_spec_caps (GstRingBufferSpec * spec) +{ + gint i, bytes; + + 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]); + GST_DEBUG ("parsed caps: format: %d, '%s'", spec->format, + format_names[spec->format]); + GST_DEBUG ("parsed caps: width: %d", spec->width); + GST_DEBUG ("parsed caps: depth: %d", spec->depth); + 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]); + } +} + +/** + * gst_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_ring_buffer_debug_spec_buff (GstRingBufferSpec * spec) +{ + 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, spec->segsize / spec->bytes_per_sample); + GST_DEBUG ("acquire ringbuffer: buffer size: %d bytes = %d samples", + spec->segsize * spec->segtotal, + spec->segsize * spec->segtotal / spec->bytes_per_sample); +} + +/** + * gst_ring_buffer_parse_caps: + * @spec: a spec + * @caps: a #GstCaps + * + * Parse @caps into @spec. + * + * Returns: TRUE if the caps could be parsed. + */ +gboolean +gst_ring_buffer_parse_caps (GstRingBufferSpec * spec, GstCaps * caps) +{ + const gchar *mimetype; + GstStructure *structure; + gint i; + + structure = gst_caps_get_structure (caps, 0); + + /* we have to differentiate between int and float formats */ + mimetype = gst_structure_get_name (structure); + + if (!strncmp (mimetype, "audio/x-raw-int", 15)) { + gint endianness; + const FormatDef *def; + gint j, bytes; + + spec->type = GST_BUFTYPE_LINEAR; + + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate) && + gst_structure_get_int (structure, "channels", &spec->channels) && + gst_structure_get_int (structure, "width", &spec->width) && + gst_structure_get_int (structure, "depth", &spec->depth) && + gst_structure_get_boolean (structure, "signed", &spec->sign))) + goto parse_error; + + /* extract endianness if needed */ + if (spec->width > 8) { + if (!gst_structure_get_int (structure, "endianness", &endianness)) + goto parse_error; + } else { + endianness = G_BYTE_ORDER; + } + + spec->bigend = endianness == G_LITTLE_ENDIAN ? FALSE : TRUE; + + def = build_linear_format (spec->depth, spec->width, spec->sign ? 0 : 1, + spec->bigend ? 1 : 0); + + if (def == NULL) + goto parse_error; + + spec->format = def->format; + + bytes = spec->width >> 3; + + for (i = 0; i < spec->channels; i++) { + for (j = 0; j < bytes; j++) { + spec->silence_sample[i * bytes + j] = def->silence[j]; + } + } + } else if (!strncmp (mimetype, "audio/x-raw-float", 17)) { + + spec->type = GST_BUFTYPE_FLOAT; + + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate) && + gst_structure_get_int (structure, "channels", &spec->channels) && + gst_structure_get_int (structure, "width", &spec->width))) + goto parse_error; + + /* match layout to format wrt to endianness */ + switch (spec->width) { + case 32: + spec->format = + G_BYTE_ORDER == G_LITTLE_ENDIAN ? GST_FLOAT32_LE : GST_FLOAT32_BE; + break; + case 64: + spec->format = + G_BYTE_ORDER == G_LITTLE_ENDIAN ? GST_FLOAT64_LE : GST_FLOAT64_BE; + break; + default: + goto parse_error; + } + /* float silence is all zeros.. */ + memset (spec->silence_sample, 0, 32); + } else if (!strncmp (mimetype, "audio/x-alaw", 12)) { + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate) && + gst_structure_get_int (structure, "channels", &spec->channels))) + goto parse_error; + + spec->type = GST_BUFTYPE_A_LAW; + spec->format = GST_A_LAW; + spec->width = 8; + spec->depth = 8; + for (i = 0; i < spec->channels; i++) + spec->silence_sample[i] = 0xd5; + } else if (!strncmp (mimetype, "audio/x-mulaw", 13)) { + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate) && + gst_structure_get_int (structure, "channels", &spec->channels))) + goto parse_error; + + spec->type = GST_BUFTYPE_MU_LAW; + spec->format = GST_MU_LAW; + spec->width = 8; + spec->depth = 8; + for (i = 0; i < spec->channels; i++) + spec->silence_sample[i] = 0xff; + } else if (!strncmp (mimetype, "audio/x-iec958", 14)) { + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate))) + goto parse_error; + + spec->type = GST_BUFTYPE_IEC958; + spec->format = GST_IEC958; + spec->width = 16; + spec->depth = 16; + spec->channels = 2; + } else if (!strncmp (mimetype, "audio/x-ac3", 11)) { + /* extract the needed information from the cap */ + if (!(gst_structure_get_int (structure, "rate", &spec->rate))) + goto parse_error; + + spec->type = GST_BUFTYPE_AC3; + spec->format = GST_AC3; + spec->width = 16; + spec->depth = 16; + spec->channels = 2; + } else { + goto parse_error; + } + + spec->bytes_per_sample = (spec->width >> 3) * spec->channels; + + 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 (spec->rate * spec->bytes_per_sample, + spec->latency_time, GST_SECOND / GST_USECOND); + /* Round to an integer number of samples */ + spec->segsize -= spec->segsize % spec->bytes_per_sample; + + 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; + + gst_ring_buffer_debug_spec_caps (spec); + gst_ring_buffer_debug_spec_buff (spec); + + return TRUE; + + /* ERRORS */ +parse_error: + { + GST_DEBUG ("could not parse caps"); + return FALSE; + } +} + +/** + * gst_ring_buffer_convert: + * @buf: the #GstRingBuffer + * @src_fmt: the source format + * @src_val: the source value + * @dest_fmt: the destination format + * @dest_val: 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. + * + * Since: 0.10.22. + */ +gboolean +gst_ring_buffer_convert (GstRingBuffer * buf, + GstFormat src_fmt, gint64 src_val, GstFormat dest_fmt, gint64 * dest_val) +{ + gboolean res = TRUE; + gint bps, rate; + + GST_DEBUG ("converting value %" G_GINT64_FORMAT " from %s (%d) to %s (%d)", + src_val, gst_format_get_name (src_fmt), src_fmt, + gst_format_get_name (dest_fmt), dest_fmt); + + if (src_fmt == dest_fmt || src_val == -1) { + *dest_val = src_val; + goto done; + } + + /* get important info */ + GST_OBJECT_LOCK (buf); + bps = buf->spec.bytes_per_sample; + rate = buf->spec.rate; + GST_OBJECT_UNLOCK (buf); + + if (bps == 0 || rate == 0) { + GST_DEBUG ("no rate or bps configured"); + res = FALSE; + goto done; + } + + switch (src_fmt) { + case GST_FORMAT_BYTES: + switch (dest_fmt) { + case GST_FORMAT_TIME: + *dest_val = gst_util_uint64_scale_int (src_val / bps, GST_SECOND, + rate); + break; + case GST_FORMAT_DEFAULT: + *dest_val = src_val / bps; + break; + default: + res = FALSE; + break; + } + break; + case GST_FORMAT_DEFAULT: + switch (dest_fmt) { + case GST_FORMAT_TIME: + *dest_val = gst_util_uint64_scale_int (src_val, GST_SECOND, rate); + break; + case GST_FORMAT_BYTES: + *dest_val = src_val * bps; + break; + default: + res = FALSE; + break; + } + break; + case GST_FORMAT_TIME: + switch (dest_fmt) { + case GST_FORMAT_DEFAULT: + *dest_val = gst_util_uint64_scale_int (src_val, rate, GST_SECOND); + break; + case GST_FORMAT_BYTES: + *dest_val = gst_util_uint64_scale_int (src_val, rate, GST_SECOND); + *dest_val *= bps; + break; + default: + res = FALSE; + break; + } + break; + default: + res = FALSE; + break; + } +done: + GST_DEBUG ("ret=%d result %" G_GINT64_FORMAT, res, *dest_val); + + return res; +} + +/** + * gst_ring_buffer_set_callback: + * @buf: the #GstRingBuffer to set the callback on + * @cb: 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_ring_buffer_set_callback (GstRingBuffer * buf, GstRingBufferCallback cb, + gpointer user_data) +{ + g_return_if_fail (GST_IS_RING_BUFFER (buf)); + + GST_OBJECT_LOCK (buf); + buf->callback = cb; + buf->cb_data = user_data; + GST_OBJECT_UNLOCK (buf); +} + + +/** + * gst_ring_buffer_open_device: + * @buf: the #GstRingBuffer + * + * 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_ring_buffer_open_device (GstRingBuffer * buf) +{ + gboolean res = TRUE; + GstRingBufferClass *rclass; + + g_return_val_if_fail (GST_IS_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_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_ring_buffer_close_device: + * @buf: the #GstRingBuffer + * + * Close the audio device associated with the ring buffer. The ring buffer + * should already have been released via gst_ring_buffer_release(). + * + * Returns: TRUE if the device could be closed, FALSE on error. + * + * MT safe. + */ +gboolean +gst_ring_buffer_close_device (GstRingBuffer * buf) +{ + gboolean res = TRUE; + GstRingBufferClass *rclass; + + g_return_val_if_fail (GST_IS_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_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_ring_buffer_device_is_open: + * @buf: the #GstRingBuffer + * + * 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_ring_buffer_device_is_open (GstRingBuffer * buf) +{ + gboolean res = TRUE; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_OBJECT_LOCK (buf); + res = buf->open; + GST_OBJECT_UNLOCK (buf); + + return res; +} + +/** + * gst_ring_buffer_acquire: + * @buf: the #GstRingBuffer 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_ring_buffer_acquire (GstRingBuffer * buf, GstRingBufferSpec * spec) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + gint i, j; + gint segsize, bps; + + g_return_val_if_fail (GST_IS_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; + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + if (G_LIKELY (rclass->acquire)) + res = rclass->acquire (buf, spec); + + if (G_UNLIKELY (!res)) + goto acquire_failed; + + if (G_UNLIKELY ((bps = buf->spec.bytes_per_sample) == 0)) + goto invalid_bps; + + /* 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 / bps; + + /* create an empty segment */ + g_free (buf->empty_seg); + buf->empty_seg = g_malloc (segsize); + + /* FIXME, we only have 32 silence samples, which might not be enough to + * represent silence in all channels */ + bps = MIN (bps, 32); + for (i = 0, j = 0; i < segsize; i++) { + buf->empty_seg[i] = buf->spec.silence_sample[j]; + j = (j + 1) % bps; + } + 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_bps: + { + g_warning + ("invalid bytes_per_sample from acquire ringbuffer %p, fix the element", + buf); + buf->acquired = FALSE; + res = FALSE; + goto done; + } +} + +/** + * gst_ring_buffer_release: + * @buf: the #GstRingBuffer to release + * + * Free the resources of the ringbuffer. + * + * Returns: TRUE if the device could be released, FALSE on error. + * + * MT safe. + */ +gboolean +gst_ring_buffer_release (GstRingBuffer * buf) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_DEBUG_OBJECT (buf, "releasing device"); + + gst_ring_buffer_stop (buf); + + GST_OBJECT_LOCK (buf); + if (G_UNLIKELY (!buf->acquired)) + goto was_released; + + buf->acquired = FALSE; + + /* if this fails, something is wrong in this file */ + g_assert (buf->open == TRUE); + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + if (G_LIKELY (rclass->release)) + res = rclass->release (buf); + + /* signal any waiters */ + GST_DEBUG_OBJECT (buf, "signal waiter"); + GST_RING_BUFFER_SIGNAL (buf); + + if (G_UNLIKELY (!res)) + goto release_failed; + + g_free (buf->empty_seg); + buf->empty_seg = NULL; + 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_ring_buffer_is_acquired: + * @buf: the #GstRingBuffer 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_ring_buffer_is_acquired (GstRingBuffer * buf) +{ + gboolean res; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_OBJECT_LOCK (buf); + res = buf->acquired; + GST_OBJECT_UNLOCK (buf); + + return res; +} + +/** + * gst_ring_buffer_activate: + * @buf: the #GstRingBuffer 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. + * + * Since: 0.10.22. + */ +gboolean +gst_ring_buffer_activate (GstRingBuffer * buf, gboolean active) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + + g_return_val_if_fail (GST_IS_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->abidata.ABI.active == active)) + goto was_active; + + rclass = GST_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->abidata.ABI.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_ring_buffer_is_active: + * @buf: the #GstRingBuffer + * + * Check if @buf is activated. + * + * MT safe. + * + * Returns: TRUE if the device is active. + * + * Since: 0.10.22. + */ +gboolean +gst_ring_buffer_is_active (GstRingBuffer * buf) +{ + gboolean res; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_OBJECT_LOCK (buf); + res = buf->abidata.ABI.active; + GST_OBJECT_UNLOCK (buf); + + return res; +} + + +/** + * gst_ring_buffer_set_flushing: + * @buf: the #GstRingBuffer to flush + * @flushing: the new mode + * + * Set the ringbuffer to flushing mode or normal mode. + * + * MT safe. + */ +void +gst_ring_buffer_set_flushing (GstRingBuffer * buf, gboolean flushing) +{ + g_return_if_fail (GST_IS_RING_BUFFER (buf)); + + GST_OBJECT_LOCK (buf); + buf->abidata.ABI.flushing = flushing; + + if (flushing) { + gst_ring_buffer_pause_unlocked (buf); + } else { + gst_ring_buffer_clear_all (buf); + } + GST_OBJECT_UNLOCK (buf); +} + +/** + * gst_ring_buffer_start: + * @buf: the #GstRingBuffer to start + * + * Start processing samples from the ringbuffer. + * + * Returns: TRUE if the device could be started, FALSE on error. + * + * MT safe. + */ +gboolean +gst_ring_buffer_start (GstRingBuffer * buf) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + gboolean resume = FALSE; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_DEBUG_OBJECT (buf, "starting ringbuffer"); + + GST_OBJECT_LOCK (buf); + if (G_UNLIKELY (buf->abidata.ABI.flushing)) + goto flushing; + + if (G_UNLIKELY (!buf->acquired)) + goto not_acquired; + + if (G_UNLIKELY (g_atomic_int_get (&buf->abidata.ABI.may_start) == FALSE)) + goto may_not_start; + + /* if stopped, set to started */ + res = g_atomic_int_compare_and_exchange (&buf->state, + GST_RING_BUFFER_STATE_STOPPED, GST_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_RING_BUFFER_STATE_PAUSED, GST_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_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_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_ring_buffer_pause_unlocked (GstRingBuffer * buf) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + + GST_DEBUG_OBJECT (buf, "pausing ringbuffer"); + + /* if started, set to paused */ + res = g_atomic_int_compare_and_exchange (&buf->state, + GST_RING_BUFFER_STATE_STARTED, GST_RING_BUFFER_STATE_PAUSED); + + if (!res) + goto not_started; + + /* signal any waiters */ + GST_DEBUG_OBJECT (buf, "signal waiter"); + GST_RING_BUFFER_SIGNAL (buf); + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + if (G_LIKELY (rclass->pause)) + res = rclass->pause (buf); + + if (G_UNLIKELY (!res)) { + buf->state = GST_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_ring_buffer_pause: + * @buf: the #GstRingBuffer to pause + * + * Pause processing samples from the ringbuffer. + * + * Returns: TRUE if the device could be paused, FALSE on error. + * + * MT safe. + */ +gboolean +gst_ring_buffer_pause (GstRingBuffer * buf) +{ + gboolean res = FALSE; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + GST_OBJECT_LOCK (buf); + if (G_UNLIKELY (buf->abidata.ABI.flushing)) + goto flushing; + + if (G_UNLIKELY (!buf->acquired)) + goto not_acquired; + + res = gst_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_ring_buffer_stop: + * @buf: the #GstRingBuffer to stop + * + * Stop processing samples from the ringbuffer. + * + * Returns: TRUE if the device could be stopped, FALSE on error. + * + * MT safe. + */ +gboolean +gst_ring_buffer_stop (GstRingBuffer * buf) +{ + gboolean res = FALSE; + GstRingBufferClass *rclass; + + g_return_val_if_fail (GST_IS_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_RING_BUFFER_STATE_STARTED, GST_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_RING_BUFFER_STATE_PAUSED, GST_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_RING_BUFFER_SIGNAL (buf); + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + if (G_LIKELY (rclass->stop)) + res = rclass->stop (buf); + + if (G_UNLIKELY (!res)) { + buf->state = GST_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_ring_buffer_delay: + * @buf: the #GstRingBuffer 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_ring_buffer_delay (GstRingBuffer * buf) +{ + GstRingBufferClass *rclass; + guint res; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), 0); + + /* buffer must be acquired */ + if (G_UNLIKELY (!gst_ring_buffer_is_acquired (buf))) + goto not_acquired; + + rclass = GST_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_ring_buffer_samples_done: + * @buf: the #GstRingBuffer 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_ring_buffer_delay()). + * + * Returns: The number of samples processed by the ringbuffer. + * + * MT safe. + */ +guint64 +gst_ring_buffer_samples_done (GstRingBuffer * buf) +{ + gint segdone; + guint64 samples; + + g_return_val_if_fail (GST_IS_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_ring_buffer_set_sample: + * @buf: the #GstRingBuffer 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_ring_buffer_set_sample (GstRingBuffer * buf, guint64 sample) +{ + g_return_if_fail (GST_IS_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_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 (GstRingBuffer * 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_ring_buffer_clear (buf, i); + } +} + +/** + * gst_ring_buffer_clear_all: + * @buf: the #GstRingBuffer to clear + * + * Fill the ringbuffer with silence. + * + * MT safe. + */ +void +gst_ring_buffer_clear_all (GstRingBuffer * buf) +{ + GstRingBufferClass *rclass; + + g_return_if_fail (GST_IS_RING_BUFFER (buf)); + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + + if (G_LIKELY (rclass->clear_all)) + rclass->clear_all (buf); +} + + +static gboolean +wait_segment (GstRingBuffer * 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_RING_BUFFER_STATE_STARTED)) { + /* see if we are allowed to start it */ + if (G_UNLIKELY (g_atomic_int_get (&buf->abidata.ABI.may_start) == FALSE)) + goto no_start; + + GST_DEBUG_OBJECT (buf, "start!"); + segments = g_atomic_int_get (&buf->segdone); + gst_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->abidata.ABI.flushing)) + goto flushing; + + if (G_UNLIKELY (g_atomic_int_get (&buf->state) != + GST_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_RING_BUFFER_WAIT (buf); + + if (G_UNLIKELY (buf->abidata.ABI.flushing)) + goto flushing; + + if (G_UNLIKELY (g_atomic_int_get (&buf->state) != + GST_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 FWD_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + /* no rate conversion */ \ + guint towrite = MIN (se + bps - s, de - d); \ + /* simple copy */ \ + if (!skip) \ + memcpy (d, s, towrite); \ + in_samples -= towrite / bps; \ + out_samples -= towrite / bps; \ + 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) \ +G_STMT_START { \ + guint8 *sb = s, *db = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, s, bps); \ + s += bps; \ + *accum += outr; \ + if ((*accum << 1) >= inr) { \ + *accum -= inr; \ + d += bps; \ + } \ + } \ + in_samples -= (s - sb)/bps; \ + out_samples -= (d - db)/bps; \ + 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) \ +G_STMT_START { \ + guint8 *sb = s, *db = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, s, bps); \ + d += bps; \ + *accum += inr; \ + if ((*accum << 1) >= outr) { \ + *accum -= outr; \ + s += bps; \ + } \ + } \ + in_samples -= (s - sb)/bps; \ + out_samples -= (d - db)/bps; \ + GST_DEBUG ("fwd_down end %d/%d",*accum,*toprocess); \ +} G_STMT_END + +#define REV_UP_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *sb = se, *db = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, se, bps); \ + se -= bps; \ + *accum += outr; \ + while (d < de && (*accum << 1) >= inr) { \ + *accum -= inr; \ + d += bps; \ + } \ + } \ + in_samples -= (sb - se)/bps; \ + out_samples -= (d - db)/bps; \ + GST_DEBUG ("rev_up end %d/%d",*accum,*toprocess); \ +} G_STMT_END + +#define REV_DOWN_SAMPLES(s,se,d,de) \ +G_STMT_START { \ + guint8 *sb = se, *db = d; \ + while (s <= se && d < de) { \ + if (!skip) \ + memcpy (d, se, bps); \ + d += bps; \ + *accum += inr; \ + while (s <= se && (*accum << 1) >= outr) { \ + *accum -= outr; \ + se -= bps; \ + } \ + } \ + in_samples -= (sb - se)/bps; \ + out_samples -= (d - db)/bps; \ + GST_DEBUG ("rev_down end %d/%d",*accum,*toprocess); \ +} G_STMT_END + +static guint +default_commit (GstRingBuffer * buf, guint64 * sample, + guchar * data, gint in_samples, gint out_samples, gint * accum) +{ + gint segdone; + gint segsize, segtotal, bps, sps; + guint8 *dest, *data_end; + gint writeseg, sampleoff; + gint *toprocess; + gint inr, outr; + gboolean reverse; + + g_return_val_if_fail (buf->data != NULL, -1); + g_return_val_if_fail (data != NULL, -1); + + dest = GST_BUFFER_DATA (buf->data); + segsize = buf->spec.segsize; + segtotal = buf->spec.segtotal; + bps = buf->spec.bytes_per_sample; + 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 + (bps * inr); + + /* figure out the segment and the offset inside the segment where + * the first sample should be written. */ + writeseg = *sample / sps; + sampleoff = (*sample % sps) * bps; + + /* 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 + ("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, bps * out_samples); + + d = dest + (ws * segsize) + sampleoff; + d_end = d + avail; + *sample += avail / bps; + + GST_DEBUG_OBJECT (buf, "write @%p seg %d, sps %d, off %d, avail %d", + dest + ws * segsize, ws, sps, sampleoff, avail); + + if (G_LIKELY (inr == outr && !reverse)) { + /* no rate conversion, simply copy samples */ + FWD_SAMPLES (data, data_end, d, d_end); + } else if (!reverse) { + if (inr >= outr) + /* forward speed up */ + FWD_UP_SAMPLES (data, data_end, d, d_end); + else + /* forward slow down */ + FWD_DOWN_SAMPLES (data, data_end, d, d_end); + } else { + if (inr >= outr) + /* reverse speed up */ + REV_UP_SAMPLES (data, data_end, d, d_end); + else + /* reverse slow down */ + REV_DOWN_SAMPLES (data, data_end, d, d_end); + } + + /* for the next iteration we write to the next segment at the beginning. */ + writeseg++; + sampleoff = 0; + } + /* we consumed all samples here */ + data = data_end + bps; + +done: + return inr - ((data_end - data) / bps); + + /* ERRORS */ +not_started: + { + GST_DEBUG_OBJECT (buf, "stopped processing"); + goto done; + } +} + +/** + * gst_ring_buffer_commit_full: + * @buf: the #GstRingBuffer to commit + * @sample: the sample position of the data + * @data: 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: 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 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. + * + * Since: 0.10.11. + */ +guint +gst_ring_buffer_commit_full (GstRingBuffer * buf, guint64 * sample, + guchar * data, gint in_samples, gint out_samples, gint * accum) +{ + GstRingBufferClass *rclass; + guint res = -1; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), -1); + + if (G_UNLIKELY (in_samples == 0 || out_samples == 0)) + return in_samples; + + rclass = GST_RING_BUFFER_GET_CLASS (buf); + + if (G_LIKELY (rclass->commit)) + res = rclass->commit (buf, sample, data, in_samples, out_samples, accum); + + return res; +} + +/** + * gst_ring_buffer_commit: + * @buf: the #GstRingBuffer to commit + * @sample: the sample position of the data + * @data: the data to commit + * @len: the number of samples in the data to commit + * + * Same as gst_ring_buffer_commit_full() but with a in_samples and out_samples + * equal to @len, ignoring accum. + * + * Returns: The number of samples written to the ringbuffer or -1 on + * error. + * + * MT safe. + */ +guint +gst_ring_buffer_commit (GstRingBuffer * buf, guint64 sample, guchar * data, + guint len) +{ + guint res; + guint64 samplep = sample; + + res = gst_ring_buffer_commit_full (buf, &samplep, data, len, len, NULL); + + return res; +} + +/** + * gst_ring_buffer_read: + * @buf: the #GstRingBuffer to read from + * @sample: the sample position of the data + * @data: where the data should be read + * @len: the number of samples in data to read + * + * 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. + * + * Returns: The number of samples read from the ringbuffer or -1 on + * error. + * + * MT safe. + */ +guint +gst_ring_buffer_read (GstRingBuffer * buf, guint64 sample, guchar * data, + guint len) +{ + gint segdone; + gint segsize, segtotal, bps, sps; + guint8 *dest; + guint to_read; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), -1); + g_return_val_if_fail (buf->data != NULL, -1); + g_return_val_if_fail (data != NULL, -1); + + dest = GST_BUFFER_DATA (buf->data); + segsize = buf->spec.segsize; + segtotal = buf->spec.segtotal; + bps = buf->spec.bytes_per_sample; + sps = buf->samples_per_seg; + + to_read = len; + /* read enough samples */ + while (to_read > 0) { + gint sampleslen; + gint readseg, 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 + ("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 * bps); + 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); + + memcpy (data, dest + (readseg * segsize) + (sampleoff * bps), + (sampleslen * bps)); + + next: + to_read -= sampleslen; + sample += sampleslen; + data += sampleslen * bps; + } + + return len - to_read; + + /* ERRORS */ +not_started: + { + GST_DEBUG_OBJECT (buf, "stopped processing"); + return len - to_read; + } +} + +/** + * gst_ring_buffer_prepare_read: + * @buf: the #GstRingBuffer to read from + * @segment: the segment to read + * @readptr: the pointer to the memory where samples can be read + * @len: 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_ring_buffer_prepare_read (GstRingBuffer * buf, gint * segment, + guint8 ** readptr, gint * len) +{ + guint8 *data; + gint segdone; + + g_return_val_if_fail (GST_IS_RING_BUFFER (buf), FALSE); + + if (buf->callback == NULL) { + /* push mode, fail when nothing is started */ + if (g_atomic_int_get (&buf->state) != GST_RING_BUFFER_STATE_STARTED) + return FALSE; + } + + g_return_val_if_fail (buf->data != 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 = GST_BUFFER_DATA (buf->data); + + /* 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 ("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_ring_buffer_advance: + * @buf: the #GstRingBuffer 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_ring_buffer_advance (GstRingBuffer * buf, guint advance) +{ + g_return_if_fail (GST_IS_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_RING_BUFFER_SIGNAL (buf); + GST_OBJECT_UNLOCK (buf); + } +} + +/** + * gst_ring_buffer_clear: + * @buf: the #GstRingBuffer 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_ring_buffer_clear (GstRingBuffer * buf, gint segment) +{ + guint8 *data; + + g_return_if_fail (GST_IS_RING_BUFFER (buf)); + + /* no data means it's already cleared */ + if (G_UNLIKELY (buf->data == NULL)) + return; + + /* no empty_seg means it's not opened */ + if (G_UNLIKELY (buf->empty_seg == NULL)) + return; + + segment %= buf->spec.segtotal; + + data = GST_BUFFER_DATA (buf->data); + data += segment * buf->spec.segsize; + + GST_LOG ("clear segment %d @%p", segment, data); + + memcpy (data, buf->empty_seg, buf->spec.segsize); +} + +/** + * gst_ring_buffer_may_start: + * @buf: the #GstRingBuffer + * @allowed: the new value + * + * Tell the ringbuffer that it is allowed to start playback when + * the ringbuffer is filled with samples. + * + * MT safe. + * + * Since: 0.10.6 + */ +void +gst_ring_buffer_may_start (GstRingBuffer * buf, gboolean allowed) +{ + g_return_if_fail (GST_IS_RING_BUFFER (buf)); + + GST_LOG_OBJECT (buf, "may start: %d", allowed); + g_atomic_int_set (&buf->abidata.ABI.may_start, allowed); +}