From 53d218ff420fbbe2e082512c3f5e45aad1c7fdfb Mon Sep 17 00:00:00 2001 From: Victor Gaydov Date: Tue, 10 Oct 2023 01:49:04 +0400 Subject: [PATCH] gh-86 Implement channel order mapping Before this commit, order of channels in surround layout was defined by ChannelPosition enum values. After this commit, each ChannelSet (and hence SampleSpec) contains ChannelOrder enum, which defines order of surround channels in memory. Currently two orders are supported: - ChanOrder_Smpte - widely used order define by ITU/SMPTE spec; compatible with Dolby, AIFF-C, AAC, and RTP/AVP - ChanOrder_Alsa - order used by ALSA hw devices ChannelMapper now takes order into account. If input & output orders differ, it automatically performs reordering. So far surround layouts were not exposed in API and CLI. Thus, changing default order did not break anything, however all tests had to be updated to match new order. --- .../roc_audio/channel_mapper.cpp | 58 +- .../roc_audio/channel_mapper_table.cpp | 59 + .../roc_audio/channel_mapper_table.h | 9 + src/tests/roc_audio/test_channel_mapper.cpp | 1010 ++++++++++++----- .../roc_audio/test_channel_mapper_table.cpp | 36 +- 5 files changed, 845 insertions(+), 327 deletions(-) diff --git a/src/internal_modules/roc_audio/channel_mapper.cpp b/src/internal_modules/roc_audio/channel_mapper.cpp index 16e219f45..a9d19c8d9 100644 --- a/src/internal_modules/roc_audio/channel_mapper.cpp +++ b/src/internal_modules/roc_audio/channel_mapper.cpp @@ -202,23 +202,38 @@ void ChannelMapper::setup_map_matrix_() { roc_panic_if_not(out_chans_.last_channel() < ChanPos_Max); roc_panic_if_not(in_chans_.last_channel() < ChanPos_Max); - // Fill mapping of output channel position to its relative offset. + // Surround layouts should have valid order. + roc_panic_if_not(out_chans_.order() > ChanOrder_None + && out_chans_.order() < ChanOrder_Max); + roc_panic_if_not(out_chans_.order() > ChanOrder_None + && in_chans_.order() < ChanOrder_Max); + + // Fill mapping of output channel position to its index in frame. + ChannelSet out_index_set; size_t out_index_map[ChanPos_Max] = {}; - for (size_t out_off = 0, out_ch = out_chans_.first_channel(); - out_ch <= out_chans_.last_channel(); out_ch++) { + const ChannelList& out_order = chan_orders[out_chans_.order()]; + + for (size_t out_index = 0, n_ord = 0; out_order.chans[n_ord] != ChanPos_Max; + n_ord++) { + const ChannelPosition out_ch = out_order.chans[n_ord]; if (out_chans_.has_channel(out_ch)) { - out_index_map[out_ch] = out_off++; + out_index_set.set_channel(out_ch, true); + out_index_map[out_ch] = out_index++; } } - // Fill mapping of input channel position to its relative offset. + // Fill mapping of input channel position to its index in frame. + ChannelSet in_index_set; size_t in_index_map[ChanPos_Max] = {}; - for (size_t in_off = 0, in_ch = in_chans_.first_channel(); - in_ch <= in_chans_.last_channel(); in_ch++) { + const ChannelList& in_order = chan_orders[in_chans_.order()]; + + for (size_t in_index = 0, n_ord = 0; in_order.chans[n_ord] != ChanPos_Max; n_ord++) { + const ChannelPosition in_ch = in_order.chans[n_ord]; if (in_chans_.has_channel(in_ch)) { - in_index_map[in_ch] = in_off++; + in_index_set.set_channel(in_ch, true); + in_index_map[in_ch] = in_index++; } } @@ -226,14 +241,14 @@ void ChannelMapper::setup_map_matrix_() { const ChannelMap* ch_map = NULL; bool is_reverse = false; - if (in_chans_ != out_chans_) { + if (out_index_set != in_index_set) { for (size_t n = 0; !ch_map && n < ROC_ARRAY_SIZE(chan_maps); n++) { - if (out_chans_.is_subset(chan_maps[n].out_mask) - && in_chans_.is_subset(chan_maps[n].in_mask)) { + if (out_index_set.is_subset(chan_maps[n].out_mask) + && in_index_set.is_subset(chan_maps[n].in_mask)) { ch_map = &chan_maps[n]; is_reverse = false; - } else if (in_chans_.is_subset(chan_maps[n].out_mask) - && out_chans_.is_subset(chan_maps[n].in_mask)) { + } else if (in_index_set.is_subset(chan_maps[n].out_mask) + && out_index_set.is_subset(chan_maps[n].in_mask)) { // This channel map describes reversed transformation. ch_map = &chan_maps[n]; is_reverse = true; @@ -252,6 +267,7 @@ void ChannelMapper::setup_map_matrix_() { for (size_t n = 0; n < ROC_ARRAY_SIZE(ch_map->rules); n++) { const ChannelMapRule& rule = ch_map->rules[n]; if (rule.coeff == 0.f) { + // Last rule. break; } @@ -268,13 +284,11 @@ void ChannelMapper::setup_map_matrix_() { coeff = 1.f / rule.coeff; } - if (out_chans_.has_channel(out_ch) && in_chans_.has_channel(in_ch)) { - roc_panic_if_not(out_ch < ChanPos_Max); - roc_panic_if_not(in_ch < ChanPos_Max); - - roc_panic_if_not(out_index_map[out_ch] < ChanPos_Max); - roc_panic_if_not(in_index_map[in_ch] < ChanPos_Max); + roc_panic_if_not(out_ch < ChanPos_Max && in_ch < ChanPos_Max); + roc_panic_if_not(out_index_map[out_ch] < ChanPos_Max + && in_index_map[in_ch] < ChanPos_Max); + if (out_index_set.has_channel(out_ch) && in_index_set.has_channel(in_ch)) { map_matrix_[out_index_map[out_ch]][in_index_map[in_ch]] = coeff; } } @@ -303,10 +317,10 @@ void ChannelMapper::setup_map_matrix_() { channel_set_to_str(out_chans_).c_str()); for (size_t ch = 0; ch < ChanPos_Max; ch++) { - if (out_chans_.has_channel(ch) && in_chans_.has_channel(ch)) { - roc_panic_if_not(out_index_map[ch] < ChanPos_Max); - roc_panic_if_not(in_index_map[ch] < ChanPos_Max); + roc_panic_if_not(out_index_map[ch] < ChanPos_Max + && in_index_map[ch] < ChanPos_Max); + if (out_index_set.has_channel(ch) && in_index_set.has_channel(ch)) { map_matrix_[out_index_map[ch]][in_index_map[ch]] = 1.f; } } diff --git a/src/internal_modules/roc_audio/channel_mapper_table.cpp b/src/internal_modules/roc_audio/channel_mapper_table.cpp index 30999736a..4c4b5a234 100644 --- a/src/internal_modules/roc_audio/channel_mapper_table.cpp +++ b/src/internal_modules/roc_audio/channel_mapper_table.cpp @@ -11,6 +11,65 @@ namespace roc { namespace audio { +// These tables define supported channel orders. +// +// When channel order is applied, the list of channels is filtered, and only +// channels present in channel mask are kept. The resulting filtered list +// defines how channels are placed in memory. +// +// This allows us to define single list that for multiple channel masks. +// For example, ITU/SMPTE defines order for each channel mask (5.x, 7.x), +// but we define only one list ChanOrder_Smpte, and after filtering it +// becomes suitable for each of the masks. +// +// The opposite is also true: if some channel is missing from the order's +// list, it is considered unsupported by the order and is zeroized. + +const ChannelList chan_orders[ChanOrder_Max] = { + // ChanOrder_None + { + { + ChanPos_Max, + }, + }, + // ChanOrder_Smpte + { + { + ChanPos_FrontLeft, + ChanPos_FrontRight, + ChanPos_FrontCenter, + ChanPos_LowFrequency, + ChanPos_BackLeft, + ChanPos_BackRight, + ChanPos_BackCenter, + ChanPos_SideLeft, + ChanPos_SideRight, + ChanPos_TopFrontLeft, + ChanPos_TopFrontRight, + ChanPos_TopMidLeft, + ChanPos_TopMidRight, + ChanPos_TopBackLeft, + ChanPos_TopBackRight, + ChanPos_Max, + }, + }, + // ChanOrder_Alsa + { + { + ChanPos_FrontLeft, + ChanPos_FrontRight, + ChanPos_BackLeft, + ChanPos_BackRight, + ChanPos_FrontCenter, + ChanPos_LowFrequency, + ChanPos_SideLeft, + ChanPos_SideRight, + ChanPos_BackCenter, + ChanPos_Max, + }, + }, +}; + // These tables define downmixing coefficients for mapping between different // surround channel sets. They are used for both downmixing and upmixing. // diff --git a/src/internal_modules/roc_audio/channel_mapper_table.h b/src/internal_modules/roc_audio/channel_mapper_table.h index 611d8fb71..205b39aa5 100644 --- a/src/internal_modules/roc_audio/channel_mapper_table.h +++ b/src/internal_modules/roc_audio/channel_mapper_table.h @@ -41,6 +41,11 @@ struct ChannelMap { ChannelMapRule rules[24]; }; +//! Defines ordered list of channels. +struct ChannelList { + ChannelPosition chans[ChanPos_Max + 1]; //!< Channels. +}; + //! Number of defined channel mappings. const size_t chan_map_count = 40; @@ -49,6 +54,10 @@ const size_t chan_map_count = 40; //! based on input and output channel masks. extern const ChannelMap chan_maps[chan_map_count]; +//! Defines mapping of channel order identifier to list of channel positions +//! in corresponding order. +extern const ChannelList chan_orders[ChanOrder_Max]; + } // namespace audio } // namespace roc diff --git a/src/tests/roc_audio/test_channel_mapper.cpp b/src/tests/roc_audio/test_channel_mapper.cpp index cb822618c..859149467 100644 --- a/src/tests/roc_audio/test_channel_mapper.cpp +++ b/src/tests/roc_audio/test_channel_mapper.cpp @@ -9,6 +9,7 @@ #include #include "roc_audio/channel_mapper.h" +#include "roc_audio/channel_mapper_table.h" #include "roc_core/macro_helpers.h" namespace roc { @@ -20,8 +21,8 @@ enum { MaxSamples = 100 }; const double Epsilon = 0.000001; -void check(sample_t* input, - sample_t* output, +void check(const sample_t* input, + const sample_t* output, size_t n_samples, ChannelLayout in_layout, ChannelOrder in_order, @@ -40,12 +41,13 @@ void check(sample_t* input, out_chans.set_channel_mask(out_mask); sample_t actual_output[MaxSamples] = {}; + memset(actual_output, 0xff, MaxSamples * sizeof(sample_t)); ChannelMapper mapper(in_chans, out_chans); mapper.map(input, n_samples * in_chans.num_channels(), actual_output, n_samples * out_chans.num_channels()); - for (size_t n = 0; n < n_samples; n++) { + for (size_t n = 0; n < n_samples * out_chans.num_channels(); n++) { DOUBLES_EQUAL(output[n], actual_output[n], Epsilon); } } @@ -54,126 +56,139 @@ void check(sample_t* input, TEST_GROUP(channel_mapper) {}; -TEST(channel_mapper, mono_mono) { +// verbatim copy +TEST(channel_mapper, mono_to_mono) { enum { NumSamples = 5, InChans = ChanMask_Surround_Mono, OutChans = ChanMask_Surround_Mono }; - sample_t input[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t input[NumSamples] = { + // FC + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; - sample_t output[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t output[NumSamples] = { + // FC + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, mono_stereo) { +// upmixing +TEST(channel_mapper, mono_to_stereo) { enum { NumSamples = 5, InChans = ChanMask_Surround_Mono, OutChans = ChanMask_Surround_Stereo }; - sample_t input[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t input[NumSamples] = { + // FC + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.1f, 0.1f, // - 0.2f, 0.2f, // - 0.3f, 0.3f, // - 0.4f, 0.4f, // - 0.5f, 0.5f, // + const sample_t output[NumSamples * 2] = { + // FL FR + 0.01f, 0.01f, // 0 + 0.02f, 0.02f, // 1 + 0.03f, 0.03f, // 2 + 0.04f, 0.04f, // 3 + 0.05f, 0.05f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, stereo_mono) { +// downmixing +TEST(channel_mapper, stereo_to_mono) { enum { NumSamples = 5, InChans = ChanMask_Surround_Stereo, OutChans = ChanMask_Surround_Mono }; - sample_t input[NumSamples * 2] = { - 0.1f, 0.3f, // - 0.2f, 0.4f, // - 0.3f, 0.5f, // - 0.4f, 0.6f, // - 0.5f, 0.7f, // + const sample_t input[NumSamples * 2] = { + // FL FR + 0.01f, 0.03f, // 0 + 0.02f, 0.04f, // 1 + 0.03f, 0.05f, // 2 + 0.04f, 0.06f, // 3 + 0.05f, 0.07f, // 4 }; - sample_t output[NumSamples] = { - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // - 0.6f, // + const sample_t output[NumSamples] = { + // FC + 0.02f, // 0 + 0.03f, // 1 + 0.04f, // 2 + 0.05f, // 3 + 0.06f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, stereo_stereo) { +// verbatim copy +TEST(channel_mapper, stereo_to_stereo) { enum { NumSamples = 5, InChans = ChanMask_Surround_Stereo, OutChans = ChanMask_Surround_Stereo }; - sample_t input[NumSamples * 2] = { - 0.1f, 0.3f, // - 0.2f, 0.4f, // - 0.3f, 0.5f, // - 0.4f, 0.6f, // - 0.5f, 0.7f, // + const sample_t input[NumSamples * 2] = { + // FL FR + 0.01f, 0.03f, // 0 + 0.02f, 0.04f, // 1 + 0.03f, 0.05f, // 2 + 0.04f, 0.06f, // 3 + 0.05f, 0.07f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.1f, 0.3f, // - 0.2f, 0.4f, // - 0.3f, 0.5f, // - 0.4f, 0.6f, // - 0.5f, 0.7f, // + const sample_t output[NumSamples * 2] = { + // FL FR + 0.01f, 0.03f, // 0 + 0.02f, 0.04f, // 1 + 0.03f, 0.05f, // 2 + 0.04f, 0.06f, // 3 + 0.05f, 0.07f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, surround_61_41) { +// downmixing +TEST(channel_mapper, surround_61_to_41) { enum { NumSamples = 5, InChans = ChanMask_Surround_6_1, OutChans = ChanMask_Surround_4_1 }; - sample_t clev = 1.000f / (1.000f + 0.707f); - sample_t slev = 0.707f / (1.000f + 0.707f); + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); - sample_t input[NumSamples * 7] = { - // FL FC FR BL BC BR LFE + const sample_t input[NumSamples * 7] = { + // FL FR FC LFE BL BR BC 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, // 0 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, // 1 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, // 2 @@ -181,55 +196,56 @@ TEST(channel_mapper, surround_61_41) { 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, // 4 }; - sample_t output[NumSamples * 5] = { + const sample_t output[NumSamples * 5] = { // 0 - clev * 0.01f + slev * 0.02f, // FL - clev * 0.03f + slev * 0.02f, // FR - clev * 0.04f + slev * 0.05f, // BL - clev * 0.06f + slev * 0.05f, // BR - 0.07f, // LFE + clev * 0.01f + slev * 0.03f, // FL + clev * 0.02f + slev * 0.03f, // FR + 0.04f, // LFE + clev * 0.05f + slev * 0.07f, // BL + clev * 0.06f + slev * 0.07f, // BR // 1 - clev * 0.11f + slev * 0.12f, // FL - clev * 0.13f + slev * 0.12f, // FR - clev * 0.14f + slev * 0.15f, // BL - clev * 0.16f + slev * 0.15f, // BR - 0.17f, // LFE + clev * 0.11f + slev * 0.13f, // FL + clev * 0.12f + slev * 0.13f, // FR + 0.14f, // LFE + clev * 0.15f + slev * 0.17f, // BL + clev * 0.16f + slev * 0.17f, // BR // 2 - clev * 0.21f + slev * 0.22f, // FL - clev * 0.23f + slev * 0.22f, // FR - clev * 0.24f + slev * 0.25f, // BL - clev * 0.26f + slev * 0.25f, // BR - 0.27f, // LFE + clev * 0.21f + slev * 0.23f, // FL + clev * 0.22f + slev * 0.23f, // FR + 0.24f, // LFE + clev * 0.25f + slev * 0.27f, // BL + clev * 0.26f + slev * 0.27f, // BR // 3 - clev * 0.31f + slev * 0.32f, // FL - clev * 0.33f + slev * 0.32f, // FR - clev * 0.34f + slev * 0.35f, // BL - clev * 0.36f + slev * 0.35f, // BR - 0.37f, // LFE + clev * 0.31f + slev * 0.33f, // FL + clev * 0.32f + slev * 0.33f, // FR + 0.34f, // LFE + clev * 0.35f + slev * 0.37f, // BL + clev * 0.36f + slev * 0.37f, // BR // 4 - clev * 0.41f + slev * 0.42f, // FL - clev * 0.43f + slev * 0.42f, // FR - clev * 0.44f + slev * 0.45f, // BL - clev * 0.46f + slev * 0.45f, // BR - 0.47f, // LFE + clev * 0.41f + slev * 0.43f, // FL + clev * 0.42f + slev * 0.43f, // FR + 0.44f, // LFE + clev * 0.45f + slev * 0.47f, // BL + clev * 0.46f + slev * 0.47f, // BR }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, surround_60_41) { +// downmixing + adding zero LFE +TEST(channel_mapper, surround_60_to_41) { enum { NumSamples = 5, InChans = ChanMask_Surround_6_0, OutChans = ChanMask_Surround_4_1 }; - sample_t clev = 1.000f / (1.000f + 0.707f); - sample_t slev = 0.707f / (1.000f + 0.707f); + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); - sample_t input[NumSamples * 7] = { - // FL FC FR BL BC BR + const sample_t input[NumSamples * 7] = { + // FL FR FC BL BR BC 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, // 0 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, // 1 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, // 2 @@ -237,55 +253,56 @@ TEST(channel_mapper, surround_60_41) { 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, // 4 }; - sample_t output[NumSamples * 5] = { + const sample_t output[NumSamples * 5] = { // 0 - clev * 0.01f + slev * 0.02f, // FL - clev * 0.03f + slev * 0.02f, // FR - clev * 0.04f + slev * 0.05f, // BL - clev * 0.06f + slev * 0.05f, // BR + clev * 0.01f + slev * 0.03f, // FL + clev * 0.02f + slev * 0.03f, // FR 0.f, // LFE + clev * 0.04f + slev * 0.06f, // BL + clev * 0.05f + slev * 0.06f, // BR // 1 - clev * 0.11f + slev * 0.12f, // FL - clev * 0.13f + slev * 0.12f, // FR - clev * 0.14f + slev * 0.15f, // BL - clev * 0.16f + slev * 0.15f, // BR + clev * 0.11f + slev * 0.13f, // FL + clev * 0.12f + slev * 0.13f, // FR 0.f, // LFE + clev * 0.14f + slev * 0.16f, // BL + clev * 0.15f + slev * 0.16f, // BR // 2 - clev * 0.21f + slev * 0.22f, // FL - clev * 0.23f + slev * 0.22f, // FR - clev * 0.24f + slev * 0.25f, // BL - clev * 0.26f + slev * 0.25f, // BR + clev * 0.21f + slev * 0.23f, // FL + clev * 0.22f + slev * 0.23f, // FR 0.f, // LFE + clev * 0.24f + slev * 0.26f, // BL + clev * 0.25f + slev * 0.26f, // BR // 3 - clev * 0.31f + slev * 0.32f, // FL - clev * 0.33f + slev * 0.32f, // FR - clev * 0.34f + slev * 0.35f, // BL - clev * 0.36f + slev * 0.35f, // BR + clev * 0.31f + slev * 0.33f, // FL + clev * 0.32f + slev * 0.33f, // FR 0.f, // LFE + clev * 0.34f + slev * 0.36f, // BL + clev * 0.35f + slev * 0.36f, // BR // 4 - clev * 0.41f + slev * 0.42f, // FL - clev * 0.43f + slev * 0.42f, // FR - clev * 0.44f + slev * 0.45f, // BL - clev * 0.46f + slev * 0.45f, // BR + clev * 0.41f + slev * 0.43f, // FL + clev * 0.42f + slev * 0.43f, // FR 0.f, // LFE + clev * 0.44f + slev * 0.46f, // BL + clev * 0.45f + slev * 0.46f, // BR }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, surround_61_40) { +// downmixing + removing LFE +TEST(channel_mapper, surround_61_to_40) { enum { NumSamples = 5, InChans = ChanMask_Surround_6_1, OutChans = ChanMask_Surround_4_0 }; - sample_t clev = 1.000f / (1.000f + 0.707f); - sample_t slev = 0.707f / (1.000f + 0.707f); + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); - sample_t input[NumSamples * 7] = { - // FL FC FR BL BC BR LFE + const sample_t input[NumSamples * 7] = { + // FL FR FC LFE BL BR BC 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, // 0 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, // 1 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, // 2 @@ -293,39 +310,40 @@ TEST(channel_mapper, surround_61_40) { 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, // 4 }; - sample_t output[NumSamples * 5] = { + const sample_t output[NumSamples * 5] = { // 0 - clev * 0.01f + slev * 0.02f, // FL - clev * 0.03f + slev * 0.02f, // FR - clev * 0.04f + slev * 0.05f, // BL - clev * 0.06f + slev * 0.05f, // BR + clev * 0.01f + slev * 0.03f, // FL + clev * 0.02f + slev * 0.03f, // FR + clev * 0.05f + slev * 0.07f, // BL + clev * 0.06f + slev * 0.07f, // BR // 1 - clev * 0.11f + slev * 0.12f, // FL - clev * 0.13f + slev * 0.12f, // FR - clev * 0.14f + slev * 0.15f, // BL - clev * 0.16f + slev * 0.15f, // BR + clev * 0.11f + slev * 0.13f, // FL + clev * 0.12f + slev * 0.13f, // FR + clev * 0.15f + slev * 0.17f, // BL + clev * 0.16f + slev * 0.17f, // BR // 2 - clev * 0.21f + slev * 0.22f, // FL - clev * 0.23f + slev * 0.22f, // FR - clev * 0.24f + slev * 0.25f, // BL - clev * 0.26f + slev * 0.25f, // BR + clev * 0.21f + slev * 0.23f, // FL + clev * 0.22f + slev * 0.23f, // FR + clev * 0.25f + slev * 0.27f, // BL + clev * 0.26f + slev * 0.27f, // BR // 3 - clev * 0.31f + slev * 0.32f, // FL - clev * 0.33f + slev * 0.32f, // FR - clev * 0.34f + slev * 0.35f, // BL - clev * 0.36f + slev * 0.35f, // BR + clev * 0.31f + slev * 0.33f, // FL + clev * 0.32f + slev * 0.33f, // FR + clev * 0.35f + slev * 0.37f, // BL + clev * 0.36f + slev * 0.37f, // BR // 4 - clev * 0.41f + slev * 0.42f, // FL - clev * 0.43f + slev * 0.42f, // FR - clev * 0.44f + slev * 0.45f, // BL - clev * 0.46f + slev * 0.45f, // BR + clev * 0.41f + slev * 0.43f, // FL + clev * 0.42f + slev * 0.43f, // FR + clev * 0.45f + slev * 0.47f, // BL + clev * 0.46f + slev * 0.47f, // BR }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, surround_6x_4x) { +// handling of incomplete masks +TEST(channel_mapper, surround_6x_to_4x) { enum { NumSamples = 5, // missing FC @@ -337,11 +355,11 @@ TEST(channel_mapper, surround_6x_4x) { (1 << ChanPos_FrontLeft) | (1 << ChanPos_BackLeft) | (1 << ChanPos_BackRight) }; - sample_t clev = 1.000f / (1.000f + 0.707f); - sample_t slev = 0.707f / (1.000f + 0.707f); + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); - sample_t input[NumSamples * 5] = { - // FL FR BL BC BR + const sample_t input[NumSamples * 5] = { + // FL FR BL BR BC 0.01f, 0.03f, 0.04f, 0.05f, 0.06f, // 0 0.11f, 0.13f, 0.14f, 0.15f, 0.16f, // 1 0.21f, 0.23f, 0.24f, 0.25f, 0.26f, // 2 @@ -349,44 +367,45 @@ TEST(channel_mapper, surround_6x_4x) { 0.41f, 0.43f, 0.44f, 0.45f, 0.46f, // 4 }; - sample_t output[NumSamples * 3] = { + const sample_t output[NumSamples * 3] = { // 0 0.01f, // FL - clev * 0.04f + slev * 0.05f, // BL - clev * 0.06f + slev * 0.05f, // BR + clev * 0.04f + slev * 0.06f, // BL + clev * 0.05f + slev * 0.06f, // BR // 1 0.11f, // FL - clev * 0.14f + slev * 0.15f, // BL - clev * 0.16f + slev * 0.15f, // BR + clev * 0.14f + slev * 0.16f, // BL + clev * 0.15f + slev * 0.16f, // BR // 2 0.21f, // FL - clev * 0.24f + slev * 0.25f, // BL - clev * 0.26f + slev * 0.25f, // BR + clev * 0.24f + slev * 0.26f, // BL + clev * 0.25f + slev * 0.26f, // BR // 3 0.31f, // FL - clev * 0.34f + slev * 0.35f, // BL - clev * 0.36f + slev * 0.35f, // BR + clev * 0.34f + slev * 0.36f, // BL + clev * 0.35f + slev * 0.36f, // BR // 4 0.41f, // FL - clev * 0.44f + slev * 0.45f, // BL - clev * 0.46f + slev * 0.45f, // BR + clev * 0.44f + slev * 0.46f, // BL + clev * 0.45f + slev * 0.46f, // BR }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, surround_41_61) { +// upmixing +TEST(channel_mapper, surround_41_to_61) { enum { NumSamples = 5, InChans = ChanMask_Surround_4_1, OutChans = ChanMask_Surround_6_1 }; - sample_t lev = (1.f / 0.707f) / (2.f / 0.707f); + const sample_t lev = (1.f / 0.707f) / (2.f / 0.707f); - sample_t input[NumSamples * 5] = { - // FL FR BL BR LFE + const sample_t input[NumSamples * 5] = { + // FL FR LFE BL BR 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, // 0 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, // 1 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, // 2 @@ -394,53 +413,54 @@ TEST(channel_mapper, surround_41_61) { 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, // 4 }; - sample_t output[NumSamples * 7] = { + const sample_t output[NumSamples * 7] = { // 0 0.01f, // FL - lev * 0.01f + lev * 0.02f, // FC 0.02f, // FR - 0.03f, // BL - lev * 0.03f + lev * 0.04f, // BC - 0.04f, // BR - 0.05f, // LFE + lev * 0.01f + lev * 0.02f, // FC + 0.03f, // LFE + 0.04f, // BL + 0.05f, // BR + lev * 0.04f + lev * 0.05f, // BC // 1 0.11f, // FL - lev * 0.11f + lev * 0.12f, // FC 0.12f, // FR - 0.13f, // BL - lev * 0.13f + lev * 0.14f, // BC - 0.14f, // BR - 0.15f, // LFE + lev * 0.11f + lev * 0.12f, // FC + 0.13f, // LFE + 0.14f, // BL + 0.15f, // BR + lev * 0.14f + lev * 0.15f, // BC // 2 0.21f, // FL - lev * 0.21f + lev * 0.22f, // FC 0.22f, // FR - 0.23f, // BL - lev * 0.23f + lev * 0.24f, // BC - 0.24f, // BR - 0.25f, // LFE + lev * 0.21f + lev * 0.22f, // FC + 0.23f, // LFE + 0.24f, // BL + 0.25f, // BR + lev * 0.24f + lev * 0.25f, // BC // 3 0.31f, // FL - lev * 0.31f + lev * 0.32f, // FC 0.32f, // FR - 0.33f, // BL - lev * 0.33f + lev * 0.34f, // BC - 0.34f, // BR - 0.35f, // LFE + lev * 0.31f + lev * 0.32f, // FC + 0.33f, // LFE + 0.34f, // BL + 0.35f, // BR + lev * 0.34f + lev * 0.35f, // BC // 4 0.41f, // FL - lev * 0.41f + lev * 0.42f, // FC 0.42f, // FR - 0.43f, // BL - lev * 0.43f + lev * 0.44f, // BC - 0.44f, // BR - 0.45f, // LFE + lev * 0.41f + lev * 0.42f, // FC + 0.43f, // LFE + 0.44f, // BL + 0.45f, // BR + lev * 0.44f + lev * 0.45f, // BC }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } +// input has one non-zero channel TEST(channel_mapper, surround_1ch) { enum { NumSamples = 5, @@ -481,9 +501,12 @@ TEST(channel_mapper, surround_1ch) { for (size_t ch = 0; ch < ChanPos_Max; ch++) { if (in_chans.has_channel(ch) && out_chans.has_channel(ch)) { size_t in_off = 0; - for (size_t in_ch = in_chans.first_channel(); - in_ch <= in_chans.last_channel(); in_ch++) { - if (in_ch == ch) { + for (size_t order_off = 0; + chan_orders[ChanOrder_Smpte].chans[order_off] != ChanPos_Max; + order_off++) { + const ChannelPosition in_ch = + chan_orders[ChanOrder_Smpte].chans[order_off]; + if (in_ch == (ChannelPosition)ch) { break; } if (in_chans.has_channel(in_ch)) { @@ -492,9 +515,12 @@ TEST(channel_mapper, surround_1ch) { } size_t out_off = 0; - for (size_t out_ch = out_chans.first_channel(); - out_ch <= out_chans.last_channel(); out_ch++) { - if (out_ch == ch) { + for (size_t order_off = 0; + chan_orders[ChanOrder_Smpte].chans[order_off] != ChanPos_Max; + order_off++) { + const ChannelPosition out_ch = + chan_orders[ChanOrder_Smpte].chans[order_off]; + if (out_ch == (ChannelPosition)ch) { break; } if (out_chans.has_channel(out_ch)) { @@ -522,184 +548,566 @@ TEST(channel_mapper, surround_1ch) { } } -TEST(channel_mapper, mono_multitrack) { - enum { NumSamples = 5, InChans = ChanMask_Surround_Mono, OutChans = 0x88 }; +// reordering without remixing +TEST(channel_mapper, surround_61_smpte_to_61_alsa) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_6_1, + OutChans = ChanMask_Surround_6_1 + }; + + const sample_t input[NumSamples * 7] = { + // FL FR FC LFE BL BR BC + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.37f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, // 4 + }; + + const sample_t output[NumSamples * 7] = { + // FL FR BL BR FC LFE BC + 0.01f, 0.02f, 0.05f, 0.06f, 0.03f, 0.04f, 0.07f, // 0 + 0.11f, 0.12f, 0.15f, 0.16f, 0.13f, 0.14f, 0.17f, // 1 + 0.21f, 0.22f, 0.25f, 0.26f, 0.23f, 0.24f, 0.27f, // 2 + 0.31f, 0.32f, 0.35f, 0.36f, 0.33f, 0.34f, 0.37f, // 3 + 0.41f, 0.42f, 0.45f, 0.46f, 0.43f, 0.44f, 0.47f, // 4 + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, + ChanLayout_Surround, ChanOrder_Alsa, OutChans); +} + +// reordering without remixing +TEST(channel_mapper, surround_61_alsa_to_61_smpte) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_6_1, + OutChans = ChanMask_Surround_6_1 + }; + + const sample_t input[NumSamples * 7] = { + // FL FR BL BR FC LFE BC + 0.01f, 0.02f, 0.05f, 0.06f, 0.03f, 0.04f, 0.07f, // 0 + 0.11f, 0.12f, 0.15f, 0.16f, 0.13f, 0.14f, 0.17f, // 1 + 0.21f, 0.22f, 0.25f, 0.26f, 0.23f, 0.24f, 0.27f, // 2 + 0.31f, 0.32f, 0.35f, 0.36f, 0.33f, 0.34f, 0.37f, // 3 + 0.41f, 0.42f, 0.45f, 0.46f, 0.43f, 0.44f, 0.47f, // 4 + }; + + const sample_t output[NumSamples * 7] = { + // FL FR FC LFE BL BR BC + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.37f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, // 4 + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Alsa, InChans, + ChanLayout_Surround, ChanOrder_Smpte, OutChans); +} + +// downmixing + reordering +TEST(channel_mapper, surround_61_smpte_to_41_alsa) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_6_1, + OutChans = ChanMask_Surround_4_1 + }; + + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); + + const sample_t input[NumSamples * 7] = { + // FL FR FC LFE BL BR BC + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.37f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, // 4 + }; + + const sample_t output[NumSamples * 5] = { + // 0 + clev * 0.01f + slev * 0.03f, // FL + clev * 0.02f + slev * 0.03f, // FR + clev * 0.05f + slev * 0.07f, // BL + clev * 0.06f + slev * 0.07f, // BR + 0.04f, // LFE + // 1 + clev * 0.11f + slev * 0.13f, // FL + clev * 0.12f + slev * 0.13f, // FR + clev * 0.15f + slev * 0.17f, // BL + clev * 0.16f + slev * 0.17f, // BR + 0.14f, // LFE + // 2 + clev * 0.21f + slev * 0.23f, // FL + clev * 0.22f + slev * 0.23f, // FR + clev * 0.25f + slev * 0.27f, // BL + clev * 0.26f + slev * 0.27f, // BR + 0.24f, // LFE + // 3 + clev * 0.31f + slev * 0.33f, // FL + clev * 0.32f + slev * 0.33f, // FR + clev * 0.35f + slev * 0.37f, // BL + clev * 0.36f + slev * 0.37f, // BR + 0.34f, // LFE + // 4 + clev * 0.41f + slev * 0.43f, // FL + clev * 0.42f + slev * 0.43f, // FR + clev * 0.45f + slev * 0.47f, // BL + clev * 0.46f + slev * 0.47f, // BR + 0.44f, // LFE + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, + ChanLayout_Surround, ChanOrder_Alsa, OutChans); +} + +// upmixing + reordering +TEST(channel_mapper, surround_41_alsa_to_61_smpte) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_4_1, + OutChans = ChanMask_Surround_6_1 + }; + + const sample_t lev = (1.f / 0.707f) / (2.f / 0.707f); + + const sample_t input[NumSamples * 5] = { + // FL FR BL BR LFE + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, // 4 + }; + + const sample_t output[NumSamples * 7] = { + // 0 + 0.01f, // FL + 0.02f, // FR + lev * 0.01f + lev * 0.02f, // FC + 0.05f, // LFE + 0.03f, // BL + 0.04f, // BR + lev * 0.03f + lev * 0.04f, // BC + // 1 + 0.11f, // FL + 0.12f, // FR + lev * 0.11f + lev * 0.12f, // FC + 0.15f, // LFE + 0.13f, // BL + 0.14f, // BR + lev * 0.13f + lev * 0.14f, // BC + // 2 + 0.21f, // FL + 0.22f, // FR + lev * 0.21f + lev * 0.22f, // FC + 0.25f, // LFE + 0.23f, // BL + 0.24f, // BR + lev * 0.23f + lev * 0.24f, // BC + // 3 + 0.31f, // FL + 0.32f, // FR + lev * 0.31f + lev * 0.32f, // FC + 0.35f, // LFE + 0.33f, // BL + 0.34f, // BR + lev * 0.33f + lev * 0.34f, // BC + // 4 + 0.41f, // FL + 0.42f, // FR + lev * 0.41f + lev * 0.42f, // FC + 0.45f, // LFE + 0.43f, // BL + 0.44f, // BR + lev * 0.43f + lev * 0.44f, // BC + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Alsa, InChans, + ChanLayout_Surround, ChanOrder_Smpte, OutChans); +} + +// channels unsupported by output order are set to zero +TEST(channel_mapper, surround_512_smpte_to_512_alsa) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_5_1_2, + OutChans = ChanMask_Surround_5_1_2 + }; + + const sample_t clev = 1.000f / (1.000f + 0.707f); + const sample_t slev = 0.707f / (1.000f + 0.707f); + + const sample_t input[NumSamples * 8] = { + // FL FR FC LFE BL BR TML TMR + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.07f, 0.08f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.17f, 0.18f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.27f, 0.28f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.37f, 0.38f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.47f, 0.48f, // 4 + }; + + const sample_t output[NumSamples * 8] = { + // 0 + clev * 0.01f + slev * 0.07f, // FL + clev * 0.02f + slev * 0.08f, // FR + clev * 0.05f + slev * 0.07f, // BL + clev * 0.06f + slev * 0.08f, // BR + 0.03f, // FC + 0.04f, // LFE + 0.00f, // - + 0.00f, // - + // 1 + clev * 0.11f + slev * 0.17f, // FL + clev * 0.12f + slev * 0.18f, // FR + clev * 0.15f + slev * 0.17f, // BL + clev * 0.16f + slev * 0.18f, // BR + 0.13f, // FC + 0.14f, // LFE + 0.00f, // - + 0.00f, // - + // 2 + clev * 0.21f + slev * 0.27f, // FL + clev * 0.22f + slev * 0.28f, // FR + clev * 0.25f + slev * 0.27f, // BL + clev * 0.26f + slev * 0.28f, // BR + 0.23f, // FC + 0.24f, // LFE + 0.00f, // - + 0.00f, // - + // 3 + clev * 0.31f + slev * 0.37f, // FL + clev * 0.32f + slev * 0.38f, // FR + clev * 0.35f + slev * 0.37f, // BL + clev * 0.36f + slev * 0.38f, // BR + 0.33f, // FC + 0.34f, // LFE + 0.00f, // - + 0.00f, // - + // 4 + clev * 0.41f + slev * 0.47f, // FL + clev * 0.42f + slev * 0.48f, // FR + clev * 0.45f + slev * 0.47f, // BL + clev * 0.46f + slev * 0.48f, // BR + 0.43f, // FC + 0.44f, // LFE + 0.00f, // - + 0.00f, // - + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, + ChanLayout_Surround, ChanOrder_Alsa, OutChans); +} + +// channels unsupported by input order are ignored +TEST(channel_mapper, surround_512_alsa_to_512_smpte) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_5_1_2, + OutChans = ChanMask_Surround_5_1_2 + }; + + const sample_t lev = (1.f / 0.707f) / (2.f / 0.707f); + + const sample_t input[NumSamples * 8] = { + // FL FR BL BR FC LFE - - + 0.01f, 0.02f, 0.03f, 0.04f, 0.05f, 0.06f, 0.99f, 0.99f, // 0 + 0.11f, 0.12f, 0.13f, 0.14f, 0.15f, 0.16f, 0.99f, 0.99f, // 1 + 0.21f, 0.22f, 0.23f, 0.24f, 0.25f, 0.26f, 0.99f, 0.99f, // 2 + 0.31f, 0.32f, 0.33f, 0.34f, 0.35f, 0.36f, 0.99f, 0.99f, // 3 + 0.41f, 0.42f, 0.43f, 0.44f, 0.45f, 0.46f, 0.99f, 0.99f, // 4 + }; + + const sample_t output[NumSamples * 8] = { + // 0 + 0.01f, // FL + 0.02f, // FR + 0.05f, // FC + 0.06f, // LFE + 0.03f, // BL + 0.04f, // BR + lev * 0.01f + lev * 0.03f, // TML + lev * 0.02f + lev * 0.04f, // TMR + // 1 + 0.11f, // FL + 0.12f, // FR + 0.15f, // FC + 0.16f, // LFE + 0.13f, // BL + 0.14f, // BR + lev * 0.11f + lev * 0.13f, // TML + lev * 0.12f + lev * 0.14f, // TMR + // 2 + 0.21f, // FL + 0.22f, // FR + 0.25f, // FC + 0.26f, // LFE + 0.23f, // BL + 0.24f, // BR + lev * 0.21f + lev * 0.23f, // TML + lev * 0.22f + lev * 0.24f, // TMR + // 3 + 0.31f, // FL + 0.32f, // FR + 0.35f, // FC + 0.36f, // LFE + 0.33f, // BL + 0.34f, // BR + lev * 0.31f + lev * 0.33f, // TML + lev * 0.32f + lev * 0.34f, // TMR + // 4 + 0.41f, // FL + 0.42f, // FR + 0.45f, // FC + 0.46f, // LFE + 0.43f, // BL + 0.44f, // BR + lev * 0.41f + lev * 0.43f, // TML + lev * 0.42f + lev * 0.44f, // TMR + }; + + check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Alsa, InChans, + ChanLayout_Surround, ChanOrder_Smpte, OutChans); +} + +// copy first channel from input, set rest to zero +TEST(channel_mapper, mono_to_multitrack) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_Mono, // FC + OutChans = 0x88 // C3, C7 + }; - sample_t input[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t input[NumSamples] = { + // FC + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.1f, 0.0f, // - 0.2f, 0.0f, // - 0.3f, 0.0f, // - 0.4f, 0.0f, // - 0.5f, 0.0f, // + const sample_t output[NumSamples * 2] = { + // C3 C7 + 0.01f, 0.00f, // 0 + 0.02f, 0.00f, // 1 + 0.03f, 0.00f, // 2 + 0.04f, 0.00f, // 3 + 0.05f, 0.00f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Multitrack, ChanOrder_None, OutChans); } -TEST(channel_mapper, stereo_multitrack) { - enum { NumSamples = 5, InChans = ChanMask_Surround_Stereo, OutChans = 0x888 }; +// copy first two channels from input, set rest to zero +TEST(channel_mapper, stereo_to_multitrack) { + enum { + NumSamples = 5, + InChans = ChanMask_Surround_Stereo, // FL, FR + OutChans = 0x888 // C3, C7, C11 + }; - sample_t input[NumSamples * 2] = { - 0.1f, -0.1f, // - 0.2f, -0.2f, // - 0.3f, -0.3f, // - 0.4f, -0.4f, // - 0.5f, -0.5f, // + const sample_t input[NumSamples * 2] = { + // FL FR + 0.01f, -0.01f, // 0 + 0.02f, -0.02f, // 1 + 0.03f, -0.03f, // 2 + 0.04f, -0.04f, // 3 + 0.05f, -0.05f, // 4 }; - sample_t output[NumSamples * 3] = { - 0.1f, -0.1f, 0.0f, // - 0.2f, -0.2f, 0.0f, // - 0.3f, -0.3f, 0.0f, // - 0.4f, -0.4f, 0.0f, // - 0.5f, -0.5f, 0.0f, // + const sample_t output[NumSamples * 3] = { + // C3 C7 C11 + 0.01f, -0.01f, 0.00f, // 0 + 0.02f, -0.02f, 0.00f, // 1 + 0.03f, -0.03f, 0.00f, // 2 + 0.04f, -0.04f, 0.00f, // 3 + 0.05f, -0.05f, 0.00f, // 4 }; check(input, output, NumSamples, ChanLayout_Surround, ChanOrder_Smpte, InChans, ChanLayout_Multitrack, ChanOrder_None, OutChans); } -TEST(channel_mapper, multitrack_mono) { - enum { NumSamples = 5, InChans = 0x88, OutChans = ChanMask_Surround_Mono }; +// copy first channel to output, ignore rest +TEST(channel_mapper, multitrack_to_mono) { + enum { + NumSamples = 5, + InChans = 0x88, // C3, C7 + OutChans = ChanMask_Surround_Mono // FC + }; - sample_t input[NumSamples * 2] = { - 0.1f, -0.1f, // - 0.2f, -0.2f, // - 0.3f, -0.3f, // - 0.4f, -0.4f, // - 0.5f, -0.5f, // + const sample_t input[NumSamples * 2] = { + // C3 C7 + 0.01f, -0.01f, // 0 + 0.02f, -0.02f, // 1 + 0.03f, -0.03f, // 2 + 0.04f, -0.04f, // 3 + 0.05f, -0.05f, // 4 }; - sample_t output[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t output[NumSamples] = { + // FC + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } -TEST(channel_mapper, multitrack_stereo) { - enum { NumSamples = 5, InChans = 0x888, OutChans = ChanMask_Surround_Stereo }; +// copy first two channels to output, ignore rest +TEST(channel_mapper, multitrack_to_stereo) { + enum { + NumSamples = 5, + InChans = 0x888, // C3, C7, C11 + OutChans = ChanMask_Surround_Stereo // FL, FR + }; - sample_t input[NumSamples * 3] = { - 0.1f, -0.1f, 0.33f, // - 0.2f, -0.2f, 0.33f, // - 0.3f, -0.3f, 0.33f, // - 0.4f, -0.4f, 0.33f, // - 0.5f, -0.5f, 0.33f, // + const sample_t input[NumSamples * 3] = { + // C3 C7 C11 + 0.01f, -0.01f, 0.33f, // 0 + 0.02f, -0.02f, 0.33f, // 1 + 0.03f, -0.03f, 0.33f, // 2 + 0.04f, -0.04f, 0.33f, // 3 + 0.05f, -0.05f, 0.33f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.1f, -0.1f, // - 0.2f, -0.2f, // - 0.3f, -0.3f, // - 0.4f, -0.4f, // - 0.5f, -0.5f, // + const sample_t output[NumSamples * 2] = { + // FL FR + 0.01f, -0.01f, // 0 + 0.02f, -0.02f, // 1 + 0.03f, -0.03f, // 2 + 0.04f, -0.04f, // 3 + 0.05f, -0.05f, // 4 }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, ChanLayout_Surround, ChanOrder_Smpte, OutChans); } +// verbatim copy TEST(channel_mapper, multitrack_same) { - enum { NumSamples = 5, InChans = 0x3, OutChans = 0x3 }; + enum { + NumSamples = 5, + InChans = 0x3, // C0, C1 + OutChans = 0x3 // C0, C1 + }; - sample_t input[NumSamples * 2] = { - 0.1f, 0.2f, // - 0.3f, 0.4f, // - 0.5f, 0.6f, // - 0.7f, 0.8f, // - 0.9f, 1.0f, // + const sample_t input[NumSamples * 2] = { + // C0 C1 + 0.01f, 0.02f, // 0 + 0.03f, 0.04f, // 1 + 0.05f, 0.06f, // 2 + 0.07f, 0.08f, // 3 + 0.09f, 1.00f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.1f, 0.2f, // - 0.3f, 0.4f, // - 0.5f, 0.6f, // - 0.7f, 0.8f, // - 0.9f, 1.0f, // + const sample_t output[NumSamples * 2] = { + // C0 C1 + 0.01f, 0.02f, // 0 + 0.03f, 0.04f, // 1 + 0.05f, 0.06f, // 2 + 0.07f, 0.08f, // 3 + 0.09f, 1.00f, // 4 }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, ChanLayout_Multitrack, ChanOrder_None, OutChans); } +// input mask is subset of output mask TEST(channel_mapper, multitrack_subset) { - enum { NumSamples = 5, InChans = 0x2, OutChans = 0x3 }; + enum { + NumSamples = 5, + InChans = 0x2, // C1 + OutChans = 0x3 // C0, C1 + }; - sample_t input[NumSamples] = { - 0.1f, // - 0.2f, // - 0.3f, // - 0.4f, // - 0.5f, // + const sample_t input[NumSamples] = { + // C1 + 0.01f, // 0 + 0.02f, // 1 + 0.03f, // 2 + 0.04f, // 3 + 0.05f, // 4 }; - sample_t output[NumSamples * 2] = { - 0.0f, 0.1f, // - 0.0f, 0.2f, // - 0.0f, 0.3f, // - 0.0f, 0.4f, // - 0.0f, 0.5f, // + const sample_t output[NumSamples * 2] = { + // C0 C1 + 0.00f, 0.01f, // 0 + 0.00f, 0.02f, // 1 + 0.00f, 0.03f, // 2 + 0.00f, 0.04f, // 3 + 0.00f, 0.05f, // 4 }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, ChanLayout_Multitrack, ChanOrder_None, OutChans); } +// input mask is superset of output mask TEST(channel_mapper, multitrack_superset) { - enum { NumSamples = 5, InChans = 0x7, OutChans = 0x3 }; + enum { + NumSamples = 5, + InChans = 0x7, // C0, C1, C2 + OutChans = 0x3 // C0, C1 + }; - sample_t input[NumSamples * 3] = { - -0.1f, 0.1f, 0.8f, // - -0.2f, 0.2f, 0.8f, // - -0.3f, 0.3f, 0.8f, // - -0.4f, 0.4f, 0.8f, // - -0.5f, 0.5f, 0.8f, // + const sample_t input[NumSamples * 3] = { + // C0 C1 C2 + -0.01f, 0.01f, 0.08f, // + -0.02f, 0.02f, 0.08f, // + -0.03f, 0.03f, 0.08f, // + -0.04f, 0.04f, 0.08f, // + -0.05f, 0.05f, 0.08f, // }; - sample_t output[NumSamples * 2] = { - -0.1f, 0.1f, // - -0.2f, 0.2f, // - -0.3f, 0.3f, // - -0.4f, 0.4f, // - -0.5f, 0.5f, // + const sample_t output[NumSamples * 2] = { + // C0 C1 + -0.01f, 0.01f, // + -0.02f, 0.02f, // + -0.03f, 0.03f, // + -0.04f, 0.04f, // + -0.05f, 0.05f, // }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, ChanLayout_Multitrack, ChanOrder_None, OutChans); } +// input and output masks overlap TEST(channel_mapper, multitrack_overlap) { - enum { NumSamples = 5, InChans = 0x5, OutChans = 0x3 }; - - sample_t input[NumSamples * 3] = { - -0.1f, 0.8f, // - -0.2f, 0.8f, // - -0.3f, 0.8f, // - -0.4f, 0.8f, // - -0.5f, 0.8f, // - }; - - sample_t output[NumSamples * 2] = { - -0.1f, 0.0f, // - -0.2f, 0.0f, // - -0.3f, 0.0f, // - -0.4f, 0.0f, // - -0.5f, 0.0f, // + enum { + NumSamples = 5, + InChans = 0x5, // C0, C2 + OutChans = 0x3 // C0, C1 + }; + + const sample_t input[NumSamples * 3] = { + // C0 C2 + -0.01f, 0.08f, // 0 + -0.02f, 0.08f, // 1 + -0.03f, 0.08f, // 2 + -0.04f, 0.08f, // 3 + -0.05f, 0.08f, // 4 + }; + + const sample_t output[NumSamples * 2] = { + // C0 C1 + -0.01f, 0.00f, // 0 + -0.02f, 0.00f, // 1 + -0.03f, 0.00f, // 2 + -0.04f, 0.00f, // 3 + -0.05f, 0.00f, // 4 }; check(input, output, NumSamples, ChanLayout_Multitrack, ChanOrder_None, InChans, diff --git a/src/tests/roc_audio/test_channel_mapper_table.cpp b/src/tests/roc_audio/test_channel_mapper_table.cpp index 96f14fd9d..2cf1cf85f 100644 --- a/src/tests/roc_audio/test_channel_mapper_table.cpp +++ b/src/tests/roc_audio/test_channel_mapper_table.cpp @@ -48,7 +48,7 @@ static ChannelMask mapped_masks[] = { ChanMask_Surround_7_1_4, // }; -int order(ChannelMask ch_mask) { +int sortpos(ChannelMask ch_mask) { if (ch_mask == 0) { return 0; } @@ -121,18 +121,18 @@ TEST(channel_mapper_table, combinations) { } } -TEST(channel_mapper_table, ordering) { +TEST(channel_mapper_table, sorting) { ChannelMask in_mask = 0; ChannelMask out_mask = 0; for (size_t n = 0; n < ROC_ARRAY_SIZE(chan_maps); n++) { - if (order(chan_maps[n].in_mask) < order(in_mask)) { + if (sortpos(chan_maps[n].in_mask) < sortpos(in_mask)) { fail("unexpected mapping order (input mask is before previous)", chan_maps[n]); } if (in_mask == chan_maps[n].in_mask) { - if (order(chan_maps[n].out_mask) < order(out_mask)) { + if (sortpos(chan_maps[n].out_mask) < sortpos(out_mask)) { fail("unexpected mapping order (output mask is before previous)", chan_maps[n]); } @@ -219,5 +219,33 @@ TEST(channel_mapper_table, completeness) { } } +TEST(channel_mapper_table, orders) { + for (int n = 0; n < ChanOrder_Max; n++) { + CHECK(n >= ChanOrder_None); + CHECK(n < ChanOrder_Max); + + const ChannelOrder order = (ChannelOrder)n; + const ChannelList& chan_list = chan_orders[order]; + + size_t n_chans = 0; + while (chan_list.chans[n_chans] != ChanPos_Max) { + n_chans++; + } + + if (order == ChanOrder_None) { + CHECK(n_chans == 0); + } else { + CHECK(n_chans > 0); + CHECK(n_chans <= ChanPos_Max); + } + + for (size_t i = 0; i < n_chans; i++) { + for (size_t j = i + 1; j < n_chans; j++) { + CHECK(chan_list.chans[i] != chan_list.chans[j]); + } + } + } +} + } // namespace audio } // namespace roc