Browse Source

aja: Add audio channel selection to capture

Adapted audio repacker for 32-bit samples,

with an ifdef to use SIMD or C version.
Paul Hindt 3 years ago
parent
commit
003241511c

+ 4 - 1
plugins/aja/CMakeLists.txt

@@ -41,7 +41,10 @@ target_sources(
           aja-widget-io.cpp
           aja-widget-io.hpp
           aja-card-manager.hpp
-          aja-ui-props.hpp)
+          aja-ui-props.hpp
+          audio-repack.c
+          audio-repack.h
+          audio-repack.hpp)
 
 target_link_libraries(aja PRIVATE OBS::libobs AJA::LibAJANTV2)
 

+ 30 - 13
plugins/aja/aja-props.cpp

@@ -13,12 +13,13 @@ SourceProps::SourceProps()
 	  pixelFormat{NTV2_FBF_INVALID},
 	  sdiTransport{SDITransport::SingleLink},
 	  sdi4kTransport{SDITransport4K::TwoSampleInterleave},
-	  audioNumChannels{8},
-	  audioSampleSize{4},
-	  audioSampleRate{48000},
+	  audioNumChannels{kDefaultAudioChannels},
+	  audioSampleSize{kDefaultAudioSampleSize},
+	  audioSampleRate{kDefaultAudioSampleRate},
 	  vpids{},
 	  autoDetect{false},
-	  deactivateWhileNotShowing{false}
+	  deactivateWhileNotShowing{false},
+	  swapFrontCenterLFE{false}
 {
 }
 
@@ -30,12 +31,13 @@ SourceProps::SourceProps(NTV2DeviceID devID)
 	  pixelFormat{NTV2_FBF_INVALID},
 	  sdiTransport{SDITransport::SingleLink},
 	  sdi4kTransport{SDITransport4K::TwoSampleInterleave},
-	  audioNumChannels{8},
-	  audioSampleSize{4},
-	  audioSampleRate{48000},
+	  audioNumChannels{kDefaultAudioChannels},
+	  audioSampleSize{kDefaultAudioSampleSize},
+	  audioSampleRate{kDefaultAudioSampleRate},
 	  vpids{},
 	  autoDetect{false},
-	  deactivateWhileNotShowing{false}
+	  deactivateWhileNotShowing{false},
+	  swapFrontCenterLFE{false}
 {
 }
 
@@ -54,6 +56,7 @@ SourceProps::SourceProps(const SourceProps &props)
 	vpids = props.vpids;
 	autoDetect = props.autoDetect;
 	deactivateWhileNotShowing = props.deactivateWhileNotShowing;
+	swapFrontCenterLFE = props.swapFrontCenterLFE;
 }
 
 SourceProps::SourceProps(SourceProps &&props)
@@ -71,6 +74,7 @@ SourceProps::SourceProps(SourceProps &&props)
 	vpids = props.vpids;
 	autoDetect = props.autoDetect;
 	deactivateWhileNotShowing = props.deactivateWhileNotShowing;
+	swapFrontCenterLFE = props.swapFrontCenterLFE;
 }
 
 void SourceProps::operator=(const SourceProps &props)
@@ -88,6 +92,7 @@ void SourceProps::operator=(const SourceProps &props)
 	vpids = props.vpids;
 	autoDetect = props.autoDetect;
 	deactivateWhileNotShowing = props.deactivateWhileNotShowing;
+	swapFrontCenterLFE = props.swapFrontCenterLFE;
 }
 
 void SourceProps::operator=(SourceProps &&props)
@@ -105,6 +110,7 @@ void SourceProps::operator=(SourceProps &&props)
 	vpids = props.vpids;
 	autoDetect = props.autoDetect;
 	deactivateWhileNotShowing = props.deactivateWhileNotShowing;
+	swapFrontCenterLFE = props.swapFrontCenterLFE;
 }
 
 bool SourceProps::operator==(const SourceProps &props)
@@ -120,7 +126,8 @@ bool SourceProps::operator==(const SourceProps &props)
 		audioNumChannels == props.audioNumChannels &&
 		audioSampleSize == props.audioSampleSize &&
 		audioSampleRate == props.audioSampleRate &&
-		deactivateWhileNotShowing == props.deactivateWhileNotShowing);
+		deactivateWhileNotShowing == props.deactivateWhileNotShowing &&
+		swapFrontCenterLFE == props.swapFrontCenterLFE);
 }
 
 bool SourceProps::operator!=(const SourceProps &props)
