blob: 93553e8fe91bbb78d47870e7b07fcacc55a8a371 [file] [log] [blame]
/*
* GStreamer
* Copyright (C) 2007-2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* 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 <string.h>
#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
#include <gst/audio/audio.h>
#include <gst/audio/gstaudiofilter.h>
#include <math.h>
#include "audiofxbaseiirfilter.h"
#define GST_CAT_DEFAULT gst_audio_fx_base_iir_filter_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define ALLOWED_CAPS \
"audio/x-raw," \
" format=(string){"GST_AUDIO_NE(F32)","GST_AUDIO_NE(F64)"}," \
" rate = (int) [ 1, MAX ]," \
" channels = (int) [ 1, MAX ]," \
" layout=(string) interleaved"
#define gst_audio_fx_base_iir_filter_parent_class parent_class
G_DEFINE_TYPE (GstAudioFXBaseIIRFilter,
gst_audio_fx_base_iir_filter, GST_TYPE_AUDIO_FILTER);
static gboolean gst_audio_fx_base_iir_filter_setup (GstAudioFilter * filter,
const GstAudioInfo * info);
static GstFlowReturn
gst_audio_fx_base_iir_filter_transform_ip (GstBaseTransform * base,
GstBuffer * buf);
static gboolean gst_audio_fx_base_iir_filter_stop (GstBaseTransform * base);
static void process_64 (GstAudioFXBaseIIRFilter * filter,
gdouble * data, guint num_samples);
static void process_32 (GstAudioFXBaseIIRFilter * filter,
gfloat * data, guint num_samples);
/* GObject vmethod implementations */
static void
gst_audio_fx_base_iir_filter_finalize (GObject * object)
{
GstAudioFXBaseIIRFilter *filter = GST_AUDIO_FX_BASE_IIR_FILTER (object);
if (filter->a) {
g_free (filter->a);
filter->a = NULL;
}
if (filter->b) {
g_free (filter->b);
filter->b = NULL;
}
if (filter->channels) {
GstAudioFXBaseIIRFilterChannelCtx *ctx;
guint i;
for (i = 0; i < filter->nchannels; i++) {
ctx = &filter->channels[i];
g_free (ctx->x);
g_free (ctx->y);
}
g_free (filter->channels);
filter->channels = NULL;
}
g_mutex_clear (&filter->lock);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_audio_fx_base_iir_filter_class_init (GstAudioFXBaseIIRFilterClass * klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
GstAudioFilterClass *filter_class = (GstAudioFilterClass *) klass;
GstCaps *caps;
GST_DEBUG_CATEGORY_INIT (gst_audio_fx_base_iir_filter_debug,
"audiofxbaseiirfilter", 0, "Audio IIR Filter Base Class");
gobject_class->finalize = gst_audio_fx_base_iir_filter_finalize;
caps = gst_caps_from_string (ALLOWED_CAPS);
gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
caps);
gst_caps_unref (caps);
filter_class->setup = GST_DEBUG_FUNCPTR (gst_audio_fx_base_iir_filter_setup);
trans_class->transform_ip =
GST_DEBUG_FUNCPTR (gst_audio_fx_base_iir_filter_transform_ip);
trans_class->transform_ip_on_passthrough = FALSE;
trans_class->stop = GST_DEBUG_FUNCPTR (gst_audio_fx_base_iir_filter_stop);
}
static void
gst_audio_fx_base_iir_filter_init (GstAudioFXBaseIIRFilter * filter)
{
gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE);
filter->a = NULL;
filter->na = 0;
filter->b = NULL;
filter->nb = 0;
filter->channels = NULL;
filter->nchannels = 0;
g_mutex_init (&filter->lock);
}
/* Evaluate the transfer function that corresponds to the IIR
* coefficients at (zr + zi*I)^-1 and return the magnitude */
gdouble
gst_audio_fx_base_iir_filter_calculate_gain (gdouble * a, guint na, gdouble * b,
guint nb, gdouble zr, gdouble zi)
{
gdouble sum_ar, sum_ai;
gdouble sum_br, sum_bi;
gdouble gain_r, gain_i;
gdouble sum_r_old;
gdouble sum_i_old;
gint i;
sum_ar = a[na - 1];
sum_ai = 0.0;
for (i = na - 2; i >= 0; i--) {
sum_r_old = sum_ar;
sum_i_old = sum_ai;
sum_ar = (sum_r_old * zr - sum_i_old * zi) + a[i];
sum_ai = (sum_r_old * zi + sum_i_old * zr) + 0.0;
}
sum_br = b[nb - 1];
sum_bi = 0.0;
for (i = nb - 2; i >= 0; i--) {
sum_r_old = sum_br;
sum_i_old = sum_bi;
sum_br = (sum_r_old * zr - sum_i_old * zi) + b[i];
sum_bi = (sum_r_old * zi + sum_i_old * zr) + 0.0;
}
gain_r =
(sum_br * sum_ar + sum_bi * sum_ai) / (sum_ar * sum_ar + sum_ai * sum_ai);
gain_i =
(sum_bi * sum_ar - sum_br * sum_ai) / (sum_ar * sum_ar + sum_ai * sum_ai);
return (sqrt (gain_r * gain_r + gain_i * gain_i));
}
void
gst_audio_fx_base_iir_filter_set_coefficients (GstAudioFXBaseIIRFilter * filter,
gdouble * a, guint na, gdouble * b, guint nb)
{
guint i;
g_return_if_fail (GST_IS_AUDIO_FX_BASE_IIR_FILTER (filter));
g_mutex_lock (&filter->lock);
g_free (filter->a);
g_free (filter->b);
filter->a = filter->b = NULL;
if (filter->channels) {
GstAudioFXBaseIIRFilterChannelCtx *ctx;
gboolean free = (na != filter->na || nb != filter->nb);
for (i = 0; i < filter->nchannels; i++) {
ctx = &filter->channels[i];
if (free)
g_free (ctx->x);
else
memset (ctx->x, 0, filter->nb * sizeof (gdouble));
if (free)
g_free (ctx->y);
else
memset (ctx->y, 0, filter->na * sizeof (gdouble));
}
g_free (filter->channels);
filter->channels = NULL;
}
filter->na = na;
filter->nb = nb;
filter->a = a;
filter->b = b;
if (filter->nchannels && !filter->channels) {
GstAudioFXBaseIIRFilterChannelCtx *ctx;
filter->channels =
g_new0 (GstAudioFXBaseIIRFilterChannelCtx, filter->nchannels);
for (i = 0; i < filter->nchannels; i++) {
ctx = &filter->channels[i];
ctx->x = g_new0 (gdouble, filter->nb);
ctx->y = g_new0 (gdouble, filter->na);
}
}
g_mutex_unlock (&filter->lock);
}
/* GstAudioFilter vmethod implementations */
static gboolean
gst_audio_fx_base_iir_filter_setup (GstAudioFilter * base,
const GstAudioInfo * info)
{
GstAudioFXBaseIIRFilter *filter = GST_AUDIO_FX_BASE_IIR_FILTER (base);
gboolean ret = TRUE;
gint channels;
g_mutex_lock (&filter->lock);
switch (GST_AUDIO_INFO_FORMAT (info)) {
case GST_AUDIO_FORMAT_F32:
filter->process = (GstAudioFXBaseIIRFilterProcessFunc)
process_32;
break;
case GST_AUDIO_FORMAT_F64:
filter->process = (GstAudioFXBaseIIRFilterProcessFunc)
process_64;
break;
default:
ret = FALSE;
break;
}
channels = GST_AUDIO_INFO_CHANNELS (info);
if (channels != filter->nchannels) {
guint i;
GstAudioFXBaseIIRFilterChannelCtx *ctx;
if (filter->channels) {
for (i = 0; i < filter->nchannels; i++) {
ctx = &filter->channels[i];
g_free (ctx->x);
g_free (ctx->y);
}
g_free (filter->channels);
}
filter->channels = g_new0 (GstAudioFXBaseIIRFilterChannelCtx, channels);
for (i = 0; i < channels; i++) {
ctx = &filter->channels[i];
ctx->x = g_new0 (gdouble, filter->nb);
ctx->y = g_new0 (gdouble, filter->na);
}
filter->nchannels = channels;
}
g_mutex_unlock (&filter->lock);
return ret;
}
static inline gdouble
process (GstAudioFXBaseIIRFilter * filter,
GstAudioFXBaseIIRFilterChannelCtx * ctx, gdouble x0)
{
gdouble val = filter->b[0] * x0;
gint i, j;
for (i = 1, j = ctx->x_pos; i < filter->nb; i++) {
val += filter->b[i] * ctx->x[j];
j--;
if (j < 0)
j = filter->nb - 1;
}
for (i = 1, j = ctx->y_pos; i < filter->na; i++) {
val -= filter->a[i] * ctx->y[j];
j--;
if (j < 0)
j = filter->na - 1;
}
val /= filter->a[0];
if (ctx->x) {
ctx->x_pos++;
if (ctx->x_pos >= filter->nb)
ctx->x_pos = 0;
ctx->x[ctx->x_pos] = x0;
}
if (ctx->y) {
ctx->y_pos++;
if (ctx->y_pos >= filter->na)
ctx->y_pos = 0;
ctx->y[ctx->y_pos] = val;
}
return val;
}
#define DEFINE_PROCESS_FUNC(width,ctype) \
static void \
process_##width (GstAudioFXBaseIIRFilter * filter, \
g##ctype * data, guint num_samples) \
{ \
gint i, j, channels = filter->nchannels; \
gdouble val; \
\
for (i = 0; i < num_samples / channels; i++) { \
for (j = 0; j < channels; j++) { \
val = process (filter, &filter->channels[j], *data); \
*data++ = val; \
} \
} \
}
DEFINE_PROCESS_FUNC (32, float);
DEFINE_PROCESS_FUNC (64, double);
#undef DEFINE_PROCESS_FUNC
/* GstBaseTransform vmethod implementations */
static GstFlowReturn
gst_audio_fx_base_iir_filter_transform_ip (GstBaseTransform * base,
GstBuffer * buf)
{
GstAudioFXBaseIIRFilter *filter = GST_AUDIO_FX_BASE_IIR_FILTER (base);
guint num_samples;
GstClockTime timestamp, stream_time;
GstMapInfo map;
timestamp = GST_BUFFER_TIMESTAMP (buf);
stream_time =
gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp);
GST_DEBUG_OBJECT (filter, "sync to %" GST_TIME_FORMAT,
GST_TIME_ARGS (timestamp));
if (GST_CLOCK_TIME_IS_VALID (stream_time))
gst_object_sync_values (GST_OBJECT (filter), stream_time);
gst_buffer_map (buf, &map, GST_MAP_READWRITE);
num_samples = map.size / GST_AUDIO_FILTER_BPS (filter);
g_mutex_lock (&filter->lock);
if (filter->a == NULL || filter->b == NULL) {
g_warn_if_fail (filter->a != NULL && filter->b != NULL);
gst_buffer_unmap (buf, &map);
g_mutex_unlock (&filter->lock);
return GST_FLOW_ERROR;
}
filter->process (filter, map.data, num_samples);
g_mutex_unlock (&filter->lock);
gst_buffer_unmap (buf, &map);
return GST_FLOW_OK;
}
static gboolean
gst_audio_fx_base_iir_filter_stop (GstBaseTransform * base)
{
GstAudioFXBaseIIRFilter *filter = GST_AUDIO_FX_BASE_IIR_FILTER (base);
guint channels = filter->nchannels;
GstAudioFXBaseIIRFilterChannelCtx *ctx;
guint i;
/* Reset the history of input and output values if
* already existing */
if (channels && filter->channels) {
for (i = 0; i < channels; i++) {
ctx = &filter->channels[i];
g_free (ctx->x);
g_free (ctx->y);
}
g_free (filter->channels);
}
filter->channels = NULL;
filter->nchannels = 0;
return TRUE;
}