/* * GStreamer * Copyright (C) 2017 Vivia Nikolaidou * * 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 * @title: 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. * * ## Example matrix generation code * 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); * ]| * * ## Example launch line * |[ * 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 * ]| * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstaudiomixmatrix.h" #include #include #include #include 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_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 "); 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_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_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; if (self->matrix == NULL) break; 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_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 = -1; 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 (mindiff < 0 || diff < mindiff) mindiff = diff; } } } if (mindiff >= 0) { 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--; } } } } } } if (gst_caps_is_empty (othercaps)) return othercaps; 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);