gstreamer/subprojects/gst-plugins-bad/gst-libs/gst/audio/gstplanaraudioadapter.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

643 lines
20 KiB
C
Raw Normal View History

/* 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()
*/
libs: fix API export/import and 'inconsistent linkage' on MSVC For each lib we build export its own API in headers when we're building it, otherwise import the API from the headers. This fixes linker warnings on Windows when building with MSVC. The problem was that we had defined all GST_*_API decorators unconditionally to GST_EXPORT. This was intentional and only supposed to be temporary, but caused linker warnings because we tell the linker that we want to export all symbols even those from externall DLLs, and when the linker notices that they were in external DLLS and not present locally it warns. What we need to do when building each library is: export the library's own symbols and import all other symbols. To this end we define e.g. BUILDING_GST_FOO and then we define the GST_FOO_API decorator either to export or to import symbols depending on whether BUILDING_GST_FOO is set or not. That way external users of each library API automatically get the import. While we're at it, add new GST_API_EXPORT in config.h and use that for GST_*_API decorators instead of GST_EXPORT. The right export define depends on the toolchain and whether we're using -fvisibility=hidden or not, so it's better to set it to the right thing directly than hard-coding a compiler whitelist in the public header. We put the export define into config.h instead of passing it via the command line to the compiler because it might contain spaces and brackets and in the autotools scenario we'd have to pass that through multiple layers of plumbing and Makefile/shell escaping and we're just not going to be *that* lucky. The export define is only used if we're compiling our lib, not by external users of the lib headers, so it's not a problem to put it into config.h Also, this means all .c files of libs need to include config.h to get the export marker defined, so fix up a few that didn't include config.h. This commit depends on a common submodule commit that makes gst-glib-gen.mak add an #include "config.h" to generated enum/marshal .c files for the autotools build. https://bugzilla.gnome.org/show_bug.cgi?id=797185
2018-09-24 10:52:22 +00:00
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#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;
GstClockTime pts;
guint64 pts_distance;
GstClockTime dts;
guint64 dts_distance;
guint64 offset;
guint64 offset_distance;
GstClockTime pts_at_discont;
GstClockTime dts_at_discont;
guint64 offset_at_discont;
guint64 distance_from_discont;
};
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)
{
adapter->pts = GST_CLOCK_TIME_NONE;
adapter->pts_distance = 0;
adapter->dts = GST_CLOCK_TIME_NONE;
adapter->dts_distance = 0;
adapter->offset = GST_BUFFER_OFFSET_NONE;
adapter->offset_distance = 0;
adapter->pts_at_discont = GST_CLOCK_TIME_NONE;
adapter->dts_at_discont = GST_CLOCK_TIME_NONE;
adapter->offset_at_discont = GST_BUFFER_OFFSET_NONE;
adapter->distance_from_discont = 0;
}
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;
adapter->pts = GST_CLOCK_TIME_NONE;
adapter->pts_distance = 0;
adapter->dts = GST_CLOCK_TIME_NONE;
adapter->dts_distance = 0;
adapter->offset = GST_BUFFER_OFFSET_NONE;
adapter->offset_distance = 0;
adapter->pts_at_discont = GST_CLOCK_TIME_NONE;
adapter->dts_at_discont = GST_CLOCK_TIME_NONE;
adapter->offset_at_discont = GST_BUFFER_OFFSET_NONE;
adapter->distance_from_discont = 0;
}
static inline void
update_timestamps_and_offset (GstPlanarAudioAdapter * adapter, GstBuffer * buf)
{
GstClockTime pts, dts;
guint64 offset;
pts = GST_BUFFER_PTS (buf);
if (GST_CLOCK_TIME_IS_VALID (pts)) {
GST_LOG_OBJECT (adapter, "new pts %" GST_TIME_FORMAT, GST_TIME_ARGS (pts));
adapter->pts = pts;
adapter->pts_distance = 0;
}
dts = GST_BUFFER_DTS (buf);
if (GST_CLOCK_TIME_IS_VALID (dts)) {
GST_LOG_OBJECT (adapter, "new dts %" GST_TIME_FORMAT, GST_TIME_ARGS (dts));
adapter->dts = dts;
adapter->dts_distance = 0;
}
offset = GST_BUFFER_OFFSET (buf);
if (offset != GST_BUFFER_OFFSET_NONE) {
GST_LOG_OBJECT (adapter, "new offset %" G_GUINT64_FORMAT, offset);
adapter->offset = offset;
adapter->offset_distance = 0;
}
if (GST_BUFFER_IS_DISCONT (buf)) {
/* Take values as-is (might be NONE) */
adapter->pts_at_discont = pts;
adapter->dts_at_discont = dts;
adapter->offset_at_discont = offset;
adapter->distance_from_discont = 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);
update_timestamps_and_offset (adapter, 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 *g = adapter->buflist;
gsize cur_samples;
/* clear state */
adapter->samples -= to_flush;
/* take skip into account */
to_flush += adapter->skip;
/* distance is always at least the amount of skipped samples */
adapter->pts_distance -= adapter->skip;
adapter->dts_distance -= adapter->skip;
adapter->offset_distance -= adapter->skip;
adapter->distance_from_discont -= adapter->skip;
g = adapter->buflist;
cur_samples = gst_buffer_get_audio_meta (g->data)->samples;
while (to_flush >= cur_samples) {
/* can skip whole buffer */
GST_LOG_OBJECT (adapter, "flushing out head buffer");
adapter->pts_distance += cur_samples;
adapter->dts_distance += cur_samples;
adapter->offset_distance += cur_samples;
adapter->distance_from_discont += cur_samples;
to_flush -= cur_samples;
gst_buffer_unref (g->data);
g = g_slist_delete_link (g, g);
--adapter->count;
if (G_UNLIKELY (g == NULL)) {
GST_LOG_OBJECT (adapter, "adapter empty now");
adapter->buflist_end = NULL;
break;
}
/* there is a new head buffer, update the timestamps */
update_timestamps_and_offset (adapter, g->data);
cur_samples = gst_buffer_get_audio_meta (g->data)->samples;
}
adapter->buflist = g;
/* account for the remaining bytes */
adapter->skip = to_flush;
adapter->pts_distance += to_flush;
adapter->dts_distance += to_flush;
adapter->offset_distance += to_flush;
adapter->distance_from_discont += to_flush;
}
/**
* 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 (cur_node && 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;
}
/**
* gst_planar_audio_adapter_get_distance_from_discont:
* @adapter: a #GstPlanarAudioAdapter
*
* Get the distance in samples since the last buffer with the
* %GST_BUFFER_FLAG_DISCONT flag.
*
* The distance will be reset to 0 for all buffers with
* %GST_BUFFER_FLAG_DISCONT on them, and then calculated for all other
* following buffers based on their size.
*
* Returns: The offset. Can be %GST_BUFFER_OFFSET_NONE.
*/
guint64
gst_planar_audio_adapter_distance_from_discont (GstPlanarAudioAdapter * adapter)
{
return adapter->distance_from_discont;
}
/**
* gst_planar_audio_adapter_offset_at_discont:
* @adapter: a #GstPlanarAudioAdapter
*
* Get the offset that was on the last buffer with the GST_BUFFER_FLAG_DISCONT
* flag, or GST_BUFFER_OFFSET_NONE.
*
* Returns: The offset at the last discont or GST_BUFFER_OFFSET_NONE.
*/
guint64
gst_planar_audio_adapter_offset_at_discont (GstPlanarAudioAdapter * adapter)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_BUFFER_OFFSET_NONE);
return adapter->offset_at_discont;
}
/**
* gst_planar_audio_adapter_pts_at_discont:
* @adapter: a #GstPlanarAudioAdapter
*
* Get the PTS that was on the last buffer with the GST_BUFFER_FLAG_DISCONT
* flag, or GST_CLOCK_TIME_NONE.
*
* Returns: The PTS at the last discont or GST_CLOCK_TIME_NONE.
*/
GstClockTime
gst_planar_audio_adapter_pts_at_discont (GstPlanarAudioAdapter * adapter)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_CLOCK_TIME_NONE);
return adapter->pts_at_discont;
}
/**
* gst_planar_audio_adapter_dts_at_discont:
* @adapter: a #GstPlanarAudioAdapter
*
* Get the DTS that was on the last buffer with the GST_BUFFER_FLAG_DISCONT
* flag, or GST_CLOCK_TIME_NONE.
*
* Returns: The DTS at the last discont or GST_CLOCK_TIME_NONE.
*/
GstClockTime
gst_planar_audio_adapter_dts_at_discont (GstPlanarAudioAdapter * adapter)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_CLOCK_TIME_NONE);
return adapter->dts_at_discont;
}
/**
* gst_planar_audio_adapter_prev_offset:
* @adapter: a #GstPlanarAudioAdapter
* @distance: (out) (allow-none): pointer to a location for distance, or %NULL
*
* Get the offset that was before the current sample in the adapter. When
* @distance is given, the amount of samples between the offset and the current
* position is returned.
*
* The offset is reset to GST_BUFFER_OFFSET_NONE and the distance is set to 0
* when the adapter is first created or when it is cleared. This also means that
* before the first sample with an offset is removed from the adapter, the
* offset and distance returned are GST_BUFFER_OFFSET_NONE and 0 respectively.
*
* Returns: The previous seen offset.
*/
guint64
gst_planar_audio_adapter_prev_offset (GstPlanarAudioAdapter * adapter,
guint64 * distance)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_BUFFER_OFFSET_NONE);
if (distance)
*distance = adapter->offset_distance;
return adapter->offset;
}
/**
* gst_planar_audio_adapter_prev_pts:
* @adapter: a #GstPlanarAudioAdapter
* @distance: (out) (allow-none): pointer to location for distance, or %NULL
*
* Get the pts that was before the current sample in the adapter. When
* @distance is given, the amount of samples between the pts and the current
* position is returned.
*
* The pts is reset to GST_CLOCK_TIME_NONE and the distance is set to 0 when
* the adapter is first created or when it is cleared. This also means that before
* the first sample with a pts is removed from the adapter, the pts
* and distance returned are GST_CLOCK_TIME_NONE and 0 respectively.
*
* Returns: The previously seen pts.
*/
GstClockTime
gst_planar_audio_adapter_prev_pts (GstPlanarAudioAdapter * adapter,
guint64 * distance)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_CLOCK_TIME_NONE);
if (distance)
*distance = adapter->pts_distance;
return adapter->pts;
}
/**
* gst_planar_audio_adapter_prev_dts:
* @adapter: a #GstPlanarAudioAdapter
* @distance: (out) (allow-none): pointer to location for distance, or %NULL
*
* Get the dts that was before the current sample in the adapter. When
* @distance is given, the amount of bytes between the dts and the current
* position is returned.
*
* The dts is reset to GST_CLOCK_TIME_NONE and the distance is set to 0 when
* the adapter is first created or when it is cleared. This also means that
* before the first sample with a dts is removed from the adapter, the dts
* and distance returned are GST_CLOCK_TIME_NONE and 0 respectively.
*
* Returns: The previously seen dts.
*/
GstClockTime
gst_planar_audio_adapter_prev_dts (GstPlanarAudioAdapter * adapter,
guint64 * distance)
{
g_return_val_if_fail (GST_IS_PLANAR_AUDIO_ADAPTER (adapter),
GST_CLOCK_TIME_NONE);
if (distance)
*distance = adapter->dts_distance;
return adapter->dts;
}