Merge pull request #8095 from tytan652/service_get_info

RFC 45-6: Enable more connection info in Services API
This commit is contained in:
Jim
2023-03-19 19:25:20 -07:00
committed by GitHub
38 changed files with 1851 additions and 491 deletions
+206 -26
View File
@@ -23,18 +23,17 @@ static const char *EncoderName(const std::string &id)
return NullToEmpty(obs_encoder_get_display_name(id.c_str()));
}
static map<int, std::string> bitrateMap;
static void HandleIntProperty(obs_property_t *prop, const char *id)
static void HandleIntProperty(obs_property_t *prop, std::vector<int> &bitrates)
{
const int max_ = obs_property_int_max(prop);
const int step = obs_property_int_step(prop);
for (int i = obs_property_int_min(prop); i <= max_; i += step)
bitrateMap[i] = id;
bitrates.push_back(i);
}
static void HandleListProperty(obs_property_t *prop, const char *id)
static void HandleListProperty(obs_property_t *prop, const char *id,
std::vector<int> &bitrates)
{
obs_combo_format format = obs_property_list_format(prop);
if (format != OBS_COMBO_FORMAT_INT) {
@@ -53,7 +52,7 @@ static void HandleListProperty(obs_property_t *prop, const char *id)
int bitrate =
static_cast<int>(obs_property_list_item_int(prop, i));
bitrateMap[bitrate] = id;
bitrates.push_back(bitrate);
}
}
@@ -86,7 +85,7 @@ static void HandleSampleRate(obs_property_t *prop, const char *id)
obs_property_modified(prop, data.get());
}
static void HandleEncoderProperties(const char *id)
static void HandleEncoderProperties(const char *id, std::vector<int> &bitrates)
{
auto DestroyProperties = [](obs_properties_t *props) {
obs_properties_destroy(props);
@@ -112,10 +111,10 @@ static void HandleEncoderProperties(const char *id)
obs_property_type type = obs_property_get_type(bitrate);
switch (type) {
case OBS_PROPERTY_INT:
return HandleIntProperty(bitrate, id);
return HandleIntProperty(bitrate, bitrates);
case OBS_PROPERTY_LIST:
return HandleListProperty(bitrate, id);
return HandleListProperty(bitrate, id, bitrates);
default:
break;
@@ -132,10 +131,75 @@ static const char *GetCodec(const char *id)
return NullToEmpty(obs_get_encoder_codec(id));
}
static void PopulateBitrateMap()
static std::vector<int> fallbackBitrates;
static map<std::string, std::vector<int>> encoderBitrates;
static void PopulateBitrateLists()
{
static once_flag once;
call_once(once, []() {
struct obs_audio_info aoi;
obs_get_audio_info(&aoi);
uint32_t output_channels = get_audio_channels(aoi.speakers);
/* NOTE: ffmpeg_aac and ffmpeg_opus have the same properties
* their bitrates will also be used as a fallback */
HandleEncoderProperties("ffmpeg_aac", fallbackBitrates);
if (fallbackBitrates.empty())
blog(LOG_ERROR, "Could not enumerate fallback encoder "
"bitrates");
ostringstream ss;
for (auto &bitrate : fallbackBitrates)
ss << "\n " << setw(3) << bitrate << " kbit/s:";
blog(LOG_DEBUG, "Fallback encoder bitrates:%s",
ss.str().c_str());
const char *id = nullptr;
for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) {
if (obs_get_encoder_type(id) != OBS_ENCODER_AUDIO)
continue;
if (strcmp(id, "ffmpeg_aac") == 0 ||
strcmp(id, "ffmpeg_opus") == 0)
continue;
std::string encoder = id;
HandleEncoderProperties(id, encoderBitrates[encoder]);
if (encoderBitrates[encoder].empty())
blog(LOG_ERROR,
"Could not enumerate %s encoder "
"bitrates",
id);
ostringstream ss;
for (auto &bitrate : encoderBitrates[encoder])
ss << "\n " << setw(3) << bitrate
<< " kbit/s";
blog(LOG_DEBUG, "%s (%s) encoder bitrates:%s",
EncoderName(id), id, ss.str().c_str());
}
if (encoderBitrates.empty() && fallbackBitrates.empty())
blog(LOG_ERROR, "Could not enumerate any audio encoder "
"bitrates");
});
}
static map<int, std::string> simpleAACBitrateMap;
static void PopulateSimpleAACBitrateMap()
{
PopulateBitrateLists();
static once_flag once;
call_once(once, []() {
const string encoders[] = {
"ffmpeg_aac",
@@ -148,7 +212,8 @@ static void PopulateBitrateMap()
struct obs_audio_info aoi;
obs_get_audio_info(&aoi);
HandleEncoderProperties(fallbackEncoder.c_str());
for (auto &bitrate : fallbackBitrates)
simpleAACBitrateMap[bitrate] = fallbackEncoder;
const char *id = nullptr;
for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) {
@@ -160,48 +225,115 @@ static void PopulateBitrateMap()
end(encoders))
continue;
if (strcmp(GetCodec(id), "AAC") != 0)
if (strcmp(GetCodec(id), "aac") != 0)
continue;
HandleEncoderProperties(id);
std::string encoder = id;
if (encoderBitrates[encoder].empty())
continue;
for (auto &bitrate : encoderBitrates[encoder])
simpleAACBitrateMap[bitrate] = encoder;
}
for (auto &encoder : encoders) {
if (encoder == fallbackEncoder)
continue;
if (strcmp(GetCodec(encoder.c_str()), "AAC") != 0)
if (strcmp(GetCodec(encoder.c_str()), "aac") != 0)
continue;
HandleEncoderProperties(encoder.c_str());
for (auto &bitrate : encoderBitrates[encoder])
simpleAACBitrateMap[bitrate] = encoder;
}
if (bitrateMap.empty()) {
if (simpleAACBitrateMap.empty()) {
blog(LOG_ERROR, "Could not enumerate any AAC encoder "
"bitrates");
return;
}
ostringstream ss;
for (auto &entry : bitrateMap)
for (auto &entry : simpleAACBitrateMap)
ss << "\n " << setw(3) << entry.first
<< " kbit/s: '" << EncoderName(entry.second) << "' ("
<< entry.second << ')';
blog(LOG_DEBUG, "AAC encoder bitrate mapping:%s",
blog(LOG_DEBUG, "AAC simple encoder bitrate mapping:%s",
ss.str().c_str());
});
}
const map<int, std::string> &GetAACEncoderBitrateMap()
static map<int, std::string> simpleOpusBitrateMap;
static void PopulateSimpleOpusBitrateMap()
{
PopulateBitrateMap();
return bitrateMap;
PopulateBitrateLists();
static once_flag once;
call_once(once, []() {
struct obs_audio_info aoi;
obs_get_audio_info(&aoi);
uint32_t output_channels = get_audio_channels(aoi.speakers);
for (auto &bitrate : fallbackBitrates)
simpleOpusBitrateMap[bitrate] = "ffmpeg_opus";
const char *id = nullptr;
for (size_t i = 0; obs_enum_encoder_types(i, &id); i++) {
if (strcmp(GetCodec(id), "opus") != 0)
continue;
std::string encoder = id;
if (encoderBitrates[encoder].empty())
continue;
for (auto &bitrate : encoderBitrates[encoder])
simpleOpusBitrateMap[bitrate] = encoder;
}
if (simpleOpusBitrateMap.empty()) {
blog(LOG_ERROR, "Could not enumerate any Opus encoder "
"bitrates");
return;
}
ostringstream ss;
for (auto &entry : simpleOpusBitrateMap)
ss << "\n " << setw(3) << entry.first
<< " kbit/s: '" << EncoderName(entry.second) << "' ("
<< entry.second << ')';
blog(LOG_DEBUG, "Opus simple encoder bitrate mapping:%s",
ss.str().c_str());
});
}
const char *GetAACEncoderForBitrate(int bitrate)
const map<int, std::string> &GetSimpleAACEncoderBitrateMap()
{
auto &map_ = GetAACEncoderBitrateMap();
PopulateSimpleAACBitrateMap();
return simpleAACBitrateMap;
}
const map<int, std::string> &GetSimpleOpusEncoderBitrateMap()
{
PopulateSimpleOpusBitrateMap();
return simpleOpusBitrateMap;
}
const char *GetSimpleAACEncoderForBitrate(int bitrate)
{
auto &map_ = GetSimpleAACEncoderBitrateMap();
auto res = map_.find(bitrate);
if (res == end(map_))
return NULL;
return res->second.c_str();
}
const char *GetSimpleOpusEncoderForBitrate(int bitrate)
{
auto &map_ = GetSimpleOpusEncoderBitrateMap();
auto res = map_.find(bitrate);
if (res == end(map_))
return NULL;
@@ -210,13 +342,13 @@ const char *GetAACEncoderForBitrate(int bitrate)
#define INVALID_BITRATE 10000
int FindClosestAvailableAACBitrate(int bitrate)
static int FindClosestAvailableSimpleBitrate(int bitrate,
const map<int, std::string> &map)
{
auto &map_ = GetAACEncoderBitrateMap();
int prev = 0;
int next = INVALID_BITRATE;
for (auto val : map_) {
for (auto val : map) {
if (next > val.first) {
if (val.first == bitrate)
return bitrate;
@@ -234,3 +366,51 @@ int FindClosestAvailableAACBitrate(int bitrate)
return prev;
return 192;
}
int FindClosestAvailableSimpleAACBitrate(int bitrate)
{
return FindClosestAvailableSimpleBitrate(
bitrate, GetSimpleAACEncoderBitrateMap());
}
int FindClosestAvailableSimpleOpusBitrate(int bitrate)
{
return FindClosestAvailableSimpleBitrate(
bitrate, GetSimpleOpusEncoderBitrateMap());
}
const std::vector<int> &GetAudioEncoderBitrates(const char *id)
{
std::string encoder = id;
PopulateBitrateLists();
if (encoderBitrates[encoder].empty())
return fallbackBitrates;
return encoderBitrates[encoder];
}
int FindClosestAvailableAudioBitrate(const char *id, int bitrate)
{
int prev = 0;
int next = INVALID_BITRATE;
std::string encoder = id;
for (auto val : encoderBitrates[encoder].empty()
? fallbackBitrates
: encoderBitrates[encoder]) {
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;
}
+11 -3
View File
@@ -3,7 +3,15 @@
#include <obs.hpp>
#include <map>
#include <vector>
const std::map<int, std::string> &GetAACEncoderBitrateMap();
const char *GetAACEncoderForBitrate(int bitrate);
int FindClosestAvailableAACBitrate(int bitrate);
const std::map<int, std::string> &GetSimpleAACEncoderBitrateMap();
const char *GetSimpleAACEncoderForBitrate(int bitrate);
int FindClosestAvailableSimpleAACBitrate(int bitrate);
const std::map<int, std::string> &GetSimpleOpusEncoderBitrateMap();
const char *GetSimpleOpusEncoderForBitrate(int bitrate);
int FindClosestAvailableSimpleOpusBitrate(int bitrate);
const std::vector<int> &GetAudioEncoderBitrates(const char *id);
int FindClosestAvailableAudioBitrate(const char *id, int bitrate);
+4 -1
View File
@@ -218,6 +218,7 @@ Basic.AutoConfig.TestPage="Final Results"
Basic.AutoConfig.TestPage.SubTitle.Testing="The program is now executing a set of tests to estimate the ideal settings"
Basic.AutoConfig.TestPage.SubTitle.Complete="Testing complete"
Basic.AutoConfig.TestPage.TestingBandwidth="Performing bandwidth test, this may take a few minutes..."
Basic.AutoConfig.TestPage.TestingBandwidth.NoOutput="No output for the protocol of this service was found"
Basic.AutoConfig.TestPage.TestingBandwidth.Connecting="Connecting to: %1..."
Basic.AutoConfig.TestPage.TestingBandwidth.ConnectFailed="Failed to connect to any servers, please check your internet connection and try again."
Basic.AutoConfig.TestPage.TestingBandwidth.Server="Testing bandwidth for: %1"
@@ -941,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"
@@ -992,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
View File
@@ -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>
+14 -16
View File
@@ -59,26 +59,24 @@ bool UIValidation::NoSourcesConfirmation(QWidget *parent)
StreamSettingsAction
UIValidation::StreamSettingsConfirmation(QWidget *parent, OBSService service)
{
// Custom services can user API key in URL or user/pass combo.
// So only check there is a URL
char const *serviceType = obs_service_get_type(service);
bool isCustomUrlService = (strcmp(serviceType, "rtmp_custom") == 0);
char const *streamUrl = obs_service_get_url(service);
char const *streamKey = obs_service_get_key(service);
bool hasStreamUrl = (streamUrl != NULL && streamUrl[0] != '\0');
bool hasStreamKey = ((streamKey != NULL && streamKey[0] != '\0') ||
isCustomUrlService);
if (hasStreamUrl && hasStreamKey)
if (obs_service_can_try_to_connect(service))
return StreamSettingsAction::ContinueStream;
QString msg;
char const *serviceType = obs_service_get_type(service);
bool isCustomService = (strcmp(serviceType, "rtmp_custom") == 0);
if (!hasStreamUrl && !hasStreamKey) {
char const *streamUrl = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL);
char const *streamKey = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_STREAM_KEY);
bool streamUrlMissing = !(streamUrl != NULL && streamUrl[0] != '\0');
bool streamKeyMissing = !(streamKey != NULL && streamKey[0] != '\0');
QString msg;
if (!isCustomService && streamUrlMissing && streamKeyMissing) {
msg = QTStr("Basic.Settings.Stream.MissingUrlAndApiKey");
} else if (!hasStreamKey) {
} else if (!isCustomService && streamKeyMissing) {
msg = QTStr("Basic.Settings.Stream.MissingStreamKey");
} else {
msg = QTStr("Basic.Settings.Stream.MissingUrl");
+49 -3
View File
@@ -88,6 +88,7 @@ public:
#define SUBTITLE_TESTING TEST_STR("Subtitle.Testing")
#define SUBTITLE_COMPLETE TEST_STR("Subtitle.Complete")
#define TEST_BW TEST_STR("TestingBandwidth")
#define TEST_BW_NO_OUTPUT TEST_STR("TestingBandwidth.NoOutput")
#define TEST_BW_CONNECTING TEST_STR("TestingBandwidth.Connecting")
#define TEST_BW_CONNECT_FAIL TEST_STR("TestingBandwidth.ConnectFailed")
#define TEST_BW_SERVER TEST_STR("TestingBandwidth.Server")
@@ -155,6 +156,23 @@ static inline void string_depad_key(string &key)
const char *FindAudioEncoderFromCodec(const char *type);
static inline bool can_use_output(const char *prot, const char *output,
const char *prot_test1,
const char *prot_test2 = nullptr)
{
return (strcmp(prot, prot_test1) == 0 ||
(prot_test2 && strcmp(prot, prot_test2) == 0)) &&
(obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0;
}
static bool return_first_id(void *data, const char *id)
{
const char **output = (const char **)data;
*output = id;
return false;
}
void AutoConfigTestPage::TestBandwidthThread()
{
bool connected = false;
@@ -268,9 +286,37 @@ void AutoConfigTestPage::TestBandwidthThread()
/* -----------------------------------*/
/* create output */
const char *output_type = obs_service_get_output_type(service);
if (!output_type)
output_type = "rtmp_output";
/* Check if the service has a preferred output type */
const char *output_type =
obs_service_get_preferred_output_type(service);
if (!output_type ||
(obs_get_output_flags(output_type) & OBS_OUTPUT_SERVICE) == 0) {
/* Otherwise, prefer first-party output types */
const char *protocol = obs_service_get_protocol(service);
if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) {
output_type = "rtmp_output";
} else if (can_use_output(protocol, "ffmpeg_hls_muxer",
"HLS")) {
output_type = "ffmpeg_hls_muxer";
} else if (can_use_output(protocol, "ffmpeg_mpegts_muxer",
"SRT", "RIST")) {
output_type = "ffmpeg_mpegts_muxer";
}
/* If third-party protocol, use the first enumerated type */
if (!output_type)
obs_enum_output_types_with_protocol(
protocol, &output_type, return_first_id);
/* If none, fail */
if (!output_type) {
QMetaObject::invokeMethod(
this, "Failure",
Q_ARG(QString, QTStr(TEST_BW_NO_OUTPUT)));
return;
}
}
OBSOutputAutoRelease output =
obs_output_create(output_type, "test_stream", nullptr, nullptr);
+213 -157
View File
@@ -186,20 +186,15 @@ 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_ = GetAACEncoderForBitrate(bitrate);
const char *id_ = GetSimpleAACEncoderForBitrate(bitrate);
if (!id_) {
id.clear();
res = nullptr;
return false;
}
if (id == id_)
return true;
id = id_;
res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);
if (res) {
@@ -210,6 +205,92 @@ static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate,
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;
}
res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);
if (res) {
obs_encoder_release(res);
return true;
}
return false;
}
static inline bool can_use_output(const char *prot, const char *output,
const char *prot_test1,
const char *prot_test2 = nullptr)
{
return (strcmp(prot, prot_test1) == 0 ||
(prot_test2 && strcmp(prot, prot_test2) == 0)) &&
(obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0;
}
static bool return_first_id(void *data, const char *id)
{
const char **output = (const char **)data;
*output = id;
return false;
}
static const char *GetStreamOutputType(const obs_service_t *service)
{
const char *protocol = obs_service_get_protocol(service);
const char *output = nullptr;
if (!protocol) {
blog(LOG_WARNING, "The service '%s' has no protocol set",
obs_service_get_id(service));
return nullptr;
}
if (!obs_is_output_protocol_registered(protocol)) {
blog(LOG_WARNING, "The protocol '%s' is not registered",
protocol);
return nullptr;
}
/* Check if the service has a preferred output type */
output = obs_service_get_preferred_output_type(service);
if (output) {
if ((obs_get_output_flags(output) & OBS_OUTPUT_SERVICE) != 0)
return output;
blog(LOG_WARNING,
"The output '%s' is not registered, fallback to another one",
output);
}
/* Otherwise, prefer first-party output types */
if (can_use_output(protocol, "rtmp_output", "RTMP", "RTMPS")) {
return "rtmp_output";
} else if (can_use_output(protocol, "ffmpeg_hls_muxer", "HLS")) {
return "ffmpeg_hls_muxer";
} else if (can_use_output(protocol, "ffmpeg_mpegts_muxer", "SRT",
"RIST")) {
return "ffmpeg_mpegts_muxer";
}
/* If third-party protocol, use the first enumerated type */
obs_enum_output_types_with_protocol(protocol, &output, return_first_id);
if (output)
return output;
blog(LOG_WARNING,
"No output compatible with the service '%s' is registered",
obs_service_get_id(service));
return nullptr;
}
/* ------------------------------------------------------------------------ */
inline BasicOutputHandler::BasicOutputHandler(OBSBasic *main_) : main(main_)
@@ -352,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;
@@ -494,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;
@@ -501,7 +580,7 @@ void SimpleOutput::LoadRecordingPreset()
if (strcmp(quality, "Stream") == 0) {
videoRecording = videoStreaming;
aacRecording = aacStreaming;
audioRecording = audioStreaming;
usingRecordingPreset = false;
return;
@@ -519,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();
@@ -595,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");
return FindClosestAvailableAACBitrate(bitrate);
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");
@@ -665,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();
@@ -691,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()
@@ -701,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
@@ -873,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) {
@@ -882,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());
}
}
}
@@ -913,18 +1025,9 @@ bool SimpleOutput::SetupStreaming(obs_service_t *service)
/* --------------------- */
const char *type = obs_service_get_output_type(service);
if (!type) {
type = "rtmp_output";
const char *url = obs_service_get_url(service);
if (url != NULL &&
strncmp(url, FTL_PROTOCOL, strlen(FTL_PROTOCOL)) == 0) {
type = "ftl_output";
} else if (url != NULL && strncmp(url, RTMP_PROTOCOL,
strlen(RTMP_PROTOCOL)) != 0) {
type = "ffmpeg_mpegts_muxer";
}
}
const char *type = GetStreamOutputType(service);
if (!type)
return false;
/* XXX: this is messy and disgusting and should be refactored */
if (outputType != type) {
@@ -960,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;
}
@@ -1037,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);
}
@@ -1120,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;
@@ -1290,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();
@@ -1314,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;
@@ -1358,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)
@@ -1377,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);
@@ -1389,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");
@@ -1468,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);
@@ -1659,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
@@ -1776,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);
@@ -1792,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) {
@@ -1827,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());
@@ -1839,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 FindClosestAvailableAACBitrate(bitrate);
return FindClosestAvailableAudioBitrate(id, bitrate);
}
inline void AdvancedOutput::SetupVodTrack(obs_service_t *service)
@@ -1898,18 +1994,9 @@ bool AdvancedOutput::SetupStreaming(obs_service_t *service)
/* --------------------- */
const char *type = obs_service_get_output_type(service);
if (!type) {
type = "rtmp_output";
const char *url = obs_service_get_url(service);
if (url != NULL &&
strncmp(url, FTL_PROTOCOL, strlen(FTL_PROTOCOL)) == 0) {
type = "ftl_output";
} else if (url != NULL && strncmp(url, RTMP_PROTOCOL,
strlen(RTMP_PROTOCOL)) != 0) {
type = "ffmpeg_mpegts_muxer";
}
}
const char *type = GetStreamOutputType(service);
if (!type)
return false;
/* XXX: this is messy and disgusting and should be refactored */
if (outputType != type) {
@@ -1945,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
View File
@@ -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);
}
+330 -122
View File
@@ -78,29 +78,10 @@ void OBSBasicSettings::InitStreamPage()
ui->twitchAddonDropdown->addItem(
QTStr("Basic.Settings.Stream.TTVAddon.Both"));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateServerList()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateKeyLink()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateVodTrackSetting()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateServiceRecommendations()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateResFPSLimits()));
connect(ui->customServer, SIGNAL(textChanged(const QString &)), this,
SLOT(UpdateKeyLink()));
connect(ui->ignoreRecommended, SIGNAL(clicked(bool)), this,
SLOT(DisplayEnforceWarning(bool)));
connect(ui->ignoreRecommended, SIGNAL(toggled(bool)), this,
SLOT(UpdateResFPSLimits()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateMoreInfoLink()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
SLOT(UpdateAdvNetworkGroup()));
connect(ui->customServer, SIGNAL(textChanged(const QString &)), this,
SLOT(UpdateAdvNetworkGroup()));
}
void OBSBasicSettings::LoadStream1Settings()
@@ -118,11 +99,13 @@ void OBSBasicSettings::LoadStream1Settings()
const char *service = obs_data_get_string(settings, "service");
const char *server = obs_data_get_string(settings, "server");
const char *key = obs_data_get_string(settings, "key");
protocol = QT_UTF8(obs_service_get_protocol(service_obj));
if (strcmp(type, "rtmp_custom") == 0) {
ui->service->setCurrentIndex(0);
ui->customServer->setText(server);
lastServiceIdx = 0;
lastCustomServer = ui->customServer->text();
bool use_auth = obs_data_get_bool(settings, "use_auth");
const char *username =
@@ -458,13 +441,46 @@ void OBSBasicSettings::UseStreamKeyAdvClicked()
ui->streamKeyWidget->setVisible(true);
}
void OBSBasicSettings::on_service_currentIndexChanged(int)
void OBSBasicSettings::on_service_currentIndexChanged(int idx)
{
bool showMore = ui->service->currentData().toInt() ==
(int)ListOpt::ShowAll;
if (showMore)
if (ui->service->currentData().toInt() == (int)ListOpt::ShowAll) {
LoadServices(true);
ui->service->showPopup();
return;
}
ServiceChanged();
UpdateMoreInfoLink();
UpdateServerList();
UpdateKeyLink();
UpdateServiceRecommendations();
UpdateVodTrackSetting();
protocol = FindProtocol();
UpdateAdvNetworkGroup();
if (ServiceSupportsCodecCheck() && UpdateResFPSLimits()) {
lastServiceIdx = idx;
if (idx == 0)
lastCustomServer = ui->customServer->text();
}
}
void OBSBasicSettings::on_customServer_textChanged(const QString &)
{
UpdateKeyLink();
protocol = FindProtocol();
UpdateAdvNetworkGroup();
if (ServiceSupportsCodecCheck())
lastCustomServer = ui->customServer->text();
}
void OBSBasicSettings::ServiceChanged()
{
std::string service = QT_TO_UTF8(ui->service->currentText());
bool custom = IsCustomService();
@@ -515,19 +531,48 @@ void OBSBasicSettings::on_service_currentIndexChanged(int)
}
}
QString OBSBasicSettings::FindProtocol()
{
if (IsCustomService() && !ui->customServer->text().isEmpty()) {
QString server = ui->customServer->text();
if (server.startsWith("rtmps://"))
return QString("RTMPS");
if (server.startsWith("ftl://"))
return QString("FTL");
if (server.startsWith("srt://"))
return QString("SRT");
if (server.startsWith("rist://"))
return QString("RIST");
} else {
obs_properties_t *props =
obs_get_service_properties("rtmp_common");
obs_property_t *services = obs_properties_get(props, "service");
OBSDataAutoRelease settings = obs_data_create();
obs_data_set_string(settings, "service",
QT_TO_UTF8(ui->service->currentText()));
obs_property_modified(services, settings);
obs_properties_destroy(props);
return QT_UTF8(obs_data_get_string(settings, "protocol"));
}
return QString("RTMP");
}
void OBSBasicSettings::UpdateServerList()
{
QString serviceName = ui->service->currentText();
bool showMore = ui->service->currentData().toInt() ==
(int)ListOpt::ShowAll;
if (showMore) {
LoadServices(true);
ui->service->showPopup();
return;
} else {
lastService = serviceName;
}
lastService = serviceName;
obs_properties_t *props = obs_get_service_properties("rtmp_common");
obs_property_t *services = obs_properties_get(props, "service");
@@ -985,17 +1030,14 @@ extern void set_closest_res(int &cx, int &cy,
* which as of this writing, and hopefully for the foreseeable future, there is
* only one.
*/
void OBSBasicSettings::UpdateResFPSLimits()
bool OBSBasicSettings::UpdateResFPSLimits()
{
if (loading)
return;
if (!ServiceSupportsCodecCheck())
return;
return false;
int idx = ui->service->currentIndex();
if (idx == -1)
return;
return false;
bool ignoreRecommended = ui->ignoreRecommended->isChecked();
BPtr<obs_service_resolution> res_list;
@@ -1073,8 +1115,7 @@ void OBSBasicSettings::UpdateResFPSLimits()
ui->ignoreRecommended->setProperty("changed", true);
stream1Changed = true;
EnableApplyButton(true);
UpdateResFPSLimits();
return;
return UpdateResFPSLimits();
}
QMessageBox::StandardButton button;
@@ -1106,7 +1147,7 @@ void OBSBasicSettings::UpdateResFPSLimits()
"setChecked",
Qt::QueuedConnection,
Q_ARG(bool, true));
return;
return false;
}
}
@@ -1187,24 +1228,8 @@ void OBSBasicSettings::UpdateResFPSLimits()
/* ------------------------------------ */
lastIgnoreRecommended = (int)ignoreRecommended;
lastServiceIdx = idx;
}
bool OBSBasicSettings::IsServiceOutputHasNetworkFeatures()
{
if (IsCustomService())
return ui->customServer->text().startsWith("rtmp");
OBSServiceAutoRelease service = SpawnTempService();
const char *output = obs_service_get_output_type(service);
if (!output)
return true;
if (strcmp(output, "rtmp_output") == 0)
return true;
return false;
return true;
}
static bool service_supports_codec(const char **codecs, const char *codec)
@@ -1234,17 +1259,19 @@ static inline bool service_supports_encoder(const char **codecs,
return service_supports_codec(codecs, codec);
}
bool OBSBasicSettings::ServiceAndCodecCompatible()
static bool return_first_id(void *data, const char *id)
{
if (IsCustomService())
return true;
if (ui->service->currentData().toInt() == (int)ListOpt::ShowAll)
return true;
const char **output = (const char **)data;
*output = id;
return false;
}
bool OBSBasicSettings::ServiceAndVCodecCompatible()
{
bool simple = (ui->outputMode->currentIndex() == 0);
bool ret;
OBSService service = SpawnTempService();
const char **codecs = obs_service_get_supported_video_codecs(service);
const char *codec;
if (simple) {
@@ -1257,7 +1284,67 @@ bool OBSBasicSettings::ServiceAndCodecCompatible()
codec = obs_get_encoder_codec(QT_TO_UTF8(encoder));
}
return service_supports_codec(codecs, codec);
OBSService service = SpawnTempService();
const char **codecs = obs_service_get_supported_video_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_video_codecs(output), ';',
false);
ret = service_supports_codec((const char **)output_codecs,
codec);
strlist_free(output_codecs);
} else {
ret = service_supports_codec(codecs, codec);
}
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. */
@@ -1272,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)
@@ -1287,15 +1390,24 @@ static QString get_simple_fallback(const QString &enc)
bool OBSBasicSettings::ServiceSupportsCodecCheck()
{
if (ServiceAndCodecCompatible()) {
if (lastServiceIdx != ui->service->currentIndex())
if (loading)
return false;
bool vcodec_compat = ServiceAndVCodecCompatible();
bool acodec_compat = ServiceAndACodecCompatible();
if (vcodec_compat && acodec_compat) {
if (lastServiceIdx != ui->service->currentIndex() ||
IsCustomService())
ResetEncoders(true);
return true;
}
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);
/* ------------------------------------------------- */
@@ -1309,27 +1421,60 @@ 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
if (button == QMessageBox::No) {
QMetaObject::invokeMethod(ui->service, "setCurrentIndex",
Qt::QueuedConnection,
Q_ARG(int, lastServiceIdx));
if (lastServiceIdx == 0 &&
lastServiceIdx == ui->service->currentIndex())
QMetaObject::invokeMethod(ui->customServer, "setText",
Qt::QueuedConnection,
Q_ARG(QString,
lastCustomServer));
else
QMetaObject::invokeMethod(ui->service,
"setCurrentIndex",
Qt::QueuedConnection,
Q_ARG(int, lastServiceIdx));
return false;
}
@@ -1342,25 +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_vcodecs;
BPtr<char *> output_acodecs;
size_t idx = 0;
if (!vcodecs || IsCustomService()) {
const char *output;
obs_enum_output_types_with_protocol(QT_TO_UTF8(protocol),
&output, return_first_id);
output_vcodecs = strlist_split(
obs_get_output_supported_video_codecs(output), ';',
false);
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");
}
/* ------------------------------------------------- */
@@ -1371,33 +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;
const char *streaming_codecs[] = {
"h264",
#ifdef ENABLE_HEVC
"hevc",
#endif
};
bool is_streaming_codec = false;
for (const char *test_codec : streaming_codecs) {
if (strcmp(codec, test_codec) == 0) {
is_streaming_codec = true;
break;
}
}
if ((caps & ENCODER_HIDE_FLAGS) != 0)
continue;
QString qName = QT_UTF8(name);
QString qType = QT_UTF8(type);
if (is_streaming_codec && 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);
}
}
/* ------------------------------------------------- */
@@ -1407,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
@@ -1453,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
@@ -1466,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);
}
}
+209 -29
View File
@@ -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 = GetAACEncoderBitrateMap();
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 = FindClosestAvailableAACBitrate(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 = FindClosestAvailableAACBitrate(track1Bitrate);
track2Bitrate = FindClosestAvailableAACBitrate(track2Bitrate);
track3Bitrate = FindClosestAvailableAACBitrate(track3Bitrate);
track4Bitrate = FindClosestAvailableAACBitrate(track4Bitrate);
track5Bitrate = FindClosestAvailableAACBitrate(track5Bitrate);
track6Bitrate = FindClosestAvailableAACBitrate(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
@@ -4465,7 +4600,8 @@ void RestrictResetBitrates(initializer_list<QComboBox *> boxes, int maxbitrate)
{
for (auto box : boxes) {
int idx = box->currentIndex();
int max_bitrate = FindClosestAvailableAACBitrate(maxbitrate);
int max_bitrate =
FindClosestAvailableAudioBitrate(box, maxbitrate);
int count = box->count();
int max_idx = box->findText(
QT_UTF8(std::to_string(max_bitrate).c_str()));
@@ -4474,8 +4610,8 @@ void RestrictResetBitrates(initializer_list<QComboBox *> boxes, int maxbitrate)
box->removeItem(i);
if (idx > max_idx) {
int default_bitrate =
FindClosestAvailableAACBitrate(maxbitrate / 2);
int default_bitrate = FindClosestAvailableAudioBitrate(
box, maxbitrate / 2);
int default_idx = box->findText(QT_UTF8(
std::to_string(default_bitrate).c_str()));
@@ -4945,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
}
@@ -4975,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);
@@ -5621,7 +5765,7 @@ void OBSBasicSettings::RecreateOutputResolutionWidget()
void OBSBasicSettings::UpdateAdvNetworkGroup()
{
bool enabled = IsServiceOutputHasNetworkFeatures();
bool enabled = protocol.contains("RTMP");
ui->advNetworkDisabled->setVisible(!enabled);
@@ -5633,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);
}
+16 -8
View File
@@ -261,16 +261,23 @@ private:
void OnOAuthStreamKeyConnected();
void OnAuthConnected();
QString lastService;
QString protocol;
QString lastCustomServer;
int prevLangIndex;
bool prevBrowserAccel;
private slots:
void ServiceChanged();
QString FindProtocol();
void UpdateServerList();
void UpdateKeyLink();
void UpdateVodTrackSetting();
void UpdateServiceRecommendations();
void RecreateOutputResolutionWidget();
void UpdateResFPSLimits();
void UpdateMoreInfoLink();
void UpdateAdvNetworkGroup();
private slots:
void RecreateOutputResolutionWidget();
bool UpdateResFPSLimits();
void DisplayEnforceWarning(bool checked);
void on_show_clicked();
void on_authPwShow_clicked();
@@ -370,9 +377,8 @@ private:
OBSService GetStream1Service();
bool IsServiceOutputHasNetworkFeatures();
bool ServiceAndCodecCompatible();
bool ServiceAndVCodecCompatible();
bool ServiceAndACodecCompatible();
bool ServiceSupportsCodecCheck();
private slots:
@@ -382,6 +388,7 @@ private slots:
void on_buttonBox_clicked(QAbstractButton *button);
void on_service_currentIndexChanged(int idx);
void on_customServer_textChanged(const QString &text);
void on_simpleOutputBrowse_clicked();
void on_advOutRecPathBrowse_clicked();
void on_advOutFFPathBrowse_clicked();
@@ -435,8 +442,6 @@ private slots:
void UpdateStreamDelayEstimate();
void UpdateAdvNetworkGroup();
void UpdateAutomaticReplayBufferCheckboxes();
void AdvOutSplitFileChanged();
@@ -464,6 +469,9 @@ private slots:
void UseStreamKeyAdvClicked();
void SimpleStreamAudioEncoderChanged();
void AdvAudioEncodersChanged();
protected:
virtual void closeEvent(QCloseEvent *event) override;
void reject() override;
+45 -1
View File
@@ -234,7 +234,15 @@ Output Definition Structure (obs_output_info)
This variable specifies which codecs are supported by an encoded
output, separated by semicolon.
(Optional, though recommended)
Required if **OBS_OUTPUT_SERVICE** flag is set, otherwise
recommended.
.. member:: const char *obs_output_info.protocols
This variable specifies which protocols are supported by an output,
separated by semicolon.
Required only if **OBS_OUTPUT_SERVICE** flag is set.
.. _output_signal_handler_reference:
@@ -671,7 +679,9 @@ General Output Functions
---------------------
.. function:: const char *obs_output_get_supported_video_codecs(const obs_output_t *output)
const char *obs_get_output_supported_video_codecs(const char *id)
const char *obs_output_get_supported_audio_codecs(const obs_output_t *output)
const char *obs_get_output_supported_video_codecs(const char *id)
:return: Supported video/audio codecs of an encoded output, separated
by semicolon
@@ -685,6 +695,40 @@ General Output Functions
---------------------
.. function:: const char *obs_output_get_protocols(const obs_output_t *output)
:return: Supported protocols, separated by semicolon. Always NULL if the
output is not **OBS_OUTPUT_SERVICE**.
---------------------
.. function:: bool obs_is_output_protocol_registered(const char *protocol)
Check if one of the registered output use the given protocol.
:return: A boolean showing if an output with the given
protocol is registered
---------------------
.. function:: bool obs_enum_output_protocols(size_t idx, char **protocol)
Enumerates all registered protocol.
---------------------
.. function:: void obs_enum_output_types_with_protocol(const char *protocol, void *data, bool (*enum_cb)(void *data, const char *id))
Enumerates through a callback all available output types for the given protocol.
:param protocol: Protocol of the outputs to enumerate
:param data: Data passed to the callback
:param enum_cb: Callback used when a matching output is found, the id
of the output is passed to the callback
:return: When all outputs are enumerated or if the callback return *false*
---------------------
Functions used by outputs
-------------------------
+91 -4
View File
@@ -137,7 +137,7 @@ Service Definition Structure
(Optional)
:return: The output type that should be used with this service
:return: The output type that should be preferred with this service
.. member:: const char **(*get_supported_video_codecs)(void *data)
@@ -148,6 +148,49 @@ Service Definition Structure
the data manually (typically best to use strlist_split to
generate this)
.. member:: const char **(*get_supported_audio_codecs)(void *data)
(Optional)
:return: A string pointer array of the supported audio codecs, should
be stored by the plugin so the caller does not need to free
the data manually (typically best to use strlist_split to
generate this)
.. member:: const char *(*obs_service_info.get_protocol)(void *data)
:return: The protocol used by the service
.. member:: const char *(*obs_service_info.get_connect_info)(void *data, uint32_t type)
Output a service connection info related to a given type value:
- **OBS_SERVICE_CONNECT_INFO_SERVER_URL** - Server URL (0)
- **OBS_SERVICE_CONNECT_INFO_STREAM_ID** - Stream ID (2)
- **OBS_SERVICE_CONNECT_INFO_STREAM_KEY** - Stream key (alias of **OBS_SERVICE_CONNECT_INFO_STREAM_ID**)
- **OBS_SERVICE_CONNECT_INFO_USERNAME** - Username (4)
- **OBS_SERVICE_CONNECT_INFO_PASSWORD** - Password (6)
- **OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE** - Encryption passphrase (8)
- Odd values as types are reserved for third-party protocols
Depending on the protocol, the service will have to provide information
to the output to be able to connect.
Irrelevant or unused types can return `NULL`.
.. member:: bool (*obs_service_info.can_try_to_connect)(void *data)
:return: If the service has all the needed connection info to be
able to connect.
NOTE: If not set, :c:func:`obs_service_can_try_to_connect()`
returns *true* by default.
General Service Functions
-------------------------
@@ -270,24 +313,36 @@ General Service Functions
:return: The URL currently used for this service
.. deprecated:: 29.1.0
Use :c:func:`obs_service_get_connect_info()` instead.
---------------------
.. function:: const char *obs_service_get_key(const obs_service_t *service)
:return: Stream key (if any) currently used for this service
.. deprecated:: 29.1.0
Use :c:func:`obs_service_get_connect_info()` instead.
---------------------
.. function:: const char *obs_service_get_username(const obs_service_t *service)
:return: User name (if any) currently used for this service
.. deprecated:: 29.1.0
Use :c:func:`obs_service_get_connect_info()` instead.
---------------------
.. function:: const char *obs_service_get_password(const obs_service_t *service)
:return: Password (if any) currently used for this service
.. deprecated:: 29.1.0
Use :c:func:`obs_service_get_connect_info()` instead.
---------------------
.. function:: void obs_service_apply_encoder_settings(obs_service_t *service, obs_data_t *video_encoder_settings, obs_data_t *audio_encoder_settings)
@@ -301,9 +356,41 @@ General Service Functions
.. function:: const char **obs_service_get_supported_video_codecs(const obs_service_t *service)
:return: An array of string pointers containing the supported codecs
for the service, terminated with a *NULL* pointer. Does not
need to be freed
:return: An array of string pointers containing the supported video
codecs for the service, terminated with a *NULL* pointer.
Does not need to be freed
---------------------
.. function:: const char **obs_service_get_supported_audio_codecs(const obs_service_t *service)
:return: An array of string pointers containing the supported audio
codecs for the service, terminated with a *NULL* pointer.
Does not need to be freed
---------------------
.. function:: const char *obs_service_get_protocol(const obs_service_t *service)
:return: Protocol currently used for this service
---------------------
.. function:: const char *obs_service_get_preferred_output_type(const obs_service_t *service)
:return: The output type that should be preferred with this service
.. function:: const char *obs_service_get_connect_info(const obs_service_t *service, uint32_t type)
:param type: Check :c:member:`obs_service_info.get_connect_info` for
type values.
:return: Connection info related to the type value.
.. function:: bool obs_service_can_try_to_connect(const obs_service_t *service)
:return: If the service has all the needed connection info to be
able to connect. Returns `true` if
:c:member:`obs_service_info.can_try_to_connect` is not set.
.. ---------------------------------------------------------------------------
+2
View File
@@ -411,6 +411,8 @@ struct obs_core_data {
obs_data_t *private_data;
volatile bool valid;
DARRAY(char *) protocols;
};
/* user hotkeys */
+22
View File
@@ -836,6 +836,9 @@ void obs_register_output_s(const struct obs_output_info *info, size_t size)
CHECK_REQUIRED_VAL_(info, start, obs_register_output);
CHECK_REQUIRED_VAL_(info, stop, obs_register_output);
if (info->flags & OBS_OUTPUT_SERVICE)
CHECK_REQUIRED_VAL_(info, protocols, obs_register_output);
if (info->flags & OBS_OUTPUT_ENCODED) {
CHECK_REQUIRED_VAL_(info, encoded_packet, obs_register_output);
} else {
@@ -856,6 +859,24 @@ void obs_register_output_s(const struct obs_output_info *info, size_t size)
#undef CHECK_REQUIRED_VAL_
REGISTER_OBS_DEF(size, obs_output_info, obs->output_types, info);
if (info->flags & OBS_OUTPUT_SERVICE) {
char **protocols = strlist_split(info->protocols, ';', false);
for (char **protocol = protocols; *protocol; ++protocol) {
bool skip = false;
for (size_t i = 0; i < obs->data.protocols.num; i++) {
if (strcmp(*protocol,
obs->data.protocols.array[i]) == 0)
skip = true;
}
if (skip)
continue;
char *new_prtcl = bstrdup(*protocol);
da_push_back(obs->data.protocols, &new_prtcl);
}
strlist_free(protocols);
}
return;
error:
@@ -907,6 +928,7 @@ void obs_register_service_s(const struct obs_service_info *info, size_t size)
CHECK_REQUIRED_VAL_(info, get_name, obs_register_service);
CHECK_REQUIRED_VAL_(info, create, obs_register_service);
CHECK_REQUIRED_VAL_(info, destroy, obs_register_service);
CHECK_REQUIRED_VAL_(info, get_protocol, obs_register_service);
#undef CHECK_REQUIRED_VAL_
REGISTER_OBS_DEF(size, obs_service_info, obs->service_types, info);
+52 -1
View File
@@ -290,7 +290,8 @@ bool obs_output_start(obs_output_t *output)
return false;
has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;
if (has_service && !obs_service_initialize(output->service, output))
if (has_service && !(obs_service_can_try_to_connect(output->service) &&
obs_service_initialize(output->service, output)))
return false;
encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;
@@ -2713,3 +2714,53 @@ const char *obs_output_get_supported_audio_codecs(const obs_output_t *output)
? output->info.encoded_audio_codecs
: NULL;
}
const char *obs_output_get_protocols(const obs_output_t *output)
{
if (!obs_output_valid(output, "obs_output_get_protocols"))
return NULL;
return (output->info.flags & OBS_OUTPUT_SERVICE)
? output->info.protocols
: NULL;
}
void obs_enum_output_types_with_protocol(const char *protocol, void *data,
bool (*enum_cb)(void *data,
const char *id))
{
if (!obs_is_output_protocol_registered(protocol))
return;
size_t protocol_len = strlen(protocol);
for (size_t i = 0; i < obs->output_types.num; i++) {
if (!(obs->output_types.array[i].flags & OBS_OUTPUT_SERVICE))
continue;
const char *substr = obs->output_types.array[i].protocols;
while (substr && substr[0] != '\0') {
const char *next = strchr(substr, ';');
size_t len = next ? (size_t)(next - substr)
: strlen(substr);
if (protocol_len == len &&
strncmp(substr, protocol, len) == 0) {
if (!enum_cb(data,
obs->output_types.array[i].id))
return;
}
substr = next ? next + 1 : NULL;
}
}
}
const char *obs_get_output_supported_video_codecs(const char *id)
{
const struct obs_output_info *info = find_output(id);
return info ? info->encoded_video_codecs : NULL;
}
const char *obs_get_output_supported_audio_codecs(const char *id)
{
const struct obs_output_info *info = find_output(id);
return info ? info->encoded_audio_codecs : NULL;
}
+3
View File
@@ -77,6 +77,9 @@ struct obs_output_info {
/* raw audio callback for multi track outputs */
void (*raw_audio2)(void *data, size_t idx, struct audio_data *frames);
/* required if OBS_OUTPUT_SERVICE */
const char *protocols;
};
EXPORT void obs_register_output_s(const struct obs_output_info *info,
+55 -10
View File
@@ -411,16 +411,6 @@ const char *obs_service_get_id(const obs_service_t *service)
: NULL;
}
const char *obs_service_get_output_type(const obs_service_t *service)
{
if (!obs_service_valid(service, "obs_service_get_output_type"))
return NULL;
if (service->info.get_output_type)
return service->info.get_output_type(service->context.data);
return NULL;
}
void obs_service_get_supported_resolutions(
const obs_service_t *service,
struct obs_service_resolution **resolutions, size_t *count)
@@ -477,3 +467,58 @@ obs_service_get_supported_video_codecs(const obs_service_t *service)
service->context.data);
return NULL;
}
const char **
obs_service_get_supported_audio_codecs(const obs_service_t *service)
{
if (service->info.get_supported_audio_codecs)
return service->info.get_supported_audio_codecs(
service->context.data);
return NULL;
}
const char *obs_service_get_protocol(const obs_service_t *service)
{
if (!obs_service_valid(service, "obs_service_get_protocol"))
return NULL;
return service->info.get_protocol(service->context.data);
}
/* OBS_DEPRECATED */
const char *obs_service_get_output_type(const obs_service_t *service)
{
return obs_service_get_preferred_output_type(service);
}
const char *obs_service_get_preferred_output_type(const obs_service_t *service)
{
if (!obs_service_valid(service,
"obs_service_get_preferred_output_type"))
return NULL;
if (service->info.get_output_type)
return service->info.get_output_type(service->context.data);
return NULL;
}
const char *obs_service_get_connect_info(const obs_service_t *service,
uint32_t type)
{
if (!obs_service_valid(service, "obs_service_get_info"))
return NULL;
if (!service->info.get_connect_info)
return NULL;
return service->info.get_connect_info(service->context.data, type);
}
bool obs_service_can_try_to_connect(const obs_service_t *service)
{
if (!obs_service_valid(service, "obs_service_can_connect"))
return false;
if (!service->info.can_try_to_connect)
return true;
return service->info.can_try_to_connect(service->context.data);
}
+20
View File
@@ -33,6 +33,17 @@ struct obs_service_resolution {
int cy;
};
/* NOTE: Odd numbers are reserved for custom info from third-party protocols */
enum obs_service_connect_info {
OBS_SERVICE_CONNECT_INFO_SERVER_URL = 0,
OBS_SERVICE_CONNECT_INFO_STREAM_ID = 2,
OBS_SERVICE_CONNECT_INFO_STREAM_KEY =
2, // Alias of OBS_SERVICE_CONNECT_INFO_STREAM_ID
OBS_SERVICE_CONNECT_INFO_USERNAME = 4,
OBS_SERVICE_CONNECT_INFO_PASSWORD = 6,
OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE = 8,
};
struct obs_service_info {
/* required */
const char *id;
@@ -77,6 +88,7 @@ struct obs_service_info {
void *type_data;
void (*free_type_data)(void *type_data);
/* TODO: Rename to 'get_preferred_output_type' once a API/ABI break happen */
const char *(*get_output_type)(void *data);
void (*get_supported_resolutions)(
@@ -88,6 +100,14 @@ struct obs_service_info {
int *audio_bitrate);
const char **(*get_supported_video_codecs)(void *data);
const char *(*get_protocol)(void *data);
const char **(*get_supported_audio_codecs)(void *data);
const char *(*get_connect_info)(void *data, uint32_t type);
bool (*can_try_to_connect)(void *data);
};
EXPORT void obs_register_service_s(const struct obs_service_info *info,
+23
View File
@@ -1078,6 +1078,10 @@ static void obs_free_data(void)
da_free(data->draw_callbacks);
da_free(data->tick_callbacks);
obs_data_release(data->private_data);
for (size_t i = 0; i < data->protocols.num; i++)
bfree(data->protocols.array[i]);
da_free(data->protocols);
}
static const char *obs_signals[] = {
@@ -3452,3 +3456,22 @@ bool obs_weak_object_references_object(obs_weak_object_t *weak,
{
return weak && object && weak->object == object;
}
bool obs_is_output_protocol_registered(const char *protocol)
{
for (size_t i = 0; i < obs->data.protocols.num; i++) {
if (strcmp(protocol, obs->data.protocols.array[i]) == 0)
return true;
}
return false;
}
bool obs_enum_output_protocols(size_t idx, char **protocol)
{
if (idx >= obs->data.protocols.num)
return false;
*protocol = obs->data.protocols.array[idx];
return true;
}
+38 -5
View File
@@ -2211,6 +2211,20 @@ obs_output_get_supported_video_codecs(const obs_output_t *output);
EXPORT const char *
obs_output_get_supported_audio_codecs(const obs_output_t *output);
EXPORT const char *obs_output_get_protocols(const obs_output_t *output);
EXPORT bool obs_is_output_protocol_registered(const char *protocol);
EXPORT bool obs_enum_output_protocols(size_t idx, char **protocol);
EXPORT void obs_enum_output_types_with_protocol(
const char *protocol, void *data,
bool (*enum_cb)(void *data, const char *id));
EXPORT const char *obs_get_output_supported_video_codecs(const char *id);
EXPORT const char *obs_get_output_supported_audio_codecs(const char *id);
/* ------------------------------------------------------------------------- */
/* Functions used by outputs */
@@ -2494,16 +2508,20 @@ EXPORT void obs_service_update(obs_service_t *service, obs_data_t *settings);
EXPORT obs_data_t *obs_service_get_settings(const obs_service_t *service);
/** Returns the URL for this service context */
EXPORT const char *obs_service_get_url(const obs_service_t *service);
OBS_DEPRECATED EXPORT const char *
obs_service_get_url(const obs_service_t *service);
/** Returns the stream key (if any) for this service context */
EXPORT const char *obs_service_get_key(const obs_service_t *service);
OBS_DEPRECATED EXPORT const char *
obs_service_get_key(const obs_service_t *service);
/** Returns the username (if any) for this service context */
EXPORT const char *obs_service_get_username(const obs_service_t *service);
OBS_DEPRECATED EXPORT const char *
obs_service_get_username(const obs_service_t *service);
/** Returns the password (if any) for this service context */
EXPORT const char *obs_service_get_password(const obs_service_t *service);
OBS_DEPRECATED EXPORT const char *
obs_service_get_password(const obs_service_t *service);
/**
* Applies service-specific video encoder settings.
@@ -2531,9 +2549,24 @@ EXPORT void obs_service_get_max_bitrate(const obs_service_t *service,
EXPORT const char **
obs_service_get_supported_video_codecs(const obs_service_t *service);
EXPORT const char **
obs_service_get_supported_audio_codecs(const obs_service_t *service);
/* NOTE: This function is temporary and should be removed/replaced at a later
* date. */
EXPORT const char *obs_service_get_output_type(const obs_service_t *service);
OBS_DEPRECATED EXPORT const char *
obs_service_get_output_type(const obs_service_t *service);
/** Returns the protocol for this service context */
EXPORT const char *obs_service_get_protocol(const obs_service_t *service);
EXPORT const char *
obs_service_get_preferred_output_type(const obs_service_t *service);
EXPORT const char *obs_service_get_connect_info(const obs_service_t *service,
uint32_t type);
EXPORT bool obs_service_can_try_to_connect(const obs_service_t *service);
/* ------------------------------------------------------------------------- */
/* Source frame allocation functions */
@@ -1,4 +1,4 @@
CoreAudioAAC="CoreAudio AAC encoder"
CoreAudioAAC="CoreAudio AAC"
Bitrate="Bitrate"
AllowHEAAC="Allow HE-AAC"
OutputSamplerate="Output Sample Rate"
+1 -1
View File
@@ -1409,7 +1409,7 @@ bool obs_module_load(void)
};
aac_info.id = "CoreAudio_AAC";
aac_info.type = OBS_ENCODER_AUDIO;
aac_info.codec = "AAC";
aac_info.codec = "aac";
aac_info.get_name = aac_get_name;
aac_info.destroy = aac_destroy;
aac_info.create = aac_create;
+2 -2
View File
@@ -1,6 +1,6 @@
FFmpegOutput="FFmpeg Output"
FFmpegAAC="FFmpeg Default AAC Encoder"
FFmpegOpus="FFmpeg Opus Encoder"
FFmpegAAC="FFmpeg AAC"
FFmpegOpus="FFmpeg Opus"
FFmpegOpts="FFmpeg Options"
FFmpegOpts.ToolTip.Source="Allows you to set FFmpeg options. This only accepts options in the option=value format.\nMultiple options can be set by separating them with a space.\nExample: rtsp_transport=tcp rtsp_flags=prefer_tcp"
Bitrate="Bitrate"
@@ -427,7 +427,7 @@ static size_t enc_frame_size(void *data)
struct obs_encoder_info aac_encoder_info = {
.id = "ffmpeg_aac",
.type = OBS_ENCODER_AUDIO,
.codec = "AAC",
.codec = "aac",
.get_name = aac_getname,
.create = aac_create,
.destroy = enc_destroy,
+5 -2
View File
@@ -126,8 +126,10 @@ bool ffmpeg_hls_mux_start(void *data)
service = obs_output_get_service(stream->output);
if (!service)
return false;
path_str = obs_service_get_url(service);
stream_key = obs_service_get_key(service);
path_str = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL);
stream_key = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_STREAM_KEY);
dstr_copy(&stream->stream_key, stream_key);
dstr_copy(&path, path_str);
dstr_replace(&path, "{stream_key}", stream_key);
@@ -331,6 +333,7 @@ struct obs_output_info ffmpeg_hls_muxer = {
.id = "ffmpeg_hls_muxer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_SERVICE,
.protocols = "HLS",
#ifdef ENABLE_HEVC
.encoded_video_codecs = "h264;hevc",
#else
+33 -21
View File
@@ -349,24 +349,26 @@ static inline int connect_mpegts_url(struct ffmpeg_output *stream, bool is_rist)
if (!is_rist) {
SRTContext *context = (SRTContext *)uc->priv_data;
context->streamid = NULL;
if (stream->ff_data.config.key != NULL) {
if (strlen(stream->ff_data.config.key))
context->streamid =
av_strdup(stream->ff_data.config.key);
if (stream->ff_data.config.stream_id != NULL) {
if (strlen(stream->ff_data.config.stream_id))
context->streamid = av_strdup(
stream->ff_data.config.stream_id);
}
context->passphrase = NULL;
if (stream->ff_data.config.password != NULL) {
if (strlen(stream->ff_data.config.password))
context->passphrase = av_strdup(
stream->ff_data.config.password);
if (stream->ff_data.config.encrypt_passphrase != NULL) {
if (strlen(stream->ff_data.config.encrypt_passphrase))
context->passphrase =
av_strdup(stream->ff_data.config
.encrypt_passphrase);
}
} else {
RISTContext *context = (RISTContext *)uc->priv_data;
context->secret = NULL;
if (stream->ff_data.config.key != NULL) {
if (strlen(stream->ff_data.config.key))
if (stream->ff_data.config.encrypt_passphrase != NULL) {
if (strlen(stream->ff_data.config.encrypt_passphrase))
context->secret =
bstrdup(stream->ff_data.config.key);
bstrdup(stream->ff_data.config
.encrypt_passphrase);
}
context->username = NULL;
if (stream->ff_data.config.username != NULL) {
@@ -879,10 +881,16 @@ static bool set_config(struct ffmpeg_output *stream)
service = obs_output_get_service(stream->output);
if (!service)
return false;
config.url = obs_service_get_url(service);
config.username = obs_service_get_username(service);
config.password = obs_service_get_password(service);
config.key = obs_service_get_key(service);
config.url = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL);
config.username = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_USERNAME);
config.password = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_PASSWORD);
config.stream_id = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_STREAM_ID);
config.encrypt_passphrase = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE);
config.format_name = "mpegts";
config.format_mime_type = "video/M2PT";
@@ -955,11 +963,14 @@ static bool set_config(struct ffmpeg_output *stream)
obs_data_release(settings);
/* 3. Audio settings */
// 3.a) set audio encoder and id to aac
// 3.a) set audio codec & id from audio encoder
obs_encoder_t *aencoder =
obs_output_get_audio_encoder(stream->output, 0);
config.audio_encoder = "aac";
config.audio_encoder_id = AV_CODEC_ID_AAC;
config.audio_encoder = obs_encoder_get_codec(aencoder);
if (strcmp(config.audio_encoder, "aac") == 0)
config.audio_encoder_id = AV_CODEC_ID_AAC;
else if (strcmp(config.audio_encoder, "opus") == 0)
config.audio_encoder_id = AV_CODEC_ID_OPUS;
// 3.b) get audio bitrate from the audio encoder.
settings = obs_encoder_get_settings(aencoder);
@@ -1289,12 +1300,13 @@ struct obs_output_info ffmpeg_mpegts_muxer = {
.id = "ffmpeg_mpegts_muxer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_SERVICE,
.protocols = "SRT;RIST",
#ifdef ENABLE_HEVC
.encoded_video_codecs = "h264;hevc;av1",
.encoded_video_codecs = "h264;hevc",
#else
.encoded_video_codecs = "h264;av1",
.encoded_video_codecs = "h264",
#endif
.encoded_audio_codecs = "aac",
.encoded_audio_codecs = "aac;opus",
.get_name = ffmpeg_mpegts_getname,
.create = ffmpeg_mpegts_create,
.destroy = ffmpeg_mpegts_destroy,
+5 -3
View File
@@ -419,7 +419,8 @@ static inline bool ffmpeg_mux_start_internal(struct ffmpeg_muxer *stream,
service = obs_output_get_service(stream->output);
if (!service)
return false;
path = obs_service_get_url(service);
path = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL);
stream->split_file = false;
} else {
@@ -944,8 +945,9 @@ struct obs_output_info ffmpeg_mpegts_muxer = {
.id = "ffmpeg_mpegts_muxer",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_MULTI_TRACK |
OBS_OUTPUT_SERVICE,
.encoded_video_codecs = "h264;av1",
.encoded_audio_codecs = "aac",
.protocols = "SRT;RIST",
.encoded_video_codecs = "h264",
.encoded_audio_codecs = "aac;opus",
.get_name = ffmpeg_mpegts_mux_getname,
.create = ffmpeg_mux_create,
.destroy = ffmpeg_mux_destroy,
+2 -1
View File
@@ -39,7 +39,8 @@ struct ffmpeg_cfg {
int frame_size; // audio frame size
const char *username;
const char *password;
const char *key;
const char *stream_id;
const char *encrypt_passphrase;
};
struct ffmpeg_audio_info {
+1 -1
View File
@@ -1,3 +1,3 @@
LibFDK="libfdk AAC Encoder"
LibFDK="libfdk AAC"
Bitrate="Bitrate"
Afterburner="Enable AAC Afterburner"
+1 -1
View File
@@ -305,7 +305,7 @@ static size_t libfdk_frame_size(void *data)
struct obs_encoder_info obs_libfdk_encoder = {
.id = "libfdk_aac",
.type = OBS_ENCODER_AUDIO,
.codec = "AAC",
.codec = "aac",
.get_name = libfdk_getname,
.create = libfdk_create,
.destroy = libfdk_destroy,
+11 -4
View File
@@ -1049,7 +1049,8 @@ static int init_connect(struct ftl_stream *stream)
obs_output_get_video_encoder(stream->output);
obs_data_t *video_settings = obs_encoder_get_settings(video_encoder);
ingest_url = obs_service_get_url(service);
ingest_url = obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL);
if (strncmp(ingest_url, FTL_URL_PROTOCOL, strlen(FTL_URL_PROTOCOL)) ==
0) {
dstr_copy(&stream->path, ingest_url + strlen(FTL_URL_PROTOCOL));
@@ -1057,7 +1058,8 @@ static int init_connect(struct ftl_stream *stream)
dstr_copy(&stream->path, ingest_url);
}
key = obs_service_get_key(service);
key = obs_service_get_connect_info(service,
OBS_SERVICE_CONNECT_INFO_STREAM_KEY);
int target_bitrate = (int)obs_data_get_int(video_settings, "bitrate");
int peak_bitrate = (int)((float)target_bitrate * 1.1f);
@@ -1093,8 +1095,12 @@ static int init_connect(struct ftl_stream *stream)
}
}
dstr_copy(&stream->username, obs_service_get_username(service));
dstr_copy(&stream->password, obs_service_get_password(service));
dstr_copy(&stream->username,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_USERNAME));
dstr_copy(&stream->password,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_PASSWORD));
dstr_depad(&stream->path);
stream->drop_threshold_usec =
@@ -1159,6 +1165,7 @@ static int _ftl_error_to_obs_error(int status)
struct obs_output_info ftl_output_info = {
.id = "ftl_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE,
.protocols = "FTL",
.encoded_video_codecs = "h264",
.encoded_audio_codecs = "opus",
.get_name = ftl_stream_getname,
+17 -4
View File
@@ -1134,10 +1134,18 @@ static bool init_connect(struct rtmp_stream *stream)
stream->got_first_video = false;
settings = obs_output_get_settings(stream->output);
dstr_copy(&stream->path, obs_service_get_url(service));
dstr_copy(&stream->key, obs_service_get_key(service));
dstr_copy(&stream->username, obs_service_get_username(service));
dstr_copy(&stream->password, obs_service_get_password(service));
dstr_copy(&stream->path,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_SERVER_URL));
dstr_copy(&stream->key,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_STREAM_KEY));
dstr_copy(&stream->username,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_USERNAME));
dstr_copy(&stream->password,
obs_service_get_connect_info(
service, OBS_SERVICE_CONNECT_INFO_PASSWORD));
dstr_depad(&stream->path);
dstr_depad(&stream->key);
drop_b = (int64_t)obs_data_get_int(settings, OPT_DROP_THRESHOLD);
@@ -1642,6 +1650,11 @@ struct obs_output_info rtmp_output_info = {
.id = "rtmp_output",
.flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED | OBS_OUTPUT_SERVICE |
OBS_OUTPUT_MULTI_TRACK,
#ifdef NO_CRYPTO
.protocols = "RTMP",
#else
.protocols = "RTMP;RTMPS",
#endif
.encoded_video_codecs = "h264",
.encoded_audio_codecs = "aac",
.get_name = rtmp_stream_getname,
+2 -2
View File
@@ -1,11 +1,11 @@
{
"$schema": "schema/package-schema.json",
"url": "https://obsproject.com/obs2_update/rtmp-services/v4",
"version": 220,
"version": 222,
"files": [
{
"name": "services.json",
"version": 220
"version": 222
}
]
}
@@ -16,6 +16,18 @@
"description": "Name of the streaming service. Will be displayed in the Service dropdown.",
"minLength": 1
},
"protocol" : {
"type": "string",
"description": "Protocol used by the service. If missing the service is considered using RTMP or RTMPS.",
"enum": [
"RTMP",
"RTMPS",
"HLS",
"FTL",
"SRT",
"RIST"
]
},
"common": {
"type": "boolean",
"description": "Whether or not the service is shown in the list before it is expanded to all services by the user.",
@@ -106,7 +118,7 @@
},
"output": {
"type": "string",
"description": "OBS output module used.",
"description": "OBS output module used. Unused since OBS Studio 30, kept for backward compatibility.",
"enum": [
"rtmp_output",
"ffmpeg_hls_muxer",
@@ -200,6 +212,18 @@
"required": [
"name",
"servers"
],
"allOf": [
{
"$comment": "Require protocol field if not an RTMP(S) URL",
"if": { "not": { "properties": { "servers": { "items": { "properties": { "url": { "format": "uri", "pattern": "^rtmps?://" } } } } } } },
"then": { "required": ["protocol"] }
},
{
"$comment": "Require recommended output field if protocol field is not RTMP(S)",
"if": { "required": ["protocol"], "properties": { "protocol": { "pattern": "^(HLS|SRT|RIST|FTL)$" } } },
"then": { "properties": { "recommended": { "required": ["output"] } } }
}
]
},
"additionalItems": true
+5 -6
View File
@@ -204,6 +204,7 @@
"common": false,
"more_info_link": "https://developers.google.com/youtube/v3/live/guides/ingestion-protocol-comparison",
"stream_key_link": "https://www.youtube.com/live_dashboard",
"protocol": "HLS",
"supported video codecs": [
"h264",
"hevc"
@@ -1445,9 +1446,7 @@
{
"name": "YouNow",
"common": false,
"supported audio codecs": [
"opus"
],
"protocol": "FTL",
"servers": [
{
"name": "younow.com",
@@ -1687,6 +1686,7 @@
},
{
"name": "SHOWROOM",
"protocol": "RTMP",
"servers": [
{
"name": "Default",
@@ -1821,9 +1821,7 @@
{
"name": "Glimesh",
"stream_key_link": "https://glimesh.tv/users/settings/stream",
"supported audio codecs": [
"opus"
],
"protocol": "FTL",
"servers": [
{
"name": "North America - Chicago, United States",
@@ -2024,6 +2022,7 @@
},
{
"name": "Dacast",
"protocol": "RTMP",
"servers": [
{
"name": "Default",
+187 -33
View File
@@ -13,15 +13,16 @@
struct rtmp_common {
char *service;
char *protocol;
char *server;
char *key;
char *output;
struct obs_service_resolution *supported_resolutions;
size_t supported_resolutions_count;
int max_fps;
char **video_codecs;
char **audio_codecs;
bool supports_additional_audio_track;
};
@@ -76,10 +77,6 @@ static void ensure_valid_url(struct rtmp_common *service, json_t *json,
static void update_recommendations(struct rtmp_common *service, json_t *rec)
{
const char *out = get_string_val(rec, "output");
if (out)
service->output = bstrdup(out);
json_t *sr = json_object_get(rec, "supported resolutions");
if (sr && json_is_array(sr)) {
DARRAY(struct obs_service_resolution) res_list;
@@ -116,18 +113,22 @@ static void rtmp_common_update(void *data, obs_data_t *settings)
struct rtmp_common *service = data;
bfree(service->supported_resolutions);
bfree(service->video_codecs);
if (service->video_codecs)
bfree(service->video_codecs);
if (service->audio_codecs)
bfree(service->audio_codecs);
bfree(service->service);
bfree(service->protocol);
bfree(service->server);
bfree(service->output);
bfree(service->key);
service->service = bstrdup(obs_data_get_string(settings, "service"));
service->protocol = bstrdup(obs_data_get_string(settings, "protocol"));
service->server = bstrdup(obs_data_get_string(settings, "server"));
service->key = bstrdup(obs_data_get_string(settings, "key"));
service->supports_additional_audio_track = false;
service->output = NULL;
service->video_codecs = NULL;
service->audio_codecs = NULL;
service->supported_resolutions = NULL;
service->supported_resolutions_count = 0;
service->max_fps = 0;
@@ -154,9 +155,6 @@ static void rtmp_common_update(void *data, obs_data_t *settings)
}
}
json_decref(root);
if (!service->output)
service->output = bstrdup("rtmp_output");
}
static void rtmp_common_destroy(void *data)
@@ -164,10 +162,13 @@ static void rtmp_common_destroy(void *data)
struct rtmp_common *service = data;
bfree(service->supported_resolutions);
bfree(service->video_codecs);
if (service->video_codecs)
bfree(service->video_codecs);
if (service->audio_codecs)
bfree(service->audio_codecs);
bfree(service->service);
bfree(service->protocol);
bfree(service->server);
bfree(service->output);
bfree(service->key);
bfree(service);
}
@@ -208,6 +209,35 @@ static inline bool get_bool_val(json_t *service, const char *key)
return json_is_true(bool_val);
}
#define RTMP_PREFIX "rtmp://"
#define RTMPS_PREFIX "rtmps://"
static bool is_protocol_available(json_t *service)
{
const char *protocol = get_string_val(service, "protocol");
if (protocol)
return obs_is_output_protocol_registered(protocol);
/* Test RTMP and RTMPS if no protocol found */
json_t *servers;
size_t index;
json_t *server;
const char *url;
bool ret = false;
servers = json_object_get(service, "servers");
json_array_foreach (servers, index, server) {
url = get_string_val(server, "url");
if (strncmp(url, RTMP_PREFIX, strlen(RTMP_PREFIX)) == 0)
ret |= obs_is_output_protocol_registered("RTMP");
else if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0)
ret |= obs_is_output_protocol_registered("RTMPS");
}
return ret;
}
static void add_service(obs_property_t *list, json_t *service, bool show_all,
const char *cur_service)
{
@@ -258,6 +288,9 @@ static void add_services(obs_property_t *list, json_t *root, bool show_all,
}
json_array_foreach (root, index, service) {
/* Skip service with non-available protocol */
if (!is_protocol_available(service))
continue;
add_service(list, service, show_all, cur_service);
}
@@ -399,11 +432,13 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service,
return;
}
/* Assumption: Twitch should be RTMP only, so no RTMPS check */
if (strcmp(name, "Twitch") == 0) {
if (fill_twitch_servers(servers_prop))
return;
}
/* Assumption: Nimo TV should be RTMP only, so no RTMPS check in the ingest */
if (strcmp(name, "Nimo TV") == 0) {
obs_property_list_add_string(
servers_prop, obs_module_text("Server.Auto"), "auto");
@@ -416,6 +451,11 @@ static void fill_servers(obs_property_t *servers_prop, json_t *service,
if (!server_name || !url)
continue;
/* Skip RTMPS server if protocol not registered */
if ((!obs_is_output_protocol_registered("RTMPS")) &&
(strncmp(url, "rtmps://", 8) == 0))
continue;
obs_property_list_add_string(servers_prop, server_name, url);
}
}
@@ -439,6 +479,29 @@ static void fill_stream_key_link(json_t *service, obs_data_t *settings)
stream_key_link);
}
static void update_protocol(json_t *service, obs_data_t *settings)
{
const char *protocol = get_string_val(service, "protocol");
if (protocol) {
obs_data_set_string(settings, "protocol", protocol);
return;
}
json_t *servers = json_object_get(service, "servers");
if (!json_is_array(servers))
return;
json_t *server = json_array_get(servers, 0);
const char *url = get_string_val(server, "url");
if (strncmp(url, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0) {
obs_data_set_string(settings, "protocol", "RTMPS");
return;
}
obs_data_set_string(settings, "protocol", "RTMP");
}
static inline json_t *find_service(json_t *root, const char *name,
const char **p_new_name)
{
@@ -449,6 +512,10 @@ static inline json_t *find_service(json_t *root, const char *name,
*p_new_name = NULL;
json_array_foreach (root, index, service) {
/* skip service with non-available protocol */
if (!is_protocol_available(service))
continue;
const char *cur_name = get_string_val(service, "name");
if (strcmp(name, cur_name) == 0)
@@ -503,6 +570,7 @@ static bool service_selected(obs_properties_t *props, obs_property_t *p,
fill_servers(obs_properties_get(props, "server"), service, name);
fill_more_info_link(service, settings);
fill_stream_key_link(service, settings);
update_protocol(service, settings);
return true;
}
@@ -667,12 +735,6 @@ static void rtmp_common_apply_settings(void *data, obs_data_t *video_settings,
}
}
static const char *rtmp_common_get_output_type(void *data)
{
struct rtmp_common *service = data;
return service->output;
}
static const char *rtmp_common_url(void *data)
{
struct rtmp_common *service = data;
@@ -874,21 +936,20 @@ static const char **rtmp_common_get_supported_video_codecs(void *data)
json_t *json_video_codecs =
json_object_get(json_service, "supported video codecs");
if (json_is_array(json_video_codecs)) {
size_t index;
json_t *item;
if (!json_is_array(json_video_codecs)) {
goto fail;
}
json_array_foreach (json_video_codecs, index, item) {
char codec[16];
size_t index;
json_t *item;
snprintf(codec, sizeof(codec), "%s",
json_string_value(item));
if (codecs.len)
dstr_cat(&codecs, ";");
dstr_cat(&codecs, codec);
}
} else {
dstr_cat(&codecs, "h264;");
json_array_foreach (json_video_codecs, index, item) {
char codec[16];
snprintf(codec, sizeof(codec), "%s", json_string_value(item));
if (codecs.len)
dstr_cat(&codecs, ";");
dstr_cat(&codecs, codec);
}
service->video_codecs = strlist_split(codecs.array, ';', false);
@@ -899,6 +960,49 @@ fail:
return (const char **)service->video_codecs;
}
static const char **rtmp_common_get_supported_audio_codecs(void *data)
{
struct rtmp_common *service = data;
if (service->audio_codecs)
return (const char **)service->audio_codecs;
struct dstr codecs = {0};
json_t *root = open_services_file();
if (!root)
return NULL;
json_t *json_service = find_service(root, service->service, NULL);
if (!json_service) {
goto fail;
}
json_t *json_audio_codecs =
json_object_get(json_service, "supported audio codecs");
if (json_is_array(json_audio_codecs)) {
goto fail;
}
size_t index;
json_t *item;
json_array_foreach (json_audio_codecs, index, item) {
char codec[16];
snprintf(codec, sizeof(codec), "%s", json_string_value(item));
if (codecs.len)
dstr_cat(&codecs, ";");
dstr_cat(&codecs, codec);
}
service->audio_codecs = strlist_split(codecs.array, ';', false);
dstr_free(&codecs);
fail:
json_decref(root);
return (const char **)service->audio_codecs;
}
static const char *rtmp_common_username(void *data)
{
struct rtmp_common *service = data;
@@ -925,6 +1029,53 @@ static const char *rtmp_common_password(void *data)
return NULL;
}
static const char *rtmp_common_get_protocol(void *data)
{
struct rtmp_common *service = data;
return service->protocol ? service->protocol : "RTMP";
}
static const char *rtmp_common_get_connect_info(void *data, uint32_t type)
{
switch ((enum obs_service_connect_info)type) {
case OBS_SERVICE_CONNECT_INFO_SERVER_URL:
return rtmp_common_url(data);
case OBS_SERVICE_CONNECT_INFO_STREAM_ID:
return rtmp_common_key(data);
case OBS_SERVICE_CONNECT_INFO_USERNAME:
return rtmp_common_username(data);
case OBS_SERVICE_CONNECT_INFO_PASSWORD:
return rtmp_common_password(data);
case OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE: {
const char *protocol = rtmp_common_get_protocol(data);
if ((strcmp(protocol, "SRT") == 0))
return rtmp_common_password(data);
else if ((strcmp(protocol, "RIST") == 0))
return rtmp_common_key(data);
break;
}
}
return NULL;
}
static bool rtmp_common_can_try_to_connect(void *data)
{
struct rtmp_common *service = data;
const char *key = rtmp_common_key(data);
if (service->service && strcmp(service->service, "Dacast") == 0)
return (key != NULL && key[0] != '\0');
const char *url = rtmp_common_url(data);
return (url != NULL && url[0] != '\0') &&
(key != NULL && key[0] != '\0');
}
struct obs_service_info rtmp_common_service = {
.id = "rtmp_common",
.get_name = rtmp_common_getname,
@@ -932,14 +1083,17 @@ struct obs_service_info rtmp_common_service = {
.destroy = rtmp_common_destroy,
.update = rtmp_common_update,
.get_properties = rtmp_common_properties,
.get_protocol = rtmp_common_get_protocol,
.get_url = rtmp_common_url,
.get_key = rtmp_common_key,
.get_username = rtmp_common_username,
.get_password = rtmp_common_password,
.get_connect_info = rtmp_common_get_connect_info,
.apply_encoder_settings = rtmp_common_apply_settings,
.get_output_type = rtmp_common_get_output_type,
.get_supported_resolutions = rtmp_common_get_supported_resolutions,
.get_max_fps = rtmp_common_get_max_fps,
.get_max_bitrate = rtmp_common_get_max_bitrate,
.get_supported_video_codecs = rtmp_common_get_supported_video_codecs,
.get_supported_audio_codecs = rtmp_common_get_supported_audio_codecs,
.can_try_to_connect = rtmp_common_can_try_to_connect,
};
+60
View File
@@ -110,6 +110,30 @@ static const char *rtmp_custom_password(void *data)
return service->password;
}
#define RTMPS_PREFIX "rtmps://"
#define FTL_PREFIX "ftl://"
#define SRT_PREFIX "srt://"
#define RIST_PREFIX "rist://"
static const char *rtmp_custom_get_protocol(void *data)
{
struct rtmp_custom *service = data;
if (strncmp(service->server, RTMPS_PREFIX, strlen(RTMPS_PREFIX)) == 0)
return "RTMPS";
if (strncmp(service->server, FTL_PREFIX, strlen(FTL_PREFIX)) == 0)
return "FTL";
if (strncmp(service->server, SRT_PREFIX, strlen(SRT_PREFIX)) == 0)
return "SRT";
if (strncmp(service->server, RIST_PREFIX, strlen(RIST_PREFIX)) == 0)
return "RIST";
return "RTMP";
}
#define RTMP_PROTOCOL "rtmp"
static void rtmp_custom_apply_settings(void *data, obs_data_t *video_settings,
@@ -124,6 +148,39 @@ static void rtmp_custom_apply_settings(void *data, obs_data_t *video_settings,
}
}
static const char *rtmp_custom_get_connect_info(void *data, uint32_t type)
{
switch ((enum obs_service_connect_info)type) {
case OBS_SERVICE_CONNECT_INFO_SERVER_URL:
return rtmp_custom_url(data);
case OBS_SERVICE_CONNECT_INFO_STREAM_ID:
return rtmp_custom_key(data);
case OBS_SERVICE_CONNECT_INFO_USERNAME:
return rtmp_custom_username(data);
case OBS_SERVICE_CONNECT_INFO_PASSWORD:
return rtmp_custom_password(data);
case OBS_SERVICE_CONNECT_INFO_ENCRYPT_PASSPHRASE: {
const char *protocol = rtmp_custom_get_protocol(data);
if ((strcmp(protocol, "SRT") == 0))
return rtmp_custom_password(data);
else if ((strcmp(protocol, "RIST") == 0))
return rtmp_custom_key(data);
break;
}
}
return NULL;
}
static bool rtmp_custom_can_try_to_connect(void *data)
{
struct rtmp_custom *service = data;
return (service->server != NULL && service->server[0] != '\0');
}
struct obs_service_info rtmp_custom_service = {
.id = "rtmp_custom",
.get_name = rtmp_custom_name,
@@ -131,9 +188,12 @@ struct obs_service_info rtmp_custom_service = {
.destroy = rtmp_custom_destroy,
.update = rtmp_custom_update,
.get_properties = rtmp_custom_properties,
.get_protocol = rtmp_custom_get_protocol,
.get_url = rtmp_custom_url,
.get_key = rtmp_custom_key,
.get_connect_info = rtmp_custom_get_connect_info,
.get_username = rtmp_custom_username,
.get_password = rtmp_custom_password,
.apply_encoder_settings = rtmp_custom_apply_settings,
.can_try_to_connect = rtmp_custom_can_try_to_connect,
};