From 003241511c4f9b0b0ddecc0b764106fe7bf5cb2e Mon Sep 17 00:00:00 2001 From: Paul Hindt Date: Mon, 20 Jun 2022 13:03:33 -0700 Subject: [PATCH] aja: Add audio channel selection to capture Adapted audio repacker for 32-bit samples, with an ifdef to use SIMD or C version. --- plugins/aja/CMakeLists.txt | 5 +- plugins/aja/aja-props.cpp | 43 +++-- plugins/aja/aja-props.hpp | 1 + plugins/aja/aja-routing.cpp | 4 +- plugins/aja/aja-source.cpp | 107 +++++++++++- plugins/aja/aja-source.hpp | 1 + plugins/aja/aja-ui-props.hpp | 22 ++- plugins/aja/audio-repack.c | 268 ++++++++++++++++++++++++++++++ plugins/aja/audio-repack.h | 51 ++++++ plugins/aja/audio-repack.hpp | 23 +++ plugins/aja/data/locale/en-US.ini | 10 ++ 11 files changed, 509 insertions(+), 26 deletions(-) create mode 100644 plugins/aja/audio-repack.c create mode 100644 plugins/aja/audio-repack.h create mode 100644 plugins/aja/audio-repack.hpp diff --git a/plugins/aja/CMakeLists.txt b/plugins/aja/CMakeLists.txt index d940255aa..02bbec614 100644 --- a/plugins/aja/CMakeLists.txt +++ b/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) diff --git a/plugins/aja/aja-props.cpp b/plugins/aja/aja-props.cpp index ec38d2084..abea0f575 100644 --- a/plugins/aja/aja-props.cpp +++ b/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} { } diff --git a/plugins/aja/aja-props.hpp b/plugins/aja/aja-props.hpp index a63a57fd2..e2ac09f50 100644 --- a/plugins/aja/aja-props.hpp +++ b/plugins/aja/aja-props.hpp @@ -47,6 +47,7 @@ public: uint32_t audioSampleRate; bool autoDetect; bool deactivateWhileNotShowing; + bool swapFrontCenterLFE; }; class OutputProps { diff --git a/plugins/aja/aja-routing.cpp b/plugins/aja/aja-routing.cpp index c914ce5a7..2638ddd02 100644 --- a/plugins/aja/aja-routing.cpp +++ b/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)); } diff --git a/plugins/aja/aja-source.cpp b/plugins/aja/aja-source.cpp index 77c78fc94..097c12c46 100644 --- a/plugins/aja/aja-source.cpp +++ b/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( 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(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(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); } diff --git a/plugins/aja/aja-source.hpp b/plugins/aja/aja-source.hpp index 81e3831ab..cb56a963a 100644 --- a/plugins/aja/aja-source.hpp +++ b/plugins/aja/aja-source.hpp @@ -1,6 +1,7 @@ #pragma once #include "aja-props.hpp" +#include "audio-repack.hpp" #include diff --git a/plugins/aja/aja-ui-props.hpp b/plugins/aja/aja-ui-props.hpp index 1c47658ea..da082d664 100644 --- a/plugins/aja/aja-ui-props.hpp +++ b/plugins/aja/aja-ui-props.hpp @@ -107,4 +107,24 @@ static const UIProperty kUIPropMultiViewAudioSource = { "ui_prop_multi_view_audio_source", "Multi View Audio Source", "", -}; \ No newline at end of file +}; + +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") diff --git a/plugins/aja/audio-repack.c b/plugins/aja/audio-repack.c new file mode 100644 index 000000000..d8c1d5974 --- /dev/null +++ b/plugins/aja/audio-repack.c @@ -0,0 +1,268 @@ +#include "audio-repack.h" + +#define USE_SIMD +#ifdef USE_SIMD +#include +#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)); +} diff --git a/plugins/aja/audio-repack.h b/plugins/aja/audio-repack.h new file mode 100644 index 000000000..992e907f8 --- /dev/null +++ b/plugins/aja/audio-repack.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include + +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 diff --git a/plugins/aja/audio-repack.hpp b/plugins/aja/audio-repack.hpp new file mode 100644 index 000000000..946a40388 --- /dev/null +++ b/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; } +}; diff --git a/plugins/aja/data/locale/en-US.ini b/plugins/aja/data/locale/en-US.ini index 8b5d0023f..88c4430c4 100644 --- a/plugins/aja/data/locale/en-US.ini +++ b/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"