mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-09-19 01:36:12 +08:00
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:
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user