mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-08 18:39:54 +00:00
eb09889176
In this specific case it wouldn't cause problems as we only ever access the first array element, but let's make explicit what is happening here. CID 1346530 and 1346529
850 lines
26 KiB
C
850 lines
26 KiB
C
/* GStreamer
|
|
* Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
|
|
* Copyright (C) 2005 Thomas Vander Stichele <thomas at apestaart dot org>
|
|
* Copyright (C) 2011 Wim Taymans <wim.taymans at gmail dot com>
|
|
*
|
|
* gstaudioconvert.c: Convert audio to different audio formats automatically
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-audioconvert
|
|
*
|
|
* Audioconvert converts raw audio buffers between various possible formats.
|
|
* It supports integer to float conversion, width/depth conversion,
|
|
* signedness and endianness conversion and channel transformations
|
|
* (ie. upmixing and downmixing), as well as dithering and noise-shaping.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch-1.0 -v -m audiotestsrc ! audioconvert ! audio/x-raw,format=S8,channels=2 ! level ! fakesink silent=TRUE
|
|
* ]| This pipeline converts audio to 8-bit. The level element shows that
|
|
* the output levels still match the one for a sine wave.
|
|
* |[
|
|
* gst-launch-1.0 -v -m uridecodebin uri=file:///path/to/audio.flac ! audioconvert ! vorbisenc ! oggmux ! filesink location=audio.ogg
|
|
* ]| The vorbis encoder takes float audio data instead of the integer data
|
|
* output by most other audio elements. This pipeline decodes a FLAC audio file
|
|
* (or any other audio file for which decoders are installed) and re-encodes
|
|
* it into an Ogg/Vorbis audio file.
|
|
* </refsect2>
|
|
*/
|
|
|
|
/*
|
|
* design decisions:
|
|
* - audioconvert converts buffers in a set of supported caps. If it supports
|
|
* a caps, it supports conversion from these caps to any other caps it
|
|
* supports. (example: if it does A=>B and A=>C, it also does B=>C)
|
|
* - audioconvert does not save state between buffers. Every incoming buffer is
|
|
* converted and the converted buffer is pushed out.
|
|
* conclusion:
|
|
* audioconvert is not supposed to be a one-element-does-anything solution for
|
|
* audio conversions.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
#include "gstaudioconvert.h"
|
|
#include "plugin.h"
|
|
|
|
GST_DEBUG_CATEGORY (audio_convert_debug);
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_PERFORMANCE);
|
|
#define GST_CAT_DEFAULT (audio_convert_debug)
|
|
|
|
/*** DEFINITIONS **************************************************************/
|
|
|
|
/* type functions */
|
|
static void gst_audio_convert_dispose (GObject * obj);
|
|
|
|
/* gstreamer functions */
|
|
static gboolean gst_audio_convert_get_unit_size (GstBaseTransform * base,
|
|
GstCaps * caps, gsize * size);
|
|
static GstCaps *gst_audio_convert_transform_caps (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * filter);
|
|
static GstCaps *gst_audio_convert_fixate_caps (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps);
|
|
static gboolean gst_audio_convert_set_caps (GstBaseTransform * base,
|
|
GstCaps * incaps, GstCaps * outcaps);
|
|
static GstFlowReturn gst_audio_convert_transform (GstBaseTransform * base,
|
|
GstBuffer * inbuf, GstBuffer * outbuf);
|
|
static gboolean gst_audio_convert_transform_meta (GstBaseTransform * trans,
|
|
GstBuffer * outbuf, GstMeta * meta, GstBuffer * inbuf);
|
|
static GstFlowReturn gst_audio_convert_submit_input_buffer (GstBaseTransform *
|
|
base, gboolean is_discont, GstBuffer * input);
|
|
static void gst_audio_convert_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_audio_convert_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
/* AudioConvert signals and args */
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_DITHERING,
|
|
PROP_NOISE_SHAPING,
|
|
};
|
|
|
|
#define DEBUG_INIT \
|
|
GST_DEBUG_CATEGORY_INIT (audio_convert_debug, "audioconvert", 0, "audio conversion element"); \
|
|
GST_DEBUG_CATEGORY_GET (GST_CAT_PERFORMANCE, "GST_PERFORMANCE");
|
|
#define gst_audio_convert_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstAudioConvert, gst_audio_convert,
|
|
GST_TYPE_BASE_TRANSFORM, DEBUG_INIT);
|
|
|
|
/*** GSTREAMER PROTOTYPES *****************************************************/
|
|
|
|
#define STATIC_CAPS \
|
|
GST_STATIC_CAPS (GST_AUDIO_CAPS_MAKE (GST_AUDIO_FORMATS_ALL) \
|
|
", layout = (string) interleaved")
|
|
|
|
static GstStaticPadTemplate gst_audio_convert_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
STATIC_CAPS);
|
|
|
|
static GstStaticPadTemplate gst_audio_convert_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
STATIC_CAPS);
|
|
|
|
|
|
/*** TYPE FUNCTIONS ***********************************************************/
|
|
static void
|
|
gst_audio_convert_class_init (GstAudioConvertClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstBaseTransformClass *basetransform_class = GST_BASE_TRANSFORM_CLASS (klass);
|
|
|
|
gobject_class->dispose = gst_audio_convert_dispose;
|
|
gobject_class->set_property = gst_audio_convert_set_property;
|
|
gobject_class->get_property = gst_audio_convert_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_DITHERING,
|
|
g_param_spec_enum ("dithering", "Dithering",
|
|
"Selects between different dithering methods.",
|
|
GST_TYPE_AUDIO_DITHER_METHOD, GST_AUDIO_DITHER_TPDF,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_NOISE_SHAPING,
|
|
g_param_spec_enum ("noise-shaping", "Noise shaping",
|
|
"Selects between different noise shaping methods.",
|
|
GST_TYPE_AUDIO_NOISE_SHAPING_METHOD, GST_AUDIO_NOISE_SHAPING_NONE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_audio_convert_src_template));
|
|
gst_element_class_add_pad_template (element_class,
|
|
gst_static_pad_template_get (&gst_audio_convert_sink_template));
|
|
gst_element_class_set_static_metadata (element_class,
|
|
"Audio converter", "Filter/Converter/Audio",
|
|
"Convert audio to different formats", "Benjamin Otte <otte@gnome.org>");
|
|
|
|
basetransform_class->get_unit_size =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_get_unit_size);
|
|
basetransform_class->transform_caps =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_transform_caps);
|
|
basetransform_class->fixate_caps =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_fixate_caps);
|
|
basetransform_class->set_caps =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_set_caps);
|
|
basetransform_class->transform =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_transform);
|
|
basetransform_class->transform_meta =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_transform_meta);
|
|
basetransform_class->submit_input_buffer =
|
|
GST_DEBUG_FUNCPTR (gst_audio_convert_submit_input_buffer);
|
|
|
|
basetransform_class->passthrough_on_same_caps = TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_init (GstAudioConvert * this)
|
|
{
|
|
this->dither = GST_AUDIO_DITHER_TPDF;
|
|
this->ns = GST_AUDIO_NOISE_SHAPING_NONE;
|
|
|
|
gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (this), TRUE);
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_dispose (GObject * obj)
|
|
{
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (obj);
|
|
|
|
if (this->convert) {
|
|
gst_audio_converter_free (this->convert);
|
|
this->convert = NULL;
|
|
}
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (obj);
|
|
}
|
|
|
|
/*** GSTREAMER FUNCTIONS ******************************************************/
|
|
|
|
/* BaseTransform vmethods */
|
|
static gboolean
|
|
gst_audio_convert_get_unit_size (GstBaseTransform * base, GstCaps * caps,
|
|
gsize * size)
|
|
{
|
|
GstAudioInfo info;
|
|
|
|
g_assert (size);
|
|
|
|
if (!gst_audio_info_from_caps (&info, caps))
|
|
goto parse_error;
|
|
|
|
*size = info.bpf;
|
|
GST_INFO_OBJECT (base, "unit_size = %" G_GSIZE_FORMAT, *size);
|
|
|
|
return TRUE;
|
|
|
|
parse_error:
|
|
{
|
|
GST_INFO_OBJECT (base, "failed to parse caps to get unit_size");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* copies the given caps */
|
|
static GstCaps *
|
|
gst_audio_convert_caps_remove_format_info (GstCaps * caps, gboolean channels)
|
|
{
|
|
GstStructure *st;
|
|
gint i, n;
|
|
GstCaps *res;
|
|
guint64 channel_mask;
|
|
|
|
res = gst_caps_new_empty ();
|
|
|
|
n = gst_caps_get_size (caps);
|
|
for (i = 0; i < n; i++) {
|
|
gboolean remove_channels = FALSE;
|
|
|
|
st = gst_caps_get_structure (caps, i);
|
|
|
|
/* If this is already expressed by the existing caps
|
|
* skip this structure */
|
|
if (i > 0 && gst_caps_is_subset_structure (res, st))
|
|
continue;
|
|
|
|
st = gst_structure_copy (st);
|
|
gst_structure_remove_field (st, "format");
|
|
|
|
/* Only remove the channels and channel-mask for non-NONE layouts */
|
|
if (gst_structure_get (st, "channel-mask", GST_TYPE_BITMASK, &channel_mask,
|
|
NULL)) {
|
|
if (channel_mask != 0)
|
|
remove_channels = TRUE;
|
|
} else {
|
|
remove_channels = TRUE;
|
|
}
|
|
|
|
if (remove_channels && channels)
|
|
gst_structure_remove_fields (st, "channel-mask", "channels", NULL);
|
|
|
|
gst_caps_append_structure (res, st);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
/* The caps can be transformed into any other caps with format info removed.
|
|
* However, we should prefer passthrough, so if passthrough is possible,
|
|
* put it first in the list. */
|
|
static GstCaps *
|
|
gst_audio_convert_transform_caps (GstBaseTransform * btrans,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * filter)
|
|
{
|
|
GstCaps *tmp, *tmp2;
|
|
GstCaps *result;
|
|
|
|
/* Get all possible caps that we can transform to */
|
|
tmp = gst_audio_convert_caps_remove_format_info (caps, TRUE);
|
|
|
|
if (filter) {
|
|
tmp2 = gst_caps_intersect_full (filter, tmp, GST_CAPS_INTERSECT_FIRST);
|
|
gst_caps_unref (tmp);
|
|
tmp = tmp2;
|
|
}
|
|
|
|
result = tmp;
|
|
|
|
GST_DEBUG_OBJECT (btrans, "transformed %" GST_PTR_FORMAT " into %"
|
|
GST_PTR_FORMAT, caps, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gint
|
|
n_bits_set (guint64 x)
|
|
{
|
|
gint i;
|
|
gint c = 0;
|
|
guint64 y = 1;
|
|
|
|
for (i = 0; i < 64; i++) {
|
|
if (x & y)
|
|
c++;
|
|
y <<= 1;
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
static guint64
|
|
find_suitable_mask (guint64 mask, gint n_chans)
|
|
{
|
|
guint64 intersection;
|
|
gint i;
|
|
|
|
i = 0;
|
|
|
|
g_assert (n_bits_set (mask) >= n_chans);
|
|
|
|
intersection = mask;
|
|
do {
|
|
intersection = intersection & ((~G_GUINT64_CONSTANT (0)) >> i);
|
|
i++;
|
|
} while (n_bits_set (intersection) > n_chans && i < 64);
|
|
|
|
if (i < 64)
|
|
return intersection;
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_fixate_format (GstBaseTransform * base, GstStructure * ins,
|
|
GstStructure * outs)
|
|
{
|
|
const gchar *in_format;
|
|
const GValue *format;
|
|
const GstAudioFormatInfo *in_info, *out_info = NULL;
|
|
GstAudioFormatFlags in_flags, out_flags = 0;
|
|
gint in_depth, out_depth = -1;
|
|
gint i, len;
|
|
|
|
in_format = gst_structure_get_string (ins, "format");
|
|
if (!in_format)
|
|
return;
|
|
|
|
format = gst_structure_get_value (outs, "format");
|
|
/* should not happen */
|
|
if (format == NULL)
|
|
return;
|
|
|
|
/* nothing to fixate? */
|
|
if (!GST_VALUE_HOLDS_LIST (format))
|
|
return;
|
|
|
|
in_info =
|
|
gst_audio_format_get_info (gst_audio_format_from_string (in_format));
|
|
if (!in_info)
|
|
return;
|
|
|
|
in_flags = GST_AUDIO_FORMAT_INFO_FLAGS (in_info);
|
|
in_flags &= ~(GST_AUDIO_FORMAT_FLAG_UNPACK);
|
|
in_flags &= ~(GST_AUDIO_FORMAT_FLAG_SIGNED);
|
|
|
|
in_depth = GST_AUDIO_FORMAT_INFO_DEPTH (in_info);
|
|
|
|
len = gst_value_list_get_size (format);
|
|
for (i = 0; i < len; i++) {
|
|
const GstAudioFormatInfo *t_info;
|
|
GstAudioFormatFlags t_flags;
|
|
gboolean t_flags_better;
|
|
const GValue *val;
|
|
const gchar *fname;
|
|
gint t_depth;
|
|
|
|
val = gst_value_list_get_value (format, i);
|
|
if (!G_VALUE_HOLDS_STRING (val))
|
|
continue;
|
|
|
|
fname = g_value_get_string (val);
|
|
t_info = gst_audio_format_get_info (gst_audio_format_from_string (fname));
|
|
if (!t_info)
|
|
continue;
|
|
|
|
/* accept input format immediately */
|
|
if (strcmp (fname, in_format) == 0) {
|
|
out_info = t_info;
|
|
break;
|
|
}
|
|
|
|
t_flags = GST_AUDIO_FORMAT_INFO_FLAGS (t_info);
|
|
t_flags &= ~(GST_AUDIO_FORMAT_FLAG_UNPACK);
|
|
t_flags &= ~(GST_AUDIO_FORMAT_FLAG_SIGNED);
|
|
|
|
t_depth = GST_AUDIO_FORMAT_INFO_DEPTH (t_info);
|
|
|
|
/* Any output format is better than no output format at all */
|
|
if (!out_info) {
|
|
out_info = t_info;
|
|
out_depth = t_depth;
|
|
out_flags = t_flags;
|
|
continue;
|
|
}
|
|
|
|
t_flags_better = (t_flags == in_flags && out_flags != in_flags);
|
|
|
|
if (t_depth == in_depth && (out_depth != in_depth || t_flags_better)) {
|
|
/* Prefer to use the first format that has the same depth with the same
|
|
* flags, and if none with the same flags exist use the first other one
|
|
* that has the same depth */
|
|
out_info = t_info;
|
|
out_depth = t_depth;
|
|
out_flags = t_flags;
|
|
} else if (t_depth >= in_depth && (in_depth > out_depth
|
|
|| (out_depth >= in_depth && t_flags_better))) {
|
|
/* Otherwise use the first format that has a higher depth with the same flags,
|
|
* if none with the same flags exist use the first other one that has a higher
|
|
* depth */
|
|
out_info = t_info;
|
|
out_depth = t_depth;
|
|
out_flags = t_flags;
|
|
} else if ((t_depth > out_depth && out_depth < in_depth)
|
|
|| (t_flags_better && out_depth == t_depth)) {
|
|
/* Else get at least the one with the highest depth, ideally with the same flags */
|
|
out_info = t_info;
|
|
out_depth = t_depth;
|
|
out_flags = t_flags;
|
|
}
|
|
|
|
}
|
|
|
|
if (out_info)
|
|
gst_structure_set (outs, "format", G_TYPE_STRING,
|
|
GST_AUDIO_FORMAT_INFO_NAME (out_info), NULL);
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_fixate_channels (GstBaseTransform * base, GstStructure * ins,
|
|
GstStructure * outs)
|
|
{
|
|
gint in_chans, out_chans;
|
|
guint64 in_mask = 0, out_mask = 0;
|
|
gboolean has_in_mask = FALSE, has_out_mask = FALSE;
|
|
|
|
if (!gst_structure_get_int (ins, "channels", &in_chans))
|
|
return; /* this shouldn't really happen, should it? */
|
|
|
|
if (!gst_structure_has_field (outs, "channels")) {
|
|
/* we could try to get the implied number of channels from the layout,
|
|
* but that seems overdoing it for a somewhat exotic corner case */
|
|
gst_structure_remove_field (outs, "channel-mask");
|
|
return;
|
|
}
|
|
|
|
/* ok, let's fixate the channels if they are not fixated yet */
|
|
gst_structure_fixate_field_nearest_int (outs, "channels", in_chans);
|
|
|
|
if (!gst_structure_get_int (outs, "channels", &out_chans)) {
|
|
/* shouldn't really happen ... */
|
|
gst_structure_remove_field (outs, "channel-mask");
|
|
return;
|
|
}
|
|
|
|
/* get the channel layout of the output if any */
|
|
has_out_mask = gst_structure_has_field (outs, "channel-mask");
|
|
if (has_out_mask) {
|
|
gst_structure_get (outs, "channel-mask", GST_TYPE_BITMASK, &out_mask, NULL);
|
|
} else {
|
|
/* channels == 1 => MONO */
|
|
if (out_chans == 2) {
|
|
out_mask =
|
|
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_LEFT) |
|
|
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_RIGHT);
|
|
has_out_mask = TRUE;
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, out_mask,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/* get the channel layout of the input if any */
|
|
has_in_mask = gst_structure_has_field (ins, "channel-mask");
|
|
if (has_in_mask) {
|
|
gst_structure_get (ins, "channel-mask", GST_TYPE_BITMASK, &in_mask, NULL);
|
|
} else {
|
|
/* channels == 1 => MONO */
|
|
if (in_chans == 2) {
|
|
in_mask =
|
|
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_LEFT) |
|
|
GST_AUDIO_CHANNEL_POSITION_MASK (FRONT_RIGHT);
|
|
has_in_mask = TRUE;
|
|
} else if (in_chans > 2)
|
|
g_warning ("%s: Upstream caps contain no channel mask",
|
|
GST_ELEMENT_NAME (base));
|
|
}
|
|
|
|
if (!has_out_mask && out_chans == 1 && (in_chans != out_chans
|
|
|| !has_in_mask))
|
|
return; /* nothing to do, default layout will be assumed */
|
|
|
|
if (in_chans == out_chans && (has_in_mask || in_chans == 1)) {
|
|
/* same number of channels and no output layout: just use input layout */
|
|
if (!has_out_mask) {
|
|
/* in_chans == 1 handled above already */
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, in_mask, NULL);
|
|
return;
|
|
}
|
|
|
|
/* If both masks are the same we're done, this includes the NONE layout case */
|
|
if (in_mask == out_mask)
|
|
return;
|
|
|
|
/* if output layout is fixed already and looks sane, we're done */
|
|
if (n_bits_set (out_mask) == out_chans)
|
|
return;
|
|
|
|
if (n_bits_set (out_mask) < in_chans) {
|
|
/* Not much we can do here, this shouldn't just happen */
|
|
g_warning ("%s: Invalid downstream channel-mask with too few bits set",
|
|
GST_ELEMENT_NAME (base));
|
|
} else {
|
|
guint64 intersection;
|
|
|
|
/* if the output layout is not fixed, check if the output layout contains
|
|
* the input layout */
|
|
intersection = in_mask & out_mask;
|
|
if (n_bits_set (intersection) >= in_chans) {
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, in_mask,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
/* output layout is not fixed and does not contain the input layout, so
|
|
* just pick the first possibility */
|
|
intersection = find_suitable_mask (out_mask, out_chans);
|
|
if (intersection) {
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, intersection,
|
|
NULL);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* ... else fall back to default layout (NB: out_layout is NULL here) */
|
|
GST_WARNING_OBJECT (base, "unexpected output channel layout");
|
|
} else {
|
|
guint64 intersection;
|
|
|
|
/* number of input channels != number of output channels:
|
|
* if this value contains a list of channel layouts (or even worse: a list
|
|
* with another list), just pick the first value and repeat until we find a
|
|
* channel position array or something else that's not a list; we assume
|
|
* the input if half-way sane and don't try to fall back on other list items
|
|
* if the first one is something unexpected or non-channel-pos-array-y */
|
|
if (n_bits_set (out_mask) >= out_chans) {
|
|
intersection = find_suitable_mask (out_mask, out_chans);
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, intersection,
|
|
NULL);
|
|
return;
|
|
}
|
|
|
|
/* what now?! Just ignore what we're given and use default positions */
|
|
GST_WARNING_OBJECT (base, "invalid or unexpected channel-positions");
|
|
}
|
|
|
|
/* missing or invalid output layout and we can't use the input layout for
|
|
* one reason or another, so just pick a default layout (we could be smarter
|
|
* and try to add/remove channels from the input layout, or pick a default
|
|
* layout based on LFE-presence in input layout, but let's save that for
|
|
* another day) */
|
|
if (out_chans > 0
|
|
&& (out_mask = gst_audio_channel_get_fallback_mask (out_chans))) {
|
|
GST_DEBUG_OBJECT (base, "using default channel layout as fallback");
|
|
gst_structure_set (outs, "channel-mask", GST_TYPE_BITMASK, out_mask, NULL);
|
|
} else {
|
|
GST_ERROR_OBJECT (base, "Have no default layout for %d channels",
|
|
out_chans);
|
|
}
|
|
}
|
|
|
|
/* try to keep as many of the structure members the same by fixating the
|
|
* possible ranges; this way we convert the least amount of things as possible
|
|
*/
|
|
static GstCaps *
|
|
gst_audio_convert_fixate_caps (GstBaseTransform * base,
|
|
GstPadDirection direction, GstCaps * caps, GstCaps * othercaps)
|
|
{
|
|
GstStructure *ins, *outs;
|
|
GstCaps *result;
|
|
|
|
GST_DEBUG_OBJECT (base, "trying to fixate othercaps %" GST_PTR_FORMAT
|
|
" based on caps %" GST_PTR_FORMAT, othercaps, caps);
|
|
|
|
result = gst_caps_intersect (othercaps, caps);
|
|
if (gst_caps_is_empty (result)) {
|
|
GstCaps *removed;
|
|
|
|
if (result)
|
|
gst_caps_unref (result);
|
|
/* try to preserve channels */
|
|
removed = gst_audio_convert_caps_remove_format_info (caps, FALSE);
|
|
result = gst_caps_intersect (othercaps, removed);
|
|
gst_caps_unref (removed);
|
|
if (gst_caps_is_empty (result)) {
|
|
if (result)
|
|
gst_caps_unref (result);
|
|
result = othercaps;
|
|
} else {
|
|
gst_caps_unref (othercaps);
|
|
}
|
|
} else {
|
|
gst_caps_unref (othercaps);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (base, "now fixating %" GST_PTR_FORMAT, result);
|
|
|
|
/* fixate remaining fields */
|
|
result = gst_caps_make_writable (result);
|
|
|
|
ins = gst_caps_get_structure (caps, 0);
|
|
outs = gst_caps_get_structure (result, 0);
|
|
|
|
gst_audio_convert_fixate_channels (base, ins, outs);
|
|
gst_audio_convert_fixate_format (base, ins, outs);
|
|
|
|
/* fixate remaining */
|
|
result = gst_caps_fixate (result);
|
|
|
|
GST_DEBUG_OBJECT (base, "fixated othercaps to %" GST_PTR_FORMAT, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_convert_set_caps (GstBaseTransform * base, GstCaps * incaps,
|
|
GstCaps * outcaps)
|
|
{
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (base);
|
|
GstAudioInfo in_info;
|
|
GstAudioInfo out_info;
|
|
|
|
GST_DEBUG_OBJECT (base, "incaps %" GST_PTR_FORMAT ", outcaps %"
|
|
GST_PTR_FORMAT, incaps, outcaps);
|
|
|
|
if (this->convert) {
|
|
gst_audio_converter_free (this->convert);
|
|
this->convert = NULL;
|
|
}
|
|
|
|
if (!gst_audio_info_from_caps (&in_info, incaps))
|
|
goto invalid_in;
|
|
if (!gst_audio_info_from_caps (&out_info, outcaps))
|
|
goto invalid_out;
|
|
|
|
this->convert = gst_audio_converter_new (&in_info, &out_info,
|
|
gst_structure_new ("GstAudioConverterConfig",
|
|
GST_AUDIO_CONVERTER_OPT_DITHER_METHOD, GST_TYPE_AUDIO_DITHER_METHOD,
|
|
this->dither,
|
|
GST_AUDIO_CONVERTER_OPT_NOISE_SHAPING_METHOD,
|
|
GST_TYPE_AUDIO_NOISE_SHAPING_METHOD, this->ns, NULL));
|
|
|
|
if (this->convert == NULL)
|
|
goto no_converter;
|
|
|
|
this->in_info = in_info;
|
|
this->out_info = out_info;
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
invalid_in:
|
|
{
|
|
GST_ERROR_OBJECT (base, "invalid input caps");
|
|
return FALSE;
|
|
}
|
|
invalid_out:
|
|
{
|
|
GST_ERROR_OBJECT (base, "invalid output caps");
|
|
return FALSE;
|
|
}
|
|
no_converter:
|
|
{
|
|
GST_ERROR_OBJECT (base, "could not make converter");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_audio_convert_transform (GstBaseTransform * base, GstBuffer * inbuf,
|
|
GstBuffer * outbuf)
|
|
{
|
|
GstFlowReturn ret;
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (base);
|
|
GstMapInfo srcmap, dstmap;
|
|
gint insize, outsize;
|
|
gboolean inbuf_writable;
|
|
GstAudioConverterFlags flags;
|
|
gsize samples, consumed, produced;
|
|
|
|
/* get amount of samples to convert. */
|
|
samples = gst_buffer_get_size (inbuf) / this->in_info.bpf;
|
|
|
|
/* get in/output sizes, to see if the buffers we got are of correct
|
|
* sizes */
|
|
insize = samples * this->in_info.bpf;
|
|
outsize = samples * this->out_info.bpf;
|
|
|
|
if (insize == 0 || outsize == 0)
|
|
return GST_FLOW_OK;
|
|
|
|
inbuf_writable = gst_buffer_is_writable (inbuf)
|
|
&& gst_buffer_n_memory (inbuf) == 1
|
|
&& gst_memory_is_writable (gst_buffer_peek_memory (inbuf, 0));
|
|
|
|
/* get src and dst data */
|
|
gst_buffer_map (inbuf, &srcmap,
|
|
inbuf_writable ? GST_MAP_READWRITE : GST_MAP_READ);
|
|
gst_buffer_map (outbuf, &dstmap, GST_MAP_WRITE);
|
|
|
|
/* check in and outsize */
|
|
if (srcmap.size < insize)
|
|
goto wrong_size;
|
|
if (dstmap.size < outsize)
|
|
goto wrong_size;
|
|
|
|
/* and convert the samples */
|
|
flags = 0;
|
|
if (inbuf_writable)
|
|
flags |= GST_AUDIO_CONVERTER_FLAG_SOURCE_WRITABLE;
|
|
|
|
if (!GST_BUFFER_FLAG_IS_SET (inbuf, GST_BUFFER_FLAG_GAP)) {
|
|
gpointer in[1] = { srcmap.data };
|
|
gpointer out[1] = { dstmap.data };
|
|
|
|
if (!gst_audio_converter_samples (this->convert, flags,
|
|
in, samples, out, samples, &consumed, &produced))
|
|
goto convert_error;
|
|
} else {
|
|
/* Create silence buffer */
|
|
gst_audio_format_fill_silence (this->out_info.finfo, dstmap.data, outsize);
|
|
}
|
|
ret = GST_FLOW_OK;
|
|
|
|
done:
|
|
gst_buffer_unmap (outbuf, &dstmap);
|
|
gst_buffer_unmap (inbuf, &srcmap);
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
wrong_size:
|
|
{
|
|
GST_ELEMENT_ERROR (this, STREAM, FORMAT,
|
|
(NULL),
|
|
("input/output buffers are of wrong size in: %" G_GSIZE_FORMAT " < %d"
|
|
" or out: %" G_GSIZE_FORMAT " < %d",
|
|
srcmap.size, insize, dstmap.size, outsize));
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
convert_error:
|
|
{
|
|
GST_ELEMENT_ERROR (this, STREAM, FORMAT,
|
|
(NULL), ("error while converting"));
|
|
ret = GST_FLOW_ERROR;
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_audio_convert_transform_meta (GstBaseTransform * trans, GstBuffer * outbuf,
|
|
GstMeta * meta, GstBuffer * inbuf)
|
|
{
|
|
const GstMetaInfo *info = meta->info;
|
|
const gchar *const *tags;
|
|
|
|
tags = gst_meta_api_type_get_tags (info->api);
|
|
|
|
if (!tags || (g_strv_length ((gchar **) tags) == 1
|
|
&& gst_meta_api_type_has_tag (info->api,
|
|
g_quark_from_string (GST_META_TAG_AUDIO_STR))))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_audio_convert_submit_input_buffer (GstBaseTransform * base,
|
|
gboolean is_discont, GstBuffer * input)
|
|
{
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (base);
|
|
|
|
if (base->segment.format == GST_FORMAT_TIME) {
|
|
input =
|
|
gst_audio_buffer_clip (input, &base->segment, this->in_info.rate,
|
|
this->in_info.bpf);
|
|
|
|
if (!input)
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
return GST_BASE_TRANSFORM_CLASS (parent_class)->submit_input_buffer (base,
|
|
is_discont, input);
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DITHERING:
|
|
this->dither = g_value_get_enum (value);
|
|
break;
|
|
case PROP_NOISE_SHAPING:
|
|
this->ns = g_value_get_enum (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_audio_convert_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioConvert *this = GST_AUDIO_CONVERT (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_DITHERING:
|
|
g_value_set_enum (value, this->dither);
|
|
break;
|
|
case PROP_NOISE_SHAPING:
|
|
g_value_set_enum (value, this->ns);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|