Browse Source

UI: Enable Replay Buffer in Advanced Mode

Replay buffer is currently only supported in simple mode.  This change
adds support to advanced mode and is, for the most part, a mirror of the
addition of replay buffer to simple mode.

Mantis-bug: https://obsproject.com/mantis/view.php?id=792

Closes jp9000/obs-studio#1019
Benjamin Schubert 8 years ago
parent
commit
9cba5660e4

File diff suppressed because it is too large
+ 525 - 791
UI/forms/OBSBasicSettings.ui


+ 181 - 1
UI/window-basic-main-outputs.cpp

@@ -991,6 +991,7 @@ struct AdvancedOutput : BasicOutputHandler {
 	bool                   ffmpegOutput;
 	bool                   ffmpegRecording;
 	bool                   useStreamEncoder;
+	bool                   usesBitrate = false;
 
 	string                 aacEncoderID[MAX_AUDIO_MIXES];
 
@@ -1009,10 +1010,13 @@ struct AdvancedOutput : BasicOutputHandler {
 
 	virtual bool StartStreaming(obs_service_t *service) override;
 	virtual bool StartRecording() override;
+	virtual bool StartReplayBuffer() override;
 	virtual void StopStreaming(bool force) override;
 	virtual void StopRecording(bool force) override;
+	virtual void StopReplayBuffer(bool force) override;
 	virtual bool StreamingActive() const override;
 	virtual bool RecordingActive() const override;
+	virtual bool ReplayBufferActive() const override;
 };
 
 static OBSData GetDataFromJsonFile(const char *jsonFile)
@@ -1050,6 +1054,15 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
 	OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json");
 	OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json");
 
+	const char *rate_control = obs_data_get_string(
+			useStreamEncoder ? streamEncSettings : recordEncSettings,
+			"rate_control");
+	if (!rate_control)
+		rate_control = "";
+	usesBitrate = astrcmpi(rate_control, "CBR") == 0 ||
+	              astrcmpi(rate_control, "VBR") == 0 ||
+	              astrcmpi(rate_control, "ABR") == 0;
+
 	if (ffmpegOutput) {
 		fileOutput = obs_output_create("ffmpeg_output",
 				"adv_ffmpeg_output", nullptr, nullptr);
@@ -1058,6 +1071,33 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
 			      "(advanced output)";
 		obs_output_release(fileOutput);
 	} else {
+		bool useReplayBuffer = config_get_bool(main->Config(),
+				"AdvOut", "RecRB");
+		if (useReplayBuffer) {
+			const char *str = config_get_string(main->Config(),
+					"Hotkeys", "ReplayBuffer");
+			obs_data_t *hotkey = obs_data_create_from_json(str);
+			replayBuffer = obs_output_create("replay_buffer",
+					Str("ReplayBuffer"), nullptr, hotkey);
+
+			obs_data_release(hotkey);
+			if (!replayBuffer)
+				throw "Failed to create replay buffer output "
+						"(simple output)";
+			obs_output_release(replayBuffer);
+
+			signal_handler_t *signal =
+					obs_output_get_signal_handler(
+							replayBuffer);
+
+			startReplayBuffer.Connect(signal, "start",
+					OBSStartReplayBuffer, this);
+			stopReplayBuffer.Connect(signal, "stop",
+					OBSStopReplayBuffer, this);
+			replayBufferStopping.Connect(signal, "stopping",
+					OBSReplayBufferStopping, this);
+		}
+
 		fileOutput = obs_output_create("ffmpeg_muxer",
 				"adv_file_output", nullptr, nullptr);
 		if (!fileOutput)
@@ -1174,6 +1214,9 @@ inline void AdvancedOutput::SetupRecording()
 
 	if (useStreamEncoder) {
 		obs_output_set_video_encoder(fileOutput, h264Streaming);
+		if (replayBuffer)
+			obs_output_set_video_encoder(replayBuffer,
+					h264Streaming);
 	} else {
 		if (rescale && rescaleRes && *rescaleRes) {
 			if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) {
@@ -1185,18 +1228,27 @@ inline void AdvancedOutput::SetupRecording()
 		obs_encoder_set_scaled_size(h264Recording, cx, cy);
 		obs_encoder_set_video(h264Recording, obs_get_video());
 		obs_output_set_video_encoder(fileOutput, h264Recording);
+		if (replayBuffer)
+			obs_output_set_video_encoder(replayBuffer,
+					h264Recording);
 	}
 
 	for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
 		if ((tracks & (1<<i)) != 0) {
 			obs_output_set_audio_encoder(fileOutput, aacTrack[i],
-					idx++);
+					idx);
+			if (replayBuffer)
+				obs_output_set_audio_encoder(replayBuffer,
+						aacTrack[i], idx);
+			idx++;
 		}
 	}
 
 	obs_data_set_string(settings, "path", path);
 	obs_data_set_string(settings, "muxer_settings", mux);
 	obs_output_update(fileOutput, settings);
+	if (replayBuffer)
+		obs_output_update(replayBuffer, settings);
 	obs_data_release(settings);
 }
 
