Signed-off-by: Vittorio Giovara <vittorio.giov...@gmail.com> --- libavutil/channel_layout.c | 86 ++++++++++++++++++++++++++++++++++++++++++++-- libavutil/channel_layout.h | 33 ++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-)
diff --git a/libavutil/channel_layout.c b/libavutil/channel_layout.c index 285997446d..d4791a9b61 100644 --- a/libavutil/channel_layout.c +++ b/libavutil/channel_layout.c @@ -260,7 +260,7 @@ void av_channel_layout_from_mask(AVChannelLayout *channel_layout, int av_channel_layout_from_string(AVChannelLayout *channel_layout, const char *str) { - int i, channels; + int i, channels, order; const char *dup = str; uint64_t mask = 0; @@ -309,6 +309,44 @@ int av_channel_layout_from_string(AVChannelLayout *channel_layout, return 0; } + /* ambisonic */ + if (sscanf(str, "ambisonic channels %d order %d", &channels, &order) == 2) { + AVChannelLayout extra = {0}; + int harmonics = 0; + + // handle nondiegetic channels or half-sphere harmonics + dup = str; + while (*dup) { + char *chname = av_get_token(&dup, "|"); + if (!chname) + return AVERROR(ENOMEM); + if (*dup) + dup++; // skip separator + + // no extra channel found + if (!strcmp(chname, str)) + break; + + if (av_channel_from_string(chname) == AV_CHAN_AMBISONIC) + harmonics++; + else { + char *nondiegetic = strstr(str, chname); + int ret = av_channel_layout_from_string(&extra, nondiegetic); + // no other channels allowed after nondiegetic + av_free(chname); + if (ret < 0) + return ret; + break; + } + av_free(chname); + } + + channel_layout->nb_channels = channels + harmonics + extra.nb_channels; + channel_layout->u.mask = extra.u.mask; + + return 0; + } + return AVERROR_INVALIDDATA; } @@ -361,6 +399,35 @@ char *av_channel_layout_describe(const AVChannelLayout *channel_layout) } return ret; } + case AV_CHANNEL_ORDER_AMBISONIC: { + char buf[64]; + int order = floor(sqrt(channel_layout->nb_channels)) - 1; + int channels = (order + 1) * (order + 1); + + snprintf(buf, sizeof(buf), "ambisonic channels %d order %d", + channels, order); + + // handle nondiegetic channels or half-sphere harmonics + for (i = channels; i < channel_layout->nb_channels; i++) { + enum AVChannel chan = av_channel_layout_get_channel(channel_layout, i); + if (chan == AV_CHAN_AMBISONIC) { + av_strlcat(buf, "|", sizeof(buf)); + av_strlcat(buf, av_channel_name(chan), sizeof(buf)); + } + } + if (channel_layout->u.mask) { + AVChannelLayout extra = {0}; + char *chlstr; + + av_channel_layout_from_mask(&extra, channel_layout->u.mask); + chlstr = av_channel_layout_describe(&extra); + av_strlcat(buf, "|", sizeof(buf)); + av_strlcat(buf, chlstr, sizeof(buf)); + av_free(chlstr); + } + + return av_strdup(buf); + } case AV_CHANNEL_ORDER_UNSPEC: { char buf[64]; snprintf(buf, sizeof(buf), "%d channels", channel_layout->nb_channels); @@ -381,6 +448,11 @@ int av_channel_layout_get_channel(const AVChannelLayout *channel_layout, int idx switch (channel_layout->order) { case AV_CHANNEL_ORDER_CUSTOM: return channel_layout->u.map[idx]; + case AV_CHANNEL_ORDER_AMBISONIC: + idx -= channel_layout->nb_channels - av_popcount64(channel_layout->u.mask); + if (idx < 0) + return AV_CHAN_AMBISONIC; + // fall-through case AV_CHANNEL_ORDER_NATIVE: for (i = 0; i < 64; i++) { if ((1ULL << i) & channel_layout->u.mask && !idx--) @@ -394,7 +466,7 @@ int av_channel_layout_get_channel(const AVChannelLayout *channel_layout, int idx int av_channel_layout_channel_index(const AVChannelLayout *channel_layout, enum AVChannel channel) { - int i; + int i, off = 0; switch (channel_layout->order) { case AV_CHANNEL_ORDER_CUSTOM: @@ -402,12 +474,17 @@ int av_channel_layout_channel_index(const AVChannelLayout *channel_layout, if (channel_layout->u.map[i] == channel) return i; return AVERROR(EINVAL); + case AV_CHANNEL_ORDER_AMBISONIC: + if (channel == AV_CHAN_AMBISONIC) + return 0; + off = channel_layout->nb_channels - av_popcount64(channel_layout->u.mask); + // fall-through case AV_CHANNEL_ORDER_NATIVE: { uint64_t mask = channel_layout->u.mask; if (!(mask & (1ULL << channel))) return AVERROR(EINVAL); mask &= (1ULL << channel) - 1; - return av_popcount64(mask); + return av_popcount64(mask) + off; } default: return AVERROR(EINVAL); @@ -422,6 +499,9 @@ int av_channel_layout_check(const AVChannelLayout *channel_layout) switch (channel_layout->order) { case AV_CHANNEL_ORDER_NATIVE: return av_popcount64(channel_layout->u.mask) == channel_layout->nb_channels; + case AV_CHANNEL_ORDER_AMBISONIC: + return channel_layout->nb_channels != 2 && + channel_layout->nb_channels >= av_popcount64(channel_layout->u.mask); case AV_CHANNEL_ORDER_CUSTOM: return !!channel_layout->u.map; case AV_CHANNEL_ORDER_UNSPEC: diff --git a/libavutil/channel_layout.h b/libavutil/channel_layout.h index 2604ad1537..bd78cc9ff9 100644 --- a/libavutil/channel_layout.h +++ b/libavutil/channel_layout.h @@ -68,6 +68,8 @@ enum AVChannel { /** Channel is empty can be safely skipped. */ AV_CHAN_SILENCE = 64, + /** Channel represents an ambisonic component. */ + AV_CHAN_AMBISONIC, }; enum AVChannelOrder { @@ -88,6 +90,26 @@ enum AVChannelOrder { * about the channel order. */ AV_CHANNEL_ORDER_UNSPEC, + /** + * Each channel represents a different speaker position, also known as + * ambisonic components. Channels are ordered according to ACN (Ambisonic + * Channel Number), and they follow these mathematical properties: + * + * @code{.unparsed} + * ACN = n * (n + 1) + m + * n = floor(sqrt(k)) - 1, + * m = k - n * (n + 1) - 1. + * @endcode + * + * for order n and degree m; the ACN component corresponds to channel + * index as k = ACN + 1. In case non-diegetic channels are present, + * they are always the last ones, and mask is initialized with a correct + * layout. + * + * Normalization is assumed to be SN3D (Schmidt Semi-Normalization) + * as defined in AmbiX format ยง 2.1. + */ + AV_CHANNEL_ORDER_AMBISONIC, }; @@ -224,6 +246,10 @@ typedef struct AVChannelLayout { * modified manually (i.e. not using any of the av_channel_layout_* * functions), the code doing it must ensure that the number of set bits * is equal to nb_channels. + * + * This member maybe be optionially used for AV_CHANNEL_ORDER_AMBISONIC. + * It is a bitmask that indicates the channel layout of the last + * non-diegetic channels present in the stream. */ uint64_t mask; /** @@ -295,6 +321,8 @@ typedef struct AVChannelLayout { { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 16, .u = { .mask = AV_CH_LAYOUT_HEXAGONAL }} #define AV_CHANNEL_LAYOUT_STEREO_DOWNMIX \ { .order = AV_CHANNEL_ORDER_NATIVE, .nb_channels = 2, .u = { .mask = AV_CH_LAYOUT_STEREO_DOWNMIX }} +#define AV_CHANNEL_LAYOUT_AMBISONIC_FIRST_ORDER \ + { .order = AV_CHANNEL_ORDER_AMBISONIC, .nb_channels = 4, .u = { .mask = 0 }} #if FF_API_OLD_CHANNEL_LAYOUT /** @@ -403,6 +431,8 @@ void 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. "5") * - the number of unordered channels (eg. "4 channels") + * - the ambisonic channel count and order followed by optional non-diegetic + * channels (eg. "ambisonic channels 9 order 2|stereo") * * @param channel_layout input channel layout * @param str string describing the channel layout @@ -452,6 +482,9 @@ int av_channel_layout_get_channel(const AVChannelLayout *channel_layout, int idx /** * Get the index of a given channel in a channel layout. * + * @note AV_CHAN_AMBISONIC will always be at index 0: callers need to use the + * ACN mathematical properties to determine the order. + * * @return index of channel in channel_layout on success or a negative number if * channel is not present in channel_layout. */ -- 2.13.1 _______________________________________________ libav-devel mailing list libav-devel@libav.org https://lists.libav.org/mailman/listinfo/libav-devel