Engine: pipe-in the EBU R 128 loudness normalization gain stuff
The idea is that Integrated Loudness is an integral part of the song, much like knowing it's beginning / ending in the file, and we must handle it the exact same way, and pipe it through all the way. At the same time, `EngineBase` knows Target Level (from settings), and these two combined tell us the Gain needed to normalize the Loudness of the particular Song (`EngineBase::Load()` does that). So the actual backend only needs to handle the Volume. We don't currently support changing Target Level on the fly. We don't currently support changing Loudness-normalizing Gain on the fly. This does not handle the case when the song is loaded from URL and thus the EBU R 128 measures, that exist, are not nessesairly correct.
This commit is contained in:
parent
40ef3191fc
commit
13d6cf201f
|
@ -341,7 +341,7 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||||
|
|
||||||
if (is_current) {
|
if (is_current) {
|
||||||
qLog(Debug) << "Playing song" << item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
|
qLog(Debug) << "Playing song" << item->Metadata().title() << result.stream_url_ << "position" << play_offset_nanosec_;
|
||||||
engine_->Play(result.media_url_, result.stream_url_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_);
|
engine_->Play(result.media_url_, result.stream_url_, stream_change_type_, song.has_cue(), song.beginning_nanosec(), song.end_nanosec(), play_offset_nanosec_, song.ebur128_integrated_loudness_lufs());
|
||||||
current_item_ = item;
|
current_item_ = item;
|
||||||
play_offset_nanosec_ = 0;
|
play_offset_nanosec_ = 0;
|
||||||
}
|
}
|
||||||
|
@ -731,7 +731,7 @@ void Player::PlayAt(const int index, const quint64 offset_nanosec, EngineBase::T
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec;
|
qLog(Debug) << "Playing song" << current_item_->Metadata().title() << url << "position" << offset_nanosec;
|
||||||
engine_->Play(current_item_->Url(), url, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec);
|
engine_->Play(current_item_->Url(), url, change, current_item_->Metadata().has_cue(), current_item_->effective_beginning_nanosec(), current_item_->effective_end_nanosec(), offset_nanosec, current_item_->effective_ebur128_integrated_loudness_lufs());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ EngineBase::EngineBase(QObject *parent)
|
||||||
volume_(100),
|
volume_(100),
|
||||||
beginning_nanosec_(0),
|
beginning_nanosec_(0),
|
||||||
end_nanosec_(0),
|
end_nanosec_(0),
|
||||||
|
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||||
scope_(kScopeSize),
|
scope_(kScopeSize),
|
||||||
buffering_(false),
|
buffering_(false),
|
||||||
equalizer_enabled_(false),
|
equalizer_enabled_(false),
|
||||||
|
@ -51,6 +52,8 @@ EngineBase::EngineBase(QObject *parent)
|
||||||
rg_preamp_(0.0),
|
rg_preamp_(0.0),
|
||||||
rg_fallbackgain_(0.0),
|
rg_fallbackgain_(0.0),
|
||||||
rg_compression_(true),
|
rg_compression_(true),
|
||||||
|
ebur128_loudness_normalization_(false),
|
||||||
|
ebur128_target_level_lufs_(-23.0),
|
||||||
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
|
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
|
||||||
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
|
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
|
||||||
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
|
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
|
||||||
|
@ -104,7 +107,7 @@ QString EngineBase::Description(const Type type) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec) {
|
bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
Q_UNUSED(force_stop_at_end);
|
Q_UNUSED(force_stop_at_end);
|
||||||
|
|
||||||
|
@ -113,15 +116,27 @@ bool EngineBase::Load(const QUrl &media_url, const QUrl &stream_url, const Track
|
||||||
beginning_nanosec_ = beginning_nanosec;
|
beginning_nanosec_ = beginning_nanosec;
|
||||||
end_nanosec_ = end_nanosec;
|
end_nanosec_ = end_nanosec;
|
||||||
|
|
||||||
|
ebur128_loudness_normalizing_gain_db_ = 0.0;
|
||||||
|
if (ebur128_loudness_normalization_ && ebur128_integrated_loudness_lufs) {
|
||||||
|
auto computeGain_dB = [](double source_dB, double target_dB) {
|
||||||
|
// Let's suppose the `source_dB` is -12 dB, while `target_dB` is -23 dB.
|
||||||
|
// In that case, we'd need to apply -11 dB of gain, which is computed as:
|
||||||
|
// -12 dB + x dB = -23 dB --> x dB = -23 dB - (-12 dB)
|
||||||
|
return target_dB - source_dB;
|
||||||
|
};
|
||||||
|
|
||||||
|
ebur128_loudness_normalizing_gain_db_ = computeGain_dB(*ebur128_integrated_loudness_lufs, ebur128_target_level_lufs_);
|
||||||
|
}
|
||||||
|
|
||||||
about_to_end_emitted_ = false;
|
about_to_end_emitted_ = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec) {
|
bool EngineBase::Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec)) {
|
if (!Load(media_url, stream_url, flags, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,6 +182,9 @@ void EngineBase::ReloadSettings() {
|
||||||
rg_fallbackgain_ = s.value("rgfallbackgain", 0.0).toDouble();
|
rg_fallbackgain_ = s.value("rgfallbackgain", 0.0).toDouble();
|
||||||
rg_compression_ = s.value("rgcompression", true).toBool();
|
rg_compression_ = s.value("rgcompression", true).toBool();
|
||||||
|
|
||||||
|
ebur128_loudness_normalization_ = s.value("ebur128_loudness_normalization", false).toBool();
|
||||||
|
ebur128_target_level_lufs_ = s.value("ebur128_target_level_lufs", -23.0).toDouble();
|
||||||
|
|
||||||
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
|
fadeout_enabled_ = s.value("FadeoutEnabled", false).toBool();
|
||||||
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
|
crossfade_enabled_ = s.value("CrossfadeEnabled", false).toBool();
|
||||||
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
|
autocrossfade_enabled_ = s.value("AutoCrossfadeEnabled", false).toBool();
|
||||||
|
|
|
@ -104,7 +104,7 @@ class EngineBase : public QObject {
|
||||||
virtual bool Init() = 0;
|
virtual bool Init() = 0;
|
||||||
virtual State state() const = 0;
|
virtual State state() const = 0;
|
||||||
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
|
virtual void StartPreloading(const QUrl&, const QUrl&, const bool, const qint64, const qint64) {}
|
||||||
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec);
|
virtual bool Load(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||||
virtual bool Play(const quint64 offset_nanosec) = 0;
|
virtual bool Play(const quint64 offset_nanosec) = 0;
|
||||||
virtual void Stop(const bool stop_after = false) = 0;
|
virtual void Stop(const bool stop_after = false) = 0;
|
||||||
virtual void Pause() = 0;
|
virtual void Pause() = 0;
|
||||||
|
@ -132,7 +132,7 @@ class EngineBase : public QObject {
|
||||||
|
|
||||||
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
// Plays a media stream represented with the URL 'u' from the given 'beginning' to the given 'end' (usually from 0 to a song's length).
|
||||||
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
// Both markers should be passed in nanoseconds. 'end' can be negative, indicating that the real length of 'u' stream is unknown.
|
||||||
bool Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec);
|
bool Play(const QUrl &media_url, const QUrl &stream_url, const TrackChangeFlags flags, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const quint64 offset_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs);
|
||||||
void SetVolume(const uint volume);
|
void SetVolume(const uint volume);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -194,6 +194,7 @@ class EngineBase : public QObject {
|
||||||
qint64 end_nanosec_;
|
qint64 end_nanosec_;
|
||||||
QUrl media_url_;
|
QUrl media_url_;
|
||||||
QUrl stream_url_;
|
QUrl stream_url_;
|
||||||
|
double ebur128_loudness_normalizing_gain_db_;
|
||||||
Scope scope_;
|
Scope scope_;
|
||||||
bool buffering_;
|
bool buffering_;
|
||||||
bool equalizer_enabled_;
|
bool equalizer_enabled_;
|
||||||
|
@ -209,6 +210,10 @@ class EngineBase : public QObject {
|
||||||
double rg_fallbackgain_;
|
double rg_fallbackgain_;
|
||||||
bool rg_compression_;
|
bool rg_compression_;
|
||||||
|
|
||||||
|
// EBU R 128 Loudness Normalization
|
||||||
|
bool ebur128_loudness_normalization_;
|
||||||
|
double ebur128_target_level_lufs_;
|
||||||
|
|
||||||
// Buffering
|
// Buffering
|
||||||
quint64 buffer_duration_nanosec_;
|
quint64 buffer_duration_nanosec_;
|
||||||
double buffer_low_watermark_;
|
double buffer_low_watermark_;
|
||||||
|
|
|
@ -176,11 +176,11 @@ void GstEngine::StartPreloading(const QUrl &media_url, const QUrl &stream_url, c
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec) {
|
bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
EnsureInitialized();
|
EnsureInitialized();
|
||||||
|
|
||||||
EngineBase::Load(stream_url, media_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
|
EngineBase::Load(stream_url, media_url, change, force_stop_at_end, beginning_nanosec, end_nanosec, ebur128_integrated_loudness_lufs);
|
||||||
|
|
||||||
const QByteArray gst_url = FixupUrl(stream_url);
|
const QByteArray gst_url = FixupUrl(stream_url);
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &stream_url, const Engine
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0);
|
std::shared_ptr<GstEnginePipeline> pipeline = CreatePipeline(media_url, stream_url, gst_url, force_stop_at_end ? end_nanosec : 0, ebur128_loudness_normalizing_gain_db_);
|
||||||
if (!pipeline) return false;
|
if (!pipeline) return false;
|
||||||
|
|
||||||
if (crossfade) StartFadeout();
|
if (crossfade) StartFadeout();
|
||||||
|
@ -648,7 +648,7 @@ void GstEngine::PlayDone(const GstStateChangeReturn ret, const quint64 offset_na
|
||||||
const QByteArray redirect_url = current_pipeline_->redirect_url();
|
const QByteArray redirect_url = current_pipeline_->redirect_url();
|
||||||
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) {
|
if (!redirect_url.isEmpty() && redirect_url != current_pipeline_->gst_url()) {
|
||||||
qLog(Info) << "Redirecting to" << redirect_url;
|
qLog(Info) << "Redirecting to" << redirect_url;
|
||||||
current_pipeline_ = CreatePipeline(current_pipeline_->media_url(), current_pipeline_->stream_url(), redirect_url, end_nanosec_);
|
current_pipeline_ = CreatePipeline(current_pipeline_->media_url(), current_pipeline_->stream_url(), redirect_url, end_nanosec_, current_pipeline_->ebur128_loudness_normalizing_gain_db());
|
||||||
Play(offset_nanosec);
|
Play(offset_nanosec);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -788,6 +788,7 @@ std::shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
||||||
ret->set_stereo_balancer_enabled(stereo_balancer_enabled_);
|
ret->set_stereo_balancer_enabled(stereo_balancer_enabled_);
|
||||||
ret->set_equalizer_enabled(equalizer_enabled_);
|
ret->set_equalizer_enabled(equalizer_enabled_);
|
||||||
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_fallbackgain_, rg_compression_);
|
ret->set_replaygain(rg_enabled_, rg_mode_, rg_preamp_, rg_fallbackgain_, rg_compression_);
|
||||||
|
ret->set_ebur128_loudness_normalization(ebur128_loudness_normalization_);
|
||||||
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
ret->set_buffer_duration_nanosec(buffer_duration_nanosec_);
|
||||||
ret->set_buffer_low_watermark(buffer_low_watermark_);
|
ret->set_buffer_low_watermark(buffer_low_watermark_);
|
||||||
ret->set_buffer_high_watermark(buffer_high_watermark_);
|
ret->set_buffer_high_watermark(buffer_high_watermark_);
|
||||||
|
@ -815,11 +816,11 @@ std::shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec) {
|
std::shared_ptr<GstEnginePipeline> GstEngine::CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db) {
|
||||||
|
|
||||||
std::shared_ptr<GstEnginePipeline> ret = CreatePipeline();
|
std::shared_ptr<GstEnginePipeline> ret = CreatePipeline();
|
||||||
QString error;
|
QString error;
|
||||||
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, error)) {
|
if (!ret->InitFromUrl(media_url, stream_url, gst_url, end_nanosec, ebur128_loudness_normalizing_gain_db, error)) {
|
||||||
ret.reset();
|
ret.reset();
|
||||||
emit Error(error);
|
emit Error(error);
|
||||||
emit StateChanged(EngineBase::State::Error);
|
emit StateChanged(EngineBase::State::Error);
|
||||||
|
|
|
@ -61,7 +61,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
||||||
bool Init() override;
|
bool Init() override;
|
||||||
EngineBase::State state() const override;
|
EngineBase::State state() const override;
|
||||||
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
|
void StartPreloading(const QUrl &media_url, const QUrl &stream_url, const bool force_stop_at_end, const qint64 beginning_nanosec, const qint64 end_nanosec) override;
|
||||||
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec) override;
|
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
||||||
bool Play(const quint64 offset_nanosec) override;
|
bool Play(const quint64 offset_nanosec) override;
|
||||||
void Stop(const bool stop_after = false) override;
|
void Stop(const bool stop_after = false) override;
|
||||||
void Pause() override;
|
void Pause() override;
|
||||||
|
@ -132,7 +132,7 @@ class GstEngine : public EngineBase, public GstBufferConsumer {
|
||||||
void StopTimers();
|
void StopTimers();
|
||||||
|
|
||||||
std::shared_ptr<GstEnginePipeline> CreatePipeline();
|
std::shared_ptr<GstEnginePipeline> CreatePipeline();
|
||||||
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec);
|
std::shared_ptr<GstEnginePipeline> CreatePipeline(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db);
|
||||||
|
|
||||||
void UpdateScope(int chunk_length);
|
void UpdateScope(int chunk_length);
|
||||||
|
|
||||||
|
|
|
@ -78,6 +78,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||||
rg_preamp_(0.0),
|
rg_preamp_(0.0),
|
||||||
rg_fallbackgain_(0.0),
|
rg_fallbackgain_(0.0),
|
||||||
rg_compression_(true),
|
rg_compression_(true),
|
||||||
|
ebur128_loudness_normalization_(false),
|
||||||
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
|
buffer_duration_nanosec_(BackendSettingsPage::kDefaultBufferDuration * kNsecPerMsec),
|
||||||
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
|
buffer_low_watermark_(BackendSettingsPage::kDefaultBufferLowWatermark),
|
||||||
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
|
buffer_high_watermark_(BackendSettingsPage::kDefaultBufferHighWatermark),
|
||||||
|
@ -99,6 +100,7 @@ GstEnginePipeline::GstEnginePipeline(QObject *parent)
|
||||||
pending_seek_nanosec_(-1),
|
pending_seek_nanosec_(-1),
|
||||||
last_known_position_ns_(0),
|
last_known_position_ns_(0),
|
||||||
next_uri_set_(false),
|
next_uri_set_(false),
|
||||||
|
ebur128_loudness_normalizing_gain_db_(0.0),
|
||||||
volume_set_(false),
|
volume_set_(false),
|
||||||
volume_internal_(-1.0),
|
volume_internal_(-1.0),
|
||||||
volume_percent_(100),
|
volume_percent_(100),
|
||||||
|
@ -237,6 +239,12 @@ void GstEnginePipeline::set_replaygain(const bool enabled, const int mode, const
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GstEnginePipeline::set_ebur128_loudness_normalization(const bool enabled) {
|
||||||
|
|
||||||
|
ebur128_loudness_normalization_ = enabled;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void GstEnginePipeline::set_buffer_duration_nanosec(const quint64 buffer_duration_nanosec) {
|
void GstEnginePipeline::set_buffer_duration_nanosec(const quint64 buffer_duration_nanosec) {
|
||||||
buffer_duration_nanosec_ = buffer_duration_nanosec;
|
buffer_duration_nanosec_ = buffer_duration_nanosec;
|
||||||
}
|
}
|
||||||
|
@ -289,11 +297,12 @@ GstElement *GstEnginePipeline::CreateElement(const QString &factory_name, const
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, QString &error) {
|
bool GstEnginePipeline::InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error) {
|
||||||
|
|
||||||
media_url_ = media_url;
|
media_url_ = media_url;
|
||||||
stream_url_ = stream_url;
|
stream_url_ = stream_url;
|
||||||
gst_url_ = gst_url;
|
gst_url_ = gst_url;
|
||||||
|
ebur128_loudness_normalizing_gain_db_ = ebur128_loudness_normalizing_gain_db;
|
||||||
end_offset_nanosec_ = end_nanosec;
|
end_offset_nanosec_ = end_nanosec;
|
||||||
|
|
||||||
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
|
guint version_major = 0, version_minor = 0, version_micro = 0, version_nano = 0;
|
||||||
|
|
|
@ -66,6 +66,7 @@ class GstEnginePipeline : public QObject {
|
||||||
void set_stereo_balancer_enabled(const bool enabled);
|
void set_stereo_balancer_enabled(const bool enabled);
|
||||||
void set_equalizer_enabled(const bool enabled);
|
void set_equalizer_enabled(const bool enabled);
|
||||||
void set_replaygain(const bool enabled, const int mode, const double preamp, const double fallbackgain, const bool compression);
|
void set_replaygain(const bool enabled, const int mode, const double preamp, const double fallbackgain, const bool compression);
|
||||||
|
void set_ebur128_loudness_normalization(const bool enabled);
|
||||||
void set_buffer_duration_nanosec(const quint64 duration_nanosec);
|
void set_buffer_duration_nanosec(const quint64 duration_nanosec);
|
||||||
void set_buffer_low_watermark(const double value);
|
void set_buffer_low_watermark(const double value);
|
||||||
void set_buffer_high_watermark(const double value);
|
void set_buffer_high_watermark(const double value);
|
||||||
|
@ -76,7 +77,7 @@ class GstEnginePipeline : public QObject {
|
||||||
void set_fading_enabled(const bool enabled);
|
void set_fading_enabled(const bool enabled);
|
||||||
|
|
||||||
// Creates the pipeline, returns false on error
|
// Creates the pipeline, returns false on error
|
||||||
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, QString &error);
|
bool InitFromUrl(const QUrl &media_url, const QUrl &stream_url, const QByteArray &gst_url, const qint64 end_nanosec, const double ebur128_loudness_normalizing_gain_db, QString &error);
|
||||||
|
|
||||||
// GstBufferConsumers get fed audio data. Thread-safe.
|
// GstBufferConsumers get fed audio data. Thread-safe.
|
||||||
void AddBufferConsumer(GstBufferConsumer *consumer);
|
void AddBufferConsumer(GstBufferConsumer *consumer);
|
||||||
|
@ -102,6 +103,7 @@ class GstEnginePipeline : public QObject {
|
||||||
// Get information about the music playback
|
// Get information about the music playback
|
||||||
QUrl media_url() const { return media_url_; }
|
QUrl media_url() const { return media_url_; }
|
||||||
QUrl stream_url() const { return stream_url_; }
|
QUrl stream_url() const { return stream_url_; }
|
||||||
|
double ebur128_loudness_normalizing_gain_db() const { return ebur128_loudness_normalizing_gain_db_; }
|
||||||
QByteArray gst_url() const { return gst_url_; }
|
QByteArray gst_url() const { return gst_url_; }
|
||||||
QUrl next_media_url() const { return next_media_url_; }
|
QUrl next_media_url() const { return next_media_url_; }
|
||||||
QUrl next_stream_url() const { return next_stream_url_; }
|
QUrl next_stream_url() const { return next_stream_url_; }
|
||||||
|
@ -215,6 +217,9 @@ class GstEnginePipeline : public QObject {
|
||||||
double rg_fallbackgain_;
|
double rg_fallbackgain_;
|
||||||
bool rg_compression_;
|
bool rg_compression_;
|
||||||
|
|
||||||
|
// EBU R 128 Loudness Normalization
|
||||||
|
bool ebur128_loudness_normalization_;
|
||||||
|
|
||||||
// Buffering
|
// Buffering
|
||||||
quint64 buffer_duration_nanosec_;
|
quint64 buffer_duration_nanosec_;
|
||||||
double buffer_low_watermark_;
|
double buffer_low_watermark_;
|
||||||
|
@ -282,6 +287,7 @@ class GstEnginePipeline : public QObject {
|
||||||
// Complete the transition to the next song when it starts playing
|
// Complete the transition to the next song when it starts playing
|
||||||
bool next_uri_set_;
|
bool next_uri_set_;
|
||||||
|
|
||||||
|
double ebur128_loudness_normalizing_gain_db_;
|
||||||
bool volume_set_;
|
bool volume_set_;
|
||||||
gdouble volume_internal_;
|
gdouble volume_internal_;
|
||||||
uint volume_percent_;
|
uint volume_percent_;
|
||||||
|
|
|
@ -98,9 +98,12 @@ bool VLCEngine::Init() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VLCEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec) {
|
bool VLCEngine::Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) {
|
||||||
|
|
||||||
|
// FIXME: why is this not calling `EngineBase::Load()`?
|
||||||
|
|
||||||
Q_UNUSED(media_url);
|
Q_UNUSED(media_url);
|
||||||
|
Q_UNUSED(ebur128_integrated_loudness_lufs);
|
||||||
Q_UNUSED(change);
|
Q_UNUSED(change);
|
||||||
Q_UNUSED(force_stop_at_end);
|
Q_UNUSED(force_stop_at_end);
|
||||||
Q_UNUSED(beginning_nanosec);
|
Q_UNUSED(beginning_nanosec);
|
||||||
|
|
|
@ -47,7 +47,7 @@ class VLCEngine : public EngineBase {
|
||||||
Type type() const override { return Type::VLC; }
|
Type type() const override { return Type::VLC; }
|
||||||
bool Init() override;
|
bool Init() override;
|
||||||
EngineBase::State state() const override { return state_; }
|
EngineBase::State state() const override { return state_; }
|
||||||
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec) override;
|
bool Load(const QUrl &media_url, const QUrl &stream_url, const EngineBase::TrackChangeFlags change, const bool force_stop_at_end, const quint64 beginning_nanosec, const qint64 end_nanosec, const std::optional<double> ebur128_integrated_loudness_lufs) override;
|
||||||
bool Play(const quint64 offset_nanosec) override;
|
bool Play(const quint64 offset_nanosec) override;
|
||||||
void Stop(const bool stop_after = false) override;
|
void Stop(const bool stop_after = false) override;
|
||||||
void Pause() override;
|
void Pause() override;
|
||||||
|
|
|
@ -87,6 +87,8 @@ class PlaylistItem : public std::enable_shared_from_this<PlaylistItem> {
|
||||||
Song StreamMetadata() { return HasTemporaryMetadata() ? temp_metadata_ : Metadata(); }
|
Song StreamMetadata() { return HasTemporaryMetadata() ? temp_metadata_ : Metadata(); }
|
||||||
QUrl StreamUrl() const { return HasTemporaryMetadata() && temp_metadata_.effective_stream_url().isValid() ? temp_metadata_.effective_stream_url() : Url(); }
|
QUrl StreamUrl() const { return HasTemporaryMetadata() && temp_metadata_.effective_stream_url().isValid() ? temp_metadata_.effective_stream_url() : Url(); }
|
||||||
|
|
||||||
|
std::optional<double> effective_ebur128_integrated_loudness_lufs() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() ? temp_metadata_.ebur128_integrated_loudness_lufs() : Metadata().ebur128_integrated_loudness_lufs(); }
|
||||||
|
|
||||||
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
|
qint64 effective_beginning_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.beginning_nanosec() != -1 ? temp_metadata_.beginning_nanosec() : Metadata().beginning_nanosec(); }
|
||||||
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
|
qint64 effective_end_nanosec() const { return HasTemporaryMetadata() && temp_metadata_.is_valid() && temp_metadata_.end_nanosec() != -1 ? temp_metadata_.end_nanosec() : Metadata().end_nanosec(); }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue