From: Vittorio Giovara <vittorio.giov...@gmail.com> Signed-off-by: Vittorio Giovara <vittorio.giov...@gmail.com> Signed-off-by: Anton Khirnov <an...@khirnov.net> Signed-off-by: James Almer <jamr...@gmail.com> --- libavutil/channel_layout.c | 147 ++++++++++++++++++++++++++++++- libavutil/channel_layout.h | 49 +++++++++++ libavutil/tests/channel_layout.c | 10 +++ tests/ref/fate/channel_layout | 6 ++ 4 files changed, 210 insertions(+), 2 deletions(-)
diff --git a/libavutil/channel_layout.c b/libavutil/channel_layout.c index 984255bfca..a51af95fcf 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; @@ -80,7 +83,10 @@ static const char *get_channel_name(enum AVChannel channel_id) static inline void get_channel_str(AVBPrint *bp, const char *str, enum AVChannel channel_id) { - if (str) + if (channel_id >= AV_CHAN_AMBISONIC_BASE && + channel_id <= AV_CHAN_AMBISONIC_END) + av_bprintf(bp, "ambisonic %d", channel_id - AV_CHAN_AMBISONIC_BASE); + else if (str) av_bprintf(bp, "%s", str); else av_bprintf(bp, "?"); @@ -116,6 +122,14 @@ int av_channel_description(char *buf, size_t buf_size, enum AVChannel channel_id enum AVChannel av_channel_from_string(const char *str) { int i; + + if (!strncmp(str, "ambisonic", 9)) { + i = strtol(str + 9, 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; @@ -415,6 +429,53 @@ int av_channel_layout_from_string(AVChannelLayout *channel_layout, return 0; } + /* 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.order != AV_CHANNEL_ORDER_NATIVE || + extra.nb_channels >= INT_MAX - channel_layout->nb_channels) { + av_channel_layout_uninit(&extra); + return AVERROR(EINVAL); + } + + channel_layout->order = AV_CHANNEL_ORDER_CUSTOM; + channel_layout->u.map = + av_mallocz_array(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); + 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; + } + return AVERROR_INVALIDDATA; } @@ -438,6 +499,67 @@ 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 dst + * and return 0. + * If it is something else, write NULL in dst and return 0. + * Return negative error code on error. + */ +static int try_describe_ambisonic(AVBPrint *bp, const AVChannelLayout *channel_layout) +{ + const AVChannelCustom *map = channel_layout->u.map; + int i, highest_ambi, order; + + highest_ambi = -1; + 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; + char buf[128]; + + extra.order = AV_CHANNEL_ORDER_CUSTOM; + extra.nb_channels = channel_layout->nb_channels - highest_ambi - 1; + extra.u.map = av_mallocz_array(extra.nb_channels, sizeof(*extra.u.map)); + if (!extra.u.map) + return AVERROR(ENOMEM); + + for (i = 0; i < extra.nb_channels; i++) + extra.u.map[i].id = map[highest_ambi + 1 + i].id; + + 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(const AVChannelLayout *channel_layout, char *buf, size_t buf_size) { @@ -458,6 +580,12 @@ int av_channel_layout_describe(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) + return res; + } + for (i = 0; i < channel_layout->nb_channels; i++) { enum AVChannel ch = av_channel_layout_channel_from_index(channel_layout, i); const char *ch_name = get_channel_name(ch); @@ -472,6 +600,9 @@ int av_channel_layout_describe(const AVChannelLayout *channel_layout, case AV_CHANNEL_ORDER_UNSPEC: av_bprintf(&bp, "%d channels", channel_layout->nb_channels); return bp.len; + case AV_CHANNEL_ORDER_AMBISONIC: + av_bprintf(&bp, "ambisonic %d", (int)floor(sqrt(channel_layout->nb_channels - 1))); + return bp.len; default: return AVERROR(EINVAL); } @@ -489,6 +620,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--) @@ -532,6 +665,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))) @@ -591,8 +729,13 @@ int av_channel_layout_compare(const AVChannelLayout *chl, const AVChannelLayout else if (chl->order == AV_CHANNEL_ORDER_UNSPEC) return 0; + /* both ambisonic with same channel count -> equal */ + if (chl->order == AV_CHANNEL_ORDER_AMBISONIC && + chl1->order == chl->order) + return 0; + /* can compare masks directly */ - if (chl->order != AV_CHANNEL_ORDER_CUSTOM && + if (chl->order == AV_CHANNEL_ORDER_NATIVE && chl->order == chl1->order) return chl->u.mask != chl1->u.mask; diff --git a/libavutil/channel_layout.h b/libavutil/channel_layout.h index 018e87ff0b..7b77a74b61 100644 --- a/libavutil/channel_layout.h +++ b/libavutil/channel_layout.h @@ -76,6 +76,23 @@ enum AVChannel { /** Channel is empty can be safely skipped. */ AV_CHAN_SILENCE = 64, + + /** + * 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 { @@ -97,6 +114,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, }; @@ -285,6 +325,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. */ AVChannelCustom *map; } u; @@ -348,6 +393,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 }} #if FF_API_OLD_CHANNEL_LAYOUT /** @@ -532,6 +579,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" 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 60854cf203..e4b42b1574 100644 --- a/libavutil/tests/channel_layout.c +++ b/libavutil/tests/channel_layout.c @@ -64,16 +64,26 @@ int main(void) printf("With AV_CHAN_FRONT_LEFT: %27s\n", buf); av_channel_name(buf, sizeof(buf), AV_CHAN_FRONT_RIGHT); printf("With AV_CHAN_FRONT_RIGHT: %26s\n", buf); + av_channel_name(buf, sizeof(buf), AV_CHAN_AMBISONIC_BASE); + printf("With AV_CHAN_AMBISONIC_BASE: %23s\n", buf); + av_channel_name(buf, sizeof(buf), AV_CHAN_AMBISONIC_END); + printf("With AV_CHAN_AMBISONIC_END: %24s\n", buf); printf("Testing av_channel_description\n"); av_channel_description(buf, sizeof(buf), AV_CHAN_FRONT_LEFT); printf("With AV_CHAN_FRONT_LEFT: %27s\n", buf); av_channel_description(buf, sizeof(buf), AV_CHAN_FRONT_RIGHT); printf("With AV_CHAN_FRONT_RIGHT: %26s\n", buf); + av_channel_description(buf, sizeof(buf), AV_CHAN_AMBISONIC_BASE); + printf("With AV_CHAN_AMBISONIC_BASE: %23s\n", buf); + av_channel_description(buf, sizeof(buf), AV_CHAN_AMBISONIC_END); + printf("With AV_CHAN_AMBISONIC_END: %24s\n", buf); 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 \"ambisonic 0\": %32d\n", av_channel_from_string("ambisonic 0")); + printf("With \"ambisonic 1023\": %29d\n", av_channel_from_string("ambisonic 1023")); printf("\nTesting av_channel_layout_from_string\n"); CHANNEL_LAYOUT_FROM_STRING(surround, "0x3f"); diff --git a/tests/ref/fate/channel_layout b/tests/ref/fate/channel_layout index 89d43f46f4..1a74216125 100644 --- a/tests/ref/fate/channel_layout +++ b/tests/ref/fate/channel_layout @@ -1,13 +1,19 @@ Testing av_channel_name With AV_CHAN_FRONT_LEFT: FL With AV_CHAN_FRONT_RIGHT: FR +With AV_CHAN_AMBISONIC_BASE: ambisonic 0 +With AV_CHAN_AMBISONIC_END: ambisonic 1023 Testing av_channel_description With AV_CHAN_FRONT_LEFT: front left With AV_CHAN_FRONT_RIGHT: front right +With AV_CHAN_AMBISONIC_BASE: ambisonic 0 +With AV_CHAN_AMBISONIC_END: ambisonic 1023 Testing av_channel_from_string With "FL": 0 With "FR": 1 +With "ambisonic 0": 1024 +With "ambisonic 1023": 2047 Testing av_channel_layout_from_string With "0x3f": 5.1 -- 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".