提交 592cdfb0 编写于 作者: J jp9000

UI: Add service res/fps limitation support to settings

Allows services to limit and enforce resolution and framerate values the
user can select in the UI if "ignore service recommendations" is not
checked. If the "ignore service recommendations" option is not checked,
the user will not be able to select or use a resolution and/or framerate
in the user interface that the service does not support. If "ignore
service recommendations" is checked, it will work as it normally would,
allowing any value to be used as per normal.

Fortunately, and hopefully for the foreseeable future, there is only one
service that enforces resolutions and framerates.
上级 ebbe8d1b
......@@ -712,6 +712,8 @@ Basic.Settings.Stream.IgnoreRecommended.Warn.Title="Override Recommended Setting
Basic.Settings.Stream.IgnoreRecommended.Warn.Text="Warning: Ignoring the service's limitations may result in degraded stream quality or prevent you from streaming.\n\nContinue?"
Basic.Settings.Stream.Recommended.MaxVideoBitrate="Maximum Video Bitrate: %1 kbps"
Basic.Settings.Stream.Recommended.MaxAudioBitrate="Maximum Audio Bitrate: %1 kbps"
Basic.Settings.Stream.Recommended.MaxResolution="Maximum Resolution: %1"
Basic.Settings.Stream.Recommended.MaxFPS="Maximum FPS: %1"
# basic mode 'output' settings
Basic.Settings.Output="Output"
......@@ -751,6 +753,10 @@ Basic.Settings.Output.Simple.Encoder.Hardware.QSV="Hardware (QSV)"
Basic.Settings.Output.Simple.Encoder.Hardware.AMD="Hardware (AMD)"
Basic.Settings.Output.Simple.Encoder.Hardware.NVENC="Hardware (NVENC)"
Basic.Settings.Output.Simple.Encoder.SoftwareLowCPU="Software (x264 low CPU usage preset, increases file size)"
Basic.Settings.Output.Warn.EnforceResolutionFPS.Title="Incompatible Resolution/Framerate"
Basic.Settings.Output.Warn.EnforceResolutionFPS.Msg="This streaming service does not support your current output resolution and/or framerate. They will be changed to the closest compatible value:\n\n%1\n\nDo you want to continue?"
Basic.Settings.Output.Warn.EnforceResolutionFPS.Resolution="Resolution: %1"
Basic.Settings.Output.Warn.EnforceResolutionFPS.FPS="FPS: %1"
Basic.Settings.Output.VideoBitrate="Video Bitrate"
Basic.Settings.Output.AudioBitrate="Audio Bitrate"
Basic.Settings.Output.Reconnect="Automatically Reconnect"
......
......@@ -4304,7 +4304,7 @@
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_10">
<widget class="QLabel" name="outputResLabel">
<property name="text">
<string>Basic.Settings.Video.ScaledResolution</string>
</property>
......@@ -4552,7 +4552,7 @@
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_28">
<layout class="QHBoxLayout" name="outputResLayout">
<property name="spacing">
<number>6</number>
</property>
......@@ -4592,8 +4592,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>28</height>
<width>818</width>
<height>675</height>
</rect>
</property>
<layout class="QFormLayout" name="hotkeyLayout">
......@@ -4639,7 +4639,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>599</width>
<width>803</width>
<height>781</height>
</rect>
</property>
......
......@@ -75,10 +75,14 @@ void OBSBasicSettings::InitStreamPage()
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->customServer, SIGNAL(editingFinished(const QString &)),
this, SLOT(UpdateKeyLink()));
connect(ui->service, SIGNAL(currentIndexChanged(int)), this,
......@@ -159,6 +163,9 @@ void OBSBasicSettings::LoadStream1Settings()
ui->ignoreRecommended->setChecked(ignoreRecommended);
loading = false;
QMetaObject::invokeMethod(this, "UpdateResFPSLimits",
Qt::QueuedConnection);
}
void OBSBasicSettings::SaveStream1Settings()
......@@ -725,3 +732,263 @@ void OBSBasicSettings::DisplayEnforceWarning(bool checked)
SimpleRecordingEncoderChanged();
}
bool OBSBasicSettings::ResFPSValid(obs_service_resolution *res_list,
size_t res_count, int max_fps)
{
if (!res_count && !max_fps)
return true;
if (res_count) {
QString res = ui->outputResolution->currentText();
bool found_res = false;
int cx, cy;
if (sscanf(QT_TO_UTF8(res), "%dx%d", &cx, &cy) != 2)
return false;
for (size_t i = 0; i < res_count; i++) {
if (res_list[i].cx == cx && res_list[i].cy == cy) {
found_res = true;
break;
}
}
if (!found_res)
return false;
}
if (max_fps) {
int fpsType = ui->fpsType->currentIndex();
if (fpsType != 0)
return false;
std::string fps_str = QT_TO_UTF8(ui->fpsCommon->currentText());
float fps;
sscanf(fps_str.c_str(), "%f", &fps);
if (fps > (float)max_fps)
return false;
}
return true;
}
extern void set_closest_res(int &cx, int &cy,
struct obs_service_resolution *res_list,
size_t count);
/* Checks for and updates the resolution and FPS limits of a service, if any.
*
* If the service has a resolution and/or FPS limit, this will enforce those
* limitations in the UI itself, preventing the user from selecting a
* resolution or FPS that's not supported.
*
* This is an unpleasant thing to have to do to users, but there is no other
* way to ensure that a service's restricted resolution/framerate values are
* properly enforced, otherwise users will just be confused when things aren't
* working correctly. The user can turn it off if they're partner (or if they
* want to risk getting in trouble with their service) by selecting the "Ignore
* recommended settings" option in the stream section of settings.
*
* This only affects services that have a resolution and/or framerate limit, of
* which as of this writing, and hopefully for the foreseeable future, there is
* only one.
*/
void OBSBasicSettings::UpdateResFPSLimits()
{
if (loading)
return;
int idx = ui->service->currentIndex();
if (idx == -1)
return;
bool ignoreRecommended = ui->ignoreRecommended->isChecked();
BPtr<obs_service_resolution> res_list;
size_t res_count = 0;
int max_fps = 0;
if (!IsCustomService() && !ignoreRecommended) {
OBSService service = GetStream1Service();
obs_service_get_supported_resolutions(service, &res_list,
&res_count);
obs_service_get_max_fps(service, &max_fps);
}
/* ------------------------------------ */
/* Check for enforced res/FPS */
QString res = ui->outputResolution->currentText();
QString fps_str;
int cx = 0, cy = 0;
double max_fpsd = (double)max_fps;
int closest_fps_index = -1;
double fpsd;
sscanf(QT_TO_UTF8(res), "%dx%d", &cx, &cy);
if (res_count)
set_closest_res(cx, cy, res_list, res_count);
if (max_fps) {
int fpsType = ui->fpsType->currentIndex();
if (fpsType == 1) { //Integer
fpsd = (double)ui->fpsInteger->value();
} else if (fpsType == 2) { //Fractional
fpsd = (double)ui->fpsNumerator->value() /
(double)ui->fpsDenominator->value();
} else { //Common
sscanf(QT_TO_UTF8(ui->fpsCommon->currentText()), "%lf",
&fpsd);
}
double closest_diff = 1000000000000.0;
for (int i = 0; i < ui->fpsCommon->count(); i++) {
double com_fpsd;
sscanf(QT_TO_UTF8(ui->fpsCommon->itemText(i)), "%lf",
&com_fpsd);
if (com_fpsd > max_fpsd) {
continue;
}
double diff = fabs(com_fpsd - fpsd);
if (diff < closest_diff) {
closest_diff = diff;
closest_fps_index = i;
fps_str = ui->fpsCommon->itemText(i);
}
}
}
QString res_str =
QString("%1x%2").arg(QString::number(cx), QString::number(cy));
/* ------------------------------------ */
/* Display message box if res/FPS bad */
bool valid = ResFPSValid(res_list, res_count, max_fps);
if (!valid) {
/* if the user was already on facebook with an incompatible
* resolution, assume it's an upgrade */
if (lastServiceIdx == -1 && lastIgnoreRecommended == -1) {
ui->ignoreRecommended->setChecked(true);
ui->ignoreRecommended->setProperty("changed", true);
stream1Changed = true;
EnableApplyButton(true);
UpdateResFPSLimits();
return;
}
QMessageBox::StandardButton button;
#define WARNING_VAL(x) \
QTStr("Basic.Settings.Output.Warn.EnforceResolutionFPS." x)
QString str;
if (res_count)
str += WARNING_VAL("Resolution").arg(res_str);
if (max_fps) {
if (!str.isEmpty())
str += "\n";
str += WARNING_VAL("FPS").arg(fps_str);
}
button = OBSMessageBox::question(this, WARNING_VAL("Title"),
WARNING_VAL("Msg").arg(str));
#undef WARNING_VAL
if (button == QMessageBox::No) {
if (idx != lastServiceIdx)
QMetaObject::invokeMethod(
ui->service, "setCurrentIndex",
Qt::QueuedConnection,
Q_ARG(int, lastServiceIdx));
else
QMetaObject::invokeMethod(ui->ignoreRecommended,
"setChecked",
Qt::QueuedConnection,
Q_ARG(bool, true));
return;
}
}
/* ------------------------------------ */
/* Update widgets/values if switching */
/* to/from enforced resolution/FPS */
ui->outputResolution->blockSignals(true);
if (res_count) {
ui->outputResolution->clear();
ui->outputResolution->setEditable(false);
int new_res_index = -1;
for (size_t i = 0; i < res_count; i++) {
obs_service_resolution val = res_list[i];
QString str =
QString("%1x%2").arg(QString::number(val.cx),
QString::number(val.cy));
ui->outputResolution->addItem(str);
if (val.cx == cx && val.cy == cy)
new_res_index = (int)i;
}
ui->outputResolution->setCurrentIndex(new_res_index);
if (!valid) {
ui->outputResolution->setProperty("changed", true);
videoChanged = true;
EnableApplyButton(true);
}
} else {
QString baseRes = ui->baseResolution->currentText();
int baseCX, baseCY;
sscanf(QT_TO_UTF8(baseRes), "%dx%d", &baseCX, &baseCY);
if (!ui->outputResolution->isEditable()) {
RecreateOutputResolutionWidget();
ui->outputResolution->blockSignals(true);
ResetDownscales((uint32_t)baseCX, (uint32_t)baseCY,
true);
ui->outputResolution->setCurrentText(res);
}
}
ui->outputResolution->blockSignals(false);
if (max_fps) {
for (int i = 0; i < ui->fpsCommon->count(); i++) {
double com_fpsd;
sscanf(QT_TO_UTF8(ui->fpsCommon->itemText(i)), "%lf",
&com_fpsd);
if (com_fpsd > max_fpsd) {
SetComboItemEnabled(ui->fpsCommon, i, false);
continue;
}
}
ui->fpsType->setCurrentIndex(0);
ui->fpsCommon->setCurrentIndex(closest_fps_index);
if (!valid) {
ui->fpsType->setProperty("changed", true);
ui->fpsCommon->setProperty("changed", true);
videoChanged = true;
EnableApplyButton(true);
}
} else {
for (int i = 0; i < ui->fpsCommon->count(); i++)
SetComboItemEnabled(ui->fpsCommon, i, true);
}
SetComboItemEnabled(ui->fpsType, 1, !max_fps);
SetComboItemEnabled(ui->fpsType, 2, !max_fps);
/* ------------------------------------ */
lastIgnoreRecommended = (int)ignoreRecommended;
lastServiceIdx = idx;
}
......@@ -1365,14 +1365,17 @@ void OBSBasicSettings::ResetDownscales(uint32_t cx, uint32_t cy,
advRecRescale = ui->advOutRecRescale->lineEdit()->text();
advFFRescale = ui->advOutFFRescale->lineEdit()->text();
ui->outputResolution->blockSignals(true);
bool lockedOutputRes = !ui->outputResolution->isEditable();
if (!lockedOutputRes) {
ui->outputResolution->blockSignals(true);
ui->outputResolution->clear();
}
if (ignoreAllSignals) {
ui->advOutRescale->blockSignals(true);
ui->advOutRecRescale->blockSignals(true);
ui->advOutFFRescale->blockSignals(true);
}
ui->outputResolution->clear();
ui->advOutRescale->clear();
ui->advOutRecRescale->clear();
ui->advOutFFRescale->clear();
......@@ -1399,7 +1402,8 @@ void OBSBasicSettings::ResetDownscales(uint32_t cx, uint32_t cy,
string res = ResString(downscaleCX, downscaleCY);
string outRes = ResString(outDownscaleCX, outDownscaleCY);
ui->outputResolution->addItem(res.c_str());
if (!lockedOutputRes)
ui->outputResolution->addItem(res.c_str());
ui->advOutRescale->addItem(outRes.c_str());
ui->advOutRecRescale->addItem(outRes.c_str());
ui->advOutFFRescale->addItem(outRes.c_str());
......@@ -1418,23 +1422,27 @@ void OBSBasicSettings::ResetDownscales(uint32_t cx, uint32_t cy,
string res = ResString(cx, cy);
float baseAspect = float(cx) / float(cy);
float outputAspect = float(out_cx) / float(out_cy);
if (!lockedOutputRes) {
float baseAspect = float(cx) / float(cy);
float outputAspect = float(out_cx) / float(out_cy);
bool closeAspect = close_float(baseAspect, outputAspect, 0.01f);
bool closeAspect = close_float(baseAspect, outputAspect, 0.01f);
if (closeAspect) {
ui->outputResolution->lineEdit()->setText(oldOutputRes);
on_outputResolution_editTextChanged(oldOutputRes);
} else {
ui->outputResolution->lineEdit()->setText(bestScale.c_str());
on_outputResolution_editTextChanged(bestScale.c_str());
}
if (closeAspect) {
ui->outputResolution->lineEdit()->setText(oldOutputRes);
on_outputResolution_editTextChanged(oldOutputRes);
} else {
ui->outputResolution->lineEdit()->setText(
bestScale.c_str());
on_outputResolution_editTextChanged(bestScale.c_str());
}
ui->outputResolution->blockSignals(false);
ui->outputResolution->blockSignals(false);
if (!closeAspect) {
ui->outputResolution->setProperty("changed", QVariant(true));
videoChanged = true;
if (!closeAspect) {
ui->outputResolution->setProperty("changed",
QVariant(true));
videoChanged = true;
}
}
if (advRescale.isEmpty())
......@@ -3884,16 +3892,22 @@ void OBSBasicSettings::on_colorFormat_currentIndexChanged(const QString &text)
static bool ValidResolutions(Ui::OBSBasicSettings *ui)
{
QString baseRes = ui->baseResolution->lineEdit()->text();
QString outputRes = ui->outputResolution->lineEdit()->text();
uint32_t cx, cy;
if (!ConvertResText(QT_TO_UTF8(baseRes), cx, cy) ||
!ConvertResText(QT_TO_UTF8(outputRes), cx, cy)) {
if (!ConvertResText(QT_TO_UTF8(baseRes), cx, cy)) {
ui->videoMsg->setText(QTStr(INVALID_RES_STR));
return false;
}
bool lockedOutRes = !ui->outputResolution->isEditable();
if (!lockedOutRes) {
QString outRes = ui->outputResolution->lineEdit()->text();
if (!ConvertResText(QT_TO_UTF8(outRes), cx, cy)) {
ui->videoMsg->setText(QTStr(INVALID_RES_STR));
return false;
}
}
ui->videoMsg->setText("");
return true;
}
......@@ -4865,3 +4879,31 @@ int OBSBasicSettings::CurrentFLVTrack()
return 0;
}
/* Using setEditable(true) on a QComboBox when there's a custom style in use
* does not work properly, so instead completely recreate the widget, which
* seems to work fine. */
void OBSBasicSettings::RecreateOutputResolutionWidget()
{
QSizePolicy sizePolicy = ui->outputResolution->sizePolicy();
delete ui->outputResolution;
ui->outputResolution = new QComboBox(ui->videoPage);
ui->outputResolution->setObjectName(
QString::fromUtf8("outputResolution"));
ui->outputResolution->setSizePolicy(sizePolicy);
ui->outputResolution->setEditable(true);
ui->outputResLabel->setBuddy(ui->outputResolution);
ui->outputResLayout->insertWidget(0, ui->outputResolution);
QWidget::setTabOrder(ui->baseResolution, ui->outputResolution);
QWidget::setTabOrder(ui->outputResolution, ui->downscaleFilter);
HookWidget(ui->outputResolution, CBEDIT_CHANGED, VIDEO_RES);
connect(ui->outputResolution, &QComboBox::editTextChanged, this,
&OBSBasicSettings::on_outputResolution_editTextChanged);
ui->outputResolution->lineEdit()->setValidator(
ui->baseResolution->lineEdit()->validator());
}
......@@ -122,6 +122,8 @@ private:
int channelIndex = 0;
int lastSimpleRecQualityIdx = 0;
int lastServiceIdx = -1;
int lastIgnoreRecommended = -1;
int lastChannelSetupIdx = 0;
OBSFFFormatDesc formats;
......@@ -175,6 +177,11 @@ private:
void SaveEncoder(QComboBox *combo, const char *section,
const char *value);
bool ResFPSValid(obs_service_resolution *res_list, size_t res_count,
int max_fps);
void ClosestResFPS(obs_service_resolution *res_list, size_t res_count,
int max_fps, int &new_cx, int &new_cy, int &new_fps);
inline bool Changed() const
{
return generalChanged || outputsChanged || stream1Changed ||
......@@ -246,6 +253,8 @@ private slots:
void UpdateKeyLink();
void UpdateVodTrackSetting();
void UpdateServiceRecommendations();
void RecreateOutputResolutionWidget();
void UpdateResFPSLimits();
void UpdateMoreInfoLink();
void DisplayEnforceWarning(bool checked);
void on_show_clicked();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册