libobs: Add surround sound audio support

(This commit also modifies the following modules: UI,
deps/media-playback, coreaudio-encoder, decklink, linux-alsa,
linux-pulseaudio, mac-capture, obs-ffmpeg, obs-filters, obs-libfdk,
obs-outputs, win-dshow, and win-wasapi)

Adds surround sound audio support to the core, core plugins, and user
interface.

Compatible streaming services: Twitch, FB 360 live
Compatible protocols: rtmp / mpeg-ts tcp udp
Compatible file formats: mkv mp4 ts  (others untested)
Compatible codecs: ffmpeg aac, fdk_aac, CoreAudio aac,
		   opus, vorbis, pcm (others untested).
Tested streaming servers: wowza, nginx
	 HLS, mpeg-dash : surround passthrough
Html5 players tested with live surround:
	 videojs, mediaelement, viblast (hls+dash), hls.js
Decklink: on win32, swap channels order for 5.1 7.1
         (due to different channel mapping on wav, mpeg, ffmpeg)
Audio filters: surround working.
Monitoring: surround working (win macOs linux (pulse-audio)).
VST:	 stereo plugins keep in general only the first two channels.
	 surround plugins should work (e.g. mcfx does).
OS: win, macOs, linux (alsa, pulse-audio).
Misc: larger audio bitrates unlocked to accommodate more channels
NB: mf-aac only supports mono and stereo + 5.1 on win 10
         (not implemented due to lack of usefulness)

