mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
8c5a8f4466
Related to: https://gitlab.freedesktop.org/gstreamer/gst-plugins-base/-/issues/972 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3901>
397 lines
12 KiB
C
397 lines
12 KiB
C
/* GStreamer
|
|
* Copyright (C) 2023 Carlos Rafael Giani <crg7475@mailbox.org>
|
|
*
|
|
* 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 <crg7475@mailbox.org>");
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|