avutil/channel_layout: factorize parsing list of channel names

Also make use of the av_channel_from_string() function to determine the channel
id. This fixes some parse issues in av_channel_layout_from_string().

Signed-off-by: Marton Balint <cus@passwd.hu>
Signed-off-by: Paul B Mahol <onemda@gmail.com>
This commit is contained in:
Marton Balint 2024-03-09 19:10:35 +01:00 committed by Paul B Mahol
parent d1a3a48638
commit ca87cd6b81
2 changed files with 66 additions and 122 deletions

View File

@ -239,13 +239,58 @@ int av_channel_layout_from_mask(AVChannelLayout *channel_layout,
return 0;
}
static int parse_channel_list(AVChannelLayout *ch_layout, const char *str)
{
int ret;
int nb_channels = 0;
AVChannelCustom *map = NULL;
AVChannelCustom custom = {0};
while (*str) {
char *channel, *chname;
int ret = av_opt_get_key_value(&str, "@", "+", AV_OPT_FLAG_IMPLICIT_KEY, &channel, &chname);
if (ret < 0) {
av_freep(&map);
return ret;
}
if (*str)
str++; // skip separator
if (!channel) {
channel = chname;
chname = NULL;
}
av_strlcpy(custom.name, chname ? chname : "", sizeof(custom.name));
custom.id = av_channel_from_string(channel);
av_free(channel);
av_free(chname);
if (custom.id == AV_CHAN_NONE) {
av_freep(&map);
return AVERROR(EINVAL);
}
av_dynarray2_add((void **)&map, &nb_channels, sizeof(custom), (void *)&custom);
if (!map)
return AVERROR(ENOMEM);
}
if (!nb_channels)
return AVERROR(EINVAL);
ch_layout->order = AV_CHANNEL_ORDER_CUSTOM;
ch_layout->u.map = map;
ch_layout->nb_channels = nb_channels;
ret = av_channel_layout_retype(ch_layout, 0, AV_CHANNEL_LAYOUT_RETYPE_FLAG_CANONICAL);
av_assert0(ret == 0);
return 0;
}
int av_channel_layout_from_string(AVChannelLayout *channel_layout,
const char *str)
{
int i;
int channels = 0, nb_channels = 0, native = 1;
enum AVChannel highest_channel = AV_CHAN_NONE;
const char *dup;
int i, matches, ret;
int channels = 0, nb_channels = 0;
char *chlist, *end;
uint64_t mask = 0;
@ -321,121 +366,20 @@ int av_channel_layout_from_string(AVChannelLayout *channel_layout,
return AVERROR(ENOMEM);
/* channel names */
av_sscanf(str, "%d channels (%[^)]", &nb_channels, chlist);
end = strchr(str, ')');
dup = chlist;
while (*dup) {
char *channel, *chname;
int ret = av_opt_get_key_value(&dup, "@", "+", AV_OPT_FLAG_IMPLICIT_KEY, &channel, &chname);
if (ret < 0) {
av_free(chlist);
return ret;
}
if (*dup)
dup++; // skip separator
if (channel && !*channel)
av_freep(&channel);
for (i = 0; i < FF_ARRAY_ELEMS(channel_names); i++) {
if (channel_names[i].name && !strcmp(channel ? channel : chname, channel_names[i].name)) {
if (channel || i < highest_channel || mask & (1ULL << i))
native = 0; // Not a native layout, use a custom one
highest_channel = i;
mask |= 1ULL << i;
break;
}
}
if (!channel && i >= FF_ARRAY_ELEMS(channel_names)) {
char *endptr = chname;
enum AVChannel id = AV_CHAN_NONE;
if (!strncmp(chname, "USR", 3)) {
const char *p = chname + 3;
id = strtol(p, &endptr, 0);
}
if (id < 0 || *endptr) {
native = 0; // Unknown channel name
channels = 0;
mask = 0;
av_free(chname);
break;
}
if (id > 63)
native = 0; // Not a native layout, use a custom one
else {
if (id < highest_channel || mask & (1ULL << id))
native = 0; // Not a native layout, use a custom one
highest_channel = id;
mask |= 1ULL << id;
}
}
channels++;
av_free(channel);
av_free(chname);
}
if (mask && native) {
av_free(chlist);
if (nb_channels && ((nb_channels != channels) || (!end || *++end)))
return AVERROR(EINVAL);
av_channel_layout_from_mask(channel_layout, mask);
return 0;
}
/* custom layout of channel names */
if (channels && !native) {
int idx = 0;
if (nb_channels && ((nb_channels != channels) || (!end || *++end))) {
av_free(chlist);
return AVERROR(EINVAL);
}
channel_layout->u.map = av_calloc(channels, sizeof(*channel_layout->u.map));
if (!channel_layout->u.map) {
av_free(chlist);
return AVERROR(ENOMEM);
}
channel_layout->order = AV_CHANNEL_ORDER_CUSTOM;
channel_layout->nb_channels = channels;
dup = chlist;
while (*dup) {
char *channel, *chname;
int ret = av_opt_get_key_value(&dup, "@", "+", AV_OPT_FLAG_IMPLICIT_KEY, &channel, &chname);
if (ret < 0) {
av_freep(&channel_layout->u.map);
av_free(chlist);
return ret;
}
if (*dup)
dup++; // skip separator
for (i = 0; i < FF_ARRAY_ELEMS(channel_names); i++) {
if (channel_names[i].name && !strcmp(channel ? channel : chname, channel_names[i].name)) {
channel_layout->u.map[idx].id = i;
if (channel)
av_strlcpy(channel_layout->u.map[idx].name, chname, sizeof(channel_layout->u.map[idx].name));
idx++;
break;
}
}
if (i >= FF_ARRAY_ELEMS(channel_names)) {
const char *p = (channel ? channel : chname) + 3;
channel_layout->u.map[idx].id = strtol(p, NULL, 0);
if (channel)
av_strlcpy(channel_layout->u.map[idx].name, chname, sizeof(channel_layout->u.map[idx].name));
idx++;
}
av_free(channel);
av_free(chname);
}
av_free(chlist);
return 0;
}
matches = av_sscanf(str, "%d channels (%[^)]", &nb_channels, chlist);
ret = parse_channel_list(channel_layout, chlist);
av_freep(&chlist);
if (ret < 0 && ret != AVERROR(EINVAL))
return ret;
if (ret >= 0) {
end = strchr(str, ')');
if (matches == 2 && (nb_channels != channel_layout->nb_channels || !end || *++end)) {
av_channel_layout_uninit(channel_layout);
return AVERROR(EINVAL);
}
return 0;
}
errno = 0;
mask = strtoull(str, &end, 0);

View File

@ -129,7 +129,7 @@ On "5.1(side)" layout with AV_CH_LAYOUT_4POINT1: 0xf
Testing av_channel_layout_from_string
With "FL+FR+FC+BL+BR+LFE": 6 channels (FL+FR+FC+BL+BR+LFE)
With "2 channels (FR+FL)": 2 channels (FR+FL)
With "2 channels (AMBI1023+FL)": fail
With "2 channels (AMBI1023+FL)": 2 channels (AMBI1023+FL)
With "3 channels (FR+FL)": fail
With "-3 channels (FR+FL)": fail
With "0 channels ()": fail
@ -143,12 +143,12 @@ With "stereo@Boo": fail
With "": fail
With "@": fail
With "@Dummy": fail
With "@FL": 1 channels (FL)
With "@FL": fail
With "Dummy": fail
With "Dummy@FL": fail
With "FR+Dummy": fail
With "FR+Dummy@FL": 1 channels (FR)
With "FR+@FL": 2 channels (FR+FL@FL)
With "FR+Dummy@FL": fail
With "FR+@FL": fail
With "FL+@": fail
With "FR+FL@Foo+USR63@Foo": 3 channels (FR+FL@Foo+USR63@Foo)