@@ -1549,6 +1601,121 @@ bool AdvancedOutput::StartRecording()
 	return true;
 }
 
+bool AdvancedOutput::StartReplayBuffer()
+{
+	const char *path;
+	const char *recFormat;
+	const char *filenameFormat;
+	bool noSpace = false;
+	bool overwriteIfExists = false;
+	const char *rbPrefix;
+	const char *rbSuffix;
+	int rbTime;
+	int rbSize;
+
+	if (!useStreamEncoder) {
+		if (!ffmpegOutput)
+			UpdateRecordingSettings();
+	} else if (!obs_output_active(streamOutput)) {
+		UpdateStreamSettings();
+	}
+
+	UpdateAudioSettings();
+
+	if (!Active())
+		SetupOutputs();
+
+	if (!ffmpegOutput || ffmpegRecording) {
+		path = config_get_string(main->Config(), "AdvOut",
+				ffmpegRecording ? "FFFilePath" : "RecFilePath");
+		recFormat = config_get_string(main->Config(), "AdvOut",
+				ffmpegRecording ? "FFExtension" : "RecFormat");
+		filenameFormat = config_get_string(main->Config(), "Output",
+				"FilenameFormatting");
+		overwriteIfExists = config_get_bool(main->Config(), "Output",
+				"OverwriteIfExists");
+		noSpace = config_get_bool(main->Config(), "AdvOut",
+				ffmpegRecording ?
+				"FFFileNameWithoutSpace" :
+				"RecFileNameWithoutSpace");
+		rbPrefix = config_get_string(main->Config(), "SimpleOutput",
+				"RecRBPrefix");
+		rbSuffix = config_get_string(main->Config(), "SimpleOutput",
+				"RecRBSuffix");
+		rbTime = config_get_int(main->Config(), "AdvOut",
+				"RecRBTime");
+		rbSize = config_get_int(main->Config(), "AdvOut",
+				"RecRBSize");
+
+		os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr;
+
+		if (!dir) {
+			if (main->isVisible())
+				OBSMessageBox::information(main,
+						QTStr("Output.BadPath.Title"),
+						QTStr("Output.BadPath.Text"));
+			else
+				main->SysTrayNotify(QTStr("Output.BadPath.Text"),
+						QSystemTrayIcon::Warning);
+			return false;
+		}
+
+		os_closedir(dir);
+
+		string strPath;
+		strPath += path;
+
+		char lastChar = strPath.back();
+		if (lastChar != '/' && lastChar != '\\')
+			strPath += "/";
+
+		strPath += GenerateSpecifiedFilename(recFormat, noSpace,
+				filenameFormat);
+		ensure_directory_exists(strPath);
+		if (!overwriteIfExists)
+			FindBestFilename(strPath, noSpace);
+
+		obs_data_t *settings = obs_data_create();
+		string f;
+
+		if (rbPrefix && *rbPrefix) {
+			f += rbPrefix;
+			if (f.back() != ' ')
+				f += " ";
+		}
+
+		f += filenameFormat;
+
+		if (rbSuffix && *rbSuffix) {
+			if (*rbSuffix != ' ')
+				f += " ";
+			f += rbSuffix;
+		}
+
+		remove_reserved_file_characters(f);
+
+		obs_data_set_string(settings, "directory", path);
+		obs_data_set_string(settings, "format", f.c_str());
+		obs_data_set_string(settings, "extension", recFormat);
+		obs_data_set_int(settings, "max_time_sec", rbTime);
+		obs_data_set_int(settings, "max_size_mb",
+				usesBitrate ? 0 : rbSize);
+
+		obs_output_update(replayBuffer, settings);
+
+		obs_data_release(settings);
+	}
+
+	if (!obs_output_start(replayBuffer)) {
+		QMessageBox::critical(main,
+				QTStr("Output.StartRecordingFailed"),
+				QTStr("Output.StartFailedGeneric"));
+		return false;
+	}
+
+	return true;
+}
+
 void AdvancedOutput::StopStreaming(bool force)
 {
 	if (force)
@@ -1565,6 +1732,14 @@ void AdvancedOutput::StopRecording(bool force)
 		obs_output_stop(fileOutput);
 }
 
