فهرست منبع

UI: Add audio codec selections

tytan652 2 سال پیش
والد
کامیت
5fe417bce1

+ 3 - 1
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"

+ 74 - 21
UI/forms/OBSBasicSettings.ui

@@ -1731,7 +1731,7 @@
                        <item row="2" column="0">
                         <widget class="QLabel" name="simpleOutStrEncoderLabel">
                          <property name="text">
-                          <string>Basic.Settings.Output.Encoder</string>
+                          <string>Basic.Settings.Output.Encoder.Video</string>
                          </property>
                          <property name="buddy">
                           <cstring>simpleOutRecEncoder</cstring>
@@ -1780,6 +1780,19 @@
                        <item row="5" column="1">
                         <widget class="QLineEdit" name="simpleOutCustom"/>
                        </item>
+                       <item row="6" column="0">
+                        <widget class="QLabel" name="simpleOutStrAEncoderLabel">
+                         <property name="text">
+                          <string>Basic.Settings.Output.Encoder.Audio</string>
+                         </property>
+                         <property name="buddy">
+                          <cstring>simpleOutStrAEncoder</cstring>
+                         </property>
+                        </widget>
+                       </item>
+                       <item row="6" column="1">
+                        <widget class="QComboBox" name="simpleOutStrAEncoder"/>
+                       </item>
                       </layout>
                      </widget>
                     </item>
@@ -1902,7 +1915,7 @@
                        <item row="4" column="0">
                         <widget class="QLabel" name="simpleOutRecEncoderLabel">
                          <property name="text">
-                          <string>Basic.Settings.Output.Encoder</string>
+                          <string>Basic.Settings.Output.Encoder.Video</string>
                          </property>
                          <property name="buddy">
                           <cstring>simpleOutRecEncoder</cstring>
@@ -1913,6 +1926,19 @@
                         <widget class="QComboBox" name="simpleOutRecEncoder"/>
                        </item>
                        <item row="5" column="0">
+                        <widget class="QLabel" name="simpleOutRecAEncoderLabel">
+                         <property name="text">
+                          <string>Basic.Settings.Output.Encoder.Audio</string>
+                         </property>
+                         <property name="buddy">
+                          <cstring>simpleOutRecAEncoder</cstring>
+                         </property>
+                        </widget>
+                       </item>
+                       <item row="5" column="1">
+                        <widget class="QComboBox" name="simpleOutRecAEncoder"/>
+                       </item>
+                       <item row="6" column="0">
                         <widget class="QLabel" name="label_420">
                          <property name="text">
                           <string>Basic.Settings.Output.CustomMuxerSettings</string>
@@ -1922,10 +1948,10 @@
                          </property>
                         </widget>
                        </item>
-                       <item row="5" column="1">
+                       <item row="6" column="1">
                         <widget class="QLineEdit" name="simpleOutMuxCustom"/>
                        </item>
-                       <item row="6" column="1">
+                       <item row="7" column="1">
                         <widget class="QCheckBox" name="simpleReplayBuf">
                          <property name="text">
                           <string>Basic.Settings.Output.UseReplayBuffer</string>
@@ -2268,16 +2294,26 @@
                             </widget>
                            </item>
                            <item row="2" column="0">
-                            <widget class="QLabel" name="advOutEncLabel">
+                            <widget class="QLabel" name="advOutAEncLabel">
                              <property name="text">
-                              <string>Basic.Settings.Output.Encoder</string>
+                              <string>Basic.Settings.Output.Encoder.Audio</string>
                              </property>
                             </widget>
                            </item>
                            <item row="2" column="1">
-                            <widget class="QComboBox" name="advOutEncoder"/>
+                            <widget class="QComboBox" name="advOutAEncoder"/>
                            </item>
                            <item row="3" column="0">
+                            <widget class="QLabel" name="advOutEncLabel">
+                             <property name="text">
+                              <string>Basic.Settings.Output.Encoder.Video</string>
+                             </property>
+                            </widget>
+                           </item>
+                           <item row="3" column="1">
+                            <widget class="QComboBox" name="advOutEncoder"/>
+                           </item>
+                           <item row="4" column="0">
                             <widget class="QCheckBox" name="advOutUseRescale">
                              <property name="sizePolicy">
                               <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
@@ -2293,7 +2329,7 @@
                              </property>
                             </widget>
                            </item>
-                           <item row="3" column="1">
+                           <item row="4" column="1">
                             <widget class="QComboBox" name="advOutRescale">
                              <property name="enabled">
                               <bool>false</bool>
@@ -2856,15 +2892,15 @@
                                  </widget>
                                 </widget>
                                </item>
-                               <item row="4" column="0">
-                                <widget class="QLabel" name="advOutRecEncLabel">
+															 <item row="4" column="0">
+                                <widget class="QLabel" name="advOutRecAEncLabel">
                                  <property name="text">
