Skip to content

Commit

Permalink
UI: Add low latency audio buffering mode to UI
Browse files Browse the repository at this point in the history
This feature is meant to reduce maximum audio buffering and turn off
dynamic buffering mode. This allows the lowest possible consistent
latency for audio buffering, which is useful for the decklink and NDI
outputs which cannot rely on audio timestamps for synchronization.

This can have a negative effect of making audio segments (partial or in
full) cut out. So audio glitching or audio loss can occur if this is
enabled.
  • Loading branch information
jp9000 committed Jul 20, 2022
1 parent 9025d92 commit 0a218e0
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 15 deletions.
5 changes: 5 additions & 0 deletions UI/data/locale/en-US.ini
Expand Up @@ -1044,6 +1044,11 @@ Basic.Settings.Audio.EnablePushToTalk="Enable Push-to-talk"
Basic.Settings.Audio.PushToTalkDelay="Push-to-talk delay"
Basic.Settings.Audio.UnknownAudioDevice="[Device not connected or not available]"
Basic.Settings.Audio.Disabled="Disabled"
Basic.Settings.Audio.LowLatencyBufferingMode="Low Latency Audio Buffering Mode (For Decklink/NDI outputs)"
Basic.Settings.Audio.LowLatencyBufferingWarning.Enabled="WARNING: Low latency audio buffering is enabled."
Basic.Settings.Audio.LowLatencyBufferingWarning="Low latency audio buffering mode may cause audio to glitch or stop playing from some sources."
Basic.Settings.Audio.LowLatencyBufferingWarning.Title="Enable low latency audio buffering mode?"
Basic.Settings.Audio.LowLatencyBufferingWarning.Confirm="Are you sure you want to enable low latency audio buffering mode?"

# basic mode 'advanced' settings
Basic.Settings.Advanced="Advanced"
Expand Down
33 changes: 26 additions & 7 deletions UI/forms/OBSBasicSettings.ui
Expand Up @@ -151,8 +151,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>806</width>
<height>1181</height>
<width>803</width>
<height>1226</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_19">
Expand Down Expand Up @@ -1379,7 +1379,7 @@
<x>0</x>
<y>0</y>
<width>820</width>
<height>679</height>
<height>676</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_21">
Expand Down Expand Up @@ -3972,7 +3972,7 @@
<x>0</x>
<y>0</y>
<width>820</width>
<height>641</height>
<height>632</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_50">
Expand Down Expand Up @@ -4372,6 +4372,13 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="lowLatencyBuffering">
<property name="text">
<string>Basic.Settings.Audio.LowLatencyBufferingMode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down Expand Up @@ -4904,8 +4911,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>609</width>
<height>870</height>
<width>713</width>
<height>923</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_23">
Expand Down Expand Up @@ -5925,7 +5932,7 @@
<tabstop>peakMeterType</tabstop>
<tabstop>monitoringDevice</tabstop>
<tabstop>disableAudioDucking</tabstop>
<tabstop>baseResolution</tabstop>
<tabstop>lowLatencyBuffering</tabstop>
<tabstop>outputResolution</tabstop>
<tabstop>downscaleFilter</tabstop>
<tabstop>fpsType</tabstop>
Expand Down Expand Up @@ -5960,6 +5967,18 @@
<tabstop>enableLowLatencyMode</tabstop>
<tabstop>browserHWAccel</tabstop>
<tabstop>hotkeyFocusType</tabstop>
<tabstop>hideOBSFromCapture</tabstop>
<tabstop>closeProjectors</tabstop>
<tabstop>moreInfoButton</tabstop>
<tabstop>ignoreRecommended</tabstop>
<tabstop>useStreamKeyAdv</tabstop>
<tabstop>baseResolution</tabstop>
<tabstop>hotkeyFilterSearch</tabstop>
<tabstop>hotkeyFilterInput</tabstop>
<tabstop>hotkeyFilterReset</tabstop>
<tabstop>hotkeyScrollArea</tabstop>
<tabstop>sdrWhiteLevel</tabstop>
<tabstop>hdrNominalPeakLevel</tabstop>
</tabstops>
<resources>
<include location="obs.qrc"/>
Expand Down
11 changes: 9 additions & 2 deletions UI/window-basic-main.cpp
Expand Up @@ -4409,7 +4409,7 @@ bool OBSBasic::ResetAudio()
{
ProfileScope("OBSBasic::ResetAudio");

struct obs_audio_info ai;
struct obs_audio_info2 ai = {};
ai.samples_per_sec =
config_get_uint(basicConfig, "Audio", "SampleRate");

Expand All @@ -4431,7 +4431,14 @@ bool OBSBasic::ResetAudio()
else
ai.speakers = SPEAKERS_STEREO;

return obs_reset_audio(&ai);
bool lowLatencyAudioBuffering = config_get_bool(
GetGlobalConfig(), "Audio", "LowLatencyAudioBuffering");
if (lowLatencyAudioBuffering) {
ai.max_buffering_ms = 20;
ai.fixed_buffering = true;
}

return obs_reset_audio2(&ai);
}