@@ -196,9 +203,19 @@ audio_format SourceProps::AudioFormat() const
 
 speaker_layout SourceProps::SpeakerLayout() const
 {
-	if (audioNumChannels == 2)
+	if (audioNumChannels == 1)
+		return SPEAKERS_MONO;
+	else if (audioNumChannels == 2)
 		return SPEAKERS_STEREO;
-	// NTV2 is always at least 8ch on modern boards
+	else if (audioNumChannels == 3)
+		return SPEAKERS_2POINT1;
+	else if (audioNumChannels == 4)
+		return SPEAKERS_4POINT0;
+	else if (audioNumChannels == 5)
+		return SPEAKERS_4POINT1;
+	else if (audioNumChannels == 6)
+		return SPEAKERS_5POINT1;
+	// NTV2 card is always set to at least 8ch
 	return SPEAKERS_7POINT1;
 }
 
@@ -213,8 +230,8 @@ OutputProps::OutputProps(NTV2DeviceID devID)
 	  pixelFormat{NTV2_FBF_INVALID},
 	  sdi4kTransport{SDITransport4K::TwoSampleInterleave},
 	  audioNumChannels{8},
-	  audioSampleSize{4},
-	  audioSampleRate{48000}
+	  audioSampleSize{kDefaultAudioSampleSize},
+	  audioSampleRate{kDefaultAudioSampleRate}
 {
 }
 

+ 1 - 0
plugins/aja/aja-props.hpp

@@ -47,6 +47,7 @@ public:
 	uint32_t audioSampleRate;
 	bool autoDetect;
 	bool deactivateWhileNotShowing;
+	bool swapFrontCenterLFE;
 };
 
 class OutputProps {

+ 2 - 2
plugins/aja/aja-routing.cpp

@@ -152,7 +152,7 @@ void Routing::StartSourceAudio(const SourceProps &props, CNTV2Card *card)
 		audioSys, NTV2InputSourceToAudioSource(inputSrc),
 		NTV2InputSourceToEmbeddedAudioInput(inputSrc));
 
-	card->SetNumberAudioChannels(props.audioNumChannels, audioSys);
+	card->SetNumberAudioChannels(kDefaultAudioChannels, audioSys);
 	card->SetAudioRate(props.AudioRate(), audioSys);
 	card->SetAudioBufferSize(NTV2_AUDIO_BUFFER_BIG, audioSys);
 
@@ -188,7 +188,7 @@ void Routing::StartSourceAudio(const SourceProps &props, CNTV2Card *card)
 
 	for (int a = 0; a < NTV2DeviceGetNumAudioSystems(card->GetDeviceID());
 	     a++) {
-		card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_OFF,
+		card->SetAudioLoopBack(NTV2_AUDIO_LOOPBACK_ON,
 				       NTV2AudioSystem(a));
 	}
 

+ 98 - 9
plugins/aja/aja-source.cpp

@@ -18,6 +18,30 @@
 #endif
 #define NTV2_AUDIOSIZE_MAX (401 * 1024)
 
+static constexpr int kDefaultAudioCaptureChannels = 2;
+
+static inline audio_repack_mode_t ConvertRepackFormat(speaker_layout format,
+						      bool swap = false)
+{
+	switch (format) {
+	case SPEAKERS_STEREO:
+		return repack_mode_8to2ch;
+	case SPEAKERS_2POINT1:
+		return repack_mode_8to3ch;
+	case SPEAKERS_4POINT0:
+		return repack_mode_8to4ch;
+	case SPEAKERS_4POINT1:
+		return swap ? repack_mode_8to5ch_swap : repack_mode_8to5ch;
+	case SPEAKERS_5POINT1:
+		return swap ? repack_mode_8to6ch_swap : repack_mode_8to6ch;
+	case SPEAKERS_7POINT1:
+		return swap ? repack_mode_8ch_swap : repack_mode_8ch;
+	default:
+		assert(false && "No repack requested");
+		return (audio_repack_mode_t)-1;
+	}
+}
+
 AJASource::AJASource(obs_source_t *source)
 	: mVideoBuffer{},
 	  mAudioBuffer{},
@@ -283,7 +307,6 @@ void AJASource::CaptureThread(AJAThread *thread, void *data)
 	// etc...
 	ULWord currentCardFrame = GetIndexForNTV2Channel(framestore) * 2;
 	card->WaitForInputFieldID(NTV2_FIELD0, channel);