-                                  <string>Basic.Settings.Output.Encoder</string>
+                                  <string>Basic.Settings.Output.Encoder.Audio</string>
                                  </property>
                                 </widget>
                                </item>
                                <item row="4" column="1">
-                                <widget class="QComboBox" name="advOutRecEncoder">
+                                <widget class="QComboBox" name="advOutRecAEncoder">
                                  <property name="sizePolicy">
                                   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                                    <horstretch>0</horstretch>
@@ -2874,6 +2910,23 @@
                                 </widget>
                                </item>
                                <item row="5" column="0">
+                                <widget class="QLabel" name="advOutRecEncLabel">
+                                 <property name="text">
+                                  <string>Basic.Settings.Output.Encoder.Video</string>
+                                 </property>
+                                </widget>
+                               </item>
+                               <item row="5" column="1">
+                                <widget class="QComboBox" name="advOutRecEncoder">
+                                 <property name="sizePolicy">
+                                  <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+                                   <horstretch>0</horstretch>
+                                   <verstretch>0</verstretch>
+                                  </sizepolicy>
+                                 </property>
+                                </widget>
+                               </item>
+                               <item row="6" column="0">
                                 <widget class="QCheckBox" name="advOutRecUseRescale">
                                  <property name="sizePolicy">
                                   <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@@ -2889,7 +2942,7 @@
                                  </property>
                                 </widget>
                                </item>
-                               <item row="5" column="1">
+                               <item row="6" column="1">
                                 <widget class="QFrame" name="advOutRecRescaleContainer">
                                  <layout class="QHBoxLayout" name="horizontalLayout_4">
                                   <property name="leftMargin">
@@ -2923,14 +2976,14 @@
                                  </layout>
                                 </widget>
                                </item>
-                               <item row="6" column="0">
+                               <item row="7" column="0">
                                 <widget class="QLabel" name="label_9001">
                                  <property name="text">
                                   <string>Basic.Settings.Output.CustomMuxerSettings</string>
                                  </property>
                                 </widget>
                                </item>
-                               <item row="6" column="1">
+                               <item row="7" column="1">
                                 <widget class="QLineEdit" name="advOutMuxCustom">
                                  <property name="sizePolicy">
                                   <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
@@ -2940,7 +2993,7 @@
                                  </property>
                                 </widget>
                                </item>
-                               <item row="7" column="0">
+                               <item row="8" column="0">
                                 <widget class="QCheckBox" name="advOutSplitFile">
                                  <property name="sizePolicy">
                                   <sizepolicy hsizetype="Minimum" vsizetype="Expanding">
@@ -2956,7 +3009,7 @@
                                  </property>
                                 </widget>
                                </item>
-                               <item row="7" column="1">
+                               <item row="8" column="1">
                                 <widget class="QComboBox" name="advOutSplitFileType">
                                  <property name="enabled">
                                   <bool>false</bool>
@@ -2978,14 +3031,14 @@
                                  </item>
                                 </widget>
                                </item>
-                               <item row="8" column="0">
+                               <item row="9" column="0">
                                 <widget class="QLabel" name="advOutSplitFileTimeLabel">
                                  <property name="text">
                                   <string>Basic.Settings.Output.SplitFile.Time</string>
                                  </property>
                                 </widget>
                                </item>
-                               <item row="8" column="1">
+                               <item row="9" column="1">
                                 <widget class="QSpinBox" name="advOutSplitFileTime">
                                  <property name="suffix">
                                   <string> min</string>
@@ -3001,14 +3054,14 @@
                                  </property>
                                 </widget>
                                </item>
-                               <item row="9" column="0">
+                               <item row="10" column="0">
                                 <widget class="QLabel" name="advOutSplitFileSizeLabel">
                                  <property name="text">
                                   <string>Basic.Settings.Output.SplitFile.Size</string>
                                  </property>
                                 </widget>
                                </item>
-                               <item row="9" column="1">
+                               <item row="10" column="1">
                                 <widget class="QSpinBox" name="advOutSplitFileSize">
                                  <property name="suffix">
                                   <string> MB</string>

+ 136 - 129
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_)
+	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;
 	}
 

+ 15 - 0
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);
 }

+ 192 - 43
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<char *> output_codecs;
+	BPtr<char *> output_vcodecs;
+	BPtr<char *> 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);
+	}
 }

+ 207 - 29
UI/window-basic-settings.cpp

@@ -260,9 +260,40 @@ static CodecDesc GetDefaultCodecDesc(const ff_format_desc *formatDesc,
 			 id);
 }
 
-static void PopulateAACBitrates(initializer_list<QComboBox *> boxes)
+#define INVALID_BITRATE 10000
+static int FindClosestAvailableAudioBitrate(QComboBox *box, int bitrate)
 {
-	auto &bitrateMap = GetSimpleAACEncoderBitrateMap();
+	QList<int> 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<QComboBox *> 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<QComboBox *> 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<int> 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<QComboBox *> 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<QComboBox *> 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);
+}

+ 5 - 1
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;