0
0
mirror of https://github.com/obsproject/obs-studio.git synced 2024-09-20 13:08:50 +02:00
obs-studio/UI/volume-control.cpp
jp9000 27a3b97f48 UI: Prevent thread stalls with fader/volume widgets
When the OBS signal is triggered for these widgets, the invokeMethod
could cause the thread to stall, which could make it wait much longer
than necessary to output audio data.  When that happens, it causes audio
monitoring to get backed up and get unnecessarily delayed, as well as
cause general audio buffering in libobs to increase unnecessarily.

A simple fix both in terms of preventing that stall and improving UI
performance is to not call invokeMethod to update the widget each time,
and then instead have those widgets update themselves via a timer at a
specific interval.
2017-02-06 11:44:05 -08:00

368 lines
8.0 KiB
C++

#include "volume-control.hpp"
#include "qt-wrappers.hpp"
#include "mute-checkbox.hpp"
#include "slider-absoluteset-style.hpp"
#include <util/platform.h>
#include <util/threading.h>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QVariant>
#include <QSlider>
#include <QLabel>
#include <QPainter>
#include <QTimer>
#include <string>
#include <math.h>
using namespace std;
QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
void VolControl::OBSVolumeChanged(void *data, float db)
{
Q_UNUSED(db);
VolControl *volControl = static_cast<VolControl*>(data);
QMetaObject::invokeMethod(volControl, "VolumeChanged");
}
void VolControl::OBSVolumeLevel(void *data, float level, float mag,
float peak, float muted)
{
VolControl *volControl = static_cast<VolControl*>(data);
if (muted)
level = mag = peak = 0.0f;
volControl->volMeter->setLevels(mag, level, peak);
}
void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata)
{
VolControl *volControl = static_cast<VolControl*>(data);
bool muted = calldata_bool(calldata, "muted");
QMetaObject::invokeMethod(volControl, "VolumeMuted",
Q_ARG(bool, muted));
}
void VolControl::VolumeChanged()
{
slider->blockSignals(true);
slider->setValue((int) (obs_fader_get_deflection(obs_fader) * 100.0f));
slider->blockSignals(false);
updateText();
}
void VolControl::VolumeLevel(float mag, float peak, float peakHold, bool muted)
{
if (muted) {
mag = 0.0f;
peak = 0.0f;
peakHold = 0.0f;
}
volMeter->setLevels(mag, peak, peakHold);
}
void VolControl::VolumeMuted(bool muted)
{
if (mute->isChecked() != muted)
mute->setChecked(muted);
}
void VolControl::SetMuted(bool checked)
{
obs_source_set_muted(source, checked);
}
void VolControl::SliderChanged(int vol)
{
obs_fader_set_deflection(obs_fader, float(vol) * 0.01f);
updateText();
}
void VolControl::updateText()
{
volLabel->setText(QString::number(obs_fader_get_db(obs_fader), 'f', 1)
.append(" dB"));
}
QString VolControl::GetName() const
{
return nameLabel->text();
}
void VolControl::SetName(const QString &newName)
{
nameLabel->setText(newName);
}
void VolControl::EmitConfigClicked()
{
emit ConfigClicked();
}
VolControl::VolControl(OBSSource source_, bool showConfig)
: source (source_),
levelTotal (0.0f),
levelCount (0.0f),
obs_fader (obs_fader_create(OBS_FADER_CUBIC)),
obs_volmeter (obs_volmeter_create(OBS_FADER_LOG))
{
QHBoxLayout *volLayout = new QHBoxLayout();
QVBoxLayout *mainLayout = new QVBoxLayout();
QHBoxLayout *textLayout = new QHBoxLayout();
QHBoxLayout *botLayout = new QHBoxLayout();
nameLabel = new QLabel();
volLabel = new QLabel();
volMeter = new VolumeMeter();
mute = new MuteCheckBox();
slider = new QSlider(Qt::Horizontal);
QFont font = nameLabel->font();
font.setPointSize(font.pointSize()-1);
nameLabel->setText(obs_source_get_name(source));
nameLabel->setFont(font);
volLabel->setFont(font);
slider->setMinimum(0);
slider->setMaximum(100);
// slider->setMaximumHeight(13);
textLayout->setContentsMargins(0, 0, 0, 0);
textLayout->addWidget(nameLabel);
textLayout->addWidget(volLabel);
textLayout->setAlignment(nameLabel, Qt::AlignLeft);
textLayout->setAlignment(volLabel, Qt::AlignRight);
mute->setChecked(obs_source_muted(source));
volLayout->addWidget(slider);
volLayout->addWidget(mute);
volLayout->setSpacing(5);
botLayout->setContentsMargins(0, 0, 0, 0);
botLayout->setSpacing(0);
botLayout->addLayout(volLayout);
if (showConfig) {
config = new QPushButton(this);
config->setProperty("themeID", "configIconSmall");
config->setFlat(true);
config->setSizePolicy(QSizePolicy::Maximum,
QSizePolicy::Maximum);
config->setMaximumSize(22, 22);
config->setAutoDefault(false);
connect(config, &QAbstractButton::clicked,
this, &VolControl::EmitConfigClicked);
botLayout->addWidget(config);
}
mainLayout->setContentsMargins(4, 4, 4, 4);
mainLayout->setSpacing(2);
mainLayout->addItem(textLayout);
mainLayout->addWidget(volMeter);
mainLayout->addItem(botLayout);
setLayout(mainLayout);
obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
signal_handler_connect(obs_source_get_signal_handler(source),
"mute", OBSVolumeMuted, this);
QWidget::connect(slider, SIGNAL(valueChanged(int)),
this, SLOT(SliderChanged(int)));
QWidget::connect(mute, SIGNAL(clicked(bool)),
this, SLOT(SetMuted(bool)));
obs_fader_attach_source(obs_fader, source);
obs_volmeter_attach_source(obs_volmeter, source);
slider->setStyle(new SliderAbsoluteSetStyle(slider->style()));
/* Call volume changed once to init the slider position and label */
VolumeChanged();
}
VolControl::~VolControl()
{
obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
obs_volmeter_remove_callback(obs_volmeter, OBSVolumeLevel, this);
signal_handler_disconnect(obs_source_get_signal_handler(source),
"mute", OBSVolumeMuted, this);
obs_fader_destroy(obs_fader);
obs_volmeter_destroy(obs_volmeter);
}
QColor VolumeMeter::getBkColor() const
{
return bkColor;
}
void VolumeMeter::setBkColor(QColor c)
{
bkColor = c;
}
QColor VolumeMeter::getMagColor() const
{
return magColor;
}
void VolumeMeter::setMagColor(QColor c)
{
magColor = c;
}
QColor VolumeMeter::getPeakColor() const
{
return peakColor;
}
void VolumeMeter::setPeakColor(QColor c)
{
peakColor = c;
}
QColor VolumeMeter::getPeakHoldColor() const
{
return peakHoldColor;
}
void VolumeMeter::setPeakHoldColor(QColor c)
{
peakHoldColor = c;
}
VolumeMeter::VolumeMeter(QWidget *parent)
: QWidget(parent)
{
setMinimumSize(1, 3);
//Default meter color settings, they only show if there is no stylesheet, do not remove.
bkColor.setRgb(0xDD, 0xDD, 0xDD);
magColor.setRgb(0x20, 0x7D, 0x17);
peakColor.setRgb(0x3E, 0xF1, 0x2B);
peakHoldColor.setRgb(0x00, 0x00, 0x00);
updateTimerRef = updateTimer.toStrongRef();
if (!updateTimerRef) {
updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
updateTimerRef->start(100);
updateTimer = updateTimerRef;
}
updateTimerRef->AddVolControl(this);
}
VolumeMeter::~VolumeMeter()
{
updateTimerRef->RemoveVolControl(this);
}
void VolumeMeter::setLevels(float nmag, float npeak, float npeakHold)
{
uint64_t ts = os_gettime_ns();
QMutexLocker locker(&dataMutex);
mag += nmag;
peak += npeak;
peakHold += npeakHold;
multiple += 1.0f;
lastUpdateTime = ts;
}
inline void VolumeMeter::calcLevels()
{
uint64_t ts = os_gettime_ns();
QMutexLocker locker(&dataMutex);
if (lastUpdateTime && ts - lastUpdateTime > 1000000000) {
mag = peak = peakHold = 0.0f;
multiple = 1.0f;
lastUpdateTime = 0;
}
if (multiple > 0.0f) {
curMag = mag / multiple;
curPeak = peak / multiple;
curPeakHold = peakHold / multiple;
mag = peak = peakHold = multiple = 0.0f;
}
}
void VolumeMeter::paintEvent(QPaintEvent *event)
{
UNUSED_PARAMETER(event);
QPainter painter(this);
QLinearGradient gradient;
int width = size().width();
int height = size().height();
calcLevels();
int scaledMag = int((float)width * curMag);
int scaledPeak = int((float)width * curPeak);
int scaledPeakHold = int((float)width * curPeakHold);
gradient.setStart(qreal(scaledMag), 0);
gradient.setFinalStop(qreal(scaledPeak), 0);
gradient.setColorAt(0, magColor);
gradient.setColorAt(1, peakColor);
// RMS
painter.fillRect(0, 0,
scaledMag, height,
magColor);
// RMS - Peak gradient
painter.fillRect(scaledMag, 0,
scaledPeak - scaledMag + 1, height,
QBrush(gradient));
// Background
painter.fillRect(scaledPeak, 0,
width - scaledPeak, height,
bkColor);
// Peak hold
if (peakHold == 1.0f)
scaledPeakHold--;
painter.setPen(peakHoldColor);
painter.drawLine(scaledPeakHold, 0,
scaledPeakHold, height);
}
void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
{
volumeMeters.push_back(meter);
}
void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
{
volumeMeters.removeOne(meter);
}
void VolumeMeterTimer::timerEvent(QTimerEvent*)
{
for (VolumeMeter *meter : volumeMeters)
meter->update();
}