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-05-27 02:15:54 +02:00
committed by jp9000
parent 54ecfc8fe7
commit bbac3280c1
30 changed files with 628 additions and 116 deletions

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)