/* -*- c-basic-offset: 2 -*- * * GStreamer * Copyright (C) 1999-2001 Erik Walthinsen * 2006 Dreamlab Technologies Ltd. * 2007-2009 Sebastian Dröge * * 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 #include #include #include #include "audiofxbasefirfilter.h" #define GST_CAT_DEFAULT gst_audio_fx_base_fir_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" /* Switch from time-domain to FFT convolution for kernels >= this */ #define FFT_THRESHOLD 32 enum { PROP_0 = 0, PROP_LOW_LATENCY, PROP_DRAIN_ON_CHANGES }; #define DEFAULT_LOW_LATENCY FALSE #define DEFAULT_DRAIN_ON_CHANGES TRUE #define gst_audio_fx_base_fir_filter_parent_class parent_class G_DEFINE_TYPE (GstAudioFXBaseFIRFilter, gst_audio_fx_base_fir_filter, GST_TYPE_AUDIO_FILTER); static GstFlowReturn gst_audio_fx_base_fir_filter_transform (GstBaseTransform * base, GstBuffer * inbuf, GstBuffer * outbuf); static gboolean gst_audio_fx_base_fir_filter_start (GstBaseTransform * base); static gboolean gst_audio_fx_base_fir_filter_stop (GstBaseTransform * base); static gboolean gst_audio_fx_base_fir_filter_sink_event (GstBaseTransform * base, GstEvent * event); static gboolean gst_audio_fx_base_fir_filter_transform_size (GstBaseTransform * base, GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize); static gboolean gst_audio_fx_base_fir_filter_setup (GstAudioFilter * base, const GstAudioInfo * info); static gboolean gst_audio_fx_base_fir_filter_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * quer); /* * The code below calculates the linear convolution: * * y[t] = \sum_{u=0}^{M-1} x[t - u] * h[u] * * where y is the output, x is the input, M is the length * of the filter kernel and h is the filter kernel. For x * holds: x[t] == 0 \forall t < 0. * * The runtime complexity of this is O (M) per sample. * */ #define DEFINE_PROCESS_FUNC(width,ctype) \ static guint \ process_##width (GstAudioFXBaseFIRFilter * self, const g##ctype * src, g##ctype * dst, guint input_samples) \ { \ gint channels = GST_AUDIO_FILTER_CHANNELS (self); \ TIME_DOMAIN_CONVOLUTION_BODY (channels); \ } #define DEFINE_PROCESS_FUNC_FIXED_CHANNELS(width,channels,ctype) \ static guint \ process_##channels##_##width (GstAudioFXBaseFIRFilter * self, const g##ctype * src, g##ctype * dst, guint input_samples) \ { \ TIME_DOMAIN_CONVOLUTION_BODY (channels); \ } #define TIME_DOMAIN_CONVOLUTION_BODY(channels) G_STMT_START { \ gint kernel_length = self->kernel_length; \ gint i, j, k, l; \ gint res_start; \ gint from_input; \ gint off; \ gdouble *buffer = self->buffer; \ gdouble *kernel = self->kernel; \ \ if (!buffer) { \ self->buffer_length = kernel_length * channels; \ self->buffer = buffer = g_new0 (gdouble, self->buffer_length); \ } \ \ input_samples *= channels; \ /* convolution */ \ for (i = 0; i < input_samples; i++) { \ dst[i] = 0.0; \ k = i % channels; \ l = i / channels; \ from_input = MIN (l, kernel_length-1); \ off = l * channels + k; \ for (j = 0; j <= from_input; j++) { \ dst[i] += src[off] * kernel[j]; \ off -= channels; \ } \ /* j == from_input && off == (l - j) * channels + k */ \ off += kernel_length * channels; \ for (; j < kernel_length; j++) { \ dst[i] += buffer[off] * kernel[j]; \ off -= channels; \ } \ } \ \ /* copy the tail of the current input buffer to the residue, while \ * keeping parts of the residue if the input buffer is smaller than \ * the kernel length */ \ /* from now on take kernel length as length over all channels */ \ kernel_length *= channels; \ if (input_samples < kernel_length) \ res_start = kernel_length - input_samples; \ else \ res_start = 0; \ \ for (i = 0; i < res_start; i++) \ buffer[i] = buffer[i + input_samples]; \ /* i == res_start */ \ for (; i < kernel_length; i++) \ buffer[i] = src[input_samples - kernel_length + i]; \ \ self->buffer_fill += kernel_length - res_start; \ if (self->buffer_fill > kernel_length) \ self->buffer_fill = kernel_length; \ \ return input_samples / channels; \ } G_STMT_END DEFINE_PROCESS_FUNC (32, float); DEFINE_PROCESS_FUNC (64, double); DEFINE_PROCESS_FUNC_FIXED_CHANNELS (32, 1, float); DEFINE_PROCESS_FUNC_FIXED_CHANNELS (64, 1, double); DEFINE_PROCESS_FUNC_FIXED_CHANNELS (32, 2, float); DEFINE_PROCESS_FUNC_FIXED_CHANNELS (64, 2, double); #undef TIME_DOMAIN_CONVOLUTION_BODY #undef DEFINE_PROCESS_FUNC #undef DEFINE_PROCESS_FUNC_FIXED_CHANNELS /* This implements FFT convolution and uses the overlap-save algorithm. * See http://cnx.org/content/m12022/latest/ or your favorite * digital signal processing book for details. * * In every pass the following is calculated: * * y = IFFT (FFT(x) * FFT(h)) * * where y is the output in the time domain, x the * input and h the filter kernel. * is the multiplication * of complex numbers. * * Due to the circular convolution theorem this * gives in the time domain: * * y[t] = \sum_{u=0}^{M-1} x[t - u] * h[u] * * where y is the output, M is the kernel length, * x the periodically extended[0] input and h the * filter kernel. * * ([0] Periodically extended means: ) * ( x[t] = x[t+kN] \forall k \in Z ) * ( where N is the length of x ) * * This means: * - Obviously x and h need to be of the same size for the FFT * - The first M-1 output values are useless because they're * built from 1 up to M-1 values from the end of the input * (circular convolusion!). * - The last M-1 input values are only used for 1 up to M-1 * output values, i.e. they need to be used again in the * next pass for the first M-1 input values. * * => The first pass needs M-1 zeroes at the beginning of the * input and the last M-1 input values of every pass need to * be used as the first M-1 input values of the next pass. * * => x must be larger than h to give a useful number of output * samples and h needs to be padded by zeroes at the end to give * it virtually the same size as x (by M we denote the number of * non-padding samples of h). If len(x)==len(h)==M only 1 output * sample would be calculated per pass, len(x)==2*len(h) would * give M+1 output samples, etc. Usually a factor between 4 and 8 * gives a low number of operations per output samples (see website * given above). * * Overall this gives a runtime complexity per sample of * * ( N log N ) * O ( --------- ) compared to O (M) for the direct calculation. * ( N - M + 1 ) */ #define DEFINE_FFT_PROCESS_FUNC(width,ctype) \ static guint \ process_fft_##width (GstAudioFXBaseFIRFilter * self, const g##ctype * src, \ g##ctype * dst, guint input_samples) \ { \ gint channels = GST_AUDIO_FILTER_CHANNELS (self); \ FFT_CONVOLUTION_BODY (channels); \ } #define DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS(width,channels,ctype) \ static guint \ process_fft_##channels##_##width (GstAudioFXBaseFIRFilter * self, const g##ctype * src, \ g##ctype * dst, guint input_samples) \ { \ FFT_CONVOLUTION_BODY (channels); \ } #define FFT_CONVOLUTION_BODY(channels) G_STMT_START { \ gint i, j; \ guint pass; \ guint kernel_length = self->kernel_length; \ guint block_length = self->block_length; \ guint buffer_length = self->buffer_length; \ guint real_buffer_length = buffer_length + kernel_length - 1; \ guint buffer_fill = self->buffer_fill; \ GstFFTF64 *fft = self->fft; \ GstFFTF64 *ifft = self->ifft; \ GstFFTF64Complex *frequency_response = self->frequency_response; \ GstFFTF64Complex *fft_buffer = self->fft_buffer; \ guint frequency_response_length = self->frequency_response_length; \ gdouble *buffer = self->buffer; \ guint generated = 0; \ gdouble re, im; \ \ if (!fft_buffer) \ self->fft_buffer = fft_buffer = \ g_new (GstFFTF64Complex, frequency_response_length); \ \ /* Buffer contains the time domain samples of input data for one chunk \ * plus some more space for the inverse FFT below. \ * \ * The samples are put at offset kernel_length, the inverse FFT \ * overwrites everthing from offset 0 to length-kernel_length+1, keeping \ * the last kernel_length-1 samples for copying to the next processing \ * step. \ */ \ if (!buffer) { \ self->buffer_length = buffer_length = block_length; \ real_buffer_length = buffer_length + kernel_length - 1; \ \ self->buffer = buffer = g_new0 (gdouble, real_buffer_length * channels); \ \ /* Beginning has kernel_length-1 zeroes at the beginning */ \ self->buffer_fill = buffer_fill = kernel_length - 1; \ } \ \ g_assert (self->buffer_length == block_length); \ \ while (input_samples) { \ pass = MIN (buffer_length - buffer_fill, input_samples); \ \ /* Deinterleave channels */ \ for (i = 0; i < pass; i++) { \ for (j = 0; j < channels; j++) { \ buffer[real_buffer_length * j + buffer_fill + kernel_length - 1 + i] = \ src[i * channels + j]; \ } \ } \ buffer_fill += pass; \ src += channels * pass; \ input_samples -= pass; \ \ /* If we don't have a complete buffer go out */ \ if (buffer_fill < buffer_length) \ break; \ \ for (j = 0; j < channels; j++) { \ /* Calculate FFT of input block */ \ gst_fft_f64_fft (fft, \ buffer + real_buffer_length * j + kernel_length - 1, fft_buffer); \ \ /* Complex multiplication of input and filter spectrum */ \ for (i = 0; i < frequency_response_length; i++) { \ re = fft_buffer[i].r; \ im = fft_buffer[i].i; \ \ fft_buffer[i].r = \ re * frequency_response[i].r - \ im * frequency_response[i].i; \ fft_buffer[i].i = \ re * frequency_response[i].i + \ im * frequency_response[i].r; \ } \ \ /* Calculate inverse FFT of the result */ \ gst_fft_f64_inverse_fft (ifft, fft_buffer, \ buffer + real_buffer_length * j); \ \ /* Copy all except the first kernel_length-1 samples to the output */ \ for (i = 0; i < buffer_length - kernel_length + 1; i++) { \ dst[i * channels + j] = \ buffer[real_buffer_length * j + kernel_length - 1 + i]; \ } \ \ /* Copy the last kernel_length-1 samples to the beginning for the next block */ \ for (i = 0; i < kernel_length - 1; i++) { \ buffer[real_buffer_length * j + kernel_length - 1 + i] = \ buffer[real_buffer_length * j + buffer_length + i]; \ } \ } \ \ generated += buffer_length - kernel_length + 1; \ dst += channels * (buffer_length - kernel_length + 1); \ \ /* The the first kernel_length-1 samples are there already */ \ buffer_fill = kernel_length - 1; \ } \ \ /* Write back cached buffer_fill value */ \ self->buffer_fill = buffer_fill; \ \ return generated; \ } G_STMT_END DEFINE_FFT_PROCESS_FUNC (32, float); DEFINE_FFT_PROCESS_FUNC (64, double); DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS (32, 1, float); DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS (64, 1, double); DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS (32, 2, float); DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS (64, 2, double); #undef FFT_CONVOLUTION_BODY #undef DEFINE_FFT_PROCESS_FUNC #undef DEFINE_FFT_PROCESS_FUNC_FIXED_CHANNELS /* Element class */ static void gst_audio_fx_base_fir_filter_calculate_frequency_response (GstAudioFXBaseFIRFilter * self) { gst_fft_f64_free (self->fft); self->fft = NULL; gst_fft_f64_free (self->ifft); self->ifft = NULL; g_free (self->frequency_response); self->frequency_response_length = 0; g_free (self->fft_buffer); self->fft_buffer = NULL; if (self->kernel && self->kernel_length >= FFT_THRESHOLD && !self->low_latency) { guint block_length, i; gdouble *kernel_tmp, *kernel = self->kernel; /* We process 4 * kernel_length samples per pass in FFT mode */ block_length = 4 * self->kernel_length; block_length = gst_fft_next_fast_length (block_length); self->block_length = block_length; kernel_tmp = g_new0 (gdouble, block_length); memcpy (kernel_tmp, kernel, self->kernel_length * sizeof (gdouble)); self->fft = gst_fft_f64_new (block_length, FALSE); self->ifft = gst_fft_f64_new (block_length, TRUE); self->frequency_response_length = block_length / 2 + 1; self->frequency_response = g_new (GstFFTF64Complex, self->frequency_response_length); gst_fft_f64_fft (self->fft, kernel_tmp, self->frequency_response); g_free (kernel_tmp); /* Normalize to make sure IFFT(FFT(x)) == x */ for (i = 0; i < self->frequency_response_length; i++) { self->frequency_response[i].r /= block_length; self->frequency_response[i].i /= block_length; } } } /* Must be called with base transform lock! */ static void gst_audio_fx_base_fir_filter_select_process_function (GstAudioFXBaseFIRFilter * self, GstAudioFormat format, gint channels) { switch (format) { case GST_AUDIO_FORMAT_F32: if (self->fft && !self->low_latency) { if (channels == 1) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_1_32; else if (channels == 2) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_2_32; else self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_32; } else { if (channels == 1) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_1_32; else if (channels == 2) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_2_32; else self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_32; } break; case GST_AUDIO_FORMAT_F64: if (self->fft && !self->low_latency) { if (channels == 1) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_1_64; else if (channels == 2) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_2_64; else self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_fft_64; } else { if (channels == 1) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_1_64; else if (channels == 2) self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_2_64; else self->process = (GstAudioFXBaseFIRFilterProcessFunc) process_64; } break; default: self->process = NULL; break; } } static void gst_audio_fx_base_fir_filter_finalize (GObject * object) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (object); g_free (self->buffer); g_free (self->kernel); gst_fft_f64_free (self->fft); gst_fft_f64_free (self->ifft); g_free (self->frequency_response); g_free (self->fft_buffer); g_mutex_clear (&self->lock); G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_audio_fx_base_fir_filter_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (object); switch (prop_id) { case PROP_LOW_LATENCY:{ gboolean low_latency; if (GST_STATE (self) >= GST_STATE_PAUSED) { g_warning ("Changing the \"low-latency\" property " "is only allowed in states < PAUSED"); return; } g_mutex_lock (&self->lock); low_latency = g_value_get_boolean (value); if (self->low_latency != low_latency) { self->low_latency = low_latency; gst_audio_fx_base_fir_filter_calculate_frequency_response (self); gst_audio_fx_base_fir_filter_select_process_function (self, GST_AUDIO_FILTER_FORMAT (self), GST_AUDIO_FILTER_CHANNELS (self)); } g_mutex_unlock (&self->lock); break; } case PROP_DRAIN_ON_CHANGES:{ g_mutex_lock (&self->lock); self->drain_on_changes = g_value_get_boolean (value); g_mutex_unlock (&self->lock); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_audio_fx_base_fir_filter_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (object); switch (prop_id) { case PROP_LOW_LATENCY: g_value_set_boolean (value, self->low_latency); break; case PROP_DRAIN_ON_CHANGES: g_value_set_boolean (value, self->drain_on_changes); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_audio_fx_base_fir_filter_class_init (GstAudioFXBaseFIRFilterClass * 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_fir_filter_debug, "audiofxbasefirfilter", 0, "FIR filter base class"); gobject_class->finalize = gst_audio_fx_base_fir_filter_finalize; gobject_class->set_property = gst_audio_fx_base_fir_filter_set_property; gobject_class->get_property = gst_audio_fx_base_fir_filter_get_property; /** * GstAudioFXBaseFIRFilter:low-latency: * * Work in low-latency mode. This mode is much slower for large filter sizes * but the latency is always only the pre-latency of the filter. */ g_object_class_install_property (gobject_class, PROP_LOW_LATENCY, g_param_spec_boolean ("low-latency", "Low latency", "Operate in low latency mode. This mode is slower but the " "latency will only be the filter pre-latency. " "Can only be changed in states < PAUSED!", DEFAULT_LOW_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); /** * GstAudioFXBaseFIRFilter:drain-on-changes: * * Whether the filter should be drained when its coeficients change * * Note: Currently this only works if the kernel size is not changed! * Support for drainless kernel size changes will be added in the future. */ g_object_class_install_property (gobject_class, PROP_DRAIN_ON_CHANGES, g_param_spec_boolean ("drain-on-changes", "Drain on changes", "Drains the filter when its coeficients change", DEFAULT_DRAIN_ON_CHANGES, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); caps = gst_caps_from_string (ALLOWED_CAPS); gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass), caps); gst_caps_unref (caps); trans_class->transform = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_transform); trans_class->start = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_start); trans_class->stop = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_stop); trans_class->sink_event = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_sink_event); trans_class->query = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_query); trans_class->transform_size = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_transform_size); filter_class->setup = GST_DEBUG_FUNCPTR (gst_audio_fx_base_fir_filter_setup); } static void gst_audio_fx_base_fir_filter_init (GstAudioFXBaseFIRFilter * self) { self->kernel = NULL; self->buffer = NULL; self->buffer_length = 0; self->start_ts = GST_CLOCK_TIME_NONE; self->start_off = GST_BUFFER_OFFSET_NONE; self->nsamples_out = 0; self->nsamples_in = 0; self->low_latency = DEFAULT_LOW_LATENCY; self->drain_on_changes = DEFAULT_DRAIN_ON_CHANGES; g_mutex_init (&self->lock); } void gst_audio_fx_base_fir_filter_push_residue (GstAudioFXBaseFIRFilter * self) { GstBuffer *outbuf; GstFlowReturn res; gint rate = GST_AUDIO_FILTER_RATE (self); gint channels = GST_AUDIO_FILTER_CHANNELS (self); gint bps = GST_AUDIO_FILTER_BPS (self); gint outsize, outsamples; GstMapInfo map; guint8 *in, *out; if (channels == 0 || rate == 0 || self->nsamples_in == 0) { self->buffer_fill = 0; g_free (self->buffer); self->buffer = NULL; return; } /* Calculate the number of samples and their memory size that * should be pushed from the residue */ outsamples = self->nsamples_in - (self->nsamples_out - self->latency); if (outsamples <= 0) { self->buffer_fill = 0; g_free (self->buffer); self->buffer = NULL; return; } outsize = outsamples * channels * bps; if (!self->fft || self->low_latency) { gint64 diffsize, diffsamples; /* Process the difference between latency and residue length samples * to start at the actual data instead of starting at the zeros before * when we only got one buffer smaller than latency */ diffsamples = ((gint64) self->latency) - ((gint64) self->buffer_fill) / channels; if (diffsamples > 0) { diffsize = diffsamples * channels * bps; in = g_new0 (guint8, diffsize); out = g_new0 (guint8, diffsize); self->nsamples_out += self->process (self, in, out, diffsamples); g_free (in); g_free (out); } outbuf = gst_buffer_new_and_alloc (outsize); /* Convolve the residue with zeros to get the actual remaining data */ in = g_new0 (guint8, outsize); gst_buffer_map (outbuf, &map, GST_MAP_READWRITE); self->nsamples_out += self->process (self, in, map.data, outsamples); gst_buffer_unmap (outbuf, &map); g_free (in); } else { guint gensamples = 0; outbuf = gst_buffer_new_and_alloc (outsize); gst_buffer_map (outbuf, &map, GST_MAP_READWRITE); while (gensamples < outsamples) { guint step_insamples = self->block_length - self->buffer_fill; guint8 *zeroes = g_new0 (guint8, step_insamples * channels * bps); guint8 *out = g_new (guint8, self->block_length * channels * bps); guint step_gensamples; step_gensamples = self->process (self, zeroes, out, step_insamples); g_free (zeroes); memcpy (map.data + gensamples * bps, out, MIN (step_gensamples, outsamples - gensamples) * bps); gensamples += MIN (step_gensamples, outsamples - gensamples); g_free (out); } self->nsamples_out += gensamples; gst_buffer_unmap (outbuf, &map); } /* Set timestamp, offset, etc from the values we * saved when processing the regular buffers */ if (GST_CLOCK_TIME_IS_VALID (self->start_ts)) GST_BUFFER_TIMESTAMP (outbuf) = self->start_ts; else GST_BUFFER_TIMESTAMP (outbuf) = 0; GST_BUFFER_TIMESTAMP (outbuf) += gst_util_uint64_scale_int (self->nsamples_out - outsamples - self->latency, GST_SECOND, rate); GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (outsamples, GST_SECOND, rate); if (self->start_off != GST_BUFFER_OFFSET_NONE) { GST_BUFFER_OFFSET (outbuf) = self->start_off + self->nsamples_out - outsamples - self->latency; GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + outsamples; } GST_DEBUG_OBJECT (self, "Pushing residue buffer of size %" G_GSIZE_FORMAT " with timestamp: %" GST_TIME_FORMAT ", duration: %" GST_TIME_FORMAT ", offset: %" G_GUINT64_FORMAT ", offset_end: %" G_GUINT64_FORMAT ", nsamples_out: %d", gst_buffer_get_size (outbuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf), outsamples); res = gst_pad_push (GST_BASE_TRANSFORM_CAST (self)->srcpad, outbuf); if (G_UNLIKELY (res != GST_FLOW_OK)) { GST_WARNING_OBJECT (self, "failed to push residue"); } self->buffer_fill = 0; } /* GstAudioFilter vmethod implementations */ /* get notified of caps and plug in the correct process function */ static gboolean gst_audio_fx_base_fir_filter_setup (GstAudioFilter * base, const GstAudioInfo * info) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); g_mutex_lock (&self->lock); if (self->buffer) { gst_audio_fx_base_fir_filter_push_residue (self); g_free (self->buffer); self->buffer = NULL; self->buffer_fill = 0; self->buffer_length = 0; self->start_ts = GST_CLOCK_TIME_NONE; self->start_off = GST_BUFFER_OFFSET_NONE; self->nsamples_out = 0; self->nsamples_in = 0; } gst_audio_fx_base_fir_filter_select_process_function (self, GST_AUDIO_INFO_FORMAT (info), GST_AUDIO_INFO_CHANNELS (info)); g_mutex_unlock (&self->lock); return (self->process != NULL); } /* GstBaseTransform vmethod implementations */ static gboolean gst_audio_fx_base_fir_filter_transform_size (GstBaseTransform * base, GstPadDirection direction, GstCaps * caps, gsize size, GstCaps * othercaps, gsize * othersize) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); guint blocklen; GstAudioInfo info; gint bpf; if (!self->fft || self->low_latency || direction == GST_PAD_SRC) { *othersize = size; return TRUE; } if (!gst_audio_info_from_caps (&info, caps)) return FALSE; bpf = GST_AUDIO_INFO_BPF (&info); size /= bpf; blocklen = self->block_length - self->kernel_length + 1; *othersize = ((size + blocklen - 1) / blocklen) * blocklen; *othersize *= bpf; return TRUE; } static GstFlowReturn gst_audio_fx_base_fir_filter_transform (GstBaseTransform * base, GstBuffer * inbuf, GstBuffer * outbuf) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); GstClockTime timestamp, expected_timestamp; gint channels = GST_AUDIO_FILTER_CHANNELS (self); gint rate = GST_AUDIO_FILTER_RATE (self); gint bps = GST_AUDIO_FILTER_BPS (self); GstMapInfo inmap, outmap; guint input_samples; guint output_samples; guint generated_samples; guint64 output_offset; gint64 diff = 0; GstClockTime stream_time; timestamp = GST_BUFFER_TIMESTAMP (outbuf); if (!GST_CLOCK_TIME_IS_VALID (timestamp) && !GST_CLOCK_TIME_IS_VALID (self->start_ts)) { GST_ERROR_OBJECT (self, "Invalid timestamp"); return GST_FLOW_ERROR; } g_mutex_lock (&self->lock); stream_time = gst_segment_to_stream_time (&base->segment, GST_FORMAT_TIME, timestamp); GST_DEBUG_OBJECT (self, "sync to %" GST_TIME_FORMAT, GST_TIME_ARGS (timestamp)); if (GST_CLOCK_TIME_IS_VALID (stream_time)) gst_object_sync_values (GST_OBJECT (self), stream_time); g_return_val_if_fail (self->kernel != NULL, GST_FLOW_ERROR); g_return_val_if_fail (channels != 0, GST_FLOW_ERROR); if (GST_CLOCK_TIME_IS_VALID (self->start_ts)) expected_timestamp = self->start_ts + gst_util_uint64_scale_int (self->nsamples_in, GST_SECOND, rate); else expected_timestamp = GST_CLOCK_TIME_NONE; /* Reset the residue if already existing on discont buffers */ if (GST_BUFFER_IS_DISCONT (inbuf) || (GST_CLOCK_TIME_IS_VALID (expected_timestamp) && (ABS (GST_CLOCK_DIFF (timestamp, expected_timestamp)) > 5 * GST_MSECOND))) { GST_DEBUG_OBJECT (self, "Discontinuity detected - flushing"); if (GST_CLOCK_TIME_IS_VALID (expected_timestamp)) gst_audio_fx_base_fir_filter_push_residue (self); self->buffer_fill = 0; g_free (self->buffer); self->buffer = NULL; self->start_ts = timestamp; self->start_off = GST_BUFFER_OFFSET (inbuf); self->nsamples_out = 0; self->nsamples_in = 0; } else if (!GST_CLOCK_TIME_IS_VALID (self->start_ts)) { self->start_ts = timestamp; self->start_off = GST_BUFFER_OFFSET (inbuf); } gst_buffer_map (inbuf, &inmap, GST_MAP_READ); gst_buffer_map (outbuf, &outmap, GST_MAP_WRITE); input_samples = (inmap.size / bps) / channels; output_samples = (outmap.size / bps) / channels; self->nsamples_in += input_samples; generated_samples = self->process (self, inmap.data, outmap.data, input_samples); gst_buffer_unmap (inbuf, &inmap); gst_buffer_unmap (outbuf, &outmap); g_assert (generated_samples <= output_samples); self->nsamples_out += generated_samples; if (generated_samples == 0) goto no_samples; /* Calculate the number of samples we can push out now without outputting * latency zeros in the beginning */ diff = ((gint64) self->nsamples_out) - ((gint64) self->latency); if (diff < 0) goto no_samples; if (diff < generated_samples) { gint64 tmp = diff; diff = generated_samples - diff; generated_samples = tmp; } else { diff = 0; } gst_buffer_resize (outbuf, diff * bps * channels, generated_samples * bps * channels); output_offset = self->nsamples_out - self->latency - generated_samples; GST_BUFFER_TIMESTAMP (outbuf) = self->start_ts + gst_util_uint64_scale_int (output_offset, GST_SECOND, rate); GST_BUFFER_DURATION (outbuf) = gst_util_uint64_scale_int (output_samples, GST_SECOND, rate); if (self->start_off != GST_BUFFER_OFFSET_NONE) { GST_BUFFER_OFFSET (outbuf) = self->start_off + output_offset; GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET (outbuf) + generated_samples; } else { GST_BUFFER_OFFSET (outbuf) = GST_BUFFER_OFFSET_NONE; GST_BUFFER_OFFSET_END (outbuf) = GST_BUFFER_OFFSET_NONE; } g_mutex_unlock (&self->lock); GST_DEBUG_OBJECT (self, "Pushing buffer of size %" G_GSIZE_FORMAT " with timestamp: %" GST_TIME_FORMAT ", duration: %" GST_TIME_FORMAT ", offset: %" G_GUINT64_FORMAT ", offset_end: %" G_GUINT64_FORMAT ", nsamples_out: %d", gst_buffer_get_size (outbuf), GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), GST_TIME_ARGS (GST_BUFFER_DURATION (outbuf)), GST_BUFFER_OFFSET (outbuf), GST_BUFFER_OFFSET_END (outbuf), generated_samples); return GST_FLOW_OK; no_samples: { g_mutex_unlock (&self->lock); return GST_BASE_TRANSFORM_FLOW_DROPPED; } } static gboolean gst_audio_fx_base_fir_filter_start (GstBaseTransform * base) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); self->buffer_fill = 0; g_free (self->buffer); self->buffer = NULL; self->start_ts = GST_CLOCK_TIME_NONE; self->start_off = GST_BUFFER_OFFSET_NONE; self->nsamples_out = 0; self->nsamples_in = 0; return TRUE; } static gboolean gst_audio_fx_base_fir_filter_stop (GstBaseTransform * base) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); g_free (self->buffer); self->buffer = NULL; self->buffer_length = 0; return TRUE; } static gboolean gst_audio_fx_base_fir_filter_query (GstBaseTransform * trans, GstPadDirection direction, GstQuery * query) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (trans); gboolean res = TRUE; switch (GST_QUERY_TYPE (query)) { case GST_QUERY_LATENCY: { GstClockTime min, max; gboolean live; guint64 latency; gint rate = GST_AUDIO_FILTER_RATE (self); if (rate == 0) { res = FALSE; } else if ((res = gst_pad_peer_query (GST_BASE_TRANSFORM (self)->sinkpad, query))) { gst_query_parse_latency (query, &live, &min, &max); GST_DEBUG_OBJECT (self, "Peer latency: min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max)); if (self->fft && !self->low_latency) latency = self->block_length - self->kernel_length + 1; else latency = self->latency; /* add our own latency */ latency = gst_util_uint64_scale_round (latency, GST_SECOND, rate); GST_DEBUG_OBJECT (self, "Our latency: %" GST_TIME_FORMAT, GST_TIME_ARGS (latency)); min += latency; if (max != GST_CLOCK_TIME_NONE) max += latency; GST_DEBUG_OBJECT (self, "Calculated total latency : min %" GST_TIME_FORMAT " max %" GST_TIME_FORMAT, GST_TIME_ARGS (min), GST_TIME_ARGS (max)); gst_query_set_latency (query, live, min, max); } break; } default: res = GST_BASE_TRANSFORM_CLASS (parent_class)->query (trans, direction, query); break; } return res; } static gboolean gst_audio_fx_base_fir_filter_sink_event (GstBaseTransform * base, GstEvent * event) { GstAudioFXBaseFIRFilter *self = GST_AUDIO_FX_BASE_FIR_FILTER (base); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: gst_audio_fx_base_fir_filter_push_residue (self); self->start_ts = GST_CLOCK_TIME_NONE; self->start_off = GST_BUFFER_OFFSET_NONE; self->nsamples_out = 0; self->nsamples_in = 0; break; default: break; } return GST_BASE_TRANSFORM_CLASS (parent_class)->sink_event (base, event); } void gst_audio_fx_base_fir_filter_set_kernel (GstAudioFXBaseFIRFilter * self, gdouble * kernel, guint kernel_length, guint64 latency, const GstAudioInfo * info) { gboolean latency_changed; GstAudioFormat format; gint channels; g_return_if_fail (kernel != NULL); g_return_if_fail (self != NULL); g_mutex_lock (&self->lock); latency_changed = (self->latency != latency || (!self->low_latency && self->kernel_length < FFT_THRESHOLD && kernel_length >= FFT_THRESHOLD) || (!self->low_latency && self->kernel_length >= FFT_THRESHOLD && kernel_length < FFT_THRESHOLD)); /* FIXME: If the latency changes, the buffer size changes too and we * have to drain in any case until this is fixed in the future */ if (self->buffer && (!self->drain_on_changes || latency_changed)) { gst_audio_fx_base_fir_filter_push_residue (self); self->start_ts = GST_CLOCK_TIME_NONE; self->start_off = GST_BUFFER_OFFSET_NONE; self->nsamples_out = 0; self->nsamples_in = 0; self->buffer_fill = 0; } g_free (self->kernel); if (!self->drain_on_changes || latency_changed) { g_free (self->buffer); self->buffer = NULL; self->buffer_fill = 0; self->buffer_length = 0; } self->kernel = kernel; self->kernel_length = kernel_length; if (info) { format = GST_AUDIO_INFO_FORMAT (info); channels = GST_AUDIO_INFO_CHANNELS (info); } else { format = GST_AUDIO_FILTER_FORMAT (self); channels = GST_AUDIO_FILTER_CHANNELS (self); } gst_audio_fx_base_fir_filter_calculate_frequency_response (self); gst_audio_fx_base_fir_filter_select_process_function (self, format, channels); if (latency_changed) { self->latency = latency; gst_element_post_message (GST_ELEMENT (self), gst_message_new_latency (GST_OBJECT (self))); } g_mutex_unlock (&self->lock); }