mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-09-09 05:38:58 +00:00
921 lines
30 KiB
C
921 lines
30 KiB
C
/* GStreamer
|
|
* Copyright (C) <2004> Benjamin Otte <otte@gnome.org>
|
|
* <2007> Stefan Kost <ensonic@users.sf.net>
|
|
* <2007> Sebastian Dröge <slomo@circular-chaos.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 <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "gstiirequalizer.h"
|
|
#include "gstiirequalizernbands.h"
|
|
#include "gstiirequalizer3bands.h"
|
|
#include "gstiirequalizer10bands.h"
|
|
|
|
#include "gst/glib-compat-private.h"
|
|
|
|
GST_DEBUG_CATEGORY (equalizer_debug);
|
|
#define GST_CAT_DEFAULT equalizer_debug
|
|
|
|
#define BANDS_LOCK(equ) g_mutex_lock(&equ->bands_lock)
|
|
#define BANDS_UNLOCK(equ) g_mutex_unlock(&equ->bands_lock)
|
|
|
|
static void gst_iir_equalizer_child_proxy_interface_init (gpointer g_iface,
|
|
gpointer iface_data);
|
|
|
|
static void gst_iir_equalizer_finalize (GObject * object);
|
|
|
|
static gboolean gst_iir_equalizer_setup (GstAudioFilter * filter,
|
|
const GstAudioInfo * info);
|
|
static GstFlowReturn gst_iir_equalizer_transform_ip (GstBaseTransform * btrans,
|
|
GstBuffer * buf);
|
|
static void set_passthrough (GstIirEqualizer * equ);
|
|
|
|
#define ALLOWED_CAPS \
|
|
"audio/x-raw," \
|
|
" format=(string) {"GST_AUDIO_NE(S16)","GST_AUDIO_NE(F32)"," \
|
|
GST_AUDIO_NE(F64)" }, " \
|
|
" rate=(int)[1000,MAX]," \
|
|
" channels=(int)[1,MAX]," \
|
|
" layout=(string)interleaved"
|
|
|
|
#define gst_iir_equalizer_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstIirEqualizer, gst_iir_equalizer,
|
|
GST_TYPE_AUDIO_FILTER,
|
|
G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
|
|
gst_iir_equalizer_child_proxy_interface_init)
|
|
G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
|
|
|
|
|
|
/* child object */
|
|
|
|
enum
|
|
{
|
|
PROP_GAIN = 1,
|
|
PROP_FREQ,
|
|
PROP_BANDWIDTH,
|
|
PROP_TYPE
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
BAND_TYPE_PEAK = 0,
|
|
BAND_TYPE_LOW_SHELF,
|
|
BAND_TYPE_HIGH_SHELF
|
|
} GstIirEqualizerBandType;
|
|
|
|
#define GST_TYPE_IIR_EQUALIZER_BAND_TYPE (gst_iir_equalizer_band_type_get_type ())
|
|
static GType
|
|
gst_iir_equalizer_band_type_get_type (void)
|
|
{
|
|
static GType gtype = 0;
|
|
|
|
if (gtype == 0) {
|
|
static const GEnumValue values[] = {
|
|
{BAND_TYPE_PEAK, "Peak filter (default for inner bands)", "peak"},
|
|
{BAND_TYPE_LOW_SHELF, "Low shelf filter (default for first band)",
|
|
"low-shelf"},
|
|
{BAND_TYPE_HIGH_SHELF, "High shelf filter (default for last band)",
|
|
"high-shelf"},
|
|
{0, NULL, NULL}
|
|
};
|
|
|
|
gtype = g_enum_register_static ("GstIirEqualizerBandType", values);
|
|
}
|
|
return gtype;
|
|
}
|
|
|
|
|
|
typedef struct _GstIirEqualizerBandClass GstIirEqualizerBandClass;
|
|
|
|
#define GST_TYPE_IIR_EQUALIZER_BAND \
|
|
(gst_iir_equalizer_band_get_type())
|
|
#define GST_IIR_EQUALIZER_BAND(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IIR_EQUALIZER_BAND,GstIirEqualizerBand))
|
|
#define GST_IIR_EQUALIZER_BAND_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IIR_EQUALIZER_BAND,GstIirEqualizerBandClass))
|
|
#define GST_IS_IIR_EQUALIZER_BAND(obj) \
|
|
(G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IIR_EQUALIZER_BAND))
|
|
#define GST_IS_IIR_EQUALIZER_BAND_CLASS(klass) \
|
|
(G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IIR_EQUALIZER_BAND))
|
|
|
|
struct _GstIirEqualizerBand
|
|
{
|
|
GstObject object;
|
|
|
|
/*< private > */
|
|
/* center frequency and gain */
|
|
gdouble freq;
|
|
gdouble gain;
|
|
gdouble width;
|
|
GstIirEqualizerBandType type;
|
|
|
|
/* second order iir filter */
|
|
gdouble b1, b2; /* IIR coefficients for outputs */
|
|
gdouble a0, a1, a2; /* IIR coefficients for inputs */
|
|
};
|
|
|
|
struct _GstIirEqualizerBandClass
|
|
{
|
|
GstObjectClass parent_class;
|
|
};
|
|
|
|
static GType gst_iir_equalizer_band_get_type (void);
|
|
|
|
static void
|
|
gst_iir_equalizer_band_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstIirEqualizerBand *band = GST_IIR_EQUALIZER_BAND (object);
|
|
GstIirEqualizer *equ =
|
|
GST_IIR_EQUALIZER (gst_object_get_parent (GST_OBJECT (band)));
|
|
|
|
switch (prop_id) {
|
|
case PROP_GAIN:{
|
|
gdouble gain;
|
|
|
|
gain = g_value_get_double (value);
|
|
GST_DEBUG_OBJECT (band, "gain = %lf -> %lf", band->gain, gain);
|
|
if (gain != band->gain) {
|
|
BANDS_LOCK (equ);
|
|
equ->need_new_coefficients = TRUE;
|
|
band->gain = gain;
|
|
set_passthrough (equ);
|
|
BANDS_UNLOCK (equ);
|
|
GST_DEBUG_OBJECT (band, "changed gain = %lf ", band->gain);
|
|
}
|
|
break;
|
|
}
|
|
case PROP_FREQ:{
|
|
gdouble freq;
|
|
|
|
freq = g_value_get_double (value);
|
|
GST_DEBUG_OBJECT (band, "freq = %lf -> %lf", band->freq, freq);
|
|
if (freq != band->freq) {
|
|
BANDS_LOCK (equ);
|
|
equ->need_new_coefficients = TRUE;
|
|
band->freq = freq;
|
|
BANDS_UNLOCK (equ);
|
|
GST_DEBUG_OBJECT (band, "changed freq = %lf ", band->freq);
|
|
}
|
|
break;
|
|
}
|
|
case PROP_BANDWIDTH:{
|
|
gdouble width;
|
|
|
|
width = g_value_get_double (value);
|
|
GST_DEBUG_OBJECT (band, "width = %lf -> %lf", band->width, width);
|
|
if (width != band->width) {
|
|
BANDS_LOCK (equ);
|
|
equ->need_new_coefficients = TRUE;
|
|
band->width = width;
|
|
BANDS_UNLOCK (equ);
|
|
GST_DEBUG_OBJECT (band, "changed width = %lf ", band->width);
|
|
}
|
|
break;
|
|
}
|
|
case PROP_TYPE:{
|
|
GstIirEqualizerBandType type;
|
|
|
|
type = g_value_get_enum (value);
|
|
GST_DEBUG_OBJECT (band, "type = %d -> %d", band->type, type);
|
|
if (type != band->type) {
|
|
BANDS_LOCK (equ);
|
|
equ->need_new_coefficients = TRUE;
|
|
band->type = type;
|
|
BANDS_UNLOCK (equ);
|
|
GST_DEBUG_OBJECT (band, "changed type = %d ", band->type);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
|
|
gst_object_unref (equ);
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_band_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstIirEqualizerBand *band = GST_IIR_EQUALIZER_BAND (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_GAIN:
|
|
g_value_set_double (value, band->gain);
|
|
break;
|
|
case PROP_FREQ:
|
|
g_value_set_double (value, band->freq);
|
|
break;
|
|
case PROP_BANDWIDTH:
|
|
g_value_set_double (value, band->width);
|
|
break;
|
|
case PROP_TYPE:
|
|
g_value_set_enum (value, band->type);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_band_class_init (GstIirEqualizerBandClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->set_property = gst_iir_equalizer_band_set_property;
|
|
gobject_class->get_property = gst_iir_equalizer_band_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_GAIN,
|
|
g_param_spec_double ("gain", "gain",
|
|
"gain for the frequency band ranging from -24.0 dB to +12.0 dB",
|
|
-24.0, 12.0, 0.0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FREQ,
|
|
g_param_spec_double ("freq", "freq",
|
|
"center frequency of the band",
|
|
0.0, 100000.0, 0.0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_BANDWIDTH,
|
|
g_param_spec_double ("bandwidth", "bandwidth",
|
|
"difference between bandedges in Hz",
|
|
0.0, 100000.0, 1.0,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_TYPE,
|
|
g_param_spec_enum ("type", "Type",
|
|
"Filter type", GST_TYPE_IIR_EQUALIZER_BAND_TYPE,
|
|
BAND_TYPE_PEAK,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_IIR_EQUALIZER, 0);
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_band_init (GstIirEqualizerBand * band,
|
|
GstIirEqualizerBandClass * klass)
|
|
{
|
|
band->freq = 0.0;
|
|
band->gain = 0.0;
|
|
band->width = 1.0;
|
|
band->type = BAND_TYPE_PEAK;
|
|
}
|
|
|
|
static GType
|
|
gst_iir_equalizer_band_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (G_UNLIKELY (!type)) {
|
|
const GTypeInfo type_info = {
|
|
sizeof (GstIirEqualizerBandClass),
|
|
NULL,
|
|
NULL,
|
|
(GClassInitFunc) gst_iir_equalizer_band_class_init,
|
|
NULL,
|
|
NULL,
|
|
sizeof (GstIirEqualizerBand),
|
|
0,
|
|
(GInstanceInitFunc) gst_iir_equalizer_band_init,
|
|
};
|
|
type =
|
|
g_type_register_static (GST_TYPE_OBJECT, "GstIirEqualizerBand",
|
|
&type_info, 0);
|
|
}
|
|
return (type);
|
|
}
|
|
|
|
|
|
/* child proxy iface */
|
|
static GObject *
|
|
gst_iir_equalizer_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
|
|
guint index)
|
|
{
|
|
GstIirEqualizer *equ = GST_IIR_EQUALIZER (child_proxy);
|
|
GObject *ret;
|
|
|
|
BANDS_LOCK (equ);
|
|
if (G_UNLIKELY (index >= equ->freq_band_count)) {
|
|
BANDS_UNLOCK (equ);
|
|
g_return_val_if_fail (index < equ->freq_band_count, NULL);
|
|
}
|
|
|
|
ret = g_object_ref (G_OBJECT (equ->bands[index]));
|
|
BANDS_UNLOCK (equ);
|
|
|
|
GST_LOG_OBJECT (equ, "return child[%d] %" GST_PTR_FORMAT, index, ret);
|
|
return ret;
|
|
}
|
|
|
|
static guint
|
|
gst_iir_equalizer_child_proxy_get_children_count (GstChildProxy * child_proxy)
|
|
{
|
|
GstIirEqualizer *equ = GST_IIR_EQUALIZER (child_proxy);
|
|
|
|
GST_LOG ("we have %d children", equ->freq_band_count);
|
|
return equ->freq_band_count;
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_child_proxy_interface_init (gpointer g_iface,
|
|
gpointer iface_data)
|
|
{
|
|
GstChildProxyInterface *iface = g_iface;
|
|
|
|
GST_DEBUG ("initializing iface");
|
|
|
|
iface->get_child_by_index = gst_iir_equalizer_child_proxy_get_child_by_index;
|
|
iface->get_children_count = gst_iir_equalizer_child_proxy_get_children_count;
|
|
}
|
|
|
|
/* equalizer implementation */
|
|
|
|
static void
|
|
gst_iir_equalizer_class_init (GstIirEqualizerClass * klass)
|
|
{
|
|
GstAudioFilterClass *audio_filter_class = (GstAudioFilterClass *) klass;
|
|
GstBaseTransformClass *btrans_class = (GstBaseTransformClass *) klass;
|
|
GObjectClass *gobject_class = (GObjectClass *) klass;
|
|
GstCaps *caps;
|
|
|
|
gobject_class->finalize = gst_iir_equalizer_finalize;
|
|
audio_filter_class->setup = gst_iir_equalizer_setup;
|
|
btrans_class->transform_ip = gst_iir_equalizer_transform_ip;
|
|
btrans_class->transform_ip_on_passthrough = FALSE;
|
|
|
|
caps = gst_caps_from_string (ALLOWED_CAPS);
|
|
gst_audio_filter_class_add_pad_templates (audio_filter_class, caps);
|
|
gst_caps_unref (caps);
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_init (GstIirEqualizer * eq)
|
|
{
|
|
g_mutex_init (&eq->bands_lock);
|
|
/* Band gains are 0 by default, passthrough until they are changed */
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (eq), TRUE);
|
|
}
|
|
|
|
static void
|
|
gst_iir_equalizer_finalize (GObject * object)
|
|
{
|
|
GstIirEqualizer *equ = GST_IIR_EQUALIZER (object);
|
|
gint i;
|
|
|
|
for (i = 0; i < equ->freq_band_count; i++) {
|
|
if (equ->bands[i])
|
|
gst_object_unparent (GST_OBJECT (equ->bands[i]));
|
|
equ->bands[i] = NULL;
|
|
}
|
|
equ->freq_band_count = 0;
|
|
|
|
g_free (equ->bands);
|
|
g_free (equ->history);
|
|
|
|
g_mutex_clear (&equ->bands_lock);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
/* Filter taken from
|
|
*
|
|
* The Equivalence of Various Methods of Computing
|
|
* Biquad Coefficients for Audio Parametric Equalizers
|
|
*
|
|
* by Robert Bristow-Johnson
|
|
*
|
|
* http://www.aes.org/e-lib/browse.cfm?elib=6326
|
|
* http://www.musicdsp.org/files/EQ-Coefficients.pdf
|
|
* http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
|
|
*
|
|
* The bandwidth method that we use here is the preferred
|
|
* one from this article transformed from octaves to frequency
|
|
* in Hz.
|
|
*/
|
|
static inline gdouble
|
|
arg_to_scale (gdouble arg)
|
|
{
|
|
return (pow (10.0, arg / 40.0));
|
|
}
|
|
|
|
static gdouble
|
|
calculate_omega (gdouble freq, gint rate)
|
|
{
|
|
gdouble omega;
|
|
|
|
if (freq / rate >= 0.5)
|
|
omega = G_PI;
|
|
else if (freq <= 0.0)
|
|
omega = 0.0;
|
|
else
|
|
omega = 2.0 * G_PI * (freq / rate);
|
|
|
|
return omega;
|
|
}
|
|
|
|
static gdouble
|
|
calculate_bw (GstIirEqualizerBand * band, gint rate)
|
|
{
|
|
gdouble bw = 0.0;
|
|
|
|
if (band->width / rate >= 0.5) {
|
|
/* If bandwidth == 0.5 the calculation below fails as tan(G_PI/2)
|
|
* is undefined. So set the bandwidth to a slightly smaller value.
|
|
*/
|
|
bw = G_PI - 0.00000001;
|
|
} else if (band->width <= 0.0) {
|
|
/* If bandwidth == 0 this band won't change anything so set
|
|
* the coefficients accordingly. The coefficient calculation
|
|
* below would create coefficients that for some reason amplify
|
|
* the band.
|
|
*/
|
|
band->a0 = 1.0;
|
|
band->a1 = 0.0;
|
|
band->a2 = 0.0;
|
|
band->b1 = 0.0;
|
|
band->b2 = 0.0;
|
|
} else {
|
|
bw = 2.0 * G_PI * (band->width / rate);
|
|
}
|
|
return bw;
|
|
}
|
|
|
|
static void
|
|
setup_peak_filter (GstIirEqualizer * equ, GstIirEqualizerBand * band)
|
|
{
|
|
gint rate = GST_AUDIO_FILTER_RATE (equ);
|
|
|
|
g_return_if_fail (rate);
|
|
|
|
{
|
|
gdouble gain, omega, bw;
|
|
gdouble alpha, alpha1, alpha2, b0;
|
|
|
|
gain = arg_to_scale (band->gain);
|
|
omega = calculate_omega (band->freq, rate);
|
|
bw = calculate_bw (band, rate);
|
|
if (bw == 0.0)
|
|
goto out;
|
|
|
|
alpha = tan (bw / 2.0);
|
|
|
|
alpha1 = alpha * gain;
|
|
alpha2 = alpha / gain;
|
|
|
|
b0 = (1.0 + alpha2);
|
|
|
|
band->a0 = (1.0 + alpha1) / b0;
|
|
band->a1 = (-2.0 * cos (omega)) / b0;
|
|
band->a2 = (1.0 - alpha1) / b0;
|
|
band->b1 = (2.0 * cos (omega)) / b0;
|
|
band->b2 = -(1.0 - alpha2) / b0;
|
|
|
|
out:
|
|
GST_INFO
|
|
("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g",
|
|
band->gain, band->width, band->freq, band->a0, band->a1, band->a2,
|
|
band->b1, band->b2);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_low_shelf_filter (GstIirEqualizer * equ, GstIirEqualizerBand * band)
|
|
{
|
|
gint rate = GST_AUDIO_FILTER_RATE (equ);
|
|
|
|
g_return_if_fail (rate);
|
|
|
|
{
|
|
gdouble gain, omega, bw;
|
|
gdouble alpha, delta, b0;
|
|
gdouble egp, egm;
|
|
|
|
gain = arg_to_scale (band->gain);
|
|
omega = calculate_omega (band->freq, rate);
|
|
bw = calculate_bw (band, rate);
|
|
if (bw == 0.0)
|
|
goto out;
|
|
|
|
egm = gain - 1.0;
|
|
egp = gain + 1.0;
|
|
alpha = tan (bw / 2.0);
|
|
|
|
delta = 2.0 * sqrt (gain) * alpha;
|
|
b0 = egp + egm * cos (omega) + delta;
|
|
|
|
band->a0 = ((egp - egm * cos (omega) + delta) * gain) / b0;
|
|
band->a1 = ((egm - egp * cos (omega)) * 2.0 * gain) / b0;
|
|
band->a2 = ((egp - egm * cos (omega) - delta) * gain) / b0;
|
|
band->b1 = ((egm + egp * cos (omega)) * 2.0) / b0;
|
|
band->b2 = -((egp + egm * cos (omega) - delta)) / b0;
|
|
|
|
|
|
out:
|
|
GST_INFO
|
|
("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g",
|
|
band->gain, band->width, band->freq, band->a0, band->a1, band->a2,
|
|
band->b1, band->b2);
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_high_shelf_filter (GstIirEqualizer * equ, GstIirEqualizerBand * band)
|
|
{
|
|
gint rate = GST_AUDIO_FILTER_RATE (equ);
|
|
|
|
g_return_if_fail (rate);
|
|
|
|
{
|
|
gdouble gain, omega, bw;
|
|
gdouble alpha, delta, b0;
|
|
gdouble egp, egm;
|
|
|
|
gain = arg_to_scale (band->gain);
|
|
omega = calculate_omega (band->freq, rate);
|
|
bw = calculate_bw (band, rate);
|
|
if (bw == 0.0)
|
|
goto out;
|
|
|
|
egm = gain - 1.0;
|
|
egp = gain + 1.0;
|
|
alpha = tan (bw / 2.0);
|
|
|
|
delta = 2.0 * sqrt (gain) * alpha;
|
|
b0 = egp - egm * cos (omega) + delta;
|
|
|
|
band->a0 = ((egp + egm * cos (omega) + delta) * gain) / b0;
|
|
band->a1 = ((egm + egp * cos (omega)) * -2.0 * gain) / b0;
|
|
band->a2 = ((egp + egm * cos (omega) - delta) * gain) / b0;
|
|
band->b1 = ((egm - egp * cos (omega)) * -2.0) / b0;
|
|
band->b2 = -((egp - egm * cos (omega) - delta)) / b0;
|
|
|
|
|
|
out:
|
|
GST_INFO
|
|
("gain = %5.1f, width= %7.2f, freq = %7.2f, a0 = %7.5g, a1 = %7.5g, a2=%7.5g b1 = %7.5g, b2 = %7.5g",
|
|
band->gain, band->width, band->freq, band->a0, band->a1, band->a2,
|
|
band->b1, band->b2);
|
|
}
|
|
}
|
|
|
|
/* Must be called with bands_lock and transform lock! */
|
|
static void
|
|
set_passthrough (GstIirEqualizer * equ)
|
|
{
|
|
gint i;
|
|
gboolean passthrough = TRUE;
|
|
|
|
for (i = 0; i < equ->freq_band_count; i++) {
|
|
passthrough = passthrough && (equ->bands[i]->gain == 0.0);
|
|
}
|
|
|
|
gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (equ), passthrough);
|
|
GST_DEBUG ("Passthrough mode: %d\n", passthrough);
|
|
}
|
|
|
|
/* Must be called with bands_lock and transform lock! */
|
|
static void
|
|
update_coefficients (GstIirEqualizer * equ)
|
|
{
|
|
gint i, n = equ->freq_band_count;
|
|
|
|
for (i = 0; i < n; i++) {
|
|
if (equ->bands[i]->type == BAND_TYPE_PEAK)
|
|
setup_peak_filter (equ, equ->bands[i]);
|
|
else if (equ->bands[i]->type == BAND_TYPE_LOW_SHELF)
|
|
setup_low_shelf_filter (equ, equ->bands[i]);
|
|
else
|
|
setup_high_shelf_filter (equ, equ->bands[i]);
|
|
}
|
|
|
|
equ->need_new_coefficients = FALSE;
|
|
}
|
|
|
|
/* Must be called with transform lock! */
|
|
static void
|
|
alloc_history (GstIirEqualizer * equ, const GstAudioInfo * info)
|
|
{
|
|
/* free + alloc = no memcpy */
|
|
g_free (equ->history);
|
|
equ->history =
|
|
g_malloc0 (equ->history_size * GST_AUDIO_INFO_CHANNELS (info) *
|
|
equ->freq_band_count);
|
|
}
|
|
|
|
void
|
|
gst_iir_equalizer_compute_frequencies (GstIirEqualizer * equ, guint new_count)
|
|
{
|
|
guint old_count, i;
|
|
gdouble freq0, freq1, step;
|
|
gchar name[20];
|
|
|
|
if (equ->freq_band_count == new_count)
|
|
return;
|
|
|
|
BANDS_LOCK (equ);
|
|
|
|
if (G_UNLIKELY (equ->freq_band_count == new_count)) {
|
|
BANDS_UNLOCK (equ);
|
|
return;
|
|
}
|
|
|
|
old_count = equ->freq_band_count;
|
|
equ->freq_band_count = new_count;
|
|
GST_DEBUG ("bands %u -> %u", old_count, new_count);
|
|
|
|
if (old_count < new_count) {
|
|
/* add new bands */
|
|
equ->bands = g_realloc (equ->bands, sizeof (GstObject *) * new_count);
|
|
for (i = old_count; i < new_count; i++) {
|
|
/* otherwise they get names like 'iirequalizerband5' */
|
|
sprintf (name, "band%u", i);
|
|
equ->bands[i] = g_object_new (GST_TYPE_IIR_EQUALIZER_BAND,
|
|
"name", name, NULL);
|
|
GST_DEBUG ("adding band[%d]=%p", i, equ->bands[i]);
|
|
|
|
gst_object_set_parent (GST_OBJECT (equ->bands[i]), GST_OBJECT (equ));
|
|
gst_child_proxy_child_added (GST_CHILD_PROXY (equ),
|
|
G_OBJECT (equ->bands[i]), name);
|
|
}
|
|
} else {
|
|
/* free unused bands */
|
|
for (i = new_count; i < old_count; i++) {
|
|
GST_DEBUG ("removing band[%d]=%p", i, equ->bands[i]);
|
|
gst_child_proxy_child_removed (GST_CHILD_PROXY (equ),
|
|
G_OBJECT (equ->bands[i]), GST_OBJECT_NAME (equ->bands[i]));
|
|
gst_object_unparent (GST_OBJECT (equ->bands[i]));
|
|
equ->bands[i] = NULL;
|
|
}
|
|
}
|
|
|
|
alloc_history (equ, GST_AUDIO_FILTER_INFO (equ));
|
|
|
|
/* set center frequencies and name band objects
|
|
* FIXME: arg! we can't change the name of parented objects :(
|
|
* application should read band->freq to get the name
|
|
*/
|
|
|
|
step = pow (HIGHEST_FREQ / LOWEST_FREQ, 1.0 / new_count);
|
|
freq0 = LOWEST_FREQ;
|
|
for (i = 0; i < new_count; i++) {
|
|
freq1 = freq0 * step;
|
|
|
|
if (i == 0)
|
|
equ->bands[i]->type = BAND_TYPE_LOW_SHELF;
|
|
else if (i == new_count - 1)
|
|
equ->bands[i]->type = BAND_TYPE_HIGH_SHELF;
|
|
else
|
|
equ->bands[i]->type = BAND_TYPE_PEAK;
|
|
|
|
equ->bands[i]->freq = freq0 + ((freq1 - freq0) / 2.0);
|
|
equ->bands[i]->width = freq1 - freq0;
|
|
GST_DEBUG ("band[%2d] = '%lf'", i, equ->bands[i]->freq);
|
|
|
|
g_object_notify (G_OBJECT (equ->bands[i]), "bandwidth");
|
|
g_object_notify (G_OBJECT (equ->bands[i]), "freq");
|
|
g_object_notify (G_OBJECT (equ->bands[i]), "type");
|
|
|
|
/*
|
|
if(equ->bands[i]->freq<10000.0)
|
|
sprintf (name,"%dHz",(gint)equ->bands[i]->freq);
|
|
else
|
|
sprintf (name,"%dkHz",(gint)(equ->bands[i]->freq/1000.0));
|
|
gst_object_set_name( GST_OBJECT (equ->bands[i]), name);
|
|
GST_DEBUG ("band[%2d] = '%s'",i,name);
|
|
*/
|
|
freq0 = freq1;
|
|
}
|
|
|
|
equ->need_new_coefficients = TRUE;
|
|
BANDS_UNLOCK (equ);
|
|
}
|
|
|
|
/* start of code that is type specific */
|
|
|
|
#define CREATE_OPTIMIZED_FUNCTIONS_INT(TYPE,BIG_TYPE,MIN_VAL,MAX_VAL) \
|
|
typedef struct { \
|
|
BIG_TYPE x1, x2; /* history of input values for a filter */ \
|
|
BIG_TYPE y1, y2; /* history of output values for a filter */ \
|
|
} SecondOrderHistory ## TYPE; \
|
|
\
|
|
static inline BIG_TYPE \
|
|
one_step_ ## TYPE (GstIirEqualizerBand *filter, \
|
|
SecondOrderHistory ## TYPE *history, BIG_TYPE input) \
|
|
{ \
|
|
/* calculate output */ \
|
|
BIG_TYPE output = filter->a0 * input + \
|
|
filter->a1 * history->x1 + filter->a2 * history->x2 + \
|
|
filter->b1 * history->y1 + filter->b2 * history->y2; \
|
|
/* update history */ \
|
|
history->y2 = history->y1; \
|
|
history->y1 = output; \
|
|
history->x2 = history->x1; \
|
|
history->x1 = input; \
|
|
\
|
|
return output; \
|
|
} \
|
|
\
|
|
static const guint \
|
|
history_size_ ## TYPE = sizeof (SecondOrderHistory ## TYPE); \
|
|
\
|
|
static void \
|
|
gst_iir_equ_process_ ## TYPE (GstIirEqualizer *equ, guint8 *data, \
|
|
guint size, guint channels) \
|
|
{ \
|
|
guint frames = size / channels / sizeof (TYPE); \
|
|
guint i, c, f, nf = equ->freq_band_count; \
|
|
BIG_TYPE cur; \
|
|
GstIirEqualizerBand **filters = equ->bands; \
|
|
\
|
|
for (i = 0; i < frames; i++) { \
|
|
SecondOrderHistory ## TYPE *history = equ->history; \
|
|
for (c = 0; c < channels; c++) { \
|
|
cur = *((TYPE *) data); \
|
|
for (f = 0; f < nf; f++) { \
|
|
cur = one_step_ ## TYPE (filters[f], history, cur); \
|
|
history++; \
|
|
} \
|
|
cur = CLAMP (cur, MIN_VAL, MAX_VAL); \
|
|
*((TYPE *) data) = (TYPE) floor (cur); \
|
|
data += sizeof (TYPE); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
#define CREATE_OPTIMIZED_FUNCTIONS(TYPE) \
|
|
typedef struct { \
|
|
TYPE x1, x2; /* history of input values for a filter */ \
|
|
TYPE y1, y2; /* history of output values for a filter */ \
|
|
} SecondOrderHistory ## TYPE; \
|
|
\
|
|
static inline TYPE \
|
|
one_step_ ## TYPE (GstIirEqualizerBand *filter, \
|
|
SecondOrderHistory ## TYPE *history, TYPE input) \
|
|
{ \
|
|
/* calculate output */ \
|
|
TYPE output = filter->a0 * input + filter->a1 * history->x1 + \
|
|
filter->a2 * history->x2 + filter->b1 * history->y1 + \
|
|
filter->b2 * history->y2; \
|
|
/* update history */ \
|
|
history->y2 = history->y1; \
|
|
history->y1 = output; \
|
|
history->x2 = history->x1; \
|
|
history->x1 = input; \
|
|
\
|
|
return output; \
|
|
} \
|
|
\
|
|
static const guint \
|
|
history_size_ ## TYPE = sizeof (SecondOrderHistory ## TYPE); \
|
|
\
|
|
static void \
|
|
gst_iir_equ_process_ ## TYPE (GstIirEqualizer *equ, guint8 *data, \
|
|
guint size, guint channels) \
|
|
{ \
|
|
guint frames = size / channels / sizeof (TYPE); \
|
|
guint i, c, f, nf = equ->freq_band_count; \
|
|
TYPE cur; \
|
|
GstIirEqualizerBand **filters = equ->bands; \
|
|
\
|
|
for (i = 0; i < frames; i++) { \
|
|
SecondOrderHistory ## TYPE *history = equ->history; \
|
|
for (c = 0; c < channels; c++) { \
|
|
cur = *((TYPE *) data); \
|
|
for (f = 0; f < nf; f++) { \
|
|
cur = one_step_ ## TYPE (filters[f], history, cur); \
|
|
history++; \
|
|
} \
|
|
*((TYPE *) data) = (TYPE) cur; \
|
|
data += sizeof (TYPE); \
|
|
} \
|
|
} \
|
|
}
|
|
|
|
CREATE_OPTIMIZED_FUNCTIONS_INT (gint16, gfloat, -32768.0, 32767.0);
|
|
CREATE_OPTIMIZED_FUNCTIONS (gfloat);
|
|
CREATE_OPTIMIZED_FUNCTIONS (gdouble);
|
|
|
|
static GstFlowReturn
|
|
gst_iir_equalizer_transform_ip (GstBaseTransform * btrans, GstBuffer * buf)
|
|
{
|
|
GstAudioFilter *filter = GST_AUDIO_FILTER (btrans);
|
|
GstIirEqualizer *equ = GST_IIR_EQUALIZER (btrans);
|
|
GstClockTime timestamp;
|
|
GstMapInfo map;
|
|
gint channels = GST_AUDIO_FILTER_CHANNELS (filter);
|
|
gboolean need_new_coefficients;
|
|
|
|
if (G_UNLIKELY (channels < 1 || equ->process == NULL))
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
|
|
BANDS_LOCK (equ);
|
|
need_new_coefficients = equ->need_new_coefficients;
|
|
BANDS_UNLOCK (equ);
|
|
|
|
timestamp = GST_BUFFER_TIMESTAMP (buf);
|
|
timestamp =
|
|
gst_segment_to_stream_time (&btrans->segment, GST_FORMAT_TIME, timestamp);
|
|
|
|
if (GST_CLOCK_TIME_IS_VALID (timestamp)) {
|
|
GstIirEqualizerBand **filters = equ->bands;
|
|
guint f, nf = equ->freq_band_count;
|
|
|
|
gst_object_sync_values (GST_OBJECT (equ), timestamp);
|
|
|
|
/* sync values for bands too */
|
|
/* FIXME: iterating equ->bands is not thread-safe here */
|
|
for (f = 0; f < nf; f++) {
|
|
gst_object_sync_values (GST_OBJECT (filters[f]), timestamp);
|
|
}
|
|
}
|
|
|
|
BANDS_LOCK (equ);
|
|
if (need_new_coefficients) {
|
|
update_coefficients (equ);
|
|
}
|
|
BANDS_UNLOCK (equ);
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READWRITE);
|
|
equ->process (equ, map.data, map.size, channels);
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
static gboolean
|
|
gst_iir_equalizer_setup (GstAudioFilter * audio, const GstAudioInfo * info)
|
|
{
|
|
GstIirEqualizer *equ = GST_IIR_EQUALIZER (audio);
|
|
|
|
switch (GST_AUDIO_INFO_FORMAT (info)) {
|
|
case GST_AUDIO_FORMAT_S16:
|
|
equ->history_size = history_size_gint16;
|
|
equ->process = gst_iir_equ_process_gint16;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F32:
|
|
equ->history_size = history_size_gfloat;
|
|
equ->process = gst_iir_equ_process_gfloat;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F64:
|
|
equ->history_size = history_size_gdouble;
|
|
equ->process = gst_iir_equ_process_gdouble;
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
alloc_history (equ, info);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
static gboolean
|
|
plugin_init (GstPlugin * plugin)
|
|
{
|
|
GST_DEBUG_CATEGORY_INIT (equalizer_debug, "equalizer", 0, "equalizer");
|
|
|
|
if (!(gst_element_register (plugin, "equalizer-nbands", GST_RANK_NONE,
|
|
GST_TYPE_IIR_EQUALIZER_NBANDS)))
|
|
return FALSE;
|
|
|
|
if (!(gst_element_register (plugin, "equalizer-3bands", GST_RANK_NONE,
|
|
GST_TYPE_IIR_EQUALIZER_3BANDS)))
|
|
return FALSE;
|
|
|
|
if (!(gst_element_register (plugin, "equalizer-10bands", GST_RANK_NONE,
|
|
GST_TYPE_IIR_EQUALIZER_10BANDS)))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
|
GST_VERSION_MINOR,
|
|
equalizer,
|
|
"GStreamer audio equalizers",
|
|
plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
|