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;