+void AdvancedOutput::StopReplayBuffer(bool force)
+{
+	if (force)
+		obs_output_force_stop(replayBuffer);
+	else
+		obs_output_stop(replayBuffer);
+}
+
 bool AdvancedOutput::StreamingActive() const
 {
 	return obs_output_active(streamOutput);
@@ -1575,6 +1750,11 @@ bool AdvancedOutput::RecordingActive() const
 	return obs_output_active(fileOutput);
 }
 
+bool AdvancedOutput::ReplayBufferActive() const
+{
+	return obs_output_active(replayBuffer);
+}
+
 /* ------------------------------------------------------------------------ */
 
 BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)

+ 4 - 0
UI/window-basic-main.cpp

@@ -1057,6 +1057,10 @@ bool OBSBasic::InitBasicConfigDefaults()
 	config_set_default_uint  (basicConfig, "AdvOut", "Track5Bitrate", 160);
 	config_set_default_uint  (basicConfig, "AdvOut", "Track6Bitrate", 160);
 
+	config_set_default_bool  (basicConfig, "AdvOut", "RecRB", false);
+	config_set_default_uint  (basicConfig, "AdvOut", "RecRBTime", 20);
+	config_set_default_int   (basicConfig, "AdvOut", "RecRBSize", 512);
+
 	config_set_default_uint  (basicConfig, "Video", "BaseCX",   cx);
 	config_set_default_uint  (basicConfig, "Video", "BaseCY",   cy);
 

+ 147 - 2
UI/window-basic-settings.cpp

@@ -387,6 +387,9 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 	HookWidget(ui->advOutTrack5Name,     EDIT_CHANGED,   OUTPUTS_CHANGED);
 	HookWidget(ui->advOutTrack6Bitrate,  COMBO_CHANGED,  OUTPUTS_CHANGED);
 	HookWidget(ui->advOutTrack6Name,     EDIT_CHANGED,   OUTPUTS_CHANGED);
+	HookWidget(ui->advReplayBuf,         CHECK_CHANGED,  OUTPUTS_CHANGED);
+	HookWidget(ui->advRBSecMax,          SCROLL_CHANGED, OUTPUTS_CHANGED);
+	HookWidget(ui->advRBMegsMax,         SCROLL_CHANGED, OUTPUTS_CHANGED);
 	HookWidget(ui->channelSetup,         COMBO_CHANGED,  AUDIO_RESTART);
 	HookWidget(ui->sampleRate,           COMBO_CHANGED,  AUDIO_RESTART);
 	HookWidget(ui->desktopAudioDevice1,  COMBO_CHANGED,  AUDIO_CHANGED);
@@ -612,6 +615,38 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
 			this, SLOT(SimpleReplayBufferChanged()));
 	connect(ui->simpleRBSecMax, SIGNAL(valueChanged(int)),
 			this, SLOT(SimpleReplayBufferChanged()));