-
 	currentCardFrame ^= 1;
 
 	card->SetInputFrame(framestore, currentCardFrame);
@@ -293,6 +316,11 @@ void AJASource::CaptureThread(AJAThread *thread, void *data)
 
 	obs_data_t *settings = obs_source_get_settings(ajaSource->mSource);
 
+	AudioRepacker *audioRepacker = new AudioRepacker(
+		ConvertRepackFormat(sourceProps.SpeakerLayout(),
+				    sourceProps.swapFrontCenterLFE),
+		sourceProps.audioSampleSize * 8);
+
 	while (ajaSource->IsCapturing()) {
 		if (card->GetModelName() == "(Not Found)") {
 			os_sleep_ms(250);
@@ -340,15 +368,35 @@ void AJASource::CaptureThread(AJAThread *thread, void *data)
 			ResetAudioBufferOffsets(card, audioSystem, offsets);
 		} else {
 			offsets.lastAddress = offsets.currentAddress;
+			uint32_t sampleCount = offsets.bytesRead /
+					       (kDefaultAudioChannels *
+						sourceProps.audioSampleSize);
 			obs_source_audio audioPacket;
-			audioPacket.samples_per_sec = 48000;
-			audioPacket.format = AUDIO_FORMAT_32BIT;
-			audioPacket.speakers = SPEAKERS_7POINT1;
-			audioPacket.frames = offsets.bytesRead / 32;
+			audioPacket.samples_per_sec =
+				sourceProps.audioSampleRate;
+			audioPacket.format = sourceProps.AudioFormat();
+			audioPacket.speakers = sourceProps.SpeakerLayout();
+			audioPacket.frames = sampleCount;
 			audioPacket.timestamp =
-				get_sample_time(audioPacket.frames, 48000);
-			audioPacket.data[0] = (uint8_t *)ajaSource->mAudioBuffer
-						      .GetHostPointer();
+				get_sample_time(audioPacket.frames,
+						sourceProps.audioSampleRate);
+			uint8_t *hostAudioBuffer =
+				(uint8_t *)ajaSource->mAudioBuffer
+					.GetHostPointer();
+			if (sourceProps.audioNumChannels >= 2 &&
+			    sourceProps.audioNumChannels <= 6) {
+				/* Repack 8ch audio to fit specified OBS speaker layout */
+				audioRepacker->repack(hostAudioBuffer,
+						      sampleCount);
+				audioPacket.data[0] =
+					(*audioRepacker)->packet_buffer;
+			} else {
+				/* Silence, or pass-through 8ch of audio */
+				if (sourceProps.audioNumChannels == 0)
+					memset(hostAudioBuffer, 0,
+					       offsets.bytesRead);
+				audioPacket.data[0] = hostAudioBuffer;
+			}
 			obs_source_output_audio(ajaSource->mSource,
 						&audioPacket);
 		}
@@ -395,7 +443,10 @@ void AJASource::CaptureThread(AJAThread *thread, void *data)
 	}
 
 	blog(LOG_INFO, "AJASource::Capturethread: Thread loop stopped");
-
+	if (audioRepacker) {
+		delete audioRepacker;
+		audioRepacker = nullptr;
+	}
 	ajaSource->GenerateTestPattern(sourceProps.videoFormat,
 				       sourceProps.pixelFormat,
 				       NTV2_TestPatt_Black);
@@ -682,6 +733,8 @@ bool aja_source_device_changed(void *data, obs_properties_t *props,
 		obs_properties_get(props, kUIPropSDITransport.id);
 	obs_property_t *sdi_4k_list =
 		obs_properties_get(props, kUIPropSDITransport4K.id);
+	obs_property_t *channel_format_list =
+		obs_properties_get(props, kUIPropChannelFormat.id);
 
 	obs_property_list_clear(vid_fmt_list);
 	obs_property_list_add_int(vid_fmt_list, obs_module_text("Auto"),
@@ -702,6 +755,22 @@ bool aja_source_device_changed(void *data, obs_properties_t *props,
 	obs_property_list_clear(sdi_4k_list);
 	populate_sdi_4k_transport_list(sdi_4k_list);
 
+	obs_property_list_clear(channel_format_list);
+	obs_property_list_add_int(channel_format_list, TEXT_CHANNEL_FORMAT_NONE,
+				  0);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_2_0CH, 2);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_2_1CH, 3);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_4_0CH, 4);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_4_1CH, 5);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_5_1CH, 6);
+	obs_property_list_add_int(channel_format_list,
+				  TEXT_CHANNEL_FORMAT_7_1CH, 8);
+
 	populate_io_selection_input_list(cardID, ajaSource->GetName(), deviceID,
 					 io_select_list);
 
