diff --git a/gst-libs/gst/audio/Makefile.am b/gst-libs/gst/audio/Makefile.am index b1c62b425a..07d1fe754c 100644 --- a/gst-libs/gst/audio/Makefile.am +++ b/gst-libs/gst/audio/Makefile.am @@ -35,6 +35,7 @@ EXTRA_DIST += dbesi0.c libgstaudio_@GST_API_VERSION@_la_SOURCES = \ audio.c \ + audio-buffer.c \ audio-format.c \ audio-channels.c \ audio-channel-mixer.c \ @@ -66,6 +67,7 @@ libgstaudio_@GST_API_VERSION@include_HEADERS = \ audio.h \ audio-prelude.h \ audio-format.h \ + audio-buffer.h \ audio-channels.h \ audio-channel-mixer.h \ audio-converter.h \ diff --git a/gst-libs/gst/audio/audio-buffer.c b/gst-libs/gst/audio/audio-buffer.c new file mode 100644 index 0000000000..081d5871ac --- /dev/null +++ b/gst-libs/gst/audio/audio-buffer.c @@ -0,0 +1,185 @@ +/* GStreamer + * Copyright (C) <2018> Collabora Ltd. + * @author George Kiagiadakis + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "audio-buffer.h" + + +static void +gst_audio_buffer_unmap_internal (GstAudioBuffer * buffer, guint n_unmap) +{ + guint i; + for (i = 0; i < n_unmap; i++) { + gst_buffer_unmap (buffer->buffer, &buffer->map_infos[i]); + } + if (buffer->planes != buffer->priv_planes_arr) + g_slice_free1 (buffer->n_planes * sizeof (gpointer), buffer->planes); + if (buffer->map_infos != buffer->priv_map_infos_arr) + g_slice_free1 (buffer->n_planes * sizeof (GstMapInfo), buffer->map_infos); +} + +/** + * gst_audio_buffer_unmap: + * @buffer: the #GstAudioBuffer to unmap + * + * Unmaps an audio buffer that was previously mapped with + * gst_audio_buffer_map(). + * + * Since: 1.16 + */ +void +gst_audio_buffer_unmap (GstAudioBuffer * buffer) +{ + gst_audio_buffer_unmap_internal (buffer, buffer->n_planes); +} + +/** + * gst_audio_buffer_map: + * @buffer: pointer to a #GstAudioBuffer + * @info: the audio properties of the buffer + * @gstbuffer: (transfer none): the #GstBuffer to be mapped + * @flags: the access mode for the memory + * + * Maps an audio @gstbuffer so that it can be read or written and stores the + * result of the map operation in @buffer. + * + * This is especially useful when the @gstbuffer is in non-interleaved (planar) + * layout, in which case this function will use the information in the + * @gstbuffer's attached #GstAudioMeta in order to map each channel in a + * separate "plane" in #GstAudioBuffer. If a #GstAudioMeta is not attached + * on the @gstbuffer, then it must be in interleaved layout. + * + * If a #GstAudioMeta is attached, then the #GstAudioInfo on the meta is checked + * against @info. Normally, they should be equal, but in case they are not, + * a g_critical will be printed and the #GstAudioInfo from the meta will be + * used. + * + * In non-interleaved buffers, it is possible to have each channel on a separate + * #GstMemory. In this case, each memory will be mapped separately to avoid + * copying their contents in a larger memory area. Do note though that it is + * not supported to have a single channel spanning over two or more different + * #GstMemory objects. Although the map operation will likely succeed in this + * case, it will be highly sub-optimal and it is recommended to merge all the + * memories in the buffer before calling this function. + * + * Note: The actual #GstBuffer is not ref'ed, but it is required to stay valid + * as long as it's mapped. + * + * Returns: %TRUE if the map operation succeeded or %FALSE on failure + * + * Since: 1.16 + */ +gboolean +gst_audio_buffer_map (GstAudioBuffer * buffer, const GstAudioInfo * info, + GstBuffer * gstbuffer, GstMapFlags flags) +{ + GstAudioMeta *meta = NULL; + guint i = 0, idx, length; + gsize skip; + + g_return_val_if_fail (buffer != NULL, FALSE); + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (info), FALSE); + g_return_val_if_fail (GST_AUDIO_INFO_FORMAT (info) != + GST_AUDIO_FORMAT_UNKNOWN, FALSE); + g_return_val_if_fail (GST_IS_BUFFER (gstbuffer), FALSE); + + meta = gst_buffer_get_audio_meta (gstbuffer); + + /* be strict on the layout */ + g_return_val_if_fail ((!meta && info->layout == GST_AUDIO_LAYOUT_INTERLEAVED) + || (meta && info->layout == meta->info.layout), FALSE); + + /* and not so strict on other fields */ + if (G_UNLIKELY (meta && !gst_audio_info_is_equal (&meta->info, info))) { + g_critical ("the GstAudioInfo argument is not equal " + "to the GstAudioMeta's attached info"); + } + + if (meta) { + /* make sure that the meta doesn't imply having more samples than + * what's actually possible to store in this buffer */ + g_return_val_if_fail (meta->samples <= + gst_buffer_get_size (gstbuffer) / GST_AUDIO_INFO_BPF (&meta->info), + FALSE); + buffer->n_samples = meta->samples; + } else { + buffer->n_samples = + gst_buffer_get_size (gstbuffer) / GST_AUDIO_INFO_BPF (info); + } + + buffer->info = meta ? meta->info : *info; + buffer->buffer = gstbuffer; + + if (GST_AUDIO_BUFFER_LAYOUT (buffer) == GST_AUDIO_LAYOUT_INTERLEAVED) { + /* interleaved */ + buffer->n_planes = 1; + buffer->planes = buffer->priv_planes_arr; + buffer->map_infos = buffer->priv_map_infos_arr; + + if (!gst_buffer_map (gstbuffer, &buffer->map_infos[0], flags)) + return FALSE; + + buffer->planes[0] = buffer->map_infos[0].data; + } else { + /* non-interleaved */ + buffer->n_planes = GST_AUDIO_BUFFER_CHANNELS (buffer); + + if (G_UNLIKELY (buffer->n_planes > 8)) { + buffer->planes = g_slice_alloc (buffer->n_planes * sizeof (gpointer)); + buffer->map_infos = + g_slice_alloc (buffer->n_planes * sizeof (GstMapInfo)); + } else { + buffer->planes = buffer->priv_planes_arr; + buffer->map_infos = buffer->priv_map_infos_arr; + } + + for (i = 0; i < buffer->n_planes; i++) { + if (!gst_buffer_find_memory (gstbuffer, meta->offsets[i], + GST_AUDIO_BUFFER_PLANE_SIZE (buffer), &idx, &length, &skip)) + goto no_memory; + + if (!gst_buffer_map_range (gstbuffer, idx, length, &buffer->map_infos[i], + flags)) + goto cannot_map; + + buffer->planes[i] = buffer->map_infos[0].data + skip; + } + } + + return TRUE; + +no_memory: + { + GST_DEBUG ("plane %u, no memory at offset %" G_GSIZE_FORMAT, i, + meta->offsets[i]); + gst_audio_buffer_unmap_internal (buffer, i); + return FALSE; + } +cannot_map: + { + GST_DEBUG ("cannot map memory range %u-%u", idx, length); + gst_audio_buffer_unmap_internal (buffer, i); + return FALSE; + } +} diff --git a/gst-libs/gst/audio/audio-buffer.h b/gst-libs/gst/audio/audio-buffer.h new file mode 100644 index 0000000000..075b59d35e --- /dev/null +++ b/gst-libs/gst/audio/audio-buffer.h @@ -0,0 +1,98 @@ +/* GStreamer + * Copyright (C) <2018> Collabora Ltd. + * @author George Kiagiadakis + * + * 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. + */ + +#ifndef __GST_AUDIO_AUDIO_H__ +#include +#endif + +#ifndef __GST_AUDIO_BUFFER_H__ +#define __GST_AUDIO_BUFFER_H__ + +G_BEGIN_DECLS + +/** + * GstAudioBuffer: + * @info: a #GstAudioInfo describing the audio properties of this buffer + * @n_samples: the size of the buffer in samples + * @n_planes: the number of planes available + * @planes: an array of @n_planes pointers pointing to the start of each + * plane in the mapped buffer + * @buffer: the mapped buffer + * + * A structure containing the result of an audio buffer map operation, + * which is executed with gst_audio_buffer_map(). For non-interleaved (planar) + * buffers, the beginning of each channel in the buffer has its own pointer in + * the @planes array. For interleaved buffers, the @planes array only contains + * one item, which is the pointer to the beginning of the buffer, and @n_planes + * equals 1. + * + * The different channels in @planes are always in the GStreamer channel order. + * + * Since: 1.16 + */ +typedef struct { + GstAudioInfo info; + + gsize n_samples; + gint n_planes; + gpointer *planes; + + GstBuffer *buffer; + + /*< private >*/ + GstMapInfo *map_infos; + gpointer priv_planes_arr[8]; + GstMapInfo priv_map_infos_arr[8]; + + gpointer _gst_reserved[GST_PADDING]; +} GstAudioBuffer; + + +GST_AUDIO_API +gboolean gst_audio_buffer_map (GstAudioBuffer *buffer, const GstAudioInfo *info, + GstBuffer *gstbuffer, GstMapFlags flags); + +GST_AUDIO_API +void gst_audio_buffer_unmap (GstAudioBuffer *buffer); + + +#define GST_AUDIO_BUFFER_FORMAT(b) (GST_AUDIO_INFO_FORMAT(&(b)->info)) +#define GST_AUDIO_BUFFER_CHANNELS(b) (GST_AUDIO_INFO_CHANNELS(&(b)->info)) +#define GST_AUDIO_BUFFER_LAYOUT(b) (GST_AUDIO_INFO_LAYOUT(&(b)->info)) +#define GST_AUDIO_BUFFER_RATE(b) (GST_AUDIO_INFO_RATE(&(b)->info)) + +#define GST_AUDIO_BUFFER_WIDTH(b) (GST_AUDIO_INFO_WIDTH(&(b)->info)) +#define GST_AUDIO_BUFFER_DEPTH(b) (GST_AUDIO_INFO_DEPTH(&(b)->info)) +#define GST_AUDIO_BUFFER_SAMPLE_STRIDE(b) (GST_AUDIO_INFO_WIDTH(&(b)->info) >> 3) +#define GST_AUDIO_BUFFER_BPS(b) (GST_AUDIO_INFO_DEPTH(&(b)->info) >> 3) +#define GST_AUDIO_BUFFER_BPF(b) (GST_AUDIO_INFO_BPF(&(b)->info)) + +#define GST_AUDIO_BUFFER_N_SAMPLES(b) ((b)->n_samples) +#define GST_AUDIO_BUFFER_N_PLANES(b) ((b)->n_planes) +#define GST_AUDIO_BUFFER_PLANE_DATA(b,p) ((b)->planes[p]) + +/* the size of each plane in bytes */ +#define GST_AUDIO_BUFFER_PLANE_SIZE(b) \ + (GST_AUDIO_BUFFER_N_SAMPLES(b) * GST_AUDIO_BUFFER_SAMPLE_STRIDE(b) * \ + GST_AUDIO_BUFFER_CHANNELS(b) / GST_AUDIO_BUFFER_N_PLANES(b)) + +G_END_DECLS + +#endif /* __GST_AUDIO_BUFFER_H__ */ diff --git a/gst-libs/gst/audio/audio.h b/gst-libs/gst/audio/audio.h index dad01c0ef2..16a985dc4d 100644 --- a/gst-libs/gst/audio/audio.h +++ b/gst-libs/gst/audio/audio.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/gst-libs/gst/audio/gstaudiometa.c b/gst-libs/gst/audio/gstaudiometa.c index 1c33fc9bef..240089d329 100644 --- a/gst-libs/gst/audio/gstaudiometa.c +++ b/gst-libs/gst/audio/gstaudiometa.c @@ -304,3 +304,188 @@ gst_audio_clipping_meta_get_info (void) } return audio_clipping_meta_info; } + + +static gboolean +gst_audio_meta_init (GstMeta * meta, gpointer params, GstBuffer * buffer) +{ + GstAudioMeta *ameta = (GstAudioMeta *) meta; + + gst_audio_info_init (&ameta->info); + ameta->samples = 0; + ameta->offsets = NULL; + + return TRUE; +} + +static void +gst_audio_meta_free (GstMeta * meta, GstBuffer * buffer) +{ + GstAudioMeta *ameta = (GstAudioMeta *) meta; + + if (ameta->offsets && ameta->offsets != ameta->priv_offsets_arr) + g_slice_free1 (ameta->info.channels * sizeof (gsize), ameta->offsets); +} + +static gboolean +gst_audio_meta_transform (GstBuffer * dest, GstMeta * meta, + GstBuffer * buffer, GQuark type, gpointer data) +{ + GstAudioMeta *smeta, *dmeta; + + smeta = (GstAudioMeta *) meta; + + if (GST_META_TRANSFORM_IS_COPY (type)) { + dmeta = gst_buffer_add_audio_meta (dest, &smeta->info, smeta->samples, + smeta->offsets); + if (!dmeta) + return FALSE; + } else { + /* return FALSE, if transform type is not supported */ + return FALSE; + } + + return TRUE; +} + +/** + * gst_buffer_add_audio_meta: + * @buffer: a #GstBuffer + * @info: the audio properties of the buffer + * @samples: the number of valid samples in the buffer + * @offsets: (nullable): the offsets (in bytes) where each channel plane starts + * in the buffer or %NULL to calculate it (see below); must be %NULL also + * when @info->layout is %GST_AUDIO_LAYOUT_INTERLEAVED + * + * Allocates and attaches a #GstAudioMeta on @buffer, which must be writable + * for that purpose. The fields of the #GstAudioMeta are directly populated + * from the arguments of this function. + * + * When @info->layout is %GST_AUDIO_LAYOUT_NON_INTERLEAVED and @offsets is + * %NULL, the offsets are calculated with a formula that assumes the planes are + * tightly packed and in sequence: + * offsets[channel] = channel * @samples * sample_stride + * + * It is not allowed for channels to overlap in memory, + * i.e. for each i in [0, channels), the range + * [@offsets[i], @offsets[i] + @samples * sample_stride) must not overlap + * with any other such range. This function will assert if the parameters + * specified cause this restriction to be violated. + * + * It is, obviously, also not allowed to specify parameters that would cause + * out-of-bounds memory access on @buffer. This is also checked, which means + * that you must add enough memory on the @buffer before adding this meta. + * + * Returns: the #GstAudioMeta that was attached on the @buffer + * + * Since: 1.16 + */ +GstAudioMeta * +gst_buffer_add_audio_meta (GstBuffer * buffer, const GstAudioInfo * info, + gsize samples, gsize offsets[]) +{ + GstAudioMeta *meta; + gint i; + gsize plane_size; + + g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE); + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (GST_AUDIO_INFO_IS_VALID (info), NULL); + g_return_val_if_fail (GST_AUDIO_INFO_FORMAT (info) != + GST_AUDIO_FORMAT_UNKNOWN, NULL); + g_return_val_if_fail (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED + || !offsets, NULL); + + meta = + (GstAudioMeta *) gst_buffer_add_meta (buffer, GST_AUDIO_META_INFO, NULL); + + meta->info = *info; + meta->samples = samples; + plane_size = samples * info->finfo->width / 8; + + if (info->layout == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { +#ifndef G_DISABLE_CHECKS + gsize max_offset = 0; + gint j; +#endif + + if (G_UNLIKELY (info->channels > 8)) + meta->offsets = g_slice_alloc (info->channels * sizeof (gsize)); + else + meta->offsets = meta->priv_offsets_arr; + + if (offsets) { + for (i = 0; i < info->channels; i++) { + meta->offsets[i] = offsets[i]; +#ifndef G_DISABLE_CHECKS + max_offset = MAX (max_offset, offsets[i]); + for (j = 0; j < info->channels; j++) { + if (i != j && !(offsets[j] + plane_size <= offsets[i] + || offsets[i] + plane_size <= offsets[j])) { + g_critical ("GstAudioMeta properties would cause channel memory " + "areas to overlap! offsets: %" G_GSIZE_FORMAT " (%d), %" + G_GSIZE_FORMAT " (%d) with plane size %" G_GSIZE_FORMAT, + offsets[i], i, offsets[j], j, plane_size); + gst_buffer_remove_meta (buffer, (GstMeta *) meta); + return NULL; + } + } +#endif + } + } else { + /* default offsets assume channels are laid out sequentially in memory */ + for (i = 0; i < info->channels; i++) + meta->offsets[i] = i * plane_size; +#ifndef G_DISABLE_CHECKS + max_offset = meta->offsets[info->channels - 1]; +#endif + } + +#ifndef G_DISABLE_CHECKS + if (max_offset + plane_size > gst_buffer_get_size (buffer)) { + g_critical ("GstAudioMeta properties would cause " + "out-of-bounds memory access on the buffer: max_offset %" + G_GSIZE_FORMAT ", samples %" G_GSIZE_FORMAT ", bps %u, buffer size %" + G_GSIZE_FORMAT, max_offset, samples, info->finfo->width / 8, + gst_buffer_get_size (buffer)); + gst_buffer_remove_meta (buffer, (GstMeta *) meta); + return NULL; + } +#endif + } + + return meta; +} + +GType +gst_audio_meta_api_get_type (void) +{ + static volatile GType type; + static const gchar *tags[] = { + GST_META_TAG_AUDIO_STR, GST_META_TAG_AUDIO_CHANNELS_STR, + GST_META_TAG_AUDIO_RATE_STR, NULL + }; + + if (g_once_init_enter (&type)) { + GType _type = gst_meta_api_type_register ("GstAudioMetaAPI", tags); + g_once_init_leave (&type, _type); + } + return type; +} + +const GstMetaInfo * +gst_audio_meta_get_info (void) +{ + static const GstMetaInfo *audio_meta_info = NULL; + + if (g_once_init_enter ((GstMetaInfo **) & audio_meta_info)) { + const GstMetaInfo *meta = gst_meta_register (GST_AUDIO_META_API_TYPE, + "GstAudioMeta", sizeof (GstAudioMeta), + gst_audio_meta_init, + gst_audio_meta_free, + gst_audio_meta_transform); + g_once_init_leave ((GstMetaInfo **) & audio_meta_info, + (GstMetaInfo *) meta); + } + return audio_meta_info; +} diff --git a/gst-libs/gst/audio/gstaudiometa.h b/gst-libs/gst/audio/gstaudiometa.h index 2f6c72f7f9..cc05b06fed 100644 --- a/gst-libs/gst/audio/gstaudiometa.h +++ b/gst-libs/gst/audio/gstaudiometa.h @@ -125,6 +125,79 @@ GstAudioClippingMeta * gst_buffer_add_audio_clipping_meta (GstBuffer *buffer, guint64 start, guint64 end); + +#define GST_AUDIO_META_API_TYPE (gst_audio_meta_api_get_type()) +#define GST_AUDIO_META_INFO (gst_audio_meta_get_info()) + +typedef struct _GstAudioMeta GstAudioMeta; + +/** + * GstAudioMeta: + * @meta: parent #GstMeta + * @info: the audio properties of the buffer + * @samples: the number of valid samples in the buffer + * @offsets: the offsets (in bytes) where each channel plane starts in the + * buffer or %NULL if the buffer has interleaved layout; if not %NULL, this + * is guaranteed to be an array of @info.channels elements + * + * Buffer metadata describing how data is laid out inside the buffer. This + * is useful for non-interleaved (planar) buffers, where it is necessary to + * have a place to store where each plane starts and how long each plane is. + * + * It is a requirement for non-interleaved buffers to have this metadata + * attached and to be mapped with gst_audio_buffer_map() in order to ensure + * correct handling of cliping and channel reordering. + * + * The different channels in @offsets are always in the GStreamer channel order. + * Zero-copy channel reordering can be implemented by swapping the values in + * @offsets. + * + * It is not allowed for channels to overlap in memory, + * i.e. for each i in [0, channels), the range + * [@offsets[i], @offsets[i] + @samples * sample_stride) must not overlap + * with any other such range. + * + * It is, however, allowed to have parts of the buffer memory unused, + * by using @offsets and @samples in such a way that leave gaps on it. + * This is used to implement zero-copy clipping in non-interleaved buffers. + * + * Obviously, due to the above, it is not safe to infer the + * number of valid samples from the size of the buffer. You should always + * use the @samples variable of this metadata. + * + * Note that for interleaved audio it is not a requirement to have this + * metadata attached and at the moment of writing, there is actually no use + * case to do so. It is, however, allowed to attach it, for some potential + * future use case. + * + * Since: 1.16 + */ +struct _GstAudioMeta { + GstMeta meta; + + GstAudioInfo info; + gsize samples; + gsize *offsets; + + /*< private >*/ + gsize priv_offsets_arr[8]; + gpointer _gst_reserved[GST_PADDING]; +}; + +GST_AUDIO_API +GType gst_audio_meta_api_get_type (void); + +GST_AUDIO_API +const GstMetaInfo * gst_audio_meta_get_info (void); + +#define gst_buffer_get_audio_meta(b) \ + ((GstAudioMeta*)gst_buffer_get_meta((b), GST_AUDIO_META_API_TYPE)) + +GST_AUDIO_API +GstAudioMeta * gst_buffer_add_audio_meta (GstBuffer *buffer, + const GstAudioInfo *info, + gsize samples, gsize offsets[]); + G_END_DECLS #endif /* __GST_AUDIO_META_H__ */ diff --git a/gst-libs/gst/audio/meson.build b/gst-libs/gst/audio/meson.build index 0dbaf7d820..bf8ce1d112 100644 --- a/gst-libs/gst/audio/meson.build +++ b/gst-libs/gst/audio/meson.build @@ -1,5 +1,6 @@ audio_src= [ 'audio.c', + 'audio-buffer.c', 'audio-channel-mixer.c', 'audio-channels.c', 'audio-converter.c', @@ -45,6 +46,7 @@ audio_mkenum_headers = [ # FIXME: check headers audio_headers = audio_mkenum_headers + [ 'audio-prelude.h', + 'audio-buffer.h', 'gstaudiobasesink.h', 'gstaudiobasesrc.h', 'gstaudiocdsrc.h',