Signed-off-by: Vittorio Giovara <[email protected]>
---
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
[email protected]
https://lists.libav.org/mailman/listinfo/libav-devel