Skip to content

Commit

Permalink
rework HLS rendition groups - cf #2639
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanlf committed Dec 4, 2023
1 parent f2807e5 commit aef6de0
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 114 deletions.
1 change: 1 addition & 0 deletions include/gpac/filters.h
Expand Up @@ -1215,6 +1215,7 @@ enum
GF_PROP_PID_CLAMP_DUR = GF_4CC('D','C','M','D'),
GF_PROP_PID_HLS_PLAYLIST = GF_4CC('H','L','V','P'),
GF_PROP_PID_HLS_GROUPID = GF_4CC('H','L','G','I'),
GF_PROP_PID_HLS_FORCE_INF = GF_4CC('H','L','F','I'),
GF_PROP_PID_HLS_EXT_MASTER = GF_4CC('H','L','M','X'),
GF_PROP_PID_HLS_EXT_VARIANT = GF_4CC('H','L','V','X'),
GF_PROP_PID_DASH_CUE = GF_4CC('D','C','U','E'),
Expand Down
1 change: 1 addition & 0 deletions include/gpac/mpd.h
Expand Up @@ -676,6 +676,7 @@ typedef struct {
u32 trackID;

Bool sub_forced;
const char *hls_forced;
} GF_MPD_Representation;

/*! AdaptationSet*/
Expand Down
42 changes: 40 additions & 2 deletions share/doc/man/gpac-filters.1
Expand Up @@ -215,7 +215,7 @@ start (dbl, default: 0.0): set playback start offset. A negative value means
.br
dur (frac, default: 0/0): set inspect duration
.br
analyze (enum, default: off, updatable): analyze sample content (NALU, OBU)
analyze (enum, default: off, updatable): analyze sample content (NALU, OBU), similar to -bsdbg option of reframer filters
.br
* off: no analyzing
.br
Expand Down Expand Up @@ -7243,7 +7243,7 @@ When running in server mode, multiple HTTP outputs with same URL/port may be use
.br
Example
.br
gpac -i dash.mpd dashin:forward=file:SID=D1 dashin:forward=segb:SID=D2 -o http://localhost:80/live.mpd:SID=D1:rdirs=dash -o http://localhost:80/live_rw.mpd:SID=D2:sigfrag
gpac -i dash.mpd dashin:forward=file:FID=D1 dashin:forward=segb:FID=D2 -o http://localhost:80/live.mpd:SID=D1:rdirs=dash -o http://localhost:80/live_rw.mpd:SID=D2:sigfrag
.br

.br
Expand Down Expand Up @@ -9534,6 +9534,44 @@ port (uint, default: 1): set sdi output port of card
start (dbl, default: 0.0): set playback start offset, [-1, 0] means percent of media dur, e.g. -1 == dur
.br

.br
.SH ohevcdec
.LP
.br
Description: OpenHEVC decoder
.br

.br
This filter decodes HEVC and LHVC (HEVC scalable extensions) from one or more PIDs through the OpenHEVC library
.br

.br
.SH Options (expert):
.LP
.br
threading (enum, default: frame): set threading mode
.br
* frameslice: parallel decoding of both frames and slices
.br
* frame: parallel decoding of frames
.br
* slice: parallel decoding of slices
.br

.br
nb_threads (uint, default: 0): set number of threads (if 0, uses number of cores minus one)
.br
no_copy (bool, default: false): directly dispatch internal decoded frame without copy
.br
pack_hfr (bool, default: false): pack 4 consecutive frames in a single output
.br
seek_reset (bool, default: false): reset decoder when seeking
.br
force_stereo (bool, default: true): use stereo output for multiview (top-bottom only)
.br
reset_switch (bool, default: false): reset decoder at config change
.br

.br
.SH uncvg
.LP
Expand Down
15 changes: 15 additions & 0 deletions share/doc/man/gpac.1
Expand Up @@ -40,6 +40,16 @@ The session can be interrupted at any time using ctrl+c, which can also be used
The possible options for gpac are:
.br

.br
.TP
.B \-mem-track
.br
enable memory tracker
.br
.TP
.B \-mem-track-stack
.br
enable memory tracker with stack dumping
.br
.TP
.B \-ltf
Expand Down Expand Up @@ -3633,6 +3643,11 @@ Name of the HLS variant playlist for this media
Name of HLS Group of a stream
.br
.TP
.B HLSForce (HLFI,str,D )
.br
Force writing EXT-X-STREAM-INF if stream is in a rendition group, value is the name of associated groups (can be empty)
.br
.TP
.B HLSMExt (HLMX,strl,D )
.br
List of extensions to add to the master playlist for this PID
Expand Down
10 changes: 10 additions & 0 deletions share/doc/man/mp4box.1
Expand Up @@ -47,6 +47,16 @@ Unless specified otherwise, a track operation option of type integer expects a t
An option of type boolean expects no following value.
.br

