Skip to content

Commit

Permalink
gh-86 Implement channel order mapping
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
gavv authored and Dmitriy Shilin committed Oct 18, 2023
1 parent de830bd commit 53d218f
Show file tree
Hide file tree
Showing 5 changed files with 845 additions and 327 deletions.
58 changes: 36 additions & 22 deletions src/internal_modules/roc_audio/channel_mapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,38 +202,53 @@ 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++;
}
}

// Find channel map that covers requested transformation.
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;
Expand All @@ -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;
}

Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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;
}
}
Expand Down
59 changes: 59 additions & 0 deletions src/internal_modules/roc_audio/channel_mapper_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
9 changes: 9 additions & 0 deletions src/internal_modules/roc_audio/channel_mapper_table.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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

Expand Down
Loading

0 comments on commit 53d218f

Please sign in to comment.