From: Steinar H. Gunderson Date: Sat, 31 Oct 2015 01:41:10 +0000 (+0100) Subject: Add the beginnings of a loudness range meter. X-Git-Tag: 1.0.0~203 X-Git-Url: https://git.sesse.net/?a=commitdiff_plain;ds=sidebyside;h=0323944a99ea3c0e175f0f3d004fa42fecd02ece;p=nageru Add the beginnings of a loudness range meter. --- diff --git a/Makefile b/Makefile index b4a8a19..d8dd820 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler -lebur128 # Qt objects -OBJS=glwidget.o main.o mainwindow.o vumeter.o -OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o +OBJS=glwidget.o main.o mainwindow.o vumeter.o lrameter.o +OBJS += glwidget.moc.o mainwindow.moc.o vumeter.moc.o lrameter.moc.o # Mixer objects OBJS += h264encode.o mixer.o bmusb/bmusb.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o resampler.o httpd.o diff --git a/lrameter.cpp b/lrameter.cpp new file mode 100644 index 0000000..79a702d --- /dev/null +++ b/lrameter.cpp @@ -0,0 +1,87 @@ +#include + +#include "lrameter.h" + +using namespace std; + +namespace { + +int lufs_to_pos(float level_lu, int height) +{ + const float min_level = 9.0f; // y=0 is top of screen, so “min” is the loudest level. + const float max_level = -18.0f; + int y = lrintf(height * (level_lu - min_level) / (max_level - min_level)); + y = std::max(y, 0); + y = std::min(y, height - 1); + return y; +} + +} // namespace + +LRAMeter::LRAMeter(QWidget *parent) + : QWidget(parent) +{ +} + +void LRAMeter::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + const int margin = 5; + + painter.fillRect(0, 0, width(), height(), parentWidget()->palette().window()); + painter.fillRect(margin, 0, width() - 2 * margin, height(), Qt::black); + + // TODO: QLinearGradient is not gamma-correct; we might want to correct for that. + QLinearGradient on(0, 0, 0, height()); + on.setColorAt(0.0f, QColor(255, 0, 0)); + on.setColorAt(0.5f, QColor(255, 255, 0)); + on.setColorAt(1.0f, QColor(0, 255, 0)); + QColor off(80, 80, 80); + + float level_lufs; + { + unique_lock lock(level_mutex); + level_lufs = this->level_lufs; + } + + float level_lu = level_lufs + 23.0f; + int y = lufs_to_pos(level_lu, height()); + + // Draw bars colored up until the level, then gray from there. + for (int level = -18; level < 9; ++level) { + int min_y = lufs_to_pos(level + 1.0f, height()) + 1; + int max_y = lufs_to_pos(level, height()) - 1; + + if (y > max_y) { + painter.fillRect(margin, min_y, width() - 2 * margin, max_y - min_y, off); + } else if (y < min_y) { + painter.fillRect(margin, min_y, width() - 2 * margin, max_y - min_y, on); + } else { + painter.fillRect(margin, min_y, width() - 2 * margin, y - min_y, off); + painter.fillRect(margin, y, width() - 2 * margin, max_y - y, on); + } + } + + // Draw the target area (+/-1 LU is allowed EBU range). + int min_y = lufs_to_pos(1.0f, height()); + int max_y = lufs_to_pos(-1.0f, height()); + + // FIXME: This outlining isn't so pretty. + { + QPen pen(Qt::black); + pen.setWidth(5); + painter.setPen(pen); + painter.drawRect(2, min_y, width() - 5, max_y - min_y); + } + { + QPen pen; + if (level_lu >= -1.0f && level_lu <= 1.0f) { + pen.setColor(Qt::green); + } else { + pen.setColor(Qt::red); + } + pen.setWidth(3); + painter.setPen(pen); + painter.drawRect(2, min_y, width() - 5, max_y - min_y); + } +} diff --git a/lrameter.h b/lrameter.h new file mode 100644 index 0000000..ec84b83 --- /dev/null +++ b/lrameter.h @@ -0,0 +1,36 @@ +// TODO: This isn't really an LRA meter right now (it ignores the range). + +#ifndef LRAMETER_H +#define LRAMETER_H + +#include +#include +#include + +#include + +class LRAMeter : public QWidget +{ + Q_OBJECT + +public: + LRAMeter(QWidget *parent); + + void set_levels(float level_lufs, float range_low_lufs, float range_high_lufs) { + std::unique_lock lock(level_mutex); + this->level_lufs = level_lufs; + this->range_low_lufs = range_low_lufs; + this->range_high_lufs = range_high_lufs; + update(); + } + +private: + void paintEvent(QPaintEvent *event) override; + + std::mutex level_mutex; + float level_lufs = -HUGE_VAL; + float range_low_lufs = -HUGE_VAL; + float range_high_lufs = -HUGE_VAL; +}; + +#endif diff --git a/mainwindow.cpp b/mainwindow.cpp index 2888c83..ecdb131 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -94,8 +94,9 @@ void MainWindow::resizeEvent(QResizeEvent* event) void MainWindow::mixer_created(Mixer *mixer) { - mixer->set_audio_level_callback([this](float level_lufs, float peak_db){ + mixer->set_audio_level_callback([this](float level_lufs, float peak_db, float global_level_lufs, float range_low_lufs, float range_high_lufs){ ui->vu_meter->set_level(level_lufs); + ui->lra_meter->set_levels(global_level_lufs, range_low_lufs, range_high_lufs); char buf[256]; snprintf(buf, sizeof(buf), "%.1f", peak_db); diff --git a/mixer.cpp b/mixer.cpp index 45dee44..308f22c 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -368,11 +368,20 @@ void Mixer::thread_func() } if (audio_level_callback != nullptr) { - double loudness_s, peak_level_l, peak_level_r; + double loudness_s, loudness_i, peak_level_l, peak_level_r; + double lra; ebur128_loudness_shortterm(r128_state, &loudness_s); + ebur128_loudness_global(r128_state, &loudness_i); + ebur128_loudness_range(r128_state, &lra); ebur128_sample_peak(r128_state, 0, &peak_level_l); ebur128_sample_peak(r128_state, 1, &peak_level_r); - audio_level_callback(loudness_s, 20.0 * log10(max(peak_level_l, peak_level_r))); + + // FIXME: This is wrong. We need proper support from libebur128 for this. + double loudness_range_low = loudness_i - 0.5 * lra; + double loudness_range_high = loudness_i + 0.5 * lra; + + audio_level_callback(loudness_s, 20.0 * log10(max(peak_level_l, peak_level_r)), + loudness_i, loudness_range_low, loudness_range_high); } // If the first card is reporting a corrupted or otherwise dropped frame, diff --git a/mixer.h b/mixer.h index 7a0bfa3..498a2d1 100644 --- a/mixer.h +++ b/mixer.h @@ -81,7 +81,7 @@ public: output_channel[output].set_frame_ready_callback(callback); } - typedef std::function audio_level_callback_t; + typedef std::function audio_level_callback_t; void set_audio_level_callback(audio_level_callback_t callback) { audio_level_callback = callback; diff --git a/ui_mainwindow.ui b/ui_mainwindow.ui index 9518f67..6341867 100644 --- a/ui_mainwindow.ui +++ b/ui_mainwindow.ui @@ -27,7 +27,7 @@ - + @@ -138,10 +138,13 @@ - + 0 + + 4 + @@ -264,6 +267,119 @@ + + + + 3 + + + 0 + + + + + + + + 0 + 0 + + + + + 24 + 0 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 239 + 219 + + + + + + + + + 255 + 255 + 255 + + + + + + + 0 + 239 + 219 + + + + + + + + + 0 + 239 + 219 + + + + + + + 0 + 239 + 219 + + + + + + + + true + + + + + + + + + + 30 + 20 + + + + RST + + + false + + + + + @@ -384,6 +500,12 @@
vumeter.h
1 + + LRAMeter + QWidget +
lrameter.h
+ 1 +
diff --git a/vumeter.cpp b/vumeter.cpp index 5b1c852..7254698 100644 --- a/vumeter.cpp +++ b/vumeter.cpp @@ -38,7 +38,7 @@ void VUMeter::paintEvent(QPaintEvent *event) if (level == -1 || level == 0) { painter.fillRect(1, min_y, width() - 2, max_y - min_y, Qt::green); } else { - painter.fillRect(1, min_y, width() - 2, max_y - min_y, Qt::darkGray); + painter.fillRect(1, min_y, width() - 2, max_y - min_y, QColor(80, 80, 80)); } }