/* 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., 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. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "gstaudioringbuffer.h" GST_DEBUG_CATEGORY_STATIC (gst_audio_ring_buffer_debug); #define GST_CAT_DEFAULT gst_audio_ring_buffer_debug /* TODO: use GLib's once https://gitlab.gnome.org/GNOME/glib/issues/1076 lands, or * use C11 atomics once MS arrives in this century. * * We also assume that signed overflow just wraps around because unfortunately * there are no unsigned versions in MSVC. */ #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__) #include static inline guint64 gst_atomic_uint64_add (guint64 * atomic, guint64 n) { return atomic_fetch_add ((_Atomic guint64 *) atomic, n); } static inline void gst_atomic_uint64_set (guint64 * atomic, guint64 n) { atomic_store ((_Atomic guint64 *) atomic, n); } static inline guint64 gst_atomic_uint64_get (guint64 * atomic) { gint64 ret = atomic_load ((_Atomic guint64 *) atomic); return ret; } #elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) static inline guint64 gst_atomic_uint64_add (guint64 * atomic, guint64 n) { return __sync_fetch_and_add (atomic, n); } static inline void gst_atomic_uint64_set (guint64 * atomic, guint64 n) { __sync_synchronize (); __asm__ __volatile__ ("":::"memory"); *atomic = n; } static inline guint64 gst_atomic_uint64_get (guint64 * atomic) { gint64 ret = *atomic; __sync_synchronize (); __asm__ __volatile__ ("":::"memory"); return ret; } #elif defined (G_PLATFORM_WIN32) #include static inline guint64 gst_atomic_uint64_add (guint64 * atomic, guint64 n) { return InterlockedExchangeAdd64 ((gint64 *) atomic, (gint64) n); } static inline void gst_atomic_uint64_set (guint64 * atomic, guint64 n) { *atomic = n; MemoryBarrier (); } static inline guint64 gst_atomic_uint64_get (guint64 * atomic) { MemoryBarrier (); return *atomic; } #else #define STR_TOKEN(s) #s #define STR(s) STR_TOKEN(s) #pragma message "No 64-bit atomic int defined for this " STR(TARGET_CPU) " platform/toolchain!" #define NO_64BIT_ATOMIC_INT_FOR_PLATFORM G_LOCK_DEFINE_STATIC (atomic_lock); static inline guint64 gst_atomic_uint64_add (guint64 * atomic, guint64 n) { guint64 ret; G_LOCK (atomic_lock); *atomic += n; ret = *atomic; G_UNLOCK (atomic_lock); return ret; } static inline void gst_atomic_uint64_set (guint64 * atomic, guint64 n) { G_LOCK (atomic_lock); *atomic = n; G_UNLOCK (atomic_lock); } static inline guint64 gst_atomic_uint64_get (gint64 * atomic) { guint64 ret; G_LOCK (atomic_lock); ret = *atomic; G_UNLOCK (atomic_lock); return ret; } #endif struct _GstAudioRingBufferPrivate { /* ATOMIC */ guint64 segdone; guint64 segbase; }; 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_WITH_PRIVATE (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->priv = gst_audio_ring_buffer_get_instance_private (ringbuffer); ringbuffer->open = FALSE; ringbuffer->acquired = FALSE; g_atomic_int_set (&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; ringbuffer->priv->segbase = 0; ringbuffer->priv->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 if (g_str_equal (mimetype, GST_DSD_MEDIA_TYPE)) { /* Notes about what the "rate" means in DSD: * * In DSD, "sample formats" don't actually exist. There is only the DSD bit; * this is what could be considered the closest equivalent to a "sample format". * But since it is impractical to deal with individual bits in software, the * bits are typically grouped into words (8/16/32 bit words). These are the * DSDU8, DSDU16LE etc. "grouping formats". * * The "rate" in DSD information refers to the number of DSD _bytes_ per second * (not bits per second, because, as said, per-bit handling in software does * not usually make sense). The way the GstAudioRingBuffer works however requires * the rate to be interpreted as the number of DSD _words_ per minute. This is * in part because that's how ALSA uses the rate. * * If the word format is DSDU8, then there's no difference to just using the * original byte rate. But if for example it is DSDU16LE, then the ringbuffer's * rate needs to be half of the rate from GstDsdInfo. For this reason, it is * essential to divide the rate from the DSD info by the word length (in bytes). * * Furthermore, the BPF is set to the stride (= format width * num channels). * The GstAudioRingBuffer can only handle interleaved DSD. This means that * there is a "stride", that is, the DSD word of channel #1 is stored first, * followed by the DSD word of channel #2 etc. and then again we get a DSD * word from channel #1, and so forth. This is similar to how interleaved * PCM works. The stride is then the size (in bytes) of the DSD words for * each channel that are played at the same time. Using this as the BPF is * very important. Otherweise, timestamp and duration figures can be off, * the segment sizes may not be an integer multiple of the DSD stride, etc. */ GstDsdInfo dsd_info; guint format_width; if (!gst_dsd_info_from_caps (&dsd_info, caps)) goto parse_error; format_width = gst_dsd_format_get_width (dsd_info.format); info.rate = dsd_info.rate / format_width; info.channels = dsd_info.channels; info.bpf = format_width * dsd_info.channels; GST_INFO ("using DSD word rate %d instead of DSD byte rate %d " "for ringbuffer", info.rate, dsd_info.rate); memcpy (info.position, dsd_info.positions, sizeof (GstAudioChannelPosition) * dsd_info.channels); GST_AUDIO_RING_BUFFER_SPEC_DSD_FORMAT (spec) = GST_DSD_INFO_FORMAT (&dsd_info); spec->type = GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DSD; } 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_new0 (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); switch (buf->spec.type) { case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW: gst_audio_format_info_fill_silence (buf->spec.info.finfo, buf->empty_seg, segsize); break; case GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DSD: memset (buf->empty_seg, GST_DSD_SILENCE_PATTERN_BYTE, segsize); break; default: /* 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_free (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 */ if (g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0)) { GST_DEBUG_OBJECT (buf, "signal waiter"); GST_AUDIO_RING_BUFFER_SIGNAL (buf); } if (G_UNLIKELY (!res)) goto release_failed; gst_atomic_uint64_set (&buf->priv->segdone, 0); g_atomic_int_set (&buf->segdone, 0); buf->priv->segbase = 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)) { g_atomic_int_set (&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; } } /** * gst_audio_ring_buffer_set_errored: * @buf: the #GstAudioRingBuffer that has encountered an error * * Mark the ringbuffer as errored after it has started. * * MT safe. * Since: 1.24 */ void gst_audio_ring_buffer_set_errored (GstAudioRingBuffer * buf) { gboolean res; /* If started set to errored */ res = g_atomic_int_compare_and_exchange (&buf->state, GST_AUDIO_RING_BUFFER_STATE_STARTED, GST_AUDIO_RING_BUFFER_STATE_ERROR); if (!res) { GST_DEBUG_OBJECT (buf, "ringbuffer was not started, checking paused"); res = g_atomic_int_compare_and_exchange (&buf->state, GST_AUDIO_RING_BUFFER_STATE_PAUSED, GST_AUDIO_RING_BUFFER_STATE_ERROR); } if (res) { GST_DEBUG_OBJECT (buf, "ringbuffer is errored"); } else { GST_DEBUG_OBJECT (buf, "Could not mark ringbuffer as errored. It must have been stopped or already errored (was state %d)", g_atomic_int_get (&buf->state)); } } 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 */ if (g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0)) { 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)) { /* Restore started state */ g_atomic_int_set (&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 (state %d)", buf->state); 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) { GST_DEBUG_OBJECT (buf, "was not paused, try errored"); res = g_atomic_int_compare_and_exchange (&buf->state, GST_AUDIO_RING_BUFFER_STATE_ERROR, GST_AUDIO_RING_BUFFER_STATE_STOPPED); } if (!res) { /* was not paused or stopped either, must have been stopped then */ res = TRUE; GST_DEBUG_OBJECT (buf, "was not paused or errored, must have been stopped"); goto done; } } /* signal any waiters */ if (g_atomic_int_compare_and_exchange (&buf->waiting, 1, 0)) { 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)) { g_atomic_int_set (&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 transferred 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 transferred 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) { guint64 segdone; guint64 samples; g_return_val_if_fail (GST_IS_AUDIO_RING_BUFFER (buf), 0); /* get the amount of segments we processed */ segdone = gst_atomic_uint64_get (&buf->priv->segdone); /* convert to samples */ samples = 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) { guint64 segdone; 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. */ segdone = gst_atomic_uint64_get (&buf->priv->segdone); buf->priv->segbase = segdone - sample / buf->samples_per_seg; buf->segbase = buf->priv->segbase; gst_audio_ring_buffer_clear_all (buf); GST_DEBUG_OBJECT (buf, "set sample to %" G_GUINT64_FORMAT ", segbase %" G_GUINT64_FORMAT, sample, buf->priv->segbase); } /** * gst_audio_ring_buffer_set_segdone: * @buf: the #GstAudioRingBuffer to use * @segdone: the segment number to set * * Sets the current segment number of the ringbuffer. * * MT safe. * * Since: 1.26 */ void gst_audio_ring_buffer_set_segdone (GstAudioRingBuffer * buf, guint64 segdone) { gst_atomic_uint64_set (&buf->priv->segdone, segdone); g_atomic_int_set (&buf->segdone, segdone); } /** * gst_audio_ring_buffer_get_segdone: * @buf: the #GstAudioRingBuffer to use * * Gets the current segment number of the ringbuffer. * * MT safe. * * Returns: Current segment number of the ringbuffer. * * Since: 1.26 */ guint64 gst_audio_ring_buffer_get_segdone (GstAudioRingBuffer * buf) { return gst_atomic_uint64_get (&buf->priv->segdone); } /** * gst_audio_ring_buffer_get_segbase: * @buf: the #GstAudioRingBuffer to use * * Gets the current segment base number of the ringbuffer. * * MT safe. * * Returns: Current segment base number of the ringbuffer. * * Since: 1.26 */ guint64 gst_audio_ring_buffer_get_segbase (GstAudioRingBuffer * buf) { return gst_atomic_uint64_get (&buf->priv->segbase); } /** * default_clear_all: * @buf: the #GstAudioRingBuffer to clear * * Fill the ringbuffer with silence. */ 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 * * Clear all samples from the ringbuffer. * * 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) { guint64 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 = gst_atomic_uint64_get (&buf->priv->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 (gst_atomic_uint64_get (&buf->priv->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) { guint64 segdone; gint segsize, segtotal, channels, bps, bpf, sps; guint8 *dest, *data_end; guint64 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) { gint64 diff; /* get the currently processed segment */ segdone = gst_atomic_uint64_get (&buf->priv->segdone) - buf->priv->segbase; /* see how far away it is from the write segment */ diff = writeseg - segdone; GST_DEBUG_OBJECT (buf, "pointer at %" G_GUINT64_FORMAT ", write to %" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT ", diff %" G_GINT64_FORMAT ", segtotal %d, segsize %d, base %" G_GUINT64_FORMAT, segdone, writeseg, sampleoff, diff, segtotal, segsize, buf->priv->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 %" G_GUINT64_FORMAT ", 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: (inout): 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) { guint64 segdone, readseg = 0; gint segsize, segtotal, channels, bps, bpf, sps; 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) { gint64 diff; /* get the currently processed segment */ segdone = gst_atomic_uint64_get (&buf->priv->segdone) - buf->priv->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 %" G_GUINT64_FORMAT ", sample %" G_GUINT64_FORMAT ", read from %" G_GUINT64_FORMAT "-%d, to_read %d, diff %" G_GINT64_FORMAT ", 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 %" G_GUINT64_FORMAT ", 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 + i * bpf + 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 " @ %" G_GUINT64_FORMAT, 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; guint64 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 = gst_atomic_uint64_get (&buf->priv->segdone); *segment = segdone % buf->spec.segtotal; *len = buf->spec.segsize; *readptr = data + *segment * *len; GST_LOG_OBJECT (buf, "prepare read from segment %d (real %" G_GUINT64_FORMAT ") @%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 */ gst_atomic_uint64_add (&buf->priv->segdone, advance); 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; buf->need_reorder = FALSE; if (memcmp (position, to, channels * sizeof (to[0])) == 0) return; if (channels == 1) { GST_LOG_OBJECT (buf, "single channel, no need to reorder"); return; } if (position_less_channels (position, channels)) { GST_LOG_OBJECT (buf, "position-less channels, no need to reorder"); return; } 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 representing the time of the first sample * in the ringbuffer segment. The timestamp is used by the #GstAudioSrc base * class to set the timestamps on output buffers. Timestamps are * expected to be taken directly from the pipeline clock and are * actual clock timestamps. #GstAudioSrc will convert to running time * by subtracting the base time, but otherwise does not adjust the * outgoing timestamps if provided. * * 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; } }