CXX=g++
PKG_MODULES = Qt5Core Qt5Gui Qt5Widgets Qt5OpenGLExtensions Qt5OpenGL libusb-1.0 movit lua5.2 libmicrohttpd
CXXFLAGS := -O2 -march=native -g -std=gnu++11 -Wall -Wno-deprecated-declarations -Werror -fPIC $(shell pkg-config --cflags $(PKG_MODULES)) -pthread -DMOVIT_SHADER_DIR=\"$(shell pkg-config --variable=shaderdir movit)\"
-LDFLAGS=$(shell pkg-config --libs $(PKG_MODULES)) -lEGL -lGL -pthread -lva -lva-drm -lva-x11 -lX11 -lavformat -lavcodec -lavutil -lzita-resampler
+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 window.o
-OBJS += glwidget.moc.o mainwindow.moc.o window.moc.o
+OBJS=glwidget.o main.o mainwindow.o window.o vumeter.o
+OBJS += glwidget.moc.o mainwindow.moc.o window.moc.o vumeter.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
#include <qtextstream.h> // Needs to come before egl.h.
#include <qcursor.h> // Needs to come before egl.h.
#include <qcoreevent.h> // Needs to come before egl.h.
+#include <qevent.h> // Needs to come before egl.h.
#include <epoxy/gl.h>
#include <epoxy/egl.h>
#include <QSurfaceFormat>
#include "context.h"
#include "mixer.h"
#include "ref_counted_gl_sync.h"
+#include "vumeter.h"
class MainWindow;
class QSurface;
static std::once_flag flag;
std::call_once(flag, [this]{
global_mixer = new Mixer(QGLFormat::toSurfaceFormat(format()));
+ global_mixer->set_audio_level_callback([this](float level){
+ global_vu_meter->set_level(level);
+ });
global_mixer->start();
});
global_mixer->set_frame_ready_callback(output, [this]{
qRegisterMetaType<std::vector<std::string>>("std::vector<std::string>");
connect(ui->preview1, SIGNAL(transition_names_updated(std::vector<std::string>)),
this, SLOT(set_transition_names(std::vector<std::string>)));
+
+ global_vu_meter = ui->vu_meter; // global_mixer does not exist yet, so need to delay the hookup.
}
void MainWindow::resizeEvent(QResizeEvent* event)
" gl_FragColor = texture2D(cbcr_tex, tc0); \n"
"} \n";
cbcr_program_num = resource_pool->compile_glsl_program(cbcr_vert_shader, cbcr_frag_shader);
+
+ r128_state = ebur128_init(2, 48000, EBUR128_MODE_SAMPLE_PEAK | EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I | EBUR128_MODE_LRA);
}
Mixer::~Mixer()
}
cards[card_index].usb->stop_dequeue_thread();
}
+
+ ebur128_destroy(&r128_state);
}
namespace {
}
}
if (card_index == 0) {
+ ebur128_add_frames_float(r128_state, samples_out.data(), samples_out.size() / 2);
h264_encoder->add_audio(pts_int, move(samples_out));
}
}
}
}
+ if (audio_level_callback != nullptr) {
+ double loudness_s;
+ ebur128_loudness_shortterm(r128_state, &loudness_s);
+ audio_level_callback(loudness_s);
+ }
+
// If the first card is reporting a corrupted or otherwise dropped frame,
// just increase the pts (skipping over this frame) and don't try to compute anything new.
if (card_copy[0].new_frame->len == 0) {
#undef Success
#include <movit/effect_chain.h>
#include <movit/flat_input.h>
+#include <ebur128.h>
#include <functional>
#include "bmusb/bmusb.h"
output_channel[output].set_frame_ready_callback(callback);
}
+ typedef std::function<void(float)> audio_level_callback_t;
+ void set_audio_level_callback(audio_level_callback_t callback)
+ {
+ audio_level_callback = callback;
+ }
+
std::vector<std::string> get_transition_names()
{
return theme->get_transition_names(pts());
std::thread mixer_thread;
bool should_quit = false;
+
+ audio_level_callback_t audio_level_callback = nullptr;
+ ebur128_state *r128_state = nullptr;
};
extern Mixer *global_mixer;
<rect>
<x>0</x>
<y>0</y>
- <width>902</width>
- <height>590</height>
+ <width>1089</width>
+ <height>664</height>
</rect>
</property>
<property name="windowTitle">
<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">
+ <layout class="QHBoxLayout" name="me_displays" stretch="0,0,0,0">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
</item>
</layout>
</item>
+ <item>
+ <widget class="VUMeter" name="vu_meter" native="true">
+ <property name="minimumSize">
+ <size>
+ <width>30</width>
+ <height>0</height>
+ </size>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
<item>
<rect>
<x>0</x>
<y>0</y>
- <width>902</width>
+ <width>1089</width>
<height>19</height>
</rect>
</property>
<extends>QWidget</extends>
<header>qglwidget.h</header>
</customwidget>
+ <customwidget>
+ <class>VUMeter</class>
+ <extends>QWidget</extends>
+ <header>vumeter.h</header>
+ <container>1</container>
+ </customwidget>
</customwidgets>
<resources/>
<connections/>
--- /dev/null
+#include <QPainter>
+
+#include "vumeter.h"
+
+using namespace std;
+
+VUMeter *global_vu_meter = nullptr;
+
+VUMeter::VUMeter(QWidget *parent)
+ : QWidget(parent)
+{
+}
+
+void VUMeter::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+
+ painter.fillRect(0, 0, width(), height(), Qt::black);
+
+ float level;
+ {
+ unique_lock<mutex> lock(level_mutex);
+ level = this->level;
+ }
+
+ const float min_level = 0.0f; // y=0 is top of screen, so “min” is the loudest level.
+ const float max_level = -60.0f;
+ int y = lrintf(height() * (level - min_level) / (max_level - min_level));
+ if (y >= 0 && y < height()) {
+ painter.setPen(Qt::white);
+ painter.drawLine(0, y, width(), y);
+ }
+}
--- /dev/null
+#ifndef VUMETER_H
+#define VUMETER_H
+
+#include <QWidget>
+#include <QPaintEvent>
+
+#include <mutex>
+
+class VUMeter : public QWidget
+{
+ Q_OBJECT
+
+public:
+ VUMeter(QWidget *parent);
+
+ void set_level(float level) {
+ std::unique_lock<std::mutex> lock(level_mutex);
+ this->level = level;
+ update();
+ }
+
+private:
+ void paintEvent(QPaintEvent *event) override;
+
+ std::mutex level_mutex;
+ float level = -HUGE_VAL;
+};
+
+extern VUMeter *global_vu_meter;
+
+#endif