+	connect(ui->advReplayBuf, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack1, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack2, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack3, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack4, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack5, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecTrack6, SIGNAL(toggled(bool)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack1Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack2Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack3Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack4Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack5Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutTrack6Bitrate, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecType, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advOutRecEncoder, SIGNAL(currentIndexChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
+	connect(ui->advRBSecMax, SIGNAL(valueChanged(int)),
+			this, SLOT(AdvReplayBufferChanged()));
 	connect(ui->listWidget, SIGNAL(currentRowChanged(int)),
 			this, SLOT(SimpleRecordingEncoderChanged()));
 
@@ -1491,6 +1526,8 @@ void OBSBasicSettings::LoadAdvOutputStreamingEncoderProperties()
 
 	connect(streamEncoderProps, SIGNAL(Changed()),
 			this, SLOT(UpdateStreamDelayEstimate()));
+	connect(streamEncoderProps, SIGNAL(Changed()),
+			this, SLOT(AdvReplayBufferChanged()));
 
 	curAdvStreamEncoder = type;
 
@@ -1557,6 +1594,8 @@ void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties()
 		recordEncoderProps = CreateEncoderPropertyView(type,
 				"recordEncoder.json");
 		ui->advOutRecStandard->layout()->addWidget(recordEncoderProps);
+		connect(recordEncoderProps, SIGNAL(Changed()),
+				this, SLOT(AdvReplayBufferChanged()));
 	}
 
 	curAdvRecordEncoder = type;
@@ -2050,6 +2089,13 @@ void OBSBasicSettings::LoadAdvancedSettings()
 			"RecRBPrefix");
 	const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput",
 			"RecRBSuffix");
+	bool replayBuf = config_get_bool(main->Config(), "AdvOut",
+			"RecRB");
+	int rbTime = config_get_int(main->Config(), "AdvOut",
+			"RecRBTime");
+	int rbSize = config_get_int(main->Config(), "AdvOut",
+			"RecRBSize");
+
 	loading = true;
 
 	LoadRendererList();
@@ -2064,6 +2110,10 @@ void OBSBasicSettings::LoadAdvancedSettings()
 	ui->simpleRBPrefix->setText(rbPrefix);
 	ui->simpleRBSuffix->setText(rbSuffix);
 
+	ui->advReplayBuf->setChecked(replayBuf);
+	ui->advRBSecMax->setValue(rbTime);
+	ui->advRBMegsMax->setValue(rbSize);
+
 	ui->reconnectEnable->setChecked(reconnect);
 	ui->reconnectRetryDelay->setValue(retryDelay);
 	ui->reconnectMaxRetries->setValue(maxRetries);
@@ -2854,6 +2904,10 @@ void OBSBasicSettings::SaveOutputSettings()
 	SaveEdit(ui->advOutTrack5Name, "AdvOut", "Track5Name");
 	SaveEdit(ui->advOutTrack6Name, "AdvOut", "Track6Name");
 
+	SaveCheckBox(ui->advReplayBuf, "AdvOut", "RecRB");
+	SaveSpinBox(ui->advRBSecMax, "AdvOut", "RecRBTime");
+	SaveSpinBox(ui->advRBMegsMax, "AdvOut", "RecRBSize");
+
 	WriteJsonData(streamEncoderProps, "streamEncoder.json");
 	WriteJsonData(recordEncoderProps, "recordEncoder.json");
 	main->ResetOutputs();
@@ -3184,6 +3238,8 @@ void OBSBasicSettings::on_advOutRecEncoder_currentIndexChanged(int idx)
 				loadSettings ? "recordEncoder.json" : nullptr,
 				true);
 		ui->advOutRecStandard->layout()->addWidget(recordEncoderProps);
+		connect(recordEncoderProps, SIGNAL(Changed()),
+				this, SLOT(AdvReplayBufferChanged()));
 	}
 }
 
