UI: Add advanced audio properties dialog

This dialog gives options such as increasing audio past 100%, forcing
the audio of a source to mono, and setting the audio sync offset of a
source (which was an oft-requested feature)
This commit is contained in:
jp9000 2014-12-28 00:38:00 -08:00
parent ce6a1146cc
commit 6fc52dfb16
9 changed files with 536 additions and 3 deletions

View file

@ -96,6 +96,7 @@ set(obs_SOURCES
window-basic-source-select.cpp
window-license-agreement.cpp
window-basic-status-bar.cpp
window-basic-adv-audio.cpp
window-basic-transform.cpp
window-basic-preview.cpp
window-namedialog.cpp
@ -103,6 +104,7 @@ set(obs_SOURCES
window-remux.cpp
properties-view.cpp
volume-control.cpp
adv-audio-control.cpp
qt-wrappers.cpp)
set(obs_HEADERS
@ -116,6 +118,7 @@ set(obs_HEADERS
window-basic-source-select.hpp
window-license-agreement.hpp
window-basic-status-bar.hpp
window-basic-adv-audio.hpp
window-basic-transform.hpp
window-basic-preview.hpp
window-namedialog.hpp
@ -124,6 +127,7 @@ set(obs_HEADERS
properties-view.hpp
display-helpers.hpp
volume-control.hpp
adv-audio-control.hpp
qt-display.hpp
qt-wrappers.hpp)

253
obs/adv-audio-control.cpp Normal file
View file

@ -0,0 +1,253 @@
#include <QHBoxLayout>
#include <QLabel>
#include <QSpinBox>
#include <QCheckBox>
#include <QSlider>
#include "qt-wrappers.hpp"
#include "adv-audio-control.hpp"
#ifndef NSEC_PER_MSEC
#define NSEC_PER_MSEC 1000000
#endif
OBSAdvAudioCtrl::OBSAdvAudioCtrl(obs_source_t *source_)
: source(source_)
{
QWidget *forceMonoContainer;
QWidget *mediaChannelContainer;
QWidget *panningContainer;
QHBoxLayout *hlayout;
QLabel *labelL, *labelR;
signal_handler_t *handler = obs_source_get_signal_handler(source);
const char *sourceName = obs_source_get_name(source);
float vol = obs_source_get_volume(source);
uint32_t flags = obs_source_get_flags(source);
forceMonoContainer = new QWidget();
mediaChannelContainer = new QWidget();
panningContainer = new QWidget();
labelL = new QLabel();
labelR = new QLabel();
nameLabel = new QLabel();
volume = new QSpinBox();
forceMono = new QCheckBox();
panning = new QSlider(Qt::Horizontal);
syncOffset = new QSpinBox();
mediaChannel1 = new QCheckBox();
mediaChannel2 = new QCheckBox();
mediaChannel3 = new QCheckBox();
mediaChannel4 = new QCheckBox();
volChangedSignal.Connect(handler, "volume", OBSSourceVolumeChanged,
this);
syncOffsetSignal.Connect(handler, "audio_sync", OBSSourceSyncChanged,
this);
flagsSignal.Connect(handler, "update_flags", OBSSourceFlagsChanged,
this);
hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0, 0, 0, 0);
forceMonoContainer->setLayout(hlayout);
hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0, 0, 0, 0);
mediaChannelContainer->setLayout(hlayout);
hlayout = new QHBoxLayout();
hlayout->setContentsMargins(0, 0, 0, 0);
panningContainer->setLayout(hlayout);
labelL->setText("L");
labelR->setText("R");
nameLabel->setMinimumWidth(170);
nameLabel->setText(QT_UTF8(sourceName));
nameLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
volume->setMinimum(0);
volume->setMaximum(2000);
volume->setMinimumWidth(130);
volume->setMaximumWidth(130);
volume->setValue(int(vol * 100.0f));
forceMono->setChecked((flags & OBS_SOURCE_FLAG_FORCE_MONO) != 0);
forceMonoContainer->setMinimumWidth(130);
forceMonoContainer->setMaximumWidth(130);
forceMonoContainer->layout()->addWidget(forceMono);
forceMonoContainer->layout()->setAlignment(forceMono,
Qt::AlignHCenter | Qt::AlignVCenter);
panning->setMinimum(0);
panning->setMaximum(100);
panning->setTickPosition(QSlider::TicksAbove);
panning->setEnabled(false);
panning->setValue(50); /* XXX */
int64_t cur_sync = obs_source_get_sync_offset(source);
syncOffset->setMinimum(-20000);
syncOffset->setMaximum(20000);
syncOffset->setMinimumWidth(130);
syncOffset->setMaximumWidth(130);
syncOffset->setValue(int(cur_sync / NSEC_PER_MSEC));
mediaChannel1->setChecked(true);
mediaChannel1->setText("1");
mediaChannel1->setEnabled(false);
mediaChannel2->setText("2");
mediaChannel2->setEnabled(false);
mediaChannel3->setText("3");
mediaChannel3->setEnabled(false);
mediaChannel4->setText("4");
mediaChannel4->setEnabled(false);
panningContainer->setMinimumWidth(140);
panningContainer->setMaximumWidth(140);
panningContainer->layout()->addWidget(labelL);
panningContainer->layout()->addWidget(panning);
panningContainer->layout()->addWidget(labelR);
mediaChannelContainer->setMinimumWidth(160);
mediaChannelContainer->setMaximumWidth(160);
mediaChannelContainer->layout()->addWidget(mediaChannel1);
mediaChannelContainer->layout()->addWidget(mediaChannel2);
mediaChannelContainer->layout()->addWidget(mediaChannel3);
mediaChannelContainer->layout()->addWidget(mediaChannel4);
QWidget::connect(volume, SIGNAL(valueChanged(int)),
this, SLOT(volumeChanged(int)));
QWidget::connect(forceMono, SIGNAL(clicked(bool)),
this, SLOT(downmixMonoChanged(bool)));
QWidget::connect(panning, SIGNAL(valueChanged(int)),
this, SLOT(panningChanged(int)));
QWidget::connect(syncOffset, SIGNAL(valueChanged(int)),
this, SLOT(syncOffsetChanged(int)));
QWidget::connect(mediaChannel1, SIGNAL(clicked(bool)),
this, SLOT(mediaChannel1Changed(bool)));
QWidget::connect(mediaChannel2, SIGNAL(clicked(bool)),
this, SLOT(mediaChannel2Changed(bool)));
QWidget::connect(mediaChannel3, SIGNAL(clicked(bool)),
this, SLOT(mediaChannel3Changed(bool)));
QWidget::connect(mediaChannel4, SIGNAL(clicked(bool)),
this, SLOT(mediaChannel4Changed(bool)));
hlayout = new QHBoxLayout;
hlayout->setContentsMargins(0, 0, 0, 0);
hlayout->addWidget(nameLabel);
hlayout->addWidget(volume);
hlayout->addWidget(forceMonoContainer);
hlayout->addWidget(panningContainer);
hlayout->addWidget(syncOffset);
hlayout->addWidget(mediaChannelContainer);
setLayout(hlayout);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
}
/* ------------------------------------------------------------------------- */
/* OBS source callbacks */
void OBSAdvAudioCtrl::OBSSourceFlagsChanged(void *param, calldata_t *calldata)
{
uint32_t flags = (uint32_t)calldata_int(calldata, "flags");
QMetaObject::invokeMethod(reinterpret_cast<OBSAdvAudioCtrl*>(param),
"SourceFlagsChanged", Q_ARG(uint32_t, flags));
}
void OBSAdvAudioCtrl::OBSSourceVolumeChanged(void *param, calldata_t *calldata)
{
float volume = (float)calldata_float(calldata, "volume");
QMetaObject::invokeMethod(reinterpret_cast<OBSAdvAudioCtrl*>(param),
"SourceVolumeChanged", Q_ARG(float, volume));
}
void OBSAdvAudioCtrl::OBSSourceSyncChanged(void *param, calldata_t *calldata)
{
int64_t offset = calldata_int(calldata, "offset");
QMetaObject::invokeMethod(reinterpret_cast<OBSAdvAudioCtrl*>(param),
"SourceSyncChanged", Q_ARG(int64_t, offset));
}
/* ------------------------------------------------------------------------- */
/* Qt event queue source callbacks */
void OBSAdvAudioCtrl::SourceFlagsChanged(uint32_t flags)
{
bool forceMonoVal = (flags & OBS_SOURCE_FLAG_FORCE_MONO) != 0;
if (forceMono->isChecked() != forceMonoVal)
forceMono->setChecked(forceMonoVal);
}
void OBSAdvAudioCtrl::SourceVolumeChanged(float value)
{
volume->setValue(int(value * 100));
}
void OBSAdvAudioCtrl::SourceSyncChanged(int64_t offset)
{
syncOffset->setValue(offset / NSEC_PER_MSEC);
}
/* ------------------------------------------------------------------------- */
/* Qt control callbacks */
void OBSAdvAudioCtrl::volumeChanged(int percentage)
{
float val = float(percentage) / 100.0f;
if (!close_float(val, obs_source_get_volume(source), 0.01f))
obs_source_set_volume(source, val);
}
void OBSAdvAudioCtrl::downmixMonoChanged(bool checked)
{
uint32_t flags = obs_source_get_flags(source);
bool forceMonoActive = (flags & OBS_SOURCE_FLAG_FORCE_MONO) != 0;
if (forceMonoActive != checked) {
if (checked)
flags |= OBS_SOURCE_FLAG_FORCE_MONO;
else
flags &= ~OBS_SOURCE_FLAG_FORCE_MONO;
obs_source_set_flags(source, flags);
}
}
void OBSAdvAudioCtrl::panningChanged(int val)
{
/* TODO */
UNUSED_PARAMETER(val);
}
void OBSAdvAudioCtrl::syncOffsetChanged(int milliseconds)
{
int64_t cur_val = obs_source_get_sync_offset(source);
if (cur_val / NSEC_PER_MSEC != milliseconds)
obs_source_set_sync_offset(source,
int64_t(milliseconds) * NSEC_PER_MSEC);
}
void OBSAdvAudioCtrl::mediaChannel1Changed(bool checked)
{
/* TODO */
UNUSED_PARAMETER(checked);
}
void OBSAdvAudioCtrl::mediaChannel2Changed(bool checked)
{
/* TODO */
UNUSED_PARAMETER(checked);
}
void OBSAdvAudioCtrl::mediaChannel3Changed(bool checked)
{
/* TODO */
UNUSED_PARAMETER(checked);
}
void OBSAdvAudioCtrl::mediaChannel4Changed(bool checked)
{
/* TODO */
UNUSED_PARAMETER(checked);
}

52
obs/adv-audio-control.hpp Normal file
View file

@ -0,0 +1,52 @@
#pragma once
#include <obs.hpp>
#include <QWidget>
class QLabel;
class QSpinBox;
class QCheckBox;
class QSlider;
class OBSAdvAudioCtrl : public QWidget {
Q_OBJECT
private:
OBSSource source;
QLabel *nameLabel = nullptr;
QSpinBox *volume = nullptr;
QCheckBox *forceMono = nullptr;
QSlider *panning = nullptr;
QSpinBox *syncOffset = nullptr;
QCheckBox *mediaChannel1 = nullptr;
QCheckBox *mediaChannel2 = nullptr;
QCheckBox *mediaChannel3 = nullptr;
QCheckBox *mediaChannel4 = nullptr;
OBSSignal volChangedSignal;
OBSSignal syncOffsetSignal;
OBSSignal flagsSignal;
static void OBSSourceFlagsChanged(void *param, calldata_t *calldata);
static void OBSSourceVolumeChanged(void *param, calldata_t *calldata);
static void OBSSourceSyncChanged(void *param, calldata_t *calldata);
public:
OBSAdvAudioCtrl(obs_source_t *source_);
inline obs_source_t *GetSource() const {return source;}
public slots:
void SourceFlagsChanged(uint32_t flags);
void SourceVolumeChanged(float volume);
void SourceSyncChanged(int64_t offset);
void volumeChanged(int percentage);
void downmixMonoChanged(bool checked);
void panningChanged(int val);
void syncOffsetChanged(int milliseconds);
void mediaChannel1Changed(bool checked);
void mediaChannel2Changed(bool checked);
void mediaChannel3Changed(bool checked);
void mediaChannel4Changed(bool checked);
};

View file

@ -189,6 +189,7 @@ Basic.MainMenu.Edit.Order.MoveUp="Move &Up"
Basic.MainMenu.Edit.Order.MoveDown="Move &Down"
Basic.MainMenu.Edit.Order.MoveToTop="Move to &Top"
Basic.MainMenu.Edit.Order.MoveToBottom="Move to &Bottom"
Basic.MainMenu.Edit.AdvAudio="&Advanced Audio Properties"
# basic mode help menu
Basic.MainMenu.Help="&Help"
@ -258,3 +259,12 @@ Basic.Settings.Audio.DesktopDevice2="Desktop Audio Device 2"
Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Audio Device"
Basic.Settings.Audio.AuxDevice2="Mic/Auxiliary Audio Device 2"
Basic.Settings.Audio.AuxDevice3="Mic/Auxiliary Audio Device 3"
# advanced audio properties
Basic.AdvAudio="Advanced Audio Properties"
Basic.AdvAudio.Name="Name"
Basic.AdvAudio.Volume="Volume (%)"
Basic.AdvAudio.Mono="Downmix to Mono"
Basic.AdvAudio.Panning="Panning"
Basic.AdvAudio.SyncOffset="Sync Offset (ms)"
Basic.AdvAudio.MediaChannel="Media Channel"

View file

@ -28,7 +28,7 @@
</property>
<property name="windowIcon">
<iconset resource="obs.qrc">
<normaloff>:/res/images/obs.png</normaloff>:/res/images/obs.png</iconset>
<normaloff>:/res/images/obs.png</normaloff>:/res/images/obs.png</iconset>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
@ -338,7 +338,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>233</width>
<width>216</width>
<height>16</height>
</rect>
</property>
@ -452,7 +452,7 @@
<x>0</x>
<y>0</y>
<width>927</width>
<height>24</height>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@ -521,6 +521,8 @@
<addaction name="separator"/>
<addaction name="transformMenu"/>
<addaction name="orderMenu"/>
<addaction name="separator"/>
<addaction name="actionAdvAudioProperties"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menuBasic_MainMenu_Edit"/>
@ -817,6 +819,11 @@
<string>Interact</string>
</property>
</action>
<action name="actionAdvAudioProperties">
<property name="text">
<string>Basic.MainMenu.Edit.AdvAudio</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View file

@ -0,0 +1,160 @@
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QScrollArea>
#include <QLabel>
#include "window-basic-adv-audio.hpp"
#include "adv-audio-control.hpp"
#include "obs-app.hpp"
#include "qt-wrappers.hpp"
Q_DECLARE_METATYPE(OBSSource);
OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent)
: QDialog(parent),
sourceAddedSignal(obs_get_signal_handler(), "source_activate",
OBSSourceAdded, this),
sourceRemovedSignal(obs_get_signal_handler(), "source_deactivate",
OBSSourceRemoved, this)
{
QScrollArea *scrollArea;
QVBoxLayout *vlayout;
QHBoxLayout *hlayout;
QWidget *widget;
QLabel *label;
hlayout = new QHBoxLayout;
hlayout->setContentsMargins(0, 0, 0, 0);
label = new QLabel(QTStr("Basic.AdvAudio.Name"));
label->setMinimumWidth(170);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
label = new QLabel(QTStr("Basic.AdvAudio.Volume"));
label->setMinimumWidth(130);
label->setMaximumWidth(130);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
label = new QLabel(QTStr("Basic.AdvAudio.Mono"));
label->setMinimumWidth(130);
label->setMaximumWidth(130);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
label = new QLabel(QTStr("Basic.AdvAudio.Panning"));
label->setMinimumWidth(140);
label->setMaximumWidth(140);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
label = new QLabel(QTStr("Basic.AdvAudio.SyncOffset"));
label->setMinimumWidth(130);
label->setMaximumWidth(130);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
label = new QLabel(QTStr("Basic.AdvAudio.MediaChannel"));
label->setMinimumWidth(160);
label->setMaximumWidth(160);
label->setAlignment(Qt::AlignHCenter);
hlayout->addWidget(label);
widget = new QWidget;
widget->setLayout(hlayout);
vlayout = new QVBoxLayout;
vlayout->setContentsMargins(0, 0, 0, 10);
vlayout->addWidget(widget);
controlArea = new QWidget;
controlArea->setLayout(vlayout);
controlArea->setSizePolicy(QSizePolicy::Preferred,
QSizePolicy::Preferred);
vlayout = new QVBoxLayout;
vlayout->addWidget(controlArea);
//vlayout->setAlignment(controlArea, Qt::AlignTop);
widget = new QWidget;
widget->setLayout(vlayout);
widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
scrollArea = new QScrollArea;
scrollArea->setWidget(widget);
scrollArea->setWidgetResizable(true);
vlayout = new QVBoxLayout;
vlayout->setContentsMargins(11, 11, 11, 11);
vlayout->addWidget(scrollArea);
setLayout(vlayout);
/* get global audio sources */
for (uint32_t i = 1; i <= 10; i++) {
obs_source_t *source = obs_get_output_source(i);
if (source) {
AddAudioSource(source);
obs_source_release(source);
}
}
/* enum user scene/sources */
obs_enum_sources(EnumSources, this);
resize(1000, 340);
setWindowTitle(QTStr("Basic.AdvAudio"));
setSizeGripEnabled(true);
setWindowModality(Qt::NonModal);
setAttribute(Qt::WA_DeleteOnClose, true);
}
bool OBSBasicAdvAudio::EnumSources(void *param, obs_source_t *source)
{
OBSBasicAdvAudio *dialog = reinterpret_cast<OBSBasicAdvAudio*>(param);
uint32_t flags = obs_source_get_output_flags(source);
if ((flags & OBS_SOURCE_AUDIO) != 0 && obs_source_active(source))
dialog->AddAudioSource(source);
return true;
}
void OBSBasicAdvAudio::OBSSourceAdded(void *param, calldata_t *calldata)
{
OBSSource source((obs_source_t*)calldata_ptr(calldata, "source"));
QMetaObject::invokeMethod(reinterpret_cast<OBSBasicAdvAudio*>(param),
"SourceAdded", Q_ARG(OBSSource, source));
}
void OBSBasicAdvAudio::OBSSourceRemoved(void *param, calldata_t *calldata)
{
OBSSource source((obs_source_t*)calldata_ptr(calldata, "source"));
QMetaObject::invokeMethod(reinterpret_cast<OBSBasicAdvAudio*>(param),
"SourceRemoved", Q_ARG(OBSSource, source));
}
inline void OBSBasicAdvAudio::AddAudioSource(obs_source_t *source)
{
OBSAdvAudioCtrl *control = new OBSAdvAudioCtrl(source);
controlArea->layout()->addWidget(control);
controls.push_back(control);
}
void OBSBasicAdvAudio::SourceAdded(OBSSource source)
{
uint32_t flags = obs_source_get_output_flags(source);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
AddAudioSource(source);
}
void OBSBasicAdvAudio::SourceRemoved(OBSSource source)
{
uint32_t flags = obs_source_get_output_flags(source);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
for (size_t i = 0; i < controls.size(); i++) {
if (controls[i]->GetSource() == source) {
delete controls[i];
controls.erase(controls.begin() + i);
break;
}
}
}

