diff options
author | Vivia Nikolaidou <vivia@toolsonair.com> | 2017-01-16 19:36:07 +0200 |
---|---|---|
committer | Sebastian Dröge <sebastian@centricular.com> | 2017-02-23 21:02:44 +0200 |
commit | fa47edf84698ffd8a62e5c72ff2ef918f3fe30f0 (patch) | |
tree | 09b6d343c703cee57dc03d4c533f133bb6302d50 /gst | |
parent | fc2ca69c2563a85c05fa4d04faf932d4df77645a (diff) |
audiomixmatrix: New element that mixes audio channels
This element transforms a given number of input channels into a given number of
output channels according to a given transformation matrix. The matrix
coefficients must be between -1 and 1. In the auto mode, input/output channels
are automatically negotiated and the transformation matrix is a truncated or
zero-padded identity matrix.
https://bugzilla.gnome.org/show_bug.cgi?id=777376
Diffstat (limited to 'gst')
-rw-r--r-- | gst/audiomixmatrix/Makefile.am | 10 | ||||
-rw-r--r-- | gst/audiomixmatrix/gstaudiomixmatrix.c | 813 | ||||
-rw-r--r-- | gst/audiomixmatrix/gstaudiomixmatrix.h | 78 |
3 files changed, 901 insertions, 0 deletions
diff --git a/gst/audiomixmatrix/Makefile.am b/gst/audiomixmatrix/Makefile.am new file mode 100644 index 000000000..e54eb9d03 --- /dev/null +++ b/gst/audiomixmatrix/Makefile.am @@ -0,0 +1,10 @@ +plugin_LTLIBRARIES = libgstaudiomixmatrix.la + +libgstaudiomixmatrix_la_SOURCES = gstaudiomixmatrix.c + +libgstaudiomixmatrix_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) +libgstaudiomixmatrix_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_API_VERSION) $(GST_BASE_LIBS) $(GST_LIBS) $(LIBM) +libgstaudiomixmatrix_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstaudiomixmatrix_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = gstaudiomixmatrix.h diff --git a/gst/audiomixmatrix/gstaudiomixmatrix.c b/gst/audiomixmatrix/gstaudiomixmatrix.c new file mode 100644 index 000000000..5604d064b --- /dev/null +++ b/gst/audiomixmatrix/gstaudiomixmatrix.c @@ -0,0 +1,813 @@ +/* + * GStreamer + * Copyright (C) 2017 Vivia Nikolaidou <vivia@toolsonair.com> + * + * gstaudiomixmatrix.c + * + * 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-audiomixmatrix + * @short_description: Transform input/output channels according to a matrix + * + * This element transforms a given number of input channels into a given + * number of output channels according to a given transformation matrix. The + * matrix coefficients must be between -1 and 1: the number of rows is equal + * to the number of output channels and the number of columns is equal to the + * number of input channels. In the first-channels mode, input/output channels + * are automatically negotiated and the transformation matrix is a truncated + * identity matrix. + * + * <refsect2> + * <title>Example matrix generation code</title> + * To generate the matrix using code: + * + * |[ + * GValue v = G_VALUE_INIT; + * GValue v2 = G_VALUE_INIT; + * GValue v3 = G_VALUE_INIT; + * + * g_value_init (&v2, GST_TYPE_ARRAY); + * g_value_init (&v3, G_TYPE_DOUBLE); + * g_value_set_double (&v3, 1); + * gst_value_array_append_value (&v2, &v3); + * g_value_unset (&v3); + * [ Repeat for as many double as your input channels - unset and reinit v3 ] + * g_value_init (&v, GST_TYPE_ARRAY); + * gst_value_array_append_value (&v, &v2); + * g_value_unset (&v2); + * [ Repeat for as many v2's as your output channels - unset and reinit v2] + * g_object_set_property (G_OBJECT (audiomixmatrix), "matrix", &v); + * g_value_unset (&v); + * ]| + * </refsect2> + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 audiotestsrc ! audio/x-raw,channels=4 ! audiomixmatrix in-channels=4 out-channels=2 channel-mask=-1 matrix="<<(double)1, (double)0, (double)0, (double)0>, <0.0, 1.0, 0.0, 0.0>>" ! audio/x-raw,channels=2 ! autoaudiosink + * ]| + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +/* FIXME 2.0: suppress warnings for deprecated API such as GValueArray + * with newer GLib versions (>= 2.31.0) */ +#define GLIB_DISABLE_DEPRECATION_WARNINGS + +#include "gstaudiomixmatrix.h" + +#include <gst/gst.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> + +GST_DEBUG_CATEGORY_STATIC (audiomixmatrix_debug); +#define GST_CAT_DEFAULT audiomixmatrix_debug + +/* GstAudioMixMatrix properties */ +enum +{ + PROP_0, + PROP_IN_CHANNELS, + PROP_OUT_CHANNELS, + PROP_MATRIX, + PROP_MATRIX_VALUE_ARRAY, + PROP_CHANNEL_MASK, + PROP_MODE +}; + +GType +gst_audio_mix_matrix_mode_get_type (void) +{ + static GType gst_audio_mix_matrix_mode_type = 0; + static const GEnumValue gst_audio_mix_matrix_mode[] = { + {GST_AUDIO_MIX_MATRIX_MODE_MANUAL, + "Manual mode: please specify input/output channels and transformation matrix", + "manual"}, + {GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS, + "First channels mode: input/output channels are auto-negotiated, transformation matrix is a truncated identity matrix", + "first-channels"}, + {0, NULL, NULL} + + }; + + if (!gst_audio_mix_matrix_mode_type) { + gst_audio_mix_matrix_mode_type = + g_enum_register_static ("GstAudioMixMatrixModeType", + gst_audio_mix_matrix_mode); + } + return gst_audio_mix_matrix_mode_type; +} + +static GstStaticPadTemplate gst_audio_mix_matrix_src_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ("audio/x-raw, channels = [1, max], layout = (string) interleaved, format = (string) {" + GST_AUDIO_NE (F32) "," GST_AUDIO_NE (F64) "," GST_AUDIO_NE (S16) "," + GST_AUDIO_NE (S32) "}") + ); + +static GstStaticPadTemplate gst_audio_mix_matrix_sink_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ("audio/x-raw, channels = [1, max], layout = (string) interleaved, format = (string) {" + GST_AUDIO_NE (F32) "," GST_AUDIO_NE (F64) "," GST_AUDIO_NE (S16) "," + GST_AUDIO_NE (S32) "}") + ); + +static void gst_audio_mix_matrix_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_audio_mix_matrix_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_audio_mix_matrix_dispose (GObject * object); +static gboolean gst_audio_mix_matrix_get_unit_size (GstBaseTransform * trans, + GstCaps * caps, gsize * size); +static gboolean gst_audio_mix_matrix_set_caps (GstBaseTransform * trans, + GstCaps * incaps, GstCaps * outcaps); +static GstFlowReturn gst_audio_mix_matrix_transform (GstBaseTransform * trans, + GstBuffer * inbuf, GstBuffer * outbuf); +static GstCaps *gst_audio_mix_matrix_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter); +static GstCaps *gst_audio_mix_matrix_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps); +static GstStateChangeReturn gst_audio_mix_matrix_change_state (GstElement * + element, GstStateChange transition); + +G_DEFINE_TYPE (GstAudioMixMatrix, gst_audio_mix_matrix, + GST_TYPE_BASE_TRANSFORM); + +static void +gst_audio_mix_matrix_class_init (GstAudioMixMatrixClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstElementClass *element_class = (GstElementClass *) klass; + GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass; + + GST_DEBUG_CATEGORY_INIT (audiomixmatrix_debug, "audiomixmatrix", 0, + "audiomixmatrix"); + gst_element_class_set_static_metadata (element_class, "Matrix audio mix", + "Filter/Audio", + "Mixes a number of input channels into a number of output channels according to a transformation matrix", + "Vivia Nikolaidou <vivia@toolsonair.com>"); + + gobject_class->set_property = gst_audio_mix_matrix_set_property; + gobject_class->get_property = gst_audio_mix_matrix_get_property; + gobject_class->dispose = gst_audio_mix_matrix_dispose; + + g_object_class_install_property (gobject_class, PROP_IN_CHANNELS, + g_param_spec_uint ("in-channels", "Input audio channels", + "How many audio channels we have on the input side", + 0, 64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_OUT_CHANNELS, + g_param_spec_uint ("out-channels", "Output audio channels", + "How many audio channels we have on the output side", + 0, 64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_MATRIX, + gst_param_spec_array ("matrix", + "Input/output channel matrix", + "Transformation matrix for input/output channels", + gst_param_spec_array ("matrix-in1", "rows", "rows", + g_param_spec_double ("matrix-in2", "cols", "cols", + -1, 1, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_MATRIX_VALUE_ARRAY, + g_param_spec_value_array ("matrix-value-array", + "Input/output channel matrix (GValueArray)", + "Transformation matrix (GValueArray) for input/output channels", + g_param_spec_value_array ("matrix-va-in1", "rows", "rows", + g_param_spec_double ("matrix-va-in2", "cols", "cols", + -1, 1, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS), + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_CHANNEL_MASK, + g_param_spec_uint64 ("channel-mask", + "Output channel mask", + "Output channel mask (-1 means \"default for these channels\")", + 0, G_MAXUINT64, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_MODE, + g_param_spec_enum ("mode", + "Channel/matrix mode", + "Whether to auto-negotiate input/output channels and matrix", + GST_TYPE_AUDIO_MIX_MATRIX_MODE, + GST_AUDIO_MIX_MATRIX_MODE_MANUAL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_audio_mix_matrix_sink_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_audio_mix_matrix_src_template)); + + trans_class->get_unit_size = + GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_get_unit_size); + trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_set_caps); + trans_class->transform = GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_transform); + trans_class->transform_caps = + GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_transform_caps); + trans_class->fixate_caps = + GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_fixate_caps); + + element_class->change_state = + GST_DEBUG_FUNCPTR (gst_audio_mix_matrix_change_state); +} + +static void +gst_audio_mix_matrix_init (GstAudioMixMatrix * self) +{ + self->in_channels = 0; + self->out_channels = 0; + self->matrix = NULL; + self->channel_mask = 0; + self->s16_conv_matrix = NULL; + self->s32_conv_matrix = NULL; + self->mode = GST_AUDIO_MIX_MATRIX_MODE_MANUAL; +} + +static void +gst_audio_mix_matrix_dispose (GObject * object) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (object); + + if (self->matrix) { + g_free (self->matrix); + self->matrix = NULL; + } + + G_OBJECT_CLASS (gst_audio_mix_matrix_parent_class)->dispose (object); +} + +static void +gst_audio_mix_matrix_convert_s16_matrix (GstAudioMixMatrix * self) +{ + gint i; + + /* converted bits - input bits - sign - bits needed for channel */ + self->shift_bytes = 32 - 16 - 1 - ceil (log (self->in_channels) / log (2)); + + if (self->s16_conv_matrix) + g_free (self->s16_conv_matrix); + self->s16_conv_matrix = + g_new (gint32, self->in_channels * self->out_channels); + for (i = 0; i < self->in_channels * self->out_channels; i++) { + self->s16_conv_matrix[i] = + (gint32) ((self->matrix[i]) * (1 << self->shift_bytes)); + } +} + +static void +gst_audio_mix_matrix_convert_s32_matrix (GstAudioMixMatrix * self) +{ + gint i; + + /* converted bits - input bits - sign - bits needed for channel */ + self->shift_bytes = 64 - 32 - 1 - (gint) (log (self->in_channels) / log (2)); + + if (self->s32_conv_matrix) + g_free (self->s32_conv_matrix); + self->s32_conv_matrix = + g_new (gint64, self->in_channels * self->out_channels); + for (i = 0; i < self->in_channels * self->out_channels; i++) { + self->s32_conv_matrix[i] = + (gint64) ((self->matrix[i]) * (1 << self->shift_bytes)); + } +} + + +static void +gst_audio_mix_matrix_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (object); + + switch (prop_id) { + case PROP_IN_CHANNELS: + self->in_channels = g_value_get_uint (value); + if (self->matrix) { + gst_audio_mix_matrix_convert_s16_matrix (self); + gst_audio_mix_matrix_convert_s32_matrix (self); + } + break; + case PROP_OUT_CHANNELS: + self->out_channels = g_value_get_uint (value); + if (self->matrix) { + gst_audio_mix_matrix_convert_s16_matrix (self); + gst_audio_mix_matrix_convert_s32_matrix (self); + } + break; + case PROP_MATRIX:{ + gint in, out; + + if (self->matrix) + g_free (self->matrix); + self->matrix = g_new (gdouble, self->in_channels * self->out_channels); + + g_return_if_fail (gst_value_array_get_size (value) == self->out_channels); + for (out = 0; out < self->out_channels; out++) { + const GValue *row = gst_value_array_get_value (value, out); + g_return_if_fail (gst_value_array_get_size (row) == self->in_channels); + for (in = 0; in < self->in_channels; in++) { + const GValue *itm; + gdouble coefficient; + + itm = gst_value_array_get_value (row, in); + g_return_if_fail (G_VALUE_HOLDS_DOUBLE (itm)); + coefficient = g_value_get_double (itm); + self->matrix[out * self->in_channels + in] = coefficient; + } + } + gst_audio_mix_matrix_convert_s16_matrix (self); + gst_audio_mix_matrix_convert_s32_matrix (self); + break; + } + case PROP_MATRIX_VALUE_ARRAY:{ + gint in, out; + GValueArray *a; + + if (self->matrix) + g_free (self->matrix); + self->matrix = g_new (gdouble, self->in_channels * self->out_channels); + + a = g_value_get_boxed (value); + g_return_if_fail (a->n_values == self->out_channels); + for (out = 0; out < self->out_channels; out++) { + const GValue *row = g_value_array_get_nth (a, out); + GValueArray *ra = g_value_get_boxed (row); + + g_return_if_fail (ra->n_values == self->in_channels); + for (in = 0; in < self->in_channels; in++) { + const GValue *itm; + gdouble coefficient; + + itm = g_value_array_get_nth (ra, in); + g_return_if_fail (G_VALUE_HOLDS_DOUBLE (itm)); + coefficient = g_value_get_double (itm); + self->matrix[out * self->in_channels + in] = coefficient; + } + } + gst_audio_mix_matrix_convert_s16_matrix (self); + gst_audio_mix_matrix_convert_s32_matrix (self); + break; + } + case PROP_CHANNEL_MASK: + self->channel_mask = g_value_get_uint64 (value); + break; + case PROP_MODE: + self->mode = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_audio_mix_matrix_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (object); + + switch (prop_id) { + case PROP_IN_CHANNELS: + g_value_set_uint (value, self->in_channels); + break; + case PROP_OUT_CHANNELS: + g_value_set_uint (value, self->out_channels); + break; + case PROP_MATRIX:{ + gint in, out; + + for (out = 0; out < self->out_channels; out++) { + GValue row = G_VALUE_INIT; + g_value_init (&row, GST_TYPE_ARRAY); + for (in = 0; in < self->in_channels; in++) { + GValue itm = G_VALUE_INIT; + g_value_init (&itm, G_TYPE_DOUBLE); + g_value_set_double (&itm, self->matrix[out * self->in_channels + in]); + gst_value_array_append_value (&row, &itm); + g_value_unset (&itm); + } + gst_value_array_append_value (value, &row); + g_value_unset (&row); + } + break; + } + case PROP_MATRIX_VALUE_ARRAY:{ + gint in, out; + GValueArray *a = g_value_array_new (self->out_channels); + + for (out = 0; out < self->out_channels; out++) { + GValue row = G_VALUE_INIT; + GValueArray *ra = g_value_array_new (self->in_channels); + + g_value_init (&row, G_TYPE_VALUE_ARRAY); + for (in = 0; in < self->in_channels; in++) { + GValue itm = G_VALUE_INIT; + g_value_init (&itm, G_TYPE_DOUBLE); + g_value_set_double (&itm, self->matrix[out * self->in_channels + in]); + g_value_array_append (ra, &itm); + g_value_unset (&itm); + } + g_value_take_boxed (&row, ra); + g_value_array_append (a, &row); + g_value_unset (&row); + } + g_value_take_boxed (value, a); + break; + } + case PROP_CHANNEL_MASK: + g_value_set_uint64 (value, self->channel_mask); + break; + case PROP_MODE: + g_value_set_enum (value, self->mode); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static GstStateChangeReturn +gst_audio_mix_matrix_change_state (GstElement * element, + GstStateChange transition) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (element); + GstStateChangeReturn s; + + s = GST_ELEMENT_CLASS (gst_audio_mix_matrix_parent_class)->change_state + (element, transition); + + if (transition == GST_STATE_CHANGE_PAUSED_TO_READY) { + if (self->s16_conv_matrix) { + g_free (self->s16_conv_matrix); + self->s16_conv_matrix = NULL; + } + + if (self->s32_conv_matrix) { + g_free (self->s32_conv_matrix); + self->s32_conv_matrix = NULL; + } + } + + return s; +} + + +static GstFlowReturn +gst_audio_mix_matrix_transform (GstBaseTransform * vfilter, + GstBuffer * inbuf, GstBuffer * outbuf) +{ + GstMapInfo inmap, outmap; + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (vfilter); + gint in, out, sample; + guint inchannels = self->in_channels; + guint outchannels = self->out_channels; + gdouble *matrix = self->matrix; + + if (!gst_buffer_map (inbuf, &inmap, GST_MAP_READ)) { + return GST_FLOW_ERROR; + } + if (!gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE)) { + gst_buffer_unmap (inbuf, &inmap); + return GST_FLOW_ERROR; + } + + switch (self->format) { + case GST_AUDIO_FORMAT_F32LE: + case GST_AUDIO_FORMAT_F32BE:{ + const gfloat *inarray; + gfloat *outarray; + guint n_samples = outmap.size / (sizeof (gfloat) * outchannels); + + inarray = (gfloat *) inmap.data; + outarray = (gfloat *) outmap.data; + + for (sample = 0; sample < n_samples; sample++) { + for (out = 0; out < outchannels; out++) { + gfloat outval = 0; + for (in = 0; in < inchannels; in++) { + outval += + inarray[sample * inchannels + + in] * matrix[out * inchannels + in]; + } + outarray[sample * outchannels + out] = outval; + } + } + break; + } + case GST_AUDIO_FORMAT_F64LE: + case GST_AUDIO_FORMAT_F64BE:{ + const gdouble *inarray; + gdouble *outarray; + guint n_samples = outmap.size / (sizeof (gdouble) * outchannels); + + inarray = (gdouble *) inmap.data; + outarray = (gdouble *) outmap.data; + + for (sample = 0; sample < n_samples; sample++) { + for (out = 0; out < outchannels; out++) { + gdouble outval = 0; + for (in = 0; in < inchannels; in++) { + outval += + inarray[sample * inchannels + + in] * matrix[out * inchannels + in]; + } + outarray[sample * outchannels + out] = outval; + } + } + break; + } + case GST_AUDIO_FORMAT_S16LE: + case GST_AUDIO_FORMAT_S16BE:{ + const gint16 *inarray; + gint16 *outarray; + guint n_samples = outmap.size / (sizeof (gint16) * outchannels); + guint n = self->shift_bytes; + gint32 *conv_matrix = self->s16_conv_matrix; + + inarray = (gint16 *) inmap.data; + outarray = (gint16 *) outmap.data; + + for (sample = 0; sample < n_samples; sample++) { + for (out = 0; out < outchannels; out++) { + gint32 outval = 0; + for (in = 0; in < inchannels; in++) { + outval += (gint32) (inarray[sample * inchannels + in] * + conv_matrix[out * inchannels + in]); + } + outarray[sample * outchannels + out] = (gint16) (outval >> n); + } + } + break; + } + case GST_AUDIO_FORMAT_S32LE: + case GST_AUDIO_FORMAT_S32BE:{ + const gint32 *inarray; + gint32 *outarray; + guint n_samples = outmap.size / (sizeof (gint32) * outchannels); + guint n = self->shift_bytes; + gint64 *conv_matrix = self->s32_conv_matrix; + + inarray = (gint32 *) inmap.data; + outarray = (gint32 *) outmap.data; + + for (sample = 0; sample < n_samples; sample++) { + for (out = 0; out < outchannels; out++) { + gint64 outval = 0; + for (in = 0; in < inchannels; in++) { + outval += (gint64) (inarray[sample * inchannels + in] * + conv_matrix[out * inchannels + in]); + } + outarray[sample * outchannels + out] = (gint32) (outval >> n); + } + } + break; + } + default: + gst_buffer_unmap (inbuf, &inmap); + gst_buffer_unmap (outbuf, &outmap); + return GST_FLOW_NOT_SUPPORTED; + + } + + gst_buffer_unmap (inbuf, &inmap); + gst_buffer_unmap (outbuf, &outmap); + return GST_FLOW_OK; +} + +static gboolean +gst_audio_mix_matrix_get_unit_size (GstBaseTransform * trans, + GstCaps * caps, gsize * size) +{ + GstAudioInfo info; + + if (!gst_audio_info_from_caps (&info, caps)) + return FALSE; + + *size = GST_AUDIO_INFO_BPF (&info); + + return TRUE; +} + +static gboolean +gst_audio_mix_matrix_set_caps (GstBaseTransform * trans, GstCaps * incaps, + GstCaps * outcaps) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (trans); + GstAudioInfo info, out_info; + + if (!gst_audio_info_from_caps (&info, incaps)) + return FALSE; + + if (!gst_audio_info_from_caps (&out_info, outcaps)) + return FALSE; + + self->format = info.finfo->format; + + if (self->mode == GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS) { + gint in, out; + + self->in_channels = info.channels; + self->out_channels = out_info.channels; + + self->matrix = g_new (gdouble, self->in_channels * self->out_channels); + + for (out = 0; out < self->out_channels; out++) { + for (in = 0; in < self->in_channels; in++) { + self->matrix[out * self->in_channels + in] = (out == in); + } + } + } else if (!self->matrix || info.channels != self->in_channels || + out_info.channels != self->out_channels) { + GST_ELEMENT_ERROR (self, LIBRARY, SETTINGS, + ("Erroneous matrix detected"), + ("Please enter a matrix with the correct input and output channels")); + return FALSE; + } + + switch (self->format) { + case GST_AUDIO_FORMAT_S16LE: + case GST_AUDIO_FORMAT_S16BE:{ + gst_audio_mix_matrix_convert_s16_matrix (self); + break; + } + case GST_AUDIO_FORMAT_S32LE: + case GST_AUDIO_FORMAT_S32BE:{ + gst_audio_mix_matrix_convert_s32_matrix (self); + break; + } + default: + break; + } + return TRUE; +} + +static GstCaps * +gst_audio_mix_matrix_fixate_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * othercaps) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (trans); + GstStructure *s, *s2; + guint capssize = gst_caps_get_size (othercaps); + gint i; + gint channels; + + if (self->mode == GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS) { + s2 = gst_caps_get_structure (caps, 0); + + /* Try to keep channel configuration as much as possible */ + if (gst_structure_get_int (s2, "channels", &channels)) { + gint mindiff = channels; + othercaps = gst_caps_make_writable (othercaps); + for (i = 0; i < capssize; i++) { + s = gst_caps_get_structure (othercaps, i); + if (!gst_structure_has_field (s, "channels")) { + mindiff = 0; + gst_structure_set (s, "channels", G_TYPE_INT, channels, NULL); + } else { + gint outchannels; + gint diff; + + gst_structure_fixate_field_nearest_int (s, "channels", channels); + if (gst_structure_get_int (s, "channels", &outchannels)) { + diff = ABS (channels - outchannels); + if (diff < mindiff) + mindiff = diff; + } + } + } + + for (i = 0; i < capssize; i++) { + gint outchannels, diff; + s = gst_caps_get_structure (othercaps, i); + if (gst_structure_get_int (s, "channels", &outchannels)) { + diff = ABS (channels - outchannels); + if (diff > mindiff) { + gst_caps_remove_structure (othercaps, i--); + capssize--; + } + } + } + } + } + + othercaps = + GST_BASE_TRANSFORM_CLASS (gst_audio_mix_matrix_parent_class)->fixate_caps + (trans, direction, caps, othercaps); + + s = gst_caps_get_structure (othercaps, 0); + + if (!gst_structure_has_field (s, "channel-mask")) { + if (self->mode == GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS || + self->channel_mask == -1) { + gint channels; + + g_assert (gst_structure_get_int (s, "channels", &channels)); + gst_structure_set (s, "channel-mask", GST_TYPE_BITMASK, + gst_audio_channel_get_fallback_mask (channels), NULL); + } else { + gst_structure_set (s, "channel-mask", GST_TYPE_BITMASK, + self->channel_mask, NULL); + } + } + + return othercaps; + +} + +static GstCaps * +gst_audio_mix_matrix_transform_caps (GstBaseTransform * trans, + GstPadDirection direction, GstCaps * caps, GstCaps * filter) +{ + GstAudioMixMatrix *self = GST_AUDIO_MIX_MATRIX (trans); + GstCaps *outcaps = gst_caps_copy (caps); + GstCaps *ret; + GstStructure *s; + gint i; + guint capssize = gst_caps_get_size (outcaps); + + if (self->mode == GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS) { + for (i = 0; i < capssize; i++) { + s = gst_caps_get_structure (outcaps, i); + if (gst_structure_has_field (s, "channels")) { + gst_structure_remove_field (s, "channels"); + } + if (gst_structure_has_field (s, "channel-mask")) { + gst_structure_remove_field (s, "channel-mask"); + } + } + goto beach; + } + + if (self->in_channels == 0 || self->out_channels == 0 || self->matrix == NULL) { + /* Not dispatching element error because we return empty caps anyway and + * we should let it fail to link. Additionally, the element error would be + * printed as WARN, so a possible gst-launch pipeline would appear to + * hang. */ + GST_ERROR_OBJECT (self, "Invalid settings detected in manual mode. " + "Please specify in-channels, out-channels and matrix."); + return gst_caps_new_empty (); + } + + if (self->in_channels == self->out_channels) { + goto beach; + } + + for (i = 0; i < capssize; i++) { + s = gst_caps_get_structure (outcaps, i); + if (direction == GST_PAD_SRC) { + gst_structure_set (s, "channels", G_TYPE_INT, self->in_channels, NULL); + gst_structure_remove_field (s, "channel-mask"); + } else if (direction == GST_PAD_SINK) { + gst_structure_set (s, "channels", G_TYPE_INT, self->out_channels, + "channel-mask", GST_TYPE_BITMASK, self->channel_mask, NULL); + } else { + g_assert_not_reached (); + } + } + +beach: + if (filter) { + ret = gst_caps_intersect_full (filter, outcaps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (outcaps); + } else { + ret = outcaps; + } + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + return gst_element_register (plugin, "audiomixmatrix", GST_RANK_NONE, + GST_TYPE_AUDIO_MIX_MATRIX); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + audiomixmatrix, + "Audio matrix mix", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN); diff --git a/gst/audiomixmatrix/gstaudiomixmatrix.h b/gst/audiomixmatrix/gstaudiomixmatrix.h new file mode 100644 index 000000000..5321954b4 --- /dev/null +++ b/gst/audiomixmatrix/gstaudiomixmatrix.h @@ -0,0 +1,78 @@ +/* + * GStreamer + * Copyright (C) 2017 Vivia Nikolaidou <vivia@toolsonair.com> + * + * gstaudiomixmatrix.h + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_AUDIO_MIX_MATRIX_H__ +#define __GST_AUDIO_MIX_MATRIX_H__ + +#include <gst/gst.h> +#include <gst/audio/audio.h> + +#define GST_TYPE_AUDIO_MIX_MATRIX (gst_audio_mix_matrix_get_type()) +#define GST_AUDIO_MIX_MATRIX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_AUDIO_MIX_MATRIX,GstAudioMixMatrix)) +#define GST_AUDIO_MIX_MATRIX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_AUDIO_MIX_MATRIX,GstAudioMixMatrixClass)) +#define GST_AUDIO_MIX_MATRIX_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_AUDIO_MIX_MATRIX,GstAudioMixMatrixClass)) +#define GST_IS_AUDIO_MIX_MATRIX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_AUDIO_MIX_MATRIX)) +#define GST_IS_AUDIO_MIX_MATRIX_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_AUDIO_MIX_MATRIX)) +#define GST_TYPE_AUDIO_MIX_MATRIX_MODE (gst_audio_mix_matrix_mode_get_type()) + +typedef struct _GstAudioMixMatrix GstAudioMixMatrix; +typedef struct _GstAudioMixMatrixClass GstAudioMixMatrixClass; + +typedef enum _GstAudioMixMatrixMode +{ + GST_AUDIO_MIX_MATRIX_MODE_MANUAL = 0, + GST_AUDIO_MIX_MATRIX_MODE_FIRST_CHANNELS = 1 +} GstAudioMixMatrixMode; + +/** + * GstAudioMixMatrix: + * + * Opaque data structure. + */ +struct _GstAudioMixMatrix +{ + GstBaseTransform audiofilter; + + /* < private > */ + guint in_channels; + guint out_channels; + gdouble *matrix; + guint64 channel_mask; + GstAudioMixMatrixMode mode; + gint32 *s16_conv_matrix; + gint64 *s32_conv_matrix; + gint shift_bytes; + + GstAudioFormat format; +}; + +struct _GstAudioMixMatrixClass +{ + GstBaseTransformClass parent_class; +}; + +GType gst_audio_mix_matrix_get_type (void); + +GType gst_audio_mix_matrix_mode_get_type (void); + +G_END_DECLS +#endif /* __GST_AUDIO_MIX_MATRIX_H__ */ |