Split the gstreamer pipeline - one path is converted to 16-bit int samples for the scope. The other is kept at float32 to preserve the bit depth of the audio.
Fixes issue 2114
This commit is contained in:
parent
5cbb86f247
commit
41a151840f
|
@ -183,33 +183,72 @@ bool GstEnginePipeline::Init() {
|
||||||
// uri decode bin -> audio bin
|
// uri decode bin -> audio bin
|
||||||
// The uri decode bin is a gstreamer builtin that automatically picks the
|
// The uri decode bin is a gstreamer builtin that automatically picks the
|
||||||
// right type of source and decoder for the URI.
|
// right type of source and decoder for the URI.
|
||||||
|
|
||||||
// The audio bin gets created here and contains:
|
// The audio bin gets created here and contains:
|
||||||
// queue -> audioconvert -> rgvolume -> rglimiter -> equalizer_preamp ->
|
// queue ! audioconvert ! <caps32>
|
||||||
// equalizer -> volume -> audioscale -> audioconvert -> audiosink
|
// ! ( rgvolume ! rglimiter ! audioconvert2 ) ! tee
|
||||||
|
// rgvolume and rglimiter are only created when replaygain is enabled.
|
||||||
|
|
||||||
|
// After the tee the pipeline splits. One split is converted to 16-bit int
|
||||||
|
// samples for the scope, the other is kept as float32 and sent to the
|
||||||
|
// speaker.
|
||||||
|
// tee1 ! probe_queue ! probe_converter ! <caps16> ! probe_sink
|
||||||
|
// tee2 ! audio_queue ! equalizer_preamp ! equalizer ! volume ! audioscale
|
||||||
|
// ! convert ! audiosink
|
||||||
|
|
||||||
// Audio bin
|
// Audio bin
|
||||||
audiobin_ = gst_bin_new("audiobin");
|
audiobin_ = gst_bin_new("audiobin");
|
||||||
gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
gst_bin_add(GST_BIN(pipeline_), audiobin_);
|
||||||
|
|
||||||
|
// Create the sink
|
||||||
if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_)))
|
if (!(audiosink_ = engine_->CreateElement(sink_, audiobin_)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (GstEngine::DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(sink_) && !device_.isEmpty())
|
if (GstEngine::DoesThisSinkSupportChangingTheOutputDeviceToAUserEditableString(sink_) && !device_.isEmpty())
|
||||||
g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(), NULL);
|
g_object_set(G_OBJECT(audiosink_), "device", device_.toUtf8().constData(), NULL);
|
||||||
|
|
||||||
if (!(queue_ = engine_->CreateElement("queue", audiobin_))) { return false; }
|
// Create all the other elements
|
||||||
if (!(equalizer_preamp_ = engine_->CreateElement("volume", audiobin_))) { return false; }
|
GstElement *tee, *probe_queue, *probe_converter, *probe_sink, *audio_queue,
|
||||||
if (!(equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_))) { return false; }
|
*convert;
|
||||||
if (!(audioconvert_ = engine_->CreateElement("audioconvert", audiobin_))) { return false; }
|
|
||||||
if (!(volume_ = engine_->CreateElement("volume", audiobin_))) { return false; }
|
queue_ = engine_->CreateElement("queue", audiobin_);
|
||||||
if (!(audioscale_ = engine_->CreateElement("audioresample", audiobin_))) { return false; }
|
audioconvert_ = engine_->CreateElement("audioconvert", audiobin_);
|
||||||
GstElement* scope_element = audioconvert_;
|
tee = engine_->CreateElement("tee", audiobin_);
|
||||||
|
|
||||||
|
probe_queue = engine_->CreateElement("queue", audiobin_);
|
||||||
|
probe_converter = engine_->CreateElement("audioconvert", audiobin_);
|
||||||
|
probe_sink = engine_->CreateElement("appsink", audiobin_);
|
||||||
|
|
||||||
|
audio_queue = engine_->CreateElement("queue", audiobin_);
|
||||||
|
equalizer_preamp_ = engine_->CreateElement("volume", audiobin_);
|
||||||
|
equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_);
|
||||||
|
volume_ = engine_->CreateElement("volume", audiobin_);
|
||||||
|
audioscale_ = engine_->CreateElement("audioresample", audiobin_);
|
||||||
|
convert = engine_->CreateElement("audioconvert", audiobin_);
|
||||||
|
|
||||||
|
if (!queue_ || !audioconvert_ || !tee || !probe_queue || !probe_converter ||
|
||||||
|
!probe_sink || !audio_queue || !equalizer_preamp_ || !equalizer_ ||
|
||||||
|
!volume_ || !audioscale_ || !convert) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the replaygain elements if it's enabled. event_probe is the
|
||||||
|
// audioconvert element we attach the probe to, which will change depending
|
||||||
|
// on whether replaygain is enabled. convert_sink is the element after the
|
||||||
|
// first audioconvert, which again will change.
|
||||||
|
GstElement* event_probe = audioconvert_;
|
||||||
|
GstElement* convert_sink = tee;
|
||||||
|
|
||||||
if (rg_enabled_) {
|
if (rg_enabled_) {
|
||||||
if (!(rgvolume_ = engine_->CreateElement("rgvolume", audiobin_))) { return false; }
|
rgvolume_ = engine_->CreateElement("rgvolume", audiobin_);
|
||||||
if (!(rglimiter_ = engine_->CreateElement("rglimiter", audiobin_))) { return false; }
|
rglimiter_ = engine_->CreateElement("rglimiter", audiobin_);
|
||||||
if (!(audioconvert2_ = engine_->CreateElement("audioconvert", audiobin_))) { return false; }
|
audioconvert2_ = engine_->CreateElement("audioconvert", audiobin_);
|
||||||
scope_element = audioconvert2_;
|
event_probe = audioconvert2_;
|
||||||
|
convert_sink = rgvolume_;
|
||||||
|
|
||||||
|
if (!rgvolume_ || !rglimiter_ || !audioconvert2_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Set replaygain settings
|
// Set replaygain settings
|
||||||
g_object_set(G_OBJECT(rgvolume_), "album-mode", rg_mode_, NULL);
|
g_object_set(G_OBJECT(rgvolume_), "album-mode", rg_mode_, NULL);
|
||||||
|
@ -217,18 +256,19 @@ bool GstEnginePipeline::Init() {
|
||||||
g_object_set(G_OBJECT(rglimiter_), "enabled", int(rg_compression_), NULL);
|
g_object_set(G_OBJECT(rglimiter_), "enabled", int(rg_compression_), NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a pad on the outside of the audiobin and connect it to the pad of
|
||||||
|
// the first element.
|
||||||
GstPad* pad = gst_element_get_pad(queue_, "sink");
|
GstPad* pad = gst_element_get_pad(queue_, "sink");
|
||||||
gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad));
|
gst_element_add_pad(audiobin_, gst_ghost_pad_new("sink", pad));
|
||||||
gst_object_unref(pad);
|
gst_object_unref(pad);
|
||||||
|
|
||||||
// Add a data probe on the src pad of the audioconvert element for our scope.
|
// Add a data probe on the src pad of the audioconvert element for our scope.
|
||||||
// We do it here because we want pre-equalized and pre-volume samples
|
// We do it here because we want pre-equalized and pre-volume samples
|
||||||
// so that our visualization are not affected by them
|
// so that our visualization are not be affected by them.
|
||||||
pad = gst_element_get_pad(scope_element, "src");
|
pad = gst_element_get_pad(event_probe, "src");
|
||||||
gst_pad_add_buffer_probe(pad, G_CALLBACK(HandoffCallback), this);
|
|
||||||
gst_pad_add_event_probe(pad, G_CALLBACK(EventHandoffCallback), this);
|
gst_pad_add_event_probe(pad, G_CALLBACK(EventHandoffCallback), this);
|
||||||
gst_object_unref (pad);
|
gst_object_unref(pad);
|
||||||
|
|
||||||
// Set the equalizer bands
|
// Set the equalizer bands
|
||||||
g_object_set(G_OBJECT(equalizer_), "num-bands", 10, NULL);
|
g_object_set(G_OBJECT(equalizer_), "num-bands", 10, NULL);
|
||||||
|
|
||||||
|
@ -251,23 +291,40 @@ bool GstEnginePipeline::Init() {
|
||||||
// only affects network sources.
|
// only affects network sources.
|
||||||
g_object_set(G_OBJECT(queue_), "max-size-time", buffer_duration_nanosec_, NULL);
|
g_object_set(G_OBJECT(queue_), "max-size-time", buffer_duration_nanosec_, NULL);
|
||||||
|
|
||||||
// Ensure we get the right type out of audioconvert for our scope
|
gst_element_link(queue_, audioconvert_);
|
||||||
GstCaps* caps = gst_caps_new_simple ("audio/x-raw-int",
|
|
||||||
|
// Create the caps to put in each path in the tee. The scope path gets 16-bit
|
||||||
|
// ints and the audiosink path gets float32.
|
||||||
|
GstCaps* caps16 = gst_caps_new_simple ("audio/x-raw-int",
|
||||||
"width", G_TYPE_INT, 16,
|
"width", G_TYPE_INT, 16,
|
||||||
"signed", G_TYPE_BOOLEAN, true,
|
"signed", G_TYPE_BOOLEAN, true,
|
||||||
NULL);
|
NULL);
|
||||||
gst_element_link_filtered(scope_element, equalizer_preamp_, caps);
|
GstCaps* caps32 = gst_caps_new_simple ("audio/x-raw-float",
|
||||||
gst_caps_unref(caps);
|
"width", G_TYPE_INT, 32,
|
||||||
|
NULL);
|
||||||
|
|
||||||
// Add an extra audioconvert at the end as osxaudiosink supports only one format.
|
// Link the elements with special caps
|
||||||
GstElement* convert = engine_->CreateElement("audioconvert", audiobin_);
|
gst_element_link_filtered(probe_converter, probe_sink, caps16);
|
||||||
if (!convert) { return false; }
|
gst_element_link_filtered(audioconvert_, convert_sink, caps32);
|
||||||
|
gst_caps_unref(caps16);
|
||||||
|
gst_caps_unref(caps32);
|
||||||
|
|
||||||
gst_element_link(queue_, audioconvert_);
|
// Link the outputs of tee to the queues on each path.
|
||||||
if (rg_enabled_)
|
gst_pad_link(gst_element_get_request_pad(tee, "src%d"), gst_element_get_pad(probe_queue, "sink"));
|
||||||
gst_element_link_many(audioconvert_, rgvolume_, rglimiter_, audioconvert2_, NULL);
|
gst_pad_link(gst_element_get_request_pad(tee, "src%d"), gst_element_get_pad(audio_queue, "sink"));
|
||||||
gst_element_link_many(equalizer_preamp_, equalizer_, volume_, audioscale_, convert, audiosink_, NULL);
|
|
||||||
|
|
||||||
|
// Link replaygain elements if enabled.
|
||||||
|
if (rg_enabled_) {
|
||||||
|
gst_element_link_many(rgvolume_, rglimiter_, audioconvert2_, tee, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link everything else.
|
||||||
|
gst_element_link(probe_queue, probe_converter);
|
||||||
|
gst_element_link_many(audio_queue, equalizer_preamp_, equalizer_, volume_,
|
||||||
|
audioscale_, convert, audiosink_, NULL);
|
||||||
|
|
||||||
|
// Add probes and handlers.
|
||||||
|
gst_pad_add_buffer_probe(gst_element_get_pad(probe_converter, "src"), G_CALLBACK(HandoffCallback), this);
|
||||||
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this);
|
gst_bus_set_sync_handler(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallbackSync, this);
|
||||||
bus_cb_id_ = gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this);
|
bus_cb_id_ = gst_bus_add_watch(gst_pipeline_get_bus(GST_PIPELINE(pipeline_)), BusCallback, this);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -231,10 +231,7 @@ class GstEnginePipeline : public QObject {
|
||||||
GstElement* uridecodebin_;
|
GstElement* uridecodebin_;
|
||||||
GstElement* audiobin_;
|
GstElement* audiobin_;
|
||||||
|
|
||||||
// Elements in the audiobin
|
// Elements in the audiobin. See comments in Init()'s definition.
|
||||||
// queue ! audioconvert ! rgvolume ! rglimiter ! audioconvert !
|
|
||||||
// equalizer_preamp ! equalizer ! volume ! audioresample ! audioconvert !
|
|
||||||
// audiosink
|
|
||||||
GstElement* queue_;
|
GstElement* queue_;
|
||||||
GstElement* audioconvert_;
|
GstElement* audioconvert_;
|
||||||
GstElement* rgvolume_;
|
GstElement* rgvolume_;
|
||||||
|
|
Loading…
Reference in New Issue