From 8c5a8f4466963ab2c8d7ba005b5d9767ed30e006 Mon Sep 17 00:00:00 2001 From: Carlos Rafael Giani Date: Mon, 17 Oct 2022 10:00:07 +0200 Subject: [PATCH] dsd: Add code for DSD audio support Related to: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/issues/972 Part-of: --- .../gst-docs/symbols/symbol_index.json | 24 + .../docs/plugins/gst_plugins_cache.json | 38 + .../gst-libs/gst/audio/audio.h | 1 + .../gst-libs/gst/audio/gstdsd.c | 1068 +++++++++++++++++ .../gst-libs/gst/audio/gstdsd.h | 338 ++++++ .../gst-libs/gst/audio/gstdsdformat.c | 109 ++ .../gst-libs/gst/audio/gstdsdformat.h | 86 ++ .../gst-libs/gst/audio/meson.build | 4 + .../gst-plugins-base/gst/dsd/gstdsdconvert.c | 397 ++++++ .../gst-plugins-base/gst/dsd/gstdsdconvert.h | 35 + .../gst-plugins-base/gst/dsd/meson.build | 14 + subprojects/gst-plugins-base/gst/dsd/plugin.c | 42 + subprojects/gst-plugins-base/gst/meson.build | 6 +- .../gst-plugins-base/meson_options.txt | 1 + .../gst-plugins-base/tests/check/libs/dsd.c | 385 ++++++ .../gst-plugins-base/tests/check/meson.build | 1 + 16 files changed, 2546 insertions(+), 3 deletions(-) create mode 100644 subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.c create mode 100644 subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.h create mode 100644 subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.c create mode 100644 subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.h create mode 100644 subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.c create mode 100644 subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.h create mode 100644 subprojects/gst-plugins-base/gst/dsd/meson.build create mode 100644 subprojects/gst-plugins-base/gst/dsd/plugin.c create mode 100644 subprojects/gst-plugins-base/tests/check/libs/dsd.c diff --git a/subprojects/gst-docs/symbols/symbol_index.json b/subprojects/gst-docs/symbols/symbol_index.json index 0453276977..ee3f5e987e 100644 --- a/subprojects/gst-docs/symbols/symbol_index.json +++ b/subprojects/gst-docs/symbols/symbol_index.json @@ -1121,6 +1121,7 @@ "GST_AUDIO_RING_BUFFER_CAST", "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_AC3", "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_A_LAW", + "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DSD", "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_DTS", "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_EAC3", "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_FLAC", @@ -1136,6 +1137,14 @@ "GST_AUDIO_RING_BUFFER_FORMAT_TYPE_RAW", "GST_AUDIO_RING_BUFFER_GET_COND", "GST_AUDIO_RING_BUFFER_SIGNAL", + "GST_AUDIO_RING_BUFFER_SPEC_FORMAT_TYPE", + "GST_AUDIO_RING_BUFFER_SPEC_INFO", + "GST_AUDIO_RING_BUFFER_SPEC_LATENCY_TIME", + "GST_AUDIO_RING_BUFFER_SPEC_BUFFER_TIME", + "GST_AUDIO_RING_BUFFER_SPEC_SEGSIZE", + "GST_AUDIO_RING_BUFFER_SPEC_SEGTOTAL", + "GST_AUDIO_RING_BUFFER_SPEC_SEGLATENCY", + "GST_AUDIO_RING_BUFFER_SPEC_DSD_FORMAT", "GST_AUDIO_RING_BUFFER_STATE_ERROR", "GST_AUDIO_RING_BUFFER_STATE_PAUSED", "GST_AUDIO_RING_BUFFER_STATE_STARTED", @@ -1651,6 +1660,16 @@ "GST_DISCOVERER_URI_INVALID", "GST_DMABUF_ALLOCATOR_CAST", "GST_DO_CHECK_TEST_ENVIRONMENT", + "GST_DSD_INFO_CHANNELS", + "GST_DSD_INFO_FORMAT", + "GST_DSD_INFO_IS_VALID", + "GST_DSD_INFO_LAYOUT", + "GST_DSD_INFO_POSITION", + "GST_DSD_INFO_RATE", + "GST_DSD_INFO_REVERSED_BYTES", + "GST_DSD_INFO_STRIDE", + "GST_DSD_PLANE_OFFSET_META_API_TYPE", + "GST_DSD_PLANE_OFFSET_META_INFO", "GST_DVB_SERVICE_ADVANCED_CODEC_DIGITAL_RADIO_SOUND", "GST_DVB_SERVICE_ADVANCED_CODEC_HD_DIGITAL_TELEVISION", "GST_DVB_SERVICE_ADVANCED_CODEC_HD_NVOD_REFERENCE", @@ -7125,6 +7144,7 @@ "GstAudioRingBufferClass::stop", "GstAudioRingBufferFormatType", "GstAudioRingBufferSpec", + "GstAudioRingBufferSpec.ABI.abi.dsd_format", "GstAudioRingBufferSpec.buffer_time", "GstAudioRingBufferSpec.caps", "GstAudioRingBufferSpec.info", @@ -19583,6 +19603,7 @@ "_FLAG", "_GST_AUDIO_FORMAT_NE", "_GST_AUDIO_FORMAT_OE", + "_GST_DSD_FORMAT_NE", "_GST_FAST_READ", "_GST_FAST_READ_SWAP", "_GST_FAST_WRITE", @@ -38376,6 +38397,7 @@ "gst_buffer_get_audio_level_meta", "gst_buffer_get_audio_meta", "gst_buffer_get_custom_meta", + "gst_buffer_get_dsd_plane_offset_meta", "gst_buffer_get_flags", "gst_buffer_get_gl_sync_meta", "gst_buffer_get_max_memory", @@ -39120,6 +39142,8 @@ "gst_dmabuf_allocator_alloc_with_flags", "gst_dmabuf_allocator_new", "gst_dmabuf_memory_get_fd", + "gst_dsd_plane_offset_meta_api_get_type", + "gst_dsd_plane_offset_meta_get_info", "gst_dynamic_type_factory_load", "gst_dynamic_type_register", "gst_egl_get_error_string", diff --git a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json index fc088c9c63..cc50f8f121 100644 --- a/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-base/docs/plugins/gst_plugins_cache.json @@ -2121,6 +2121,44 @@ "tracers": {}, "url": "Unknown package origin" }, + "dsd": { + "description": "elements for processing DSD audio", + "elements": { + "dsdconvert": { + "author": "Carlos Rafael Giani ", + "description": "Convert between different DSD grouping formats", + "hierarchy": [ + "GstDsdConvert", + "GstBaseTransform", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "klass": "Filter/Converter/Audio", + "pad-templates": { + "sink": { + "caps": "audio/x-dsd:\n format: { DSDU32BE, DSDU16BE, DSDU8, DSDU32LE, DSDU16LE }\n rate: [ 1, 2147483647 ]\n layout: { (string)interleaved, (string)non-interleaved }\n reversed-bytes: { (boolean)false, (boolean)true }\n channels: [ 1, 2147483647 ]\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "audio/x-dsd:\n format: { DSDU32BE, DSDU16BE, DSDU8, DSDU32LE, DSDU16LE }\n rate: [ 1, 2147483647 ]\n layout: { (string)interleaved, (string)non-interleaved }\n reversed-bytes: { (boolean)false, (boolean)true }\n channels: [ 1, 2147483647 ]\n", + "direction": "src", + "presence": "always" + } + }, + "rank": "secondary" + } + }, + "filename": "gstdsd", + "license": "LGPL", + "other-types": {}, + "package": "GStreamer Base Plug-ins", + "source": "gst-plugins-base", + "tracers": {}, + "url": "Unknown package origin" + }, "encoding": { "description": "various encoding-related elements", "elements": { diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/audio.h b/subprojects/gst-plugins-base/gst-libs/gst/audio/audio.h index a0aa8a6031..b347b162d0 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/audio/audio.h +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/audio.h @@ -35,6 +35,7 @@ #include #include #include +#include G_BEGIN_DECLS diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.c b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.c new file mode 100644 index 0000000000..31493b415b --- /dev/null +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.c @@ -0,0 +1,1068 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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 +#include "gstdsd.h" + +#ifndef GST_DISABLE_GST_DEBUG +#define GST_CAT_DEFAULT ensure_debug_category() +static GstDebugCategory * +ensure_debug_category (void) +{ + static gsize cat_gonce = 0; + + if (g_once_init_enter (&cat_gonce)) { + gsize cat_done; + + cat_done = (gsize) _gst_debug_category_new ("gst-dsd", 0, "GStreamer DSD"); + + g_once_init_leave (&cat_gonce, cat_done); + } + + return (GstDebugCategory *) cat_gonce; +} +#else +#define ensure_debug_category() /* NOOP */ +#endif /* GST_DISABLE_GST_DEBUG */ + +static const guint8 byte_bit_reversal_table[256] = { + 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, + 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, + 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, + 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, + 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, + 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, + 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, + 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, + 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, + 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, + 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, + 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, + 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, + 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, + 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, + 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, + 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, + 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, + 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, + 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, + 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, + 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, + 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, + 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, + 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, + 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, + 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, + 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, + 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, + 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, + 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, + 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff, +}; + +static const char * +layout_to_string (GstAudioLayout layout) +{ + const char *layout_str = NULL; + + switch (layout) { + case GST_AUDIO_LAYOUT_INTERLEAVED: + layout_str = "interleaved"; + break; + case GST_AUDIO_LAYOUT_NON_INTERLEAVED: + layout_str = "non-interleaved"; + break; + default: + g_return_val_if_reached (NULL); + } + + return layout_str; +} + +static gboolean +gst_dsd_plane_offset_meta_init (GstMeta * meta, gpointer params, + GstBuffer * buffer) +{ + GstDsdPlaneOffsetMeta *ofs_meta = (GstDsdPlaneOffsetMeta *) meta; + ofs_meta->offsets = NULL; + + return TRUE; +} + +static void +gst_dsd_plane_offset_meta_free (GstMeta * meta, GstBuffer * buffer) +{ + GstDsdPlaneOffsetMeta *ofs_meta = (GstDsdPlaneOffsetMeta *) meta; + + if (ofs_meta->offsets && ofs_meta->offsets != ofs_meta->priv_offsets_arr) + g_free (ofs_meta->offsets); +} + +static gboolean +gst_dsd_plane_offset_meta_transform (GstBuffer * dest, GstMeta * meta, + GstBuffer * buffer, GQuark type, gpointer data) +{ + GstDsdPlaneOffsetMeta *smeta, *dmeta; + + smeta = (GstDsdPlaneOffsetMeta *) meta; + + if (GST_META_TRANSFORM_IS_COPY (type)) { + dmeta = gst_buffer_add_dsd_plane_offset_meta (dest, smeta->num_channels, + smeta->num_bytes_per_channel, smeta->offsets); + if (!dmeta) + return FALSE; + } else { + /* return FALSE, if transform type is not supported */ + return FALSE; + } + + return TRUE; +} + +GType +gst_dsd_plane_offset_meta_api_get_type (void) +{ + static GType type; + static const gchar *tags[] = { + GST_META_TAG_AUDIO_STR, + GST_META_TAG_DSD_PLANE_OFFSETS_STR, + NULL + }; + + if (g_once_init_enter (&type)) { + GType _type = gst_meta_api_type_register ("GstDsdPlaneOffsetMetaAPI", tags); + g_once_init_leave (&type, _type); + } + return type; +} + +const GstMetaInfo * +gst_dsd_plane_offset_meta_get_info (void) +{ + static const GstMetaInfo *dsd_plane_offset_meta_info = NULL; + + if (g_once_init_enter ((GstMetaInfo **) & dsd_plane_offset_meta_info)) { + const GstMetaInfo *meta = + gst_meta_register (GST_DSD_PLANE_OFFSET_META_API_TYPE, + "GstDsdPlaneOffsetMeta", + sizeof (GstDsdPlaneOffsetMeta), + gst_dsd_plane_offset_meta_init, + gst_dsd_plane_offset_meta_free, + gst_dsd_plane_offset_meta_transform); + g_once_init_leave ((GstMetaInfo **) & dsd_plane_offset_meta_info, + (GstMetaInfo *) meta); + } + return dsd_plane_offset_meta_info; +} + +/** + * gst_buffer_add_dsd_plane_offset_meta: + * @buffer: a #GstBuffer + * @num_channels: Number of channels in the DSD data + * @num_bytes_per_channel: Number of bytes per channel + * @offsets: (nullable): the offsets (in bytes) where each channel plane starts + * in the buffer + * + * Allocates and attaches a #GstDsdPlaneOffsetMeta on @buffer, which must be + * writable for that purpose. The fields of the #GstDsdPlaneOffsetMeta are + * directly populated from the arguments of this function. + * + * If @offsets is NULL, then the meta's offsets field is left uninitialized. + * This is useful if for example offset values are to be calculated in the + * meta's offsets field in-place. Similarly, @num_bytes_per_channel can be + * set to 0, but only if @offsets is NULL. This is useful if the number of + * bytes per channel is known only later. + * + * It is not allowed for channels to overlap in memory, + * i.e. for each i in [0, channels), the range + * [@offsets[i], @offsets[i] + @num_bytes_per_channel) 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. + * + * This meta is only needed for non-interleaved (= planar) DSD data. + * + * Returns: (transfer none): the #GstDsdPlaneOffsetMeta that was attached + * on the @buffer + * + * Since: 1.24 + */ +GstDsdPlaneOffsetMeta * +gst_buffer_add_dsd_plane_offset_meta (GstBuffer * buffer, gint num_channels, + gsize num_bytes_per_channel, gsize offsets[]) +{ + GstDsdPlaneOffsetMeta *meta; + gint i; +#ifndef G_DISABLE_CHECKS + gsize max_offset = 0; + gint j; +#endif + + g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL); + g_return_val_if_fail (num_channels >= 1, NULL); + g_return_val_if_fail (!offsets || (num_bytes_per_channel >= 1), NULL); + + meta = (GstDsdPlaneOffsetMeta *) gst_buffer_add_meta (buffer, + GST_DSD_PLANE_OFFSET_META_INFO, NULL); + + meta->num_channels = num_channels; + meta->num_bytes_per_channel = num_bytes_per_channel; + + if (G_UNLIKELY (num_channels > 8)) + meta->offsets = g_new (gsize, num_channels); + else + meta->offsets = meta->priv_offsets_arr; + + if (offsets) { + for (i = 0; i < num_channels; i++) { + meta->offsets[i] = offsets[i]; +#ifndef G_DISABLE_CHECKS + max_offset = MAX (max_offset, offsets[i]); + for (j = 0; j < num_channels; j++) { + if (i != j && !(offsets[j] + num_bytes_per_channel <= offsets[i] + || offsets[i] + num_bytes_per_channel <= offsets[j])) { + g_critical ("GstDsdPlaneOffsetMeta properties would cause channel " + "memory areas to overlap! offsets: %" G_GSIZE_FORMAT " (%d), %" + G_GSIZE_FORMAT " (%d) with %" G_GSIZE_FORMAT " bytes per channel", + offsets[i], i, offsets[j], j, num_bytes_per_channel); + gst_buffer_remove_meta (buffer, (GstMeta *) meta); + return NULL; + } + } +#endif + } + +#ifndef G_DISABLE_CHECKS + if (max_offset + num_bytes_per_channel > gst_buffer_get_size (buffer)) { + g_critical ("GstDsdPlaneOffsetMeta properties would cause " + "out-of-bounds memory access on the buffer: max_offset %" + G_GSIZE_FORMAT ", %" G_GSIZE_FORMAT " bytes per channel, " + "buffer size %" G_GSIZE_FORMAT, max_offset, num_bytes_per_channel, + gst_buffer_get_size (buffer)); + gst_buffer_remove_meta (buffer, (GstMeta *) meta); + return NULL; + } +#endif + } + + return meta; +} + +G_DEFINE_BOXED_TYPE (GstDsdInfo, gst_dsd_info, + (GBoxedCopyFunc) gst_dsd_info_copy, (GBoxedFreeFunc) gst_dsd_info_free); + +/** + * gst_dsd_info_new: + * + * Allocate a new #GstDsdInfo that is also initialized with + * gst_dsd_info_init(). + * + * Returns: a new #GstDsdInfo. free with gst_dsd_info_free(). + * + * Since: 1.24 + */ +GstDsdInfo * +gst_dsd_info_new (void) +{ + GstDsdInfo *info; + + info = g_slice_new (GstDsdInfo); + gst_dsd_info_init (info); + + return info; +} + +/** + * gst_dsd_info_new_from_caps: + * @caps: a #GstCaps + * + * Parse @caps to generate a #GstDsdInfo. + * + * Returns: A #GstDsdInfo, or %NULL if @caps couldn't be parsed + * + * Since: 1.24 + */ +GstDsdInfo * +gst_dsd_info_new_from_caps (const GstCaps * caps) +{ + GstDsdInfo *ret; + + g_return_val_if_fail (caps != NULL, NULL); + + ret = gst_dsd_info_new (); + + if (gst_dsd_info_from_caps (ret, caps)) { + return ret; + } else { + gst_dsd_info_free (ret); + return NULL; + } +} + +/** + * gst_dsd_info_init: + * @info: (out caller-allocates): a #GstDsdInfo + * + * Initialize @info with default values. + * + * Since: 1.24 + */ +void +gst_dsd_info_init (GstDsdInfo * info) +{ + g_return_if_fail (info != NULL); + + memset (info, 0, sizeof (GstDsdInfo)); + info->format = GST_DSD_FORMAT_UNKNOWN; +} + +/** + * gst_dsd_info_set_format: + * @info: a #GstDsdInfo + * @format: the format + * @rate: the DSD rate + * @channels: the number of channels + * @position: (array fixed-size=64) (nullable): the channel positions + * + * Set the default info for the DSD info of @format and @rate and @channels. + * + * Note: This initializes @info first, no values are preserved. + * + * Since: 1.24 + */ +void +gst_dsd_info_set_format (GstDsdInfo * info, GstDsdFormat format, + gint rate, gint channels, const GstAudioChannelPosition * positions) +{ + gint i; + + g_return_if_fail (info != NULL); + g_return_if_fail (format != GST_DSD_FORMAT_UNKNOWN); + g_return_if_fail (channels <= 64 || positions == NULL); + + gst_dsd_info_init (info); + + info->format = format; + info->rate = rate; + info->channels = channels; + info->layout = GST_AUDIO_LAYOUT_INTERLEAVED; + info->flags = GST_AUDIO_FLAG_NONE; + + memset (&info->positions, 0xff, sizeof (info->positions)); + + if (!positions && channels == 1) { + info->positions[0] = GST_AUDIO_CHANNEL_POSITION_MONO; + return; + } else if (!positions && channels == 2) { + info->positions[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + info->positions[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + return; + } else { + if (!positions + || !gst_audio_check_valid_channel_positions (positions, channels, + TRUE)) { + if (positions) + g_warning ("Invalid channel positions"); + } else { + memcpy (&info->positions, positions, + info->channels * sizeof (info->positions[0])); + if (info->positions[0] == GST_AUDIO_CHANNEL_POSITION_NONE) + info->flags |= GST_AUDIO_FLAG_UNPOSITIONED; + return; + } + } + + /* Otherwise a NONE layout */ + info->flags |= GST_AUDIO_FLAG_UNPOSITIONED; + for (i = 0; i < MIN (64, channels); i++) + info->positions[i] = GST_AUDIO_CHANNEL_POSITION_NONE; +} + +/** + * gst_dsd_info_copy: + * @info: a #GstDsdInfo + * + * Copy a GstDsdInfo structure. + * + * Returns: a new #GstDsdInfo. free with gst_dsd_info_free. + * + * Since: 1.24 + */ +GstDsdInfo * +gst_dsd_info_copy (const GstDsdInfo * info) +{ + return g_slice_dup (GstDsdInfo, info); +} + +/** + * gst_dsd_info_free: + * @info: a #GstDsdInfo + * + * Free a GstDsdInfo structure previously allocated with gst_dsd_info_new() + * or gst_dsd_info_copy(). + * + * Since: 1.24 + */ +void +gst_dsd_info_free (GstDsdInfo * info) +{ + g_slice_free (GstDsdInfo, info); +} + +/** + * gst_dsd_info_from_caps: + * @info: (out caller-allocates): a #GstDsdInfo + * @caps: a #GstCaps + * + * Parse @caps and update @info. + * + * Returns: TRUE if @caps could be parsed + * + * Since: 1.24 + */ +gboolean +gst_dsd_info_from_caps (GstDsdInfo * info, const GstCaps * caps) +{ + GstStructure *fmt_structure; + const gchar *media_type; + const gchar *format_str = NULL; + const gchar *layout_str = NULL; + gboolean reversed_bytes = FALSE; + GstAudioFlags flags = GST_AUDIO_FLAG_NONE; + + guint64 channel_mask = 0; + + g_return_val_if_fail (info != NULL, FALSE); + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); + + fmt_structure = gst_caps_get_structure (caps, 0); + media_type = gst_structure_get_name (fmt_structure); + + g_return_val_if_fail (g_strcmp0 (media_type, GST_DSD_MEDIA_TYPE) == 0, FALSE); + + /* Parse the format */ + + format_str = gst_structure_get_string (fmt_structure, "format"); + if (format_str == NULL) { + GST_ERROR ("caps have no format field; caps: %" GST_PTR_FORMAT, caps); + goto error; + } + + info->format = gst_dsd_format_from_string (format_str); + if (info->format == GST_DSD_FORMAT_UNKNOWN) { + GST_ERROR ("caps have unsupported/invalid format field; caps: %" + GST_PTR_FORMAT, caps); + goto error; + } + + /* Parse the rate */ + + if (!gst_structure_get_int (fmt_structure, "rate", &(info->rate))) { + GST_ERROR ("caps have no rate field; caps: %" GST_PTR_FORMAT, caps); + goto error; + } + + if (info->rate < 1) { + GST_ERROR ("caps have invalid rate field; caps: %" GST_PTR_FORMAT, caps); + goto error; + } + + /* Parse the channels and the channel mask */ + + if (!gst_structure_get_int (fmt_structure, "channels", &(info->channels))) { + GST_ERROR ("caps have no channels field; caps: %" GST_PTR_FORMAT, caps); + goto error; + } + + if (info->channels < 1) { + GST_ERROR ("caps have invalid channels field; caps: %" GST_PTR_FORMAT, + caps); + goto error; + } + + if (!gst_structure_get (fmt_structure, "channel-mask", GST_TYPE_BITMASK, + &channel_mask, NULL) || (channel_mask == 0 && info->channels == 1) + ) { + switch (info->channels) { + case 1: + info->positions[0] = GST_AUDIO_CHANNEL_POSITION_MONO; + break; + + case 2: + info->positions[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT; + info->positions[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT; + break; + + default: + GST_ERROR + ("caps indicate multichannel DSD data but they do not contain channel-mask field; caps: %" + GST_PTR_FORMAT, caps); + goto error; + } + } else if (channel_mask == 0) { + gint i; + flags |= GST_AUDIO_FLAG_UNPOSITIONED; + for (i = 0; i < MIN (64, info->channels); i++) + info->positions[i] = GST_AUDIO_CHANNEL_POSITION_NONE; + } else { + if (!gst_audio_channel_positions_from_mask (info->channels, channel_mask, + info->positions)) { + GST_ERROR ("invalid channel mask 0x%016" G_GINT64_MODIFIER + "x for %d channels", channel_mask, info->channels); + goto error; + } + } + + /* Parse the layout */ + + layout_str = gst_structure_get_string (fmt_structure, "layout"); + if (layout_str == NULL || g_strcmp0 (layout_str, "interleaved") == 0) + info->layout = GST_AUDIO_LAYOUT_INTERLEAVED; + else if (g_strcmp0 (layout_str, "non-interleaved") == 0) + info->layout = GST_AUDIO_LAYOUT_NON_INTERLEAVED; + else { + GST_ERROR ("caps contain invalid layout field; caps: %" GST_PTR_FORMAT, + caps); + goto error; + } + + gst_structure_get (fmt_structure, "reversed-bytes", G_TYPE_BOOLEAN, + &reversed_bytes, NULL); + + info->flags = flags; + info->reversed_bytes = reversed_bytes; + + return TRUE; + +error: + return FALSE; +} + +/** + * gst_dsd_info_to_caps: + * @info: a #GstDsdInfo + * + * Convert the values of @info into a #GstCaps. + * + * Returns: (transfer full): the new #GstCaps containing the + * info of @info. + * + * Since: 1.24 + */ +GstCaps * +gst_dsd_info_to_caps (const GstDsdInfo * info) +{ + GstCaps *caps; + const gchar *format; + GstAudioFlags flags; + + g_return_val_if_fail (info != NULL, NULL); + g_return_val_if_fail (info->format < GST_NUM_DSD_FORMATS, NULL); + g_return_val_if_fail (info->rate >= 1, NULL); + g_return_val_if_fail (info->channels >= 1, NULL); + + format = gst_dsd_format_to_string (info->format); + g_return_val_if_fail (format != NULL, NULL); + + flags = info->flags; + if ((flags & GST_AUDIO_FLAG_UNPOSITIONED) && info->channels > 1 + && info->positions[0] != GST_AUDIO_CHANNEL_POSITION_NONE) { + flags &= ~GST_AUDIO_FLAG_UNPOSITIONED; + GST_WARNING ("Unpositioned audio channel position flag set but " + "channel positions present"); + } else if (!(flags & GST_AUDIO_FLAG_UNPOSITIONED) && info->channels > 1 + && info->positions[0] == GST_AUDIO_CHANNEL_POSITION_NONE) { + flags |= GST_AUDIO_FLAG_UNPOSITIONED; + GST_WARNING ("Unpositioned audio channel position flag not set " + "but no channel positions present"); + } + + caps = gst_caps_new_simple (GST_DSD_MEDIA_TYPE, + "format", G_TYPE_STRING, format, + "rate", G_TYPE_INT, info->rate, + "channels", G_TYPE_INT, info->channels, + "layout", G_TYPE_STRING, layout_to_string (info->layout), + "reversed-bytes", G_TYPE_BOOLEAN, info->reversed_bytes, NULL); + + if (info->channels > 1 + || info->positions[0] != GST_AUDIO_CHANNEL_POSITION_MONO) { + guint64 channel_mask = 0; + + if ((flags & GST_AUDIO_FLAG_UNPOSITIONED)) { + channel_mask = 0; + } else { + if (!gst_audio_channel_positions_to_mask (info->positions, info->channels, + TRUE, &channel_mask)) + goto invalid_channel_positions; + } + + if (info->channels == 1 + && info->positions[0] == GST_AUDIO_CHANNEL_POSITION_MONO) { + /* Default mono special case */ + } else { + gst_caps_set_simple (caps, "channel-mask", GST_TYPE_BITMASK, channel_mask, + NULL); + } + } + + return caps; + +invalid_channel_positions: + GST_ERROR ("Invalid channel positions"); + gst_caps_unref (caps); + return NULL; +} + +/** + * gst_dsd_info_is_equal: + * @info: a #GstDsdInfo + * @other: a #GstDsdInfo + * + * Compares two #GstDsdInfo and returns whether they are equal or not + * + * Returns: %TRUE if @info and @other are equal, else %FALSE. + * + * Since: 1.24 + */ +gboolean +gst_dsd_info_is_equal (const GstDsdInfo * info, const GstDsdInfo * other) +{ + if (info == other) + return TRUE; + + if (GST_DSD_INFO_FORMAT (info) != GST_DSD_INFO_FORMAT (other)) + return FALSE; + if (GST_DSD_INFO_RATE (info) != GST_DSD_INFO_RATE (other)) + return FALSE; + if (GST_DSD_INFO_CHANNELS (info) != GST_DSD_INFO_CHANNELS (other)) + return FALSE; + if (GST_DSD_INFO_LAYOUT (info) != GST_DSD_INFO_LAYOUT (other)) + return FALSE; + if (GST_DSD_INFO_REVERSED_BYTES (info) != GST_DSD_INFO_REVERSED_BYTES (other)) + return FALSE; + if (memcmp (info->positions, other->positions, + GST_AUDIO_INFO_CHANNELS (info) * sizeof (GstAudioChannelPosition)) != + 0) + return FALSE; + + return TRUE; +} + +static void +gst_dsd_convert_copy_bytes_same_format (const guint8 * input_data, + guint8 * output_data, GstDsdFormat format, gsize num_bytes, + gboolean reverse_byte_bits) +{ + if (reverse_byte_bits) { + guint index; + for (index = 0; index < num_bytes; ++index) + output_data[index] = byte_bit_reversal_table[input_data[index]]; + } else + memcpy (output_data, input_data, num_bytes); +} + +/* The conversion functions work by figuring out the index in the input + * data that corresponds to the current index in the output data. The DSD + * bits are grouped into "words" according to the DSD format. For example, + * if input_format is GST_DSD_FORMAT_U16LE, then the input data is + * grouped into 16-bit (= 2 byte) words. The in/out_word_index values + * are the word indices into the input/output data. in/out_word_offset + * values are the offsets *within* the words that are currently being + * accessed. in/out_index are the combination of these values. + * position is the offset in the time axis (= the position value that + * would be used for seeking). In PCM terms, this is the equivalent of + * (byte_offset / bytes_per_frame). + * + * The calculations first figure out the position and channel_nr out + * of out_index. Using these two values it is then possible to calculate + * in_word_index, in_word_width, and ultimately, in_index. The final + * step is then to copy the DSD byte from in_index in input_data to + * out_index in output_data (with reversing the byte's bits if requested). + * + * Conversions to non-interleaved formats work a little differently: + * instead of one out_index there is one plane_index, that is, the + * output is produced per-plane. + * + * For example, with interleaved -> interleaved conversion, given stereo + * data (-> num_channels is 2), U16BE input, and U32BE output, then + * in_word_width is 2, out_word_width is 4, out_stride is 2*4 = 8. An + * out_index 15 means (note that indices start at 0, so channel #1 is the + * second channel): + * + * - out_word_index = out_index / out_word_width = 15 / 8 = 1 + * out_index refers to word #1 in the output array + * - out_word_offset = out_index - out_word_index * out_word_width = 15 - 1*8 = 7 + * out_index refers to byte #7 in output word #1 + * - channel_nr = out_word_index % num_channels = 1 % 2 = 1 + * out_index is referring to a byte that belongs to channel #1 + * - position = (out_index / out_stride) * out_word_width + out_word_offset = + * (15/8) * 4 + 7 = 11 + * out_index refers to time axis offset 11 (in bytes) + * + * Then: + * - in_word_index = (position / in_word_width) * num_channels + channel_nrh = + * (11/2) * 2 + 1 = 11 + * - in_word_offset = position % in_word_width = 11 % 2 = 1 + * - in_index = in_word_index * in_word_width + in_word_offset = 11 * 2 + 1 = 23 + * + * -> We copy the byte #23 in input_data to byte #15 in output_data. + */ + +static void +gst_dsd_convert_interleaved_to_interleaved (const guint8 * input_data, + guint8 * output_data, GstDsdFormat input_format, GstDsdFormat output_format, + gsize num_dsd_bytes, gint num_channels, gboolean reverse_byte_bits) +{ + if (input_format != output_format) { + guint out_index; + guint in_word_width, out_word_width; + guint out_stride; + gboolean input_is_le = gst_dsd_format_is_le (input_format); + gboolean output_is_le = gst_dsd_format_is_le (output_format); + + in_word_width = gst_dsd_format_get_width (input_format); + out_word_width = gst_dsd_format_get_width (output_format); + out_stride = out_word_width * num_channels; + + for (out_index = 0; out_index < num_dsd_bytes; ++out_index) { + guint in_word_index, in_word_offset; + guint out_word_index, out_word_offset; + guint in_index; + guint channel_nr; + guint position; + guint8 input_byte; + + out_word_index = out_index / out_word_width; + out_word_offset = out_index % out_word_width; + if (output_is_le) + out_word_offset = out_word_width - 1 - out_word_offset; + + channel_nr = out_word_index % num_channels; + position = (out_index / out_stride) * out_word_width + out_word_offset; + + in_word_index = (position / in_word_width) * num_channels + channel_nr; + in_word_offset = position % in_word_width; + if (input_is_le) + in_word_offset = in_word_width - 1 - in_word_offset; + + in_index = in_word_index * in_word_width + in_word_offset; + + input_byte = input_data[in_index]; + output_data[out_index] = + reverse_byte_bits ? byte_bit_reversal_table[input_byte] : input_byte; + } + } else + gst_dsd_convert_copy_bytes_same_format (input_data, output_data, + input_format, num_dsd_bytes, reverse_byte_bits); +} + +static void +gst_dsd_convert_interleaved_to_non_interleaved (const guint8 * input_data, + guint8 * output_data, GstDsdFormat input_format, GstDsdFormat output_format, + const gsize * output_plane_offsets, gsize num_dsd_bytes, gint num_channels, + gboolean reverse_byte_bits) +{ + guint plane_index; + guint in_word_width, out_word_width; + guint channel_nr; + gsize num_bytes_per_plane = num_dsd_bytes / num_channels; + gboolean input_is_le = gst_dsd_format_is_le (input_format); + gboolean output_is_le = gst_dsd_format_is_le (output_format); + + in_word_width = gst_dsd_format_get_width (input_format); + out_word_width = gst_dsd_format_get_width (output_format); + + for (channel_nr = 0; channel_nr < num_channels; ++channel_nr) { + for (plane_index = 0; plane_index < num_bytes_per_plane; ++plane_index) { + guint in_word_index, in_word_offset; + guint out_word_index, out_word_offset; + guint in_index; + guint out_index; + guint position; + guint8 input_byte; + + out_word_index = plane_index / out_word_width; + out_word_offset = plane_index % out_word_width; + if (output_is_le) + out_word_offset = out_word_width - 1 - out_word_offset; + + position = plane_index; + + in_word_index = (position / in_word_width) * num_channels + channel_nr; + in_word_offset = position % in_word_width; + if (input_is_le) + in_word_offset = in_word_width - 1 - in_word_offset; + + in_index = in_word_index * in_word_width + in_word_offset; + out_index = + output_plane_offsets[channel_nr] + out_word_index * out_word_width + + out_word_offset; + + input_byte = input_data[in_index]; + output_data[out_index] = + reverse_byte_bits ? byte_bit_reversal_table[input_byte] : input_byte; + } + } +} + +static void +gst_dsd_convert_non_interleaved_to_interleaved (const guint8 * input_data, + guint8 * output_data, GstDsdFormat input_format, GstDsdFormat output_format, + const gsize * input_plane_offsets, gsize num_dsd_bytes, gint num_channels, + gboolean reverse_byte_bits) +{ + guint out_index; + guint in_word_width, out_word_width; + guint out_stride; + gboolean input_is_le = gst_dsd_format_is_le (input_format); + gboolean output_is_le = gst_dsd_format_is_le (output_format); + + in_word_width = gst_dsd_format_get_width (input_format); + out_word_width = gst_dsd_format_get_width (output_format); + out_stride = out_word_width * num_channels; + + for (out_index = 0; out_index < num_dsd_bytes; ++out_index) { + guint in_word_index, in_word_offset; + guint out_word_index, out_word_offset; + guint in_index; + guint channel_nr; + guint position; + guint8 input_byte; + + out_word_index = out_index / out_word_width; + out_word_offset = out_index % out_word_width; + if (output_is_le) + out_word_offset = out_word_width - 1 - out_word_offset; + + channel_nr = out_word_index % num_channels; + position = (out_index / out_stride) * out_word_width + out_word_offset; + + in_word_index = position / in_word_width; + in_word_offset = position % in_word_width; + if (input_is_le) + in_word_offset = in_word_width - 1 - in_word_offset; + + in_index = + input_plane_offsets[channel_nr] + in_word_index * in_word_width + + in_word_offset; + + input_byte = input_data[in_index]; + output_data[out_index] = + reverse_byte_bits ? byte_bit_reversal_table[input_byte] : input_byte; + } +} + +static void +gst_dsd_convert_non_interleaved_to_non_interleaved (const guint8 * input_data, + guint8 * output_data, GstDsdFormat input_format, GstDsdFormat output_format, + const gsize * input_plane_offsets, const gsize * output_plane_offsets, + gsize num_dsd_bytes, gint num_channels, gboolean reverse_byte_bits) +{ + gboolean same_format = input_format == output_format; + gboolean same_plane_offsets = memcmp (input_plane_offsets, + output_plane_offsets, num_channels * sizeof (gsize)) == 0; + + if (same_format && same_plane_offsets) { + gst_dsd_convert_copy_bytes_same_format (input_data, output_data, + input_format, num_dsd_bytes, reverse_byte_bits); + } else if (same_format) { + gint channel_nr; + gsize num_bytes_per_plane = num_dsd_bytes / num_channels; + + if (reverse_byte_bits) { + guint plane_index; + guint8 input_byte; + + for (channel_nr = 0; channel_nr < num_channels; ++channel_nr) { + for (plane_index = 0; plane_index < num_bytes_per_plane; ++plane_index) { + guint in_index = input_plane_offsets[channel_nr] + plane_index; + guint out_index = output_plane_offsets[channel_nr] + plane_index; + input_byte = input_data[in_index]; + output_data[out_index] = byte_bit_reversal_table[input_byte]; + } + } + } else { + for (channel_nr = 0; channel_nr < num_channels; ++channel_nr) { + memcpy (output_data + output_plane_offsets[channel_nr], + input_data + input_plane_offsets[channel_nr], num_bytes_per_plane); + } + } + } else { + guint channel_nr; + guint plane_index; + gsize num_bytes_per_plane = num_dsd_bytes / num_channels; + guint in_word_width, out_word_width; + gboolean input_is_le = gst_dsd_format_is_le (input_format); + gboolean output_is_le = gst_dsd_format_is_le (output_format); + + in_word_width = gst_dsd_format_get_width (input_format); + out_word_width = gst_dsd_format_get_width (output_format); + + for (channel_nr = 0; channel_nr < num_channels; ++channel_nr) { + for (plane_index = 0; plane_index < num_bytes_per_plane; ++plane_index) { + guint in_word_index, in_word_offset; + guint out_word_index, out_word_offset; + guint in_index; + guint out_index; + guint position; + guint8 input_byte; + + out_word_index = plane_index / out_word_width; + out_word_offset = plane_index % out_word_width; + if (output_is_le) + out_word_offset = out_word_width - 1 - out_word_offset; + + position = plane_index; + + in_word_index = position / in_word_width; + in_word_offset = position % in_word_width; + if (input_is_le) + in_word_offset = in_word_width - 1 - in_word_offset; + + in_index = + input_plane_offsets[channel_nr] + in_word_index * in_word_width + + in_word_offset; + out_index = + output_plane_offsets[channel_nr] + out_word_index * out_word_width + + out_word_offset; + + input_byte = input_data[in_index]; + output_data[out_index] = + reverse_byte_bits ? byte_bit_reversal_table[input_byte] : + input_byte; + } + } + } +} + +/** + * gst_dsd_convert: + * @input_data: the DSD format conversion's input source + * @output_data: the DSD format conversion's output destination + * @input_format: DSD format of the input data to convert from + * @output_format: DSD format of the output data to convert to + * @input_layout: Input data layout + * @output_layout: Output data layout + * @input_plane_offsets: Plane offsets for non-interleaved input data + * @output_plane_offsets: Plane offsets for non-interleaved output data + * @num_dsd_bytes: How many bytes with DSD data to convert + * @num_channels: Number of channels (must be at least 1) + * @reverse_byte_bits: If TRUE, reverse the bits in each DSD byte + * + * Converts DSD data from one layout and grouping format to another. + * @num_bytes must be an integer multiple of the width of both input + * and output format. For example, if the input format is GST_DSD_FORMAT_U32LE, + * and the output format is GST_DSD_FORMAT_U16BE, then @num_bytes must + * be an integer multiple of both 4 (U32LE width) and 2 (U16BE width). + * + * @reverse_byte_bits is necessary if the bit order within the DSD bytes + * needs to be reversed. This is rarely necessary, and is not to be + * confused with the endianness of formats (which determines the ordering + * of *bytes*). + * + * @input_plane_offsets must not be NULL if @input_layout is set to + * #GST_AUDIO_LAYOUT_NON_INTERLEAVED. The same applies to @output_plane_offsets. + * These plane offsets define the starting offset of the planes (there is + * exactly one plane per channel) within @input_data and @output_data + * respectively. If GST_AUDIO_LAYOUT_INTERLEAVED is used, the plane offsets + * are ignored. + * + * Since: 1.24 + */ +void +gst_dsd_convert (const guint8 * input_data, guint8 * output_data, + GstDsdFormat input_format, GstDsdFormat output_format, + GstAudioLayout input_layout, GstAudioLayout output_layout, + const gsize * input_plane_offsets, const gsize * output_plane_offsets, + gsize num_dsd_bytes, gint num_channels, gboolean reverse_byte_bits) +{ + g_return_if_fail (input_data != NULL); + g_return_if_fail (output_data != NULL); + g_return_if_fail (input_format < GST_NUM_DSD_FORMATS); + g_return_if_fail (output_format < GST_NUM_DSD_FORMATS); + g_return_if_fail (input_layout == GST_AUDIO_LAYOUT_INTERLEAVED + || input_plane_offsets != NULL); + g_return_if_fail (output_layout == GST_AUDIO_LAYOUT_INTERLEAVED + || output_plane_offsets != NULL); + g_return_if_fail (num_dsd_bytes > 0); + g_return_if_fail ( + (num_dsd_bytes % gst_dsd_format_get_width (input_format)) == 0); + g_return_if_fail ( + (num_dsd_bytes % gst_dsd_format_get_width (output_format)) == 0); + g_return_if_fail (num_channels > 0); + + GST_LOG ("converting DSD: input: format %s layout %s output: format %s " + "layout %s num channels: %d num DSD bytes: %" G_GSIZE_FORMAT " " + "reverse byte bits: %d", gst_dsd_format_to_string (input_format), + layout_to_string (input_layout), gst_dsd_format_to_string (output_format), + layout_to_string (output_layout), num_channels, num_dsd_bytes, + reverse_byte_bits); + + switch (input_layout) { + case GST_AUDIO_LAYOUT_INTERLEAVED: + switch (output_layout) { + case GST_AUDIO_LAYOUT_INTERLEAVED: + gst_dsd_convert_interleaved_to_interleaved (input_data, output_data, + input_format, output_format, num_dsd_bytes, num_channels, + reverse_byte_bits); + break; + + case GST_AUDIO_LAYOUT_NON_INTERLEAVED: + gst_dsd_convert_interleaved_to_non_interleaved (input_data, + output_data, input_format, output_format, output_plane_offsets, + num_dsd_bytes, num_channels, reverse_byte_bits); + break; + + default: + g_assert_not_reached (); + } + break; + + case GST_AUDIO_LAYOUT_NON_INTERLEAVED: + switch (output_layout) { + case GST_AUDIO_LAYOUT_INTERLEAVED: + gst_dsd_convert_non_interleaved_to_interleaved (input_data, + output_data, input_format, output_format, input_plane_offsets, + num_dsd_bytes, num_channels, reverse_byte_bits); + break; + + case GST_AUDIO_LAYOUT_NON_INTERLEAVED: + gst_dsd_convert_non_interleaved_to_non_interleaved (input_data, + output_data, input_format, output_format, input_plane_offsets, + output_plane_offsets, num_dsd_bytes, num_channels, + reverse_byte_bits); + break; + + default: + g_assert_not_reached (); + } + break; + + default: + g_assert_not_reached (); + } +} diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.h b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.h new file mode 100644 index 0000000000..4947c32db1 --- /dev/null +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsd.h @@ -0,0 +1,338 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +/** + * GST_DSD_MEDIA_TYPE: + * + * The GStreamer media type for DSD. + * + * Since: 1.24 + */ +#define GST_DSD_MEDIA_TYPE "audio/x-dsd" + +/** + * GST_DSD_CAPS_MAKE: + * @format: string format that describes the DSD bits grouping, + * as string (e.g. "DSDU32BE", "DSDU8", etc.) + * + * Generic caps string for DSD audio, for use in pad templates. + * + * Since: 1.24 + */ +#define GST_DSD_CAPS_MAKE(format) \ + GST_DSD_MEDIA_TYPE ", " \ + "format = (string) " format ", " \ + "rate = " GST_AUDIO_RATE_RANGE ", " \ + "layout = (string) { interleaved, non-interleaved }, " \ + "reversed-bytes = (gboolean) { false, true }, " \ + "channels = " GST_AUDIO_CHANNELS_RANGE + +/** + * GST_DSD_MAKE_DSD_RATE_44x: + * + * Calculates a valid DSD-44x rate (in bytes) from commonly used rate + * multiplier specifications like DSD64, DSD128 etc. + * + * For example, to get the rate for DSD64-44x, use 64 as the multiplier + * argument. + * + * Since: 1.24 + */ +#define GST_DSD_MAKE_DSD_RATE_44x(multiplier) \ + ((gint) ((gint64) multiplier) * 44100 / 8) + +/** + * GST_DSD_MAKE_DSD_RATE_48x: + * + * Calculates a valid DSD-48x rate (in bytes) from commonly used rate + * multiplier specifications like DSD64, DSD128 etc. + * + * For example, to get the rate for DSD64-48x, use 64 as the multiplier + * argument. + * + * Since: 1.24 + */ +#define GST_DSD_MAKE_DSD_RATE_48x(multiplier) \ + ((gint) ((gint64) multiplier) * 48000 / 8) +/** + * GST_DSD_SILENCE_PATTERN_BYTE: + * + * Silence pattern for DSD data. + * + * In DSD, a nullbyte does not correspond to silence. To fill memory regions + * with "DSD silence", these regions must be filled with byte 0x69 instead + * (this is the DSD silence pattern). This constant provides that pattern + * in a more readable fashion. + * + * Since: 1.24 + */ +#define GST_DSD_SILENCE_PATTERN_BYTE (0x69) + +typedef struct _GstDsdInfo GstDsdInfo; + +/** + * GstDsdInfo: + * @format: DSD grouping format + * @rate: DSD rate + * @channels: number of channels (must be at least 1) + * @layout: audio layout + * @reversed_bytes: true if the DSD bits in the data bytes are reversed, + * that is, the least significant bit comes first + * @positions: positions for each channel + * + * Information describing DSD audio properties. + * + * In DSD, the "sample format" is the bit. Unlike PCM, there are no further + * "sample formats" in DSD. However, in software, DSD bits are grouped into + * bytes (since dealing with individual bits is impractical), and these bytes + * in turn are grouped into words. This becomes relevant when interleaving + * channels and transmitting DSD data through audio APIs. The different + * types of grouping DSD bytes are referred to as the "DSD grouping forma" + * or just "DSD format". #GstDsdFormat has a list of valid ways of grouping + * DSD bytes into words. + * + * DSD rates are equivalent to PCM sample rates, except that they specify + * how many DSD bytes are consumed per second. This refers to the bytes per + * second _per channel_; the rate does not change when the number of channel + * changes. (Strictly speaking, it would be more correct to measure the + * *bits* per second, since the bit is the DSD "sample format", but it is + * more practical to use bytes.) In DSD, bit rates are always an integer + * multiple of the CD audio rate (44100) or the DAT rate (48000). DSD64-44x + * is 44100 * 64 = 2822400 bits per second, or 352800 bytes per second + * (the latter would be used in this info structure). DSD64-48x is + * 48000 * 64 = 3072000 bits per second, or 384000 bytes per second. + * #GST_DSD_MAKE_DSD_RATE_44x can be used for specifying DSD-44x rates, + * *and #GST_DSD_MAKE_DSD_RATE_48x can be used for specifying DSD-48x ones. + * Also, since DSD-48x is less well known, when the multiplier is given + * without the 44x/48x specifier, 44x is typically implied. + * + * It is important to know that in DSD, different format widths correspond + * to different playtimes. That is, a word with 32 DSD bits covers two times + * as much playtime as a word with 16 DSD bits. This is in contrast to PCM, + * where one word (= one PCM sample) always covers a time period of 1/samplerate, + * no matter how many bits a PCM sample is made of. For this reason, DSD + * and PCM widths and strides cannot be used the same way. + * + * Multiple channels are arranged in DSD data either interleaved or non- + * interleaved. This is similar to PCM. Interleaved layouts rotate between + * channels and words. First, word 0 of channel 0 is present. Then word + * 0 of channel 1 follows. Then word 0 of channel 2 etc. until all + * channels are through, then comes word 1 of channel 0 etc. + * + * Non-interleaved data is planar. First, all words of channel 0 are + * present, then all words of channel 1 etc. Unlike interleaved data, + * non-interleaved data can be sparse, that is, there can be space in + * between the planes. the @positions array specifies the plane offsets. + * + * In uncommon cases, the DSD bits in the data bytes can be stored in reverse + * order. For example, normally, in DSDU8, the first byte contains DSD bits + * 0 to 7, and the most significant bit of that byte is DSD bit 0. If this + * order is reversed, then bit 7 is the first one instead. In that ase, + * @reversed_bytes is set to TRUE. + * + * Use the provided macros to access the info in this structure. + * + * Since: 1.24 + */ +struct _GstDsdInfo { + GstDsdFormat format; + gint rate; + gint channels; + GstAudioLayout layout; + gboolean reversed_bytes; + GstAudioChannelPosition positions[64]; + GstAudioFlags flags; + + /*< private >*/ + gpointer _gst_reserved[GST_PADDING]; +}; + +#define GST_TYPE_DSD_INFO (gst_dsd_info_get_type ()) +GST_AUDIO_API +GType gst_dsd_info_get_type (void); + +#define GST_DSD_INFO_IS_VALID(i) ((i)->format < GST_NUM_DSD_FORMATS && (i)->rate > 0 && (i)->channels > 0) + +#define GST_DSD_INFO_FORMAT(info) ((info)->format) +#define GST_DSD_INFO_RATE(info) ((info)->rate) +#define GST_DSD_INFO_CHANNELS(info) ((info)->channels) +#define GST_DSD_INFO_LAYOUT(info) ((info)->layout) +#define GST_DSD_INFO_REVERSED_BYTES(info) ((info)->reversed_bytes) +#define GST_DSD_INFO_POSITION(info,c) ((info)->position[c]) + +/** + * GST_DSD_INFO_STRIDE: + * + * Calculates the stride for a given #GstDsdInfo. + * + * Note that this is only useful if the info's audio layout + * is GST_AUDIO_LAYOUT_INTERLEAVED. + * + * Since: 1.24 + */ +#define GST_DSD_INFO_STRIDE(info) (gst_dsd_format_get_width((info)->format) * (info)->channels) + +/*** GstDsdPlaneOffsetMeta ***/ + +#define GST_DSD_PLANE_OFFSET_META_API_TYPE (gst_dsd_plane_offset_meta_api_get_type()) +#define GST_DSD_PLANE_OFFSET_META_INFO (gst_dsd_plane_offset_meta_get_info()) + +/** + * GST_META_TAG_DSD_PLANE_OFFSETS_STR: + * + * This metadata stays relevant as long as the DSD plane offsets are unchanged. + * + * Since: 1.24 + */ +#define GST_META_TAG_DSD_PLANE_OFFSETS_STR "dsdplaneoffsets" + +typedef struct _GstDsdPlaneOffsetMeta GstDsdPlaneOffsetMeta; + +/** + * GstDsdPlaneOffsetMeta: + * @meta: parent #GstMeta + * @num_channels: number of channels in the DSD data + * @num_bytes_per_channel: the number of valid bytes per channel in the buffer + * @offsets: the offsets (in bytes) where each channel plane starts in the buffer + * + * Buffer metadata describing planar DSD contents in the buffer. This is not needed + * for interleaved DSD data, and is required for non-interleaved (= planar) data. + * + * 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] + @num_bytes_per_channel) must not overlap + * with any other such range. + * + * It is, however, allowed to have parts of the buffer memory unused, by using + * @offsets and @num_bytes_per_channel 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 bytes from the size of the buffer. You should always + * use the @num_bytes_per_channel variable of this metadata. + * + * Since: 1.24 + */ +struct _GstDsdPlaneOffsetMeta { + GstMeta meta; + gint num_channels; + gsize num_bytes_per_channel; + gsize *offsets; + + /*< private >*/ + gsize priv_offsets_arr[8]; + gpointer _gst_reserved[GST_PADDING]; +}; + +GST_AUDIO_API +GType gst_dsd_plane_offset_meta_api_get_type (void); + +GST_AUDIO_API +const GstMetaInfo * gst_dsd_plane_offset_meta_get_info (void); + +#define gst_buffer_get_dsd_plane_offset_meta(b) \ + ((GstDsdPlaneOffsetMeta*)gst_buffer_get_meta((b), GST_DSD_PLANE_OFFSET_META_API_TYPE)) + +GST_AUDIO_API +GstDsdPlaneOffsetMeta * gst_buffer_add_dsd_plane_offset_meta (GstBuffer *buffer, + gint num_channels, + gsize num_bytes_per_channel, + gsize offsets[]); + +GST_AUDIO_API +GstDsdInfo * gst_dsd_info_new (void); + +GST_AUDIO_API +GstDsdInfo * gst_dsd_info_new_from_caps (const GstCaps * caps); + +GST_AUDIO_API +void gst_dsd_info_init (GstDsdInfo * info); + +GST_AUDIO_API +void gst_dsd_info_set_format (GstDsdInfo * info, + GstDsdFormat format, + gint rate, + gint channels, + const GstAudioChannelPosition * positions); + +GST_AUDIO_API +GstDsdInfo * gst_dsd_info_copy (const GstDsdInfo * info); + +GST_AUDIO_API +void gst_dsd_info_free (GstDsdInfo * info); + +GST_AUDIO_API +gboolean gst_dsd_info_from_caps (GstDsdInfo *info, + const GstCaps *caps); + +GST_AUDIO_API +GstCaps * gst_dsd_info_to_caps (const GstDsdInfo *info); + +GST_AUDIO_API +gboolean gst_dsd_info_is_equal (const GstDsdInfo *info, + const GstDsdInfo *other); + +GST_AUDIO_API +void gst_dsd_convert (const guint8 *input_data, + guint8 *output_data, + GstDsdFormat input_format, + GstDsdFormat output_format, + GstAudioLayout input_layout, + GstAudioLayout output_layout, + const gsize *input_plane_offsets, + const gsize *output_plane_offsets, + gsize num_dsd_bytes, + gint num_channels, + gboolean reverse_bytes); + +/** + * gst_dsd_format_is_le: + * @format: The format. + * + * Useful for determining whether a format is a little-endian. + * GST_DSD_FORMAT_U8 and GST_DSD_FORMAT_UNKNOWN + * are not considered little-endian. + * + * Returns: TRUE if the format is a little-endian one. + */ +static inline gboolean +gst_dsd_format_is_le (GstDsdFormat format) +{ + switch (format) { + case GST_DSD_FORMAT_U16LE: + case GST_DSD_FORMAT_U32LE: + return TRUE; + default: + return FALSE; + } +} + +G_END_DECLS diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.c b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.c new file mode 100644 index 0000000000..8c657b5695 --- /dev/null +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.c @@ -0,0 +1,109 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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 "gstdsdformat.h" + +/** + * gst_dsd_format_from_string: + * @format: a format string + * + * Convert the @format string to its #GstDsdFormat. + * + * Returns: the #GstDsdFormat for @format or GST_DSD_FORMAT_UNKNOWN when the + * string is not a known format. + * + * Since: 1.24 + */ +GstDsdFormat +gst_dsd_format_from_string (const gchar * str) +{ + if (g_strcmp0 (str, "DSDU8") == 0) + return GST_DSD_FORMAT_U8; + else if (g_strcmp0 (str, "DSDU16LE") == 0) + return GST_DSD_FORMAT_U16LE; + else if (g_strcmp0 (str, "DSDU16BE") == 0) + return GST_DSD_FORMAT_U16BE; + else if (g_strcmp0 (str, "DSDU32LE") == 0) + return GST_DSD_FORMAT_U32LE; + else if (g_strcmp0 (str, "DSDU32BE") == 0) + return GST_DSD_FORMAT_U32BE; + else + return GST_DSD_FORMAT_UNKNOWN; +} + +/** + * gst_dsd_format_to_string: + * @format: a #GstDsdFormat + * + * Returns a string containing a descriptive name for + * the #GstDsdFormat if there is one, or NULL otherwise. + * + * Returns: the name corresponding to @format + * + * Since: 1.24 + */ +const gchar * +gst_dsd_format_to_string (GstDsdFormat format) +{ + switch (format) { + case GST_DSD_FORMAT_U8: + return "DSDU8"; + case GST_DSD_FORMAT_U16LE: + return "DSDU16LE"; + case GST_DSD_FORMAT_U16BE: + return "DSDU16BE"; + case GST_DSD_FORMAT_U32LE: + return "DSDU32LE"; + case GST_DSD_FORMAT_U32BE: + return "DSDU32BE"; + default: + return NULL; + } +} + +/** + * gst_dsd_format_get_width: + * @format: a #GstDsdFormat + * + * Returns: Number of bytes in this DSD grouping format. + * + * Since: 1.24 + */ +guint +gst_dsd_format_get_width (GstDsdFormat format) +{ + switch (format) { + case GST_DSD_FORMAT_U8: + return 1; + case GST_DSD_FORMAT_U16LE: + return 2; + case GST_DSD_FORMAT_U16BE: + return 2; + case GST_DSD_FORMAT_U32LE: + return 4; + case GST_DSD_FORMAT_U32BE: + return 4; + default: + return 0; + } +} diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.h b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.h new file mode 100644 index 0000000000..b5c97143d9 --- /dev/null +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/gstdsdformat.h @@ -0,0 +1,86 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#if G_BYTE_ORDER == G_BIG_ENDIAN +#define _GST_DSD_FORMAT_NE(fmt) GST_DSD_FORMAT_ ## fmt ## BE +#elif G_BYTE_ORDER == G_LITTLE_ENDIAN +#define _GST_DSD_FORMAT_NE(fmt) GST_DSD_FORMAT_ ## fmt ## LE +#endif + +/** + * GstDsdFormat: + * @GST_NUM_DSD_FORMATS: number of valid DSD formats + * @GST_DSD_FORMAT_UNKNOWN: unknown / invalid DSD format + * @GST_DSD_FORMAT_U8: 8 DSD bits in 1 byte + * @GST_DSD_FORMAT_U16LE: 16 DSD bits in 2 bytes, little endian order + * @GST_DSD_FORMAT_U16BE: 16 DSD bits in 2 bytes, big endian order + * @GST_DSD_FORMAT_U32LE: 32 DSD bits in 4 bytes, little endian order + * @GST_DSD_FORMAT_U32BE: 32 DSD bits in 4 bytes, big endian order + * @GST_DSD_FORMAT_U16: 16 DSD bits in 2 bytes, native endianness + * @GST_DSD_FORMAT_U32: 32 DSD bits in 4 bytes, native endianness + * + * Enum value describing how DSD bits are grouped. + * + * Since: 1.24 + */ +typedef enum { + GST_DSD_FORMAT_U8 = 0, + GST_DSD_FORMAT_U16LE, + GST_DSD_FORMAT_U16BE, + GST_DSD_FORMAT_U32LE, + GST_DSD_FORMAT_U32BE, + + GST_NUM_DSD_FORMATS, + + GST_DSD_FORMAT_UNKNOWN = 0xffffffff, + + /* native endianness equivalents */ + GST_DSD_FORMAT_U16 = _GST_DSD_FORMAT_NE(U16), + GST_DSD_FORMAT_U32 = _GST_DSD_FORMAT_NE(U32) +} GstDsdFormat; + +/** + * GST_DSD_FORMATS_ALL: + * + * List of all DSD formats, for use in template caps strings. + * + * Big endian formats are preferred, since little-endian ones flip around + * the DSD bytes, and most DSD hardware uses big endian formats. + * + * Since: 1.24 + */ +#define GST_DSD_FORMATS_ALL "{ DSDU32BE, DSDU16BE, DSDU8, DSDU32LE, DSDU16LE }" + +GST_AUDIO_API +GstDsdFormat gst_dsd_format_from_string (const gchar *str); + +GST_AUDIO_API +const gchar * gst_dsd_format_to_string (GstDsdFormat format); + +GST_AUDIO_API +guint gst_dsd_format_get_width (GstDsdFormat format); + +G_END_DECLS diff --git a/subprojects/gst-plugins-base/gst-libs/gst/audio/meson.build b/subprojects/gst-plugins-base/gst-libs/gst/audio/meson.build index 536de8e5a1..fe31d31182 100644 --- a/subprojects/gst-plugins-base/gst-libs/gst/audio/meson.build +++ b/subprojects/gst-plugins-base/gst-libs/gst/audio/meson.build @@ -24,6 +24,8 @@ audio_src = files([ 'gstaudioutilsprivate.c', 'streamvolume.c', 'gstaudiostreamalign.c', + 'gstdsd.c', + 'gstdsdformat.c', ]) audio_mkenum_headers = files([ @@ -41,6 +43,8 @@ audio_mkenum_headers = files([ 'gstaudiocdsrc.h', 'gstaudiobasesink.h', 'gstaudiostreamalign.h', + 'gstdsd.h', + 'gstdsdformat.h', ]) # FIXME: check headers diff --git a/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.c b/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.c new file mode 100644 index 0000000000..d45adb0f1c --- /dev/null +++ b/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.c @@ -0,0 +1,397 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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 "gstdsdconvert.h" + + +/** + * SECTION:element-dsdconvert + * + * Dsdconvert converts between DSD grouping formats and byte reversals. + * See #GstDsdInfo and @gst_dsd_convert for details about the conversion. + * Neither the DSD rate nor the channel count can be changed; this only + * converts the grouping format. + * + * Since: 1.24 + */ + + +GST_DEBUG_CATEGORY_STATIC (dsd_convert_debug); +#define GST_CAT_DEFAULT dsd_convert_debug + +#define STATIC_CAPS \ + GST_STATIC_CAPS (GST_DSD_CAPS_MAKE (GST_DSD_FORMATS_ALL)) + +static GstStaticPadTemplate static_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + STATIC_CAPS); + +static GstStaticPadTemplate static_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + STATIC_CAPS); + +struct _GstDsdConvert +{ + GstBaseTransform parent; + + GstDsdInfo in_info; + GstDsdInfo out_info; + + GstAdapter *input_adapter; +}; + +#define gst_dsd_convert_parent_class parent_class +G_DEFINE_TYPE (GstDsdConvert, gst_dsd_convert, GST_TYPE_BASE_TRANSFORM); +GST_ELEMENT_REGISTER_DEFINE (dsdconvert, "dsdconvert", + GST_RANK_SECONDARY, GST_TYPE_DSD_CONVERT); + +static gboolean gst_dsd_convert_set_caps (GstBaseTransform * base, + GstCaps * incaps, GstCaps * outcaps); +static GstFlowReturn gst_dsd_convert_prepare_output_buffer (GstBaseTransform * + trans, GstBuffer * input, GstBuffer ** outbuf); +static GstCaps *gst_dsd_convert_transform_caps (GstBaseTransform * base, + GstPadDirection direction, GstCaps * caps, GstCaps * filter); +static gboolean gst_dsd_convert_transform_size (GstBaseTransform * base, + GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps, + gsize * othersize); +static GstFlowReturn gst_dsd_convert_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf); + +static void +gst_dsd_convert_class_init (GstDsdConvertClass * klass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (dsd_convert_debug, "dsdconvert", 0, + "DSD grouping format converter"); + + gst_element_class_add_static_pad_template (element_class, + &static_sink_template); + gst_element_class_add_static_pad_template (element_class, + &static_src_template); + + basetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_dsd_convert_set_caps); + basetransform_class->prepare_output_buffer = + GST_DEBUG_FUNCPTR (gst_dsd_convert_prepare_output_buffer); + basetransform_class->transform_caps = + GST_DEBUG_FUNCPTR (gst_dsd_convert_transform_caps); + basetransform_class->transform_size = + GST_DEBUG_FUNCPTR (gst_dsd_convert_transform_size); + basetransform_class->transform = + GST_DEBUG_FUNCPTR (gst_dsd_convert_transform); + + gst_element_class_set_static_metadata (element_class, "DSD converter", + "Filter/Converter/Audio", + "Convert between different DSD grouping formats", + "Carlos Rafael Giani "); +} + +static void +gst_dsd_convert_init (G_GNUC_UNUSED GstDsdConvert * self) +{ +} + +static gboolean +gst_dsd_convert_set_caps (GstBaseTransform * base, + GstCaps * incaps, GstCaps * outcaps) +{ + GstDsdConvert *self = GST_DSD_CONVERT (base); + gboolean can_passthrough; + + if (!gst_dsd_info_from_caps (&self->in_info, incaps)) + goto invalid_in; + if (!gst_dsd_info_from_caps (&self->out_info, outcaps)) + goto invalid_out; + + can_passthrough = gst_dsd_info_is_equal (&self->in_info, &self->out_info); + gst_base_transform_set_passthrough (base, can_passthrough); + + return TRUE; + +invalid_in: + { + GST_ERROR_OBJECT (base, "invalid input caps"); + return FALSE; + } +invalid_out: + { + GST_ERROR_OBJECT (base, "invalid output caps"); + return FALSE; + } +} + +static GstFlowReturn +gst_dsd_convert_prepare_output_buffer (GstBaseTransform * trans, + GstBuffer * input, GstBuffer ** outbuf) +{ + GstFlowReturn flow_ret; + GstDsdConvert *self = GST_DSD_CONVERT_CAST (trans); + + /* The point of this prepare_buffer override is to add the plane + * offset meta if the outgoing data uses a non-interleaved layout. */ + + flow_ret = + GST_BASE_TRANSFORM_CLASS (parent_class)->prepare_output_buffer (trans, + input, outbuf); + if (flow_ret != GST_FLOW_OK) + return flow_ret; + + if (GST_DSD_INFO_LAYOUT (&self->out_info) == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { + g_assert (*outbuf != NULL); + + GST_LOG_OBJECT (trans, "adding dsd plane offset meta to output buffer"); + + /* Add the meta, with num_channels set to 0 and offsets to NULL. That's + * because we do not yet know these quantities - they need to instead + * be set in gst_dsd_convert_transform(). */ + gst_buffer_add_dsd_plane_offset_meta (*outbuf, + GST_DSD_INFO_CHANNELS (&self->out_info), 0, NULL); + } + + return GST_FLOW_OK; +} + +static gboolean +remove_format_from_structure (GstCapsFeatures * features, + GstStructure * structure, gpointer user_data G_GNUC_UNUSED) +{ + gst_structure_remove_fields (structure, "format", "layout", + "reversed-bytes", NULL); + return TRUE; +} + +static GstCaps * +gst_dsd_convert_transform_caps (GstBaseTransform * base, + GstPadDirection direction, GstCaps * caps, GstCaps * filter) +{ + GstCaps *tmp, *tmp2, *template_caps; + GstCaps *result; + + tmp = gst_caps_copy (caps); + + /* Remove any existing format, layout, reversed-bytes fields. */ + gst_caps_map_in_place (tmp, remove_format_from_structure, NULL); + + /* Then fill in the removed fields with those from the template caps. */ + template_caps = gst_static_pad_template_get_caps (&static_sink_template); + tmp2 = gst_caps_intersect_full (tmp, template_caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (tmp); + gst_caps_unref (template_caps); + tmp = tmp2; + + if (filter) { + tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (tmp); + tmp = tmp2; + } + + result = tmp; + + GST_DEBUG_OBJECT (base, "transformed %" GST_PTR_FORMAT " into %" + GST_PTR_FORMAT, caps, result); + + return result; +} + +static gboolean +gst_dsd_convert_transform_size (GstBaseTransform * base, + GstPadDirection direction, GstCaps * caps, gsize size, + GstCaps * othercaps, gsize * othersize) +{ + GstDsdConvert *self = (GstDsdConvert *) (base); + GstDsdInfo info; + GstDsdInfo otherinfo; + guint width, otherwidth, maxwidth; + + g_return_val_if_fail (caps != NULL, FALSE); + g_return_val_if_fail (othercaps != NULL, FALSE); + g_return_val_if_fail (othersize != NULL, FALSE); + + if (!gst_dsd_info_from_caps (&info, caps)) + goto invalid_caps; + + if (!gst_dsd_info_from_caps (&otherinfo, othercaps)) + goto invalid_othercaps; + + width = gst_dsd_format_get_width (GST_DSD_INFO_FORMAT (&info)); + otherwidth = gst_dsd_format_get_width (GST_DSD_INFO_FORMAT (&otherinfo)); + maxwidth = MAX (width, otherwidth); + + *othersize = (size / maxwidth) * maxwidth; + + GST_LOG_OBJECT (self, "transformed size %" G_GSIZE_FORMAT " to othersize %" + G_GSIZE_FORMAT "; width: %u otherwidth: %u", size, *othersize, width, + otherwidth); + + return TRUE; + +invalid_caps: + { + GST_INFO_OBJECT (base, "failed to parse caps to transform size"); + return FALSE; + } + +invalid_othercaps: + { + GST_INFO_OBJECT (base, "failed to parse othercaps to transform size"); + return FALSE; + } +} + +static GstFlowReturn +gst_dsd_convert_transform (GstBaseTransform * base, + GstBuffer * inbuf, GstBuffer * outbuf) +{ + GstDsdConvert *self = GST_DSD_CONVERT (base); + GstFlowReturn flow_ret = GST_FLOW_OK; + GstMapInfo in_map_info, out_map_info; + gboolean inbuf_mapped = FALSE; + gboolean outbuf_mapped = FALSE; + gboolean need_to_reverse_bytes; + const gsize *input_plane_offsets = NULL; + const gsize *output_plane_offsets = NULL; + GstDsdPlaneOffsetMeta *in_dsd_plane_ofs_meta = NULL; + GstDsdPlaneOffsetMeta *out_dsd_plane_ofs_meta = NULL; + gint num_channels; + gsize num_dsd_bytes = 0; + + g_return_val_if_fail (inbuf != NULL, GST_FLOW_ERROR); + g_return_val_if_fail (outbuf != NULL, GST_FLOW_ERROR); + + GST_LOG_OBJECT (self, "about to transform input buffer %" GST_PTR_FORMAT + "; output buffer size: %" G_GSIZE_FORMAT, inbuf, + gst_buffer_get_size (outbuf)); + + num_channels = GST_DSD_INFO_CHANNELS (&self->in_info); + + /* Get the plane offset metas if the audio layouts are non-interleaved. + * Some of the quantities necessary for converting from/to non-interleaved + * data are known only later, which is why not all of them are set here. */ + + if (GST_DSD_INFO_LAYOUT (&self->in_info) == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { + in_dsd_plane_ofs_meta = gst_buffer_get_dsd_plane_offset_meta (inbuf); + if (G_UNLIKELY (in_dsd_plane_ofs_meta == NULL)) + goto in_dsd_plane_ofs_meta_missing; + + input_plane_offsets = in_dsd_plane_ofs_meta->offsets; + num_dsd_bytes = in_dsd_plane_ofs_meta->num_bytes_per_channel * num_channels; + } + + if (GST_DSD_INFO_LAYOUT (&self->out_info) == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { + out_dsd_plane_ofs_meta = gst_buffer_get_dsd_plane_offset_meta (outbuf); + if (G_UNLIKELY (out_dsd_plane_ofs_meta == NULL)) + goto out_dsd_plane_ofs_meta_missing; + } + + /* Map the input and output buffers now. */ + + if (!gst_buffer_map (inbuf, &in_map_info, GST_MAP_READ)) + goto in_map_failed; + inbuf_mapped = TRUE; + + if (!gst_buffer_map (outbuf, &out_map_info, GST_MAP_WRITE)) + goto out_map_failed; + outbuf_mapped = TRUE; + + /* In case of interleaved input audio, we only know now how many DSD bytes + * there are, because then, the amount equals the number of bytes in the + * buffer divided by the number of channels. This does not apply to + * non-interleaved (= planar) data, since in such data, there can be + * a space between the planes. */ + if (GST_DSD_INFO_LAYOUT (&self->in_info) == GST_AUDIO_LAYOUT_INTERLEAVED) + num_dsd_bytes = in_map_info.size; + + need_to_reverse_bytes = GST_DSD_INFO_REVERSED_BYTES (&self->in_info) != + GST_DSD_INFO_REVERSED_BYTES (&self->out_info); + + /* We now have the necessary info to complete the output plane offset meta + * (which is present if we are producing non-interleaved data). */ + if (GST_DSD_INFO_LAYOUT (&self->out_info) == GST_AUDIO_LAYOUT_NON_INTERLEAVED) { + gint channel_idx; + + out_dsd_plane_ofs_meta->num_bytes_per_channel = + num_dsd_bytes / num_channels; + + for (channel_idx = 0; channel_idx < num_channels; ++channel_idx) { + out_dsd_plane_ofs_meta->offsets[channel_idx] = + out_dsd_plane_ofs_meta->num_bytes_per_channel * channel_idx; + } + + output_plane_offsets = out_dsd_plane_ofs_meta->offsets; + } + + /* Do the actual conversion. */ + gst_dsd_convert (in_map_info.data, out_map_info.data, + GST_DSD_INFO_FORMAT (&self->in_info), + GST_DSD_INFO_FORMAT (&self->out_info), + GST_DSD_INFO_LAYOUT (&self->in_info), + GST_DSD_INFO_LAYOUT (&self->out_info), input_plane_offsets, + output_plane_offsets, num_dsd_bytes, num_channels, need_to_reverse_bytes); + +finish: + if (inbuf_mapped) + gst_buffer_unmap (inbuf, &in_map_info); + if (outbuf_mapped) + gst_buffer_unmap (outbuf, &out_map_info); + + return flow_ret; + +in_dsd_plane_ofs_meta_missing: + { + GST_ERROR_OBJECT (base, + "input buffer has no DSD plane offset meta; buffer details: %" + GST_PTR_FORMAT, inbuf); + flow_ret = GST_FLOW_ERROR; + goto finish; + } + +out_dsd_plane_ofs_meta_missing: + { + GST_ERROR_OBJECT (base, + "output buffer has no DSD plane offset meta; buffer details: %" + GST_PTR_FORMAT, outbuf); + flow_ret = GST_FLOW_ERROR; + goto finish; + } + +in_map_failed: + { + GST_ERROR_OBJECT (base, "could not map input buffer; buffer details: %" + GST_PTR_FORMAT, inbuf); + flow_ret = GST_FLOW_ERROR; + goto finish; + } + +out_map_failed: + { + GST_ERROR_OBJECT (base, "could not map output buffer; buffer details: %" + GST_PTR_FORMAT, outbuf); + flow_ret = GST_FLOW_ERROR; + goto finish; + } +} diff --git a/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.h b/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.h new file mode 100644 index 0000000000..539ac07af3 --- /dev/null +++ b/subprojects/gst-plugins-base/gst/dsd/gstdsdconvert.h @@ -0,0 +1,35 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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. + */ + +#pragma once + +#include +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_DSD_CONVERT (gst_dsd_convert_get_type()) +G_DECLARE_FINAL_TYPE (GstDsdConvert, gst_dsd_convert, + GST, DSD_CONVERT, GstBaseTransform) +#define GST_DSD_CONVERT_CAST(obj) ((GstDsdConvert *)(obj)) + +GST_ELEMENT_REGISTER_DECLARE (dsdconvert); + +G_END_DECLS diff --git a/subprojects/gst-plugins-base/gst/dsd/meson.build b/subprojects/gst-plugins-base/gst/dsd/meson.build new file mode 100644 index 0000000000..ef1db3cf67 --- /dev/null +++ b/subprojects/gst-plugins-base/gst/dsd/meson.build @@ -0,0 +1,14 @@ +dsd_sources = [ + 'gstdsdconvert.c', + 'plugin.c' + ] + +gstdsd = library('gstdsd', + dsd_sources, + c_args : gst_plugins_base_args, + include_directories: [configinc, libsinc], + dependencies : [audio_dep, gst_base_dep], + install : true, + install_dir : plugins_install_dir, +) +plugins += [gstdsd] diff --git a/subprojects/gst-plugins-base/gst/dsd/plugin.c b/subprojects/gst-plugins-base/gst/dsd/plugin.c new file mode 100644 index 0000000000..cb35e0756e --- /dev/null +++ b/subprojects/gst-plugins-base/gst/dsd/plugin.c @@ -0,0 +1,42 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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 + +/** + * plugin-dsd: + * + * Since: 1.24 + */ + +#include "gstdsdconvert.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return GST_ELEMENT_REGISTER (dsdconvert, plugin); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + dsd, + "elements for processing DSD audio", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/subprojects/gst-plugins-base/gst/meson.build b/subprojects/gst-plugins-base/gst/meson.build index 2d14191ce9..0dc44cd519 100644 --- a/subprojects/gst-plugins-base/gst/meson.build +++ b/subprojects/gst-plugins-base/gst/meson.build @@ -1,7 +1,7 @@ foreach plugin : ['adder', 'app', 'audioconvert', 'audiomixer', 'audiorate', 'audioresample', - 'audiotestsrc', 'compositor', 'encoding', 'gio', 'overlaycomposition', 'pbtypes', 'playback', - 'rawparse', 'subparse', 'tcp', 'typefind', 'videoconvertscale', 'videorate', - 'videotestsrc', 'volume'] + 'audiotestsrc', 'compositor', 'dsd', 'encoding', 'gio', 'overlaycomposition', + 'pbtypes', 'playback', 'rawparse', 'subparse', 'tcp', 'typefind', + 'videoconvertscale', 'videorate', 'videotestsrc', 'volume'] if not get_option(plugin).disabled() subdir(plugin) endif diff --git a/subprojects/gst-plugins-base/meson_options.txt b/subprojects/gst-plugins-base/meson_options.txt index 0db18408e7..2fb13cfafb 100644 --- a/subprojects/gst-plugins-base/meson_options.txt +++ b/subprojects/gst-plugins-base/meson_options.txt @@ -37,6 +37,7 @@ option('audioresample', type : 'feature', value : 'auto') option('audiotestsrc', type : 'feature', value : 'auto') option('compositor', type : 'feature', value : 'auto') option('drm', type : 'feature', value : 'auto') +option('dsd', type : 'feature', value : 'auto') option('encoding', type : 'feature', value : 'auto') option('gio', type : 'feature', value : 'auto') option('gio-typefinder', type : 'feature', value : 'auto') diff --git a/subprojects/gst-plugins-base/tests/check/libs/dsd.c b/subprojects/gst-plugins-base/tests/check/libs/dsd.c new file mode 100644 index 0000000000..94aee6706f --- /dev/null +++ b/subprojects/gst-plugins-base/tests/check/libs/dsd.c @@ -0,0 +1,385 @@ +/* GStreamer + * Copyright (C) 2023 Carlos Rafael Giani + * + * 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. + */ + +#include +#include + + +#define NUM_CHANNELS (2) +#define NUM_BYTES_PER_CHANNEL (16) +#define NUM_PATTERN_BYTES (NUM_CHANNELS * NUM_BYTES_PER_CHANNEL) + + +typedef struct +{ + GstDsdFormat format; + guint8 bytes[NUM_CHANNELS * NUM_BYTES_PER_CHANNEL]; +} DsdTestPattern; + +/* The following test patterns contain bytes 0x00 to 0x0F for the left channel + * and 0x80 to 0x8F for the right channel. These bytes are grouped in the test + * patterns according to their (non-)interleaved layout and grouping format. */ + +/* *INDENT-OFF* */ + +static const DsdTestPattern interleaved_dsd_test_patterns[] = { + { + GST_DSD_FORMAT_U8, + { + 0x00, 0x80, 0x01, 0x81, 0x02, 0x82, 0x03, 0x83, 0x04, 0x84, 0x05, 0x85, 0x06, 0x86, 0x07, 0x87, + 0x08, 0x88, 0x09, 0x89, 0x0A, 0x8A, 0x0B, 0x8B, 0x0C, 0x8C, 0x0D, 0x8D, 0x0E, 0x8E, 0x0F, 0x8F + } + }, + { + GST_DSD_FORMAT_U16LE, + { + 0x01, 0x00, 0x81, 0x80, 0x03, 0x02, 0x83, 0x82, 0x05, 0x04, 0x85, 0x84, 0x07, 0x06, 0x87, 0x86, + 0x09, 0x08, 0x89, 0x88, 0x0B, 0x0A, 0x8B, 0x8A, 0x0D, 0x0C, 0x8D, 0x8C, 0x0F, 0x0E, 0x8F, 0x8E + } + }, + { + GST_DSD_FORMAT_U16BE, + { + 0x00, 0x01, 0x80, 0x81, 0x02, 0x03, 0x82, 0x83, 0x04, 0x05, 0x84, 0x85, 0x06, 0x07, 0x86, 0x87, + 0x08, 0x09, 0x88, 0x89, 0x0A, 0x0B, 0x8A, 0x8B, 0x0C, 0x0D, 0x8C, 0x8D, 0x0E, 0x0F, 0x8E, 0x8F + } + }, + { + GST_DSD_FORMAT_U32LE, + { + 0x03, 0x02, 0x01, 0x00, 0x83, 0x82, 0x81, 0x80, 0x07, 0x06, 0x05, 0x04, 0x87, 0x86, 0x85, 0x84, + 0x0B, 0x0A, 0x09, 0x08, 0x8B, 0x8A, 0x89, 0x88, 0x0F, 0x0E, 0x0D, 0x0C, 0x8F, 0x8E, 0x8D, 0x8C + } + }, + { + GST_DSD_FORMAT_U32BE, + { + 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0x83, 0x04, 0x05, 0x06, 0x07, 0x84, 0x85, 0x86, 0x87, + 0x08, 0x09, 0x0A, 0x0B, 0x88, 0x89, 0x8A, 0x8B, 0x0C, 0x0D, 0x0E, 0x0F, 0x8C, 0x8D, 0x8E, 0x8F + } + } +}; + +/* *INDENT-ON* */ + +static const int num_interleaved_dsd_test_patterns = + sizeof (interleaved_dsd_test_patterns) / sizeof (DsdTestPattern); + +/* *INDENT-OFF* */ + +static const DsdTestPattern non_interleaved_dsd_test_patterns[] = { + { + GST_DSD_FORMAT_U8, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F + } + }, + { + GST_DSD_FORMAT_U16LE, + { + 0x01, 0x00, 0x03, 0x02, 0x05, 0x04, 0x07, 0x06, 0x09, 0x08, 0x0B, 0x0A, 0x0D, 0x0C, 0x0F, 0x0E, + 0x81, 0x80, 0x83, 0x82, 0x85, 0x84, 0x87, 0x86, 0x89, 0x88, 0x8B, 0x8A, 0x8D, 0x8C, 0x8F, 0x8E + } + }, + { + GST_DSD_FORMAT_U16BE, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + } + }, + { + GST_DSD_FORMAT_U32LE, + { + 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0B, 0x0A, 0x09, 0x08, 0x0F, 0x0E, 0x0D, 0x0C, + 0x83, 0x82, 0x81, 0x80, 0x87, 0x86, 0x85, 0x84, 0x8B, 0x8A, 0x89, 0x88, 0x8F, 0x8E, 0x8D, 0x8C, + } + }, + { + GST_DSD_FORMAT_U32BE, + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + } + }, +}; + +/* *INDENT-ON* */ + +static const int num_non_interleaved_dsd_test_patterns = + sizeof (non_interleaved_dsd_test_patterns) / sizeof (DsdTestPattern); + +static const gsize dsd_plane_offsets[NUM_CHANNELS] = { 0, 16 }; + +GST_START_TEST (test_dsd_conversion_interleaved_to_interleaved) +{ + int in_idx, out_idx; + guint8 actual_output_pattern[NUM_PATTERN_BYTES]; + + for (in_idx = 0; in_idx < num_interleaved_dsd_test_patterns; ++in_idx) { + for (out_idx = 0; out_idx < num_interleaved_dsd_test_patterns; ++out_idx) { + guint8 const *input_pattern = interleaved_dsd_test_patterns[in_idx].bytes; + guint8 const *expected_output_pattern = + interleaved_dsd_test_patterns[out_idx].bytes; + GstDsdFormat input_format = interleaved_dsd_test_patterns[in_idx].format; + GstDsdFormat output_format = + interleaved_dsd_test_patterns[out_idx].format; + gboolean conversion_ok; + + gst_dsd_convert (input_pattern, actual_output_pattern, input_format, + output_format, GST_AUDIO_LAYOUT_INTERLEAVED, + GST_AUDIO_LAYOUT_INTERLEAVED, NULL, NULL, NUM_PATTERN_BYTES, + NUM_CHANNELS, FALSE); + + conversion_ok = memcmp (actual_output_pattern, expected_output_pattern, + NUM_PATTERN_BYTES) == 0; + + if (!conversion_ok) { + GST_MEMDUMP ("expected:", expected_output_pattern, NUM_PATTERN_BYTES); + GST_MEMDUMP ("actual :", actual_output_pattern, NUM_PATTERN_BYTES); + } + + fail_unless (conversion_ok, "DSD conversion output incorrect"); + } + } +} + +GST_END_TEST; + +GST_START_TEST (test_dsd_conversion_interleaved_to_non_interleaved) +{ + int in_idx, out_idx; + guint8 actual_output_pattern[NUM_PATTERN_BYTES]; + + for (in_idx = 0; in_idx < num_interleaved_dsd_test_patterns; ++in_idx) { + for (out_idx = 0; out_idx < num_non_interleaved_dsd_test_patterns; + ++out_idx) { + guint8 const *input_pattern = interleaved_dsd_test_patterns[in_idx].bytes; + guint8 const *expected_output_pattern = + non_interleaved_dsd_test_patterns[out_idx].bytes; + GstDsdFormat input_format = interleaved_dsd_test_patterns[in_idx].format; + GstDsdFormat output_format = + non_interleaved_dsd_test_patterns[out_idx].format; + gboolean conversion_ok; + + gst_dsd_convert (input_pattern, actual_output_pattern, input_format, + output_format, GST_AUDIO_LAYOUT_INTERLEAVED, + GST_AUDIO_LAYOUT_NON_INTERLEAVED, NULL, dsd_plane_offsets, + NUM_PATTERN_BYTES, NUM_CHANNELS, FALSE); + + conversion_ok = memcmp (actual_output_pattern, expected_output_pattern, + NUM_PATTERN_BYTES) == 0; + + if (!conversion_ok) { + GST_MEMDUMP ("expected:", expected_output_pattern, NUM_PATTERN_BYTES); + GST_MEMDUMP ("actual :", actual_output_pattern, NUM_PATTERN_BYTES); + } + + fail_unless (conversion_ok, "DSD conversion output incorrect"); + } + } +} + +GST_END_TEST; + +GST_START_TEST (test_dsd_conversion_non_interleaved_to_interleaved) +{ + int in_idx, out_idx; + guint8 actual_output_pattern[NUM_PATTERN_BYTES]; + + for (in_idx = 0; in_idx < num_non_interleaved_dsd_test_patterns; ++in_idx) { + for (out_idx = 0; out_idx < num_interleaved_dsd_test_patterns; ++out_idx) { + guint8 const *input_pattern = + non_interleaved_dsd_test_patterns[in_idx].bytes; + guint8 const *expected_output_pattern = + interleaved_dsd_test_patterns[out_idx].bytes; + GstDsdFormat input_format = + non_interleaved_dsd_test_patterns[in_idx].format; + GstDsdFormat output_format = + interleaved_dsd_test_patterns[out_idx].format; + gboolean conversion_ok; + + gst_dsd_convert (input_pattern, + actual_output_pattern, + input_format, + output_format, + GST_AUDIO_LAYOUT_NON_INTERLEAVED, + GST_AUDIO_LAYOUT_INTERLEAVED, + dsd_plane_offsets, NULL, NUM_PATTERN_BYTES, NUM_CHANNELS, FALSE); + + conversion_ok = memcmp (actual_output_pattern, expected_output_pattern, + NUM_PATTERN_BYTES) == 0; + + if (!conversion_ok) { + GST_MEMDUMP ("expected:", expected_output_pattern, NUM_PATTERN_BYTES); + GST_MEMDUMP ("actual :", actual_output_pattern, NUM_PATTERN_BYTES); + } + + fail_unless (conversion_ok, "DSD conversion output incorrect"); + } + } +} + +GST_END_TEST; + +GST_START_TEST (test_dsd_conversion_non_interleaved_to_non_interleaved) +{ + int in_idx, out_idx; + guint8 actual_output_pattern[NUM_PATTERN_BYTES]; + + for (in_idx = 0; in_idx < num_non_interleaved_dsd_test_patterns; ++in_idx) { + for (out_idx = 0; out_idx < num_non_interleaved_dsd_test_patterns; + ++out_idx) { + guint8 const *input_pattern = + non_interleaved_dsd_test_patterns[in_idx].bytes; + guint8 const *expected_output_pattern = + non_interleaved_dsd_test_patterns[out_idx].bytes; + GstDsdFormat input_format = + non_interleaved_dsd_test_patterns[in_idx].format; + GstDsdFormat output_format = + non_interleaved_dsd_test_patterns[out_idx].format; + gboolean conversion_ok; + + gst_dsd_convert (input_pattern, + actual_output_pattern, + input_format, + output_format, + GST_AUDIO_LAYOUT_NON_INTERLEAVED, + GST_AUDIO_LAYOUT_NON_INTERLEAVED, + dsd_plane_offsets, + dsd_plane_offsets, NUM_PATTERN_BYTES, NUM_CHANNELS, FALSE); + + conversion_ok = memcmp (actual_output_pattern, expected_output_pattern, + NUM_PATTERN_BYTES) == 0; + + if (!conversion_ok) { + GST_MEMDUMP ("expected:", expected_output_pattern, NUM_PATTERN_BYTES); + GST_MEMDUMP ("actual :", actual_output_pattern, NUM_PATTERN_BYTES); + } + + fail_unless (conversion_ok, "DSD conversion output incorrect"); + } + } +} + +GST_END_TEST; + +GST_START_TEST (test_dsd_info_from_caps) +{ + GstDsdInfo info; + GstCaps *full_caps, *minimal_caps; + + full_caps = gst_caps_new_simple (GST_DSD_MEDIA_TYPE, + "format", G_TYPE_STRING, "DSDU16LE", + "rate", G_TYPE_INT, GST_DSD_MAKE_DSD_RATE_44x (128), + "channels", G_TYPE_INT, 2, + "layout", G_TYPE_STRING, "non-interleaved", + "channel-mask", GST_TYPE_BITMASK, 0x3, NULL); + fail_unless (gst_dsd_info_from_caps (&info, full_caps)); + fail_unless_equals_int (info.format, GST_DSD_FORMAT_U16LE); + fail_unless_equals_int (info.rate, GST_DSD_MAKE_DSD_RATE_44x (128)); + fail_unless_equals_int (info.channels, 2); + fail_unless_equals_int (info.layout, GST_AUDIO_LAYOUT_NON_INTERLEAVED); + fail_unless_equals_int (info.positions[0], + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT); + fail_unless_equals_int (info.positions[1], + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT); + gst_caps_unref (full_caps); + + minimal_caps = gst_caps_new_simple (GST_DSD_MEDIA_TYPE, + "format", G_TYPE_STRING, "DSDU16LE", + "rate", G_TYPE_INT, GST_DSD_MAKE_DSD_RATE_44x (128), + "channels", G_TYPE_INT, 2, NULL); + fail_unless (gst_dsd_info_from_caps (&info, minimal_caps)); + fail_unless_equals_int (info.format, GST_DSD_FORMAT_U16LE); + fail_unless_equals_int (info.rate, GST_DSD_MAKE_DSD_RATE_44x (128)); + fail_unless_equals_int (info.channels, 2); + fail_unless_equals_int (info.layout, GST_AUDIO_LAYOUT_INTERLEAVED); + fail_unless_equals_int (info.positions[0], + GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT); + fail_unless_equals_int (info.positions[1], + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT); + gst_caps_unref (minimal_caps); +} + +GST_END_TEST; + +GST_START_TEST (test_dsd_info_to_caps) +{ + GstStructure *str; + GstCaps *caps; + gint rate; + gint channels; + guint64 channel_mask; + GstDsdInfo info = { + .format = GST_DSD_FORMAT_U16LE, + .rate = GST_DSD_MAKE_DSD_RATE_44x (64), + .channels = 2, + .layout = GST_AUDIO_LAYOUT_INTERLEAVED, + .positions = {GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT, + GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT} + , + .flags = GST_AUDIO_FLAG_NONE + }; + + caps = gst_dsd_info_to_caps (&info); + fail_if (caps == NULL, "caps were not created"); + fail_unless_equals_int (gst_caps_get_size (caps), 1); + + str = gst_caps_get_structure (caps, 0); + fail_if (str == NULL, "could not get structure"); + fail_unless (gst_structure_has_name (str, GST_DSD_MEDIA_TYPE)); + fail_unless (gst_structure_has_field_typed (str, "format", G_TYPE_STRING)); + fail_unless_equals_string (gst_structure_get_string (str, "format"), + "DSDU16LE"); + fail_unless (gst_structure_get_int (str, "rate", &rate)); + fail_unless_equals_int (rate, GST_DSD_MAKE_DSD_RATE_44x (64)); + fail_unless (gst_structure_get_int (str, "channels", &channels)); + fail_unless_equals_int (channels, 2); + fail_unless_equals_string (gst_structure_get_string (str, "layout"), + "interleaved"); + fail_unless (gst_structure_get (str, "channel-mask", GST_TYPE_BITMASK, + &channel_mask, NULL)); + fail_unless_equals_uint64 (channel_mask, 0x3); + + gst_caps_unref (caps); +} + +GST_END_TEST; + +static Suite * +dsd_suite (void) +{ + Suite *s = suite_create ("dsd"); + TCase *tc_chain = tcase_create ("general"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_dsd_conversion_interleaved_to_interleaved); + tcase_add_test (tc_chain, test_dsd_conversion_interleaved_to_non_interleaved); + tcase_add_test (tc_chain, test_dsd_conversion_non_interleaved_to_interleaved); + tcase_add_test (tc_chain, + test_dsd_conversion_non_interleaved_to_non_interleaved); + tcase_add_test (tc_chain, test_dsd_info_from_caps); + tcase_add_test (tc_chain, test_dsd_info_to_caps); + + return s; +} + +GST_CHECK_MAIN (dsd); diff --git a/subprojects/gst-plugins-base/tests/check/meson.build b/subprojects/gst-plugins-base/tests/check/meson.build index dfcb92b547..50d4cbbfe6 100644 --- a/subprojects/gst-plugins-base/tests/check/meson.build +++ b/subprojects/gst-plugins-base/tests/check/meson.build @@ -10,6 +10,7 @@ base_tests = [ [ 'libs/audiosink.c' ], [ 'libs/baseaudiovisualizer.c' ], [ 'libs/discoverer.c' ], + [ 'libs/dsd.c' ], [ 'libs/fft.c' ], [ 'libs/libsabi.c', false, [ gstgl_dep, gstglx11_dep, gstglwayland_dep, gstglegl_dep ] ], [ 'libs/mikey.c' ],