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();