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.
master
jp9000 2020-06-20 07:42:14 -07:00
parent 6377fe3177
commit a72a52fa38
12 changed files with 299 additions and 8 deletions

View File

@ -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"

View File

@ -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}"

View File

@ -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 <string>: Use specific scene collection."
<< "\n"
<< "--profile <string>: Use specific profile.\n"

View File

@ -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;

View File

@ -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 */

View File

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

View File

@ -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<QBoxLayout *>(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

View File

@ -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 {

View File

@ -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<BasicOutputHandler *>(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<BasicOutputHandler *>(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;

View File

@ -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;
}
};

View File

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

View File

@ -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<QPushButton> pause;
QScopedPointer<QPushButton> replay;
QPointer<QPushButton> vcamButton;
bool vcamEnabled = false;
QScopedPointer<QSystemTrayIcon> trayIcon;
QPointer<QAction> sysTrayStream;
QPointer<QAction> 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();