extern char *get_new_source_name(const char *name, const char *format);
Expand Down
78 changes: 72 additions & 6 deletions UI/window-basic-settings.cpp
Expand Up @@ -760,6 +760,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
SLOT(SurroundWarning(int)));
connect(ui->channelSetup, SIGNAL(currentIndexChanged(int)), this,
SLOT(SpeakerLayoutChanged(int)));
connect(ui->lowLatencyBuffering, SIGNAL(clicked(bool)), this,
SLOT(LowLatencyBufferingChanged(bool)));
connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)), this,
SLOT(SimpleRecordingQualityChanged()));
connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)), this,
Expand Down Expand Up @@ -926,6 +928,7 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)

channelIndex = ui->channelSetup->currentIndex();
sampleRateIndex = ui->sampleRate->currentIndex();
llBufferingEnabled = ui->lowLatencyBuffering->isChecked();

QRegularExpression rx("\\d{1,5}x\\d{1,5}");
QValidator *validator = new QRegularExpressionValidator(rx, this);
Expand All @@ -934,6 +937,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)

connect(ui->useStreamKeyAdv, SIGNAL(clicked()), this,
SLOT(UseStreamKeyAdvClicked()));

UpdateAudioWarnings();
}

OBSBasicSettings::~OBSBasicSettings()
Expand Down Expand Up @@ -2536,6 +2541,8 @@ void OBSBasicSettings::LoadAudioSettings()
config_get_double(main->Config(), "Audio", "MeterDecayRate");
uint32_t peakMeterTypeIdx =
config_get_uint(main->Config(), "Audio", "PeakMeterType");
bool enableLLAudioBuffering = config_get_bool(
GetGlobalConfig(), "Audio", "LowLatencyAudioBuffering");

loading = true;

Expand Down Expand Up @@ -2572,6 +2579,7 @@ void OBSBasicSettings::LoadAudioSettings()
ui->meterDecayRate->setCurrentIndex(0);

ui->peakMeterType->setCurrentIndex(peakMeterTypeIdx);
ui->lowLatencyBuffering->setChecked(enableLLAudioBuffering);

LoadAudioDevices();
LoadAudioSources();
Expand Down Expand Up @@ -3754,6 +3762,14 @@ void OBSBasicSettings::SaveAudioSettings()
main->UpdateVolumeControlsPeakMeterType();
}

if (WidgetChanged(ui->lowLatencyBuffering)) {
bool enableLLAudioBuffering =
ui->lowLatencyBuffering->isChecked();
config_set_bool(GetGlobalConfig(), "Audio",
"LowLatencyAudioBuffering",
enableLLAudioBuffering);
}