.br
.TP
.B \-mem-track
.br
enable memory tracker
.br
.TP
.B \-mem-track-stack
.br
enable memory tracker with stack dumping
.br
.TP
.B \-p (string)
Expand Down
1 change: 1 addition & 0 deletions src/filter_core/filter_props.c
Expand Up @@ -1564,6 +1564,7 @@ GF_BuiltInProperty GF_BuiltInProps [] =
DEC_PROP_F( GF_PROP_PID_CLAMP_DUR, "ClampDur", "Max media duration to process from PID in DASH mode", GF_PROP_FRACTION64, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_HLS_PLAYLIST, "HLSPL", "Name of the HLS variant playlist for this media", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_HLS_GROUPID, "HLSGroup", "Name of HLS Group of a stream", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_HLS_FORCE_INF, "HLSForce", "Force writing EXT-X-STREAM-INF if stream is in a rendition group, value is the name of associated groups (can be empty)", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_HLS_EXT_MASTER, "HLSMExt", "List of extensions to add to the master playlist for this PID", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_HLS_EXT_VARIANT, "HLSVExt", "List of extensions to add to the variant playlist for this PID", GF_PROP_STRING_LIST, GF_PROP_FLAG_GSF_REM),
DEC_PROP_F( GF_PROP_PID_DASH_CUE, "DCue", "Name of a cue list file for this PID - see dasher help", GF_PROP_STRING, GF_PROP_FLAG_GSF_REM),
Expand Down
3 changes: 3 additions & 0 deletions src/filters/dasher.c
Expand Up @@ -7751,6 +7751,9 @@ static void dasher_mark_segment_start(GF_DasherCtx *ctx, GF_DashStream *ds, GF_F
p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_GROUPID);
if (p)
ds->rep->groupID = p->value.string;
p = gf_filter_pid_get_property(ds->ipid, GF_PROP_PID_HLS_FORCE_INF);
if (p)
ds->rep->hls_forced = p->value.string;

ds->rep->dash_dur = ds->dash_dur;
ds->rep->hls_max_seg_dur = ds->dash_dur;
Expand Down
175 changes: 64 additions & 111 deletions src/media_tools/mpd.c
Expand Up @@ -3467,29 +3467,9 @@ static GF_Err mpd_write_generation_comment(GF_MPD const * const mpd, FILE *out)
return GF_OK;
}