@@ -904,8 +973,12 @@ static void aja_source_update(void *data, obs_data_t *settings)
 		obs_data_get_int(settings, kUIPropSDITransport.id));
 	auto sdi_t4k_select = static_cast<SDITransport4K>(
 		obs_data_get_int(settings, kUIPropSDITransport4K.id));
+	auto num_audio_channels =
+		obs_data_get_int(settings, kUIPropChannelFormat.id);
 	bool deactivateWhileNotShowing =
 		obs_data_get_bool(settings, kUIPropDeactivateWhenNotShowing.id);
+	bool swapFrontCenterLFE =
+		obs_data_get_bool(settings, kUIPropChannelSwap_FC_LFE.id);
 	const std::string &wantCardID =
 		obs_data_get_string(settings, kUIPropDevice.id);
 
@@ -989,6 +1062,8 @@ static void aja_source_update(void *data, obs_data_t *settings)
 			? SDITransport::Unknown
 			: static_cast<SDITransport>(sdi_trx_select);
 	want_props.sdi4kTransport = sdi_t4k_select;
+	want_props.audioNumChannels = (uint32_t)num_audio_channels;
+	want_props.swapFrontCenterLFE = swapFrontCenterLFE;
 	want_props.vpids.clear();
 	want_props.deactivateWhileNotShowing = deactivateWhileNotShowing;
 	if (aja::IsIOSelectionSDI(io_select)) {
@@ -1099,11 +1174,21 @@ static obs_properties_t *aja_source_get_properties(void *data)
 	obs_properties_add_list(props, kUIPropSDITransport4K.id,
 				obs_module_text(kUIPropSDITransport4K.text),
 				OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+	obs_properties_add_list(props, kUIPropChannelFormat.id,
+				obs_module_text(kUIPropChannelFormat.text),
+				OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT);
+	obs_property_t *swap = obs_properties_add_bool(
+		props, kUIPropChannelSwap_FC_LFE.id,
+		obs_module_text(kUIPropChannelSwap_FC_LFE.text));
+	obs_property_set_long_description(swap,
+					  kUIPropChannelSwap_FC_LFE.tooltip);
 	obs_properties_add_bool(
 		props, kUIPropDeactivateWhenNotShowing.id,
 		obs_module_text(kUIPropDeactivateWhenNotShowing.text));
 	obs_properties_add_bool(props, kUIPropBuffering.id,
 				obs_module_text(kUIPropBuffering.text));
+	obs_properties_add_bool(props, kUIPropBuffering.id,
+				obs_module_text(kUIPropBuffering.text));
 
 	obs_property_set_modified_callback(vid_fmt_list,
 					   aja_video_format_changed);
@@ -1128,6 +1213,10 @@ void aja_source_get_defaults(obs_data_t *settings)
 	obs_data_set_default_int(
 		settings, kUIPropSDITransport4K.id,
 		static_cast<long long>(SDITransport4K::TwoSampleInterleave));
+	obs_data_set_default_int(settings, kUIPropChannelFormat.id,
+				 kDefaultAudioCaptureChannels);
+	obs_data_set_default_bool(settings, kUIPropChannelSwap_FC_LFE.id,
+				  false);
 	obs_data_set_default_bool(settings, kUIPropDeactivateWhenNotShowing.id,
 				  false);
 }

+ 1 - 0
plugins/aja/aja-source.hpp

@@ -1,6 +1,7 @@
 #pragma once
 
 #include "aja-props.hpp"
+#include "audio-repack.hpp"
 
 #include <obs-module.h>
 

+ 21 - 1
plugins/aja/aja-ui-props.hpp

@@ -107,4 +107,24 @@ static const UIProperty kUIPropMultiViewAudioSource = {
 	"ui_prop_multi_view_audio_source",
 	"Multi View Audio Source",
 	"",
-};
+};
+
+static const UIProperty kUIPropChannelFormat = {
+	"ui_prop_channel_format",
+	"ChannelFormat",
+	"",
+};
+
+static const UIProperty kUIPropChannelSwap_FC_LFE = {
+	"ui_prop_channel_swap_fc_lfe",
+	"SwapFC-LFE",
+	"SwapFC-LFE.Tooltip",
+};
+
+#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_7_1CH obs_module_text("ChannelFormat.7_1ch")

