diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini
index a1dd412e3..21536d473 100644
--- a/obs/data/locale/en-US.ini
+++ b/obs/data/locale/en-US.ini
@@ -19,6 +19,7 @@ Properties="Properties"
MoveUp="Move Up"
MoveDown="Move Down"
Settings="Settings"
+Name="Name"
Exit="Exit"
Mixer="Mixer"
Browse="Browse"
@@ -216,9 +217,13 @@ Basic.Settings.Stream.StreamType="Stream Type"
# basic mode 'output' settings
Basic.Settings.Output="Output"
+Basic.Settings.Output.Encoder="Encoder"
+Basic.Settings.Output.SelectDirectory="Select Recording Directory"
+Basic.Settings.Output.SelectFile="Select Recording File"
Basic.Settings.Output.Mode="Output Mode"
Basic.Settings.Output.Mode.Simple="Simple (Stream and/or record)"
-Basic.Settings.Output.Mode.Advanced="Advanced (Custom output type)"
+Basic.Settings.Output.Mode.Adv="Advanced"
+Basic.Settings.Output.Mode.FFmpeg="FFmpeg Output"
Basic.Settings.Output.Simple.SavePath="FLV Recording Path"
Basic.Settings.Output.VideoBitrate="Video Bitrate"
Basic.Settings.Output.AudioBitrate="Audio Bitrate"
@@ -229,6 +234,29 @@ Basic.Settings.Output.Advanced="Enable Advanced Encoder Settings"
Basic.Settings.Output.EncoderPreset="Encoder Preset (higher = less CPU)"
Basic.Settings.Output.CustomEncoderSettings="Custom Encoder Settings"
Basic.Settings.Output.UseCBR="Use Constant Bitrate"
+Basic.Settings.Output.UseBufferSize="Use Custom Buffer Size"
+
+# basic mode 'output' settings - advanced section
+Basic.Settings.Output.Adv.Rescale="Rescale Output"
+Basic.Settings.Output.Adv.AudioTrack="Audio Track"
+Basic.Settings.Output.Adv.Streaming="Streaming"
+Basic.Settings.Output.Adv.Audio.Track1="Track 1"
+Basic.Settings.Output.Adv.Audio.Track2="Track 2"
+Basic.Settings.Output.Adv.Audio.Track3="Track 3"
+Basic.Settings.Output.Adv.Audio.Track4="Track 4"
+
+# basic mode 'output' settings - advanced section - recording subsection
+Basic.Settings.Output.Adv.Recording="Recording"
+Basic.Settings.Output.Adv.Recording.Type="Type"
+Basic.Settings.Output.Adv.Recording.Type.Standard="Standard"
+Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput="Custom Output (FFmpeg)"
+Basic.Settings.Output.Adv.Recording.UseStreamEncoder="(Use stream encoder)"
+Basic.Settings.Output.Adv.FFmpeg.SaveFilter="Common recording formats (*.avi *.mp4 *.flv *.ts *.mkv *.wav *.aac);;All Files (*.*)"
+Basic.Settings.Output.Adv.FFmpeg.SavePathURL="File path or URL"
+Basic.Settings.Output.Adv.FFmpeg.VEncoder="Video Encoder (blank=default)"
+Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings="Video Encoder Settings (if any)"
+Basic.Settings.Output.Adv.FFmpeg.AEncoder="Audio Encoder (blank=default)"
+Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings="Audio Encoder Settings (if any)"
# basic mode 'video' settings
Basic.Settings.Video="Video"
diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui
index 12cc748cb..71d3d5e61 100644
--- a/obs/forms/OBSBasicSettings.ui
+++ b/obs/forms/OBSBasicSettings.ui
@@ -115,9 +115,6 @@
- -
-
-
-
@@ -134,6 +131,9 @@
+ -
+
+
@@ -280,7 +280,7 @@
-
- false
+ true
@@ -298,7 +298,7 @@
-
- Basic.Settings.Output.Mode.Custom
+ Basic.Settings.Output.Mode.Adv
@@ -638,7 +638,1167 @@
-
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ 0
+
+
+ true
+
+
+
+ Basic.Settings.Output.Adv.Streaming
+
+
+
+ 9
+
+
+ 0
+
+
+ 9
+
+
+ 9
+
+
-
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
-
+
+
+ Basic.Settings.Output.Reconnect
+
+
+ true
+
+
+
+ -
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.RetryDelay
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 30
+
+
+
+ -
+
+
+ Basic.Settings.Output.MaxRetries
+
+
+
+ -
+
+
+ 1
+
+
+ 10000
+
+
+
+ -
+
+
+ Basic.Settings.Output.Encoder
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::RightToLeft
+
+
+ Basic.Settings.Output.Adv.Rescale
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.AudioTrack
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 1
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
+
+ -
+
+
+ 3
+
+
+
+ -
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Basic.Settings.Output.Adv.Recording
+
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+ -
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.Adv.Recording.Type
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+ Basic.Settings.Output.Adv.Recording.Type.Standard
+
+
+ -
+
+ Basic.Settings.Output.Adv.Recording.Type.FFmpegOutput
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.Simple.SavePath
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+ Browse
+
+
+
+
+
+ -
+
+
+ Basic.Settings.Output.Encoder
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::RightToLeft
+
+
+ Basic.Settings.Output.Adv.Rescale
+
+
+
+ -
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ false
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.AudioTrack
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 1
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
+
+ -
+
+
+ 3
+
+
+
+ -
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.Adv.FFmpeg.SavePathURL
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
-
+
+
+ true
+
+
+
+ -
+
+
+ true
+
+
+ Browse
+
+
+
+
+
+ -
+
+
+ Basic.Settings.Output.VideoBitrate
+
+
+
+ -
+
+
+ 0
+
+
+ 1000000000
+
+
+ 2500
+
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.FFmpeg.VEncoder
+
+
+
+ -
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.FFmpeg.VEncoderSettings
+
+
+
+ -
+
+
+ -
+
+
+ Basic.Settings.Output.AudioBitrate
+
+
+
+ -
+
+
+ 32
+
+
+ 4096
+
+
+ 16
+
+
+ 128
+
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.AudioTrack
+
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.FFmpeg.AEncoder
+
+
+
+ -
+
+
+ -
+
+
+ Basic.Settings.Output.Adv.FFmpeg.AEncoderSettings
+
+
+
+ -
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Qt::RightToLeft
+
+
+ Basic.Settings.Output.Adv.Rescale
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ 1
+
+
+ true
+
+
+
+ -
+
+
+ 2
+
+
+
+ -
+
+
+ 3
+
+
+
+ -
+
+
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Basic.Settings.Audio
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Basic.Settings.Output.Adv.Audio.Track1
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
-
+
+
+ 4
+
+
-
+
+ 32
+
+
+ -
+
+ 64
+
+
+ -
+
+ 96
+
+
+ -
+
+ 128
+
+
+ -
+
+ 160
+
+
+ -
+
+ 192
+
+
+ -
+
+ 256
+
+
+ -
+
+ 320
+
+
+
+
+ -
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.AudioBitrate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Basic.Settings.Output.Adv.Audio.Track2
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
-
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.AudioBitrate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 4
+
+
-
+
+ 32
+
+
+ -
+
+ 64
+
+
+ -
+
+ 96
+
+
+ -
+
+ 128
+
+
+ -
+
+ 160
+
+
+ -
+
+ 192
+
+
+ -
+
+ 256
+
+
+ -
+
+ 320
+
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Basic.Settings.Output.Adv.Audio.Track3
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
-
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.AudioBitrate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 4
+
+
-
+
+ 32
+
+
+ -
+
+ 64
+
+
+ -
+
+ 96
+
+
+ -
+
+ 128
+
+
+ -
+
+ 160
+
+
+ -
+
+ 192
+
+
+ -
+
+ 256
+
+
+ -
+
+ 320
+
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Basic.Settings.Output.Adv.Audio.Track4
+
+
+
+ QFormLayout::AllNonFixedFieldsGrow
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
-
+
+
+
+ 170
+ 0
+
+
+
+ Basic.Settings.Output.AudioBitrate
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+ -
+
+
+ 4
+
+
-
+
+ 32
+
+
+ -
+
+ 64
+
+
+ -
+
+ 96
+
+
+ -
+
+ 128
+
+
+ -
+
+ 160
+
+
+ -
+
+ 192
+
+
+ -
+
+ 256
+
+
+ -
+
+ 320
+
+
+
+
+ -
+
+
+ Name
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1162,12 +2322,12 @@
setCurrentIndex(int)
- 252
- 29
+ 159
+ 34
- 250
- 39
+ 241
+ 34
@@ -1263,7 +2423,7 @@
750
- 321
+ 347
@@ -1279,7 +2439,7 @@
367
- 321
+ 347
@@ -1347,5 +2507,133 @@
+
+ advOutRecType
+ currentIndexChanged(int)
+ stackedWidget
+ setCurrentIndex(int)
+
+
+ 737
+ 113
+
+
+ 528
+ 426
+
+
+
+
+ advOutFFUseRescale
+ toggled(bool)
+ advOutFFRescale
+ setEnabled(bool)
+
+
+ 238
+ 186
+
+
+ 495
+ 186
+
+
+
+
+ advOutReconnect
+ toggled(bool)
+ label_27
+ setEnabled(bool)
+
+
+ 467
+ 103
+
+
+ 266
+ 123
+
+
+
+
+ advOutReconnect
+ toggled(bool)
+ advOutRetryDelay
+ setEnabled(bool)
+
+
+ 505
+ 104
+
+
+ 473
+ 134
+
+
+
+
+ advOutReconnect
+ toggled(bool)
+ label_26
+ setEnabled(bool)
+
+
+ 536
+ 105
+
+
+ 288
+ 153
+
+
+
+
+ advOutReconnect
+ toggled(bool)
+ advOutMaxRetries
+ setEnabled(bool)
+
+
+ 544
+ 103
+
+
+ 463
+ 154
+
+
+
+
+ advOutUseRescale
+ toggled(bool)
+ advOutRescale
+ setEnabled(bool)
+
+
+ 409
+ 253
+
+
+ 466
+ 263
+
+
+
+
+ advOutRecUseRescale
+ toggled(bool)
+ advOutRecRescale
+ setEnabled(bool)
+
+
+ 399
+ 247
+
+
+ 469
+ 248
+
+
+
diff --git a/obs/window-basic-main-outputs.cpp b/obs/window-basic-main-outputs.cpp
index b332c2192..afbaa511e 100644
--- a/obs/window-basic-main-outputs.cpp
+++ b/obs/window-basic-main-outputs.cpp
@@ -245,7 +245,446 @@ bool SimpleOutput::RecordingActive() const
/* ------------------------------------------------------------------------ */
+struct AdvancedOutput : BasicOutputHandler {
+ OBSEncoder aacTrack[4];
+ OBSEncoder h264Streaming;
+ OBSEncoder h264Recording;
+
+ bool ffmpegRecording;
+ bool useStreamEncoder;
+
+ AdvancedOutput(OBSBasic *main_);
+
+ inline void UpdateStreamSettings();
+ inline void UpdateRecordingSettings();
+ virtual void Update() override;
+
+ inline void SetupStreaming();
+ inline void SetupRecording();
+ inline void SetupFFmpeg();
+ inline void SetupAudio();
+ void SetupOutputs();
+
+ virtual bool StartStreaming(obs_service_t *service) override;
+ virtual bool StartRecording() override;
+ virtual void StopStreaming() override;
+ virtual void StopRecording() override;
+ virtual bool StreamingActive() const override;
+ virtual bool RecordingActive() const override;
+};
+
+static OBSData GetDataFromJsonFile(const char *jsonFile)
+{
+ char fullPath[512];
+
+ int ret = os_get_config_path(fullPath, sizeof(fullPath), jsonFile);
+ if (ret > 0) {
+ BPtr jsonData = os_quick_read_utf8_file(fullPath);
+ if (!!jsonData) {
+ obs_data_t *data = obs_data_create_from_json(jsonData);
+ OBSData dataRet(data);
+ obs_data_release(data);
+ return dataRet;
+ }
+ }
+
+ return nullptr;
+}
+
+AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
+{
+ const char *recType = config_get_string(main->Config(), "AdvOut",
+ "RecType");
+ const char *streamEncoder = config_get_string(main->Config(), "AdvOut",
+ "Encoder");
+ const char *recordEncoder = config_get_string(main->Config(), "AdvOut",
+ "RecEncoder");
+
+ ffmpegRecording = astrcmpi(recType, "FFmpeg") == 0;
+ useStreamEncoder = astrcmpi(recordEncoder, "none") == 0;
+
+ OBSData streamEncSettings = GetDataFromJsonFile(
+ "obs-studio/basic/streamEncoder.json");
+ OBSData recordEncSettings = GetDataFromJsonFile(
+ "obs-studio/basic/recordEncoder.json");
+
+ streamOutput = obs_output_create("rtmp_output", "adv_stream",
+ nullptr);
+ if (!streamOutput)
+ throw "Failed to create stream output (advanced output)";
+
+ if (ffmpegRecording) {
+ fileOutput = obs_output_create("ffmpeg_output",
+ "adv_ffmpeg_output", nullptr);
+ if (!fileOutput)
+ throw "Failed to create recording FFmpeg output "
+ "(advanced output)";
+ } else {
+ fileOutput = obs_output_create("flv_output", "adv_file_output",
+ nullptr);
+ if (!fileOutput)
+ throw "Failed to create recording output "
+ "(advanced output)";
+
+ if (!useStreamEncoder) {
+ h264Recording = obs_video_encoder_create(recordEncoder,
+ "recording_h264", recordEncSettings);
+ if (!h264Recording)
+ throw "Failed to create recording h264 "
+ "encoder (advanced output)";
+ }
+ }
+
+ h264Streaming = obs_video_encoder_create(streamEncoder,
+ "streaming_h264", streamEncSettings);
+ if (!h264Streaming)
+ throw "Failed to create streaming h264 encoder "
+ "(advanced output)";
+
+ for (int i = 0; i < 4; i++) {
+ char name[9];
+ sprintf(name, "adv_aac%d", i);
+
+ aacTrack[i] = obs_audio_encoder_create("libfdk_aac",
+ name, nullptr, i);
+ if (!aacTrack[i])
+ aacTrack[i] = obs_audio_encoder_create("ffmpeg_aac",
+ name, nullptr, i);
+ if (!aacTrack[i])
+ throw "Failed to create audio encoder "
+ "(advanced output)";
+ }
+
+ signal_handler_connect(obs_output_get_signal_handler(streamOutput),
+ "start", OBSStartStreaming, this);
+ signal_handler_connect(obs_output_get_signal_handler(streamOutput),
+ "stop", OBSStopStreaming, this);
+
+ signal_handler_connect(obs_output_get_signal_handler(fileOutput),
+ "start", OBSStartRecording, this);
+ signal_handler_connect(obs_output_get_signal_handler(fileOutput),
+ "stop", OBSStopRecording, this);
+}
+
+void AdvancedOutput::UpdateStreamSettings()
+{
+ OBSData settings = GetDataFromJsonFile(
+ "obs-studio/basic/streamEncoder.json");
+ obs_encoder_update(h264Streaming, settings);
+}
+
+inline void AdvancedOutput::UpdateRecordingSettings()
+{
+ OBSData settings = GetDataFromJsonFile(
+ "obs-studio/basic/recordEncoder.json");
+ obs_encoder_update(h264Recording, settings);
+}
+
+void AdvancedOutput::Update()
+{
+ UpdateStreamSettings();
+ if (useStreamEncoder && !ffmpegRecording)
+ UpdateRecordingSettings();
+}
+
+inline void AdvancedOutput::SetupStreaming()
+{
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "Rescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "RescaleRes");
+ bool multitrack = config_get_bool(main->Config(), "AdvOut",
+ "Multitrack");
+ int trackIndex = config_get_int(main->Config(), "AdvOut",
+ "TrackIndex");
+ int trackCount = config_get_int(main->Config(), "AdvOut",
+ "TrackCount");
+ unsigned int cx = 0;
+ unsigned int cy = 0;
+
+ if (rescale && sscanf(rescaleRes, "%ux%u", &cx, &cy) != 3) {
+ cx = 0;
+ cy = 0;
+ }
+
+ obs_encoder_set_scaled_size(h264Streaming, cx, cy);
+ obs_encoder_set_video(h264Streaming, obs_get_video());
+
+ obs_output_set_video_encoder(streamOutput, h264Streaming);
+
+ if (multitrack) {
+ int i = 0;
+ for (; i < trackCount; i++)
+ obs_output_set_audio_encoder(streamOutput, aacTrack[i],
+ i);
+ for (; i < 4; i++)
+ obs_output_set_audio_encoder(streamOutput, nullptr, i);
+ } else {
+ obs_output_set_audio_encoder(streamOutput,
+ aacTrack[trackIndex - 1], 0);
+ }
+}
+
+inline void AdvancedOutput::SetupRecording()
+{
+ const char *path = config_get_string(main->Config(), "AdvOut",
+ "RecFilePath");
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "RecRescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "RecRescaleRes");
+ bool multitrack = config_get_bool(main->Config(), "AdvOut",
+ "RecMultitrack");
+ int trackIndex = config_get_int(main->Config(), "AdvOut",
+ "RecTrackIndex");
+ int trackCount = config_get_int(main->Config(), "AdvOut",
+ "RecTrackCount");
+ obs_data_t *settings = obs_data_create();
+ unsigned int cx = 0;
+ unsigned int cy = 0;
+
+ if (useStreamEncoder) {
+ obs_output_set_video_encoder(fileOutput, h264Streaming);
+ } else {
+ if (rescale && sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) {
+ cx = 0;
+ cy = 0;
+ }
+
+ obs_encoder_set_scaled_size(h264Recording, cx, cy);
+ obs_encoder_set_video(h264Recording, obs_get_video());
+ obs_output_set_video_encoder(fileOutput, h264Recording);
+ }
+
+ if (multitrack) {
+ int i = 0;
+ for (; i < trackCount; i++)
+ obs_output_set_audio_encoder(fileOutput, aacTrack[i],
+ i);
+ for (; i < 4; i++)
+ obs_output_set_audio_encoder(fileOutput, nullptr, i);
+ } else {
+ obs_output_set_audio_encoder(fileOutput,
+ aacTrack[trackIndex - 1], 0);
+ }
+
+ obs_data_set_string(settings, "path", path);
+ obs_output_update(fileOutput, settings);
+ obs_data_release(settings);
+}
+
+inline void AdvancedOutput::SetupFFmpeg()
+{
+ const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
+ int vBitrate = config_get_int(main->Config(), "AdvOut",
+ "FFVBitrate");
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "FFRescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "FFRescaleRes");
+ const char *vEncoder = config_get_string(main->Config(), "AdvOut",
+ "FFVEncoder");
+ const char *vEncCustom = config_get_string(main->Config(), "AdvOut",
+ "FFVCustom");
+ int aBitrate = config_get_int(main->Config(), "AdvOut",
+ "FFABitrate");
+ int aTrack = config_get_int(main->Config(), "AdvOut",
+ "FFAudioTrack");
+ const char *aEncoder = config_get_string(main->Config(), "AdvOut",
+ "FFAEncoder");
+ const char *aEncCustom = config_get_string(main->Config(), "AdvOut",
+ "FFACustom");
+ obs_data_t *settings = obs_data_create();
+
+ obs_data_set_string(settings, "url", url);
+ obs_data_set_int(settings, "video_bitrate", vBitrate);
+ obs_data_set_string(settings, "video_encoder", vEncoder);
+ obs_data_set_string(settings, "video_settings", vEncCustom);
+ obs_data_set_int(settings, "audio_bitrate", aBitrate);
+ obs_data_set_string(settings, "audio_encoder", aEncoder);
+ obs_data_set_string(settings, "audio_settings", aEncCustom);
+
+ if (rescale && rescaleRes && *rescaleRes) {
+ int width;
+ int height;
+ int val = sscanf(rescaleRes, "%dx%d", &width, &height);
+
+ if (val == 2 && width && height) {
+ obs_data_set_int(settings, "scale_width", width);
+ obs_data_set_int(settings, "scale_height", height);
+ }
+ }
+
+ obs_output_set_mixer(fileOutput, aTrack - 1);
+ obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
+ obs_output_update(fileOutput, settings);
+
+ obs_data_release(settings);
+}
+
+static inline void SetEncoderName(obs_encoder_t *encoder, const char *name,
+ const char *defaultName)
+{
+ obs_encoder_set_name(encoder, (name && *name) ? name : defaultName);
+}
+
+inline void AdvancedOutput::SetupAudio()
+{
+ int track1Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track1Bitrate");
+ int track2Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track2Bitrate");
+ int track3Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track3Bitrate");
+ int track4Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track4Bitrate");
+ const char *name1 = config_get_string(main->Config(), "AdvOut",
+ "Track1Name");
+ const char *name2 = config_get_string(main->Config(), "AdvOut",
+ "Track2Name");
+ const char *name3 = config_get_string(main->Config(), "AdvOut",
+ "Track3Name");
+ const char *name4 = config_get_string(main->Config(), "AdvOut",
+ "Track4Name");
+ obs_data_t *settings[4];
+
+ for (size_t i = 0; i < 4; i++)
+ settings[i] = obs_data_create();
+
+ obs_data_set_int(settings[0], "bitrate", track1Bitrate);
+ obs_data_set_int(settings[1], "bitrate", track2Bitrate);
+ obs_data_set_int(settings[2], "bitrate", track3Bitrate);
+ obs_data_set_int(settings[3], "bitrate", track4Bitrate);
+
+ SetEncoderName(aacTrack[0], name1, "Track1");
+ SetEncoderName(aacTrack[1], name2, "Track2");
+ SetEncoderName(aacTrack[2], name3, "Track3");
+ SetEncoderName(aacTrack[3], name4, "Track4");
+
+ for (size_t i = 0; i < 4; i++) {
+ obs_encoder_update(aacTrack[i], settings[i]);
+ obs_data_release(settings[i]);
+ }
+}
+
+void AdvancedOutput::SetupOutputs()
+{
+ obs_encoder_set_video(h264Streaming, obs_get_video());
+ if (h264Recording)
+ obs_encoder_set_video(h264Recording, obs_get_video());
+ obs_encoder_set_audio(aacTrack[0], obs_get_audio());
+ obs_encoder_set_audio(aacTrack[1], obs_get_audio());
+ obs_encoder_set_audio(aacTrack[2], obs_get_audio());
+ obs_encoder_set_audio(aacTrack[3], obs_get_audio());
+
+ SetupStreaming();
+ SetupAudio();
+
+ if (ffmpegRecording)
+ SetupFFmpeg();
+ else
+ SetupRecording();
+}
+
+bool AdvancedOutput::StartStreaming(obs_service_t *service)
+{
+ AdvancedOutput::Update();
+ if (!Active())
+ SetupOutputs();
+
+ obs_output_set_service(streamOutput, service);
+
+ bool reconnect = config_get_bool(main->Config(), "AdvOut", "Reconnect");
+ int retryDelay = config_get_int(main->Config(), "AdvOut", "RetryDelay");
+ int maxRetries = config_get_int(main->Config(), "AdvOut", "MaxRetries");
+ if (!reconnect)
+ maxRetries = 0;
+
+ obs_output_set_reconnect_settings(streamOutput, maxRetries,
+ retryDelay);
+
+ if (obs_output_start(streamOutput)) {
+ activeRefs++;
+ return true;
+ }
+
+ return false;
+}
+
+bool AdvancedOutput::StartRecording()
+{
+ AdvancedOutput::Update();
+ if (!Active())
+ SetupOutputs();
+
+ if (!ffmpegRecording) {
+ const char *path = config_get_string(main->Config(),
+ "AdvOut", "RecFilePath");
+
+ os_dir_t *dir = path ? os_opendir(path) : nullptr;
+
+ if (!dir) {
+ QMessageBox::information(main,
+ QTStr("Output.BadPath.Title"),
+ QTStr("Output.BadPath.Text"));
+ return false;
+ }
+
+ os_closedir(dir);
+
+ string strPath;
+ strPath += path;
+
+ char lastChar = strPath.back();
+ if (lastChar != '/' && lastChar != '\\')
+ strPath += "/";
+
+ strPath += GenerateTimeDateFilename("flv");
+
+ obs_data_t *settings = obs_data_create();
+ obs_data_set_string(settings, "path", strPath.c_str());
+
+ obs_output_update(fileOutput, settings);
+
+ obs_data_release(settings);
+ }
+
+ if (obs_output_start(fileOutput)) {
+ activeRefs++;
+ return true;
+ }
+
+ return false;
+}
+
+void AdvancedOutput::StopStreaming()
+{
+ obs_output_stop(streamOutput);
+}
+
+void AdvancedOutput::StopRecording()
+{
+ obs_output_stop(fileOutput);
+}
+
+bool AdvancedOutput::StreamingActive() const
+{
+ return obs_output_active(streamOutput);
+}
+
+bool AdvancedOutput::RecordingActive() const
+{
+ return obs_output_active(fileOutput);
+}
+
+/* ------------------------------------------------------------------------ */
+
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)
{
return new SimpleOutput(main);
}
+
+BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main)
+{
+ return new AdvancedOutput(main);
+}
diff --git a/obs/window-basic-main-outputs.hpp b/obs/window-basic-main-outputs.hpp
index 1134c8b47..b3d6b39fa 100644
--- a/obs/window-basic-main-outputs.hpp
+++ b/obs/window-basic-main-outputs.hpp
@@ -25,3 +25,4 @@ struct BasicOutputHandler {
};
BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main);
+BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main);
diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp
index f91715cc2..a25fa19de 100644
--- a/obs/window-basic-main.cpp
+++ b/obs/window-basic-main.cpp
@@ -385,7 +385,6 @@ bool OBSBasic::InitBasicConfigDefaults()
uint32_t cx = monitors[0].cx;
uint32_t cy = monitors[0].cy;
- /* TODO: temporary */
config_set_default_string(basicConfig, "Output", "Type", "Simple");
config_set_default_string(basicConfig, "SimpleOutput", "FilePath",
@@ -407,6 +406,39 @@ bool OBSBasic::InitBasicConfigDefaults()
config_set_default_string(basicConfig, "SimpleOutput", "Preset",
"veryfast");
+ config_set_default_bool (basicConfig, "AdvOut", "Reconnect", true);
+ config_set_default_uint (basicConfig, "AdvOut", "RetryDelay", 2);
+ config_set_default_uint (basicConfig, "AdvOut", "MaxRetries", 20);
+ config_set_default_bool (basicConfig, "AdvOut", "UseRescale", false);
+ config_set_default_bool (basicConfig, "AdvOut", "Multitrack", false);
+ config_set_default_uint (basicConfig, "AdvOut", "TrackIndex", 1);
+ config_set_default_uint (basicConfig, "AdvOut", "TrackCount", 1);
+ config_set_default_string(basicConfig, "AdvOut", "Encoder", "obs_x264");
+
+ config_set_default_string(basicConfig, "AdvOut", "RecType", "Standard");
+
+ config_set_default_string(basicConfig, "AdvOut", "RecFilePath",
+ GetDefaultVideoSavePath().c_str());
+ config_set_default_bool (basicConfig, "AdvOut", "RecUseRescale",
+ false);
+ config_set_default_bool (basicConfig, "AdvOut", "RecMultitrack",
+ false);
+ config_set_default_uint (basicConfig, "AdvOut", "RecTrackIndex", 1);
+ config_set_default_uint (basicConfig, "AdvOut", "RecTrackCount", 1);
+ config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
+ "none");
+
+ config_set_default_uint (basicConfig, "AdvOut", "FFVBitrate", 2500);
+ config_set_default_bool (basicConfig, "AdvOut", "FFUseRescale",
+ false);
+ config_set_default_uint (basicConfig, "AdvOut", "FFABitrate", 160);
+ config_set_default_uint (basicConfig, "AdvOut", "FFAudioTrack", 1);
+
+ config_set_default_uint (basicConfig, "AdvOut", "Track1Bitrate", 160);
+ config_set_default_uint (basicConfig, "AdvOut", "Track2Bitrate", 160);
+ config_set_default_uint (basicConfig, "AdvOut", "Track3Bitrate", 160);
+ config_set_default_uint (basicConfig, "AdvOut", "Track4Bitrate", 160);
+
config_set_default_uint (basicConfig, "Video", "BaseCX", cx);
config_set_default_uint (basicConfig, "Video", "BaseCY", cy);
@@ -504,9 +536,14 @@ void OBSBasic::InitPrimitives()
void OBSBasic::ResetOutputs()
{
+ const char *mode = config_get_string(basicConfig, "Output", "Mode");
+ bool advOut = astrcmpi(mode, "Advanced") == 0;
+
if (!outputHandler || !outputHandler->Active()) {
outputHandler.reset();
- outputHandler.reset(CreateSimpleOutputHandler(this));
+ outputHandler.reset(advOut ?
+ CreateAdvancedOutputHandler(this) :
+ CreateSimpleOutputHandler(this));
} else {
outputHandler->Update();
}
diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp
index 2397ee818..c2d3262bf 100644
--- a/obs/window-basic-settings.cpp
+++ b/obs/window-basic-settings.cpp
@@ -141,9 +141,49 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
HookWidget(ui->simpleOutAdvanced, CHECK_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->simpleOutUseCBR, CHECK_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->simpleOutPreset, COMBO_CHANGED, OUTPUTS_CHANGED);
- HookWidget(ui->simpleOutCustom, EDIT_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->simpleOutUseBufsize, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->simpleOutPreset, COMBO_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->simpleOutVBufsize, SCROLL_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutReconnect, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRetryDelay, SCROLL_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutMaxRetries, SCROLL_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutEncoder, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutUseRescale, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRescale, CBEDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack1, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack2, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack3, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack4, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecType, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecPath, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecEncoder, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecUseRescale, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecRescale, CBEDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecTrack1, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecTrack2, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecTrack3, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutRecTrack4, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFURL, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFVBitrate, SCROLL_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFUseRescale, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFRescale, CBEDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFVEncoder, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFVCfg, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFABitrate, SCROLL_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFTrack1, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFTrack2, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFTrack3, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFTrack4, CHECK_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFAEncoder, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutFFACfg, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack1Bitrate, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack1Name, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack2Bitrate, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack2Name, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack3Bitrate, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack3Name, EDIT_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack4Bitrate, COMBO_CHANGED, OUTPUTS_CHANGED);
+ HookWidget(ui->advOutTrack4Name, EDIT_CHANGED, OUTPUTS_CHANGED);
HookWidget(ui->channelSetup, COMBO_CHANGED, AUDIO_RESTART);
HookWidget(ui->sampleRate, COMBO_CHANGED, AUDIO_RESTART);
HookWidget(ui->desktopAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED);
@@ -168,6 +208,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
LoadServiceTypes();
LoadServiceInfo();
+ LoadEncoderTypes();
LoadSettings(false);
}
@@ -189,12 +230,15 @@ void OBSBasicSettings::SaveComboData(QComboBox *widget, const char *section,
}
}
-void OBSBasicSettings::SaveCheckBox(QCheckBox *widget, const char *section,
- const char *value)
+void OBSBasicSettings::SaveCheckBox(QAbstractButton *widget,
+ const char *section, const char *value, bool invert)
{
- if (WidgetChanged(widget))
- config_set_bool(main->Config(), section, value,
- widget->isChecked());
+ if (WidgetChanged(widget)) {
+ bool checked = widget->isChecked();
+ if (invert) checked = !checked;
+
+ config_set_bool(main->Config(), section, value, checked);
+ }
}
void OBSBasicSettings::SaveEdit(QLineEdit *widget, const char *section,
@@ -246,6 +290,31 @@ void OBSBasicSettings::LoadServiceInfo()
obs_data_release(settings);
}
+#define TEXT_USE_STREAM_ENC \
+ QTStr("Basic.Settings.Output.Adv.Recording.UseStreamEncoder")
+
+void OBSBasicSettings::LoadEncoderTypes()
+{
+ const char *type;
+ size_t idx = 0;
+
+ ui->advOutRecEncoder->addItem(TEXT_USE_STREAM_ENC, "none");
+
+ while (obs_enum_encoder_types(idx++, &type)) {
+ const char *name = obs_encoder_get_display_name(type);
+ const char *codec = obs_get_encoder_codec(type);
+
+ if (strcmp(codec, "h264") != 0)
+ continue;
+
+ QString qName = QT_UTF8(name);
+ QString qType = QT_UTF8(type);
+
+ ui->advOutEncoder->addItem(qName, qType);
+ ui->advOutRecEncoder->addItem(qName, qType);
+ }
+}
+
void OBSBasicSettings::LoadLanguageList()
{
const char *currentLang = App()->GetLocale();
@@ -320,7 +389,18 @@ static const size_t numVals = sizeof(vals)/sizeof(double);
void OBSBasicSettings::ResetDownscales(uint32_t cx, uint32_t cy)
{
+ QString advRescale;
+ QString advRecRescale;
+ QString advFFRescale;
+
+ advRescale = ui->advOutRescale->lineEdit()->text();
+ advRecRescale = ui->advOutRecRescale->lineEdit()->text();
+ advFFRescale = ui->advOutFFRescale->lineEdit()->text();
+
ui->outputResolution->clear();
+ ui->advOutRescale->clear();
+ ui->advOutRecRescale->clear();
+ ui->advOutFFRescale->clear();
for (size_t idx = 0; idx < numVals; idx++) {
uint32_t downscaleCX = uint32_t(double(cx) / vals[idx]);
@@ -328,9 +408,25 @@ void OBSBasicSettings::ResetDownscales(uint32_t cx, uint32_t cy)
string res = ResString(downscaleCX, downscaleCY);
ui->outputResolution->addItem(res.c_str());
+ ui->advOutRescale->addItem(res.c_str());
+ ui->advOutRecRescale->addItem(res.c_str());
+ ui->advOutFFRescale->addItem(res.c_str());
}
- ui->outputResolution->lineEdit()->setText(ResString(cx, cy).c_str());
+ string res = ResString(cx, cy);
+
+ ui->outputResolution->lineEdit()->setText(res.c_str());
+
+ if (advRescale.isEmpty())
+ advRescale = res.c_str();
+ if (advRecRescale.isEmpty())
+ advRecRescale = res.c_str();
+ if (advFFRescale.isEmpty())
+ advFFRescale = res.c_str();
+
+ ui->advOutRescale->lineEdit()->setText(advRescale);
+ ui->advOutRecRescale->lineEdit()->setText(advRecRescale);
+ ui->advOutFFRescale->lineEdit()->setText(advFFRescale);
}
void OBSBasicSettings::LoadDownscaleFilters()
@@ -483,11 +579,219 @@ void OBSBasicSettings::LoadSimpleOutputSettings()
ui->simpleOutCustom->setText(custom);
}
+void OBSBasicSettings::LoadAdvOutputStreamingSettings()
+{
+ bool reconnect = config_get_bool(main->Config(), "AdvOut",
+ "Reconnect");
+ int retryDelay = config_get_int(main->Config(), "AdvOut",
+ "RetryDelay");
+ int maxRetries = config_get_int(main->Config(), "AdvOut",
+ "MaxRetries");
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "Rescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "RescaleRes");
+ int trackIndex = config_get_int(main->Config(), "AdvOut",
+ "TrackIndex");
+
+ ui->advOutReconnect->setChecked(reconnect);
+ ui->advOutRetryDelay->setValue(retryDelay);
+ ui->advOutMaxRetries->setValue(maxRetries);
+ ui->advOutUseRescale->setChecked(rescale);
+ ui->advOutRescale->setCurrentText(rescaleRes);
+
+ switch (trackIndex) {
+ case 1: ui->advOutTrack1->setChecked(true); break;
+ case 2: ui->advOutTrack2->setChecked(true); break;
+ case 3: ui->advOutTrack3->setChecked(true); break;
+ case 4: ui->advOutTrack4->setChecked(true); break;
+ }
+}
+
+OBSPropertiesView *OBSBasicSettings::CreateEncoderPropertyView(
+ const char *encoder, const char *path, bool changed)
+{
+ obs_data_t *settings = obs_encoder_defaults(encoder);
+ OBSPropertiesView *view;
+
+ char encoderJsonPath[512];
+ int ret = os_get_config_path(encoderJsonPath, sizeof(encoderJsonPath),
+ path);
+ if (ret > 0) {
+ BPtr jsonData = os_quick_read_utf8_file(encoderJsonPath);
+ if (!!jsonData) {
+ obs_data_t *data = obs_data_create_from_json(jsonData);
+ obs_data_apply(settings, data);
+ obs_data_release(data);
+ }
+ }
+
+ view = new OBSPropertiesView(settings, encoder,
+ (PropertiesReloadCallback)obs_get_encoder_properties,
+ 170);
+ view->setFrameShape(QFrame::StyledPanel);
+ view->setProperty("changed", QVariant(changed));
+ QObject::connect(view, SIGNAL(Changed()), this, SLOT(OutputsChanged()));
+
+ obs_data_release(settings);
+ return view;
+}
+
+void OBSBasicSettings::LoadAdvOutputStreamingEncoderProperties()
+{
+ const char *encoder = config_get_string(main->Config(), "AdvOut",
+ "Encoder");
+
+ delete streamEncoderProps;
+ streamEncoderProps = CreateEncoderPropertyView(encoder,
+ "obs-studio/basic/streamEncoder.json");
+ ui->advOutputStreamTab->layout()->addWidget(streamEncoderProps);
+
+ SetComboByValue(ui->advOutEncoder, encoder);
+}
+
+void OBSBasicSettings::LoadAdvOutputRecordingSettings()
+{
+ const char *type = config_get_string(main->Config(), "AdvOut",
+ "RecType");
+ const char *path = config_get_string(main->Config(), "AdvOut",
+ "RecFilePath");
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "RecRescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "RecRescaleRes");
+ int trackIndex = config_get_int(main->Config(), "AdvOut",
+ "RecTrackIndex");
+
+ int typeIndex = (astrcmpi(type, "FFmpeg") == 0) ? 1 : 0;
+ ui->advOutRecType->setCurrentIndex(typeIndex);
+ ui->advOutRecPath->setText(path);
+ ui->advOutRecUseRescale->setChecked(rescale);
+ ui->advOutRecRescale->setCurrentText(rescaleRes);
+
+ switch (trackIndex) {
+ case 1: ui->advOutRecTrack1->setChecked(true); break;
+ case 2: ui->advOutRecTrack2->setChecked(true); break;
+ case 3: ui->advOutRecTrack3->setChecked(true); break;
+ case 4: ui->advOutRecTrack4->setChecked(true); break;
+ }
+}
+
+void OBSBasicSettings::LoadAdvOutputRecordingEncoderProperties()
+{
+ const char *encoder = config_get_string(main->Config(), "AdvOut",
+ "RecEncoder");
+
+ delete recordEncoderProps;
+ recordEncoderProps = nullptr;
+
+ if (astrcmpi(encoder, "none") != 0) {
+ recordEncoderProps = CreateEncoderPropertyView(encoder,
+ "obs-studio/basic/recordEncoder.json");
+ ui->advOutRecStandard->layout()->addWidget(recordEncoderProps);
+ }
+
+ SetComboByValue(ui->advOutRecEncoder, encoder);
+}
+
+void OBSBasicSettings::LoadAdvOutputFFmpegSettings()
+{
+ const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
+ int videoBitrate = config_get_int(main->Config(), "AdvOut",
+ "FFVBitrate");
+ bool rescale = config_get_bool(main->Config(), "AdvOut",
+ "FFRescale");
+ const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
+ "FFRescaleRes");
+ const char *vEncoder = config_get_string(main->Config(), "AdvOut",
+ "FFVEncoder");
+ const char *vEncCustom = config_get_string(main->Config(), "AdvOut",
+ "FFVCustom");
+ int audioBitrate = config_get_int(main->Config(), "AdvOut",
+ "FFABitrate");
+ int audioTrack = config_get_int(main->Config(), "AdvOut",
+ "FFAudioTrack");
+ const char *aEncoder = config_get_string(main->Config(), "AdvOut",
+ "FFAEncoder");
+ const char *aEncCustom = config_get_string(main->Config(), "AdvOut",
+ "FFACustom");
+
+ ui->advOutFFURL->setText(url);
+ ui->advOutFFVBitrate->setValue(videoBitrate);
+ ui->advOutFFUseRescale->setChecked(rescale);
+ ui->advOutFFRescale->setCurrentText(rescaleRes);
+ ui->advOutFFVEncoder->setText(vEncoder);
+ ui->advOutFFVCfg->setText(vEncCustom);
+ ui->advOutFFABitrate->setValue(audioBitrate);
+ ui->advOutFFAEncoder->setText(aEncoder);
+ ui->advOutFFACfg->setText(aEncCustom);
+
+ switch (audioTrack) {
+ case 1: ui->advOutFFTrack1->setChecked(true); break;
+ case 2: ui->advOutFFTrack2->setChecked(true); break;
+ case 3: ui->advOutFFTrack3->setChecked(true); break;
+ case 4: ui->advOutFFTrack4->setChecked(true); break;
+ }
+}
+
+void OBSBasicSettings::LoadAdvOutputAudioSettings()
+{
+ int track1Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track1Bitrate");
+ int track2Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track2Bitrate");
+ int track3Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track3Bitrate");
+ int track4Bitrate = config_get_uint(main->Config(), "AdvOut",
+ "Track4Bitrate");
+ const char *name1 = config_get_string(main->Config(), "AdvOut",
+ "Track1Name");
+ const char *name2 = config_get_string(main->Config(), "AdvOut",
+ "Track2Name");
+ const char *name3 = config_get_string(main->Config(), "AdvOut",
+ "Track3Name");
+ const char *name4 = config_get_string(main->Config(), "AdvOut",
+ "Track4Name");
+
+ SetComboByName(ui->advOutTrack1Bitrate,
+ std::to_string(track1Bitrate).c_str());
+ SetComboByName(ui->advOutTrack2Bitrate,
+ std::to_string(track2Bitrate).c_str());
+ SetComboByName(ui->advOutTrack3Bitrate,
+ std::to_string(track3Bitrate).c_str());
+ SetComboByName(ui->advOutTrack4Bitrate,
+ std::to_string(track4Bitrate).c_str());
+
+ ui->advOutTrack1Name->setText(name1);
+ ui->advOutTrack2Name->setText(name2);
+ ui->advOutTrack3Name->setText(name3);
+ ui->advOutTrack4Name->setText(name4);
+}
+
void OBSBasicSettings::LoadOutputSettings()
{
loading = true;
+ const char *mode = config_get_string(main->Config(), "Output", "Mode");
+
+ int modeIdx = astrcmpi(mode, "Advanced") == 0 ? 1 : 0;
+ ui->outputMode->setCurrentIndex(modeIdx);
+
LoadSimpleOutputSettings();
+ LoadAdvOutputStreamingSettings();
+ LoadAdvOutputStreamingEncoderProperties();
+ LoadAdvOutputRecordingSettings();
+ LoadAdvOutputRecordingEncoderProperties();
+ LoadAdvOutputFFmpegSettings();
+ LoadAdvOutputAudioSettings();
+
+ if (video_output_active(obs_get_video())) {
+ ui->outputMode->setEnabled(false);
+ ui->advOutTopContainer->setEnabled(false);
+ ui->advOutRecTopContainer->setEnabled(false);
+ ui->advOutRecTypeContainer->setEnabled(false);
+ ui->advOutputAudioTracksTab->setEnabled(false);
+ }
loading = false;
}
@@ -645,9 +949,60 @@ void OBSBasicSettings::SaveVideoSettings()
main->ResetVideo();
}
-/* TODO: Temporary! */
+static inline const char *OutputModeFromIdx(int idx)
+{
+ if (idx == 1)
+ return "Advanced";
+ else
+ return "Simple";
+}
+
+static inline const char *RecTypeFromIdx(int idx)
+{
+ if (idx == 1)
+ return "FFmpeg";
+ else
+ return "Standard";
+}
+
+static void WriteJsonData(OBSPropertiesView *view, const char *path)
+{
+ char full_path[512];
+
+ if (!view || !WidgetChanged(view))
+ return;
+
+ int ret = os_get_config_path(full_path, sizeof(full_path), path);
+ if (ret > 0) {
+ obs_data_t *settings = view->GetSettings();
+ if (settings) {
+ const char *json = obs_data_get_json(settings);
+ if (json && *json) {
+ os_quick_write_utf8_file(full_path, json,
+ strlen(json), false);
+ }
+ }
+ }
+}
+
+static void SaveTrackIndex(config_t *config, const char *section,
+ const char *name,
+ QAbstractButton *check1,
+ QAbstractButton *check2,
+ QAbstractButton *check3,
+ QAbstractButton *check4)
+{
+ if (check1->isChecked()) config_set_int(config, section, name, 1);
+ else if (check2->isChecked()) config_set_int(config, section, name, 2);
+ else if (check3->isChecked()) config_set_int(config, section, name, 3);
+ else if (check4->isChecked()) config_set_int(config, section, name, 4);
+}
+
void OBSBasicSettings::SaveOutputSettings()
{
+ config_set_string(main->Config(), "Output", "Mode",
+ OutputModeFromIdx(ui->outputMode->currentIndex()));
+
SaveSpinBox(ui->simpleOutputVBitrate, "SimpleOutput", "VBitrate");
SaveCombo(ui->simpleOutputABitrate, "SimpleOutput", "ABitrate");
SaveEdit(ui->simpleOutputPath, "SimpleOutput", "FilePath");
@@ -662,6 +1017,55 @@ void OBSBasicSettings::SaveOutputSettings()
if (ui->simpleOutUseBufsize->isChecked())
SaveSpinBox(ui->simpleOutVBufsize, "SimpleOutput", "VBufsize");
+
+ SaveCheckBox(ui->advOutReconnect, "AdvOut", "Reconnect");
+ SaveSpinBox(ui->advOutRetryDelay, "AdvOut", "RetryDelay");
+ SaveSpinBox(ui->advOutMaxRetries, "AdvOut", "MaxRetries");
+ SaveComboData(ui->advOutEncoder, "AdvOut", "Encoder");
+ SaveCheckBox(ui->advOutUseRescale, "AdvOut", "Rescale");
+ SaveCombo(ui->advOutRescale, "AdvOut", "RescaleRes");
+ SaveTrackIndex(main->Config(), "AdvOut", "TrackIndex",
+ ui->advOutTrack1, ui->advOutTrack2,
+ ui->advOutTrack3, ui->advOutTrack4);
+
+ config_set_string(main->Config(), "AdvOut", "RecType",
+ RecTypeFromIdx(ui->advOutRecType->currentIndex()));
+
+ SaveEdit(ui->advOutRecPath, "AdvOut", "RecFilePath");
+ SaveComboData(ui->advOutRecEncoder, "AdvOut", "RecEncoder");
+ SaveCheckBox(ui->advOutRecUseRescale, "AdvOut", "RecRescale");
+ SaveCombo(ui->advOutRecRescale, "AdvOut", "RecRescaleRes");
+ SaveTrackIndex(main->Config(), "AdvOut", "RecTrackIndex",
+ ui->advOutRecTrack1, ui->advOutRecTrack2,
+ ui->advOutRecTrack3, ui->advOutRecTrack4);
+
+ SaveEdit(ui->advOutFFURL, "AdvOut", "FFURL");
+ SaveSpinBox(ui->advOutFFVBitrate, "AdvOut", "FFVBitrate");
+ SaveCheckBox(ui->advOutFFUseRescale, "AdvOut", "FFRescale");
+ SaveCombo(ui->advOutFFRescale, "AdvOut", "FFRescaleRes");
+ SaveEdit(ui->advOutFFVEncoder, "AdvOut", "FFVEncoder");
+ SaveEdit(ui->advOutFFVCfg, "AdvOut", "FFVCustom");
+ SaveSpinBox(ui->advOutFFABitrate, "AdvOut", "FFABitrate");
+ SaveEdit(ui->advOutFFAEncoder, "AdvOut", "FFAEncoder");
+ SaveEdit(ui->advOutFFACfg, "AdvOut", "FFACustom");
+ SaveTrackIndex(main->Config(), "AdvOut", "FFAudioTrack",
+ ui->advOutFFTrack1, ui->advOutFFTrack2,
+ ui->advOutFFTrack3, ui->advOutFFTrack4);
+
+ SaveCombo(ui->advOutTrack1Bitrate, "AdvOut", "Track1Bitrate");
+ SaveCombo(ui->advOutTrack2Bitrate, "AdvOut", "Track2Bitrate");
+ SaveCombo(ui->advOutTrack3Bitrate, "AdvOut", "Track3Bitrate");
+ SaveCombo(ui->advOutTrack4Bitrate, "AdvOut", "Track4Bitrate");
+ SaveEdit(ui->advOutTrack1Name, "AdvOut", "Track1Name");
+ SaveEdit(ui->advOutTrack2Name, "AdvOut", "Track2Name");
+ SaveEdit(ui->advOutTrack3Name, "AdvOut", "Track3Name");
+ SaveEdit(ui->advOutTrack4Name, "AdvOut", "Track4Name");
+
+ WriteJsonData(streamEncoderProps,
+ "obs-studio/basic/streamEncoder.json");
+ WriteJsonData(recordEncoderProps,
+ "obs-studio/basic/recordEncoder.json");
+ main->ResetOutputs();
}
void OBSBasicSettings::SaveAudioSettings()
@@ -797,7 +1201,7 @@ void OBSBasicSettings::on_streamType_currentIndexChanged(int idx)
void OBSBasicSettings::on_simpleOutputBrowse_clicked()
{
QString dir = QFileDialog::getExistingDirectory(this,
- QTStr("OpenDirectory"),
+ QTStr("Basic.Settings.Output.SelectDirectory"),
ui->simpleOutputPath->text(),
QFileDialog::ShowDirsOnly |
QFileDialog::DontResolveSymlinks);
@@ -807,6 +1211,61 @@ void OBSBasicSettings::on_simpleOutputBrowse_clicked()
ui->simpleOutputPath->setText(dir);
}
+void OBSBasicSettings::on_advOutRecPathBrowse_clicked()
+{
+ QString dir = QFileDialog::getExistingDirectory(this,
+ QTStr("Basic.Settings.Output.SelectDirectory"),
+ ui->advOutRecPath->text(),
+ QFileDialog::ShowDirsOnly |
+ QFileDialog::DontResolveSymlinks);
+ if (dir.isEmpty())
+ return;
+
+ ui->advOutRecPath->setText(dir);
+}
+
+void OBSBasicSettings::on_advOutFFPathBrowse_clicked()
+{
+ QString file = QFileDialog::getSaveFileName(this,
+ QTStr("Basic.Settings.Output.SelectFile"),
+ ui->simpleOutputPath->text(),
+ QTStr("Basic.Settings.Output.Adv.FFmpeg.SaveFilter"));
+ if (file.isEmpty())
+ return;
+
+ ui->advOutFFURL->setText(file);
+}
+
+void OBSBasicSettings::on_advOutEncoder_currentIndexChanged(int idx)
+{
+ QString encoder = GetComboData(ui->advOutEncoder);
+
+ delete streamEncoderProps;
+ streamEncoderProps = CreateEncoderPropertyView(QT_TO_UTF8(encoder),
+ "obs-studio/basic/streamEncoder.json", true);
+ ui->advOutputStreamTab->layout()->addWidget(streamEncoderProps);
+
+ UNUSED_PARAMETER(idx);
+}
+
+void OBSBasicSettings::on_advOutRecEncoder_currentIndexChanged(int idx)
+{
+ ui->advOutRecUseRescale->setEnabled(idx > 0);
+ ui->advOutRecRescaleContainer->setEnabled(idx > 0);
+
+ delete recordEncoderProps;
+ recordEncoderProps = nullptr;
+
+ if (idx > 0) {
+ QString encoder = GetComboData(ui->advOutRecEncoder);
+
+ recordEncoderProps = CreateEncoderPropertyView(
+ QT_TO_UTF8(encoder),
+ "obs-studio/basic/recordEncoder.json", true);
+ ui->advOutRecStandard->layout()->addWidget(recordEncoderProps);
+ }
+}
+
static inline bool StreamExists(const char *name)
{
return obs_get_service_by_name(name) != nullptr;
diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp
index 1a778a7fe..177dd0e7b 100644
--- a/obs/window-basic-settings.hpp
+++ b/obs/window-basic-settings.hpp
@@ -45,13 +45,15 @@ private:
bool loading = true;
OBSPropertiesView *streamProperties = nullptr;
+ OBSPropertiesView *streamEncoderProps = nullptr;
+ OBSPropertiesView *recordEncoderProps = nullptr;
void SaveCombo(QComboBox *widget, const char *section,
const char *value);
void SaveComboData(QComboBox *widget, const char *section,
const char *value);
- void SaveCheckBox(QCheckBox *widget, const char *section,
- const char *value);
+ void SaveCheckBox(QAbstractButton *widget, const char *section,
+ const char *value, bool invert = false);
void SaveEdit(QLineEdit *widget, const char *section,
const char *value);
void SaveSpinBox(QSpinBox *widget, const char *section,
@@ -83,6 +85,7 @@ private:
void LoadServiceTypes();
void LoadServiceInfo();
+ void LoadEncoderTypes();
void LoadGeneralSettings();
void LoadOutputSettings();
@@ -90,11 +93,20 @@ private:
void LoadVideoSettings();
void LoadSettings(bool changedOnly);
+ OBSPropertiesView *CreateEncoderPropertyView(const char *encoder,
+ const char *path, bool changed = false);
+
/* general */
void LoadLanguageList();
/* output */
void LoadSimpleOutputSettings();
+ void LoadAdvOutputStreamingSettings();
+ void LoadAdvOutputStreamingEncoderProperties();
+ void LoadAdvOutputRecordingSettings();
+ void LoadAdvOutputRecordingEncoderProperties();
+ void LoadAdvOutputFFmpegSettings();
+ void LoadAdvOutputAudioSettings();
/* audio */
void LoadListValues(QComboBox *widget, obs_property_t *prop,
@@ -123,6 +135,10 @@ private slots:
void on_streamType_currentIndexChanged(int idx);
void on_simpleOutputBrowse_clicked();
+ void on_advOutRecPathBrowse_clicked();
+ void on_advOutFFPathBrowse_clicked();
+ void on_advOutEncoder_currentIndexChanged(int idx);
+ void on_advOutRecEncoder_currentIndexChanged(int idx);
void on_baseResolution_editTextChanged(const QString &text);