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:
Vincent Penquerc'h 2011-11-23 13:22:12 +00:00
parent bfdda8e09a
commit 5c8812f58c
2 changed files with 57 additions and 2 deletions

View file

@ -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;

View file

@ -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;