+ 268 - 0
plugins/aja/audio-repack.c

@@ -0,0 +1,268 @@
+#include "audio-repack.h"
+
+#define USE_SIMD
+#ifdef USE_SIMD
+#include <util/sse-intrin.h>
+#endif
+
+#define NUM_CHANNELS 8 /* max until OBS supports higher channel counts */
+
+int check_buffer(struct audio_repack *repack, uint32_t frame_count)
+{
+	const uint32_t new_size =
+		frame_count * repack->base_dst_size + repack->pad_dst_size;
+
+	if (repack->packet_size < new_size) {
+		repack->packet_buffer =
+			brealloc(repack->packet_buffer, new_size);
+		if (!repack->packet_buffer)
+			return -1;
+		repack->packet_size = new_size;
+	}
+
+	return 0;
+}
+
+#ifdef USE_SIMD
+/*
+ * Squash array of 8ch to new channel count
+ * 16-bit PCM, SIMD version
+ * For instance:
+ * 2.1:
+ *
+ * | FL | FR | LFE | emp | emp | emp |emp |emp |
+ * |    |    |
+ * | FL | FR | LFE |
+*/
+int repack_squash16(struct audio_repack *repack, const uint8_t *bsrc,
+		    uint32_t frame_count)
+{
+	if (check_buffer(repack, frame_count) < 0)
+		return -1;
+
+	int squash = repack->squash_count;
+	const __m128i *src = (__m128i *)bsrc;
+	const __m128i *end = src + frame_count;
+	uint16_t *dst = (uint16_t *)repack->packet_buffer;
+
+	/*  Audio needs squashing in order to avoid resampling issues.
+	 * The condition checks for 7.1 audio for which no squash is needed.
+	 */
+	if (squash > 0) {
+		while (src != end) {
+			__m128i target = _mm_load_si128(src++);
+			_mm_storeu_si128((__m128i *)dst, target);
+			dst += NUM_CHANNELS - squash;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Squash array of 8ch and swap Front Center channel with LFE
+ * 16-bit PCM, SIMD version
+ * For instance:
+ * 2.1:
+ *
+ * | FL | FR | FC | LFE | RL | RR | LC | RC |
+ * |    |    |
+ * | FL | FR | LFE | FC | RL | RR | LC | RC |
+*/
+int repack_squash_swap16(struct audio_repack *repack, const uint8_t *bsrc,
+			 uint32_t frame_count)
+{
+	if (check_buffer(repack, frame_count) < 0)
+		return -1;
+	int squash = repack->squash_count;
+	const __m128i *src = (__m128i *)bsrc;
+	const __m128i *end = src + frame_count;
+	uint16_t *dst = (uint16_t *)repack->packet_buffer;
+	while (src != end) {
+		__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 += NUM_CHANNELS - squash;
+	}
+	return 0;
+}
+
+/*
+ * Squash array of 8ch to new channel count
+ * 32-bit PCM, SIMD version
+ */
+int repack_squash32(struct audio_repack *repack, const uint8_t *bsrc,
+		    uint32_t frame_count)
+{
+	if (check_buffer(repack, frame_count) < 0)
+		return -1;
+
+	int squash = repack->squash_count;
+	const __m128i *src = (__m128i *)bsrc;
+	const __m128i *end =
+		(__m128i *)(bsrc + (frame_count * repack->base_src_size));
+	uint32_t *dst = (uint32_t *)repack->packet_buffer;
+
+	if (squash > 0) {
+		while (src != end) {
+			__m128i tgt_lo = _mm_loadu_si128(src++);
+			__m128i tgt_hi = _mm_loadu_si128(src++);
+			_mm_storeu_si128((__m128i *)dst, tgt_lo);
+			dst += 4;
+			_mm_storeu_si128((__m128i *)dst, tgt_hi);
+			dst += 4;
+			dst -= squash;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Squash array of 8ch to new channel count and swap FC with LFE
+ * 32-bit PCM, SIMD version
+ */
+int repack_squash_swap32(struct audio_repack *repack, const uint8_t *bsrc,
+			 uint32_t frame_count)
+{
+	if (check_buffer(repack, frame_count) < 0)
+		return -1;
+	int squash = repack->squash_count;
+	const __m128i *src = (__m128i *)bsrc;
+	const __m128i *end =
+		(__m128i *)(bsrc + (frame_count * repack->base_src_size));
+	uint32_t *dst = (uint32_t *)repack->packet_buffer;
+	while (src != end) {
+		__m128i tgt_lo = _mm_shuffle_epi32(_mm_loadu_si128(src++),
+						   _MM_SHUFFLE(2, 3, 1, 0));
+		__m128i tgt_hi = _mm_loadu_si128(src++);
+		_mm_storeu_si128((__m128i *)dst, tgt_lo);
+		dst += 4;
+		_mm_storeu_si128((__m128i *)dst, tgt_hi);
+		dst += 4;
+		dst -= squash;
+	}
+	return 0;
+}
+
+#else
+
+/*
+ * Squash array of 8ch to new channel count
+ * 16-bit or 32-bit PCM, C version
+ */
+int repack_squash(struct audio_repack *repack, const uint8_t *bsrc,
+		  uint32_t frame_count)
+{
+	if (check_buffer(repack, frame_count) < 0)
+		return -1;
+	int squash = repack->squash_count;
+	const uint8_t *src = bsrc;
+	const uint8_t *end = src + frame_count * repack->base_src_size;
+	uint8_t *dst = repack->packet_buffer;
+	uint32_t new_channel_count = NUM_CHANNELS - squash;
+	if (squash > 0) {
+		while (src != end) {
+			memcpy(dst, src,
+			       repack->bytes_per_sample * new_channel_count);
+			dst += (new_channel_count * repack->bytes_per_sample);
+			src += NUM_CHANNELS * repack->bytes_per_sample;
+		}
+	}
+
+	return 0;
+}
+
+void shuffle_8ch(uint8_t *dst, const uint8_t *src, size_t szb, int ch1, int ch2,
+		 int ch3, int ch4, int ch5, int ch6, int ch7, int ch8)
+{
+	/* shuffle 8 channels of audio */
+	for (size_t i = 0; i < szb; i++) {
+		dst[0 * szb + i] = src[ch1 * szb + i];
+		dst[1 * szb + i] = src[ch2 * szb + i];
+		dst[2 * szb + i] = src[ch3 * szb + i];
+		dst[3 * szb + i] = src[ch4 * szb + i];
+		dst[4 * szb + i] = src[ch5 * szb + i];
+		dst[5 * szb + i] = src[ch6 * szb + i];
+		dst[6 * szb + i] = src[ch7 * szb + i];
+		dst[7 * szb + i] = src[ch8 * szb + i];
+	}
+}
+
+/*
+ * Squash array of 8ch to new channel count and swap FC with LFE
+ * 16-bit or 32-bit PCM, C version
+ */
+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->squash_count;
+	const uint8_t *src = bsrc;
+	const uint8_t *end = src + (frame_count * repack->base_src_size);
+	uint8_t *dst = repack->packet_buffer;
+	uint32_t new_channel_count = NUM_CHANNELS - squash;
+	if (squash > 0) {
+		while (src != end) {
+			shuffle_8ch(dst, src, 4, 0, 1, 3, 2, 4, 5, 6, 7);
+			dst += (new_channel_count * repack->bytes_per_sample);
+			src += (NUM_CHANNELS * repack->bytes_per_sample);
+		}
+	}
+
+	return 0;
+}
+#endif
+
+int audio_repack_init(struct audio_repack *repack,
+		      audio_repack_mode_t repack_mode, uint8_t bits_per_sample)
+{
+	memset(repack, 0, sizeof(*repack));
+
+	if (bits_per_sample != 16 && bits_per_sample != 32)
+		return -1;
+	int _audio_repack_ch[9] = {2, 3, 4, 5, 6, 5, 6, 8, 8};
+	int bytes_per_sample = (bits_per_sample / 8);
+	repack->bytes_per_sample = bytes_per_sample;
+	repack->base_src_size = NUM_CHANNELS * bytes_per_sample;
+	repack->base_dst_size =
+		_audio_repack_ch[repack_mode] * bytes_per_sample;
+	uint32_t squash_count = NUM_CHANNELS - _audio_repack_ch[repack_mode];
+	repack->pad_dst_size = squash_count * bytes_per_sample;
+	repack->squash_count = squash_count;
+
+#ifdef USE_SIMD
+	if (bits_per_sample == 16) {
+		repack->repack_func = &repack_squash16;
+		if (repack_mode == repack_mode_8to5ch_swap ||
+		    repack_mode == repack_mode_8to6ch_swap ||
+		    repack_mode == repack_mode_8ch_swap)
+			repack->repack_func = &repack_squash_swap16;
+	} else if (bits_per_sample == 32) {
+		repack->repack_func = &repack_squash32;
+		if (repack_mode == repack_mode_8to5ch_swap ||
+		    repack_mode == repack_mode_8to6ch_swap ||
+		    repack_mode == repack_mode_8ch_swap)
+			repack->repack_func = &repack_squash_swap32;
+	}
+#else
+	repack->repack_func = &repack_squash;
+	if (repack_mode == repack_mode_8to5ch_swap ||
+	    repack_mode == repack_mode_8to6ch_swap ||
+	    repack_mode == repack_mode_8ch_swap)
+		repack->repack_func = &repack_squash_swap;
+#endif
+
+	return 0;
+}
+
+void audio_repack_free(struct audio_repack *repack)
+{
+	if (repack->packet_buffer)
+		bfree(repack->packet_buffer);
+
+	memset(repack, 0, sizeof(*repack));
+}

+ 51 - 0
plugins/aja/audio-repack.h

@@ -0,0 +1,51 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <string.h>
+
+#include <obs.h>
+
+struct audio_repack;
+
+typedef int (*audio_repack_func_t)(struct audio_repack *, const uint8_t *,
+				   uint32_t);
+
+struct audio_repack {
+	uint8_t *packet_buffer;
+	uint32_t packet_size;
+
+	uint32_t base_src_size;
+	uint32_t base_dst_size;
+	uint32_t pad_dst_size;
+	uint32_t squash_count;
+	uint32_t bytes_per_sample;
+
+	audio_repack_func_t repack_func;
+};
+
+enum _audio_repack_mode {
+	repack_mode_8to2ch = 0,
+	repack_mode_8to3ch,
+	repack_mode_8to4ch,
+	repack_mode_8to5ch,
+	repack_mode_8to6ch,
+	repack_mode_8to5ch_swap,
+	repack_mode_8to6ch_swap,
+	repack_mode_8ch_swap,
+	repack_mode_8ch,
+};
+
+typedef enum _audio_repack_mode audio_repack_mode_t;
+
+extern int audio_repack_init(struct audio_repack *repack,
+			     audio_repack_mode_t repack_mode,
+			     uint8_t bits_per_sample);
+extern void audio_repack_free(struct audio_repack *repack);
+
+#ifdef __cplusplus
+}
+#endif

+ 23 - 0
plugins/aja/audio-repack.hpp

@@ -0,0 +1,23 @@
+#pragma once
+
+#include "audio-repack.h"
+
+class AudioRepacker {
+	struct audio_repack arepack;
+
+public:
+	inline AudioRepacker(audio_repack_mode_t repack_mode,
+			     int bits_per_sample)
+	{
+		audio_repack_init(&arepack, repack_mode, bits_per_sample);
+	}
+	inline ~AudioRepacker() { audio_repack_free(&arepack); }
+
+	inline int repack(const uint8_t *src, uint32_t frame_size)
+	{
+		return (*arepack.repack_func)(&arepack, src, frame_size);
+	}
+
+	inline operator struct audio_repack *() { return &arepack; }
+	inline struct audio_repack *operator->() { return &arepack; }
+};

+ 10 - 0
plugins/aja/data/locale/en-US.ini

@@ -15,3 +15,13 @@ IOSelect="Select..."
 SDITransport="SDI Transport"
 SDITransport4K="SDI 4K Transport"
 Auto="Auto"
+ChannelFormat="Channel"
+ChannelFormat.None="None"
+ChannelFormat.2_0ch="2ch"
+ChannelFormat.2_1ch="2.1ch"
+ChannelFormat.4_0ch="4ch"
+ChannelFormat.4_1ch="4.1ch"
+ChannelFormat.5_1ch="5.1ch"
+ChannelFormat.7_1ch="7.1ch"
+SwapFC-LFE="Swap FC and LFE"
+SwapFC-LFE.Tooltip="Swap Front Center Channel and LFE Channel"