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