diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt
index 041422232..62a4986b9 100644
--- a/UI/CMakeLists.txt
+++ b/UI/CMakeLists.txt
@@ -103,6 +103,7 @@ target_sources(
forms/OBSBasicSettings.ui
forms/OBSBasicSourceSelect.ui
forms/OBSBasicTransform.ui
+ forms/OBSBasicVCamConfig.ui
forms/OBSExtraBrowsers.ui
forms/OBSImporter.ui
forms/OBSLogReply.ui
@@ -258,6 +259,8 @@ target_sources(
window-basic-transform.cpp
window-basic-transform.hpp
window-basic-preview.hpp
+ window-basic-vcam-config.cpp
+ window-basic-vcam-config.hpp
window-dock.cpp
window-dock.hpp
window-importer.cpp
diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini
index cb45ecd5a..0d6f0aeb7 100644
--- a/UI/data/locale/en-US.ini
+++ b/UI/data/locale/en-US.ini
@@ -723,6 +723,17 @@ Basic.Main.Ungroup="Ungroup"
Basic.Main.GridMode="Grid Mode"
Basic.Main.ListMode="List Mode"
+# virtual camera configuration
+Basic.Main.VirtualCamConfig="Configure Virtual Camera"
+Basic.VCam.VirtualCamera="Virtual Camera"
+Basic.VCam.OutputType="Output Type"
+Basic.VCam.OutputSelection="Output Selection"
+Basic.VCam.Internal="Internal"
+Basic.VCam.InternalDefault="Program Output (Default)"
+Basic.VCam.InternalPreview="Preview Output"
+Basic.VCam.Start="Start"
+Basic.VCam.Update="Update"
+
# basic mode file menu
Basic.MainMenu.File="&File"
Basic.MainMenu.File.Export="&Export"
diff --git a/UI/forms/OBSBasicVCamConfig.ui b/UI/forms/OBSBasicVCamConfig.ui
new file mode 100644
index 000000000..bc255b159
--- /dev/null
+++ b/UI/forms/OBSBasicVCamConfig.ui
@@ -0,0 +1,113 @@
+
+
+ OBSBasicVCamConfig
+
+
+
+ 0
+ 0
+ 400
+ 170
+
+
+
+ Basic.VCam.VirtualCamera
+
+
+ -
+
+
+ Basic.VCam.OutputType
+
+
+
+ -
+
+
-
+
+ Basic.VCam.Internal
+
+
+ -
+
+ Basic.Scene
+
+
+ -
+
+ Basic.Main.Source
+
+
+
+
+ -
+
+
+ Basic.VCam.OutputSelection
+
+
+
+ -
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Ok
+
+
+
+
+
+
+
+
+ buttonBox
+ accepted()
+ OBSBasicVCamConfig
+ accept()
+
+
+ 248
+ 254
+
+
+ 157
+ 274
+
+
+
+
+ buttonBox
+ rejected()
+ OBSBasicVCamConfig
+ reject()
+
+
+ 316
+ 260
+
+
+ 286
+ 274
+
+
+
+
+
diff --git a/UI/record-button.cpp b/UI/record-button.cpp
index 0e080a796..a0f028938 100644
--- a/UI/record-button.cpp
+++ b/UI/record-button.cpp
@@ -18,19 +18,143 @@ void RecordButton::resizeEvent(QResizeEvent *event)
event->accept();
}
-void ReplayBufferButton::resizeEvent(QResizeEvent *event)
+static QWidget *firstWidget(QLayoutItem *item)
+{
+ auto widget = item->widget();
+ if (widget)
+ return widget;
+
+ auto layout = item->layout();
+ if (!layout)
+ return nullptr;
+
+ auto n = layout->count();
+ for (auto i = 0, n = layout->count(); i < n; i++) {
+ widget = firstWidget(layout->itemAt(i));
+ if (widget)
+ return widget;
+ }
+ return nullptr;
+}
+
+static QWidget *lastWidget(QLayoutItem *item)
+{
+ auto widget = item->widget();
+ if (widget)
+ return widget;
+
+ auto layout = item->layout();
+ if (!layout)
+ return nullptr;
+
+ auto n = layout->count();
+ for (auto i = layout->count(); i > 0; i--) {
+ widget = lastWidget(layout->itemAt(i - 1));
+ if (widget)
+ return widget;
+ }
+ return nullptr;
+}
+
+static QWidget *getNextWidget(QBoxLayout *container, QLayoutItem *item)
+{
+ for (auto i = 1, n = container->count(); i < n; i++) {
+ if (container->itemAt(i - 1) == item)
+ return firstWidget(container->itemAt(i));
+ }
+ return nullptr;
+}
+
+ControlsSplitButton::ControlsSplitButton(const QString &text,
+ const QVariant &themeID,
+ void (OBSBasic::*clicked)())
+ : QHBoxLayout(OBSBasic::Get())
+{
+ button.reset(new QPushButton(text));
+ button->setCheckable(true);
+ button->setProperty("themeID", themeID);
+
+ button->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
+ button->installEventFilter(this);
+
+ OBSBasic *main = OBSBasic::Get();
+ connect(button.data(), &QPushButton::clicked, main, clicked);
+
+ addWidget(button.data());
+}
+
+void ControlsSplitButton::addIcon(const QString &name, const QVariant &themeID,
+ void (OBSBasic::*clicked)())
+{
+ icon.reset(new QPushButton());
+ icon->setAccessibleName(name);
+ icon->setToolTip(name);
+ icon->setChecked(false);
+ icon->setProperty("themeID", themeID);
+
+ QSizePolicy sp;
+ sp.setHeightForWidth(true);
+ icon->setSizePolicy(sp);
+
+ OBSBasic *main = OBSBasic::Get();
+ connect(icon.data(), &QAbstractButton::clicked, main, clicked);
+
+ addWidget(icon.data());
+ QWidget::setTabOrder(button.data(), icon.data());
+
+ auto next = getNextWidget(main->ui->buttonsVLayout, this);
+ if (next)
+ QWidget::setTabOrder(icon.data(), next);
+}
+
+void ControlsSplitButton::removeIcon()
+{
+ icon.reset();
+}
+
+void ControlsSplitButton::insert(int index)
{
OBSBasic *main = OBSBasic::Get();
- if (!main->replay)
- return;
+ auto count = main->ui->buttonsVLayout->count();
+ if (index < 0)
+ index = 0;
+ else if (index > count)
+ index = count;
- QSize replaySize = main->replay->size();
- int height = main->ui->recordButton->size().height();
+ main->ui->buttonsVLayout->insertLayout(index, this);
- if (replaySize.height() != height || replaySize.width() != height) {
- main->replay->setMinimumSize(height, height);
- main->replay->setMaximumSize(height, height);
+ QWidget *prev = button.data();
+
+ if (index > 0) {
+ prev = lastWidget(main->ui->buttonsVLayout->itemAt(index - 1));
+ if (prev)
+ QWidget::setTabOrder(prev, button.data());
+ prev = button.data();
}
- event->accept();
+ if (icon) {
+ QWidget::setTabOrder(button.data(), icon.data());
+ prev = icon.data();
+ }
+
+ if (index < count) {
+ auto next = firstWidget(
+ main->ui->buttonsVLayout->itemAt(index + 1));
+ if (next)
+ QWidget::setTabOrder(prev, next);
+ }
+}
+
+bool ControlsSplitButton::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::Resize && icon) {
+ QSize iconSize = icon->size();
+ int height = button->height();
+
+ if (iconSize.height() != height || iconSize.width() != height) {
+ icon->setMinimumSize(height, height);
+ icon->setMaximumSize(height, height);
+ }
+ }
+ return QObject::eventFilter(obj, event);
}
diff --git a/UI/record-button.hpp b/UI/record-button.hpp
index d6c083e21..ea8d40c7d 100644
--- a/UI/record-button.hpp
+++ b/UI/record-button.hpp
@@ -1,6 +1,8 @@
#pragma once
#include
+#include
+#include
class RecordButton : public QPushButton {
Q_OBJECT
@@ -11,15 +13,27 @@ public:
virtual void resizeEvent(QResizeEvent *event) override;
};
-class ReplayBufferButton : public QPushButton {
+class OBSBasic;
+
+class ControlsSplitButton : public QHBoxLayout {
Q_OBJECT
public:
- inline ReplayBufferButton(const QString &text,
- QWidget *parent = nullptr)
- : QPushButton(text, parent)
- {
- }
+ ControlsSplitButton(const QString &text, const QVariant &themeID,
+ void (OBSBasic::*clicked)());
- virtual void resizeEvent(QResizeEvent *event) override;
+ void addIcon(const QString &name, const QVariant &themeID,
+ void (OBSBasic::*clicked)());
+ void removeIcon();
+ void insert(int index);
+
+ inline QPushButton *first() { return button.data(); }
+ inline QPushButton *second() { return icon.data(); }
+
+protected:
+ virtual bool eventFilter(QObject *obj, QEvent *event) override;
+
+private:
+ QScopedPointer button;
+ QScopedPointer icon;
};
diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp
index f669de091..2f450fd07 100644
--- a/UI/window-basic-main-outputs.cpp
+++ b/UI/window-basic-main-outputs.cpp
@@ -5,6 +5,7 @@
#include "audio-encoders.hpp"
#include "window-basic-main.hpp"
#include "window-basic-main-outputs.hpp"
+#include "window-basic-vcam-config.hpp"
using namespace std;
@@ -178,6 +179,9 @@ static void OBSStopVirtualCam(void *data, calldata_t *params)
os_atomic_set_bool(&virtualcam_active, false);
QMetaObject::invokeMethod(output->main, "OnVirtualCamStop",
Q_ARG(int, code));
+
+ obs_output_set_media(output->virtualCam, nullptr, nullptr);
+ OBSBasicVCamConfig::StopVideo();
}
/* ------------------------------------------------------------------------ */
@@ -226,8 +230,11 @@ inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_)
bool BasicOutputHandler::StartVirtualCam()
{
if (main->vcamEnabled) {
- obs_output_set_media(virtualCam, obs_get_video(),
- obs_get_audio());
+ video_t *video = OBSBasicVCamConfig::StartVideo();
+ if (!video)
+ return false;
+
+ obs_output_set_media(virtualCam, video, obs_get_audio());
if (!Active())
SetupOutputs();
diff --git a/UI/window-basic-main-transitions.cpp b/UI/window-basic-main-transitions.cpp
index 25e3a447f..a64030e57 100644
--- a/UI/window-basic-main-transitions.cpp
+++ b/UI/window-basic-main-transitions.cpp
@@ -21,6 +21,7 @@
#include
#include
#include "window-basic-main.hpp"
+#include "window-basic-vcam-config.hpp"
#include "display-helpers.hpp"
#include "window-namedialog.hpp"
#include "menu-button.hpp"
@@ -283,6 +284,9 @@ void OBSBasic::OverrideTransition(OBSSource transition)
obs_transition_swap_begin(transition, oldTransition);
obs_set_output_source(0, transition);
obs_transition_swap_end(transition, oldTransition);
+
+ // Transition overrides don't raise an event so we need to call update directly
+ OBSBasicVCamConfig::UpdateOutputSource();
}
}
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index f436956c2..6ed3ff77c 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -52,6 +52,7 @@
#include "window-basic-main.hpp"
#include "window-basic-stats.hpp"
#include "window-basic-main-outputs.hpp"
+#include "window-basic-vcam-config.hpp"
#include "window-log-reply.hpp"
#ifdef __APPLE__
#include "window-permissions.hpp"
@@ -1655,16 +1656,15 @@ void OBSBasic::ReplayBufferClicked()
void OBSBasic::AddVCamButton()
{
- vcamButton = new ReplayBufferButton(QTStr("Basic.Main.StartVirtualCam"),
- this);
- vcamButton->setCheckable(true);
- connect(vcamButton.data(), &QPushButton::clicked, this,
- &OBSBasic::VCamButtonClicked);
+ OBSBasicVCamConfig::Init();
- vcamButton->setProperty("themeID", "vcamButton");
- ui->buttonsVLayout->insertWidget(2, vcamButton);
- setTabOrder(ui->recordButton, vcamButton);
- setTabOrder(vcamButton, ui->modeSwitch);
+ vcamButton = new ControlsSplitButton(
+ QTStr("Basic.Main.StartVirtualCam"), "vcamButton",
+ &OBSBasic::VCamButtonClicked);
+ vcamButton->addIcon(QTStr("Basic.Main.VirtualCamConfig"),
+ QStringLiteral("configIconSmall"),
+ &OBSBasic::VCamConfigButtonClicked);
+ vcamButton->insert(2);
}
void OBSBasic::ResetOutputs()
@@ -1680,28 +1680,13 @@ void OBSBasic::ResetOutputs()
: CreateSimpleOutputHandler(this));
delete replayBufferButton;
- delete replayLayout;
if (outputHandler->replayBuffer) {
- replayBufferButton = new ReplayBufferButton(
- QTStr("Basic.Main.StartReplayBuffer"), this);
- replayBufferButton->setCheckable(true);
- connect(replayBufferButton.data(),
- &QPushButton::clicked, this,
+ replayBufferButton = new ControlsSplitButton(
+ QTStr("Basic.Main.StartReplayBuffer"),
+ "replayBufferButton",
&OBSBasic::ReplayBufferClicked);
-
- replayBufferButton->setSizePolicy(QSizePolicy::Ignored,
- QSizePolicy::Fixed);
-
- replayLayout = new QHBoxLayout(this);
- replayLayout->addWidget(replayBufferButton);
-
- replayBufferButton->setProperty("themeID",
- "replayBufferButton");
- ui->buttonsVLayout->insertLayout(2, replayLayout);
- setTabOrder(ui->recordButton, replayBufferButton);
- setTabOrder(replayBufferButton,
- ui->buttonsVLayout->itemAt(3)->widget());
+ replayBufferButton->insert(2);
}
if (sysTrayReplayBuffer)
@@ -7354,19 +7339,19 @@ void OBSBasic::StartReplayBuffer()
return;
if (!UIValidation::NoSourcesConfirmation(this)) {
- replayBufferButton->setChecked(false);
+ replayBufferButton->first()->setChecked(false);
return;
}
if (!OutputPathValid()) {
OutputPathInvalidMessage();
- replayBufferButton->setChecked(false);
+ replayBufferButton->first()->setChecked(false);
return;
}
if (LowDiskSpace()) {
DiskSpaceMessage();
- replayBufferButton->setChecked(false);
+ replayBufferButton->first()->setChecked(false);
return;
}
@@ -7376,7 +7361,7 @@ void OBSBasic::StartReplayBuffer()
SaveProject();
if (!outputHandler->StartReplayBuffer()) {
- replayBufferButton->setChecked(false);
+ replayBufferButton->first()->setChecked(false);
} else if (os_atomic_load_bool(&recording_paused)) {
ShowReplayBufferPauseWarning();
}
@@ -7387,10 +7372,12 @@ void OBSBasic::ReplayBufferStopping()
if (!outputHandler || !outputHandler->replayBuffer)
return;
- replayBufferButton->setText(QTStr("Basic.Main.StoppingReplayBuffer"));
+ replayBufferButton->first()->setText(
+ QTStr("Basic.Main.StoppingReplayBuffer"));
if (sysTrayReplayBuffer)
- sysTrayReplayBuffer->setText(replayBufferButton->text());
+ sysTrayReplayBuffer->setText(
+ replayBufferButton->first()->text());
replayBufferStopping = true;
if (api)
@@ -7415,11 +7402,13 @@ void OBSBasic::ReplayBufferStart()
if (!outputHandler || !outputHandler->replayBuffer)
return;
- replayBufferButton->setText(QTStr("Basic.Main.StopReplayBuffer"));
- replayBufferButton->setChecked(true);
+ replayBufferButton->first()->setText(
+ QTStr("Basic.Main.StopReplayBuffer"));
+ replayBufferButton->first()->setChecked(true);
if (sysTrayReplayBuffer)
- sysTrayReplayBuffer->setText(replayBufferButton->text());
+ sysTrayReplayBuffer->setText(
+ replayBufferButton->first()->text());
replayBufferStopping = false;
if (api)
@@ -7472,11 +7461,13 @@ void OBSBasic::ReplayBufferStop(int code)
if (!outputHandler || !outputHandler->replayBuffer)
return;
- replayBufferButton->setText(QTStr("Basic.Main.StartReplayBuffer"));
- replayBufferButton->setChecked(false);
+ replayBufferButton->first()->setText(
+ QTStr("Basic.Main.StartReplayBuffer"));
+ replayBufferButton->first()->setChecked(false);
if (sysTrayReplayBuffer)
- sysTrayReplayBuffer->setText(replayBufferButton->text());
+ sysTrayReplayBuffer->setText(
+ replayBufferButton->first()->text());
blog(LOG_INFO, REPLAY_BUFFER_STOP);
@@ -7525,7 +7516,7 @@ void OBSBasic::StartVirtualCam()
SaveProject();
if (!outputHandler->StartVirtualCam()) {
- vcamButton->setChecked(false);
+ vcamButton->first()->setChecked(false);
}
}
@@ -7547,10 +7538,10 @@ void OBSBasic::OnVirtualCamStart()
if (!outputHandler || !outputHandler->virtualCam)
return;
- vcamButton->setText(QTStr("Basic.Main.StopVirtualCam"));
+ vcamButton->first()->setText(QTStr("Basic.Main.StopVirtualCam"));
if (sysTrayVirtualCam)
sysTrayVirtualCam->setText(QTStr("Basic.Main.StopVirtualCam"));
- vcamButton->setChecked(true);
+ vcamButton->first()->setChecked(true);
if (api)
api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STARTED);
@@ -7565,10 +7556,10 @@ void OBSBasic::OnVirtualCamStop(int)
if (!outputHandler || !outputHandler->virtualCam)
return;
- vcamButton->setText(QTStr("Basic.Main.StartVirtualCam"));
+ vcamButton->first()->setText(QTStr("Basic.Main.StartVirtualCam"));
if (sysTrayVirtualCam)
sysTrayVirtualCam->setText(QTStr("Basic.Main.StartVirtualCam"));
- vcamButton->setChecked(false);
+ vcamButton->first()->setChecked(false);
if (api)
api->on_event(OBS_FRONTEND_EVENT_VIRTUALCAM_STOPPED);
@@ -7720,7 +7711,7 @@ void OBSBasic::VCamButtonClicked()
StopVirtualCam();
} else {
if (!UIValidation::NoSourcesConfirmation(this)) {
- vcamButton->setChecked(false);
+ vcamButton->first()->setChecked(false);
return;
}
@@ -7728,6 +7719,12 @@ void OBSBasic::VCamButtonClicked()
}
}
+void OBSBasic::VCamConfigButtonClicked()
+{
+ OBSBasicVCamConfig config(this);
+ config.exec();
+}
+
void OBSBasic::on_settingsButton_clicked()
{
on_action_Settings_triggered();
@@ -9854,6 +9851,7 @@ void OBSBasic::PauseRecording()
os_atomic_set_bool(&recording_paused, true);
+ auto replay = replayBufferButton->second();
if (replay)
replay->setEnabled(false);
@@ -9898,6 +9896,7 @@ void OBSBasic::UnpauseRecording()
os_atomic_set_bool(&recording_paused, false);
+ auto replay = replayBufferButton->second();
if (replay)
replay->setEnabled(true);
@@ -9974,28 +9973,13 @@ void OBSBasic::UpdateReplayBuffer(bool activate)
{
if (!activate || !outputHandler ||
!outputHandler->ReplayBufferActive()) {
- replay.reset();
+ replayBufferButton->removeIcon();
return;
}
- replay.reset(new QPushButton());
- replay->setAccessibleName(QTStr("Basic.Main.SaveReplay"));
- replay->setToolTip(QTStr("Basic.Main.SaveReplay"));
- replay->setChecked(false);
- replay->setProperty("themeID",
- QVariant(QStringLiteral("replayIconSmall")));
-
- QSizePolicy sp;
- sp.setHeightForWidth(true);
- replay->setSizePolicy(sp);
-
- connect(replay.data(), &QAbstractButton::clicked, this,
- &OBSBasic::ReplayBufferSave);
- replayLayout->addWidget(replay.data());
- setTabOrder(replayLayout->itemAt(0)->widget(),
- replayLayout->itemAt(1)->widget());
- setTabOrder(replayLayout->itemAt(1)->widget(),
- ui->buttonsVLayout->itemAt(3)->widget());
+ replayBufferButton->addIcon(QTStr("Basic.Main.SaveReplay"),
+ QStringLiteral("replayIconSmall"),
+ &OBSBasic::ReplayBufferSave);
}
#define MBYTE (1024ULL * 1024ULL)
diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp
index d85265858..284b0b7c3 100644
--- a/UI/window-basic-main.hpp
+++ b/UI/window-basic-main.hpp
@@ -178,7 +178,7 @@ class OBSBasic : public OBSMainWindow {
friend class AutoConfig;
friend class AutoConfigStreamPage;
friend class RecordButton;
- friend class ReplayBufferButton;
+ friend class ControlsSplitButton;
friend class ExtraBrowsersModel;
friend class ExtraBrowsersDelegate;
friend class DeviceCaptureToolbar;
@@ -299,12 +299,10 @@ private:
QPointer startStreamMenu;
QPointer transitionButton;
- QPointer replayBufferButton;
- QPointer replayLayout;
+ QPointer replayBufferButton;
QScopedPointer pause;
- QScopedPointer replay;
- QPointer vcamButton;
+ QPointer vcamButton;
bool vcamEnabled = false;
QScopedPointer trayIcon;
@@ -1062,6 +1060,7 @@ private slots:
void on_streamButton_clicked();
void on_recordButton_clicked();
void VCamButtonClicked();
+ void VCamConfigButtonClicked();
void on_settingsButton_clicked();
void Screenshot(OBSSource source_ = nullptr);
void ScreenshotSelectedSource();
diff --git a/UI/window-basic-vcam-config.cpp b/UI/window-basic-vcam-config.cpp
new file mode 100644
index 000000000..5f21f71e7
--- /dev/null
+++ b/UI/window-basic-vcam-config.cpp
@@ -0,0 +1,264 @@
+#include "window-basic-vcam-config.hpp"
+#include "window-basic-main.hpp"
+#include "qt-wrappers.hpp"
+#include "remote-text.hpp"
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+enum class VCamOutputType {
+ Internal,
+ Scene,
+ Source,
+};
+
+enum class VCamInternalType {
+ Default,
+ Preview,
+};
+
+struct VCamConfig {
+ VCamOutputType type = VCamOutputType::Internal;
+ VCamInternalType internal = VCamInternalType::Default;
+ string scene;
+ string source;
+};
+
+static VCamConfig *vCamConfig = nullptr;
+
+OBSBasicVCamConfig::OBSBasicVCamConfig(QWidget *parent)
+ : QDialog(parent), ui(new Ui::OBSBasicVCamConfig)
+{
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ ui->setupUi(this);
+
+ auto type = (int)vCamConfig->type;
+ ui->outputType->setCurrentIndex(type);
+ OutputTypeChanged(type);
+ connect(ui->outputType,
+ static_cast(
+ &QComboBox::currentIndexChanged),
+ this, &OBSBasicVCamConfig::OutputTypeChanged);
+
+ auto start = ui->buttonBox->button(QDialogButtonBox::Ok);
+ if (!obs_frontend_virtualcam_active())
+ start->setText(QTStr("Basic.VCam.Start"));
+ else
+ start->setText(QTStr("Basic.VCam.Update"));
+ connect(start, &QPushButton::clicked, this,
+ &OBSBasicVCamConfig::SaveAndStart);
+}
+
+void OBSBasicVCamConfig::OutputTypeChanged(int type)
+{
+ auto list = ui->outputSelection;
+ list->clear();
+
+ switch ((VCamOutputType)type) {
+ case VCamOutputType::Internal:
+ list->addItem(QTStr("Basic.VCam.InternalDefault"));
+ list->addItem(QTStr("Basic.VCam.InternalPreview"));
+ list->setCurrentIndex((int)vCamConfig->internal);
+ break;
+
+ case VCamOutputType::Scene: {
+ // Scenes in default order
+ BPtr scenes = obs_frontend_get_scene_names();
+ int idx = 0;
+ for (char **temp = scenes; *temp; temp++) {
+ list->addItem(*temp);
+
+ if (vCamConfig->scene.compare(*temp) == 0)
+ list->setCurrentIndex(list->count() - 1);
+ }
+ break;
+ }
+
+ case VCamOutputType::Source: {
+ // Sources in alphabetical order
+ vector sources;
+ auto AddSource = [&](obs_source_t *source) {
+ auto name = obs_source_get_name(source);
+ auto flags = obs_source_get_output_flags(source);
+
+ if (!(obs_source_get_output_flags(source) &
+ OBS_SOURCE_VIDEO))
+ return;
+
+ sources.push_back(name);
+ };
+ using AddSource_t = decltype(AddSource);
+
+ obs_enum_sources(
+ [](void *data, obs_source_t *source) {
+ auto &AddSource =
+ *static_cast(data);
+ if (!obs_source_removed(source))
+ AddSource(source);
+ return true;
+ },
+ static_cast(&AddSource));
+
+ // Sort and select current item
+ sort(sources.begin(), sources.end());
+ for (auto &&source : sources) {
+ list->addItem(source.c_str());
+
+ if (vCamConfig->source == source)
+ list->setCurrentIndex(list->count() - 1);
+ }
+ break;
+ }
+ }
+}
+
+void OBSBasicVCamConfig::SaveAndStart()
+{
+ auto type = (VCamOutputType)ui->outputType->currentIndex();
+ auto out = ui->outputSelection;
+ switch (type) {
+ case VCamOutputType::Internal:
+ vCamConfig->internal = (VCamInternalType)out->currentIndex();
+ break;
+ case VCamOutputType::Scene:
+ vCamConfig->scene = out->currentText().toStdString();
+ break;
+ case VCamOutputType::Source:
+ vCamConfig->source = out->currentText().toStdString();
+ break;
+ default:
+ // unknown value, don't save type
+ return;
+ }
+
+ vCamConfig->type = type;
+
+ // Start the vcam if needed, if already running just update the source
+ if (!obs_frontend_virtualcam_active())
+ obs_frontend_start_virtualcam();
+ else
+ UpdateOutputSource();
+}
+
+static void SaveCallback(obs_data_t *data, bool saving, void *)
+{
+ if (saving) {
+ OBSDataAutoRelease obj = obs_data_create();
+
+ obs_data_set_int(obj, "type", (int)vCamConfig->type);
+ obs_data_set_int(obj, "internal", (int)vCamConfig->internal);
+ obs_data_set_string(obj, "scene", vCamConfig->scene.c_str());
+ obs_data_set_string(obj, "source", vCamConfig->source.c_str());
+
+ obs_data_set_obj(data, "virtual-camera", obj);
+ } else {
+ OBSDataAutoRelease obj =
+ obs_data_get_obj(data, "virtual-camera");
+
+ vCamConfig->type =
+ (VCamOutputType)obs_data_get_int(obj, "type");
+ vCamConfig->internal =
+ (VCamInternalType)obs_data_get_int(obj, "internal");
+ vCamConfig->scene = obs_data_get_string(obj, "scene");
+ vCamConfig->source = obs_data_get_string(obj, "source");
+ }
+}
+
+static void EventCallback(enum obs_frontend_event event, void *)
+{
+ if (vCamConfig->type != VCamOutputType::Internal)
+ return;
+
+ // Update output source if the preview scene changes
+ // or if the default transition is changed
+ switch (event) {
+ case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
+ if (vCamConfig->internal != VCamInternalType::Preview)
+ return;
+ break;
+ case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
+ if (vCamConfig->internal != VCamInternalType::Default)
+ return;
+ break;
+ default:
+ return;
+ }
+
+ OBSBasicVCamConfig::UpdateOutputSource();
+}
+
+void OBSBasicVCamConfig::Init()
+{
+ if (vCamConfig)
+ return;
+
+ vCamConfig = new VCamConfig;
+
+ obs_frontend_add_save_callback(SaveCallback, nullptr);
+ obs_frontend_add_event_callback(EventCallback, nullptr);
+}
+
+static obs_view_t *view = nullptr;
+static video_t *video = nullptr;
+
+video_t *OBSBasicVCamConfig::StartVideo()
+{
+ if (!video) {
+ view = obs_view_create();
+ video = obs_view_add(view);
+ }
+ UpdateOutputSource();
+ return video;
+}
+
+void OBSBasicVCamConfig::StopVideo()
+{
+ if (view) {
+ obs_view_remove(view);
+ obs_view_set_source(view, 0, nullptr);
+ obs_view_destroy(view);
+ view = nullptr;
+ }
+ video = nullptr;
+}
+
+void OBSBasicVCamConfig::UpdateOutputSource()
+{
+ if (!view)
+ return;
+
+ obs_source_t *source = nullptr;
+
+ switch ((VCamOutputType)vCamConfig->type) {
+ case VCamOutputType::Internal:
+ switch (vCamConfig->internal) {
+ case VCamInternalType::Default:
+ source = obs_get_output_source(0);
+ break;
+ case VCamInternalType::Preview:
+ OBSSource s = OBSBasic::Get()->GetCurrentSceneSource();
+ obs_source_get_ref(s);
+ source = s;
+ break;
+ }
+ break;
+
+ case VCamOutputType::Scene:
+ source = obs_get_source_by_name(vCamConfig->scene.c_str());
+ break;
+
+ case VCamOutputType::Source:
+ source = obs_get_source_by_name(vCamConfig->source.c_str());
+ break;
+ }
+
+ auto current = obs_view_get_source(view, 0);
+ if (source != current)
+ obs_view_set_source(view, 0, source);
+ obs_source_release(source);
+ obs_source_release(current);
+}
diff --git a/UI/window-basic-vcam-config.hpp b/UI/window-basic-vcam-config.hpp
new file mode 100644
index 000000000..4a00e0219
--- /dev/null
+++ b/UI/window-basic-vcam-config.hpp
@@ -0,0 +1,27 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "ui_OBSBasicVCamConfig.h"
+
+class OBSBasicVCamConfig : public QDialog {
+ Q_OBJECT
+
+public:
+ static void Init();
+
+ static video_t *StartVideo();
+ static void StopVideo();
+ static void UpdateOutputSource();
+
+ explicit OBSBasicVCamConfig(QWidget *parent = 0);
+
+private slots:
+ void OutputTypeChanged(int type);
+ void SaveAndStart();
+
+private:
+ std::unique_ptr ui;
+};
diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c
index b03487ac5..38371180a 100644
--- a/libobs/obs-encoder.c
+++ b/libobs/obs-encoder.c
@@ -187,7 +187,7 @@ static inline bool has_scaling(const struct obs_encoder *encoder)
static inline bool gpu_encode_available(const struct obs_encoder *encoder)
{
- struct obs_core_video *const video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
return (encoder->info.caps & OBS_ENCODER_CAP_PASS_TEXTURE) != 0 &&
(video->using_p010_tex || video->using_nv12_tex);
}
diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h
index 86c4627fa..32effee37 100644
--- a/libobs/obs-internal.h
+++ b/libobs/obs-internal.h
@@ -245,8 +245,9 @@ struct obs_task_info {
void *param;
};
-struct obs_core_video {
- graphics_t *graphics;
+struct obs_core_video_mix {
+ struct obs_view *view;
+
gs_stagesurf_t *active_copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];
gs_stagesurf_t *copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];
gs_texture_t *convert_textures[NUM_CHANNELS];
@@ -264,6 +265,41 @@ struct obs_core_video {
bool using_p010_tex;
struct circlebuf vframe_info_buffer;
struct circlebuf vframe_info_buffer_gpu;
+ gs_stagesurf_t *mapped_surfaces[NUM_CHANNELS];
+ int cur_texture;
+ volatile long raw_active;
+ volatile long gpu_encoder_active;
+ bool gpu_was_active;
+ bool raw_was_active;
+ bool was_active;
+ pthread_mutex_t gpu_encoder_mutex;
+ struct circlebuf gpu_encoder_queue;
+ struct circlebuf gpu_encoder_avail_queue;
+ DARRAY(obs_encoder_t *) gpu_encoders;
+ os_sem_t *gpu_encode_semaphore;
+ os_event_t *gpu_encode_inactive;
+ pthread_t gpu_encode_thread;
+ bool gpu_encode_thread_initialized;
+ volatile bool gpu_encode_stop;
+
+ video_t *video;
+
+ bool gpu_conversion;
+ const char *conversion_techs[NUM_CHANNELS];
+ bool conversion_needed;
+ float conversion_width_i;
+ float conversion_height_i;
+
+ float color_matrix[16];
+ enum obs_scale_type scale_type;
+};
+
+extern int obs_init_video_mix(struct obs_video_info *ovi,
+ struct obs_core_video_mix *video);
+extern void obs_free_video_mix(struct obs_core_video_mix *video);
+
+struct obs_core_video {
+ graphics_t *graphics;
gs_effect_t *default_effect;
gs_effect_t *default_rect_effect;
gs_effect_t *opaque_effect;
@@ -276,42 +312,19 @@ struct obs_core_video {
gs_effect_t *bilinear_lowres_effect;
gs_effect_t *premultiplied_alpha_effect;
gs_samplerstate_t *point_sampler;
- gs_stagesurf_t *mapped_surfaces[NUM_CHANNELS];
- int cur_texture;
- volatile long raw_active;
- volatile long gpu_encoder_active;
- pthread_mutex_t gpu_encoder_mutex;
- struct circlebuf gpu_encoder_queue;
- struct circlebuf gpu_encoder_avail_queue;
- DARRAY(obs_encoder_t *) gpu_encoders;
- os_sem_t *gpu_encode_semaphore;
- os_event_t *gpu_encode_inactive;
- pthread_t gpu_encode_thread;
- bool gpu_encode_thread_initialized;
- volatile bool gpu_encode_stop;
uint64_t video_time;
uint64_t video_frame_interval_ns;
+ uint64_t video_half_frame_interval_ns;
uint64_t video_avg_frame_time_ns;
double video_fps;
- video_t *video;
pthread_t video_thread;
uint32_t total_frames;
uint32_t lagged_frames;
bool thread_initialized;
- bool gpu_conversion;
- const char *conversion_techs[NUM_CHANNELS];
- bool conversion_needed;
- float conversion_width_i;
- float conversion_height_i;
-
- uint32_t output_width;
- uint32_t output_height;
uint32_t base_width;
uint32_t base_height;
- float color_matrix[16];
- enum obs_scale_type scale_type;
gs_texture_t *transparent_texture;
@@ -330,6 +343,10 @@ struct obs_core_video {
pthread_mutex_t task_mutex;
struct circlebuf tasks;
+
+ pthread_mutex_t mixes_mutex;
+ DARRAY(struct obs_core_video_mix) mixes;
+ struct obs_core_video_mix *main_mix;
};
struct audio_monitor;
@@ -463,11 +480,6 @@ struct obs_graphics_context {
uint64_t frame_time_total_ns;
uint64_t fps_total_ns;
uint32_t fps_total_frames;
-#ifdef _WIN32
- bool gpu_was_active;
-#endif
- bool raw_was_active;
- bool was_active;
const char *video_thread_name;
};
diff --git a/libobs/obs-source-deinterlace.c b/libobs/obs-source-deinterlace.c
index ca0db4244..d8906d0c3 100644
--- a/libobs/obs-source-deinterlace.c
+++ b/libobs/obs-source-deinterlace.c
@@ -148,7 +148,6 @@ static inline uint64_t uint64_diff(uint64_t ts1, uint64_t ts2)
static inline void deinterlace_get_closest_frames(obs_source_t *s,
uint64_t sys_time)
{
- const struct video_output_info *info;
uint64_t half_interval;
if (s->async_unbuffered && s->deinterlace_offset) {
@@ -169,9 +168,7 @@ static inline void deinterlace_get_closest_frames(obs_source_t *s,
if (!s->async_frames.num)
return;
- info = video_output_get_info(obs->video.video);
- half_interval = (uint64_t)info->fps_den * 500000000ULL /
- (uint64_t)info->fps_num;
+ half_interval = obs->video.video_half_frame_interval_ns;
if (first_frame(s) || ready_deinterlace_frames(s, sys_time)) {
uint64_t offset;
diff --git a/libobs/obs-video-gpu-encode.c b/libobs/obs-video-gpu-encode.c
index e98f35283..0dfb11df0 100644
--- a/libobs/obs-video-gpu-encode.c
+++ b/libobs/obs-video-gpu-encode.c
@@ -17,14 +17,12 @@
#include "obs-internal.h"
-static void *gpu_encode_thread(void *unused)
+static void *gpu_encode_thread(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
- uint64_t interval = video_output_get_frame_time(obs->video.video);
+ uint64_t interval = video_output_get_frame_time(video->video);
DARRAY(obs_encoder_t *) encoders;
int wait_frames = NUM_ENCODE_TEXTURE_FRAMES_TO_WAIT;
- UNUSED_PARAMETER(unused);
da_init(encoders);
os_set_thread_name("obs gpu encode thread");
@@ -149,10 +147,11 @@ static void *gpu_encode_thread(void *unused)
return NULL;
}
-bool init_gpu_encoding(struct obs_core_video *video)
+bool init_gpu_encoding(struct obs_core_video_mix *video)
{
#ifdef _WIN32
- struct obs_video_info *ovi = &video->ovi;
+ const struct video_output_info *info =
+ video_output_get_info(video->video);
video->gpu_encode_stop = false;
@@ -161,16 +160,14 @@ bool init_gpu_encoding(struct obs_core_video *video)
gs_texture_t *tex;
gs_texture_t *tex_uv;
- if (ovi->output_format == VIDEO_FORMAT_P010) {
- gs_texture_create_p010(&tex, &tex_uv, ovi->output_width,
- ovi->output_height,
- GS_RENDER_TARGET |
- GS_SHARED_KM_TEX);
+ if (info->format == VIDEO_FORMAT_P010) {
+ gs_texture_create_p010(
+ &tex, &tex_uv, info->width, info->height,
+ GS_RENDER_TARGET | GS_SHARED_KM_TEX);
} else {
- gs_texture_create_nv12(&tex, &tex_uv, ovi->output_width,
- ovi->output_height,
- GS_RENDER_TARGET |
- GS_SHARED_KM_TEX);
+ gs_texture_create_nv12(
+ &tex, &tex_uv, info->width, info->height,
+ GS_RENDER_TARGET | GS_SHARED_KM_TEX);
}
if (!tex) {
return false;
@@ -191,7 +188,7 @@ bool init_gpu_encoding(struct obs_core_video *video)
0)
return false;
if (pthread_create(&video->gpu_encode_thread, NULL, gpu_encode_thread,
- NULL) != 0)
+ video) != 0)
return false;
os_event_signal(video->gpu_encode_inactive);
@@ -204,7 +201,7 @@ bool init_gpu_encoding(struct obs_core_video *video)
#endif
}
-void stop_gpu_encoding_thread(struct obs_core_video *video)
+void stop_gpu_encoding_thread(struct obs_core_video_mix *video)
{
if (video->gpu_encode_thread_initialized) {
os_atomic_set_bool(&video->gpu_encode_stop, true);
@@ -214,7 +211,7 @@ void stop_gpu_encoding_thread(struct obs_core_video *video)
}
}
-void free_gpu_encoding(struct obs_core_video *video)
+void free_gpu_encoding(struct obs_core_video_mix *video)
{
if (video->gpu_encode_semaphore) {
os_sem_destroy(video->gpu_encode_semaphore);
diff --git a/libobs/obs-video.c b/libobs/obs-video.c
index 4ac2deff3..2584ed9be 100644
--- a/libobs/obs-video.c
+++ b/libobs/obs-video.c
@@ -37,8 +37,7 @@ static uint64_t tick_sources(uint64_t cur_time, uint64_t last_time)
float seconds;
if (!last_time)
- last_time = cur_time -
- video_output_get_frame_time(obs->video.video);
+ last_time = cur_time - obs->video.video_frame_interval_ns;
delta_time = cur_time - last_time;
seconds = (float)((double)delta_time / 1000000000.0);
@@ -113,7 +112,7 @@ static inline void set_render_size(uint32_t width, uint32_t height)
gs_set_viewport(0, 0, width, height);
}
-static inline void unmap_last_surface(struct obs_core_video *video)
+static inline void unmap_last_surface(struct obs_core_video_mix *video)
{
for (int c = 0; c < NUM_CHANNELS; ++c) {
if (video->mapped_surfaces[c]) {
@@ -124,8 +123,11 @@ static inline void unmap_last_surface(struct obs_core_video *video)
}
static const char *render_main_texture_name = "render_main_texture";
-static inline void render_main_texture(struct obs_core_video *video)
+static inline void render_main_texture(struct obs_core_video_mix *video)
{
+ uint32_t base_width = obs->video.base_width;
+ uint32_t base_height = obs->video.base_height;
+
profile_start(render_main_texture_name);
GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_MAIN_TEXTURE,
render_main_texture_name);
@@ -137,7 +139,7 @@ static inline void render_main_texture(struct obs_core_video *video)
video->render_space);
gs_clear(GS_CLEAR_COLOR, &clear_color, 1.0f, 0);
- set_render_size(video->base_width, video->base_height);
+ set_render_size(base_width, base_height);
pthread_mutex_lock(&obs->data.draw_callbacks_mutex);
@@ -145,13 +147,12 @@ static inline void render_main_texture(struct obs_core_video *video)
struct draw_callback *callback;
callback = obs->data.draw_callbacks.array + (i - 1);
- callback->draw(callback->param, video->base_width,
- video->base_height);
+ callback->draw(callback->param, base_width, base_height);
}
pthread_mutex_unlock(&obs->data.draw_callbacks_mutex);
- obs_view_render(&obs->data.main_view);
+ obs_view_render(video->view);
video->texture_rendered = true;
@@ -160,17 +161,21 @@ static inline void render_main_texture(struct obs_core_video *video)
}
static inline gs_effect_t *
-get_scale_effect_internal(struct obs_core_video *video)
+get_scale_effect_internal(struct obs_core_video_mix *mix)
{
+ struct obs_core_video *video = &obs->video;
+ const struct video_output_info *info =
+ video_output_get_info(mix->video);
+
/* if the dimension is under half the size of the original image,
* bicubic/lanczos can't sample enough pixels to create an accurate
* image, so use the bilinear low resolution effect instead */
- if (video->output_width < (video->base_width / 2) &&
- video->output_height < (video->base_height / 2)) {
+ if (info->width < (video->base_width / 2) &&
+ info->height < (video->base_height / 2)) {
return video->bilinear_lowres_effect;
}
- switch (video->scale_type) {
+ switch (mix->scale_type) {
case OBS_SCALE_BILINEAR:
return video->default_effect;
case OBS_SCALE_LANCZOS:
@@ -193,15 +198,17 @@ static inline bool resolution_close(struct obs_core_video *video,
return labs(width_cmp) <= 16 && labs(height_cmp) <= 16;
}
-static inline gs_effect_t *get_scale_effect(struct obs_core_video *video,
+static inline gs_effect_t *get_scale_effect(struct obs_core_video_mix *mix,
uint32_t width, uint32_t height)
{
+ struct obs_core_video *video = &obs->video;
+
if (resolution_close(video, width, height)) {
return video->default_effect;
} else {
/* if the scale method couldn't be loaded, use either bicubic
* or bilinear by default */
- gs_effect_t *effect = get_scale_effect_internal(video);
+ gs_effect_t *effect = get_scale_effect_internal(mix);
if (!effect)
effect = !!video->bicubic_effect
? video->bicubic_effect
@@ -211,17 +218,19 @@ static inline gs_effect_t *get_scale_effect(struct obs_core_video *video,
}
static const char *render_output_texture_name = "render_output_texture";
-static inline gs_texture_t *render_output_texture(struct obs_core_video *video)
+static inline gs_texture_t *
+render_output_texture(struct obs_core_video_mix *mix)
{
- gs_texture_t *texture = video->render_texture;
- gs_texture_t *target = video->output_texture;
+ struct obs_core_video *video = &obs->video;
+ gs_texture_t *texture = mix->render_texture;
+ gs_texture_t *target = mix->output_texture;
uint32_t width = gs_texture_get_width(target);
uint32_t height = gs_texture_get_height(target);
- gs_effect_t *effect = get_scale_effect(video, width, height);
+ gs_effect_t *effect = get_scale_effect(mix, width, height);
gs_technique_t *tech;
- if (video->ovi.output_format == VIDEO_FORMAT_RGBA) {
+ if (video_output_get_format(mix->video) == VIDEO_FORMAT_RGBA) {
tech = gs_effect_get_technique(effect, "DrawAlphaDivide");
} else {
if ((effect == video->default_effect) &&
@@ -298,13 +307,13 @@ static void render_convert_plane(gs_effect_t *effect, gs_texture_t *target,
}
static const char *render_convert_texture_name = "render_convert_texture";
-static void render_convert_texture(struct obs_core_video *video,
+static void render_convert_texture(struct obs_core_video_mix *video,
gs_texture_t *const *const convert_textures,
gs_texture_t *texture)
{
profile_start(render_convert_texture_name);
- gs_effect_t *effect = video->conversion_effect;
+ gs_effect_t *effect = obs->video.conversion_effect;
gs_eparam_t *color_vec0 =
gs_effect_get_param_by_name(effect, "color_vec0");
gs_eparam_t *color_vec1 =
@@ -330,7 +339,7 @@ static void render_convert_texture(struct obs_core_video *video,
if (convert_textures[0]) {
const float hdr_nominal_peak_level =
- video->hdr_nominal_peak_level;
+ obs->video.hdr_nominal_peak_level;
const float multiplier =
obs_get_video_sdr_white_level() / 10000.f;
gs_effect_set_texture(image, texture);
@@ -381,7 +390,7 @@ static void render_convert_texture(struct obs_core_video *video,
static const char *stage_output_texture_name = "stage_output_texture";
static inline void
-stage_output_texture(struct obs_core_video *video, int cur_texture,
+stage_output_texture(struct obs_core_video_mix *video, int cur_texture,
gs_texture_t *const *const convert_textures,
gs_stagesurf_t *const *const copy_surfaces,
size_t channel_count)
@@ -418,7 +427,8 @@ stage_output_texture(struct obs_core_video *video, int cur_texture,
}
#ifdef _WIN32
-static inline bool queue_frame(struct obs_core_video *video, bool raw_active,
+static inline bool queue_frame(struct obs_core_video_mix *video,
+ bool raw_active,
struct obs_vframe_info *vframe_info)
{
bool duplicate =
@@ -480,7 +490,7 @@ finish:
extern void full_stop(struct obs_encoder *encoder);
-static inline void encode_gpu(struct obs_core_video *video, bool raw_active,
+static inline void encode_gpu(struct obs_core_video_mix *video, bool raw_active,
struct obs_vframe_info *vframe_info)
{
while (queue_frame(video, raw_active, vframe_info))
@@ -488,7 +498,8 @@ static inline void encode_gpu(struct obs_core_video *video, bool raw_active,
}
static const char *output_gpu_encoders_name = "output_gpu_encoders";
-static void output_gpu_encoders(struct obs_core_video *video, bool raw_active)
+static void output_gpu_encoders(struct obs_core_video_mix *video,
+ bool raw_active)
{
profile_start(output_gpu_encoders_name);
@@ -510,8 +521,9 @@ end:
}
#endif
-static inline void render_video(struct obs_core_video *video, bool raw_active,
- const bool gpu_active, int cur_texture)
+static inline void render_video(struct obs_core_video_mix *video,
+ bool raw_active, const bool gpu_active,
+ int cur_texture)
{
gs_begin_scene();
@@ -559,7 +571,7 @@ static inline void render_video(struct obs_core_video *video, bool raw_active,
gs_end_scene();
}
-static inline bool download_frame(struct obs_core_video *video,
+static inline bool download_frame(struct obs_core_video_mix *video,
int prev_texture, struct video_data *frame)
{
if (!video->textures_copied[prev_texture])
@@ -763,7 +775,7 @@ static inline void copy_rgbx_frame(struct video_frame *output,
}
}
-static inline void output_video_data(struct obs_core_video *video,
+static inline void output_video_data(struct obs_core_video_mix *video,
struct video_data *input_frame, int count)
{
const struct video_output_info *info;
@@ -786,8 +798,7 @@ static inline void output_video_data(struct obs_core_video *video,
}
}
-static inline void video_sleep(struct obs_core_video *video, bool raw_active,
- const bool gpu_active, uint64_t *p_time,
+static inline void video_sleep(struct obs_core_video *video, uint64_t *p_time,
uint64_t interval_ns)
{
struct obs_vframe_info vframe_info;
@@ -815,12 +826,20 @@ static inline void video_sleep(struct obs_core_video *video, bool raw_active,
vframe_info.timestamp = cur_time;
vframe_info.count = count;
- if (raw_active)
- circlebuf_push_back(&video->vframe_info_buffer, &vframe_info,
- sizeof(vframe_info));
- if (gpu_active)
- circlebuf_push_back(&video->vframe_info_buffer_gpu,
- &vframe_info, sizeof(vframe_info));
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+ struct obs_core_video_mix *video = obs->video.mixes.array + i;
+ bool raw_active = video->raw_was_active;
+ bool gpu_active = video->gpu_was_active;
+
+ if (raw_active)
+ circlebuf_push_back(&video->vframe_info_buffer,
+ &vframe_info, sizeof(vframe_info));
+ if (gpu_active)
+ circlebuf_push_back(&video->vframe_info_buffer_gpu,
+ &vframe_info, sizeof(vframe_info));
+ }
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
}
static const char *output_frame_gs_context_name = "gs_context(video->graphics)";
@@ -828,9 +847,11 @@ static const char *output_frame_render_video_name = "render_video";
static const char *output_frame_download_frame_name = "download_frame";
static const char *output_frame_gs_flush_name = "gs_flush";
static const char *output_frame_output_video_data_name = "output_video_data";
-static inline void output_frame(bool raw_active, const bool gpu_active)
+static inline void output_frame(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
+ const bool raw_active = video->raw_was_active;
+ const bool gpu_active = video->gpu_was_active;
+
int cur_texture = video->cur_texture;
int prev_texture = cur_texture == 0 ? NUM_TEXTURES - 1
: cur_texture - 1;
@@ -840,7 +861,7 @@ static inline void output_frame(bool raw_active, const bool gpu_active)
memset(&frame, 0, sizeof(struct video_data));
profile_start(output_frame_gs_context_name);
- gs_enter_context(video->graphics);
+ gs_enter_context(obs->video.graphics);
profile_start(output_frame_render_video_name);
GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_RENDER_VIDEO,
@@ -877,28 +898,42 @@ static inline void output_frame(bool raw_active, const bool gpu_active)
video->cur_texture = 0;
}
+static inline void output_frames(void)
+{
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+ struct obs_core_video_mix *mix = obs->video.mixes.array + i;
+ if (mix->view) {
+ output_frame(mix);
+ } else {
+ obs_free_video_mix(mix);
+ da_erase(obs->video.mixes, i);
+ i--;
+ num--;
+ }
+ }
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+}
+
#define NBSP "\xC2\xA0"
-static void clear_base_frame_data(void)
+static void clear_base_frame_data(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
video->texture_rendered = false;
video->texture_converted = false;
circlebuf_free(&video->vframe_info_buffer);
video->cur_texture = 0;
}
-static void clear_raw_frame_data(void)
+static void clear_raw_frame_data(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
memset(video->textures_copied, 0, sizeof(video->textures_copied));
circlebuf_free(&video->vframe_info_buffer);
}
#ifdef _WIN32
-static void clear_gpu_frame_data(void)
+static void clear_gpu_frame_data(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
circlebuf_free(&video->vframe_info_buffer_gpu);
}
#endif
@@ -1006,35 +1041,63 @@ static void uninit_winrt_state(struct winrt_state *winrt)
static const char *tick_sources_name = "tick_sources";
static const char *render_displays_name = "render_displays";
static const char *output_frame_name = "output_frame";
-bool obs_graphics_thread_loop(struct obs_graphics_context *context)
+static inline void update_active_state(struct obs_core_video_mix *video)
{
- /* defer loop break to clean up sources */
- const bool stop_requested = video_output_stopped(obs->video.video);
+ const bool raw_was_active = video->raw_was_active;
+ const bool gpu_was_active = video->gpu_was_active;
+ const bool was_active = video->was_active;
- uint64_t frame_start = os_gettime_ns();
- uint64_t frame_time_ns;
- bool raw_active = os_atomic_load_long(&obs->video.raw_active) > 0;
+ bool raw_active = os_atomic_load_long(&video->raw_active) > 0;
#ifdef _WIN32
const bool gpu_active =
- os_atomic_load_long(&obs->video.gpu_encoder_active) > 0;
+ os_atomic_load_long(&video->gpu_encoder_active) > 0;
const bool active = raw_active || gpu_active;
#else
const bool gpu_active = 0;
const bool active = raw_active;
#endif
- if (!context->was_active && active)
- clear_base_frame_data();
- if (!context->raw_was_active && raw_active)
- clear_raw_frame_data();
+ if (!was_active && active)
+ clear_base_frame_data(video);
+ if (!raw_was_active && raw_active)
+ clear_raw_frame_data(video);
#ifdef _WIN32
- if (!context->gpu_was_active && gpu_active)
- clear_gpu_frame_data();
+ if (!gpu_was_active && gpu_active)
+ clear_gpu_frame_data(video);
- context->gpu_was_active = gpu_active;
+ video->gpu_was_active = gpu_active;
#endif
- context->raw_was_active = raw_active;
- context->was_active = active;
+ video->raw_was_active = raw_active;
+ video->was_active = active;
+}
+
+static inline void update_active_states(void)
+{
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+ update_active_state(obs->video.mixes.array + i);
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+}
+
+static inline bool stop_requested(void)
+{
+ bool success = true;
+
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+ if (!video_output_stopped(obs->video.mixes.array[i].video))
+ success = false;
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+ return success;
+}
+
+bool obs_graphics_thread_loop(struct obs_graphics_context *context)
+{
+ uint64_t frame_start = os_gettime_ns();
+ uint64_t frame_time_ns;
+
+ update_active_states();
profile_start(context->video_thread_name);
@@ -1056,7 +1119,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
#endif
profile_start(output_frame_name);
- output_frame(raw_active, gpu_active);
+ output_frames();
profile_end(output_frame_name);
profile_start(render_displays_name);
@@ -1071,8 +1134,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
profile_reenable_thread();
- video_sleep(&obs->video, raw_active, gpu_active, &obs->video.video_time,
- context->interval);
+ video_sleep(&obs->video, &obs->video.video_time, context->interval);
context->frame_time_total_ns += frame_time_ns;
context->fps_total_ns += (obs->video.video_time - context->last_time);
@@ -1091,7 +1153,7 @@ bool obs_graphics_thread_loop(struct obs_graphics_context *context)
context->fps_total_frames = 0;
}
- return !stop_requested;
+ return !stop_requested();
}
void *obs_graphics_thread(void *param)
@@ -1103,10 +1165,9 @@ void *obs_graphics_thread(void *param)
is_graphics_thread = true;
- const uint64_t interval = video_output_get_frame_time(obs->video.video);
+ const uint64_t interval = obs->video.video_frame_interval_ns;
obs->video.video_time = os_gettime_ns();
- obs->video.video_frame_interval_ns = interval;
os_set_thread_name("libobs: graphics thread");
@@ -1118,16 +1179,11 @@ void *obs_graphics_thread(void *param)
srand((unsigned int)time(NULL));
struct obs_graphics_context context;
- context.interval = video_output_get_frame_time(obs->video.video);
+ context.interval = interval;
context.frame_time_total_ns = 0;
context.fps_total_ns = 0;
context.fps_total_frames = 0;
context.last_time = 0;
-#ifdef _WIN32
- context.gpu_was_active = false;
-#endif
- context.raw_was_active = false;
- context.was_active = false;
context.video_thread_name = video_thread_name;
#ifdef __APPLE__
diff --git a/libobs/obs-view.c b/libobs/obs-view.c
index 69cf1dcde..d2092d9fd 100644
--- a/libobs/obs-view.c
+++ b/libobs/obs-view.c
@@ -139,3 +139,50 @@ void obs_view_render(obs_view_t *view)
pthread_mutex_unlock(&view->channels_mutex);
}
+
+static inline size_t find_mix_for_view(obs_view_t *view)
+{
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+ if (obs->video.mixes.array[i].view == view)
+ return i;
+ }
+
+ return DARRAY_INVALID;
+}
+
+static inline void set_main_mix()
+{
+ size_t idx = find_mix_for_view(&obs->data.main_view);
+
+ struct obs_core_video_mix *mix = NULL;
+ if (idx != DARRAY_INVALID)
+ mix = obs->video.mixes.array + idx;
+ obs->video.main_mix = mix;
+}
+
+video_t *obs_view_add(obs_view_t *view)
+{
+ struct obs_core_video_mix mix = {0};
+ mix.view = view;
+ if (obs_init_video_mix(&obs->video.ovi, &mix) != 0) {
+ obs_free_video_mix(&mix);
+ return NULL;
+ }
+
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ da_push_back(obs->video.mixes, &mix);
+ set_main_mix();
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+ return mix.video;
+}
+
+void obs_view_remove(obs_view_t *view)
+{
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ size_t idx = find_mix_for_view(view);
+ if (idx != DARRAY_INVALID)
+ obs->video.mixes.array[idx].view = NULL;
+ set_main_mix();
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+}
diff --git a/libobs/obs.c b/libobs/obs.c
index 2a1743366..95223f464 100644
--- a/libobs/obs.c
+++ b/libobs/obs.c
@@ -44,9 +44,10 @@ static inline void make_video_info(struct video_output_info *vi,
vi->cache_size = 6;
}
-static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
+static inline void calc_gpu_conversion_sizes(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
+ const struct video_output_info *info =
+ video_output_get_info(video->video);
video->conversion_needed = false;
video->conversion_techs[0] = NULL;
@@ -55,19 +56,19 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
video->conversion_width_i = 0.f;
video->conversion_height_i = 0.f;
- switch ((uint32_t)ovi->output_format) {
+ switch ((uint32_t)info->format) {
case VIDEO_FORMAT_I420:
video->conversion_needed = true;
video->conversion_techs[0] = "Planar_Y";
video->conversion_techs[1] = "Planar_U_Left";
video->conversion_techs[2] = "Planar_V_Left";
- video->conversion_width_i = 1.f / (float)ovi->output_width;
+ video->conversion_width_i = 1.f / (float)info->width;
break;
case VIDEO_FORMAT_NV12:
video->conversion_needed = true;
video->conversion_techs[0] = "NV12_Y";
video->conversion_techs[1] = "NV12_UV";
- video->conversion_width_i = 1.f / (float)ovi->output_width;
+ video->conversion_width_i = 1.f / (float)info->width;
break;
case VIDEO_FORMAT_I444:
video->conversion_needed = true;
@@ -77,13 +78,13 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
break;
case VIDEO_FORMAT_I010:
video->conversion_needed = true;
- video->conversion_width_i = 1.f / (float)ovi->output_width;
- video->conversion_height_i = 1.f / (float)ovi->output_height;
- if (ovi->colorspace == VIDEO_CS_2100_PQ) {
+ video->conversion_width_i = 1.f / (float)info->width;
+ video->conversion_height_i = 1.f / (float)info->height;
+ if (info->colorspace == VIDEO_CS_2100_PQ) {
video->conversion_techs[0] = "I010_PQ_Y";
video->conversion_techs[1] = "I010_PQ_U";
video->conversion_techs[2] = "I010_PQ_V";
- } else if (ovi->colorspace == VIDEO_CS_2100_HLG) {
+ } else if (info->colorspace == VIDEO_CS_2100_HLG) {
video->conversion_techs[0] = "I010_HLG_Y";
video->conversion_techs[1] = "I010_HLG_U";
video->conversion_techs[2] = "I010_HLG_V";
@@ -95,12 +96,12 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
break;
case VIDEO_FORMAT_P010:
video->conversion_needed = true;
- video->conversion_width_i = 1.f / (float)ovi->output_width;
- video->conversion_height_i = 1.f / (float)ovi->output_height;
- if (ovi->colorspace == VIDEO_CS_2100_PQ) {
+ video->conversion_width_i = 1.f / (float)info->width;
+ video->conversion_height_i = 1.f / (float)info->height;
+ if (info->colorspace == VIDEO_CS_2100_PQ) {
video->conversion_techs[0] = "P010_PQ_Y";
video->conversion_techs[1] = "P010_PQ_UV";
- } else if (ovi->colorspace == VIDEO_CS_2100_HLG) {
+ } else if (info->colorspace == VIDEO_CS_2100_HLG) {
video->conversion_techs[0] = "P010_HLG_Y";
video->conversion_techs[1] = "P010_HLG_UV";
} else {
@@ -110,22 +111,21 @@ static inline void calc_gpu_conversion_sizes(const struct obs_video_info *ovi)
}
}
-static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
+static bool obs_init_gpu_conversion(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
+ const struct video_output_info *info =
+ video_output_get_info(video->video);
- calc_gpu_conversion_sizes(ovi);
+ calc_gpu_conversion_sizes(video);
- video->using_nv12_tex = ovi->output_format == VIDEO_FORMAT_NV12
- ? gs_nv12_available()
- : false;
- video->using_p010_tex = ovi->output_format == VIDEO_FORMAT_P010
- ? gs_p010_available()
- : false;
+ video->using_nv12_tex =
+ info->format == VIDEO_FORMAT_NV12 ? gs_nv12_available() : false;
+ video->using_p010_tex =
+ info->format == VIDEO_FORMAT_P010 ? gs_p010_available() : false;
if (!video->conversion_needed) {
blog(LOG_INFO, "GPU conversion not available for format: %u",
- (unsigned int)ovi->output_format);
+ (unsigned int)info->format);
video->gpu_conversion = false;
video->using_nv12_tex = false;
video->using_p010_tex = false;
@@ -151,19 +151,19 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
video->convert_textures_encode[1] = NULL;
video->convert_textures_encode[2] = NULL;
if (video->using_nv12_tex) {
- if (!gs_texture_create_nv12(
- &video->convert_textures_encode[0],
- &video->convert_textures_encode[1],
- ovi->output_width, ovi->output_height,
- GS_RENDER_TARGET | GS_SHARED_KM_TEX)) {
+ if (!gs_texture_create_nv12(&video->convert_textures_encode[0],
+ &video->convert_textures_encode[1],
+ info->width, info->height,
+ GS_RENDER_TARGET |
+ GS_SHARED_KM_TEX)) {
return false;
}
} else if (video->using_p010_tex) {
- if (!gs_texture_create_p010(
- &video->convert_textures_encode[0],
- &video->convert_textures_encode[1],
- ovi->output_width, ovi->output_height,
- GS_RENDER_TARGET | GS_SHARED_KM_TEX)) {
+ if (!gs_texture_create_p010(&video->convert_textures_encode[0],
+ &video->convert_textures_encode[1],
+ info->width, info->height,
+ GS_RENDER_TARGET |
+ GS_SHARED_KM_TEX)) {
return false;
}
}
@@ -171,68 +171,66 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
bool success = true;
- const struct video_output_info *info =
- video_output_get_info(video->video);
switch (info->format) {
case VIDEO_FORMAT_I420:
video->convert_textures[0] =
- gs_texture_create(ovi->output_width, ovi->output_height,
+ gs_texture_create(info->width, info->height, GS_R8, 1,
+ NULL, GS_RENDER_TARGET);
+ video->convert_textures[1] =
+ gs_texture_create(info->width / 2, info->height / 2,
+ GS_R8, 1, NULL, GS_RENDER_TARGET);
+ video->convert_textures[2] =
+ gs_texture_create(info->width / 2, info->height / 2,
GS_R8, 1, NULL, GS_RENDER_TARGET);
- video->convert_textures[1] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1,
- NULL, GS_RENDER_TARGET);
- video->convert_textures[2] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8, 1,
- NULL, GS_RENDER_TARGET);
if (!video->convert_textures[0] ||
!video->convert_textures[1] || !video->convert_textures[2])
success = false;
break;
case VIDEO_FORMAT_NV12:
video->convert_textures[0] =
- gs_texture_create(ovi->output_width, ovi->output_height,
- GS_R8, 1, NULL, GS_RENDER_TARGET);
- video->convert_textures[1] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8G8,
- 1, NULL, GS_RENDER_TARGET);
+ gs_texture_create(info->width, info->height, GS_R8, 1,
+ NULL, GS_RENDER_TARGET);
+ video->convert_textures[1] =
+ gs_texture_create(info->width / 2, info->height / 2,
+ GS_R8G8, 1, NULL, GS_RENDER_TARGET);
if (!video->convert_textures[0] || !video->convert_textures[1])
success = false;
break;
case VIDEO_FORMAT_I444:
video->convert_textures[0] =
- gs_texture_create(ovi->output_width, ovi->output_height,
- GS_R8, 1, NULL, GS_RENDER_TARGET);
+ gs_texture_create(info->width, info->height, GS_R8, 1,
+ NULL, GS_RENDER_TARGET);
video->convert_textures[1] =
- gs_texture_create(ovi->output_width, ovi->output_height,
- GS_R8, 1, NULL, GS_RENDER_TARGET);
+ gs_texture_create(info->width, info->height, GS_R8, 1,
+ NULL, GS_RENDER_TARGET);
video->convert_textures[2] =
- gs_texture_create(ovi->output_width, ovi->output_height,
- GS_R8, 1, NULL, GS_RENDER_TARGET);
+ gs_texture_create(info->width, info->height, GS_R8, 1,
+ NULL, GS_RENDER_TARGET);
if (!video->convert_textures[0] ||
!video->convert_textures[1] || !video->convert_textures[2])
success = false;
break;
case VIDEO_FORMAT_I010:
video->convert_textures[0] =
- gs_texture_create(ovi->output_width, ovi->output_height,
+ gs_texture_create(info->width, info->height, GS_R16, 1,
+ NULL, GS_RENDER_TARGET);
+ video->convert_textures[1] =
+ gs_texture_create(info->width / 2, info->height / 2,
+ GS_R16, 1, NULL, GS_RENDER_TARGET);
+ video->convert_textures[2] =
+ gs_texture_create(info->width / 2, info->height / 2,
GS_R16, 1, NULL, GS_RENDER_TARGET);
- video->convert_textures[1] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R16,
- 1, NULL, GS_RENDER_TARGET);
- video->convert_textures[2] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R16,
- 1, NULL, GS_RENDER_TARGET);
if (!video->convert_textures[0] ||
!video->convert_textures[1] || !video->convert_textures[2])
success = false;
break;
case VIDEO_FORMAT_P010:
video->convert_textures[0] =
- gs_texture_create(ovi->output_width, ovi->output_height,
- GS_R16, 1, NULL, GS_RENDER_TARGET);
- video->convert_textures[1] = gs_texture_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_RG16,
- 1, NULL, GS_RENDER_TARGET);
+ gs_texture_create(info->width, info->height, GS_R16, 1,
+ NULL, GS_RENDER_TARGET);
+ video->convert_textures[1] =
+ gs_texture_create(info->width / 2, info->height / 2,
+ GS_RG16, 1, NULL, GS_RENDER_TARGET);
if (!video->convert_textures[0] || !video->convert_textures[1])
success = false;
break;
@@ -257,72 +255,71 @@ static bool obs_init_gpu_conversion(struct obs_video_info *ovi)
return success;
}
-static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i)
+static bool obs_init_gpu_copy_surfaces(struct obs_core_video_mix *video,
+ size_t i)
{
- struct obs_core_video *video = &obs->video;
-
const struct video_output_info *info =
video_output_get_info(video->video);
switch (info->format) {
case VIDEO_FORMAT_I420:
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R8);
+ info->width, info->height, GS_R8);
if (!video->copy_surfaces[i][0])
return false;
video->copy_surfaces[i][1] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8);
+ info->width / 2, info->height / 2, GS_R8);
if (!video->copy_surfaces[i][1])
return false;
video->copy_surfaces[i][2] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8);
+ info->width / 2, info->height / 2, GS_R8);
if (!video->copy_surfaces[i][2])
return false;
break;
case VIDEO_FORMAT_NV12:
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R8);
+ info->width, info->height, GS_R8);
if (!video->copy_surfaces[i][0])
return false;
video->copy_surfaces[i][1] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R8G8);
+ info->width / 2, info->height / 2, GS_R8G8);
if (!video->copy_surfaces[i][1])
return false;
break;
case VIDEO_FORMAT_I444:
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R8);
+ info->width, info->height, GS_R8);
if (!video->copy_surfaces[i][0])
return false;
video->copy_surfaces[i][1] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R8);
+ info->width, info->height, GS_R8);
if (!video->copy_surfaces[i][1])
return false;
video->copy_surfaces[i][2] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R8);
+ info->width, info->height, GS_R8);
if (!video->copy_surfaces[i][2])
return false;
break;
case VIDEO_FORMAT_I010:
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R16);
+ info->width, info->height, GS_R16);
if (!video->copy_surfaces[i][0])
return false;
video->copy_surfaces[i][1] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R16);
+ info->width / 2, info->height / 2, GS_R16);
if (!video->copy_surfaces[i][1])
return false;
video->copy_surfaces[i][2] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_R16);
+ info->width / 2, info->height / 2, GS_R16);
if (!video->copy_surfaces[i][2])
return false;
break;
case VIDEO_FORMAT_P010:
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_R16);
+ info->width, info->height, GS_R16);
if (!video->copy_surfaces[i][0])
return false;
video->copy_surfaces[i][1] = gs_stagesurface_create(
- ovi->output_width / 2, ovi->output_height / 2, GS_RG16);
+ info->width / 2, info->height / 2, GS_RG16);
if (!video->copy_surfaces[i][1])
return false;
break;
@@ -333,9 +330,10 @@ static bool obs_init_gpu_copy_surfaces(struct obs_video_info *ovi, size_t i)
return true;
}
-static bool obs_init_textures(struct obs_video_info *ovi)
+static bool obs_init_textures(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
+ const struct video_output_info *info =
+ video_output_get_info(video->video);
bool success = true;
@@ -343,16 +341,16 @@ static bool obs_init_textures(struct obs_video_info *ovi)
#ifdef _WIN32
if (video->using_nv12_tex) {
video->copy_surfaces_encode[i] =
- gs_stagesurface_create_nv12(ovi->output_width,
- ovi->output_height);
+ gs_stagesurface_create_nv12(info->width,
+ info->height);
if (!video->copy_surfaces_encode[i]) {
success = false;
break;
}
} else if (video->using_p010_tex) {
video->copy_surfaces_encode[i] =
- gs_stagesurface_create_p010(ovi->output_width,
- ovi->output_height);
+ gs_stagesurface_create_p010(info->width,
+ info->height);
if (!video->copy_surfaces_encode[i]) {
success = false;
break;
@@ -361,13 +359,13 @@ static bool obs_init_textures(struct obs_video_info *ovi)
#endif
if (video->gpu_conversion) {
- if (!obs_init_gpu_copy_surfaces(ovi, i)) {
+ if (!obs_init_gpu_copy_surfaces(video, i)) {
success = false;
break;
}
} else {
video->copy_surfaces[i][0] = gs_stagesurface_create(
- ovi->output_width, ovi->output_height, GS_RGBA);
+ info->width, info->height, GS_RGBA);
if (!video->copy_surfaces[i][0]) {
success = false;
break;
@@ -376,7 +374,7 @@ static bool obs_init_textures(struct obs_video_info *ovi)
}
enum gs_color_format format = GS_RGBA;
- switch (ovi->output_format) {
+ switch (info->format) {
case VIDEO_FORMAT_I010:
case VIDEO_FORMAT_P010:
case VIDEO_FORMAT_I210:
@@ -386,28 +384,27 @@ static bool obs_init_textures(struct obs_video_info *ovi)
}
enum gs_color_space space = GS_CS_SRGB;
- switch (ovi->colorspace) {
+ switch (info->colorspace) {
case VIDEO_CS_2100_PQ:
case VIDEO_CS_2100_HLG:
space = GS_CS_709_EXTENDED;
break;
default:
- switch (ovi->output_format) {
+ switch (info->format) {
case VIDEO_FORMAT_I010:
case VIDEO_FORMAT_P010:
space = GS_CS_SRGB_16F;
}
}
- video->render_texture = gs_texture_create(ovi->base_width,
- ovi->base_height, format, 1,
- NULL, GS_RENDER_TARGET);
+ video->render_texture =
+ gs_texture_create(obs->video.base_width, obs->video.base_height,
+ format, 1, NULL, GS_RENDER_TARGET);
if (!video->render_texture)
success = false;
- video->output_texture = gs_texture_create(ovi->output_width,
- ovi->output_height, format, 1,
- NULL, GS_RENDER_TARGET);
+ video->output_texture = gs_texture_create(
+ info->width, info->height, format, 1, NULL, GS_RENDER_TARGET);
if (!video->output_texture)
success = false;
@@ -558,15 +555,15 @@ static int obs_init_graphics(struct obs_video_info *ovi)
return success ? OBS_VIDEO_SUCCESS : OBS_VIDEO_FAIL;
}
-static inline void set_video_matrix(struct obs_core_video *video,
- struct obs_video_info *ovi)
+static inline void set_video_matrix(struct obs_core_video_mix *video,
+ struct video_output_info *info)
{
struct matrix4 mat;
struct vec4 r_row;
- if (format_is_yuv(ovi->output_format)) {
+ if (format_is_yuv(info->format)) {
video_format_get_parameters_for_format(
- ovi->colorspace, ovi->range, ovi->output_format,
+ info->colorspace, info->range, info->format,
(float *)&mat, NULL, NULL);
matrix4_inv(&mat, &mat);
@@ -581,24 +578,23 @@ static inline void set_video_matrix(struct obs_core_video *video,
memcpy(video->color_matrix, &mat, sizeof(float) * 16);
}
-static int obs_init_video(struct obs_video_info *ovi)
+int obs_init_video_mix(struct obs_video_info *ovi,
+ struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
struct video_output_info vi;
- int errorcode;
+
+ pthread_mutex_init_value(&video->gpu_encoder_mutex);
make_video_info(&vi, ovi);
- video->base_width = ovi->base_width;
- video->base_height = ovi->base_height;
- video->output_width = ovi->output_width;
- video->output_height = ovi->output_height;
video->gpu_conversion = ovi->gpu_conversion;
video->scale_type = ovi->scale_type;
+ video->gpu_was_active = false;
+ video->raw_was_active = false;
+ video->was_active = false;
- set_video_matrix(video, ovi);
-
- errorcode = video_output_open(&video->video, &vi);
+ set_video_matrix(video, &vi);
+ int errorcode = video_output_open(&video->video, &vi);
if (errorcode != VIDEO_OUTPUT_SUCCESS) {
if (errorcode == VIDEO_OUTPUT_INVALIDPARAM) {
blog(LOG_ERROR, "Invalid video parameters specified");
@@ -609,20 +605,37 @@ static int obs_init_video(struct obs_video_info *ovi)
return OBS_VIDEO_FAIL;
}
- gs_enter_context(video->graphics);
-
- if (ovi->gpu_conversion && !obs_init_gpu_conversion(ovi))
+ if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
- if (!obs_init_textures(ovi))
+
+ gs_enter_context(obs->video.graphics);
+
+ if (video->gpu_conversion && !obs_init_gpu_conversion(video))
+ return OBS_VIDEO_FAIL;
+ if (!obs_init_textures(video))
return OBS_VIDEO_FAIL;
gs_leave_context();
- if (pthread_mutex_init(&video->gpu_encoder_mutex, NULL) < 0)
- return OBS_VIDEO_FAIL;
+ return OBS_VIDEO_SUCCESS;
+}
+
+static int obs_init_video(struct obs_video_info *ovi)
+{
+ struct obs_core_video *video = &obs->video;
+ video->base_width = ovi->base_width;
+ video->base_height = ovi->base_height;
+ video->video_frame_interval_ns =
+ util_mul_div64(1000000000ULL, ovi->fps_den, ovi->fps_num);
+ video->video_half_frame_interval_ns =
+ util_mul_div64(500000000ULL, ovi->fps_den, ovi->fps_num);
+
if (pthread_mutex_init(&video->task_mutex, NULL) < 0)
return OBS_VIDEO_FAIL;
+ if (pthread_mutex_init(&video->mixes_mutex, NULL) < 0)
+ return OBS_VIDEO_FAIL;
+ int errorcode;
#ifdef __APPLE__
errorcode = pthread_create(&video->video_thread, NULL,
obs_graphics_thread_autorelease, obs);
@@ -635,84 +648,90 @@ static int obs_init_video(struct obs_video_info *ovi)
video->thread_initialized = true;
video->ovi = *ovi;
+
+ if (!obs_view_add(&obs->data.main_view))
+ return OBS_VIDEO_FAIL;
+
return OBS_VIDEO_SUCCESS;
}
static void stop_video(void)
{
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++)
+ video_output_stop(obs->video.mixes.array[i].video);
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
struct obs_core_video *video = &obs->video;
void *thread_retval;
- if (video->video) {
- video_output_stop(video->video);
- if (video->thread_initialized) {
- pthread_join(video->video_thread, &thread_retval);
- video->thread_initialized = false;
- }
+ if (video->thread_initialized) {
+ pthread_join(video->video_thread, &thread_retval);
+ video->thread_initialized = false;
}
}
-static void obs_free_video(void)
+static void obs_free_render_textures(struct obs_core_video_mix *video)
{
- struct obs_core_video *video = &obs->video;
+ if (!obs->video.graphics)
+ return;
+ gs_enter_context(obs->video.graphics);
+
+ for (size_t c = 0; c < NUM_CHANNELS; c++) {
+ if (video->mapped_surfaces[c]) {
+ gs_stagesurface_unmap(video->mapped_surfaces[c]);
+ video->mapped_surfaces[c] = NULL;
+ }
+ }
+
+ for (size_t i = 0; i < NUM_TEXTURES; i++) {
+ for (size_t c = 0; c < NUM_CHANNELS; c++) {
+ if (video->copy_surfaces[i][c]) {
+ gs_stagesurface_destroy(
+ video->copy_surfaces[i][c]);
+ video->copy_surfaces[i][c] = NULL;
+ }
+
+ video->active_copy_surfaces[i][c] = NULL;
+ }
+#ifdef _WIN32
+ if (video->copy_surfaces_encode[i]) {
+ gs_stagesurface_destroy(video->copy_surfaces_encode[i]);
+ video->copy_surfaces_encode[i] = NULL;
+ }
+#endif
+ }
+
+ gs_texture_destroy(video->render_texture);
+
+ for (size_t c = 0; c < NUM_CHANNELS; c++) {
+ if (video->convert_textures[c]) {
+ gs_texture_destroy(video->convert_textures[c]);
+ video->convert_textures[c] = NULL;
+ }
+#ifdef _WIN32
+ if (video->convert_textures_encode[c]) {
+ gs_texture_destroy(video->convert_textures_encode[c]);
+ video->convert_textures_encode[c] = NULL;
+ }
+#endif
+ }
+
+ gs_texture_destroy(video->output_texture);
+ video->render_texture = NULL;
+ video->output_texture = NULL;
+
+ gs_leave_context();
+}
+
+void obs_free_video_mix(struct obs_core_video_mix *video)
+{
if (video->video) {
video_output_close(video->video);
video->video = NULL;
- if (!video->graphics)
- return;
-
- gs_enter_context(video->graphics);
-
- for (size_t c = 0; c < NUM_CHANNELS; c++) {
- if (video->mapped_surfaces[c]) {
- gs_stagesurface_unmap(
- video->mapped_surfaces[c]);
- video->mapped_surfaces[c] = NULL;
- }
- }
-
- for (size_t i = 0; i < NUM_TEXTURES; i++) {
- for (size_t c = 0; c < NUM_CHANNELS; c++) {
- if (video->copy_surfaces[i][c]) {
- gs_stagesurface_destroy(
- video->copy_surfaces[i][c]);
- video->copy_surfaces[i][c] = NULL;
- }
-
- video->active_copy_surfaces[i][c] = NULL;
- }
-#ifdef _WIN32
- if (video->copy_surfaces_encode[i]) {
- gs_stagesurface_destroy(
- video->copy_surfaces_encode[i]);
- video->copy_surfaces_encode[i] = NULL;
- }
-#endif
- }
-
- gs_texture_destroy(video->render_texture);
-
- for (size_t c = 0; c < NUM_CHANNELS; c++) {
- if (video->convert_textures[c]) {
- gs_texture_destroy(video->convert_textures[c]);
- video->convert_textures[c] = NULL;
- }
-#ifdef _WIN32
- if (video->convert_textures_encode[c]) {
- gs_texture_destroy(
- video->convert_textures_encode[c]);
- video->convert_textures_encode[c] = NULL;
- }
-#endif
- }
-
- gs_texture_destroy(video->output_texture);
- video->render_texture = NULL;
- video->output_texture = NULL;
-
- gs_leave_context();
+ obs_free_render_textures(video);
circlebuf_free(&video->vframe_info_buffer);
circlebuf_free(&video->vframe_info_buffer_gpu);
@@ -726,15 +745,30 @@ static void obs_free_video(void)
pthread_mutex_init_value(&video->gpu_encoder_mutex);
da_free(video->gpu_encoders);
- pthread_mutex_destroy(&video->task_mutex);
- pthread_mutex_init_value(&video->task_mutex);
- circlebuf_free(&video->tasks);
-
video->gpu_encoder_active = 0;
video->cur_texture = 0;
}
}
+static void obs_free_video(void)
+{
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ size_t num = obs->video.mixes.num;
+ if (num)
+ blog(LOG_WARNING, "%d views remain at shutdown", num);
+ for (size_t i = 0; i < num; i++)
+ obs_free_video_mix(obs->video.mixes.array + i);
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+ pthread_mutex_destroy(&obs->video.mixes_mutex);
+ pthread_mutex_init_value(&obs->video.mixes_mutex);
+ da_free(obs->video.mixes);
+
+ pthread_mutex_destroy(&obs->video.task_mutex);
+ pthread_mutex_init_value(&obs->video.task_mutex);
+ circlebuf_free(&obs->video.tasks);
+}
+
static void obs_free_graphics(void)
{
struct obs_core_video *video = &obs->video;
@@ -892,6 +926,7 @@ static void obs_free_data(void)
data->valid = false;
+ obs_view_remove(&data->main_view);
obs_main_view_free(&data->main_view);
blog(LOG_INFO, "Freeing OBS context data");
@@ -1057,8 +1092,8 @@ static bool obs_init(const char *locale, const char *module_config_path,
pthread_mutex_init_value(&obs->audio.monitoring_mutex);
pthread_mutex_init_value(&obs->audio.task_mutex);
- pthread_mutex_init_value(&obs->video.gpu_encoder_mutex);
pthread_mutex_init_value(&obs->video.task_mutex);
+ pthread_mutex_init_value(&obs->video.mixes_mutex);
obs->name_store_owned = !store;
obs->name_store = store ? store : profiler_name_store_create();
@@ -1331,15 +1366,13 @@ int obs_reset_video(struct obs_video_info *ovi)
return OBS_VIDEO_FAIL;
/* don't allow changing of video settings if active. */
- if (obs->video.video && obs_video_active())
+ if (obs_video_active())
return OBS_VIDEO_CURRENTLY_ACTIVE;
if (!size_valid(ovi->output_width, ovi->output_height) ||
!size_valid(ovi->base_width, ovi->base_height))
return OBS_VIDEO_INVALID_PARAM;
- struct obs_core_video *video = &obs->video;
-
stop_video();
obs_free_video();
@@ -1347,7 +1380,7 @@ int obs_reset_video(struct obs_video_info *ovi)
ovi->output_width &= 0xFFFFFFFC;
ovi->output_height &= 0xFFFFFFFE;
- if (!video->graphics) {
+ if (!obs->video.graphics) {
int errorcode = obs_init_graphics(ovi);
if (errorcode != OBS_VIDEO_SUCCESS) {
obs_free_graphics();
@@ -1461,12 +1494,10 @@ bool obs_reset_audio(const struct obs_audio_info *oai)
bool obs_get_video_info(struct obs_video_info *ovi)
{
- struct obs_core_video *video = &obs->video;
-
- if (!video->graphics)
+ if (!obs->video.graphics)
return false;
- *ovi = video->ovi;
+ *ovi = obs->video.ovi;
return true;
}
@@ -1617,7 +1648,7 @@ audio_t *obs_get_audio(void)
video_t *obs_get_video(void)
{
- return obs->video.video;
+ return obs->video.main_mix->video;
}
/* TODO: optimize this later so it's not just O(N) string lookups */
@@ -1974,12 +2005,12 @@ static void obs_render_main_texture_internal(enum gs_blend_type src_c,
enum gs_blend_type src_a,
enum gs_blend_type dest_a)
{
- struct obs_core_video *video;
+ struct obs_core_video_mix *video;
gs_texture_t *tex;
gs_effect_t *effect;
gs_eparam_t *param;
- video = &obs->video;
+ video = obs->video.main_mix;
if (!video->texture_rendered)
return;
@@ -2033,9 +2064,9 @@ void obs_render_main_texture_src_color_only(void)
gs_texture_t *obs_get_main_texture(void)
{
- struct obs_core_video *video;
+ struct obs_core_video_mix *video;
- video = &obs->video;
+ video = obs->video.main_mix;
if (!video->texture_rendered)
return NULL;
@@ -2678,12 +2709,31 @@ uint32_t obs_get_lagged_frames(void)
return obs->video.lagged_frames;
}
+struct obs_core_video_mix *get_mix_for_video(video_t *v)
+{
+ struct obs_core_video_mix *result = NULL;
+
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+ struct obs_core_video_mix *mix = obs->video.mixes.array + i;
+
+ if (v == mix->video) {
+ result = mix;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+ return result;
+}
+
void start_raw_video(video_t *v, const struct video_scale_info *conversion,
void (*callback)(void *param, struct video_data *frame),
void *param)
{
- struct obs_core_video *video = &obs->video;
- os_atomic_inc_long(&video->raw_active);
+ struct obs_core_video_mix *video = get_mix_for_video(v);
+ if (video)
+ os_atomic_inc_long(&video->raw_active);
video_output_connect(v, conversion, callback, param);
}
@@ -2691,8 +2741,9 @@ void stop_raw_video(video_t *v,
void (*callback)(void *param, struct video_data *frame),
void *param)
{
- struct obs_core_video *video = &obs->video;
- os_atomic_dec_long(&video->raw_active);
+ struct obs_core_video_mix *video = get_mix_for_video(v);
+ if (video)
+ os_atomic_dec_long(&video->raw_active);
video_output_disconnect(v, callback, param);
}
@@ -2701,7 +2752,7 @@ void obs_add_raw_video_callback(const struct video_scale_info *conversion,
struct video_data *frame),
void *param)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
start_raw_video(video->video, conversion, callback, param);
}
@@ -2709,7 +2760,7 @@ void obs_remove_raw_video_callback(void (*callback)(void *param,
struct video_data *frame),
void *param)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
stop_raw_video(video->video, callback, param);
}
@@ -2752,13 +2803,13 @@ obs_data_t *obs_get_private_data(void)
return private_data;
}
-extern bool init_gpu_encoding(struct obs_core_video *video);
-extern void stop_gpu_encoding_thread(struct obs_core_video *video);
-extern void free_gpu_encoding(struct obs_core_video *video);
+extern bool init_gpu_encoding(struct obs_core_video_mix *video);
+extern void stop_gpu_encoding_thread(struct obs_core_video_mix *video);
+extern void free_gpu_encoding(struct obs_core_video_mix *video);
bool start_gpu_encode(obs_encoder_t *encoder)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
bool success = true;
obs_enter_graphics();
@@ -2784,7 +2835,7 @@ bool start_gpu_encode(obs_encoder_t *encoder)
void stop_gpu_encode(obs_encoder_t *encoder)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
bool call_free = false;
os_atomic_dec_long(&video->gpu_encoder_active);
@@ -2811,21 +2862,32 @@ void stop_gpu_encode(obs_encoder_t *encoder)
bool obs_video_active(void)
{
- struct obs_core_video *video = &obs->video;
+ bool result = false;
- return os_atomic_load_long(&video->raw_active) > 0 ||
- os_atomic_load_long(&video->gpu_encoder_active) > 0;
+ pthread_mutex_lock(&obs->video.mixes_mutex);
+ for (size_t i = 0, num = obs->video.mixes.num; i < num; i++) {
+ struct obs_core_video_mix *video = obs->video.mixes.array + i;
+
+ if (os_atomic_load_long(&video->raw_active) > 0 ||
+ os_atomic_load_long(&video->gpu_encoder_active) > 0) {
+ result = true;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&obs->video.mixes_mutex);
+
+ return result;
}
bool obs_nv12_tex_active(void)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
return video->using_nv12_tex;
}
bool obs_p010_tex_active(void)
{
- struct obs_core_video *video = &obs->video;
+ struct obs_core_video_mix *video = obs->video.main_mix;
return video->using_p010_tex;
}
diff --git a/libobs/obs.h b/libobs/obs.h
index 82a043d2c..4fcce92e3 100644
--- a/libobs/obs.h
+++ b/libobs/obs.h
@@ -909,6 +909,12 @@ EXPORT obs_source_t *obs_view_get_source(obs_view_t *view, uint32_t channel);
/** Renders the sources of this view context */
EXPORT void obs_view_render(obs_view_t *view);
+/** Adds a view to the main render loop */
+EXPORT video_t *obs_view_add(obs_view_t *view);
+
+/** Removes a view from the main render loop */
+EXPORT void obs_view_remove(obs_view_t *view);
+
/* ------------------------------------------------------------------------- */
/* Display context */