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:
@@ -21,52 +21,48 @@ int check_buffer(struct audio_repack *repack,
|
||||
}
|
||||
|
||||
/*
|
||||
Swap channel between LFE and FC, and
|
||||
squash data array
|
||||
|
||||
| FL | FR |LFE | FC | BL | BR |emp |emp |
|
||||
| | x | |
|
||||
| FL | FR | FC |LFE | BL | BR |
|
||||
* Swap channel 3 & 4, 5 & 7, 6 & 8 and squash arrays
|
||||
* 4.0 (quad):
|
||||
*
|
||||
* | FL | FR | BR | BL | emp | emp |emp |emp |
|
||||
* | | x |
|
||||
* | FL | FR | BL | BC |
|
||||
*
|
||||
* 4.1:
|
||||
*
|
||||
* | FL | FR |LFE | FC | BC | emp |emp |emp |
|
||||
* | | x | |
|
||||
* | FL | FR | FC |LFE | BC |
|
||||
*
|
||||
* 5.1:
|
||||
*
|
||||
* | FL | FR |LFE | FC |(emp|emp)|(BL|BR)|
|
||||
* | | x x
|
||||
* | FL | FR | FC |LFE | BL | BR |
|
||||
*
|
||||
* 7.1:
|
||||
*
|
||||
* | FL | FR |LFE | FC |( SL | SR )|(BL |BR )|
|
||||
* | | x X
|
||||
* | FL | FR | FC |LFE |( BL | BR )|(SL |SR )|
|
||||
*/
|
||||
int repack_8to6ch_swap23(struct audio_repack *repack,
|
||||
|
||||
int repack_squash_swap(struct audio_repack *repack,
|
||||
const uint8_t *bsrc, uint32_t frame_count)
|
||||
{
|
||||
if (check_buffer(repack, frame_count) < 0)
|
||||
return -1;
|
||||
|
||||
int squash = repack->extra_dst_size;
|
||||
const __m128i *src = (__m128i *)bsrc;
|
||||
const __m128i *esrc = src + frame_count;
|
||||
uint32_t *dst = (uint32_t *)repack->packet_buffer;
|
||||
uint16_t *dst = (uint16_t *)repack->packet_buffer;
|
||||
while (src != esrc) {
|
||||
__m128i target = _mm_load_si128(src++);
|
||||
__m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0));
|
||||
_mm_storeu_si128((__m128i *)dst, buf);
|
||||
dst += 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Swap channel between LFE and FC
|
||||
|
||||
| FL | FR |LFE | FC | BL | BR |SBL |SBR |
|
||||
| | x | | | |
|
||||
| FL | FR | FC |LFE | BL | BR |SBL |SBR |
|
||||
*/
|
||||
int repack_8ch_swap23(struct audio_repack *repack,
|
||||
const uint8_t *bsrc, uint32_t frame_count)
|
||||
{
|
||||
if (check_buffer(repack, frame_count) < 0)
|
||||
return -1;
|
||||
|
||||
const __m128i *src = (__m128i *)bsrc;
|
||||
const __m128i *esrc = src + frame_count;
|
||||
__m128i *dst = (__m128i *)repack->packet_buffer;
|
||||
while (src != esrc) {
|
||||
__m128i target = _mm_load_si128(src++);
|
||||
__m128i buf = _mm_shufflelo_epi16(target, _MM_SHUFFLE(2, 3, 1, 0));
|
||||
_mm_store_si128(dst++, buf);
|
||||
__m128i buf = _mm_shufflelo_epi16(target,_MM_SHUFFLE(2, 3, 1, 0));
|
||||
__m128i buf2 = _mm_shufflehi_epi16(buf, _MM_SHUFFLE(1, 0, 3, 2));
|
||||
_mm_storeu_si128((__m128i *)dst, buf2);
|
||||
dst += 8 - squash;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -81,18 +77,32 @@ int audio_repack_init(struct audio_repack *repack,
|
||||
return -1;
|
||||
|
||||
switch (repack_mode) {
|
||||
case repack_mode_8to6ch_swap23:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 6 * (16 / 8);
|
||||
repack->extra_dst_size = 2;
|
||||
repack->repack_func = &repack_8to6ch_swap23;
|
||||
case repack_mode_8to4ch_swap23:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 4 * (16 / 8);
|
||||
repack->extra_dst_size = 4;
|
||||
repack->repack_func = &repack_squash_swap;
|
||||
break;
|
||||
|
||||
case repack_mode_8ch_swap23:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 8 * (16 / 8);
|
||||
case repack_mode_8to5ch_swap23:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 5 * (16 / 8);
|
||||
repack->extra_dst_size = 3;
|
||||
repack->repack_func = &repack_squash_swap;
|
||||
break;
|
||||
|
||||
case repack_mode_8to6ch_swap23:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 6 * (16 / 8);
|
||||
repack->extra_dst_size = 2;
|
||||
repack->repack_func = &repack_squash_swap;
|
||||
break;
|
||||
|
||||
case repack_mode_8ch_swap23_swap46_swap57:
|
||||
repack->base_src_size = 8 * (16 / 8);
|
||||
repack->base_dst_size = 8 * (16 / 8);
|
||||
repack->extra_dst_size = 0;
|
||||
repack->repack_func = &repack_8ch_swap23;
|
||||
repack->repack_func = &repack_squash_swap;
|
||||
break;
|
||||
|
||||
default: return -1;
|
||||
|
@@ -26,8 +26,10 @@ struct audio_repack {
|
||||
};
|
||||
|
||||
enum _audio_repack_mode {
|
||||
repack_mode_8to4ch_swap23,
|
||||
repack_mode_8to5ch_swap23,
|
||||
repack_mode_8to6ch_swap23,
|
||||
repack_mode_8ch_swap23,
|
||||
repack_mode_8ch_swap23_swap46_swap57,
|
||||
};
|
||||
|
||||
typedef enum _audio_repack_mode audio_repack_mode_t;
|
||||
|
@@ -12,6 +12,13 @@ ColorRange.Full="Full"
|
||||
ChannelFormat="Channel"
|
||||
ChannelFormat.None="None"
|
||||
ChannelFormat.2_0ch="2ch"
|
||||
ChannelFormat.2_1ch="2.1"
|
||||
ChannelFormat.3_1ch="3.1"
|
||||
ChannelFormat.4_0ch="4ch"
|
||||
ChannelFormat.4_1ch="4.1ch"
|
||||
ChannelFormat.5_1ch="5.1ch"
|
||||
ChannelFormat.5_1chBack="5.1ch (Back)"
|
||||
ChannelFormat.5_1chBack="5.1ch Back"
|
||||
ChannelFormat.7_1ch="7.1ch"
|
||||
ChannelFormat.7_1chBack="7.1ch Back"
|
||||
ChannelFormat.8_0ch="8.0ch"
|
||||
ChannelFormat.16_0ch="16.0ch"
|
||||
|
@@ -9,7 +9,11 @@
|
||||
#define LOG(level, message, ...) blog(level, "%s: " message, \
|
||||
obs_source_get_name(this->decklink->GetSource()), ##__VA_ARGS__)
|
||||
|
||||
#define ISSTEREO(flag) ((flag) == SPEAKERS_STEREO)
|
||||
#ifdef _WIN32
|
||||
#define IS_WIN 1
|
||||
#else
|
||||
#define IS_WIN 0
|
||||
#endif
|
||||
|
||||
static inline enum video_format ConvertPixelFormat(BMDPixelFormat format)
|
||||
{
|
||||
@@ -26,9 +30,13 @@ static inline enum video_format ConvertPixelFormat(BMDPixelFormat format)
|
||||
static inline int ConvertChannelFormat(speaker_layout format)
|
||||
{
|
||||
switch (format) {
|
||||
case SPEAKERS_2POINT1:
|
||||
case SPEAKERS_QUAD:
|
||||
case SPEAKERS_4POINT1:
|
||||
case SPEAKERS_5POINT1:
|
||||
case SPEAKERS_5POINT1_SURROUND:
|
||||
case SPEAKERS_7POINT1:
|
||||
case SPEAKERS_7POINT1_SURROUND:
|
||||
return 8;
|
||||
|
||||
default:
|
||||
@@ -40,13 +48,16 @@ static inline int ConvertChannelFormat(speaker_layout format)
|
||||
static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format)
|
||||
{
|
||||
switch (format) {
|
||||
case SPEAKERS_QUAD:
|
||||
return repack_mode_8to4ch_swap23;
|
||||
case SPEAKERS_4POINT1:
|
||||
return repack_mode_8to5ch_swap23;
|
||||
case SPEAKERS_5POINT1:
|
||||
case SPEAKERS_5POINT1_SURROUND:
|
||||
return repack_mode_8to6ch_swap23;
|
||||
|
||||
case SPEAKERS_7POINT1:
|
||||
return repack_mode_8ch_swap23;
|
||||
|
||||
case SPEAKERS_7POINT1_SURROUND:
|
||||
return repack_mode_8ch_swap23_swap46_swap57;
|
||||
default:
|
||||
assert(false && "No repack requested");
|
||||
return (audio_repack_mode_t)-1;
|
||||
@@ -90,15 +101,23 @@ void DeckLinkDeviceInstance::HandleAudioPacket(
|
||||
(uint64_t)currentPacket.samples_per_sec;
|
||||
}
|
||||
|
||||
if (!ISSTEREO(channelFormat)) {
|
||||
int maxdevicechannel = device->GetMaxChannel();
|
||||
bool isWin = IS_WIN;
|
||||
|
||||
if (channelFormat != SPEAKERS_UNKNOWN &&
|
||||
channelFormat != SPEAKERS_MONO &&
|
||||
channelFormat != SPEAKERS_STEREO &&
|
||||
channelFormat != SPEAKERS_2POINT1 &&
|
||||
maxdevicechannel >= 8 &&
|
||||
isWin) {
|
||||
|
||||
if (audioRepacker->repack((uint8_t *)bytes, frameCount) < 0) {
|
||||
LOG(LOG_ERROR, "Failed to convert audio packet data");
|
||||
return;
|
||||
}
|
||||
|
||||
currentPacket.data[0] = (*audioRepacker)->packet_buffer;
|
||||
currentPacket.data[0] = (*audioRepacker)->packet_buffer;
|
||||
} else {
|
||||
currentPacket.data[0] = (uint8_t *)bytes;
|
||||
currentPacket.data[0] = (uint8_t *)bytes;
|
||||
}
|
||||
|
||||
nextAudioTS = timestamp +
|
||||
@@ -218,6 +237,9 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
|
||||
channelFormat = decklink->GetChannelFormat();
|
||||
currentPacket.speakers = channelFormat;
|
||||
|
||||
int maxdevicechannel = device->GetMaxChannel();
|
||||
bool isWin = IS_WIN;
|
||||
|
||||
if (channelFormat != SPEAKERS_UNKNOWN) {
|
||||
const int channel = ConvertChannelFormat(channelFormat);
|
||||
const HRESULT audioResult = input->EnableAudioInput(
|
||||
@@ -226,8 +248,15 @@ bool DeckLinkDeviceInstance::StartCapture(DeckLinkDeviceMode *mode_)
|
||||
if (audioResult != S_OK)
|
||||
LOG(LOG_WARNING, "Failed to enable audio input; continuing...");
|
||||
|
||||
if (!ISSTEREO(channelFormat)) {
|
||||
const audio_repack_mode_t repack_mode = ConvertRepackFormat(channelFormat);
|
||||
if (channelFormat != SPEAKERS_UNKNOWN &&
|
||||
channelFormat != SPEAKERS_MONO &&
|
||||
channelFormat != SPEAKERS_STEREO &&
|
||||
channelFormat != SPEAKERS_2POINT1 &&
|
||||
maxdevicechannel >= 8 &&
|
||||
isWin) {
|
||||
|
||||
const audio_repack_mode_t repack_mode = ConvertRepackFormat
|
||||
(channelFormat);
|
||||
audioRepacker = new AudioRepacker(repack_mode);
|
||||
}
|
||||
}
|
||||
|
@@ -29,8 +29,12 @@ OBS_MODULE_USE_DEFAULT_LOCALE("decklink", "en-US")
|
||||
#define TEXT_CHANNEL_FORMAT obs_module_text("ChannelFormat")
|
||||
#define TEXT_CHANNEL_FORMAT_NONE obs_module_text("ChannelFormat.None")
|
||||
#define TEXT_CHANNEL_FORMAT_2_0CH obs_module_text("ChannelFormat.2_0ch")
|
||||
#define TEXT_CHANNEL_FORMAT_2_1CH obs_module_text("ChannelFormat.2_1ch")
|
||||
#define TEXT_CHANNEL_FORMAT_4_0CH obs_module_text("ChannelFormat.4_0ch")
|
||||
#define TEXT_CHANNEL_FORMAT_4_1CH obs_module_text("ChannelFormat.4_1ch")
|
||||
#define TEXT_CHANNEL_FORMAT_5_1CH obs_module_text("ChannelFormat.5_1ch")
|
||||
#define TEXT_CHANNEL_FORMAT_5_1CH_BACK obs_module_text("ChannelFormat.5_1chBack")
|
||||
#define TEXT_CHANNEL_FORMAT_5_1CH_BACK \
|
||||
obs_module_text("ChannelFormat.5_1chBack")
|
||||
#define TEXT_CHANNEL_FORMAT_7_1CH obs_module_text("ChannelFormat.7_1ch")
|
||||
#define TEXT_BUFFERING obs_module_text("Buffering")
|
||||
|
||||
@@ -154,10 +158,12 @@ static bool decklink_device_changed(obs_properties_t *props,
|
||||
}
|
||||
|
||||
if (device->GetMaxChannel() >= 8) {
|
||||
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_4_0CH,
|
||||
SPEAKERS_QUAD);
|
||||
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH,
|
||||
SPEAKERS_5POINT1);
|
||||
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_5_1CH_BACK,
|
||||
SPEAKERS_5POINT1_SURROUND);
|
||||
obs_property_list_add_int(channelList,
|
||||
TEXT_CHANNEL_FORMAT_5_1CH_BACK, SPEAKERS_5POINT1_SURROUND);
|
||||
obs_property_list_add_int(channelList, TEXT_CHANNEL_FORMAT_7_1CH,
|
||||
SPEAKERS_7POINT1);
|
||||
}
|
||||
@@ -253,6 +259,18 @@ static obs_properties_t *decklink_get_properties(void *data)
|
||||
SPEAKERS_UNKNOWN);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_0CH,
|
||||
SPEAKERS_STEREO);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_2_1CH,
|
||||
SPEAKERS_2POINT1);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_0CH,
|
||||
SPEAKERS_QUAD);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_4_1CH,
|
||||
SPEAKERS_4POINT1);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_5_1CH,
|
||||
SPEAKERS_5POINT1);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_5_1CH_BACK,
|
||||
SPEAKERS_5POINT1_SURROUND);
|
||||
obs_property_list_add_int(list, TEXT_CHANNEL_FORMAT_7_1CH,
|
||||
SPEAKERS_7POINT1);
|
||||
|
||||
obs_properties_add_bool(props, BUFFERING, TEXT_BUFFERING);
|
||||
|
||||
|
Reference in New Issue
Block a user