From a72a52fa382f6a34c049c3280ab60e93a11becbc Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sat, 20 Jun 2020 07:42:14 -0700 Subject: [PATCH] UI: Add virtual camera to UI Adds a virtual camera button to the main user interface. If virtual camera is not installed, it will not add the button. On Windows, it detects whether the virtual camera filters are properly registered, and will only add the button if the virtual camera filter is confirmed registered. Also adds a virtual camera option to the auto-configuration wizard, which will just simply set the user's resolution/scale to 1920x1080 at 30 FPS. --- UI/data/locale/en-US.ini | 3 + UI/installer/mp-installer.nsi | 14 ++++ UI/obs-app.cpp | 7 +- UI/obs-app.hpp | 1 + UI/win-update/updater/updater.cpp | 34 ++++++++ UI/window-basic-auto-config-test.cpp | 15 +++- UI/window-basic-auto-config.cpp | 21 ++++- UI/window-basic-auto-config.hpp | 2 + UI/window-basic-main-outputs.cpp | 68 ++++++++++++++++ UI/window-basic-main-outputs.hpp | 11 ++- UI/window-basic-main.cpp | 115 +++++++++++++++++++++++++++ UI/window-basic-main.hpp | 16 +++- 12 files changed, 299 insertions(+), 8 deletions(-) diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 09cf33cd2..6a11e28d6 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -150,6 +150,7 @@ Basic.AutoConfig.StartPage="Usage Information" Basic.AutoConfig.StartPage.SubTitle="Specify what you want to use the program for" Basic.AutoConfig.StartPage.PrioritizeStreaming="Optimize for streaming, recording is secondary" Basic.AutoConfig.StartPage.PrioritizeRecording="Optimize just for recording, I will not be streaming" +Basic.AutoConfig.StartPage.PrioritizeVirtualCam="I will only be using the virtual camera" Basic.AutoConfig.VideoPage="Video Settings" Basic.AutoConfig.VideoPage.SubTitle="Specify the desired video settings you would like to use" Basic.AutoConfig.VideoPage.BaseResolution.UseCurrent="Use Current (%1x%2)" @@ -520,6 +521,7 @@ Basic.Main.StartRecording="Start Recording" Basic.Main.StartReplayBuffer="Start Replay Buffer" Basic.Main.SaveReplay="Save Replay" Basic.Main.StartStreaming="Start Streaming" +Basic.Main.StartVirtualCam="Start Virtual Camera" Basic.Main.StopRecording="Stop Recording" Basic.Main.PauseRecording="Pause Recording" Basic.Main.UnpauseRecording="Unpause Recording" @@ -529,6 +531,7 @@ Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..." Basic.Main.StopStreaming="Stop Streaming" Basic.Main.StoppingStreaming="Stopping Stream..." Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)" +Basic.Main.StopVirtualCam="Stop Virtual Camera" Basic.Main.Group="Group %1" Basic.Main.GroupItems="Group Selected Items" Basic.Main.Ungroup="Ungroup" diff --git a/UI/installer/mp-installer.nsi b/UI/installer/mp-installer.nsi index abcc85cf0..371198bc3 100644 --- a/UI/installer/mp-installer.nsi +++ b/UI/installer/mp-installer.nsi @@ -373,6 +373,14 @@ Section -FinishSection ClearErrors WriteRegDWORD HKLM "Software\Khronos\Vulkan\ImplicitLayers" "$APPDATA\obs-studio-hook\obs-vulkan32.json" 0 + # --------------------------------------- + # Register virtual camera dlls + + Exec '"$SYSDIR\regsvr32.exe" /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll"' + ${if} ${RunningX64} + Exec '"$SYSDIR\regsvr32.exe" /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll"' + ${endif} + # --------------------------------------- ClearErrors @@ -420,6 +428,12 @@ Section "un.obs-studio Program Files" UninstallSection1 SetShellVarContext current ClearErrors + ; Unregister virtual camera dlls + Exec '"$SYSDIR\regsvr32.exe" /u /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module32.dll"' + ${if} ${RunningX64} + Exec '"$SYSDIR\regsvr32.exe" /u /s "$INSTDIR\data\obs-plugins\win-dshow\obs-virtualcam-module64.dll"' + ${endif} + ; Remove from registry... DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" DeleteRegKey HKLM "SOFTWARE\${APPNAME}" diff --git a/UI/obs-app.cpp b/UI/obs-app.cpp index 1504da761..c417f5afb 100644 --- a/UI/obs-app.cpp +++ b/UI/obs-app.cpp @@ -75,6 +75,7 @@ bool opt_start_streaming = false; bool opt_start_recording = false; bool opt_studio_mode = false; bool opt_start_replaybuffer = false; +bool opt_start_virtualcam = false; bool opt_minimize_tray = false; bool opt_allow_opengl = false; bool opt_always_on_top = false; @@ -2425,6 +2426,9 @@ int main(int argc, char *argv[]) } else if (arg_is(argv[i], "--startreplaybuffer", nullptr)) { opt_start_replaybuffer = true; + } else if (arg_is(argv[i], "--startvirtualcam", nullptr)) { + opt_start_virtualcam = true; + } else if (arg_is(argv[i], "--collection", nullptr)) { if (++i < argc) opt_starting_collection = argv[i]; @@ -2451,7 +2455,8 @@ int main(int argc, char *argv[]) << "--help, -h: Get list of available commands.\n\n" << "--startstreaming: Automatically start streaming.\n" << "--startrecording: Automatically start recording.\n" - << "--startreplaybuffer: Start replay buffer.\n\n" + << "--startreplaybuffer: Start replay buffer.\n" + << "--startvirtualcam: Start virtual camera (if available).\n\n" << "--collection : Use specific scene collection." << "\n" << "--profile : Use specific profile.\n" diff --git a/UI/obs-app.hpp b/UI/obs-app.hpp index de5cac72a..506a154c4 100644 --- a/UI/obs-app.hpp +++ b/UI/obs-app.hpp @@ -225,6 +225,7 @@ extern std::string remuxFilename; extern bool opt_start_streaming; extern bool opt_start_recording; extern bool opt_start_replaybuffer; +extern bool opt_start_virtualcam; extern bool opt_minimize_tray; extern bool opt_studio_mode; extern bool opt_allow_opengl; diff --git a/UI/win-update/updater/updater.cpp b/UI/win-update/updater/updater.cpp index 0fa32636f..ead988226 100644 --- a/UI/win-update/updater/updater.cpp +++ b/UI/win-update/updater/updater.cpp @@ -1424,6 +1424,40 @@ static bool Update(wchar_t *cmdLine) } } + /* ------------------------------------- * + * Install virtual camera */ + + if (!bIsPortable) { + wchar_t regsvr[MAX_PATH]; + wchar_t src[MAX_PATH]; + wchar_t tmp[MAX_PATH]; + wchar_t tmp2[MAX_PATH]; + + SHGetFolderPathW(nullptr, CSIDL_SYSTEM, nullptr, + SHGFP_TYPE_CURRENT, regsvr); + StringCbCat(regsvr, sizeof(regsvr), L"\\regsvr32.exe"); + + GetCurrentDirectoryW(_countof(src), src); + StringCbCat(src, sizeof(src), + L"\\data\\obs-plugins\\win-dshow\\"); + + StringCbCopy(tmp, sizeof(tmp), L"\"\""); + StringCbCat(tmp, sizeof(tmp), regsvr); + StringCbCat(tmp, sizeof(tmp), L"\" /s \""); + StringCbCat(tmp, sizeof(tmp), src); + StringCbCat(tmp, sizeof(tmp), L"obs-virtualcam-module"); + + StringCbCopy(tmp2, sizeof(tmp2), tmp); + StringCbCat(tmp2, sizeof(tmp2), L"32.dll\"\""); + _wsystem(tmp2); + + if (is_64bit_windows()) { + StringCbCopy(tmp2, sizeof(tmp2), tmp); + StringCbCat(tmp2, sizeof(tmp2), L"64.dll\"\""); + _wsystem(tmp2); + } + } + /* ------------------------------------- * * Update hook files and vulkan registry */ diff --git a/UI/window-basic-auto-config-test.cpp b/UI/window-basic-auto-config-test.cpp index 49713760d..8f698cff1 100644 --- a/UI/window-basic-auto-config-test.cpp +++ b/UI/window-basic-auto-config-test.cpp @@ -990,7 +990,7 @@ void AutoConfigTestPage::FinalizeResults() return new QLabel(QTStr(str), this); }; - if (wiz->type != AutoConfig::Type::Recording) { + if (wiz->type == AutoConfig::Type::Streaming) { const char *serverType = wiz->customServer ? "rtmp_custom" : "rtmp_common"; @@ -1093,7 +1093,7 @@ void AutoConfigTestPage::NextStage() started = true; } - if (wiz->type == AutoConfig::Type::Recording) { + if (wiz->type != AutoConfig::Type::Streaming) { stage = Stage::StreamEncoder; } else if (!wiz->bandwidthTest) { stage = Stage::BandwidthTest; @@ -1163,8 +1163,17 @@ AutoConfigTestPage::~AutoConfigTestPage() void AutoConfigTestPage::initializePage() { + if (wiz->type == AutoConfig::Type::VirtualCam) { + wiz->idealResolutionCX = wiz->baseResolutionCX; + wiz->idealResolutionCY = wiz->baseResolutionCY; + wiz->idealFPSNum = 30; + wiz->idealFPSDen = 1; + stage = Stage::Finished; + } else { + stage = Stage::Starting; + } + setSubTitle(QTStr(SUBTITLE_TESTING)); - stage = Stage::Starting; softwareTested = false; cancel = false; DeleteLayout(results); diff --git a/UI/window-basic-auto-config.cpp b/UI/window-basic-auto-config.cpp index e79718474..74d60fd90 100644 --- a/UI/window-basic-auto-config.cpp +++ b/UI/window-basic-auto-config.cpp @@ -69,6 +69,18 @@ AutoConfigStartPage::AutoConfigStartPage(QWidget *parent) ui->setupUi(this); setTitle(QTStr("Basic.AutoConfig.StartPage")); setSubTitle(QTStr("Basic.AutoConfig.StartPage.SubTitle")); + + OBSBasic *main = OBSBasic::Get(); + if (main->VCamEnabled()) { + QRadioButton *prioritizeVCam = new QRadioButton( + QTStr("Basic.AutoConfig.StartPage.PrioritizeVirtualCam"), + this); + QBoxLayout *box = reinterpret_cast(layout()); + box->insertWidget(2, prioritizeVCam); + + connect(prioritizeVCam, &QPushButton::clicked, this, + &AutoConfigStartPage::PrioritizeVCam); + } } AutoConfigStartPage::~AutoConfigStartPage() @@ -78,7 +90,9 @@ AutoConfigStartPage::~AutoConfigStartPage() int AutoConfigStartPage::nextId() const { - return AutoConfig::VideoPage; + return wiz->type == AutoConfig::Type::VirtualCam + ? AutoConfig::TestPage + : AutoConfig::VideoPage; } void AutoConfigStartPage::on_prioritizeStreaming_clicked() @@ -91,6 +105,11 @@ void AutoConfigStartPage::on_prioritizeRecording_clicked() wiz->type = AutoConfig::Type::Recording; } +void AutoConfigStartPage::PrioritizeVCam() +{ + wiz->type = AutoConfig::Type::VirtualCam; +} + /* ------------------------------------------------------------------------- */ #define RES_TEXT(x) "Basic.AutoConfig.VideoPage." x diff --git a/UI/window-basic-auto-config.hpp b/UI/window-basic-auto-config.hpp index 874bacb43..98540c8c6 100644 --- a/UI/window-basic-auto-config.hpp +++ b/UI/window-basic-auto-config.hpp @@ -33,6 +33,7 @@ class AutoConfig : public QWizard { Invalid, Streaming, Recording, + VirtualCam, }; enum class Service { @@ -139,6 +140,7 @@ public: public slots: void on_prioritizeStreaming_clicked(); void on_prioritizeRecording_clicked(); + void PrioritizeVCam(); }; class AutoConfigVideoPage : public QWizardPage { diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 30a1280f6..47eb33070 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -14,6 +14,7 @@ volatile bool streaming_active = false; volatile bool recording_active = false; volatile bool recording_paused = false; volatile bool replaybuf_active = false; +volatile bool virtualcam_active = false; #define RTMP_PROTOCOL "rtmp" @@ -139,6 +140,30 @@ static void OBSReplayBufferStopping(void *data, calldata_t *params) UNUSED_PARAMETER(params); } +static void OBSStartVirtualCam(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + + output->virtualCamActive = true; + os_atomic_set_bool(&virtualcam_active, true); + QMetaObject::invokeMethod(output->main, "OnVirtualCamStart"); + + UNUSED_PARAMETER(params); +} + +static void OBSStopVirtualCam(void *data, calldata_t *params) +{ + BasicOutputHandler *output = static_cast(data); + int code = (int)calldata_int(params, "code"); + + output->virtualCamActive = false; + os_atomic_set_bool(&virtualcam_active, false); + QMetaObject::invokeMethod(output->main, "OnVirtualCamStop", + Q_ARG(int, code)); + + UNUSED_PARAMETER(params); +} + static void FindBestFilename(string &strPath, bool noSpace) { int num = 2; @@ -197,6 +222,49 @@ static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate, /* ------------------------------------------------------------------------ */ +inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_) +{ + if (main->vcamEnabled) { + virtualCam = obs_output_create("virtualcam_output", + "virtualcam_output", nullptr, + nullptr); + obs_output_release(virtualCam); + + signal_handler_t *signal = + obs_output_get_signal_handler(virtualCam); + startVirtualCam.Connect(signal, "start", OBSStartVirtualCam, + this); + stopVirtualCam.Connect(signal, "stop", OBSStopVirtualCam, this); + } +} + +bool BasicOutputHandler::StartVirtualCam() +{ + if (main->vcamEnabled) { + obs_output_set_media(virtualCam, obs_get_video(), + obs_get_audio()); + return obs_output_start(virtualCam); + } + return false; +} + +void BasicOutputHandler::StopVirtualCam() +{ + if (main->vcamEnabled) { + obs_output_stop(virtualCam); + } +} + +bool BasicOutputHandler::VirtualCamActive() const +{ + if (main->vcamEnabled) { + return obs_output_active(virtualCam); + } + return false; +} + +/* ------------------------------------------------------------------------ */ + struct SimpleOutput : BasicOutputHandler { OBSEncoder aacStreaming; OBSEncoder h264Streaming; diff --git a/UI/window-basic-main-outputs.hpp b/UI/window-basic-main-outputs.hpp index b5aae7759..dc8b77f9a 100644 --- a/UI/window-basic-main-outputs.hpp +++ b/UI/window-basic-main-outputs.hpp @@ -8,10 +8,12 @@ struct BasicOutputHandler { OBSOutput fileOutput; OBSOutput streamOutput; OBSOutput replayBuffer; + OBSOutput virtualCam; bool streamingActive = false; bool recordingActive = false; bool delayActive = false; bool replayBufferActive = false; + bool virtualCamActive = false; OBSBasic *main; std::string outputType; @@ -23,31 +25,36 @@ struct BasicOutputHandler { OBSSignal stopReplayBuffer; OBSSignal startStreaming; OBSSignal stopStreaming; + OBSSignal startVirtualCam; + OBSSignal stopVirtualCam; OBSSignal streamDelayStarting; OBSSignal streamStopping; OBSSignal recordStopping; OBSSignal replayBufferStopping; - inline BasicOutputHandler(OBSBasic *main_) : main(main_) {} + inline BasicOutputHandler(OBSBasic *main_); virtual ~BasicOutputHandler(){}; virtual bool StartStreaming(obs_service_t *service) = 0; virtual bool StartRecording() = 0; virtual bool StartReplayBuffer() { return false; } + virtual bool StartVirtualCam(); virtual void StopStreaming(bool force = false) = 0; virtual void StopRecording(bool force = false) = 0; virtual void StopReplayBuffer(bool force = false) { (void)force; } + virtual void StopVirtualCam(); virtual bool StreamingActive() const = 0; virtual bool RecordingActive() const = 0; virtual bool ReplayBufferActive() const { return false; } + virtual bool VirtualCamActive() const; virtual void Update() = 0; inline bool Active() const { return streamingActive || recordingActive || delayActive || - replayBufferActive; + replayBufferActive || virtualCamActive; } }; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 1a643e54d..88f0209b4 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1080,6 +1080,12 @@ retryScene: opt_start_replaybuffer = false; } + if (opt_start_virtualcam) { + QMetaObject::invokeMethod(this, "StartVirtualCam", + Qt::QueuedConnection); + opt_start_virtualcam = false; + } + copyStrings.clear(); copyFiltersString = nullptr; @@ -1518,6 +1524,18 @@ void OBSBasic::ReplayBufferClicked() StartReplayBuffer(); }; +void OBSBasic::AddVCamButton() +{ + vcamButton = new ReplayBufferButton(QTStr("Basic.Main.StartVirtualCam"), + this); + vcamButton->setCheckable(true); + connect(vcamButton.data(), &QPushButton::clicked, this, + &OBSBasic::VCamButtonClicked); + + vcamButton->setProperty("themeID", "vcamButton"); + ui->buttonsVLayout->insertWidget(2, vcamButton); +} + void OBSBasic::ResetOutputs() { ProfileScope("OBSBasic::ResetOutputs"); @@ -1645,6 +1663,13 @@ void OBSBasic::OBSInit() cef = obs_browser_init_panel(); #endif + obs_data_t *obsData = obs_get_private_data(); + vcamEnabled = obs_data_get_bool(obsData, "vcamEnabled"); + if (vcamEnabled) { + AddVCamButton(); + } + obs_data_release(obsData); + InitBasicConfigDefaults2(); CheckForSimpleModeX264Fallback(); @@ -2248,6 +2273,23 @@ void OBSBasic::CreateHotkeys() LoadHotkeyPair(replayBufHotkeys, "OBSBasic.StartReplayBuffer", "OBSBasic.StopReplayBuffer"); + if (vcamEnabled) { + vcamHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.StartVirtualCam", + Str("Basic.Main.StartVirtualCam"), + "OBSBasic.StopVirtualCam", + Str("Basic.Main.StopVirtualCam"), + MAKE_CALLBACK(!basic.outputHandler->VirtualCamActive(), + basic.StartVirtualCam, + "Starting virtual camera"), + MAKE_CALLBACK(basic.outputHandler->VirtualCamActive(), + basic.StopVirtualCam, + "Stopping virtual camera"), + this, this); + LoadHotkeyPair(vcamHotkeys, "OBSBasic.StartVirtualCam", + "OBSBasic.StopVirtualCam"); + } + togglePreviewHotkeys = obs_hotkey_pair_register_frontend( "OBSBasic.EnablePreview", Str("Basic.Main.PreviewConextMenu.Enable"), @@ -5247,6 +5289,10 @@ void OBSBasic::OpenSceneFilters() "==== Streaming Start ===============================================" #define STREAMING_STOP \ "==== Streaming Stop ================================================" +#define VIRTUAL_CAM_START \ + "==== Virtual Camera Start ==========================================" +#define VIRTUAL_CAM_STOP \ + "==== Virtual Camera Stop ===========================================" void OBSBasic::StartStreaming() { @@ -5967,6 +6013,61 @@ void OBSBasic::ReplayBufferStop(int code) UpdateReplayBuffer(false); } +void OBSBasic::StartVirtualCam() +{ + if (!outputHandler || !outputHandler->virtualCam) + return; + if (outputHandler->VirtualCamActive()) + return; + if (disableOutputsRef) + return; + + SaveProject(); + + if (!outputHandler->StartVirtualCam()) { + vcamButton->setChecked(false); + } +} + +void OBSBasic::StopVirtualCam() +{ + if (!outputHandler || !outputHandler->virtualCam) + return; + + SaveProject(); + + if (outputHandler->VirtualCamActive()) + outputHandler->StopVirtualCam(); + + OnDeactivate(); +} + +void OBSBasic::OnVirtualCamStart() +{ + if (!outputHandler || !outputHandler->virtualCam) + return; + + vcamButton->setText(QTStr("Basic.Main.StopVirtualCam")); + vcamButton->setChecked(true); + + OnActivate(); + + blog(LOG_INFO, VIRTUAL_CAM_START); +} + +void OBSBasic::OnVirtualCamStop(int) +{ + if (!outputHandler || !outputHandler->virtualCam) + return; + + vcamButton->setText(QTStr("Basic.Main.StartVirtualCam")); + vcamButton->setChecked(false); + + blog(LOG_INFO, VIRTUAL_CAM_STOP); + + OnDeactivate(); +} + void OBSBasic::on_streamButton_clicked() { if (outputHandler->StreamingActive()) { @@ -6073,6 +6174,20 @@ void OBSBasic::on_recordButton_clicked() } } +void OBSBasic::VCamButtonClicked() +{ + if (outputHandler->VirtualCamActive()) { + StopVirtualCam(); + } else { + if (!UIValidation::NoSourcesConfirmation(this)) { + vcamButton->setChecked(false); + return; + } + + StartVirtualCam(); + } +} + void OBSBasic::on_settingsButton_clicked() { on_action_Settings_triggered(); diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index e9dd2b6b2..d2cc692a9 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -166,6 +166,7 @@ class OBSBasic : public OBSMainWindow { friend class ReplayBufferButton; friend class ExtraBrowsersModel; friend class ExtraBrowsersDelegate; + friend struct BasicOutputHandler; friend struct OBSStudioAPI; enum class MoveDir { Up, Down, Left, Right }; @@ -256,6 +257,9 @@ private: QScopedPointer pause; QScopedPointer replay; + QPointer vcamButton; + bool vcamEnabled = false; + QScopedPointer trayIcon; QPointer sysTrayStream; QPointer sysTrayRecord; @@ -382,7 +386,7 @@ private: QModelIndexList GetAllSelectedSourceItems(); obs_hotkey_pair_id streamingHotkeys, recordingHotkeys, pauseHotkeys, - replayBufHotkeys, togglePreviewHotkeys; + replayBufHotkeys, vcamHotkeys, togglePreviewHotkeys; obs_hotkey_id forceStreamingStopHotkey; void InitDefaultTransitions(); @@ -561,6 +565,12 @@ public slots: void ReplayBufferStopping(); void ReplayBufferStop(int code); + void StartVirtualCam(); + void StopVirtualCam(); + + void OnVirtualCamStart(); + void OnVirtualCamStop(int code); + void SaveProjectDeferred(); void SaveProject(); @@ -740,6 +750,8 @@ public: return os_atomic_load_bool(&previewProgramMode); } + inline bool VCamEnabled() const { return vcamEnabled; } + bool StreamingActive() const; bool Active() const; @@ -747,6 +759,7 @@ public: int ResetVideo(); bool ResetAudio(); + void AddVCamButton(); void ResetOutputs(); void ResetAudioDevice(const char *sourceId, const char *deviceId, @@ -887,6 +900,7 @@ private slots: void on_streamButton_clicked(); void on_recordButton_clicked(); + void VCamButtonClicked(); void on_settingsButton_clicked(); void on_actionHelpPortal_triggered();