diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index a5dd0da3a..0d91518d5 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -942,7 +942,8 @@ Basic.Settings.Output.Format.HLS="HLS (.m3u8 + .ts)" Basic.Settings.Output.Format.fMP4="Fragmented MP4 (.mp4)" Basic.Settings.Output.Format.fMOV="Fragmented MOV (.mov)" Basic.Settings.Output.Format.TT="Fragmented MP4/MOV writes the recording in chunks and does not require the same finalization as traditional MP4/MOV files.\nThis ensures the file remains playable even if writing to disk is interrupted, for example, as a result of a BSOD or power loss.\n\nThis may not be compatible with all players and editors. Use File → Remux Recordings to convert the file into a more compatible format if necessary." -Basic.Settings.Output.Encoder="Encoder" +Basic.Settings.Output.Encoder.Video="Video Encoder" +Basic.Settings.Output.Encoder.Audio="Audio Encoder" Basic.Settings.Output.SelectDirectory="Select Recording Directory" Basic.Settings.Output.SelectFile="Select Recording File" Basic.Settings.Output.DynamicBitrate="Dynamically change bitrate to manage congestion" @@ -993,6 +994,7 @@ Basic.Settings.Output.Warn.EnforceResolutionFPS.Resolution="Resolution: %1" Basic.Settings.Output.Warn.EnforceResolutionFPS.FPS="FPS: %1" Basic.Settings.Output.Warn.ServiceCodecCompatibility.Title="Incompatible Encoder" Basic.Settings.Output.Warn.ServiceCodecCompatibility.Msg="The streaming service \"%1\" does not support the encoder \"%2\". The encoder will be changed to \"%3\".\n\nDo you want to continue?" +Basic.Settings.Output.Warn.ServiceCodecCompatibility.Msg2="The streaming service \"%1\" does not support encoders \"%2\" and \"%3\". These encoders will be changed to \"%4\" and \"%5\".\n\nDo you want to continue?" Basic.Settings.Output.VideoBitrate="Video Bitrate" Basic.Settings.Output.AudioBitrate="Audio Bitrate" Basic.Settings.Output.Reconnect="Automatically Reconnect" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index bb894fb20..7eb799700 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -1731,7 +1731,7 @@ - Basic.Settings.Output.Encoder + Basic.Settings.Output.Encoder.Video simpleOutRecEncoder @@ -1780,6 +1780,19 @@ + + + + Basic.Settings.Output.Encoder.Audio + + + simpleOutStrAEncoder + + + + + + @@ -1902,7 +1915,7 @@ - Basic.Settings.Output.Encoder + Basic.Settings.Output.Encoder.Video simpleOutRecEncoder @@ -1913,6 +1926,19 @@ + + + Basic.Settings.Output.Encoder.Audio + + + simpleOutRecAEncoder + + + + + + + Basic.Settings.Output.CustomMuxerSettings @@ -1922,10 +1948,10 @@ - + - + Basic.Settings.Output.UseReplayBuffer @@ -2268,16 +2294,26 @@ - + - Basic.Settings.Output.Encoder + Basic.Settings.Output.Encoder.Audio - + + + + Basic.Settings.Output.Encoder.Video + + + + + + + @@ -2293,7 +2329,7 @@ - + false @@ -2856,15 +2892,15 @@ - - + + - Basic.Settings.Output.Encoder + Basic.Settings.Output.Encoder.Audio - + 0 @@ -2874,6 +2910,23 @@ + + + Basic.Settings.Output.Encoder.Video + + + + + + + + 0 + 0 + + + + + @@ -2889,7 +2942,7 @@ - + @@ -2923,14 +2976,14 @@ - + Basic.Settings.Output.CustomMuxerSettings - + @@ -2940,7 +2993,7 @@ - + @@ -2956,7 +3009,7 @@ - + false @@ -2978,14 +3031,14 @@ - + Basic.Settings.Output.SplitFile.Time - + min @@ -3001,14 +3054,14 @@ - + Basic.Settings.Output.SplitFile.Size - + MB diff --git a/UI/window-basic-main-outputs.cpp b/UI/window-basic-main-outputs.cpp index 9f8ae3297..bcd861831 100644 --- a/UI/window-basic-main-outputs.cpp +++ b/UI/window-basic-main-outputs.cpp @@ -186,20 +186,34 @@ static void OBSStopVirtualCam(void *data, calldata_t *params) /* ------------------------------------------------------------------------ */ -static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate, - const char *name, size_t idx) +static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, + const char *name, size_t idx) { const char *id_ = GetSimpleAACEncoderForBitrate(bitrate); if (!id_) { - id.clear(); res = nullptr; return false; } - if (id == id_) - return true; + res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); + + if (res) { + obs_encoder_release(res); + return true; + } + + return false; +} + +static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, + const char *name, size_t idx) +{ + const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate); + if (!id_) { + res = nullptr; + return false; + } - id = id_; res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr); if (res) { @@ -419,16 +433,12 @@ void BasicOutputHandler::DestroyVirtualCamView() /* ------------------------------------------------------------------------ */ struct SimpleOutput : BasicOutputHandler { - OBSEncoder aacStreaming; + OBSEncoder audioStreaming; OBSEncoder videoStreaming; - OBSEncoder aacRecording; - OBSEncoder aacArchive; + OBSEncoder audioRecording; + OBSEncoder audioArchive; OBSEncoder videoRecording; - string aacRecEncID; - string aacStreamEncID; - string aacArchiveEncID; - string videoEncoder; string videoQuality; bool usingRecordingPreset = false; @@ -561,6 +571,8 @@ void SimpleOutput::LoadRecordingPreset() config_get_string(main->Config(), "SimpleOutput", "RecQuality"); const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); + const char *audio_encoder = config_get_string( + main->Config(), "SimpleOutput", "RecAudioEncoder"); videoEncoder = encoder; videoQuality = quality; @@ -568,7 +580,7 @@ void SimpleOutput::LoadRecordingPreset() if (strcmp(quality, "Stream") == 0) { videoRecording = videoStreaming; - aacRecording = aacStreaming; + audioRecording = audioStreaming; usingRecordingPreset = false; return; @@ -586,28 +598,56 @@ void SimpleOutput::LoadRecordingPreset() LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder)); usingRecordingPreset = true; - if (!CreateAACEncoder(aacRecording, aacRecEncID, 192, - "simple_aac_recording", 0)) - throw "Failed to create aac recording encoder " + bool success = false; + + if (strcmp(audio_encoder, "opus") == 0) + success = CreateSimpleOpusEncoder( + audioRecording, 192, "simple_opus_recording", + 0); + else + success = CreateSimpleAACEncoder( + audioRecording, 192, "simple_aac_recording", 0); + + if (!success) + throw "Failed to create audio recording encoder " "(simple output)"; } } -#define SIMPLE_ARCHIVE_NAME "simple_archive_aac" +#define SIMPLE_ARCHIVE_NAME "simple_archive_audio" SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) { const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder"); + const char *audio_encoder = config_get_string( + main->Config(), "SimpleOutput", "StreamAudioEncoder"); LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder)); - if (!CreateAACEncoder(aacStreaming, aacStreamEncID, GetAudioBitrate(), - "simple_aac", 0)) - throw "Failed to create aac streaming encoder (simple output)"; - if (!CreateAACEncoder(aacArchive, aacArchiveEncID, GetAudioBitrate(), - SIMPLE_ARCHIVE_NAME, 1)) - throw "Failed to create aac arhive encoder (simple output)"; + bool success = false; + + if (strcmp(audio_encoder, "opus") == 0) + success = CreateSimpleOpusEncoder( + audioStreaming, GetAudioBitrate(), "simple_opus", 0); + else + success = CreateSimpleAACEncoder( + audioStreaming, GetAudioBitrate(), "simple_aac", 0); + + if (!success) + throw "Failed to create audio streaming encoder (simple output)"; + + if (strcmp(audio_encoder, "opus") == 0) + success = CreateSimpleOpusEncoder(audioArchive, + GetAudioBitrate(), + SIMPLE_ARCHIVE_NAME, 1); + else + success = CreateSimpleAACEncoder(audioArchive, + GetAudioBitrate(), + SIMPLE_ARCHIVE_NAME, 1); + + if (!success) + throw "Failed to create audio archive encoder (simple output)"; LoadRecordingPreset(); @@ -662,16 +702,21 @@ SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) int SimpleOutput::GetAudioBitrate() const { + const char *audio_encoder = config_get_string( + main->Config(), "SimpleOutput", "StreamAudioEncoder"); int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); + if (strcmp(audio_encoder, "opus") == 0) + return FindClosestAvailableSimpleOpusBitrate(bitrate); + return FindClosestAvailableSimpleAACBitrate(bitrate); } void SimpleOutput::Update() { OBSDataAutoRelease videoSettings = obs_data_create(); - OBSDataAutoRelease aacSettings = obs_data_create(); + OBSDataAutoRelease audioSettings = obs_data_create(); int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); @@ -732,15 +777,15 @@ void SimpleOutput::Update() if (advanced) obs_data_set_string(videoSettings, "x264opts", custom); - obs_data_set_string(aacSettings, "rate_control", "CBR"); - obs_data_set_int(aacSettings, "bitrate", audioBitrate); + obs_data_set_string(audioSettings, "rate_control", "CBR"); + obs_data_set_int(audioSettings, "bitrate", audioBitrate); obs_service_apply_encoder_settings(main->GetService(), videoSettings, - aacSettings); + audioSettings); if (!enforceBitrate) { obs_data_set_int(videoSettings, "bitrate", videoBitrate); - obs_data_set_int(aacSettings, "bitrate", audioBitrate); + obs_data_set_int(audioSettings, "bitrate", audioBitrate); } video_t *video = obs_get_video(); @@ -758,8 +803,8 @@ void SimpleOutput::Update() } obs_encoder_update(videoStreaming, videoSettings); - obs_encoder_update(aacStreaming, aacSettings); - obs_encoder_update(aacArchive, aacSettings); + obs_encoder_update(audioStreaming, audioSettings); + obs_encoder_update(audioArchive, audioSettings); } void SimpleOutput::UpdateRecordingAudioSettings() @@ -768,7 +813,7 @@ void SimpleOutput::UpdateRecordingAudioSettings() obs_data_set_int(settings, "bitrate", 192); obs_data_set_string(settings, "rate_control", "CBR"); - obs_encoder_update(aacRecording, settings); + obs_encoder_update(audioRecording, settings); } #define CROSS_DIST_CUTOFF 2000.0 @@ -940,8 +985,8 @@ inline void SimpleOutput::SetupOutputs() { SimpleOutput::Update(); obs_encoder_set_video(videoStreaming, obs_get_video()); - obs_encoder_set_audio(aacStreaming, obs_get_audio()); - obs_encoder_set_audio(aacArchive, obs_get_audio()); + obs_encoder_set_audio(audioStreaming, obs_get_audio()); + obs_encoder_set_audio(audioArchive, obs_get_audio()); if (usingRecordingPreset) { if (ffmpegOutput) { @@ -949,7 +994,7 @@ inline void SimpleOutput::SetupOutputs() obs_get_audio()); } else { obs_encoder_set_video(videoRecording, obs_get_video()); - obs_encoder_set_audio(aacRecording, obs_get_audio()); + obs_encoder_set_audio(audioRecording, obs_get_audio()); } } } @@ -1018,41 +1063,11 @@ bool SimpleOutput::SetupStreaming(obs_service_t *service) bool isEncoded = obs_output_get_flags(streamOutput) & OBS_OUTPUT_ENCODED; - if (isEncoded) { - const char *codec = - obs_output_get_supported_audio_codecs( - streamOutput); - if (!codec) { - blog(LOG_WARNING, "Failed to load audio codec"); - return false; - } - - if (strcmp(codec, "aac") != 0) { - const char *id = - FindAudioEncoderFromCodec(codec); - int audioBitrate = GetAudioBitrate(); - OBSDataAutoRelease settings = obs_data_create(); - obs_data_set_int(settings, "bitrate", - audioBitrate); - - aacStreaming = obs_audio_encoder_create( - id, "alt_audio_enc", nullptr, 0, - nullptr); - obs_encoder_release(aacStreaming); - if (!aacStreaming) - return false; - - obs_encoder_update(aacStreaming, settings); - obs_encoder_set_audio(aacStreaming, - obs_get_audio()); - } - } - outputType = type; } obs_output_set_video_encoder(streamOutput, videoStreaming); - obs_output_set_audio_encoder(streamOutput, aacStreaming, 0); + obs_output_set_audio_encoder(streamOutput, audioStreaming, 0); obs_output_set_service(streamOutput, service); return true; } @@ -1095,7 +1110,7 @@ void SimpleOutput::SetupVodTrack(obs_service_t *service) enable = advanced && enable && ServiceSupportsVodTrack(name); if (enable) - obs_output_set_audio_encoder(streamOutput, aacArchive, 1); + obs_output_set_audio_encoder(streamOutput, audioArchive, 1); else clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME); } @@ -1178,11 +1193,11 @@ void SimpleOutput::UpdateRecording() if (!ffmpegOutput) { obs_output_set_video_encoder(fileOutput, videoRecording); - obs_output_set_audio_encoder(fileOutput, aacRecording, 0); + obs_output_set_audio_encoder(fileOutput, audioRecording, 0); } if (replayBuffer) { obs_output_set_video_encoder(replayBuffer, videoRecording); - obs_output_set_audio_encoder(replayBuffer, aacRecording, 0); + obs_output_set_audio_encoder(replayBuffer, audioRecording, 0); } recordingConfigured = true; @@ -1348,17 +1363,16 @@ bool SimpleOutput::ReplayBufferActive() const struct AdvancedOutput : BasicOutputHandler { OBSEncoder streamAudioEnc; OBSEncoder streamArchiveEnc; - OBSEncoder aacTrack[MAX_AUDIO_MIXES]; + OBSEncoder recordTrack[MAX_AUDIO_MIXES]; OBSEncoder videoStreaming; OBSEncoder videoRecording; bool ffmpegOutput; bool ffmpegRecording; bool useStreamEncoder; + bool useStreamAudioEncoder; bool usesBitrate = false; - string aacEncoderID[MAX_AUDIO_MIXES]; - AdvancedOutput(OBSBasic *main_); inline void UpdateStreamSettings(); @@ -1372,7 +1386,7 @@ struct AdvancedOutput : BasicOutputHandler { inline void SetupRecording(); inline void SetupFFmpeg(); void SetupOutputs() override; - int GetAudioBitrate(size_t i) const; + int GetAudioBitrate(size_t i, const char *id) const; virtual bool SetupStreaming(obs_service_t *service) override; virtual bool StartStreaming(obs_service_t *service) override; @@ -1416,7 +1430,7 @@ static void ApplyEncoderDefaults(OBSData &settings, settings = std::move(dataRet); } -#define ADV_ARCHIVE_NAME "adv_archive_aac" +#define ADV_ARCHIVE_NAME "adv_archive_audio" #ifdef __APPLE__ static void translate_macvth264_encoder(const char *&encoder) @@ -1435,8 +1449,12 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) config_get_string(main->Config(), "AdvOut", "RecType"); const char *streamEncoder = config_get_string(main->Config(), "AdvOut", "Encoder"); + const char *streamAudioEncoder = + config_get_string(main->Config(), "AdvOut", "AudioEncoder"); const char *recordEncoder = config_get_string(main->Config(), "AdvOut", "RecEncoder"); + const char *recAudioEncoder = + config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); #ifdef __APPLE__ translate_macvth264_encoder(streamEncoder); translate_macvth264_encoder(recordEncoder); @@ -1447,6 +1465,7 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) ffmpegOutput && config_get_bool(main->Config(), "AdvOut", "FFOutputToFile"); useStreamEncoder = astrcmpi(recordEncoder, "none") == 0; + useStreamAudioEncoder = astrcmpi(recAudioEncoder, "none") == 0; OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json"); OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json"); @@ -1526,30 +1545,43 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) astrcmpi(rate_control, "ABR") == 0; for (int i = 0; i < MAX_AUDIO_MIXES; i++) { - char name[9]; - snprintf(name, sizeof(name), "adv_aac%d", i); + char name[19]; + snprintf(name, sizeof(name), "adv_record_audio_%d", i); - if (!CreateAACEncoder(aacTrack[i], aacEncoderID[i], - GetAudioBitrate(i), name, i)) + recordTrack[i] = obs_audio_encoder_create( + useStreamAudioEncoder ? streamAudioEncoder + : recAudioEncoder, + name, nullptr, i, nullptr); + + if (!recordTrack[i]) { throw "Failed to create audio encoder " "(advanced output)"; + } + + obs_encoder_release(recordTrack[i]); } std::string id; int streamTrack = config_get_int(main->Config(), "AdvOut", "TrackIndex") - 1; - if (!CreateAACEncoder(streamAudioEnc, id, GetAudioBitrate(streamTrack), - "adv_stream_aac", streamTrack)) + streamAudioEnc = obs_audio_encoder_create(streamAudioEncoder, + "adv_stream_audio", nullptr, + streamTrack, nullptr); + if (!streamAudioEnc) throw "Failed to create streaming audio encoder " "(advanced output)"; + obs_encoder_release(streamAudioEnc); id = ""; int vodTrack = config_get_int(main->Config(), "AdvOut", "VodTrackIndex") - 1; - if (!CreateAACEncoder(streamArchiveEnc, id, GetAudioBitrate(vodTrack), - ADV_ARCHIVE_NAME, vodTrack)) + streamArchiveEnc = obs_audio_encoder_create(streamAudioEncoder, + ADV_ARCHIVE_NAME, nullptr, + streamTrack, nullptr); + if (!streamArchiveEnc) throw "Failed to create archive audio encoder " "(advanced output)"; + obs_encoder_release(streamArchiveEnc); startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this); @@ -1717,21 +1749,22 @@ inline void AdvancedOutput::SetupRecording() if (!flv) { for (int i = 0; i < MAX_AUDIO_MIXES; i++) { if ((tracks & (1 << i)) != 0) { - obs_output_set_audio_encoder(fileOutput, - aacTrack[i], idx); + obs_output_set_audio_encoder( + fileOutput, recordTrack[i], idx); if (replayBuffer) obs_output_set_audio_encoder( - replayBuffer, aacTrack[i], idx); + replayBuffer, recordTrack[i], + idx); idx++; } } } else if (flv && tracks != 0) { - obs_output_set_audio_encoder(fileOutput, aacTrack[tracks - 1], - idx); + obs_output_set_audio_encoder(fileOutput, + recordTrack[tracks - 1], idx); if (replayBuffer) - obs_output_set_audio_encoder(replayBuffer, - aacTrack[tracks - 1], idx); + obs_output_set_audio_encoder( + replayBuffer, recordTrack[tracks - 1], idx); } // Use fragmented MOV/MP4 if user has not already specified custom movflags @@ -1834,13 +1867,12 @@ inline void AdvancedOutput::UpdateAudioSettings() config_get_int(main->Config(), "AdvOut", "TrackIndex"); int vodTrackIndex = config_get_int(main->Config(), "AdvOut", "VodTrackIndex"); + const char *audioEncoder = + config_get_string(main->Config(), "AdvOut", "AudioEncoder"); + const char *recAudioEncoder = + config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); OBSDataAutoRelease settings[MAX_AUDIO_MIXES]; - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { - settings[i] = obs_data_create(); - obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i)); - } - for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { string cfg_name = "Track"; cfg_name += to_string((int)i + 1); @@ -1850,13 +1882,19 @@ inline void AdvancedOutput::UpdateAudioSettings() string def_name = "Track"; def_name += to_string((int)i + 1); - SetEncoderName(aacTrack[i], name, def_name.c_str()); + SetEncoderName(recordTrack[i], name, def_name.c_str()); } for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) { int track = (int)(i + 1); + settings[i] = obs_data_create(); + obs_data_set_int(settings[i], "bitrate", + GetAudioBitrate(i, recAudioEncoder)); - obs_encoder_update(aacTrack[i], settings[i]); + obs_encoder_update(recordTrack[i], settings[i]); + + obs_data_set_int(settings[i], "bitrate", + GetAudioBitrate(i, audioEncoder)); if (track == streamTrackIndex || track == vodTrackIndex) { if (applyServiceSettings) { @@ -1885,7 +1923,7 @@ void AdvancedOutput::SetupOutputs() if (videoRecording) obs_encoder_set_video(videoRecording, obs_get_video()); for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) - obs_encoder_set_audio(aacTrack[i], obs_get_audio()); + obs_encoder_set_audio(recordTrack[i], obs_get_audio()); obs_encoder_set_audio(streamAudioEnc, obs_get_audio()); obs_encoder_set_audio(streamArchiveEnc, obs_get_audio()); @@ -1897,14 +1935,14 @@ void AdvancedOutput::SetupOutputs() SetupRecording(); } -int AdvancedOutput::GetAudioBitrate(size_t i) const +int AdvancedOutput::GetAudioBitrate(size_t i, const char *id) const { static const char *names[] = { "Track1Bitrate", "Track2Bitrate", "Track3Bitrate", "Track4Bitrate", "Track5Bitrate", "Track6Bitrate", }; int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]); - return FindClosestAvailableSimpleAACBitrate(bitrate); + return FindClosestAvailableAudioBitrate(id, bitrate); } inline void AdvancedOutput::SetupVodTrack(obs_service_t *service) @@ -1994,37 +2032,6 @@ bool AdvancedOutput::SetupStreaming(obs_service_t *service) bool isEncoded = obs_output_get_flags(streamOutput) & OBS_OUTPUT_ENCODED; - if (isEncoded) { - const char *codec = - obs_output_get_supported_audio_codecs( - streamOutput); - if (!codec) { - blog(LOG_WARNING, "Failed to load audio codec"); - return false; - } - - if (strcmp(codec, "aac") != 0) { - OBSDataAutoRelease settings = - obs_encoder_get_settings( - streamAudioEnc); - - const char *id = - FindAudioEncoderFromCodec(codec); - - streamAudioEnc = obs_audio_encoder_create( - id, "alt_audio_enc", nullptr, - streamTrack - 1, nullptr); - - if (!streamAudioEnc) - return false; - - obs_encoder_release(streamAudioEnc); - obs_encoder_update(streamAudioEnc, settings); - obs_encoder_set_audio(streamAudioEnc, - obs_get_audio()); - } - } - outputType = type; } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 1a3465ba4..b7eb69e59 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -1485,6 +1485,10 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512); config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix", "Replay"); + config_set_default_string(basicConfig, "SimpleOutput", + "StreamAudioEncoder", "aac"); + config_set_default_string(basicConfig, "SimpleOutput", + "RecAudioEncoder", "aac"); config_set_default_bool(basicConfig, "AdvOut", "ApplyServiceSettings", true); @@ -1501,6 +1505,8 @@ bool OBSBasic::InitBasicConfigDefaults() config_set_default_bool(basicConfig, "AdvOut", "RecUseRescale", false); config_set_default_uint(basicConfig, "AdvOut", "RecTracks", (1 << 0)); config_set_default_string(basicConfig, "AdvOut", "RecEncoder", "none"); + config_set_default_string(basicConfig, "AdvOut", "RecAudioEncoder", + "none"); config_set_default_uint(basicConfig, "AdvOut", "FLVTrack", 1); config_set_default_bool(basicConfig, "AdvOut", "FFOutputToFile", true); @@ -1629,6 +1635,15 @@ void OBSBasic::InitBasicConfigDefaults2() useNV ? SIMPLE_ENCODER_NVENC : SIMPLE_ENCODER_X264); + const char *aac_default = "ffmpeg_aac"; + if (EncoderAvailable("CoreAudio_AAC")) + aac_default = "CoreAudio_AAC"; + else if (EncoderAvailable("libfdk_aac")) + aac_default = "libfdk_aac"; + + config_set_default_string(basicConfig, "AdvOut", "AudioEncoder", + aac_default); + if (update_nvenc_presets(basicConfig)) config_save_safe(basicConfig, "tmp", nullptr); } diff --git a/UI/window-basic-settings-stream.cpp b/UI/window-basic-settings-stream.cpp index a82cf3b53..004d8ab50 100644 --- a/UI/window-basic-settings-stream.cpp +++ b/UI/window-basic-settings-stream.cpp @@ -1267,7 +1267,7 @@ static bool return_first_id(void *data, const char *id) return false; } -bool OBSBasicSettings::ServiceAndCodecCompatible() +bool OBSBasicSettings::ServiceAndVCodecCompatible() { bool simple = (ui->outputMode->currentIndex() == 0); bool ret; @@ -1309,6 +1309,44 @@ bool OBSBasicSettings::ServiceAndCodecCompatible() return ret; } +bool OBSBasicSettings::ServiceAndACodecCompatible() +{ + bool simple = (ui->outputMode->currentIndex() == 0); + bool ret; + + QString codec; + + if (simple) { + codec = ui->simpleOutStrAEncoder->currentData().toString(); + } else { + QString encoder = ui->advOutAEncoder->currentData().toString(); + codec = obs_get_encoder_codec(QT_TO_UTF8(encoder)); + } + + OBSService service = SpawnTempService(); + const char **codecs = obs_service_get_supported_audio_codecs(service); + + if (!codecs || IsCustomService()) { + const char *output; + char **output_codecs; + + obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol), + &output, return_first_id); + output_codecs = strlist_split( + obs_get_output_supported_audio_codecs(output), ';', + false); + + ret = service_supports_codec((const char **)output_codecs, + QT_TO_UTF8(codec)); + + strlist_free(output_codecs); + } else { + ret = service_supports_codec(codecs, QT_TO_UTF8(codec)); + } + + return ret; +} + /* we really need a way to find fallbacks in a less hardcoded way. maybe. */ static QString get_adv_fallback(const QString &enc) { @@ -1321,6 +1359,22 @@ static QString get_adv_fallback(const QString &enc) return "obs_x264"; } +static QString get_adv_audio_fallback(const QString &enc) +{ + const char *codec = obs_get_encoder_codec(QT_TO_UTF8(enc)); + + if (strcmp(codec, "aac") == 0) + return "ffmpeg_opus"; + + QString aac_default = "ffmpeg_aac"; + if (EncoderAvailable("CoreAudio_AAC")) + aac_default = "CoreAudio_AAC"; + else if (EncoderAvailable("libfdk_aac")) + aac_default = "libfdk_aac"; + + return aac_default; +} + static QString get_simple_fallback(const QString &enc) { if (enc == SIMPLE_ENCODER_NVENC_HEVC) @@ -1339,7 +1393,10 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() if (loading) return false; - if (ServiceAndCodecCompatible()) { + bool vcodec_compat = ServiceAndVCodecCompatible(); + bool acodec_compat = ServiceAndACodecCompatible(); + + if (vcodec_compat && acodec_compat) { if (lastServiceIdx != ui->service->currentIndex() || IsCustomService()) ResetEncoders(true); @@ -1347,8 +1404,10 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() } QString service = ui->service->currentText(); - QString cur_name; - QString fb_name; + QString cur_video_name; + QString fb_video_name; + QString cur_audio_name; + QString fb_audio_name; bool simple = (ui->outputMode->currentIndex() == 0); /* ------------------------------------------------- */ @@ -1362,20 +1421,45 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() int cur_idx = ui->simpleOutStrEncoder->findData(cur_enc); int fb_idx = ui->simpleOutStrEncoder->findData(fb_enc); - cur_name = ui->simpleOutStrEncoder->itemText(cur_idx); - fb_name = ui->simpleOutStrEncoder->itemText(fb_idx); + cur_video_name = ui->simpleOutStrEncoder->itemText(cur_idx); + fb_video_name = ui->simpleOutStrEncoder->itemText(fb_idx); + + cur_enc = ui->simpleOutStrAEncoder->currentData().toString(); + fb_enc = (cur_enc == "opus") ? "aac" : "opus"; + + cur_audio_name = ui->simpleOutStrAEncoder->itemText( + ui->simpleOutStrAEncoder->findData(cur_enc)); + fb_audio_name = ui->simpleOutStrAEncoder->itemText( + ui->simpleOutStrAEncoder->findData(fb_enc)); } else { QString cur_enc = ui->advOutEncoder->currentData().toString(); QString fb_enc = get_adv_fallback(cur_enc); - cur_name = obs_encoder_get_display_name(QT_TO_UTF8(cur_enc)); - fb_name = obs_encoder_get_display_name(QT_TO_UTF8(fb_enc)); + cur_video_name = + obs_encoder_get_display_name(QT_TO_UTF8(cur_enc)); + fb_video_name = + obs_encoder_get_display_name(QT_TO_UTF8(fb_enc)); + + cur_enc = ui->advOutAEncoder->currentData().toString(); + fb_enc = get_adv_audio_fallback(cur_enc); + + cur_audio_name = + obs_encoder_get_display_name(QT_TO_UTF8(cur_enc)); + fb_audio_name = + obs_encoder_get_display_name(QT_TO_UTF8(fb_enc)); } #define WARNING_VAL(x) \ QTStr("Basic.Settings.Output.Warn.ServiceCodecCompatibility." x) - QString msg = WARNING_VAL("Msg").arg(service, cur_name, fb_name); + QString msg = WARNING_VAL("Msg").arg( + service, vcodec_compat ? cur_audio_name : cur_video_name, + vcodec_compat ? fb_audio_name : fb_video_name); + if (!vcodec_compat && !acodec_compat) + msg = WARNING_VAL("Msg2").arg(service, cur_video_name, + cur_audio_name, fb_video_name, + fb_audio_name); + auto button = OBSMessageBox::question(this, WARNING_VAL("Title"), msg); #undef WARNING_VAL @@ -1403,37 +1487,60 @@ bool OBSBasicSettings::ServiceSupportsCodecCheck() void OBSBasicSettings::ResetEncoders(bool streamOnly) { - QString lastAdvEnc = ui->advOutEncoder->currentData().toString(); - QString lastEnc = ui->simpleOutStrEncoder->currentData().toString(); + QString lastAdvVideoEnc = ui->advOutEncoder->currentData().toString(); + QString lastVideoEnc = + ui->simpleOutStrEncoder->currentData().toString(); + QString lastAdvAudioEnc = ui->advOutAEncoder->currentData().toString(); + QString lastAudioEnc = + ui->simpleOutStrAEncoder->currentData().toString(); OBSService service = SpawnTempService(); - const char **codecs = obs_service_get_supported_video_codecs(service); + const char **vcodecs = obs_service_get_supported_video_codecs(service); + const char **acodecs = obs_service_get_supported_audio_codecs(service); const char *type; - BPtr output_codecs; + BPtr output_vcodecs; + BPtr output_acodecs; size_t idx = 0; - if (!codecs || IsCustomService()) { + if (!vcodecs || IsCustomService()) { const char *output; obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol), &output, return_first_id); - output_codecs = strlist_split( + output_vcodecs = strlist_split( obs_get_output_supported_video_codecs(output), ';', false); - codecs = (const char **)output_codecs.Get(); + vcodecs = (const char **)output_vcodecs.Get(); + } + + if (!acodecs || IsCustomService()) { + const char *output; + + obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol), + &output, return_first_id); + output_acodecs = strlist_split( + obs_get_output_supported_audio_codecs(output), ';', + false); + acodecs = (const char **)output_acodecs.Get(); } QSignalBlocker s1(ui->simpleOutStrEncoder); QSignalBlocker s2(ui->advOutEncoder); + QSignalBlocker s3(ui->simpleOutStrAEncoder); + QSignalBlocker s4(ui->advOutAEncoder); /* ------------------------------------------------- */ /* clear encoder lists */ ui->simpleOutStrEncoder->clear(); ui->advOutEncoder->clear(); + ui->simpleOutStrAEncoder->clear(); + ui->advOutAEncoder->clear(); if (!streamOnly) { ui->advOutRecEncoder->clear(); ui->advOutRecEncoder->addItem(TEXT_USE_STREAM_ENC, "none"); + ui->advOutRecAEncoder->clear(); + ui->advOutRecAEncoder->addItem(TEXT_USE_STREAM_ENC, "none"); } /* ------------------------------------------------- */ @@ -1444,19 +1551,25 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) const char *codec = obs_get_encoder_codec(type); uint32_t caps = obs_get_encoder_caps(type); - if (obs_get_encoder_type(type) != OBS_ENCODER_VIDEO) - continue; - - if ((caps & ENCODER_HIDE_FLAGS) != 0) - continue; - QString qName = QT_UTF8(name); QString qType = QT_UTF8(type); - if (service_supports_codec(codecs, codec)) - ui->advOutEncoder->addItem(qName, qType); - if (!streamOnly) - ui->advOutRecEncoder->addItem(qName, qType); + if (obs_get_encoder_type(type) == OBS_ENCODER_VIDEO) { + if ((caps & ENCODER_HIDE_FLAGS) != 0) + continue; + + if (service_supports_codec(vcodecs, codec)) + ui->advOutEncoder->addItem(qName, qType); + if (!streamOnly) + ui->advOutRecEncoder->addItem(qName, qType); + } + + if (obs_get_encoder_type(type) == OBS_ENCODER_AUDIO) { + if (service_supports_codec(acodecs, codec)) + ui->advOutAEncoder->addItem(qName, qType); + if (!streamOnly) + ui->advOutRecAEncoder->addItem(qName, qType); + } } /* ------------------------------------------------- */ @@ -1466,40 +1579,40 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) ui->simpleOutStrEncoder->addItem(ENCODER_STR("Software"), QString(SIMPLE_ENCODER_X264)); - if (service_supports_encoder(codecs, "obs_qsv11")) + if (service_supports_encoder(vcodecs, "obs_qsv11")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.QSV.H264"), QString(SIMPLE_ENCODER_QSV)); - if (service_supports_encoder(codecs, "obs_qsv11_av1")) + if (service_supports_encoder(vcodecs, "obs_qsv11_av1")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.QSV.AV1"), QString(SIMPLE_ENCODER_QSV_AV1)); - if (service_supports_encoder(codecs, "ffmpeg_nvenc")) + if (service_supports_encoder(vcodecs, "ffmpeg_nvenc")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.NVENC.H264"), QString(SIMPLE_ENCODER_NVENC)); - if (service_supports_encoder(codecs, "jim_av1_nvenc")) + if (service_supports_encoder(vcodecs, "jim_av1_nvenc")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.NVENC.AV1"), QString(SIMPLE_ENCODER_NVENC_AV1)); #ifdef ENABLE_HEVC - if (service_supports_encoder(codecs, "h265_texture_amf")) + if (service_supports_encoder(vcodecs, "h265_texture_amf")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.AMD.HEVC"), QString(SIMPLE_ENCODER_AMD_HEVC)); - if (service_supports_encoder(codecs, "ffmpeg_hevc_nvenc")) + if (service_supports_encoder(vcodecs, "ffmpeg_hevc_nvenc")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.NVENC.HEVC"), QString(SIMPLE_ENCODER_NVENC_HEVC)); #endif - if (service_supports_encoder(codecs, "h264_texture_amf")) + if (service_supports_encoder(vcodecs, "h264_texture_amf")) ui->simpleOutStrEncoder->addItem( ENCODER_STR("Hardware.AMD.H264"), QString(SIMPLE_ENCODER_AMD)); /* Preprocessor guard required for the macOS version check */ #ifdef __APPLE__ if (service_supports_encoder( - codecs, "com.apple.videotoolbox.videoencoder.ave.avc") + vcodecs, "com.apple.videotoolbox.videoencoder.ave.avc") #ifndef __aarch64__ && os_get_emulation_status() == true #endif @@ -1512,7 +1625,7 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) } #ifdef ENABLE_HEVC if (service_supports_encoder( - codecs, "com.apple.videotoolbox.videoencoder.ave.hevc") + vcodecs, "com.apple.videotoolbox.videoencoder.ave.hevc") #ifndef __aarch64__ && os_get_emulation_status() == true #endif @@ -1525,36 +1638,72 @@ void OBSBasicSettings::ResetEncoders(bool streamOnly) } #endif #endif + if (service_supports_encoder(acodecs, "CoreAudio_AAC") || + service_supports_encoder(acodecs, "libfdk_aac") || + service_supports_encoder(acodecs, "ffmpeg_aac")) + ui->simpleOutStrAEncoder->addItem("AAC", "aac"); + if (service_supports_encoder(acodecs, "ffmpeg_opus")) + ui->simpleOutStrAEncoder->addItem("Opus", "opus"); #undef ENCODER_STR /* ------------------------------------------------- */ /* Find fallback encoders */ - if (!lastAdvEnc.isEmpty()) { - int idx = ui->advOutEncoder->findData(lastAdvEnc); + if (!lastAdvVideoEnc.isEmpty()) { + int idx = ui->advOutEncoder->findData(lastAdvVideoEnc); if (idx == -1) { - lastAdvEnc = get_adv_fallback(lastAdvEnc); + lastAdvVideoEnc = get_adv_fallback(lastAdvVideoEnc); ui->advOutEncoder->setProperty("changed", QVariant(true)); OutputsChanged(); } - idx = ui->advOutEncoder->findData(lastAdvEnc); + idx = ui->advOutEncoder->findData(lastAdvVideoEnc); s2.unblock(); ui->advOutEncoder->setCurrentIndex(idx); } - if (!lastEnc.isEmpty()) { - int idx = ui->simpleOutStrEncoder->findData(lastEnc); + if (!lastAdvAudioEnc.isEmpty()) { + int idx = ui->advOutAEncoder->findData(lastAdvAudioEnc); if (idx == -1) { - lastEnc = get_simple_fallback(lastEnc); + lastAdvAudioEnc = + get_adv_audio_fallback(lastAdvAudioEnc); + ui->advOutAEncoder->setProperty("changed", + QVariant(true)); + OutputsChanged(); + } + + idx = ui->advOutAEncoder->findData(lastAdvAudioEnc); + s4.unblock(); + ui->advOutAEncoder->setCurrentIndex(idx); + } + + if (!lastVideoEnc.isEmpty()) { + int idx = ui->simpleOutStrEncoder->findData(lastVideoEnc); + if (idx == -1) { + lastVideoEnc = get_simple_fallback(lastVideoEnc); ui->simpleOutStrEncoder->setProperty("changed", QVariant(true)); OutputsChanged(); } - idx = ui->simpleOutStrEncoder->findData(lastEnc); + idx = ui->simpleOutStrEncoder->findData(lastVideoEnc); s1.unblock(); ui->simpleOutStrEncoder->setCurrentIndex(idx); } + + if (!lastAudioEnc.isEmpty()) { + int idx = ui->simpleOutStrAEncoder->findData(lastAudioEnc); + if (idx == -1) { + lastAudioEnc = (lastAudioEnc == "opus") ? "aac" + : "opus"; + ui->simpleOutStrAEncoder->setProperty("changed", + QVariant(true)); + OutputsChanged(); + } + + idx = ui->simpleOutStrAEncoder->findData(lastAudioEnc); + s3.unblock(); + ui->simpleOutStrAEncoder->setCurrentIndex(idx); + } } diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index a4c3a1dc7..84abe98ba 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -260,9 +260,40 @@ static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc, id); } -static void PopulateAACBitrates(initializer_list boxes) +#define INVALID_BITRATE 10000 +static int FindClosestAvailableAudioBitrate(QComboBox *box, int bitrate) { - auto &bitrateMap = GetSimpleAACEncoderBitrateMap(); + QList bitrates; + int prev = 0; + int next = INVALID_BITRATE; + + for (int i = 0; i < box->count(); i++) + bitrates << box->itemText(i).toInt(); + + for (int val : bitrates) { + if (next > val) { + if (val == bitrate) + return bitrate; + + if (val < next && val > bitrate) + next = val; + if (val > prev && val < bitrate) + prev = val; + } + } + + if (next != INVALID_BITRATE) + return next; + if (prev != 0) + return prev; + return 192; +} +#undef INVALID_BITRATE + +static void PopulateSimpleBitrates(QComboBox *box, bool opus) +{ + auto &bitrateMap = opus ? GetSimpleOpusEncoderBitrateMap() + : GetSimpleAACEncoderBitrateMap(); if (bitrateMap.empty()) return; @@ -272,17 +303,52 @@ static void PopulateAACBitrates(initializer_list boxes) QString::number(entry.first), obs_encoder_get_display_name(entry.second.c_str())); + QString currentBitrate = box->currentText(); + box->clear(); + + for (auto &pair : pairs) { + box->addItem(pair.first); + box->setItemData(box->count() - 1, pair.second, + Qt::ToolTipRole); + } + + if (box->findData(currentBitrate) == -1) { + int bitrate = FindClosestAvailableAudioBitrate( + box, currentBitrate.toInt()); + box->setCurrentText(QString::number(bitrate)); + } else + box->setCurrentText(currentBitrate); +} + +static void PopulateAdvancedBitrates(initializer_list boxes, + const char *stream_id, const char *rec_id) +{ + auto &streamBitrates = GetAudioEncoderBitrates(stream_id); + auto &recBitrates = GetAudioEncoderBitrates(rec_id); + if (streamBitrates.empty() || recBitrates.empty()) + return; + + QList streamBitratesList; + for (auto &bitrate : streamBitrates) + streamBitratesList << bitrate; + for (auto box : boxes) { - QString currentText = box->currentText(); + QString currentBitrate = box->currentText(); box->clear(); - for (auto &pair : pairs) { - box->addItem(pair.first); - box->setItemData(box->count() - 1, pair.second, - Qt::ToolTipRole); + for (auto &bitrate : recBitrates) { + if (streamBitratesList.indexOf(bitrate) == -1) + continue; + + box->addItem(QString::number(bitrate)); } - box->setCurrentText(currentText); + if (box->findData(currentBitrate) == -1) { + int bitrate = FindClosestAvailableAudioBitrate( + box, currentBitrate.toInt()); + box->setCurrentText(QString::number(bitrate)); + } else + box->setCurrentText(currentBitrate); } } @@ -362,11 +428,6 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) main->EnableOutputs(false); - PopulateAACBitrates({ui->simpleOutputABitrate, ui->advOutTrack1Bitrate, - ui->advOutTrack2Bitrate, ui->advOutTrack3Bitrate, - ui->advOutTrack4Bitrate, ui->advOutTrack5Bitrate, - ui->advOutTrack6Bitrate}); - ui->listWidget->setAttribute(Qt::WA_MacShowFocusRect, false); /* clang-format off */ @@ -424,17 +485,20 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->simpleOutRecFormat, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutputVBitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutStrEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->simpleOutStrAEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutputABitrate, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutAdvanced, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutPreset, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutCustom, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutRecQuality, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutRecEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->simpleOutRecAEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleOutMuxCustom, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleReplayBuf, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleRBSecMax, SCROLL_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->simpleRBMegsMax, SCROLL_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutAEncoder, 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); @@ -448,6 +512,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->advOutNoSpace, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecFormat, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->advOutRecAEncoder, COMBO_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecUseRescale, CHECK_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutRecRescale, CBEDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->advOutMuxCustom, EDIT_CHANGED, OUTPUTS_CHANGED); @@ -919,6 +984,13 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) connect(ui->useStreamKeyAdv, SIGNAL(clicked()), this, SLOT(UseStreamKeyAdvClicked())); + connect(ui->simpleOutStrAEncoder, SIGNAL(currentIndexChanged(int)), + this, SLOT(SimpleStreamAudioEncoderChanged)); + connect(ui->advOutAEncoder, SIGNAL(currentIndexChanged(int)), this, + SLOT(AdvAudioEncodersChanged())); + connect(ui->advOutRecAEncoder, SIGNAL(currentIndexChanged(int)), this, + SLOT(AdvAudioEncodersChanged())); + UpdateAudioWarnings(); UpdateAdvNetworkGroup(); } @@ -1821,6 +1893,8 @@ void OBSBasicSettings::LoadSimpleOutputSettings() config_get_uint(main->Config(), "SimpleOutput", "VBitrate"); const char *streamEnc = config_get_string( main->Config(), "SimpleOutput", "StreamEncoder"); + const char *streamAudioEnc = config_get_string( + main->Config(), "SimpleOutput", "StreamAudioEncoder"); int audioBitrate = config_get_uint(main->Config(), "SimpleOutput", "ABitrate"); bool advanced = @@ -1841,6 +1915,8 @@ void OBSBasicSettings::LoadSimpleOutputSettings() config_get_string(main->Config(), "SimpleOutput", "RecQuality"); const char *recEnc = config_get_string(main->Config(), "SimpleOutput", "RecEncoder"); + const char *recAudioEnc = config_get_string( + main->Config(), "SimpleOutput", "RecAudioEncoder"); const char *muxCustom = config_get_string( main->Config(), "SimpleOutput", "MuxerCustom"); bool replayBuf = @@ -1856,7 +1932,10 @@ void OBSBasicSettings::LoadSimpleOutputSettings() curAMDPreset = amdPreset; curAMDAV1Preset = amdAV1Preset; - audioBitrate = FindClosestAvailableSimpleAACBitrate(audioBitrate); + bool isOpus = strcmp(streamAudioEnc, "opus") == 0; + audioBitrate = + isOpus ? FindClosestAvailableSimpleOpusBitrate(audioBitrate) + : FindClosestAvailableSimpleAACBitrate(audioBitrate); ui->simpleOutputPath->setText(path); ui->simpleNoSpace->setChecked(noSpace); @@ -1865,6 +1944,8 @@ void OBSBasicSettings::LoadSimpleOutputSettings() int idx = ui->simpleOutRecFormat->findData(format); ui->simpleOutRecFormat->setCurrentIndex(idx); + PopulateSimpleBitrates(ui->simpleOutputABitrate, isOpus); + const char *speakers = config_get_string(main->Config(), "Audio", "ChannelSetup"); @@ -1888,11 +1969,21 @@ void OBSBasicSettings::LoadSimpleOutputSettings() idx = 0; ui->simpleOutStrEncoder->setCurrentIndex(idx); + idx = ui->simpleOutStrAEncoder->findData(QString(streamAudioEnc)); + if (idx == -1) + idx = 0; + ui->simpleOutStrAEncoder->setCurrentIndex(idx); + idx = ui->simpleOutRecEncoder->findData(QString(recEnc)); if (idx == -1) idx = 0; ui->simpleOutRecEncoder->setCurrentIndex(idx); + idx = ui->simpleOutRecAEncoder->findData(QString(recAudioEnc)); + if (idx == -1) + idx = 0; + ui->simpleOutRecAEncoder->setCurrentIndex(idx); + ui->simpleOutMuxCustom->setText(muxCustom); ui->simpleReplayBuf->setChecked(replayBuf); @@ -2237,12 +2328,31 @@ void OBSBasicSettings::LoadAdvOutputAudioSettings() const char *name6 = config_get_string(main->Config(), "AdvOut", "Track6Name"); - track1Bitrate = FindClosestAvailableSimpleAACBitrate(track1Bitrate); - track2Bitrate = FindClosestAvailableSimpleAACBitrate(track2Bitrate); - track3Bitrate = FindClosestAvailableSimpleAACBitrate(track3Bitrate); - track4Bitrate = FindClosestAvailableSimpleAACBitrate(track4Bitrate); - track5Bitrate = FindClosestAvailableSimpleAACBitrate(track5Bitrate); - track6Bitrate = FindClosestAvailableSimpleAACBitrate(track6Bitrate); + const char *encoder_id = + config_get_string(main->Config(), "AdvOut", "AudioEncoder"); + const char *rec_encoder_id = + config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); + + PopulateAdvancedBitrates( + {ui->advOutTrack1Bitrate, ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, ui->advOutTrack6Bitrate}, + encoder_id, + strcmp(rec_encoder_id, "none") != 0 ? rec_encoder_id + : encoder_id); + + track1Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack1Bitrate, track1Bitrate); + track2Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack2Bitrate, track2Bitrate); + track3Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack3Bitrate, track3Bitrate); + track4Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack4Bitrate, track4Bitrate); + track5Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack5Bitrate, track5Bitrate); + track6Bitrate = FindClosestAvailableAudioBitrate( + ui->advOutTrack6Bitrate, track6Bitrate); // restrict list of bitrates when multichannel is OFF const char *speakers = @@ -2292,8 +2402,15 @@ void OBSBasicSettings::LoadOutputSettings() LoadSimpleOutputSettings(); LoadAdvOutputStreamingSettings(); LoadAdvOutputStreamingEncoderProperties(); + + const char *type = + config_get_string(main->Config(), "AdvOut", "AudioEncoder"); + SetComboByValue(ui->advOutAEncoder, type); + LoadAdvOutputRecordingSettings(); LoadAdvOutputRecordingEncoderProperties(); + type = config_get_string(main->Config(), "AdvOut", "RecAudioEncoder"); + SetComboByValue(ui->advOutRecAEncoder, type); LoadAdvOutputFFmpegSettings(); LoadAdvOutputAudioSettings(); @@ -2302,6 +2419,8 @@ void OBSBasicSettings::LoadOutputSettings() ui->outputModeLabel->setEnabled(false); ui->simpleOutStrEncoderLabel->setEnabled(false); ui->simpleOutStrEncoder->setEnabled(false); + ui->simpleOutStrAEncoderLabel->setEnabled(false); + ui->simpleOutStrAEncoder->setEnabled(false); ui->simpleRecordingGroupBox->setEnabled(false); ui->replayBufferGroupBox->setEnabled(false); ui->advOutTopContainer->setEnabled(false); @@ -3634,6 +3753,8 @@ void OBSBasicSettings::SaveOutputSettings() SaveSpinBox(ui->simpleOutputVBitrate, "SimpleOutput", "VBitrate"); SaveComboData(ui->simpleOutStrEncoder, "SimpleOutput", "StreamEncoder"); + SaveComboData(ui->simpleOutStrAEncoder, "SimpleOutput", + "StreamAudioEncoder"); SaveCombo(ui->simpleOutputABitrate, "SimpleOutput", "ABitrate"); SaveEdit(ui->simpleOutputPath, "SimpleOutput", "FilePath"); SaveCheckBox(ui->simpleNoSpace, "SimpleOutput", "FileNameWithoutSpace"); @@ -3643,6 +3764,8 @@ void OBSBasicSettings::SaveOutputSettings() SaveEdit(ui->simpleOutCustom, "SimpleOutput", "x264Settings"); SaveComboData(ui->simpleOutRecQuality, "SimpleOutput", "RecQuality"); SaveComboData(ui->simpleOutRecEncoder, "SimpleOutput", "RecEncoder"); + SaveComboData(ui->simpleOutRecAEncoder, "SimpleOutput", + "RecAudioEncoder"); SaveEdit(ui->simpleOutMuxCustom, "SimpleOutput", "MuxerCustom"); SaveCheckBox(ui->simpleReplayBuf, "SimpleOutput", "RecRB"); SaveSpinBox(ui->simpleRBSecMax, "SimpleOutput", "RecRBTime"); @@ -3651,6 +3774,7 @@ void OBSBasicSettings::SaveOutputSettings() curAdvStreamEncoder = GetComboData(ui->advOutEncoder); SaveComboData(ui->advOutEncoder, "AdvOut", "Encoder"); + SaveComboData(ui->advOutAEncoder, "AdvOut", "AudioEncoder"); SaveCheckBox(ui->advOutUseRescale, "AdvOut", "Rescale"); SaveCombo(ui->advOutRescale, "AdvOut", "RescaleRes"); SaveTrackIndex(main->Config(), "AdvOut", "TrackIndex", ui->advOutTrack1, @@ -3666,6 +3790,7 @@ void OBSBasicSettings::SaveOutputSettings() SaveCheckBox(ui->advOutNoSpace, "AdvOut", "RecFileNameWithoutSpace"); SaveComboData(ui->advOutRecFormat, "AdvOut", "RecFormat"); SaveComboData(ui->advOutRecEncoder, "AdvOut", "RecEncoder"); + SaveComboData(ui->advOutRecAEncoder, "AdvOut", "RecAudioEncoder"); SaveCheckBox(ui->advOutRecUseRescale, "AdvOut", "RecRescale"); SaveCombo(ui->advOutRecRescale, "AdvOut", "RecRescaleRes"); SaveEdit(ui->advOutMuxCustom, "AdvOut", "RecMuxerCustom"); @@ -4404,16 +4529,26 @@ void OBSBasicSettings::SpeakerLayoutChanged(int idx) QString speakerLayoutQstr = ui->channelSetup->itemText(idx); std::string speakerLayout = QT_TO_UTF8(speakerLayoutQstr); bool surround = IsSurround(speakerLayout.c_str()); + bool isOpus = ui->simpleOutStrAEncoder->currentData().toString() == + "opus"; if (surround) { /* * Display all bitrates */ - PopulateAACBitrates( - {ui->simpleOutputABitrate, ui->advOutTrack1Bitrate, - ui->advOutTrack2Bitrate, ui->advOutTrack3Bitrate, - ui->advOutTrack4Bitrate, ui->advOutTrack5Bitrate, - ui->advOutTrack6Bitrate}); + PopulateSimpleBitrates(ui->simpleOutputABitrate, isOpus); + + const char *encoder_id = QT_TO_UTF8( + ui->advOutAEncoder->currentData().toString()); + QString rec_encoder_id = + ui->advOutRecAEncoder->currentData().toString(); + PopulateAdvancedBitrates( + {ui->advOutTrack1Bitrate, ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, ui->advOutTrack6Bitrate}, + encoder_id, + rec_encoder_id == "none" ? encoder_id + : QT_TO_UTF8(rec_encoder_id)); } else { /* * Reset audio bitrate for simple and adv mode, update list of @@ -4466,7 +4601,7 @@ void RestrictResetBitrates(initializer_list boxes, int maxbitrate) for (auto box : boxes) { int idx = box->currentIndex(); int max_bitrate = - FindClosestAvailableSimpleAACBitrate(maxbitrate); + FindClosestAvailableAudioBitrate(box, maxbitrate); int count = box->count(); int max_idx = box->findText( QT_UTF8(std::to_string(max_bitrate).c_str())); @@ -4475,9 +4610,8 @@ void RestrictResetBitrates(initializer_list boxes, int maxbitrate) box->removeItem(i); if (idx > max_idx) { - int default_bitrate = - FindClosestAvailableSimpleAACBitrate( - maxbitrate / 2); + int default_bitrate = FindClosestAvailableAudioBitrate( + box, maxbitrate / 2); int default_idx = box->findText(QT_UTF8( std::to_string(default_bitrate).c_str())); @@ -4947,6 +5081,12 @@ void OBSBasicSettings::FillSimpleRecordingValues() QString(SIMPLE_ENCODER_APPLE_HEVC)); #endif + if (EncoderAvailable("CoreAudio_AAC") || + EncoderAvailable("libfdk_aac") || EncoderAvailable("ffmpeg_aac")) + ui->simpleOutRecAEncoder->addItem("AAC", "aac"); + if (EncoderAvailable("ffmpeg_opus")) + ui->simpleOutRecAEncoder->addItem("Opus", "opus"); + #undef ADD_QUALITY #undef ENCODER_STR } @@ -4977,6 +5117,8 @@ void OBSBasicSettings::SimpleRecordingQualityChanged() bool showEncoder = !streamQuality && !losslessQuality; ui->simpleOutRecEncoder->setVisible(showEncoder); ui->simpleOutRecEncoderLabel->setVisible(showEncoder); + ui->simpleOutRecAEncoder->setVisible(showEncoder); + ui->simpleOutRecAEncoderLabel->setVisible(showEncoder); ui->simpleOutRecFormat->setVisible(!losslessQuality); ui->simpleOutRecFormatLabel->setVisible(!losslessQuality); @@ -5635,3 +5777,39 @@ void OBSBasicSettings::UpdateAdvNetworkGroup() ui->enableLowLatencyMode->setVisible(enabled); #endif } + +void OBSBasicSettings::SimpleStreamAudioEncoderChanged() +{ + PopulateSimpleBitrates( + ui->simpleOutputABitrate, + ui->simpleOutStrAEncoder->currentData().toString() == "opus"); + + if (IsSurround(QT_TO_UTF8(ui->channelSetup->currentText()))) + return; + + RestrictResetBitrates({ui->simpleOutputABitrate}, 320); +} + +void OBSBasicSettings::AdvAudioEncodersChanged() +{ + QString streamEncoder = ui->advOutAEncoder->currentData().toString(); + QString recEncoder = ui->advOutRecAEncoder->currentData().toString(); + + if (recEncoder == "none") + recEncoder = streamEncoder; + + PopulateAdvancedBitrates( + {ui->advOutTrack1Bitrate, ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, ui->advOutTrack6Bitrate}, + QT_TO_UTF8(streamEncoder), QT_TO_UTF8(recEncoder)); + + if (IsSurround(QT_TO_UTF8(ui->channelSetup->currentText()))) + return; + + RestrictResetBitrates({ui->advOutTrack1Bitrate, ui->advOutTrack2Bitrate, + ui->advOutTrack3Bitrate, ui->advOutTrack4Bitrate, + ui->advOutTrack5Bitrate, + ui->advOutTrack6Bitrate}, + 320); +} diff --git a/UI/window-basic-settings.hpp b/UI/window-basic-settings.hpp index e535f88a2..d3442db3c 100644 --- a/UI/window-basic-settings.hpp +++ b/UI/window-basic-settings.hpp @@ -377,7 +377,8 @@ private: OBSService GetStream1Service(); - bool ServiceAndCodecCompatible(); + bool ServiceAndVCodecCompatible(); + bool ServiceAndACodecCompatible(); bool ServiceSupportsCodecCheck(); private slots: @@ -468,6 +469,9 @@ private slots: void UseStreamKeyAdvClicked(); + void SimpleStreamAudioEncoderChanged(); + void AdvAudioEncodersChanged(); + protected: virtual void closeEvent(QCloseEvent *event) override; void reject() override;