Closes jp9000/obs-studio#968
This commit is contained in:
pkviet
2017-11-26 03:41:53 -08:00
committed by jp9000
parent 54ecfc8fe7
commit bbac3280c1
30 changed files with 628 additions and 116 deletions
+8
View File
@@ -148,6 +148,10 @@ static void PopulateBitrateMap()
{
call_once(populateBitrateMap, []()
{
struct obs_audio_info aoi;
obs_get_audio_info(&aoi);
uint32_t output_channels = get_audio_channels(aoi.speakers);
HandleEncoderProperties(fallbackEncoder.c_str());
const char *id = nullptr;
@@ -174,6 +178,10 @@ static void PopulateBitrateMap()
if (aac_ != GetCodec(encoder.c_str()))
continue;
// disable mf_aac if audio ouput is not stereo nor mono
if ((output_channels >= 3) && (encoder == "mf_aac"))
continue;
HandleEncoderProperties(encoder.c_str());
}
+4
View File
@@ -683,6 +683,10 @@ Basic.Settings.Video.DownscaleFilter.Lanczos="Lanczos (Sharpened scaling, 32 sam
Basic.Settings.Audio="Audio"
Basic.Settings.Audio.SampleRate="Sample Rate"
Basic.Settings.Audio.Channels="Channels"
Basic.Settings.Audio.MultiChannelWarning.Enabled="WARNING: Surround sound audio is enabled."
Basic.Settings.Audio.MultichannelWarning="If streaming, check to see if your streaming service supports both surround sound ingest and surround sound playback. Twitch, Facebook 360 Live, Mixer RTMP, Smashcast are examples where surround sound is fully supported. Although Facebook Live and YouTube Live both accept surround ingest, Facebook Live downmixes to stereo, and YouTube Live plays only two channels.\n\nOBS audio filters are compatible with surround sound, though VST plugin support isn't guaranteed."
Basic.Settings.Audio.MultichannelWarning.Title="Enable surround sound audio?"
Basic.Settings.Audio.MultichannelWarning.Confirm="Are you sure you want to enable surround sound audio?"
Basic.Settings.Audio.DesktopDevice="Desktop Audio Device"
Basic.Settings.Audio.DesktopDevice2="Desktop Audio Device 2"
Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Audio Device"
+41 -3
View File
@@ -3180,6 +3180,31 @@
<string>Stereo</string>
</property>
</item>
<item>
<property name="text">
<string>2.1</string>
</property>
</item>
<item>
<property name="text">
<string>4.0 Quad</string>
</property>
</item>
<item>
<property name="text">
<string>4.1</string>
</property>
</item>
<item>
<property name="text">
<string>5.1</string>
</property>
</item>
<item>
<property name="text">
<string>7.1</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
@@ -3309,6 +3334,19 @@
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="audioMsg_2">
<property name="text">
<string notr="true"/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="themeID" stdset="0">
<string notr="true">warning</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="videoPage">
@@ -3613,8 +3651,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>98</width>
<height>28</height>
<width>818</width>
<height>697</height>
</rect>
</property>
<layout class="QFormLayout" name="hotkeyLayout">
@@ -3659,7 +3697,7 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-62</y>
<width>803</width>
<height>761</height>
</rect>
+10
View File
@@ -3039,6 +3039,16 @@ bool OBSBasic::ResetAudio()
if (strcmp(channelSetupStr, "Mono") == 0)
ai.speakers = SPEAKERS_MONO;
else if (strcmp(channelSetupStr, "2.1") == 0)
ai.speakers = SPEAKERS_2POINT1;
else if (strcmp(channelSetupStr, "4.0 Quad") == 0)
ai.speakers = SPEAKERS_QUAD;
else if (strcmp(channelSetupStr, "4.1") == 0)
ai.speakers = SPEAKERS_4POINT1;
else if (strcmp(channelSetupStr, "5.1") == 0)
ai.speakers = SPEAKERS_5POINT1;
else if (strcmp(channelSetupStr, "7.1") == 0)
ai.speakers = SPEAKERS_7POINT1;
else
ai.speakers = SPEAKERS_STEREO;
+211 -1
View File
@@ -243,6 +243,9 @@ static void PopulateAACBitrates(initializer_list<QComboBox*> boxes)
}
}
void RestrictResetBitrates(initializer_list<QComboBox*> boxes,
int maxbitrate);
void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal,
const char *slot)
{
@@ -256,6 +259,7 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal,
#define CHECK_CHANGED SIGNAL(clicked(bool))
#define SCROLL_CHANGED SIGNAL(valueChanged(int))
#define DSCROLL_CHANGED SIGNAL(valueChanged(double))
#define TOGGLE_CHANGED SIGNAL(toggled(bool))
#define GENERAL_CHANGED SLOT(GeneralChanged())
#define STREAM1_CHANGED SLOT(Stream1Changed())
@@ -587,6 +591,10 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
FillAudioMonitoringDevices();
#endif
connect(ui->channelSetup, SIGNAL(currentIndexChanged(int)),
this, SLOT(SurroundWarning(int)));
connect(ui->channelSetup, SIGNAL(currentIndexChanged(int)),
this, SLOT(SpeakerLayoutChanged(int)));
connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)),
this, SLOT(SimpleRecordingQualityChanged()));
connect(ui->simpleOutRecQuality, SIGNAL(currentIndexChanged(int)),
@@ -1368,6 +1376,30 @@ void OBSBasicSettings::LoadVideoSettings()
loading = false;
}
static inline bool IsSurround(const char *speakers)
{
static const char *surroundLayouts[] = {
"2.1",
"4.0 Quad",
"4.1",
"5.1",
"7.1",
nullptr
};
if (!speakers || !*speakers)
return false;
const char **curLayout = surroundLayouts;
for (; *curLayout; ++curLayout) {
if (strcmp(*curLayout, speakers) == 0) {
return true;
}
}
return false;
}
void OBSBasicSettings::LoadSimpleOutputSettings()
{
const char *path = config_get_string(main->Config(), "SimpleOutput",
@@ -1423,6 +1455,13 @@ void OBSBasicSettings::LoadSimpleOutputSettings()
int idx = ui->simpleOutRecFormat->findText(format);
ui->simpleOutRecFormat->setCurrentIndex(idx);
const char *speakers = config_get_string(main->Config(), "Audio",
"ChannelSetup");
// restrict list of bitrates when multichannel is OFF
if (!IsSurround(speakers))
RestrictResetBitrates({ui->simpleOutputABitrate}, 320);
SetComboByName(ui->simpleOutputABitrate,
std::to_string(audioBitrate).c_str());
@@ -1741,6 +1780,20 @@ void OBSBasicSettings::LoadAdvOutputAudioSettings()
track5Bitrate = FindClosestAvailableAACBitrate(track5Bitrate);
track6Bitrate = FindClosestAvailableAACBitrate(track6Bitrate);
// restrict list of bitrates when multichannel is OFF
const char *speakers = config_get_string(main->Config(), "Audio",
"ChannelSetup");
// restrict list of bitrates when multichannel is OFF
if (!IsSurround(speakers)) {
RestrictResetBitrates({ui->advOutTrack1Bitrate,
ui->advOutTrack2Bitrate,
ui->advOutTrack3Bitrate,
ui->advOutTrack4Bitrate,
ui->advOutTrack5Bitrate,
ui->advOutTrack6Bitrate}, 320);
}
SetComboByName(ui->advOutTrack1Bitrate,
std::to_string(track1Bitrate).c_str());
SetComboByName(ui->advOutTrack2Bitrate,
@@ -2044,6 +2097,16 @@ void OBSBasicSettings::LoadAudioSettings()
if (strcmp(speakers, "Mono") == 0)
ui->channelSetup->setCurrentIndex(0);
else if (strcmp(speakers, "2.1") == 0)
ui->channelSetup->setCurrentIndex(2);
else if (strcmp(speakers, "4.0 Quad") == 0)
ui->channelSetup->setCurrentIndex(3);
else if (strcmp(speakers, "4.1") == 0)
ui->channelSetup->setCurrentIndex(4);
else if (strcmp(speakers, "5.1") == 0)
ui->channelSetup->setCurrentIndex(5);
else if (strcmp(speakers, "7.1") == 0)
ui->channelSetup->setCurrentIndex(6);
else
ui->channelSetup->setCurrentIndex(1);
@@ -2918,7 +2981,34 @@ void OBSBasicSettings::SaveAudioSettings()
QString sampleRateStr = ui->sampleRate->currentText();
int channelSetupIdx = ui->channelSetup->currentIndex();
const char *channelSetup = (channelSetupIdx == 0) ? "Mono" : "Stereo";
const char *channelSetup;
switch (channelSetupIdx) {
case 0:
channelSetup = "Mono";
break;
case 1:
channelSetup = "Stereo";
break;
case 2:
channelSetup = "2.1";
break;
case 3:
channelSetup = "4.0 Quad";
break;
case 4:
channelSetup = "4.1";
break;
case 5:
channelSetup = "5.1";
break;
case 6:
channelSetup = "7.1";
break;
default:
channelSetup = "Stereo";
break;
}
int sampleRate = 44100;
if (sampleRateStr == "48khz")
@@ -3436,6 +3526,85 @@ void OBSBasicSettings::ReloadAudioSources()
LoadAudioSources();
}
#define MULTI_CHANNEL_WARNING "Basic.Settings.Audio.MultichannelWarning"
void OBSBasicSettings::SpeakerLayoutChanged(int idx)
{
QString speakerLayoutQstr = ui->channelSetup->itemText(idx);
std::string speakerLayout = QT_TO_UTF8(speakerLayoutQstr);
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,
ui->advOutTrack4Bitrate,
ui->advOutTrack5Bitrate,
ui->advOutTrack6Bitrate});
} else {
/*
* 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,
ui->advOutTrack4Bitrate,
ui->advOutTrack5Bitrate,
ui->advOutTrack6Bitrate}, 320);
SaveCombo(ui->simpleOutputABitrate, "SimpleOutput", "ABitrate");
SaveCombo(ui->advOutTrack1Bitrate, "AdvOut", "Track1Bitrate");
SaveCombo(ui->advOutTrack2Bitrate, "AdvOut", "Track2Bitrate");
SaveCombo(ui->advOutTrack3Bitrate, "AdvOut", "Track3Bitrate");
SaveCombo(ui->advOutTrack4Bitrate, "AdvOut", "Track4Bitrate");
SaveCombo(ui->advOutTrack5Bitrate, "AdvOut", "Track5Bitrate");
SaveCombo(ui->advOutTrack6Bitrate, "AdvOut", "Track6Bitrate");
}
}
/*
* resets current bitrate if too large and restricts the number of bitrates
* displayed when multichannel OFF
*/
void RestrictResetBitrates(initializer_list<QComboBox*> boxes, int maxbitrate)
{
for (auto box : boxes) {
int idx = box->currentIndex();
int max_bitrate = FindClosestAvailableAACBitrate(maxbitrate);
int count = box->count();
int max_idx = box->findText(QT_UTF8(std::to_string
(max_bitrate).c_str()));
for (int i = (count - 1); i > max_idx; i--)
box->removeItem(i);
if (idx > max_idx) {
int default_bitrate = FindClosestAvailableAACBitrate(
maxbitrate / 2);
int default_idx = box->findText(QT_UTF8(std::to_string
(default_bitrate).c_str()));
box->setCurrentIndex(default_idx);
box->setProperty("changed", QVariant(true));
} else {
box->setCurrentIndex(idx);
}
}
}
void OBSBasicSettings::VideoChangedRestart()
{
if (!loading) {
@@ -4007,6 +4176,47 @@ void OBSBasicSettings::SimpleRecordingEncoderChanged()
ui->simpleOutInfoLayout->addWidget(simpleOutRecWarning);
}
void OBSBasicSettings::SurroundWarning(int idx)
{
if (idx == lastChannelSetupIdx || idx == -1)
return;
if (loading) {
lastChannelSetupIdx = idx;
return;
}
QString speakerLayoutQstr = ui->channelSetup->itemText(idx);
bool surround = IsSurround(QT_TO_UTF8(speakerLayoutQstr));
QString lastQstr = ui->channelSetup->itemText(lastChannelSetupIdx);
bool wasSurround = IsSurround(QT_TO_UTF8(lastQstr));
if (surround && !wasSurround) {
QMessageBox::StandardButton button;
QString warningString =
QTStr("Basic.Settings.ProgramRestart") +
QStringLiteral("\n\n") +
QTStr(MULTI_CHANNEL_WARNING) +
QStringLiteral("\n\n") +
QTStr(MULTI_CHANNEL_WARNING ".Confirm");
button = OBSMessageBox::question(this,
QTStr(MULTI_CHANNEL_WARNING ".Title"),
warningString);
if (button == QMessageBox::No) {
QMetaObject::invokeMethod(ui->channelSetup,
"setCurrentIndex", Qt::QueuedConnection,
Q_ARG(int, lastChannelSetupIdx));
return;
}
}
lastChannelSetupIdx = idx;
}
void OBSBasicSettings::SimpleRecordingQualityLosslessWarning(int idx)
{
if (idx == lastSimpleRecQualityIdx || idx == -1)
+3
View File
@@ -98,6 +98,7 @@ private:
std::string savedTheme;
int lastSimpleRecQualityIdx = 0;
int lastChannelSetupIdx = 0;
OBSFFFormatDesc formats;
@@ -274,6 +275,8 @@ private slots:
void AudioChanged();
void AudioChangedRestart();
void ReloadAudioSources();
void SurroundWarning(int idx);
void SpeakerLayoutChanged(int idx);
void OutputsChanged();
void Stream1Changed();
void VideoChanged();