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
This commit is contained in:
Benjamin Schubert 2017-09-05 20:01:49 -04:00 committed by jp9000
parent 48cc90d5bb
commit 9cba5660e4
5 changed files with 2491 additions and 2046 deletions

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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