mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 16:21:17 +00:00
9041a588f9
... in that it will now call subclass with info on proposed audio format without having set that info already in base class. As such, subclass can not rely on audio format info being available there.
363 lines
10 KiB
C
363 lines
10 KiB
C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2008 Wim Taymans <wim.taymans@gmail.com>
|
|
*
|
|
* 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., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-audiokaraoke
|
|
*
|
|
* Remove the voice from audio by filtering the center channel.
|
|
* This plugin is useful for karaoke applications.
|
|
*
|
|
* <refsect2>
|
|
* <title>Example launch line</title>
|
|
* |[
|
|
* gst-launch filesrc location=song.ogg ! oggdemux ! vorbisdec ! audiokaraoke ! audioconvert ! alsasink
|
|
* ]|
|
|
* </refsect2>
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <math.h>
|
|
|
|
#include <gst/gst.h>
|
|
#include <gst/base/gstbasetransform.h>
|
|
#include <gst/audio/audio.h>
|
|
#include <gst/audio/gstaudiofilter.h>
|
|
|
|
#include "audiokaraoke.h"
|
|
|
|
#define GST_CAT_DEFAULT gst_audio_karaoke_debug
|
|
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
|
|
|
|
/* Filter signals and args */
|
|
enum
|
|
{
|
|
/* FILL ME */
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
#define DEFAULT_LEVEL 1.0
|
|
#define DEFAULT_MONO_LEVEL 1.0
|
|
#define DEFAULT_FILTER_BAND 220.0
|
|
#define DEFAULT_FILTER_WIDTH 100.0
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_LEVEL,
|
|
PROP_MONO_LEVEL,
|
|
PROP_FILTER_BAND,
|
|
PROP_FILTER_WIDTH,
|
|
PROP_LAST
|
|
};
|
|
|
|
#define ALLOWED_CAPS \
|
|
"audio/x-raw," \
|
|
" format=(string){"GST_AUDIO_NE(S16)","GST_AUDIO_NE(F32)"}," \
|
|
" rate=(int)[1,MAX]," \
|
|
" channels=(int)2," \
|
|
" channel-mask=(bitmask)0x3," \
|
|
" layout=(string) interleaved"
|
|
|
|
G_DEFINE_TYPE (GstAudioKaraoke, gst_audio_karaoke, GST_TYPE_AUDIO_FILTER);
|
|
|
|
static void gst_audio_karaoke_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_audio_karaoke_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static gboolean gst_audio_karaoke_setup (GstAudioFilter * filter,
|
|
const GstAudioInfo * info);
|
|
static GstFlowReturn gst_audio_karaoke_transform_ip (GstBaseTransform * base,
|
|
GstBuffer * buf);
|
|
|
|
static void gst_audio_karaoke_transform_int (GstAudioKaraoke * filter,
|
|
gint16 * data, guint num_samples);
|
|
static void gst_audio_karaoke_transform_float (GstAudioKaraoke * filter,
|
|
gfloat * data, guint num_samples);
|
|
|
|
/* GObject vmethod implementations */
|
|
|
|
static void
|
|
gst_audio_karaoke_class_init (GstAudioKaraokeClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstCaps *caps;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (gst_audio_karaoke_debug, "audiokaraoke", 0,
|
|
"audiokaraoke element");
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
|
|
gobject_class->set_property = gst_audio_karaoke_set_property;
|
|
gobject_class->get_property = gst_audio_karaoke_get_property;
|
|
|
|
g_object_class_install_property (gobject_class, PROP_LEVEL,
|
|
g_param_spec_float ("level", "Level",
|
|
"Level of the effect (1.0 = full)", 0.0, 1.0, DEFAULT_LEVEL,
|
|
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_MONO_LEVEL,
|
|
g_param_spec_float ("mono-level", "Mono Level",
|
|
"Level of the mono channel (1.0 = full)", 0.0, 1.0, DEFAULT_LEVEL,
|
|
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FILTER_BAND,
|
|
g_param_spec_float ("filter-band", "Filter Band",
|
|
"The Frequency band of the filter", 0.0, 441.0, DEFAULT_FILTER_BAND,
|
|
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (gobject_class, PROP_FILTER_WIDTH,
|
|
g_param_spec_float ("filter-width", "Filter Width",
|
|
"The Frequency width of the filter", 0.0, 100.0, DEFAULT_FILTER_WIDTH,
|
|
G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gst_element_class_set_details_simple (gstelement_class, "AudioKaraoke",
|
|
"Filter/Effect/Audio",
|
|
"Removes voice from sound", "Wim Taymans <wim.taymans@gmail.com>");
|
|
|
|
caps = gst_caps_from_string (ALLOWED_CAPS);
|
|
gst_audio_filter_class_add_pad_templates (GST_AUDIO_FILTER_CLASS (klass),
|
|
caps);
|
|
gst_caps_unref (caps);
|
|
|
|
GST_AUDIO_FILTER_CLASS (klass)->setup =
|
|
GST_DEBUG_FUNCPTR (gst_audio_karaoke_setup);
|
|
GST_BASE_TRANSFORM_CLASS (klass)->transform_ip =
|
|
GST_DEBUG_FUNCPTR (gst_audio_karaoke_transform_ip);
|
|
}
|
|
|
|
static void
|
|
gst_audio_karaoke_init (GstAudioKaraoke * filter)
|
|
{
|
|
gst_base_transform_set_in_place (GST_BASE_TRANSFORM (filter), TRUE);
|
|
gst_base_transform_set_gap_aware (GST_BASE_TRANSFORM (filter), TRUE);
|
|
|
|
filter->level = DEFAULT_LEVEL;
|
|
filter->mono_level = DEFAULT_MONO_LEVEL;
|
|
filter->filter_band = DEFAULT_FILTER_BAND;
|
|
filter->filter_width = DEFAULT_FILTER_WIDTH;
|
|
}
|
|
|
|
static void
|
|
update_filter (GstAudioKaraoke * filter, const GstAudioInfo * info)
|
|
{
|
|
gfloat A, B, C;
|
|
gint rate;
|
|
|
|
if (info) {
|
|
rate = GST_AUDIO_INFO_RATE (info);
|
|
} else {
|
|
rate = GST_AUDIO_FILTER_RATE (filter);
|
|
}
|
|
|
|
if (rate == 0)
|
|
return;
|
|
|
|
C = exp (-2 * G_PI * filter->filter_width / rate);
|
|
B = -4 * C / (1 + C) * cos (2 * G_PI * filter->filter_band / rate);
|
|
A = sqrt (1 - B * B / (4 * C)) * (1 - C);
|
|
|
|
filter->A = A;
|
|
filter->B = B;
|
|
filter->C = C;
|
|
filter->y1 = 0.0;
|
|
filter->y2 = 0.0;
|
|
}
|
|
|
|
static void
|
|
gst_audio_karaoke_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioKaraoke *filter;
|
|
|
|
filter = GST_AUDIO_KARAOKE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LEVEL:
|
|
filter->level = g_value_get_float (value);
|
|
break;
|
|
case PROP_MONO_LEVEL:
|
|
filter->mono_level = g_value_get_float (value);
|
|
break;
|
|
case PROP_FILTER_BAND:
|
|
filter->filter_band = g_value_get_float (value);
|
|
update_filter (filter, NULL);
|
|
break;
|
|
case PROP_FILTER_WIDTH:
|
|
filter->filter_width = g_value_get_float (value);
|
|
update_filter (filter, NULL);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_audio_karaoke_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstAudioKaraoke *filter;
|
|
|
|
filter = GST_AUDIO_KARAOKE (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_LEVEL:
|
|
g_value_set_float (value, filter->level);
|
|
break;
|
|
case PROP_MONO_LEVEL:
|
|
g_value_set_float (value, filter->mono_level);
|
|
break;
|
|
case PROP_FILTER_BAND:
|
|
g_value_set_float (value, filter->filter_band);
|
|
break;
|
|
case PROP_FILTER_WIDTH:
|
|
g_value_set_float (value, filter->filter_width);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* GstAudioFilter vmethod implementations */
|
|
|
|
static gboolean
|
|
gst_audio_karaoke_setup (GstAudioFilter * base, const GstAudioInfo * info)
|
|
{
|
|
GstAudioKaraoke *filter = GST_AUDIO_KARAOKE (base);
|
|
gboolean ret = TRUE;
|
|
|
|
switch (GST_AUDIO_INFO_FORMAT (info)) {
|
|
case GST_AUDIO_FORMAT_S16:
|
|
filter->process = (GstAudioKaraokeProcessFunc)
|
|
gst_audio_karaoke_transform_int;
|
|
break;
|
|
case GST_AUDIO_FORMAT_F32:
|
|
filter->process = (GstAudioKaraokeProcessFunc)
|
|
gst_audio_karaoke_transform_float;
|
|
break;
|
|
default:
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
update_filter (filter, info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
gst_audio_karaoke_transform_int (GstAudioKaraoke * filter,
|
|
gint16 * data, guint num_samples)
|
|
{
|
|
gint i, l, r, o, x;
|
|
gint channels;
|
|
gdouble y;
|
|
gint level;
|
|
|
|
channels = GST_AUDIO_FILTER_CHANNELS (filter);
|
|
level = filter->level * 256;
|
|
|
|
for (i = 0; i < num_samples; i += channels) {
|
|
/* get left and right inputs */
|
|
l = data[i];
|
|
r = data[i + 1];
|
|
/* do filtering */
|
|
x = (l + r) / 2;
|
|
y = (filter->A * x - filter->B * filter->y1) - filter->C * filter->y2;
|
|
filter->y2 = filter->y1;
|
|
filter->y1 = y;
|
|
/* filter mono signal */
|
|
o = (int) (y * filter->mono_level);
|
|
o = CLAMP (o, G_MININT16, G_MAXINT16);
|
|
o = (o * level) >> 8;
|
|
/* now cut the center */
|
|
x = l - ((r * level) >> 8) + o;
|
|
r = r - ((l * level) >> 8) + o;
|
|
data[i] = CLAMP (x, G_MININT16, G_MAXINT16);
|
|
data[i + 1] = CLAMP (r, G_MININT16, G_MAXINT16);
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_audio_karaoke_transform_float (GstAudioKaraoke * filter,
|
|
gfloat * data, guint num_samples)
|
|
{
|
|
gint i;
|
|
gint channels;
|
|
gdouble l, r, o;
|
|
gdouble y;
|
|
|
|
channels = GST_AUDIO_FILTER_CHANNELS (filter);
|
|
|
|
for (i = 0; i < num_samples; i += channels) {
|
|
/* get left and right inputs */
|
|
l = data[i];
|
|
r = data[i + 1];
|
|
/* do filtering */
|
|
y = (filter->A * ((l + r) / 2.0) - filter->B * filter->y1) -
|
|
filter->C * filter->y2;
|
|
filter->y2 = filter->y1;
|
|
filter->y1 = y;
|
|
/* filter mono signal */
|
|
o = y * filter->mono_level * filter->level;
|
|
/* now cut the center */
|
|
data[i] = l - (r * filter->level) + o;
|
|
data[i + 1] = r - (l * filter->level) + o;
|
|
}
|
|
}
|
|
|
|
/* GstBaseTransform vmethod implementations */
|
|
static GstFlowReturn
|
|
gst_audio_karaoke_transform_ip (GstBaseTransform * base, GstBuffer * buf)
|
|
{
|
|
GstAudioKaraoke *filter = GST_AUDIO_KARAOKE (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);
|
|
|
|
if (gst_base_transform_is_passthrough (base) ||
|
|
G_UNLIKELY (GST_BUFFER_FLAG_IS_SET (buf, GST_BUFFER_FLAG_GAP)))
|
|
return GST_FLOW_OK;
|
|
|
|
gst_buffer_map (buf, &map, GST_MAP_READWRITE);
|
|
num_samples = map.size / GST_AUDIO_FILTER_BPS (filter);
|
|
|
|
filter->process (filter, map.data, num_samples);
|
|
|
|
gst_buffer_unmap (buf, &map);
|
|
|
|
return GST_FLOW_OK;
|
|
}
|