mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-08-24 10:14:13 -05:00
Merge pull request #8095 from tytan652/service_get_info
RFC 45-6: Enable more connection info in Services API
This commit is contained in:
+206
-26
@@ -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
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
-------------------------
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
.. ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -411,6 +411,8 @@ struct obs_core_data {
|
||||
obs_data_t *private_data;
|
||||
|
||||
volatile bool valid;
|
||||
|
||||
DARRAY(char *) protocols;
|
||||
};
|
||||
|
||||
/* user hotkeys */
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,3 +1,3 @@
|
||||
LibFDK="libfdk AAC Encoder"
|
||||
LibFDK="libfdk AAC"
|
||||
Bitrate="Bitrate"
|
||||
Afterburner="Enable AAC Afterburner"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user