mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-28 04:31:06 +00:00
opusdec: implement replay gain
It would ideally be better to leave this to a rgvolume element, but we don't control the pipeline. So do it by default, and allow disabling it via a property, so the correct volume should always be output.
This commit is contained in:
parent
bfdda8e09a
commit
5c8812f58c
2 changed files with 57 additions and 2 deletions
|
@ -41,6 +41,7 @@
|
||||||
# include "config.h"
|
# include "config.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <gst/tag/tag.h>
|
#include <gst/tag/tag.h>
|
||||||
#include "gstopusheader.h"
|
#include "gstopusheader.h"
|
||||||
|
@ -67,12 +68,16 @@ GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
GST_STATIC_CAPS ("audio/x-opus")
|
GST_STATIC_CAPS ("audio/x-opus")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
#define DB_TO_LINEAR(x) pow (10., (x) / 20.)
|
||||||
|
|
||||||
#define DEFAULT_USE_INBAND_FEC FALSE
|
#define DEFAULT_USE_INBAND_FEC FALSE
|
||||||
|
#define DEFAULT_APPLY_GAIN TRUE
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
PROP_0,
|
PROP_0,
|
||||||
PROP_USE_INBAND_FEC
|
PROP_USE_INBAND_FEC,
|
||||||
|
PROP_APPLY_GAIN
|
||||||
};
|
};
|
||||||
|
|
||||||
GST_BOILERPLATE (GstOpusDec, gst_opus_dec, GstAudioDecoder,
|
GST_BOILERPLATE (GstOpusDec, gst_opus_dec, GstAudioDecoder,
|
||||||
|
@ -131,6 +136,11 @@ gst_opus_dec_class_init (GstOpusDecClass * klass)
|
||||||
"Use forward error correction if available", DEFAULT_USE_INBAND_FEC,
|
"Use forward error correction if available", DEFAULT_USE_INBAND_FEC,
|
||||||
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
g_object_class_install_property (gobject_class, PROP_APPLY_GAIN,
|
||||||
|
g_param_spec_boolean ("apply-gain", "Apply gain",
|
||||||
|
"Apply gain if any is specified in the header", DEFAULT_APPLY_GAIN,
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT (opusdec_debug, "opusdec", 0,
|
GST_DEBUG_CATEGORY_INIT (opusdec_debug, "opusdec", 0,
|
||||||
"opus decoding element");
|
"opus decoding element");
|
||||||
}
|
}
|
||||||
|
@ -150,6 +160,7 @@ gst_opus_dec_reset (GstOpusDec * dec)
|
||||||
dec->primed = FALSE;
|
dec->primed = FALSE;
|
||||||
|
|
||||||
dec->pre_skip = 0;
|
dec->pre_skip = 0;
|
||||||
|
dec->r128_gain = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -158,6 +169,7 @@ gst_opus_dec_init (GstOpusDec * dec, GstOpusDecClass * g_class)
|
||||||
dec->sample_rate = 0;
|
dec->sample_rate = 0;
|
||||||
dec->n_channels = 0;
|
dec->n_channels = 0;
|
||||||
dec->use_inband_fec = FALSE;
|
dec->use_inband_fec = FALSE;
|
||||||
|
dec->apply_gain = DEFAULT_APPLY_GAIN;
|
||||||
|
|
||||||
gst_opus_dec_reset (dec);
|
gst_opus_dec_reset (dec);
|
||||||
}
|
}
|
||||||
|
@ -190,6 +202,18 @@ gst_opus_dec_stop (GstAudioDecoder * dec)
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
gst_opus_dec_get_r128_gain (gint16 r128_gain)
|
||||||
|
{
|
||||||
|
return r128_gain / (double) (1 << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double
|
||||||
|
gst_opus_dec_get_r128_volume (gint16 r128_gain)
|
||||||
|
{
|
||||||
|
return DB_TO_LINEAR (gst_opus_dec_get_r128_gain (r128_gain));
|
||||||
|
}
|
||||||
|
|
||||||
static GstFlowReturn
|
static GstFlowReturn
|
||||||
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
|
gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
|
||||||
{
|
{
|
||||||
|
@ -198,7 +222,11 @@ gst_opus_dec_parse_header (GstOpusDec * dec, GstBuffer * buf)
|
||||||
g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR);
|
g_return_val_if_fail (GST_BUFFER_SIZE (buf) >= 19, GST_FLOW_ERROR);
|
||||||
|
|
||||||
dec->pre_skip = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 10);
|
dec->pre_skip = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 10);
|
||||||
GST_INFO_OBJECT (dec, "Found pre-skip of %u samples", dec->pre_skip);
|
dec->r128_gain = GST_READ_UINT16_LE (GST_BUFFER_DATA (buf) + 14);
|
||||||
|
dec->r128_gain_volume = gst_opus_dec_get_r128_volume (dec->r128_gain);
|
||||||
|
GST_INFO_OBJECT (dec,
|
||||||
|
"Found pre-skip of %u samples, R128 gain %d (volume %f)",
|
||||||
|
dec->pre_skip, dec->r128_gain, dec->r128_gain_volume);
|
||||||
|
|
||||||
return GST_FLOW_OK;
|
return GST_FLOW_OK;
|
||||||
}
|
}
|
||||||
|
@ -357,6 +385,23 @@ opus_dec_chain_parse_data (GstOpusDec * dec, GstBuffer * buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Apply gain */
|
||||||
|
/* Would be better off leaving this to a volume element, as this is
|
||||||
|
a naive conversion that does too many int/float conversions.
|
||||||
|
However, we don't have control over the pipeline...
|
||||||
|
So make it optional if the user program wants to use a volume,
|
||||||
|
but do it by default so the correct volume goes out by default */
|
||||||
|
if (dec->apply_gain && outbuf && dec->r128_gain) {
|
||||||
|
unsigned int i, nsamples = GST_BUFFER_SIZE (outbuf) / 2;
|
||||||
|
double volume = dec->r128_gain_volume;
|
||||||
|
gint16 *samples = (gint16 *) GST_BUFFER_DATA (outbuf);
|
||||||
|
GST_DEBUG_OBJECT (dec, "Applying gain: volume %f", volume);
|
||||||
|
for (i = 0; i < nsamples; ++i) {
|
||||||
|
int sample = (int) (samples[i] * volume + 0.5);
|
||||||
|
samples[i] = sample < -32768 ? -32768 : sample > 32767 ? 32767 : sample;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
|
res = gst_audio_decoder_finish_frame (GST_AUDIO_DECODER (dec), outbuf, 1);
|
||||||
|
|
||||||
if (res != GST_FLOW_OK)
|
if (res != GST_FLOW_OK)
|
||||||
|
@ -505,6 +550,9 @@ gst_opus_dec_get_property (GObject * object, guint prop_id, GValue * value,
|
||||||
case PROP_USE_INBAND_FEC:
|
case PROP_USE_INBAND_FEC:
|
||||||
g_value_set_boolean (value, dec->use_inband_fec);
|
g_value_set_boolean (value, dec->use_inband_fec);
|
||||||
break;
|
break;
|
||||||
|
case PROP_APPLY_GAIN:
|
||||||
|
g_value_set_boolean (value, dec->apply_gain);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -521,6 +569,9 @@ gst_opus_dec_set_property (GObject * object, guint prop_id,
|
||||||
case PROP_USE_INBAND_FEC:
|
case PROP_USE_INBAND_FEC:
|
||||||
dec->use_inband_fec = g_value_get_boolean (value);
|
dec->use_inband_fec = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_APPLY_GAIN:
|
||||||
|
dec->apply_gain = g_value_get_boolean (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -54,6 +54,10 @@ struct _GstOpusDec {
|
||||||
int sample_rate;
|
int sample_rate;
|
||||||
int n_channels;
|
int n_channels;
|
||||||
guint32 pre_skip;
|
guint32 pre_skip;
|
||||||
|
gint16 r128_gain;
|
||||||
|
|
||||||
|
gboolean apply_gain;
|
||||||
|
double r128_gain_volume;
|
||||||
|
|
||||||
gboolean use_inband_fec;
|
gboolean use_inband_fec;
|
||||||
GstBuffer *last_buffer;
|
GstBuffer *last_buffer;
|
||||||
|
|
Loading…
Reference in a new issue