diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 3818eecb5..14cc0376c 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -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) diff --git a/obs/adv-audio-control.cpp b/obs/adv-audio-control.cpp new file mode 100644 index 000000000..da1c0e32b --- /dev/null +++ b/obs/adv-audio-control.cpp @@ -0,0 +1,253 @@ +#include +#include +#include +#include +#include +#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(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(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(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); +} diff --git a/obs/adv-audio-control.hpp b/obs/adv-audio-control.hpp new file mode 100644 index 000000000..7197cb0b3 --- /dev/null +++ b/obs/adv-audio-control.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +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); +}; diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index f532be900..82fb10ef1 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -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" diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index 9b68ab9d6..9305572f6 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -28,7 +28,7 @@ - :/res/images/obs.png:/res/images/obs.png + :/res/images/obs.png:/res/images/obs.png @@ -338,7 +338,7 @@ 0 0 - 233 + 216 16 @@ -452,7 +452,7 @@ 0 0 927 - 24 + 26 @@ -521,6 +521,8 @@ + + @@ -817,6 +819,11 @@ Interact + + + Basic.MainMenu.Edit.AdvAudio + + diff --git a/obs/window-basic-adv-audio.cpp b/obs/window-basic-adv-audio.cpp new file mode 100644 index 000000000..67844428b --- /dev/null +++ b/obs/window-basic-adv-audio.cpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include +#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(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(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(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; + } + } +} diff --git a/obs/window-basic-adv-audio.hpp b/obs/window-basic-adv-audio.hpp new file mode 100644 index 000000000..8f9a4a49c --- /dev/null +++ b/obs/window-basic-adv-audio.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +class OBSAdvAudioCtrl; + +// "Basic advanced audio"? ... + +class OBSBasicAdvAudio : public QDialog { + Q_OBJECT + +private: + QWidget *controlArea; + OBSSignal sourceAddedSignal; + OBSSignal sourceRemovedSignal; + + std::vector 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); +}; diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 0f3740d32..e15c80856 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -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) { diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 60d9fa1d9..b95edbb4d 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -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 #include @@ -61,6 +62,7 @@ private: QPointer interaction; QPointer properties; QPointer transformWindow; + QPointer 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();