static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Representation *rep, char *m3u8_name, const char *codec_ext, const char *g_type, const char *g_id_pref, u32 g_as_idx, const char *g2_type, const char *g2_id_pref, u32 g2_as_idx, GF_List *groups_done, const GF_MPD_AdaptationSet *set, u32 max_alt_bandwidth, u32 max_alt_width, u32 max_alt_height, Double max_alt_fps)
static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Representation *rep, char *m3u8_name, const char *codec_ext, const char *g_type, const char *g_id_pref, const char *g2_type, const char *g2_id_pref, const GF_MPD_AdaptationSet *set, u32 max_alt_bandwidth, u32 max_alt_width, u32 max_alt_height, Double max_alt_fps)
{
u32 i;
if (groups_done) {
u32 count=gf_list_count(groups_done);
Bool g1_done = GF_FALSE;
Bool g2_done = GF_FALSE;
for (i=0; i<count; i++) {
const char *group_name = gf_list_get(groups_done, i);
if (g_id_pref && !strcmp(group_name, g_id_pref)) g1_done=GF_TRUE;
if (g2_id_pref && !strcmp(group_name, g2_id_pref)) g1_done=GF_TRUE;
}
if (g_id_pref) {
if (g1_done) return;
if (!g_as_idx)
gf_list_add(groups_done, (void *) g_id_pref);
}
if (g2_id_pref) {
if (g2_done) return;
if (!g2_as_idx)
gf_list_add(groups_done, (void *) g2_id_pref);
}
}

if (set && set->intra_only)
gf_fprintf(out, "#EXT-X-I-FRAME-STREAM-INF:");
Expand Down Expand Up @@ -3517,14 +3497,10 @@ static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Repres

if (g_type && g_id_pref) {
gf_fprintf(out, ",%s=\"%s", g_type, g_id_pref);
if (g_as_idx)
gf_fprintf(out, "%d", g_as_idx);
gf_fprintf(out, "\"");
}
if (g2_type && g2_id_pref) {
gf_fprintf(out,",%s=\"%s", g2_type, g2_id_pref);
if (g2_as_idx)
gf_fprintf(out,"%d", g2_as_idx);
gf_fprintf(out,"\"");
}
for (i=0; i<rep->nb_hls_master_tags; i++) {
Expand All @@ -3536,12 +3512,11 @@ static void gf_mpd_write_m3u8_playlist_tags_entry(FILE *out, const GF_MPD_Repres

}

static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32 as_idx, const GF_MPD_Representation *rep, FILE *out, char *m3u8_name, GF_MPD_Period *period, u32 nb_alt_media, u32 nb_subs, u32 nb_cc)
static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32 as_idx, const GF_MPD_Representation *rep, FILE *out, char *m3u8_name, GF_MPD_Period *period, u32 nb_alt_media, u32 nb_subs, u32 nb_cc, const char *forced_inf_ids)
{
u32 i;
GF_MPD_AdaptationSet *r_as;
GF_MPD_Representation *r_rep;
GF_List *groups_done;

if (!rep->mime_type) return;

Expand Down Expand Up @@ -3575,7 +3550,7 @@ static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32
if (rep->groupID)
gf_fprintf(out, ",GROUP-ID=\"%s\"", rep->groupID);
else
gf_fprintf(out, ",GROUP-ID=\"%s%d\"", g_id, as_idx);
gf_fprintf(out, ",GROUP-ID=\"%s\"", g_id);

//write tags, filtering out non-configurable ones
for (i=0; i<rep->nb_hls_master_tags; i++) {
Expand Down Expand Up @@ -3616,102 +3591,74 @@ static void gf_mpd_write_m3u8_playlist_tags(const GF_MPD_AdaptationSet *as, u32
}

//no other streams, directly write the entry
if (!nb_alt_media && !nb_subs && !nb_cc) {
gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, NULL, NULL, NULL, 0, NULL, NULL, 0, NULL, as, 0, 0, 0, 0);
if (!nb_alt_media && !nb_subs && !nb_cc && (!forced_inf_ids || !forced_inf_ids[0])) {
gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, NULL, NULL, NULL, NULL, NULL, as, 0, 0, 0, 0);
return;
}
groups_done = gf_list_new();

//otherwise browse all adaptation sets
//gather all codecs and max values for alternate stream type and subs
char *grp_codecs = NULL;
const char *g_type = NULL;
const char *g_id = NULL;
const char *g_type_subs = NULL;
const char *g_id_subs = NULL;
u32 g_m_bandwidth=0, g_m_bandwidth_subs=0, g_m_width=0, g_m_height=0;
Double g_m_fps=0;
i=0;
while ( (r_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
u32 j, g_as_idx, g_m_bandwidth=0, g_m_width=0, g_m_height=0;
Double g_m_fps=0;
Bool is_alt_media = GF_FALSE;
GF_MPD_AdaptationSet *r2_as;
GF_MPD_Representation *r2_rep;
const char *g_type = NULL;
const char *g_id = NULL;
const char *g_codec = NULL;
if (as==r_as) continue;

g_as_idx = (r_as->group>0) ? r_as->group : i;
r_rep = (GF_MPD_Representation *) gf_list_get(r_as->representations, 0);

//if audio streams are present, the first pass gather audio and we will need a second loop to get subs
if (nb_alt_media) {
if (r_rep->streamtype==GF_STREAM_AUDIO) {
g_type = "AUDIO";
g_id = "audio";
g_codec = r_rep->codecs;
g_m_bandwidth = r_rep->bandwidth;
if (r_rep->groupID) {
g_id = r_rep->groupID;
g_as_idx = 0;
}
u32 j=0;
while ( (r_rep = (GF_MPD_Representation *) gf_list_enum(r_as->representations, &j))) {
const char *hls_group = rep->groupID;
if (forced_inf_ids) {
if (r_rep == rep) continue;
if ((r_rep->streamtype!=rep->streamtype) && (r_rep->streamtype!=GF_STREAM_TEXT)) continue;
hls_group = forced_inf_ids;
} else {
if (r_rep->streamtype==rep->streamtype) continue;
}
else if (r_rep->streamtype==GF_STREAM_VISUAL) {
g_type = "VIDEO";
g_id = "video";
g_codec = r_rep->codecs;
g_m_bandwidth = r_rep->bandwidth;
g_m_width = r_rep->width;
g_m_height = r_rep->height;
g_m_fps = r_rep->fps;
if (r_rep->groupID) {
g_id = r_rep->groupID;
g_as_idx = 0;
//both groups specified, check if we match
if (r_rep->groupID && hls_group) {
char *res = strstr(hls_group, r_rep->groupID);
if (res) {
u32 gid_len = (u32) strlen(r_rep->groupID);
if (res[gid_len] && (res[gid_len]!=',')) res = NULL;
}
if (!res) continue;
}
//if rep we generate EXT-X-STREAM-INF for has a group set, do not match (even if no group set on tested rep)
else if (hls_group) continue;
//always match if no groups are specified

for (j=1; j<gf_list_count(r_as->representations); j++) {
r_rep = (GF_MPD_Representation *) gf_list_get(r_as->representations, 0);
if (r_rep->bandwidth > g_m_bandwidth) g_m_bandwidth = r_rep->bandwidth;
if (r_rep->width > g_m_width) g_m_width = r_rep->width;
if (r_rep->height > g_m_height) g_m_height = r_rep->height;
if (r_rep->fps > g_m_fps) g_m_fps = r_rep->fps;
}
if (gf_sys_is_test_mode() && !g_m_width)
g_m_bandwidth = 0;
if (!grp_codecs || !strstr(grp_codecs, r_rep->codecs))
gf_dynstrcat(&grp_codecs, r_rep->codecs, ",");

is_alt_media = GF_TRUE;
} else {
if (r_rep->streamtype==GF_STREAM_TEXT) {
g_type = "SUBTITLES";
g_id = "subs";
if (r_rep->streamtype==GF_STREAM_AUDIO) {
if (!g_type) g_type = "AUDIO";
if (!g_id) g_id = r_rep->groupID ? r_rep->groupID : "audio";
g_m_bandwidth = MAX(g_m_bandwidth, r_rep->bandwidth);
}
}
//not our type
if (!g_type) continue;
//no audio, or no subs
if (!is_alt_media || !nb_subs) {
gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, g_codec, g_type, g_id, g_as_idx, NULL, NULL, 0, groups_done, as, g_m_bandwidth, g_m_width, g_m_height, g_m_fps);
continue;
}
//audio and subs, we need a second loop on audio to get all subs
j=0;
while ( (r2_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &j))) {
u32 g2_as_idx;
const char *g2_type = NULL;
const char *g2_id = NULL;
if (r_as==r2_as) continue;
g2_as_idx = (r2_as->group>0) ? r2_as->group : j;

r2_rep = (GF_MPD_Representation *) gf_list_get(r2_as->representations, 0);
if (r2_rep->streamtype==GF_STREAM_TEXT) {
g2_type = "SUBTITLES";
g2_id = "subs";
if (r_rep->groupID) {
g2_id = r_rep->groupID;
g2_as_idx = 0;
}
else if (r_rep->streamtype==GF_STREAM_VISUAL) {
if (!g_type) g_type = "VIDEO";
if (!g_id) g_id = r_rep->groupID ? r_rep->groupID : "video";
g_m_bandwidth = MAX(g_m_bandwidth, r_rep->bandwidth);
g_m_width = MAX(g_m_width, r_rep->width);
g_m_height = MAX(g_m_height, r_rep->height);
g_m_fps = MAX(g_m_fps, r_rep->fps);
}
else if (r_rep->streamtype==GF_STREAM_TEXT) {
if (!g_type_subs) g_type_subs = "SUBTITLES";
if (!g_id_subs) g_id_subs = r_rep->groupID ? r_rep->groupID : "subs";
g_m_bandwidth_subs = MAX(g_m_bandwidth_subs, r_rep->bandwidth);
}
if (!g2_type) continue;

gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, g_codec, g_type, g_id, g_as_idx, g2_type, g2_id, g2_as_idx, NULL, as, 0, 0, 0, 0);
}
}
gf_list_del(groups_done);
g_m_bandwidth += g_m_bandwidth_subs;
if (gf_sys_is_test_mode() && !g_m_width)
g_m_bandwidth = 0;

gf_mpd_write_m3u8_playlist_tags_entry(out, rep, m3u8_name, grp_codecs, g_type, g_id, g_type_subs, g_id_subs, as, g_m_bandwidth, g_m_width, g_m_height, g_m_fps);
if (grp_codecs) gf_free(grp_codecs);
grp_codecs=NULL;
}

static const char *gf_mpd_m3u8_get_init_seg(const GF_MPD_Period *period, const GF_MPD_AdaptationSet *as, const GF_MPD_Representation *rep)
Expand Down Expand Up @@ -4299,8 +4246,14 @@ GF_Err gf_mpd_write_m3u8_master_playlist(GF_MPD const * const mpd, FILE *out, co
if (force_base_url && ((mpd->hls_abs_url==2) || (mpd->hls_abs_url==3)))
force_url = gf_url_concatenate(force_base_url, name);

gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, force_url ? force_url : name, is_primary ? period : NULL, nb_alt_streams, nb_subs, nb_cc);
gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, force_url ? force_url : name, is_primary ? period : NULL, nb_alt_streams, nb_subs, nb_cc, 0);
gf_fprintf(out, "\n");

if (!is_primary && rep->hls_forced) {
gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, force_url ? force_url : name, period, 0, 0, 0, rep->hls_forced);
gf_fprintf(out, "\n");
}

if (force_url) gf_free(force_url);
}
}
Expand Down

0 comments on commit aef6de0

Please sign in to comment.