View file

@ -0,0 +1,34 @@
#pragma once
#include <obs.hpp>
#include <QDialog>
#include <vector>
class OBSAdvAudioCtrl;
// "Basic advanced audio"? ...
class OBSBasicAdvAudio : public QDialog {
Q_OBJECT
private:
QWidget *controlArea;
OBSSignal sourceAddedSignal;
OBSSignal sourceRemovedSignal;
std::vector<OBSAdvAudioCtrl*> controls;
inline void AddAudioSource(obs_source_t *source);
static bool EnumSources(void *param, obs_source_t *source);
static void OBSSourceAdded(void *param, calldata_t *calldata);
static void OBSSourceRemoved(void *param, calldata_t *calldata);
public slots:
void SourceAdded(OBSSource source);
void SourceRemoved(OBSSource source);
public:
OBSBasicAdvAudio(QWidget *parent);
};

View file

@ -619,6 +619,9 @@ OBSBasic::~OBSBasic()
if (transformWindow)
delete transformWindow;
if (advAudioWindow)
delete advAudioWindow;
ClearVolumeControls();
ui->sources->clear();
ui->scenes->clear();
@ -1561,6 +1564,13 @@ void OBSBasic::on_action_Settings_triggered()
settings.exec();
}
void OBSBasic::on_actionAdvAudioProperties_triggered()
{
advAudioWindow = new OBSBasicAdvAudio(this);
advAudioWindow->show();
advAudioWindow->setAttribute(Qt::WA_DeleteOnClose, true);
}
void OBSBasic::on_scenes_currentItemChanged(QListWidgetItem *current,
QListWidgetItem *prev)
{

View file

@ -28,6 +28,7 @@
#include "window-basic-interaction.hpp"
#include "window-basic-properties.hpp"
#include "window-basic-transform.hpp"
#include "window-basic-adv-audio.hpp"
#include <util/platform.h>
#include <util/util.hpp>
@ -61,6 +62,7 @@ private:
QPointer<OBSBasicInteraction> interaction;
QPointer<OBSBasicProperties> properties;
QPointer<OBSBasicTransform> transformWindow;
QPointer<OBSBasicAdvAudio> advAudioWindow;
QNetworkAccessManager networkManager;
@ -230,6 +232,7 @@ private slots:
void on_actionShow_Recordings_triggered();
void on_actionRemux_triggered();
void on_action_Settings_triggered();
void on_actionAdvAudioProperties_triggered();
void on_actionShowLogs_triggered();
void on_actionUploadCurrentLog_triggered();
void on_actionUploadLastLog_triggered();