Commit 2d0f83bb0b for ffmpeg
commit 2d0f83bb0bbe6ca457ae01cd922f5a7d3257f6ec
Author: Kacper Michajłow <kasper93@gmail.com>
Date: Wed Oct 14 10:31:54 2026 +0200
avformat/hls: associate renditions with variants through rendition groups
Keep the renditions of each TYPE and GROUP-ID in a rendition group found
through an AVTree, so a variant adds the playlists of its groups without
scanning all renditions, and the in-stream renditions of a group are
added to a main Media Playlist once rather than once per variant that
shares it.
Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 272fb8c2d6..129df0ccff 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -90,6 +90,8 @@ struct segment {
};
struct rendition;
+struct rendition_group;
+struct variant;
enum PlaylistType {
PLS_TYPE_UNSPECIFIED,
@@ -177,6 +179,11 @@ struct playlist {
int n_renditions;
struct rendition **renditions;
+ /* The last variant this playlist was added to and the last rendition
+ * group whose renditions were added to it. */
+ struct variant *last_variant;
+ struct rendition_group *last_group;
+
/* Media Initialization Sections (EXT-X-MAP) associated with this
* playlist, if any. */
int n_init_sections;
@@ -201,7 +208,6 @@ struct playlist {
struct rendition {
enum AVMediaType type;
struct playlist *playlist;
- char group_id[MAX_FIELD_LEN];
char language[MAX_FIELD_LEN];
char name[MAX_FIELD_LEN];
int disposition;
@@ -219,6 +225,27 @@ struct variant {
char subtitles_group[MAX_FIELD_LEN];
};
+/*
+ * The renditions with the same TYPE and GROUP-ID, which variants reference
+ * by the group id.
+ */
+struct rendition_group {
+ enum AVMediaType type;
+ char id[MAX_FIELD_LEN];
+
+ /* playlists of the external renditions */
+ int n_playlists;
+ struct playlist **playlists;
+
+ /* renditions that are part of the variant main Media Playlists */
+ int n_renditions;
+ struct rendition **renditions;
+
+ /* variants that reference the group */
+ int n_variants;
+ struct variant **variants;
+};
+
typedef struct HLSContext {
AVClass *class;
AVFormatContext *ctx;
@@ -229,6 +256,9 @@ typedef struct HLSContext {
struct AVTreeNode *playlists_by_url;
int n_renditions;
struct rendition **renditions;
+ int n_rendition_groups;
+ struct rendition_group **rendition_groups;
+ struct AVTreeNode *rendition_groups_by_id;
int64_t cur_seq_no;
int m3u8_hold_counters;
@@ -334,6 +364,16 @@ static void free_rendition_list(HLSContext *c)
av_freep(&c->renditions[i]);
av_freep(&c->renditions);
c->n_renditions = 0;
+ for (i = 0; i < c->n_rendition_groups; i++) {
+ av_freep(&c->rendition_groups[i]->playlists);
+ av_freep(&c->rendition_groups[i]->renditions);
+ av_freep(&c->rendition_groups[i]->variants);
+ av_freep(&c->rendition_groups[i]);
+ }
+ av_freep(&c->rendition_groups);
+ c->n_rendition_groups = 0;
+ av_tree_destroy(c->rendition_groups_by_id);
+ c->rendition_groups_by_id = NULL;
}
static int cmp_url_playlist(const void *key, const void *b)
@@ -549,14 +589,59 @@ struct rendition_info {
char characteristics[MAX_CHARACTERISTICS_LEN];
};
-static struct rendition *new_rendition(HLSContext *c, struct rendition_info *info,
- const char *url_base)
+static int cmp_rendition_group(const void *a, const void *b)
+{
+ const struct rendition_group *ga = a, *gb = b;
+
+ if (ga->type != gb->type)
+ return FFDIFFSIGN(ga->type, gb->type);
+ return strcmp(ga->id, gb->id);
+}
+
+static struct rendition_group *find_rendition_group(HLSContext *c,
+ enum AVMediaType type,
+ const char *id)
+{
+ struct rendition_group key = { .type = type };
+
+ av_strlcpy(key.id, id, sizeof(key.id));
+ return av_tree_find(c->rendition_groups_by_id, &key, cmp_rendition_group, NULL);
+}
+
+static struct rendition_group *new_rendition_group(HLSContext *c,
+ enum AVMediaType type,
+ const char *id)
+{
+ struct rendition_group *group = find_rendition_group(c, type, id);
+ struct AVTreeNode *node;
+
+ if (group)
+ return group;
+
+ group = av_mallocz(sizeof(*group));
+ node = av_tree_node_alloc();
+ if (!group || !node ||
+ av_dynarray_add_nofree(&c->rendition_groups, &c->n_rendition_groups, group) < 0) {
+ av_free(group);
+ av_free(node);
+ return NULL;
+ }
+ group->type = type;
+ av_strlcpy(group->id, id, sizeof(group->id));
+ av_tree_insert(&c->rendition_groups_by_id, group, cmp_rendition_group, &node);
+ return group;
+}
+
+static int new_rendition(HLSContext *c, struct rendition_info *info,
+ const char *url_base)
{
+ struct rendition_group *group;
struct rendition *rend;
enum AVMediaType type = AVMEDIA_TYPE_UNKNOWN;
char *characteristic;
char *chr_ptr;
char *saveptr;
+ int ret;
if (!strcmp(info->type, "AUDIO"))
type = AVMEDIA_TYPE_AUDIO;
@@ -567,27 +652,34 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf
else if (!strcmp(info->type, "CLOSED-CAPTIONS"))
/* CLOSED-CAPTIONS is ignored since we do not support CEA-608 CC in
* AVC SEI RBSP anyway */
- return NULL;
+ return 0;
if (type == AVMEDIA_TYPE_UNKNOWN) {
av_log(c->ctx, AV_LOG_WARNING, "Can't support the type: %s\n", info->type);
- return NULL;
+ return 0;
}
/* URI is mandatory for subtitles as per spec */
if (type == AVMEDIA_TYPE_SUBTITLE && !info->uri[0]) {
av_log(c->ctx, AV_LOG_ERROR, "The URI tag is REQUIRED for subtitle.\n");
- return NULL;
+ return 0;
}
+ group = new_rendition_group(c, type, info->group_id);
+ if (!group)
+ return AVERROR(ENOMEM);
+
rend = av_mallocz(sizeof(struct rendition));
if (!rend)
- return NULL;
+ return AVERROR(ENOMEM);
- dynarray_add(&c->renditions, &c->n_renditions, rend);
+ ret = av_dynarray_add_nofree(&c->renditions, &c->n_renditions, rend);
+ if (ret < 0) {
+ av_free(rend);
+ return ret;
+ }
rend->type = type;
- strcpy(rend->group_id, info->group_id);
strcpy(rend->language, info->language);
strcpy(rend->name, info->name);
@@ -599,10 +691,18 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf
rend->playlist->is_subtitle = 1;
rend->playlist->is_id3_timestamped = 0;
}
- dynarray_add(&rend->playlist->renditions,
- &rend->playlist->n_renditions, rend);
+ ret = av_dynarray_add_nofree(&rend->playlist->renditions,
+ &rend->playlist->n_renditions, rend);
+ if (ret < 0)
+ return ret;
}
}
+ if (rend->playlist)
+ ret = av_dynarray_add_nofree(&group->playlists, &group->n_playlists, rend->playlist);
+ else
+ ret = av_dynarray_add_nofree(&group->renditions, &group->n_renditions, rend);
+ if (ret < 0)
+ return ret;
if (info->assoc_language[0]) {
size_t langlen = strlen(rend->language);
@@ -633,7 +733,7 @@ static struct rendition *new_rendition(HLSContext *c, struct rendition_info *inf
chr_ptr = NULL;
}
- return rend;
+ return 0;
}
static void handle_rendition_args(void *vinfo, const char *key,
@@ -966,7 +1066,9 @@ static int parse_playlist(HLSContext *c, const char *url,
goto fail;
}
ff_parse_key_value(ptr, handle_rendition_args, &info);
- new_rendition(c, &info, url);
+ ret = new_rendition(c, &info, url);
+ if (ret < 0)
+ goto fail;
} else if (av_strstart(line, "#EXT-X-TARGETDURATION:", &ptr)) {
int64_t t;
ret = ensure_playlist(c, &pls, url);
@@ -2017,28 +2119,59 @@ restart:
goto restart;
}
-static void add_renditions_to_variant(HLSContext *c, struct variant *var,
- enum AVMediaType type, const char *group_id)
+static int add_renditions_to_variant(HLSContext *c, struct variant *var,
+ enum AVMediaType type, const char *group_id)
{
- int i;
+ struct rendition_group *group = find_rendition_group(c, type, group_id);
+ int ret;
+
+ if (!group)
+ return 0;
- for (i = 0; i < c->n_renditions; i++) {
- struct rendition *rend = c->renditions[i];
+ ret = av_dynarray_add_nofree(&group->variants, &group->n_variants, var);
+ if (ret < 0)
+ return ret;
- if (rend->type == type && !strcmp(rend->group_id, group_id)) {
+ /* add the playlists of the external renditions to the variant */
+ for (int i = 0; i < group->n_playlists; i++) {
+ struct playlist *pls = group->playlists[i];
- if (rend->playlist)
- /* rendition is an external playlist
- * => add the playlist to the variant */
- dynarray_add(&var->playlists, &var->n_playlists, rend->playlist);
- else
- /* rendition is part of the variant main Media Playlist
- * => add the rendition to the main Media Playlist */
- dynarray_add(&var->playlists[0]->renditions,
- &var->playlists[0]->n_renditions,
- rend);
+ if (pls->last_variant != var) {
+ ret = av_dynarray_add_nofree(&var->playlists, &var->n_playlists, pls);
+ if (ret < 0)
+ return ret;
+ pls->last_variant = var;
}
}
+ return 0;
+}
+
+/* The renditions without a playlist are part of the main Media Playlist of
+ * every variant that references their group. */
+static int add_renditions_to_playlists(HLSContext *c, enum AVMediaType type)
+{
+ for (int i = 0; i < c->n_rendition_groups; i++) {
+ struct rendition_group *group = c->rendition_groups[i];
+
+ if (group->type != type || !group->n_renditions)
+ continue;
+
+ for (int j = 0; j < group->n_variants; j++) {
+ struct playlist *pls = group->variants[j]->playlists[0];
+
+ if (pls->last_group == group)
+ continue;
+
+ for (int k = 0; k < group->n_renditions; k++) {
+ int ret = av_dynarray_add_nofree(&pls->renditions, &pls->n_renditions,
+ group->renditions[k]);
+ if (ret < 0)
+ return ret;
+ }
+ pls->last_group = group;
+ }
+ }
+ return 0;
}
static void add_metadata_from_renditions(AVFormatContext *s, struct playlist *pls,
@@ -2399,13 +2532,19 @@ static int hls_read_header(AVFormatContext *s)
for (i = 0; i < c->n_variants; i++) {
struct variant *var = c->variants[i];
- if (var->audio_group[0])
- add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group);
- if (var->video_group[0])
- add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group);
- if (var->subtitles_group[0])
- add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group);
+ if (var->audio_group[0] &&
+ (ret = add_renditions_to_variant(c, var, AVMEDIA_TYPE_AUDIO, var->audio_group)) < 0)
+ return ret;
+ if (var->video_group[0] &&
+ (ret = add_renditions_to_variant(c, var, AVMEDIA_TYPE_VIDEO, var->video_group)) < 0)
+ return ret;
+ if (var->subtitles_group[0] &&
+ (ret = add_renditions_to_variant(c, var, AVMEDIA_TYPE_SUBTITLE, var->subtitles_group)) < 0)
+ return ret;
}
+ if ((ret = add_renditions_to_playlists(c, AVMEDIA_TYPE_AUDIO)) < 0 ||
+ (ret = add_renditions_to_playlists(c, AVMEDIA_TYPE_VIDEO)) < 0)
+ return ret;
/* Create a program for each variant */
for (i = 0; i < c->n_variants; i++) {