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
--- /dev/null
+#include <QPainter>
+
+#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<mutex> 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);
+ }
+}
--- /dev/null
+// TODO: This isn't really an LRA meter right now (it ignores the range).
+
+#ifndef LRAMETER_H
+#define LRAMETER_H
+
+#include <QWidget>
+#include <QLabel>
+#include <QPaintEvent>
+
+#include <mutex>
+
+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<std::mutex> 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
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);
}
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,
output_channel[output].set_frame_ready_callback(callback);
}
- typedef std::function<void(float, float)> audio_level_callback_t;
+ typedef std::function<void(float, float, float, float, float)> audio_level_callback_t;
void set_audio_level_callback(audio_level_callback_t callback)
{
audio_level_callback = callback;
<item row="0" column="0">
<layout class="QVBoxLayout" name="vertical_layout" stretch="0,0,0">
<item>
- <layout class="QHBoxLayout" name="me_displays" stretch="0,0,0,0">
+ <layout class="QHBoxLayout" name="me_displays" stretch="0,0,0,0,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
</layout>
</item>
<item>
- <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0">
+ <layout class="QVBoxLayout" name="vu_meter_vertical_layout" stretch="1,0">
<property name="leftMargin">
<number>0</number>
</property>
+ <property name="bottomMargin">
+ <number>4</number>
+ </property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="bottomMargin">
</item>
</layout>
</item>
+ <item>
+ <layout class="QVBoxLayout" name="lra_vertical_layout" stretch="1,0">
+ <property name="spacing">
+ <number>3</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="LRAMeter" name="lra_meter" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>24</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="palette">
+ <palette>
+ <active>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>0</red>
+ <green>239</green>
+ <blue>219</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </active>
+ <inactive>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>255</red>
+ <green>255</green>
+ <blue>255</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>0</red>
+ <green>239</green>
+ <blue>219</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </inactive>
+ <disabled>
+ <colorrole role="Base">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>0</red>
+ <green>239</green>
+ <blue>219</blue>
+ </color>
+ </brush>
+ </colorrole>
+ <colorrole role="Window">
+ <brush brushstyle="SolidPattern">
+ <color alpha="255">
+ <red>0</red>
+ <green>239</green>
+ <blue>219</blue>
+ </color>
+ </brush>
+ </colorrole>
+ </disabled>
+ </palette>
+ </property>
+ <property name="autoFillBackground">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pushButton">
+ <property name="maximumSize">
+ <size>
+ <width>30</width>
+ <height>20</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>RST</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</item>
<item>
<header>vumeter.h</header>
<container>1</container>
</customwidget>
+ <customwidget>
+ <class>LRAMeter</class>
+ <extends>QWidget</extends>
+ <header>lrameter.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections/>
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));
}
}