GstEnginePipeline: Detect if autoaudiosink has volume

Fixes #1037
This commit is contained in:
Jonas Kvinge 2022-12-04 08:37:33 +01:00
parent 6267edaa81
commit b2073df3c3
3 changed files with 76 additions and 26 deletions

View File

@ -61,6 +61,8 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
explicit GstEngine(TaskManager *task_manager, QObject *parent = nullptr); explicit GstEngine(TaskManager *task_manager, QObject *parent = nullptr);
~GstEngine() override; ~GstEngine() override;
static const char *kAutoSink;
bool Init() override; bool Init() override;
Engine::State state() const override; Engine::State state() const override;
void StartPreloading(const QUrl &stream_url, const QUrl &original_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override; void StartPreloading(const QUrl &stream_url, const QUrl &original_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
@ -145,7 +147,6 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
static QString GSTdiscovererErrorMessage(GstDiscovererResult result); static QString GSTdiscovererErrorMessage(GstDiscovererResult result);
private: private:
static const char *kAutoSink;
static const char *kALSASink; static const char *kALSASink;
static const char *kOpenALSASink; static const char *kOpenALSASink;
static const char *kOSSSink; static const char *kOSSSink;

View File

@ -102,12 +102,15 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
use_fudge_timer_(false), use_fudge_timer_(false),
pipeline_(nullptr), pipeline_(nullptr),
audiobin_(nullptr), audiobin_(nullptr),
audiosink_(nullptr),
audioqueue_(nullptr), audioqueue_(nullptr),
volume_(nullptr), volume_(nullptr),
volume_sw_(nullptr),
volume_fading_(nullptr), volume_fading_(nullptr),
audiopanorama_(nullptr), audiopanorama_(nullptr),
equalizer_(nullptr), equalizer_(nullptr),
equalizer_preamp_(nullptr), equalizer_preamp_(nullptr),
element_added_cb_id_(-1),
pad_added_cb_id_(-1), pad_added_cb_id_(-1),
notify_source_cb_id_(-1), notify_source_cb_id_(-1),
about_to_finish_cb_id_(-1), about_to_finish_cb_id_(-1),
@ -130,6 +133,10 @@ GstEnginePipeline::~GstEnginePipeline() {
fader_.reset(); fader_.reset();
} }
if (element_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(audiobin_), element_added_cb_id_);
}
if (pad_added_cb_id_ != -1) { if (pad_added_cb_id_ != -1) {
g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_); g_signal_handler_disconnect(G_OBJECT(pipeline_), pad_added_cb_id_);
} }
@ -260,8 +267,14 @@ bool GstEnginePipeline::InitFromUrl(const QByteArray &stream_url, const QUrl &or
if (!InitAudioBin(error)) return false; if (!InitAudioBin(error)) return false;
if (volume_) { if (volume_enabled_ && !volume_) {
notify_volume_cb_id_ = CHECKED_GCONNECT(G_OBJECT(volume_), "notify::volume", &VolumeCallback, this); if (output_ == GstEngine::kAutoSink) {
element_added_cb_id_ = CHECKED_GCONNECT(G_OBJECT(audiobin_), "deep-element-added", &ElementAddedCallback, this);
}
else {
qLog(Debug) << output_ << "does not have volume, using own volume.";
SetupVolume(volume_sw_);
}
} }
// Set playbin's sink to be our custom audio-sink. // Set playbin's sink to be our custom audio-sink.
@ -291,14 +304,14 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
if (!audiobin_) return false; if (!audiobin_) return false;
// Create the sink // Create the sink
GstElement *audiosink = CreateElement(output_, output_, audiobin_, error); audiosink_ = CreateElement(output_, output_, audiobin_, error);
if (!audiosink) { if (!audiosink_) {
gst_object_unref(GST_OBJECT(audiobin_)); gst_object_unref(GST_OBJECT(audiobin_));
return false; return false;
} }
if (device_.isValid()) { if (device_.isValid()) {
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink), "device")) { if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "device")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (device_.metaType().id()) { switch (device_.metaType().id()) {
#else #else
@ -312,7 +325,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
QString device = device_.toString(); QString device = device_.toString();
if (!device.isEmpty()) { if (!device.isEmpty()) {
qLog(Debug) << "Setting device" << device << "for" << output_; qLog(Debug) << "Setting device" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink), "device", device.toUtf8().constData(), nullptr); g_object_set(G_OBJECT(audiosink_), "device", device.toUtf8().constData(), nullptr);
} }
break; break;
} }
@ -324,7 +337,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
QByteArray device = device_.toByteArray(); QByteArray device = device_.toByteArray();
if (!device.isEmpty()) { if (!device.isEmpty()) {
qLog(Debug) << "Setting device" << device_ << "for" << output_; qLog(Debug) << "Setting device" << device_ << "for" << output_;
g_object_set(G_OBJECT(audiosink), "device", device.constData(), nullptr); g_object_set(G_OBJECT(audiosink_), "device", device.constData(), nullptr);
} }
break; break;
} }
@ -335,7 +348,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
#endif #endif
qint64 device = device_.toLongLong(); qint64 device = device_.toLongLong();
qLog(Debug) << "Setting device" << device << "for" << output_; qLog(Debug) << "Setting device" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink), "device", device, nullptr); g_object_set(G_OBJECT(audiosink_), "device", device, nullptr);
break; break;
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -345,7 +358,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
#endif #endif
int device = device_.toInt(); int device = device_.toInt();
qLog(Debug) << "Setting device" << device << "for" << output_; qLog(Debug) << "Setting device" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink), "device", device, nullptr); g_object_set(G_OBJECT(audiosink_), "device", device, nullptr);
break; break;
} }
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -355,7 +368,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
#endif #endif
QUuid device = device_.toUuid(); QUuid device = device_.toUuid();
qLog(Debug) << "Setting device" << device << "for" << output_; qLog(Debug) << "Setting device" << device << "for" << output_;
g_object_set(G_OBJECT(audiosink), "device", device, nullptr); g_object_set(G_OBJECT(audiosink_), "device", device, nullptr);
break; break;
} }
default: default:
@ -364,7 +377,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
} }
} }
else if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink), "port-pattern")) { else if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "port-pattern")) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
switch (device_.metaType().id()) { switch (device_.metaType().id()) {
#else #else
@ -378,7 +391,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
QString port_pattern = device_.toString(); QString port_pattern = device_.toString();
if (!port_pattern.isEmpty()) { if (!port_pattern.isEmpty()) {
qLog(Debug) << "Setting port pattern" << port_pattern << "for" << output_; qLog(Debug) << "Setting port pattern" << port_pattern << "for" << output_;
g_object_set(G_OBJECT(audiosink), "port-pattern", port_pattern.toUtf8().constData(), nullptr); g_object_set(G_OBJECT(audiosink_), "port-pattern", port_pattern.toUtf8().constData(), nullptr);
} }
break; break;
} }
@ -391,7 +404,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
QByteArray port_pattern = device_.toByteArray(); QByteArray port_pattern = device_.toByteArray();
if (!port_pattern.isEmpty()) { if (!port_pattern.isEmpty()) {
qLog(Debug) << "Setting port pattern" << port_pattern << "for" << output_; qLog(Debug) << "Setting port pattern" << port_pattern << "for" << output_;
g_object_set(G_OBJECT(audiosink), "port-pattern", port_pattern.constData(), nullptr); g_object_set(G_OBJECT(audiosink_), "port-pattern", port_pattern.constData(), nullptr);
} }
break; break;
} }
@ -404,9 +417,9 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
} }
if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink), "volume")) { if (g_object_class_find_property(G_OBJECT_GET_CLASS(audiosink_), "volume")) {
qLog(Debug) << output_ << "has volume element, enabling system volume synchronization."; qLog(Debug) << output_ << "has volume, enabling volume synchronization.";
volume_ = audiosink; SetupVolume(audiosink_);
} }
// Create all the other elements // Create all the other elements
@ -425,16 +438,14 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
return false; return false;
} }
// Create the volume elements if it's enabled. // Create the volume element if it's enabled.
GstElement *swvolume = nullptr;
if (volume_enabled_ && !volume_) { if (volume_enabled_ && !volume_) {
swvolume = CreateElement("volume", "volume_sw", audiobin_, error); volume_sw_ = CreateElement("volume", "volume_sw", audiobin_, error);
if (!swvolume) { if (!volume_sw_) {
gst_object_unref(GST_OBJECT(audiobin_)); gst_object_unref(GST_OBJECT(audiobin_));
audiobin_ = nullptr; audiobin_ = nullptr;
return false; return false;
} }
volume_ = swvolume;
} }
if (fading_enabled_) { if (fading_enabled_) {
@ -630,14 +641,14 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
} }
// Link software volume element if enabled. // Link software volume element if enabled.
if (volume_enabled_ && swvolume) { if (volume_enabled_ && volume_sw_) {
if (!gst_element_link(element_link, swvolume)) { if (!gst_element_link(element_link, volume_sw_)) {
gst_object_unref(GST_OBJECT(audiobin_)); gst_object_unref(GST_OBJECT(audiobin_));
audiobin_ = nullptr; audiobin_ = nullptr;
error = "gst_element_link() failed."; error = "gst_element_link() failed.";
return false; return false;
} }
element_link = swvolume; element_link = volume_sw_;
} }
// Link fading volume element if enabled. // Link fading volume element if enabled.
@ -675,7 +686,7 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
qLog(Debug) << "Setting channels to" << channels_; qLog(Debug) << "Setting channels to" << channels_;
gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr); gst_caps_set_simple(caps, "channels", G_TYPE_INT, channels_, nullptr);
} }
gst_element_link_filtered(element_link, audiosink, caps); gst_element_link_filtered(element_link, audiosink_, caps);
gst_caps_unref(caps); gst_caps_unref(caps);
} }
@ -702,6 +713,15 @@ bool GstEnginePipeline::InitAudioBin(QString &error) {
} }
void GstEnginePipeline::SetupVolume(GstElement *element) {
if (volume_) return;
volume_ = element;
notify_volume_cb_id_ = CHECKED_GCONNECT(G_OBJECT(element), "notify::volume", &VolumeCallback, this);
}
GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) { GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeInfo *info, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self); GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
@ -729,6 +749,30 @@ GstPadProbeReturn GstEnginePipeline::EventHandoffCallback(GstPad*, GstPadProbeIn
} }
void GstEnginePipeline::ElementAddedCallback(GstBin *bin, GstBin*, GstElement *element, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);
if (bin != GST_BIN(instance->audiobin_) || GST_ELEMENT(gst_element_get_parent(element)) != instance->audiosink_ || instance->volume_) return;
g_signal_handler_disconnect(G_OBJECT(instance->audiobin_), instance->element_added_cb_id_);
instance->element_added_cb_id_ = -1;
GstElement *volume = nullptr;
if (GST_IS_STREAM_VOLUME(element)) {
qLog(Debug) << instance->output_ << "has volume, enabling volume synchronization.";
volume = element;
}
else {
qLog(Debug) << instance->output_ << "does not have volume, using own volume.";
volume = instance->volume_sw_;
}
instance->SetupVolume(volume);
instance->SetVolume(instance->volume_percent_);
}
void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec*, gpointer self) { void GstEnginePipeline::SourceSetupCallback(GstPlayBin *bin, GParamSpec*, gpointer self) {
GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self); GstEnginePipeline *instance = reinterpret_cast<GstEnginePipeline*>(self);

View File

@ -143,9 +143,11 @@ class GstEnginePipeline : public QObject {
private: private:
GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const; GstElement *CreateElement(const QString &factory_name, const QString &name, GstElement *bin, QString &error) const;
bool InitAudioBin(QString &error); bool InitAudioBin(QString &error);
void SetupVolume(GstElement *element);
// Static callbacks. The GstEnginePipeline instance is passed in the last argument. // Static callbacks. The GstEnginePipeline instance is passed in the last argument.
static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer); static GstPadProbeReturn EventHandoffCallback(GstPad*, GstPadProbeInfo*, gpointer);
static void ElementAddedCallback(GstBin *bin, GstBin*, GstElement *element, gpointer self);
static void SourceSetupCallback(GstPlayBin*, GParamSpec *pspec, gpointer); static void SourceSetupCallback(GstPlayBin*, GParamSpec *pspec, gpointer);
static void VolumeCallback(GstElement*, GParamSpec*, gpointer self); static void VolumeCallback(GstElement*, GParamSpec*, gpointer self);
static void NewPadCallback(GstElement*, GstPad*, gpointer); static void NewPadCallback(GstElement*, GstPad*, gpointer);
@ -281,13 +283,16 @@ class GstEnginePipeline : public QObject {
GstElement *pipeline_; GstElement *pipeline_;
GstElement *audiobin_; GstElement *audiobin_;
GstElement *audiosink_;
GstElement *audioqueue_; GstElement *audioqueue_;
GstElement *volume_; GstElement *volume_;
GstElement *volume_sw_;
GstElement *volume_fading_; GstElement *volume_fading_;
GstElement *audiopanorama_; GstElement *audiopanorama_;
GstElement *equalizer_; GstElement *equalizer_;
GstElement *equalizer_preamp_; GstElement *equalizer_preamp_;
int element_added_cb_id_;
int pad_added_cb_id_; int pad_added_cb_id_;
int notify_source_cb_id_; int notify_source_cb_id_;
int about_to_finish_cb_id_; int about_to_finish_cb_id_;