mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-08 10:31:05 +00:00
392 lines
12 KiB
C
392 lines
12 KiB
C
|
/* GStreamer
|
||
|
* Copyright (C) 2018 Collabora Ltd
|
||
|
* @author George Kiagiadakis <george.kiagiadakis@collabora.com>
|
||
|
*
|
||
|
* This library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Library General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* Library General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Library General Public
|
||
|
* License along with this library; if not, write to the
|
||
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||
|
* Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SECTION:gstplanaraudioadapter
|
||
|
* @title: GstPlanarAudioAdapter
|
||
|
* @short_description: adapts incoming audio data on a sink pad into chunks of N samples
|
||
|
*
|
||
|
* This class is similar to GstAdapter, but it is made to work with
|
||
|
* non-interleaved (planar) audio buffers. Before using, an audio format
|
||
|
* must be configured with gst_planar_audio_adapter_configure()
|
||
|
*/
|
||
|
|
||
|
#include "gstplanaraudioadapter.h"
|
||
|
|
||
|
GST_DEBUG_CATEGORY_STATIC (gst_planar_audio_adapter_debug);
|
||
|
#define GST_CAT_DEFAULT gst_planar_audio_adapter_debug
|
||
|
|
||
|
struct _GstPlanarAudioAdapter
|
||
|
{
|
||
|
GObject object;
|
||
|
|
||
|
GstAudioInfo info;
|
||
|
GSList *buflist;
|
||
|
GSList *buflist_end;
|
||
|
gsize samples;
|
||
|
gsize skip;
|
||
|
guint count;
|
||
|
};
|
||
|
|
||
|
struct _GstPlanarAudioAdapterClass
|
||
|
{
|
||
|
GObjectClass parent_class;
|
||
|
};
|
||
|
|
||
|
#define _do_init \
|
||
|
GST_DEBUG_CATEGORY_INIT (gst_planar_audio_adapter_debug, "planaraudioadapter", \
|
||
|
0, "object to splice and merge audio buffers to desired size")
|
||
|
#define gst_planar_audio_adapter_parent_class parent_class
|
||
|
G_DEFINE_TYPE_WITH_CODE (GstPlanarAudioAdapter, gst_planar_audio_adapter,
|
||
|
G_TYPE_OBJECT, _do_init);
|
||
|
|
||
|
static void gst_planar_audio_adapter_dispose (GObject * object);
|
||
|
|
||
|
static void
|
||
|
gst_planar_audio_adapter_class_init (GstPlanarAudioAdapterClass * klass)
|
||
|
{
|
||
|
GObjectClass *object = G_OBJECT_CLASS (klass);
|
||
|
|
||
|
object->dispose = gst_planar_audio_adapter_dispose;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_planar_audio_adapter_init (GstPlanarAudioAdapter * adapter)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_planar_audio_adapter_dispose (GObject * object)
|
||
|
{
|
||
|
GstPlanarAudioAdapter *adapter = GST_PLANAR_AUDIO_ADAPTER (object);
|
||
|
|
||
|
gst_planar_audio_adapter_clear (adapter);
|
||
|
|
||
|
GST_CALL_PARENT (G_OBJECT_CLASS, dispose, (object));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_new:
|
||
|
*
|
||
|
* Creates a new #GstPlanarAudioAdapter. Free with g_object_unref().
|
||
|
*
|
||
|
* Returns: (transfer full): a new #GstPlanarAudioAdapter
|
||
|
*/
|
||
|
GstPlanarAudioAdapter *
|
||
|
gst_planar_audio_adapter_new (void)
|
||
|
{
|
||
|
return g_object_new (GST_TYPE_PLANAR_AUDIO_ADAPTER, NULL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_configure:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
* @info: a #GstAudioInfo describing the format of the audio data
|
||
|
*
|
||
|
* Sets up the @adapter to handle audio data of the specified audio format.
|
||
|
* Note that this will internally clear the adapter and re-initialize it.
|
||
|
*/
|
||
|
void
|
||
|
gst_planar_audio_adapter_configure (GstPlanarAudioAdapter * adapter,
|
||
|
const GstAudioInfo * info)
|
||
|
{
|
||
|
g_return_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter));
|
||
|
g_return_if_fail (info != NULL);
|
||
|
g_return_if_fail (GST_AUDIO_INFO_IS_VALID (info));
|
||
|
g_return_if_fail (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED);
|
||
|
|
||
|
gst_planar_audio_adapter_clear (adapter);
|
||
|
adapter->info = *info;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_clear:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
*
|
||
|
* Removes all buffers from @adapter.
|
||
|
*/
|
||
|
void
|
||
|
gst_planar_audio_adapter_clear (GstPlanarAudioAdapter * adapter)
|
||
|
{
|
||
|
g_return_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter));
|
||
|
|
||
|
g_slist_foreach (adapter->buflist, (GFunc) gst_mini_object_unref, NULL);
|
||
|
g_slist_free (adapter->buflist);
|
||
|
adapter->buflist = NULL;
|
||
|
adapter->buflist_end = NULL;
|
||
|
adapter->count = 0;
|
||
|
adapter->samples = 0;
|
||
|
adapter->skip = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_push:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
* @buf: (transfer full): a #GstBuffer to queue in the adapter
|
||
|
*
|
||
|
* Adds the data from @buf to the data stored inside @adapter and takes
|
||
|
* ownership of the buffer.
|
||
|
*/
|
||
|
void
|
||
|
gst_planar_audio_adapter_push (GstPlanarAudioAdapter * adapter, GstBuffer * buf)
|
||
|
{
|
||
|
GstAudioMeta *meta;
|
||
|
gsize samples;
|
||
|
|
||
|
g_return_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter));
|
||
|
g_return_if_fail (GST_AUDIO_INFO_IS_VALID (&adapter->info));
|
||
|
g_return_if_fail (GST_IS_BUFFER (buf));
|
||
|
|
||
|
meta = gst_buffer_get_audio_meta (buf);
|
||
|
g_return_if_fail (meta != NULL);
|
||
|
g_return_if_fail (gst_audio_info_is_equal (&meta->info, &adapter->info));
|
||
|
|
||
|
samples = meta->samples;
|
||
|
adapter->samples += samples;
|
||
|
|
||
|
if (G_UNLIKELY (adapter->buflist == NULL)) {
|
||
|
GST_LOG_OBJECT (adapter, "pushing %p first %" G_GSIZE_FORMAT " samples",
|
||
|
buf, samples);
|
||
|
adapter->buflist = adapter->buflist_end = g_slist_append (NULL, buf);
|
||
|
} else {
|
||
|
/* Otherwise append to the end, and advance our end pointer */
|
||
|
GST_LOG_OBJECT (adapter, "pushing %p %" G_GSIZE_FORMAT " samples at end, "
|
||
|
"samples now %" G_GSIZE_FORMAT, buf, samples, adapter->samples);
|
||
|
adapter->buflist_end = g_slist_append (adapter->buflist_end, buf);
|
||
|
adapter->buflist_end = g_slist_next (adapter->buflist_end);
|
||
|
}
|
||
|
++adapter->count;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
gst_planar_audio_adapter_flush_unchecked (GstPlanarAudioAdapter * adapter,
|
||
|
gsize to_flush)
|
||
|
{
|
||
|
GSList *cur = adapter->buflist;
|
||
|
gsize cur_samples;
|
||
|
|
||
|
while (to_flush > 0) {
|
||
|
cur_samples = gst_buffer_get_audio_meta (cur->data)->samples;
|
||
|
cur_samples -= adapter->skip;
|
||
|
|
||
|
if (to_flush >= cur_samples) {
|
||
|
gst_buffer_unref (cur->data);
|
||
|
cur = g_slist_remove_link (cur, cur);
|
||
|
|
||
|
to_flush -= cur_samples;
|
||
|
adapter->samples -= cur_samples;
|
||
|
adapter->skip = 0;
|
||
|
--adapter->count;
|
||
|
} else {
|
||
|
adapter->samples -= to_flush;
|
||
|
adapter->skip += to_flush;
|
||
|
to_flush = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
adapter->buflist = cur;
|
||
|
if (!adapter->buflist)
|
||
|
adapter->buflist_end = NULL;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_flush:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
* @to_flush: the number of samples to flush
|
||
|
*
|
||
|
* Flushes the first @to_flush samples in the @adapter. The caller must ensure
|
||
|
* that at least this many samples are available.
|
||
|
*/
|
||
|
void
|
||
|
gst_planar_audio_adapter_flush (GstPlanarAudioAdapter * adapter, gsize to_flush)
|
||
|
{
|
||
|
g_return_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter));
|
||
|
g_return_if_fail (to_flush <= adapter->samples);
|
||
|
|
||
|
/* flushing out 0 bytes will do nothing */
|
||
|
if (G_UNLIKELY (to_flush == 0))
|
||
|
return;
|
||
|
|
||
|
gst_planar_audio_adapter_flush_unchecked (adapter, to_flush);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_get_buffer:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
* @nsamples: the number of samples to get
|
||
|
* @flags: hint the intended use of the returned buffer
|
||
|
*
|
||
|
* Returns a #GstBuffer containing the first @nsamples of the @adapter, but
|
||
|
* does not flush them from the adapter.
|
||
|
* Use gst_planar_audio_adapter_take_buffer() for flushing at the same time.
|
||
|
*
|
||
|
* The map @flags can be used to give an optimization hint to this function.
|
||
|
* When the requested buffer is meant to be mapped only for reading, it might
|
||
|
* be possible to avoid copying memory in some cases.
|
||
|
*
|
||
|
* Caller owns a reference to the returned buffer. gst_buffer_unref() after
|
||
|
* usage.
|
||
|
*
|
||
|
* Free-function: gst_buffer_unref
|
||
|
*
|
||
|
* Returns: (transfer full) (nullable): a #GstBuffer containing the first
|
||
|
* @nsamples of the adapter, or %NULL if @nsamples samples are not
|
||
|
* available. gst_buffer_unref() when no longer needed.
|
||
|
*/
|
||
|
GstBuffer *
|
||
|
gst_planar_audio_adapter_get_buffer (GstPlanarAudioAdapter * adapter,
|
||
|
gsize nsamples, GstMapFlags flags)
|
||
|
{
|
||
|
GstBuffer *buffer = NULL;
|
||
|
GstBuffer *cur;
|
||
|
gsize hsamples, skip;
|
||
|
|
||
|
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter), NULL);
|
||
|
g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (&adapter->info), NULL);
|
||
|
g_return_val_if_fail (nsamples > 0, NULL);
|
||
|
|
||
|
GST_LOG_OBJECT (adapter, "getting buffer of %" G_GSIZE_FORMAT " samples",
|
||
|
nsamples);
|
||
|
|
||
|
/* we don't have enough data, return NULL. This is unlikely
|
||
|
* as one usually does an _available() first instead of grabbing a
|
||
|
* random size. */
|
||
|
if (G_UNLIKELY (nsamples > adapter->samples))
|
||
|
return NULL;
|
||
|
|
||
|
cur = adapter->buflist->data;
|
||
|
skip = adapter->skip;
|
||
|
hsamples = gst_buffer_get_audio_meta (cur)->samples;
|
||
|
|
||
|
|
||
|
if (skip == 0 && hsamples == nsamples) {
|
||
|
/* our head buffer fits exactly the requirements */
|
||
|
GST_LOG_OBJECT (adapter, "providing buffer of %" G_GSIZE_FORMAT " samples"
|
||
|
" as head buffer", nsamples);
|
||
|
|
||
|
buffer = gst_buffer_ref (cur);
|
||
|
|
||
|
} else if (hsamples >= nsamples + skip && !(flags & GST_MAP_WRITE)) {
|
||
|
/* return a buffer with the same data as our head buffer but with
|
||
|
* a modified GstAudioMeta that maps only the parts of the planes
|
||
|
* that should be made available to the caller. This is more efficient
|
||
|
* for reading (no mem copy), but will hit performance if the caller
|
||
|
* decides to map for writing or otherwise do a deep copy */
|
||
|
GST_LOG_OBJECT (adapter, "providing buffer of %" G_GSIZE_FORMAT " samples"
|
||
|
" via copy region", nsamples);
|
||
|
|
||
|
buffer = gst_buffer_copy_region (cur, GST_BUFFER_COPY_ALL, 0, -1);
|
||
|
gst_audio_buffer_truncate (buffer, adapter->info.bpf, skip, nsamples);
|
||
|
|
||
|
} else {
|
||
|
gint c, bps;
|
||
|
GstAudioMeta *meta;
|
||
|
|
||
|
/* construct a buffer with concatenated memory chunks from the appropriate
|
||
|
* places. These memories will be copied into a single memory chunk
|
||
|
* as soon as the buffer is mapped */
|
||
|
GST_LOG_OBJECT (adapter, "providing buffer of %" G_GSIZE_FORMAT " samples"
|
||
|
" via memory concatenation", nsamples);
|
||
|
|
||
|
bps = adapter->info.finfo->width / 8;
|
||
|
|
||
|
for (c = 0; c < adapter->info.channels; c++) {
|
||
|
gsize need = nsamples;
|
||
|
gsize cur_skip = skip;
|
||
|
gsize take_from_cur;
|
||
|
GSList *cur_node = adapter->buflist;
|
||
|
|
||
|
while (need > 0) {
|
||
|
cur = cur_node->data;
|
||
|
meta = gst_buffer_get_audio_meta (cur);
|
||
|
take_from_cur = need > (meta->samples - cur_skip) ?
|
||
|
meta->samples - cur_skip : need;
|
||
|
|
||
|
cur = gst_buffer_copy_region (cur, GST_BUFFER_COPY_MEMORY,
|
||
|
meta->offsets[c] + cur_skip * bps, take_from_cur * bps);
|
||
|
|
||
|
if (!buffer)
|
||
|
buffer = cur;
|
||
|
else
|
||
|
gst_buffer_append (buffer, cur);
|
||
|
|
||
|
need -= take_from_cur;
|
||
|
cur_skip = 0;
|
||
|
cur_node = g_slist_next (cur_node);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
gst_buffer_add_audio_meta (buffer, &adapter->info, nsamples, NULL);
|
||
|
}
|
||
|
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_take_buffer:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
* @nsamples: the number of samples to take
|
||
|
* @flags: hint the intended use of the returned buffer
|
||
|
*
|
||
|
* Returns a #GstBuffer containing the first @nsamples bytes of the
|
||
|
* @adapter. The returned bytes will be flushed from the adapter.
|
||
|
*
|
||
|
* See gst_planar_audio_adapter_get_buffer() for more details.
|
||
|
*
|
||
|
* Caller owns a reference to the returned buffer. gst_buffer_unref() after
|
||
|
* usage.
|
||
|
*
|
||
|
* Free-function: gst_buffer_unref
|
||
|
*
|
||
|
* Returns: (transfer full) (nullable): a #GstBuffer containing the first
|
||
|
* @nsamples of the adapter, or %NULL if @nsamples samples are not
|
||
|
* available. gst_buffer_unref() when no longer needed.
|
||
|
*/
|
||
|
GstBuffer *
|
||
|
gst_planar_audio_adapter_take_buffer (GstPlanarAudioAdapter * adapter,
|
||
|
gsize nsamples, GstMapFlags flags)
|
||
|
{
|
||
|
GstBuffer *buffer;
|
||
|
|
||
|
buffer = gst_planar_audio_adapter_get_buffer (adapter, nsamples, flags);
|
||
|
if (buffer)
|
||
|
gst_planar_audio_adapter_flush_unchecked (adapter, nsamples);
|
||
|
|
||
|
return buffer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* gst_planar_audio_adapter_available:
|
||
|
* @adapter: a #GstPlanarAudioAdapter
|
||
|
*
|
||
|
* Gets the maximum amount of samples available, that is it returns the maximum
|
||
|
* value that can be supplied to gst_planar_audio_adapter_get_buffer() without
|
||
|
* that function returning %NULL.
|
||
|
*
|
||
|
* Returns: number of samples available in @adapter
|
||
|
*/
|
||
|
gsize
|
||
|
gst_planar_audio_adapter_available (GstPlanarAudioAdapter * adapter)
|
||
|
{
|
||
|
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter), 0);
|
||
|
|
||
|
return adapter->samples;
|
||
|
}
|