@@ -3730,8 +3786,15 @@ void OBSBasicSettings::SimpleStreamingEncoderChanged()
 
 void OBSBasicSettings::UpdateAutomaticReplayBufferCheckboxes()
 {
-	bool state = ui->simpleReplayBuf->isChecked() &&
-		ui->outputMode->currentIndex() == 0;
+	bool state = false;
+	switch (ui->outputMode->currentIndex()) {
+	case 0:
+		state = ui->simpleReplayBuf->isChecked();
+		break;
+	case 1:
+		state = ui->advReplayBuf->isChecked();
+		break;
+	}
 	ui->replayWhileStreaming->setEnabled(state);
 	ui->keepReplayStreamStops->setEnabled(state &&
 			ui->replayWhileStreaming->isChecked());
@@ -3766,7 +3829,89 @@ void OBSBasicSettings::SimpleReplayBufferChanged()
 	ui->simpleReplayBuf->setVisible(!lossless);
 
 	UpdateAutomaticReplayBufferCheckboxes();
+}
+
+void OBSBasicSettings::AdvReplayBufferChanged()
+{
+	obs_data_t *settings;
+	QString encoder = ui->advOutRecEncoder->currentText();
+	bool useStream = QString::compare(encoder, TEXT_USE_STREAM_ENC) == 0;
+
+	if (useStream && streamEncoderProps) {
+		settings = streamEncoderProps->GetSettings();
+	} else if (!useStream && recordEncoderProps) {
+		settings = recordEncoderProps->GetSettings();
+	} else {
+		if (useStream)
+			encoder = GetComboData(ui->advOutEncoder);
+		settings = obs_encoder_defaults(encoder.toUtf8().constData());
 
+		if (!settings)
+			return;
+
+		char encoderJsonPath[512];
+		int ret = GetProfilePath(encoderJsonPath,
+				sizeof(encoderJsonPath), "recordEncoder.json");
+		if (ret > 0) {
+			obs_data_t *data = obs_data_create_from_json_file_safe(
+					encoderJsonPath, "bak");
+			obs_data_apply(settings, data);
+			obs_data_release(data);
+		}
+	}
+
+	int vbitrate = (int)obs_data_get_int(settings, "bitrate");
+	const char *rateControl = obs_data_get_string(settings, "rate_control");
+
+	bool lossless = strcmp(rateControl, "lossless") == 0 ||
+			ui->advOutRecType->currentIndex() == 1;
+	bool replayBufferEnabled = ui->advReplayBuf->isChecked();
+
+	int abitrate = 0;
+	if (ui->advOutRecTrack1->isChecked())
+		abitrate += ui->advOutTrack1Bitrate->currentText().toInt();
+	if (ui->advOutRecTrack2->isChecked())
+		abitrate += ui->advOutTrack2Bitrate->currentText().toInt();
+	if (ui->advOutRecTrack3->isChecked())
+		abitrate += ui->advOutTrack3Bitrate->currentText().toInt();
+	if (ui->advOutRecTrack4->isChecked())
+		abitrate += ui->advOutTrack4Bitrate->currentText().toInt();
+	if (ui->advOutRecTrack5->isChecked())
+		abitrate += ui->advOutTrack5Bitrate->currentText().toInt();
+	if (ui->advOutRecTrack6->isChecked())
+		abitrate += ui->advOutTrack6Bitrate->currentText().toInt();
+
+	int seconds = ui->advRBSecMax->value();
+
+	int64_t memMB = int64_t(seconds) * int64_t(vbitrate + abitrate) *
+			1000 / 8 / 1024 / 1024;
+	if (memMB < 1)
+		memMB = 1;
+
+	if (!rateControl)
+		rateControl = "";
+
+	bool varRateControl = (astrcmpi(rateControl, "CBR") == 0 ||
+	                       astrcmpi(rateControl, "VBR") == 0 ||
+	                       astrcmpi(rateControl, "ABR") == 0);
+	if (vbitrate == 0)
+		varRateControl = false;
+
+	ui->advRBMegsMax->setVisible(!varRateControl);
+	ui->advRBMegsMaxLabel->setVisible(!varRateControl);
+
+	if (varRateControl)
+		ui->advRBEstimate->setText(
+				QTStr(ESTIMATE_STR).arg(
+				QString::number(int(memMB))));
+	else
+		ui->advRBEstimate->setText(QTStr(ESTIMATE_UNKNOWN_STR));
+
+	ui->advReplayBufferGroupBox->setVisible(!lossless && replayBufferEnabled);
+	ui->line_4->setVisible(!lossless && replayBufferEnabled);
+	ui->advReplayBuf->setEnabled(!lossless);
+
+	UpdateAutomaticReplayBufferCheckboxes();
 }
 
 #define SIMPLE_OUTPUT_WARNING(str) \

+ 1 - 0
UI/window-basic-settings.hpp

@@ -295,6 +295,7 @@ private slots:
 	void SimpleRecordingQualityLosslessWarning(int idx);
 
 	void SimpleReplayBufferChanged();
+	void AdvReplayBufferChanged();
 
 	void SimpleStreamingEncoderChanged();
 

Some files were not shown because too many files changed in this diff