for (auto &audioSource : audioSources) {
auto source = OBSGetStrongRef(get<0>(audioSource));
if (!source)
Expand Down Expand Up @@ -4287,9 +4303,12 @@ void OBSBasicSettings::AudioChangedRestart()
if (!loading) {
int currentChannelIndex = ui->channelSetup->currentIndex();
int currentSampleRateIndex = ui->sampleRate->currentIndex();
bool currentLLAudioBufVal =
ui->lowLatencyBuffering->isChecked();

if (currentChannelIndex != channelIndex ||
currentSampleRateIndex != sampleRateIndex) {
currentSampleRateIndex != sampleRateIndex ||
currentLLAudioBufVal != llBufferingEnabled) {
audioChanged = true;
ui->audioMsg->setText(
QTStr("Basic.Settings.ProgramRestart"));
Expand Down Expand Up @@ -4318,13 +4337,9 @@ void OBSBasicSettings::SpeakerLayoutChanged(int idx)
bool surround = IsSurround(speakerLayout.c_str());

if (surround) {
QString warning = QTStr(MULTI_CHANNEL_WARNING ".Enabled") +
QStringLiteral("\n\n") +
QTStr(MULTI_CHANNEL_WARNING);
/*
* Display all bitrates
*/
ui->audioMsg_2->setText(warning);
PopulateAACBitrates(
{ui->simpleOutputABitrate, ui->advOutTrack1Bitrate,
ui->advOutTrack2Bitrate, ui->advOutTrack3Bitrate,
Expand All @@ -4335,7 +4350,6 @@ void OBSBasicSettings::SpeakerLayoutChanged(int idx)
* Reset audio bitrate for simple and adv mode, update list of
* bitrates and save setting.
*/
ui->audioMsg_2->setText(QString());
RestrictResetBitrates(
{ui->simpleOutputABitrate, ui->advOutTrack1Bitrate,
ui->advOutTrack2Bitrate, ui->advOutTrack3Bitrate,
Expand All @@ -4351,6 +4365,8 @@ void OBSBasicSettings::SpeakerLayoutChanged(int idx)
SaveCombo(ui->advOutTrack5Bitrate, "AdvOut", "Track5Bitrate");
SaveCombo(ui->advOutTrack6Bitrate, "AdvOut", "Track6Bitrate");
}

UpdateAudioWarnings();
}

void OBSBasicSettings::HideOBSWindowWarning(int state)
Expand Down Expand Up @@ -5257,6 +5273,56 @@ void OBSBasicSettings::SurroundWarning(int idx)
lastChannelSetupIdx = idx;
}

#define LL_BUFFERING_WARNING "Basic.Settings.Audio.LowLatencyBufferingWarning"

void OBSBasicSettings::UpdateAudioWarnings()
{
QString speakerLayoutQstr = ui->channelSetup->currentText();
bool surround = IsSurround(QT_TO_UTF8(speakerLayoutQstr));
bool lowBufferingActive = ui->lowLatencyBuffering->isChecked();

QString text;

if (surround) {
text = QTStr(MULTI_CHANNEL_WARNING ".Enabled") +
QStringLiteral("\n\n") + QTStr(MULTI_CHANNEL_WARNING);
}

if (lowBufferingActive) {
if (!text.isEmpty())
text += QStringLiteral("\n\n");

text += QTStr(LL_BUFFERING_WARNING ".Enabled") +
QStringLiteral("\n\n") + QTStr(LL_BUFFERING_WARNING);
}

ui->audioMsg_2->setText(text);
}

void OBSBasicSettings::LowLatencyBufferingChanged(bool checked)
{
if (checked) {
QString warningStr = QTStr(LL_BUFFERING_WARNING) +
QStringLiteral("\n\n") +
QTStr(LL_BUFFERING_WARNING ".Confirm");

auto button = OBSMessageBox::question(
this, QTStr(LL_BUFFERING_WARNING ".Title"), warningStr);

if (button == QMessageBox::No) {
QMetaObject::invokeMethod(ui->lowLatencyBuffering,
"setChecked",
Qt::QueuedConnection,
Q_ARG(bool, false));
return;
}
}

QMetaObject::invokeMethod(this, "UpdateAudioWarnings",
Qt::QueuedConnection);
QMetaObject::invokeMethod(this, "AudioChangedRestart");
}

void OBSBasicSettings::SimpleRecordingQualityLosslessWarning(int idx)
{
if (idx == lastSimpleRecQualityIdx || idx == -1)
Expand Down
3 changes: 3 additions & 0 deletions UI/window-basic-settings.hpp
Expand Up @@ -120,6 +120,7 @@ class OBSBasicSettings : public QDialog {
std::string savedTheme;
int sampleRateIndex = 0;
int channelIndex = 0;
bool llBufferingEnabled = false;

int lastSimpleRecQualityIdx = 0;
int lastServiceIdx = -1;
Expand Down Expand Up @@ -379,6 +380,8 @@ private slots:
void ReloadAudioSources();
void SurroundWarning(int idx);
void SpeakerLayoutChanged(int idx);
void LowLatencyBufferingChanged(bool checked);
void UpdateAudioWarnings();
void OutputsChanged();
void Stream1Changed();
void VideoChanged();
Expand Down

0 comments on commit 0a218e0

Please sign in to comment.