From: Vittorio Giovara <vittorio.giov...@gmail.com> Signed-off-by: James Almer <jamr...@gmail.com> --- libavutil/channel_layout.c | 166 ++++++++++++++++++++++++++++++- libavutil/channel_layout.h | 52 +++++++++- libavutil/tests/channel_layout.c | 20 ++++ tests/ref/fate/channel_layout | 13 +++ 4 files changed, 247 insertions(+), 4 deletions(-)
diff --git a/libavutil/channel_layout.c b/libavutil/channel_layout.c index 68b40cc37c..3863e50e91 100644 --- a/libavutil/channel_layout.c +++ b/libavutil/channel_layout.c @@ -31,6 +31,9 @@ #include "bprint.h" #include "common.h" +#define CHAN_IS_AMBI(x) ((x) >= AV_CHAN_AMBISONIC_BASE &&\ + (x) <= AV_CHAN_AMBISONIC_END) + struct channel_name { const char *name; const char *description; @@ -81,7 +84,10 @@ void av_channel_name_bprint(AVBPrint *bp, enum AVChannel channel_id) { av_bprint_clear(bp); - if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names)) + if (channel_id >= AV_CHAN_AMBISONIC_BASE && + channel_id <= AV_CHAN_AMBISONIC_END) + av_bprintf(bp, "AMBI%d", channel_id - AV_CHAN_AMBISONIC_BASE); + else if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names)) av_bprintf(bp, "%s", channel_names[channel_id].name); else av_bprintf(bp, "USR%d", channel_id); @@ -104,7 +110,10 @@ void av_channel_description_bprint(AVBPrint *bp, enum AVChannel channel_id) { av_bprint_clear(bp); - if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names)) + if (channel_id >= AV_CHAN_AMBISONIC_BASE && + channel_id <= AV_CHAN_AMBISONIC_END) + av_bprintf(bp, "ambisonic ACN %d", channel_id - AV_CHAN_AMBISONIC_BASE); + else if ((unsigned)channel_id < FF_ARRAY_ELEMS(channel_names)) av_bprintf(bp, "%s", channel_names[channel_id].description); else av_bprintf(bp, "user %d", channel_id); @@ -128,6 +137,14 @@ enum AVChannel av_channel_from_string(const char *str) int i; char *endptr = (char *)str; enum AVChannel id = AV_CHAN_NONE; + + if (!strncmp(str, "AMBI", 4)) { + i = strtol(str + 4, NULL, 0); + if (i < 0 || i > AV_CHAN_AMBISONIC_END - AV_CHAN_AMBISONIC_BASE) + return AV_CHAN_NONE; + return AV_CHAN_AMBISONIC_BASE + i; + } + for (i = 0; i < FF_ARRAY_ELEMS(channel_names); i++) { if (channel_names[i].name && !strcmp(str, channel_names[i].name)) return i; @@ -395,6 +412,60 @@ int av_channel_layout_from_string(AVChannelLayout *channel_layout, } } + /* ambisonic */ + if (!strncmp(str, "ambisonic ", 10)) { + const char *p = str + 10; + char *endptr; + AVChannelLayout extra = {0}; + int order; + + order = strtol(p, &endptr, 0); + if (order < 0 || order + 1 > INT_MAX / (order + 1) || + (*endptr && *endptr != '+')) + return AVERROR(EINVAL); + + channel_layout->order = AV_CHANNEL_ORDER_AMBISONIC; + channel_layout->nb_channels = (order + 1) * (order + 1); + + if (*endptr) { + int ret = av_channel_layout_from_string(&extra, endptr + 1); + if (ret < 0) + return ret; + if (extra.nb_channels >= INT_MAX - channel_layout->nb_channels) { + av_channel_layout_uninit(&extra); + return AVERROR(EINVAL); + } + + if (extra.order == AV_CHANNEL_ORDER_NATIVE) { + channel_layout->u.mask = extra.u.mask; + } else { + channel_layout->order = AV_CHANNEL_ORDER_CUSTOM; + channel_layout->u.map = + av_calloc(channel_layout->nb_channels + extra.nb_channels, + sizeof(*channel_layout->u.map)); + if (!channel_layout->u.map) { + av_channel_layout_uninit(&extra); + return AVERROR(ENOMEM); + } + + for (i = 0; i < channel_layout->nb_channels; i++) + channel_layout->u.map[i].id = AV_CHAN_AMBISONIC_BASE + i; + for (i = 0; i < extra.nb_channels; i++) { + enum AVChannel ch = av_channel_layout_channel_from_index(&extra, i); + if (CHAN_IS_AMBI(ch)) { + av_channel_layout_uninit(&extra); + return AVERROR(EINVAL); + } + channel_layout->u.map[channel_layout->nb_channels + i].id = ch; + } + } + channel_layout->nb_channels += extra.nb_channels; + av_channel_layout_uninit(&extra); + } + + return 0; + } + /* channel names */ while (*dup) { char *chname = av_get_token(&dup, "+"); @@ -526,6 +597,77 @@ int av_channel_layout_copy(AVChannelLayout *dst, const AVChannelLayout *src) return 0; } +/** + * If the custom layout is n-th order standard-order ambisonic, with optional + * extra non-diegetic channels at the end, write its string description in bp. + * Return a negative error code on error. + */ +static int try_describe_ambisonic(AVBPrint *bp, const AVChannelLayout *channel_layout) +{ + int i, highest_ambi, order; + + highest_ambi = -1; + if (channel_layout->order == AV_CHANNEL_ORDER_AMBISONIC) + highest_ambi = channel_layout->nb_channels - av_popcount64(channel_layout->u.mask) - 1; + else { + const AVChannelCustom *map = channel_layout->u.map; + for (i = 0; i < channel_layout->nb_channels; i++) { + int is_ambi = CHAN_IS_AMBI(map[i].id); + + /* ambisonic following non-ambisonic */ + if (i > 0 && is_ambi && !CHAN_IS_AMBI(map[i - 1].id)) + return 0; + + /* non-default ordering */ + if (is_ambi && map[i].id - AV_CHAN_AMBISONIC_BASE != i) + return 0; + + if (CHAN_IS_AMBI(map[i].id)) + highest_ambi = i; + } + } + /* no ambisonic channels*/ + if (highest_ambi < 0) + return 0; + + order = floor(sqrt(highest_ambi)); + /* incomplete order - some harmonics are missing */ + if ((order + 1) * (order + 1) != highest_ambi + 1) + return 0; + + av_bprintf(bp, "ambisonic %d", order); + + /* extra channels present */ + if (highest_ambi < channel_layout->nb_channels - 1) { + AVChannelLayout extra = { 0 }; + char buf[128]; + + if (channel_layout->order == AV_CHANNEL_ORDER_AMBISONIC) { + extra.order = AV_CHANNEL_ORDER_NATIVE; + extra.nb_channels = av_popcount64(channel_layout->u.mask); + extra.u.mask = channel_layout->u.mask; + } else { + const AVChannelCustom *map = channel_layout->u.map; + + extra.order = AV_CHANNEL_ORDER_CUSTOM; + extra.nb_channels = channel_layout->nb_channels - highest_ambi - 1; + extra.u.map = av_calloc(extra.nb_channels, sizeof(*extra.u.map)); + if (!extra.u.map) + return AVERROR(ENOMEM); + + memcpy(extra.u.map, &map[highest_ambi + 1], + sizeof(*extra.u.map) * extra.nb_channels); + } + + av_channel_layout_describe(&extra, buf, sizeof(buf)); + av_channel_layout_uninit(&extra); + + av_bprintf(bp, "+%s", buf); + } + + return 0; +} + int av_channel_layout_describe_bprint(const AVChannelLayout *channel_layout, AVBPrint *bp) { @@ -542,6 +684,11 @@ int av_channel_layout_describe_bprint(const AVChannelLayout *channel_layout, } // fall-through case AV_CHANNEL_ORDER_CUSTOM: + if (channel_layout->order == AV_CHANNEL_ORDER_CUSTOM) { + int res = try_describe_ambisonic(bp, channel_layout); + if (res < 0 || bp->len) + return res; + } for (i = 0; i < channel_layout->nb_channels; i++) { const char *ch_name = NULL; enum AVChannel ch = AV_CHAN_NONE; @@ -567,6 +714,8 @@ int av_channel_layout_describe_bprint(const AVChannelLayout *channel_layout, case AV_CHANNEL_ORDER_UNSPEC: av_bprintf(bp, "%d channels", channel_layout->nb_channels); return 0; + case AV_CHANNEL_ORDER_AMBISONIC: + return try_describe_ambisonic(bp, channel_layout); default: return AVERROR(EINVAL); } @@ -601,6 +750,8 @@ av_channel_layout_channel_from_index(const AVChannelLayout *channel_layout, switch (channel_layout->order) { case AV_CHANNEL_ORDER_CUSTOM: return channel_layout->u.map[idx].id; + case AV_CHANNEL_ORDER_AMBISONIC: + return AV_CHAN_AMBISONIC_BASE + idx; case AV_CHANNEL_ORDER_NATIVE: for (i = 0; i < 64; i++) { if ((1ULL << i) & channel_layout->u.mask && !idx--) @@ -651,6 +802,11 @@ int av_channel_layout_index_from_channel(const AVChannelLayout *channel_layout, if (channel_layout->u.map[i].id == channel) return i; return AVERROR(EINVAL); + case AV_CHANNEL_ORDER_AMBISONIC: + if (!CHAN_IS_AMBI(channel) || + channel - AV_CHAN_AMBISONIC_BASE >= channel_layout->nb_channels) + return AVERROR(EINVAL); + return channel - AV_CHAN_AMBISONIC_BASE; case AV_CHANNEL_ORDER_NATIVE: { uint64_t mask = channel_layout->u.mask; if (!(mask & (1ULL << channel))) @@ -701,6 +857,9 @@ int av_channel_layout_check(const AVChannelLayout *channel_layout) return 0; } return 1; + case AV_CHANNEL_ORDER_AMBISONIC: + /* If non-diegetic channels are present, ensure they are taken into account */ + return av_popcount64(channel_layout->u.mask) < channel_layout->nb_channels; case AV_CHANNEL_ORDER_UNSPEC: return 1; default: @@ -725,7 +884,8 @@ int av_channel_layout_compare(const AVChannelLayout *chl, const AVChannelLayout return 0; /* can compare masks directly */ - if (chl->order != AV_CHANNEL_ORDER_CUSTOM && + if ((chl->order == AV_CHANNEL_ORDER_NATIVE || + chl->order == AV_CHANNEL_ORDER_AMBISONIC) && chl->order == chl1->order) return chl->u.mask != chl1->u.mask; diff --git a/libavutil/channel_layout.h b/libavutil/channel_layout.h index 6356a9a38a..b7e709a022 100644 --- a/libavutil/channel_layout.h +++ b/libavutil/channel_layout.h @@ -79,6 +79,23 @@ enum AVChannel { /** Channel contains data, but its position is unknown. */ AV_CHAN_UNKWNOWN = 128, + + /** + * Range of channels between AV_CHAN_AMBISONIC_BASE and + * AV_CHAN_AMBISONIC_END represent Ambisonic components using the ACN system. + * + * Given a channel id <i> between AV_CHAN_AMBISONIC_BASE and + * AV_CHAN_AMBISONIC_END (inclusive), the ACN index of the channel <n> is + * <n> = <i> - AV_CHAN_AMBISONIC_BASE. + * + * @note these values are only used for AV_CHANNEL_ORDER_CUSTOM channel + * orderings, the AV_CHANNEL_ORDER_AMBISONIC ordering orders the channels + * implicitly by their position in the stream. + */ + AV_CHAN_AMBISONIC_BASE = 0x400, + // leave space for 1024 ids, which correspond to maximum order-32 harmonics, + // which should be enough for the foreseeable use cases + AV_CHAN_AMBISONIC_END = 0x7ff, }; enum AVChannelOrder { @@ -100,6 +117,29 @@ enum AVChannelOrder { * channels at arbitrary positions. */ AV_CHANNEL_ORDER_CUSTOM, + /** + * The audio is represented as the decomposition of the sound field into + * spherical harmonics. Each channel corresponds to a single expansion + * component. Channels are ordered according to ACN (Ambisonic Channel + * Number). + * + * The channel with the index n in the stream contains the spherical + * harmonic of degree l and order m given by + * @code{.unparsed} + * l = floor(sqrt(n)), + * m = n - l * (l + 1). + * @endcode + * + * Conversely given a spherical harmonic of degree l and order m, the + * corresponding channel index n is given by + * @code{.unparsed} + * n = l * (l + 1) + m. + * @endcode + * + * Normalization is assumed to be SN3D (Schmidt Semi-Normalization) + * as defined in AmbiX format $ 2.1. + */ + AV_CHANNEL_ORDER_AMBISONIC, }; @@ -266,7 +306,8 @@ typedef struct AVChannelLayout { */ union { /** - * This member must be used for AV_CHANNEL_ORDER_NATIVE. + * This member must be used for AV_CHANNEL_ORDER_NATIVE, and may be used + * for AV_CHANNEL_ORDER_AMBISONIC to signal non-diegetic channels. * It is a bitmask, where the position of each set bit means that the * AVChannel with the corresponding value is present. * @@ -288,6 +329,11 @@ typedef struct AVChannelLayout { * I.e. when map[i].id is equal to AV_CHAN_FOO, then AV_CH_FOO is the * i-th channel in the audio data. * + * When map[i].id is in the range between AV_CHAN_AMBISONIC_BASE and + * AV_CHAN_AMBISONIC_END (inclusive), the channel contains an ambisonic + * component with ACN index (as defined above) + * n = map[i].id - AV_CHAN_AMBISONIC_BASE. + * * map[i].name may be filled with a 0-terminated string, in which case * it will be used for the purpose of identifying the channel with the * convenience functions below. Otherise it must be zeroed. @@ -359,6 +405,8 @@ typedef struct AVChannelLayout { { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 2, .u = { .mask = AV_CH_LAYOUT_STEREO_DOWNMIX }} #define AV_CHANNEL_LAYOUT_22POINT2 \ { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 24, .u = { .mask = AV_CH_LAYOUT_22POINT2 }} +#define AV_CHANNEL_LAYOUT_AMBISONIC_FIRST_ORDER \ + { .order = AV_CHANNEL_ORDER_AMBISONIC, .nb_channels = 4, .u = { .mask = 0 }} struct AVBPrint; @@ -554,6 +602,8 @@ int av_channel_layout_from_mask(AVChannelLayout *channel_layout, uint64_t mask); * - a hexadecimal value of a channel layout (eg. "0x4") * - the number of channels with default layout (eg. "5c") * - the number of unordered channels (eg. "4", "4C", or "4 channels") + * - the ambisonic order followed by optional non-diegetic channels (eg. + * "ambisonic 2+stereo") * * @param channel_layout input channel layout * @param str string describing the channel layout diff --git a/libavutil/tests/channel_layout.c b/libavutil/tests/channel_layout.c index 7e6be0be17..b3318aed27 100644 --- a/libavutil/tests/channel_layout.c +++ b/libavutil/tests/channel_layout.c @@ -66,6 +66,10 @@ int main(void) printf("With AV_CHAN_FRONT_RIGHT: %26s\n", bp.str); av_channel_name_bprint(&bp, 63); printf("With 63: %43s\n", bp.str); + av_channel_name_bprint(&bp, AV_CHAN_AMBISONIC_BASE); + printf("With AV_CHAN_AMBISONIC_BASE: %23s\n", bp.str); + av_channel_name_bprint(&bp, AV_CHAN_AMBISONIC_END); + printf("With AV_CHAN_AMBISONIC_END: %24s\n", bp.str); printf("Testing av_channel_description\n"); av_channel_description_bprint(&bp, AV_CHAN_FRONT_LEFT); @@ -74,11 +78,17 @@ int main(void) printf("With AV_CHAN_FRONT_RIGHT: %26s\n", bp.str); av_channel_description_bprint(&bp, 63); printf("With 63: %43s\n", bp.str); + av_channel_description_bprint(&bp, AV_CHAN_AMBISONIC_BASE); + printf("With AV_CHAN_AMBISONIC_BASE: %23s\n", bp.str); + av_channel_description_bprint(&bp, AV_CHAN_AMBISONIC_END); + printf("With AV_CHAN_AMBISONIC_END: %24s\n", bp.str); printf("\nTesting av_channel_from_string\n"); printf("With \"FL\": %41d\n", av_channel_from_string("FL")); printf("With \"FR\": %41d\n", av_channel_from_string("FR")); printf("With \"USR63\": %38d\n", av_channel_from_string("USR63")); + printf("With \"AMBI0\": %38d\n", av_channel_from_string("AMBI0")); + printf("With \"AMBI1023\": %35d\n", av_channel_from_string("AMBI1023")); printf("\n==Native layouts==\n"); @@ -177,6 +187,8 @@ int main(void) printf("\nTesting av_channel_layout_from_string\n"); CHANNEL_LAYOUT_FROM_STRING(custom, "FL+FR+FC+BL+BR+LFE"); printf("With \"FL+FR+FC+BL+BR+LFE\": %25s\n", bp.str); + CHANNEL_LAYOUT_FROM_STRING(custom, "ambisonic 1+FR+FL"); + printf("With \"ambisonic 1+FR+FL\": %26s\n", bp.str); CHANNEL_LAYOUT_FROM_STRING(custom, "FR+FL+USR63"); printf("With \"FR+FL+USR63\" layout: %25s\n", bp.str); @@ -225,6 +237,14 @@ int main(void) CHANNEL_LAYOUT_CHANNEL_FROM_INDEX(custom, 3); printf("On \"FR+FL+USR63\" layout with 3: %20d\n", ret); + printf("\n==Ambisonic layouts==\n"); + + printf("\nTesting av_channel_layout_from_string\n"); + CHANNEL_LAYOUT_FROM_STRING(custom, "ambisonic 1"); + printf("With \"ambisonic 1\": %32s\n", bp.str); + CHANNEL_LAYOUT_FROM_STRING(custom, "ambisonic 2+stereo"); + printf("With \"ambisonic 2+stereo\": %25s\n", bp.str); + av_channel_layout_uninit(&surround); av_channel_layout_uninit(&custom); av_bprint_finalize(&bp, NULL); diff --git a/tests/ref/fate/channel_layout b/tests/ref/fate/channel_layout index bac00086d6..4614934028 100644 --- a/tests/ref/fate/channel_layout +++ b/tests/ref/fate/channel_layout @@ -2,15 +2,21 @@ Testing av_channel_name With AV_CHAN_FRONT_LEFT: FL With AV_CHAN_FRONT_RIGHT: FR With 63: USR63 +With AV_CHAN_AMBISONIC_BASE: AMBI0 +With AV_CHAN_AMBISONIC_END: AMBI1023 Testing av_channel_description With AV_CHAN_FRONT_LEFT: front left With AV_CHAN_FRONT_RIGHT: front right With 63: user 63 +With AV_CHAN_AMBISONIC_BASE: ambisonic ACN 0 +With AV_CHAN_AMBISONIC_END: ambisonic ACN 1023 Testing av_channel_from_string With "FL": 0 With "FR": 1 With "USR63": 63 +With "AMBI0": 1024 +With "AMBI1023": 2047 ==Native layouts== @@ -69,6 +75,7 @@ On 5.1(side) layout with "BC": -1 Testing av_channel_layout_from_string With "FL+FR+FC+BL+BR+LFE": FL+FR+FC+BL+BR+LFE +With "ambisonic 1+FR+FL": ambisonic 1+FR+FL With "FR+FL+USR63" layout: FR+FL+USR63 Testing av_channel_layout_index_from_string @@ -96,3 +103,9 @@ On "FR+FL+USR63" layout with 0: 1 On "FR+FL+USR63" layout with 1: 0 On "FR+FL+USR63" layout with 2: 63 On "FR+FL+USR63" layout with 3: -1 + +==Ambisonic layouts== + +Testing av_channel_layout_from_string +With "ambisonic 1": ambisonic 1 +With "ambisonic 2+stereo": ambisonic 2+stereo -- 2.34.1 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".