blob: 45dfb55d9ce4cc4cb54fda9f5b36c457f0b4ac14 [file] [edit]
/*
* GPAC - Multimedia Framework C SDK
*
* Authors: Jean Le Feuvre, Cyril Concolato
* Copyright (c) Telecom ParisTech 2000-2026
* All rights reserved
*
* This file is part of GPAC / 3GPP/MPEG Media Presentation Description input module
*
* GPAC is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GPAC is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <gpac/mpd.h>
#include <gpac/base_coding.h>
#include <gpac/download.h>
#include <gpac/internal/m3u8.h>
#include <gpac/network.h>
#include <gpac/maths.h>
#ifndef GPAC_DISABLE_MPD
#define MPD_STORE_EXTENSION_ATTR(_elem) \
if (!_elem->x_attributes) _elem->x_attributes = gf_list_new(); \
i--; \
gf_list_rem(root->attributes, i); \
gf_list_add(_elem->x_attributes, att); \
#define MPD_STORE_EXTENSION_NODE(_elem) \
if (!_elem->x_children) _elem->x_children = gf_list_new(); \
i--; \
gf_list_rem(root->content, i); \
child->orig_pos = child_idx;\
gf_list_add(_elem->x_children, child); \
#define MPD_FREE_EXTENSION_NODE(_elem) \
if (_elem->x_attributes) {\
while (gf_list_count(_elem->x_attributes)) {\
GF_XMLAttribute *att = gf_list_pop_back(_elem->x_attributes);\
if (att->name) gf_free(att->name);\
if (att->value) gf_free(att->value);\
gf_free(att);\
}\
gf_list_del(_elem->x_attributes);\
}\
if (_elem->x_children) {\
while (gf_list_count(_elem->x_children)) {\
GF_XMLNode *child = gf_list_pop_back(_elem->x_children);\
gf_xml_dom_node_del(child);\
}\
gf_list_del(_elem->x_children);\
}\
static Bool gf_mpd_parse_bool(const char * const attr)
{
if (!strcmp(attr, "true")) return 1;
if (!strcmp(attr, "1")) return 1;
return 0;
}
static char *gf_mpd_parse_string(char *attr)
{
return gf_strdup(attr);
}
static Bool gf_mpd_valid_child(GF_MPD *mpd, GF_XMLNode *child)
{
if (child->type != GF_XML_NODE_TYPE) return 0;
if (!mpd->xml_namespace && !child->ns) return 1;
if (mpd->xml_namespace && child->ns && !strcmp(mpd->xml_namespace, child->ns)) return 1;
if (child->ns && !strcmp(child->ns, "gpac")) return 1;
return 0;
}
static char *gf_mpd_parse_text_content(GF_XMLNode *child)
{
u32 child_index = 0;
while (1) {
child = gf_list_get(child->content, child_index);
if (!child) {
break;
} else if (child->type == GF_XML_TEXT_TYPE) {
return gf_mpd_parse_string(child->name);
}
child_index++;
}
return NULL;
}
static u32 gf_mpd_parse_int(const char * const attr)
{
return atoi(attr);
}
static u64 gf_mpd_parse_long_int(const char * const attr)
{
u64 longint;
sscanf(attr, LLU, &longint);
return longint;
}
static Double gf_mpd_parse_double(const char * const attr)
{
return atof(attr);
}
static GF_MPD_Fractional *gf_mpd_parse_frac(const char * const attr, const char sep, GF_MPD_Fractional *res)
{
char str[6];
int ok;
if (res==NULL) {
GF_SAFEALLOC(res, GF_MPD_Fractional);
if (!res) return NULL;
res->den = 1;
}
snprintf(str, sizeof(str), "%%d%c%%d", sep);
ok = sscanf(attr, str, &res->num, &res->den);
if (ok!=2) {
res->den = 1;
res->num = atoi(attr);
}
return res;
}
static GF_Fraction gf_mpd_parse_fraction(const char * const attr)
{
GF_Fraction res;
int ok = sscanf(attr, "%d/%u", &res.num, &res.den);
if (ok!=2) {
res.den = 1;
res.num = atoi(attr);
}
return res;
}
u64 gf_mpd_parse_date(const char * const attr)
{
return gf_net_parse_date(attr);
}
static u64 gf_mpd_parse_duration(const char * const duration)
{
u32 i;
char *sep1, *sep2;
u32 h, m, s, ms;
u32 year, month, day;
Bool has_year, has_month, has_day;
u64 y_dur;
const char *startT;
if (!duration) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Error parsing duration: no value indicated\n"));
return 0;
}
i = 0;
while (1) {
if (duration[i] == ' ') i++;
else if (duration[i] == 0) return 0;
else {
break;
}
}
if (duration[i] != 'P') {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Error parsing duration: no value indicated\n"));
return 0;
}
startT = strchr(duration+1, 'T');
if (duration[i+1] == 0) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Error parsing duration: no value indicated\n"));
return 0;
}
year = month = day = 0;
has_year = strchr(duration+1, 'Y') ? GF_TRUE : GF_FALSE;
has_month = strchr(duration+1, 'M') ? GF_TRUE : GF_FALSE;
has_day = strchr(duration+1, 'D') ? GF_TRUE : GF_FALSE;
if (has_year && has_month && has_day) sscanf(duration+1, "%dY%dM%dD", &year, &month, &day);
else if (has_month && has_day) sscanf(duration+1, "%dM%dD", &month, &day);
else if (has_year && has_month) sscanf(duration+1, "%dY%dM", &year, &month);
else if (has_year && has_day) sscanf(duration+1, "%dY%dD", &year, &day);
else if (has_year) sscanf(duration+1, "%dY", &year);
else if (has_month) sscanf(duration+1, "%dM", &month);
else if (has_day) sscanf(duration+1, "%dD", &day);
y_dur = (year*365 + month*30 + day ) * 24;
y_dur *= 3600;
y_dur *= 1000;
if (! startT) return y_dur;
h = m = s = ms = 0;
if (NULL != (sep1 = strchr(startT+1, 'H'))) {
*sep1 = 0;
h = atoi(duration+i+2);
*sep1 = 'H';
sep1++;
} else {
sep1 = (char *) startT+1;
}
if (NULL != (sep2 = strchr(sep1, 'M'))) {
*sep2 = 0;
m = atoi(sep1);
*sep2 = 'M';
sep2++;
} else {
sep2 = sep1;
}
if (NULL != (sep1 = strchr(sep2, 'S'))) {
char *sep_dec = strchr(sep2, '.');
*sep1 = 0;
if (sep_dec) {
sep_dec[0] = 0;
s = atoi(sep2);
ms = atoi(sep_dec+1);
sep_dec[0] = '.';
} else {
s = atoi(sep2);
}
*sep1 = 'S';
}
return y_dur + (u64) ( ((h*3600+m*60+s)*(u64)1000) + ms );
}
static u32 gf_mpd_parse_duration_u32(const char * const duration)
{
u64 dur = gf_mpd_parse_duration(duration);
if (dur <= GF_UINT_MAX) {
return (u32)dur;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD] Parsed duration %s ("LLU") doesn't fit on 32 bits! Setting to the 32 bits max.\n", duration, dur));
return GF_UINT_MAX;
}
}
static GF_MPD_ByteRange *gf_mpd_parse_byte_range(const char * const attr)
{
GF_MPD_ByteRange *br;
GF_SAFEALLOC(br, GF_MPD_ByteRange);
if (!br) return NULL;
sscanf(attr, LLD"-"LLD, &br->start_range, &br->end_range);
return br;
}
GF_Err gf_mpd_parse_base_url(GF_List *container, GF_XMLNode *node)
{
u32 i;
GF_Err e;
GF_XMLAttribute *att;
GF_MPD_BaseURL *url;
GF_SAFEALLOC(url, GF_MPD_BaseURL);
if (! url) return GF_OUT_OF_MEM;
e = gf_list_add(container, url);
if (e) return GF_OUT_OF_MEM;
i = 0;
while ( (att = gf_list_enum(node->attributes, &i))) {
if (!strcmp(att->name, "serviceLocation")) url->service_location = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "byteRange")) url->byte_range = gf_mpd_parse_byte_range(att->value);
}
url->URL = gf_mpd_parse_text_content(node);
return GF_OK;
}
static GF_Err gf_mpd_parse_program_info(GF_MPD *mpd, GF_XMLNode *root)
{
GF_MPD_ProgramInfo *info;
GF_XMLNode *child;
GF_XMLAttribute *att;
u32 child_idx, i;
GF_SAFEALLOC(info, GF_MPD_ProgramInfo);
if (!info) return GF_OUT_OF_MEM;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "moreInformationURL")) {
info->more_info_url = gf_mpd_parse_string(att->value);
} else if (!strcmp(att->name, "lang")) {
info->lang = gf_mpd_parse_string(att->value);
} else {
MPD_STORE_EXTENSION_ATTR(info)
}
}
child_idx = i = 0;
while ((child = gf_list_enum(root->content, &i))) {
if (child->type != GF_XML_NODE_TYPE)
continue;
if (!strcmp(child->name, "Title")) {
GF_XMLNode *data_node = gf_list_get(child->content, 0);
if (data_node && data_node->type == GF_XML_TEXT_TYPE) {
info->title = gf_strdup(data_node->name);
}
} else if (!strcmp(child->name, "Source")) {
GF_XMLNode *data_node = gf_list_get(child->content, 0);
if (data_node && data_node->type == GF_XML_TEXT_TYPE) {
info->source = gf_strdup(data_node->name);
}
} else if (!strcmp(child->name, "Copyright")) {
GF_XMLNode *data_node = gf_list_get(child->content, 0);
if (data_node && data_node->type == GF_XML_TEXT_TYPE) {
info->copyright = gf_strdup(data_node->name);
}
} else {
MPD_STORE_EXTENSION_NODE(info)
}
child_idx++;
}
return gf_list_add(mpd->program_infos, info);
}
static GF_MPD_URL *gf_mpd_parse_url(GF_XMLNode *root)
{
u32 i;
GF_MPD_URL *url;
GF_XMLAttribute *att;
GF_SAFEALLOC(url, GF_MPD_URL);
if (!url) return NULL;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "sourceURL")) url->sourceURL = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "range")) url->byte_range = gf_mpd_parse_byte_range(att->value);
}
return url;
}
static void gf_mpd_parse_segment_base_generic(GF_MPD *mpd, GF_MPD_SegmentBase *seg, GF_XMLNode *root)
{
GF_XMLAttribute *att;
GF_XMLNode *child;
u32 i = 0;
/*infinite by default*/
seg->time_shift_buffer_depth = (u32) -1; /*infinite by default*/
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "timescale")) seg->timescale = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "presentationTimeOffset")) seg->presentation_time_offset = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "indexRange")) seg->index_range = gf_mpd_parse_byte_range(att->value);
else if (!strcmp(att->name, "indexRangeExact")) seg->index_range_exact = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "availabilityTimeOffset")) seg->availability_time_offset = gf_mpd_parse_double(att->value);
else if (!strcmp(att->name, "timeShiftBufferDepth")) seg->time_shift_buffer_depth = gf_mpd_parse_duration_u32(att->value);
}
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child)) continue;
if (!strcmp(child->name, "Initialization")) seg->initialization_segment = gf_mpd_parse_url(child);
else if (!strcmp(child->name, "RepresentationIndex")) seg->representation_index = gf_mpd_parse_url(child);
}
}
static GF_MPD_SegmentTimeline *gf_mpd_parse_segment_timeline(GF_MPD *mpd, GF_XMLNode *root)
{
u32 i, j;
u64 curr_start_time = 0;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_MPD_SegmentTimeline *seg;
GF_SAFEALLOC(seg, GF_MPD_SegmentTimeline);
if (!seg) return NULL;
seg->entries = gf_list_new();
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child)) continue;
if (!strcmp(child->name, "S")) {
GF_MPD_SegmentTimelineEntry *seg_tl_ent;
GF_SAFEALLOC(seg_tl_ent, GF_MPD_SegmentTimelineEntry);
if (!seg_tl_ent) continue;
seg_tl_ent->start_time = curr_start_time;
gf_list_add(seg->entries, seg_tl_ent);
j = 0;
while ( (att = gf_list_enum(child->attributes, &j)) ) {
if (!strcmp(att->name, "t"))
seg_tl_ent->start_time = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "d"))
seg_tl_ent->duration = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "r")) {
seg_tl_ent->repeat_count = gf_mpd_parse_int(att->value);
if (seg_tl_ent->repeat_count == (u32)-1)
seg_tl_ent->repeat_count--;
}
else if (!strcmp(att->name, "k"))
seg_tl_ent->nb_parts = gf_mpd_parse_int(att->value);
}
if (seg_tl_ent->start_time)
curr_start_time = seg_tl_ent->start_time;
curr_start_time += (u64) (seg_tl_ent->duration * (seg_tl_ent->repeat_count+1));
}
}
return seg;
}
static GF_MPD_SegmentBase *gf_mpd_parse_segment_base(GF_MPD *mpd, GF_XMLNode *root)
{
GF_MPD_SegmentBase *seg;
GF_SAFEALLOC(seg, GF_MPD_SegmentBase);
if (!seg) return NULL;
gf_mpd_parse_segment_base_generic(mpd, seg, root);
return seg;
}
void gf_mpd_parse_multiple_segment_base(GF_MPD *mpd, GF_MPD_MultipleSegmentBase *seg, GF_XMLNode *root)
{
u32 i;
GF_XMLAttribute *att;
GF_XMLNode *child;
gf_mpd_parse_segment_base_generic(mpd, (GF_MPD_SegmentBase*)seg, root);
seg->start_number = (u32) -1;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "duration")) seg->duration = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "startNumber")) seg->start_number = gf_mpd_parse_int(att->value);
}
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child)) continue;
if (!strcmp(child->name, "SegmentTimeline")) seg->segment_timeline = gf_mpd_parse_segment_timeline(mpd, child);
else if (!strcmp(child->name, "BitstreamSwitching")) seg->bitstream_switching_url = gf_mpd_parse_url(child);
}
}
#if 0 //unused
GF_MPD_SegmentURL *gf_mpd_segmenturl_new(const char*media, u64 start_range, u64 end_range, const char *index, u64 idx_start_range, u64 idx_end_range)
{
GF_MPD_SegmentURL *seg_url;
GF_SAFEALLOC(seg_url, GF_MPD_SegmentURL);
GF_SAFEALLOC(seg_url->media_range, GF_MPD_ByteRange);
seg_url->media_range->start_range = start_range;
seg_url->media_range->end_range = end_range;
if (idx_start_range || idx_end_range) {
GF_SAFEALLOC(seg_url->index_range, GF_MPD_ByteRange);
seg_url->index_range->start_range = idx_start_range;
seg_url->index_range->end_range = idx_end_range;
}
if(media)
seg_url->media=gf_strdup(media);
return seg_url;
}
#endif
void gf_mpd_parse_segment_url(GF_List *container, GF_XMLNode *root)
{
u32 i;
GF_MPD_SegmentURL *seg;
GF_XMLAttribute *att;
GF_SAFEALLOC(seg, GF_MPD_SegmentURL);
if (!seg) return;
gf_list_add(container, seg);
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "media")) seg->media = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "index")) seg->index = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "mediaRange")) seg->media_range = gf_mpd_parse_byte_range(att->value);
else if (!strcmp(att->name, "indexRange")) seg->index_range = gf_mpd_parse_byte_range(att->value);
//else if (!strcmp(att->name, "hls:keyMethod")) seg->key_url = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "hls:keyURL")) seg->key_url = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "hls:keyIV")) {
GF_Err e = gf_bin128_parse(att->value, seg->key_iv);
if (e != GF_OK) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Cannot parse hls:keyIV\n"));
return;
}
}
else if (!strcmp(att->name, "duration")) seg->duration=gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "t")) seg->first_tfdt = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "n")) seg->first_pck_seq = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "d")) seg->duration = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "s")) seg->frag_start_offset = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "dt")) seg->frag_tfdt = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "f")) seg->split_first_dur = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "l")) seg->split_last_dur = gf_mpd_parse_int(att->value);
}
}
static GF_MPD_SegmentList *gf_mpd_parse_segment_list(GF_MPD *mpd, GF_XMLNode *root)
{
u32 i;
GF_MPD_SegmentList *seg;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_SAFEALLOC(seg, GF_MPD_SegmentList);
if (!seg) return NULL;
seg->segment_URLs = gf_list_new();
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (strstr(att->name, "href")) seg->xlink_href = gf_mpd_parse_string(att->value);
else if (strstr(att->name, "actuate")) seg->xlink_actuate_on_load = !strcmp(att->value, "onLoad") ? 1 : 0;
else if (!strcmp(att->name, "sampleDuration")) seg->sample_duration = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "srcTimescale")) seg->src_timescale = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "srcDelay")) seg->pid_delay = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "firstCTSOffset")) seg->first_cts_offset = att->value ? atoi(att->value) : 0;
}
gf_mpd_parse_multiple_segment_base(mpd, (GF_MPD_MultipleSegmentBase *)seg, root);
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child)) continue;
if (mpd->index_mode) {
if (!strcmp(child->name, "I")) gf_mpd_parse_segment_url(seg->segment_URLs, child);
} else {
if (!strcmp(child->name, "SegmentURL")) gf_mpd_parse_segment_url(seg->segment_URLs, child);
}
}
if (!gf_list_count(seg->segment_URLs)) {
gf_list_del(seg->segment_URLs);
seg->segment_URLs = NULL;
}
return seg;
}
static GF_MPD_SegmentTemplate *gf_mpd_parse_segment_template(GF_MPD *mpd, GF_XMLNode *root)
{
u32 i;
GF_MPD_SegmentTemplate *seg;
GF_XMLAttribute *att;
GF_SAFEALLOC(seg, GF_MPD_SegmentTemplate);
if (!seg) return NULL;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "media")) seg->media = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "index")) seg->index = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "initialization") ) seg->initialization = gf_mpd_parse_string(att->value);
else if (!stricmp(att->name, "initialisation") || !stricmp(att->name, "initialization") ) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Wrong spelling: got %s but expected \"initialization\" \n", att->name ));
seg->initialization = gf_mpd_parse_string(att->value);
}
else if (!strcmp(att->name, "bitstreamSwitching")) seg->bitstream_switching = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "k"))
seg->nb_parts = gf_mpd_parse_int(att->value);
}
gf_mpd_parse_multiple_segment_base(mpd, (GF_MPD_MultipleSegmentBase *)seg, root);
return seg;
}
static GF_Err gf_mpd_parse_content_component(GF_List *comps, GF_XMLNode *root)
{
u32 i;
GF_XMLAttribute *att;
GF_MPD_ContentComponent *comp;
GF_SAFEALLOC(comp, GF_MPD_ContentComponent);
if (!comp) return GF_OUT_OF_MEM;
i = 0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "id")) comp->id = atoi(att->value);
else if (!strcmp(att->name, "contentType")) comp->type = gf_strdup(att->value);
else if (!strcmp(att->name, "lang")) comp->lang = gf_strdup(att->value);
}
gf_list_add(comps, comp);
return GF_OK;
}
static GF_Err gf_mpd_parse_inband_event(GF_List *comps, GF_XMLNode *root) {
u32 i;
GF_XMLAttribute *att;
GF_MPD_Inband_Event *ibe;
GF_SAFEALLOC(ibe, GF_MPD_Inband_Event);
if (!ibe) return GF_OUT_OF_MEM;
i = 0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "schemeIdUri")) ibe->scheme_id_uri = gf_strdup(att->value);
else if (!strcmp(att->name, "value")) ibe->value = gf_strdup(att->value);
}
gf_list_add(comps, ibe);
return GF_OK;
}
static GF_Err gf_mpd_parse_event_streams(GF_List *comps, GF_XMLNode *root) {
u32 i;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_MPD_EventStream *es;
GF_SAFEALLOC(es, GF_MPD_EventStream);
if (!es) return GF_OUT_OF_MEM;
es->entries = gf_list_new();
if (!es->entries) {
gf_free(es);
return GF_OUT_OF_MEM;
}
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "schemeIdUri")) es->scheme_id_uri = gf_strdup(att->value);
else if (!strcmp(att->name, "timescale")) es->timescale = gf_mpd_parse_int(att->value);
}
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (child->type != GF_XML_NODE_TYPE) continue;
if (!strcmp(child->name, "Event")) {
GF_MPD_EventStreamEntry *event;
GF_SAFEALLOC(event, GF_MPD_EventStreamEntry);
if (!event) return GF_OUT_OF_MEM;;
gf_list_add(es->entries, event);
u32 j = 0;
GF_XMLAttribute *event_att;
while ( (event_att = gf_list_enum(child->attributes, &j)) ) {
if (!strcmp(event_att->name, "presentationTime")) event->presentation_time = gf_mpd_parse_long_int(event_att->value);
else if (!strcmp(event_att->name, "duration")) event->duration = gf_mpd_parse_int(event_att->value);
else if (!strcmp(event_att->name, "id")) event->id = gf_mpd_parse_int(event_att->value);
GF_XMLNode *signal;
u32 k = 0;
while ( (signal = gf_list_enum(child->content, &k)) ) {
if (signal->type != GF_XML_NODE_TYPE) continue;
if (!strcmp(signal->name, "Signal")) {
GF_XMLNode *binary;
u32 l = 0;
while ( (binary = gf_list_enum(signal->content, &l)) ) {
if (binary->type != GF_XML_NODE_TYPE) continue;
if (!strcmp(binary->name, "Binary")) {
GF_XMLNode *b64;
u32 m = 0;
while ( (b64 = gf_list_enum(binary->content, &m)) ) {
if (b64->type != GF_XML_TEXT_TYPE) continue;
if(!event->message) {
event->message_size = (u32)strlen(b64->name)+1;
event->message = gf_malloc(event->message_size);
if (!event->message) return GF_OUT_OF_MEM;
event->message_size = gf_base64_decode((u8*)b64->name, event->message_size, event->message, event->message_size);
}
}
}
}
}
}
}
}
}
gf_list_add(comps, es);
return GF_OK;
}
static GF_Err gf_mpd_parse_descriptor_ex(GF_List *container, GF_MPD_Descriptor **out_ptr, GF_XMLNode *root)
{
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_MPD_Descriptor *mpd_desc;
u32 i = 0;
u32 child_idx = 0;
GF_SAFEALLOC(mpd_desc, GF_MPD_Descriptor);
if (!mpd_desc) return GF_OUT_OF_MEM;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "schemeIdUri")) mpd_desc->scheme_id_uri = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "value")) mpd_desc->value = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "id")) mpd_desc->id = gf_mpd_parse_string(att->value);
else {
MPD_STORE_EXTENSION_ATTR(mpd_desc)
}
}
if (container)
gf_list_add(container, mpd_desc);
else {
gf_assert(out_ptr);
if (*out_ptr) gf_mpd_descriptor_free(*out_ptr);
*out_ptr = mpd_desc;
}
i = 0;
child_idx = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (child->type != GF_XML_NODE_TYPE) continue;
MPD_STORE_EXTENSION_NODE(mpd_desc)
child_idx++;
}
return GF_OK;
}
static GF_Err gf_mpd_parse_descriptor(GF_List *container, GF_XMLNode *root)
{
return gf_mpd_parse_descriptor_ex(container, NULL, root);
}
GF_MPD_ProducerReferenceTime *gf_mpd_parse_produce_ref_time(GF_XMLNode *root)
{
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_MPD_ProducerReferenceTime *pref;
u32 i = 0;
GF_SAFEALLOC(pref, GF_MPD_ProducerReferenceTime);
if (!pref) return NULL;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "id")) pref->ID = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "inband")) pref->inband = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "type")) {
if (!strcmp(att->value, "application")) pref->type = GF_MPD_PRODUCER_REF_APPLICATION;
else if (!strcmp(att->value, "captured")) pref->type = GF_MPD_PRODUCER_REF_CAPTURED;
}
else if (!strcmp(att->name, "applicationScheme") && !pref->scheme) pref->scheme = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "wallClockTime") && !pref->wallclock) pref->wallclock = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "presentationTime")) pref->presentation_time = gf_mpd_parse_long_int(att->value);
}
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (child->type != GF_XML_NODE_TYPE) continue;
if (!strcmp(child->name, "UTCTiming"))
gf_mpd_parse_descriptor_ex(NULL, &pref->utc_timing, child);
}
return pref;
}
static void gf_mpd_parse_common_representation_attr(GF_MPD *mpd, GF_MPD_CommonAttributes *com, GF_XMLNode *root, GF_XMLAttribute *att, u32 *index)
{
if (!strcmp(att->name, "profiles")) com->profiles = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "width")) com->width = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "height")) com->height = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "sar")) {
if (com->sar) gf_free(com->sar);
com->sar = gf_mpd_parse_frac(att->value, ':', NULL);
}
else if (!strcmp(att->name, "frameRate")) {
if (com->framerate) gf_free(com->framerate);
com->framerate = gf_mpd_parse_frac(att->value, '/', NULL);
}
else if (!strcmp(att->name, "audioSamplingRate")) com->samplerate = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "mimeType")) com->mime_type = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "segmentProfiles")) com->segmentProfiles = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "codecs")) com->codecs = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "maximumSAPPeriod")) com->maximum_sap_period = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "startWithSAP")) {
if (!strcmp(att->value, "false")) com->starts_with_sap = 0;
else com->starts_with_sap = gf_mpd_parse_int(att->value);
}
else if (!strcmp(att->name, "maxPlayoutRate")) com->max_playout_rate = gf_mpd_parse_double(att->value);
else if (!strcmp(att->name, "codingDependency")) com->coding_dependency = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "scanType")) {
if (!strcmp(att->value, "progressive")) com->scan_type = GF_MPD_SCANTYPE_PROGRESSIVE;
else if (!strcmp(att->value, "interlaced")) com->scan_type = GF_MPD_SCANTYPE_INTERLACED;
}
else if (!strcmp(att->name, "selectionPriority")) com->selection_priority = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "tag")) com->tag = gf_mpd_parse_string(att->value);
else {
u32 i = *index;
MPD_STORE_EXTENSION_ATTR(com);
(*index) = i;
}
}
static void gf_mpd_parse_common_representation_child(GF_MPD *mpd, GF_MPD_CommonAttributes *com, GF_XMLNode *root, GF_XMLNode *child, u32 *index, u32 child_idx)
{
if (!strcmp(child->name, "FramePacking")) {
gf_mpd_parse_descriptor(com->frame_packing, child);
}
else if (!strcmp(child->name, "AudioChannelConfiguration")) {
gf_mpd_parse_descriptor(com->audio_channels, child);
}
else if (!strcmp(child->name, "ContentProtection")) {
gf_mpd_parse_descriptor(com->content_protection, child);
}
else if (!strcmp(child->name, "EssentialProperty")) {
gf_mpd_parse_descriptor(com->essential_properties, child);
}
else if (!strcmp(child->name, "SupplementalProperty")) {
gf_mpd_parse_descriptor(com->supplemental_properties, child);
}
else if (!strcmp(child->name, "ProducerReferenceTime")) {
GF_MPD_ProducerReferenceTime *pref = gf_mpd_parse_produce_ref_time(child);
if (pref) {
if (!com->producer_reference_time) com->producer_reference_time = gf_list_new();
gf_list_add(com->producer_reference_time, pref);
}
} else {
u32 i = *index;
MPD_STORE_EXTENSION_NODE(com);
*index = i;
}
}
static void gf_mpd_init_common_attributes(GF_MPD_CommonAttributes *com)
{
com->audio_channels = gf_list_new();
com->content_protection = gf_list_new();
com->essential_properties = gf_list_new();
com->supplemental_properties = gf_list_new();
com->frame_packing = gf_list_new();
com->max_playout_rate = 1.0;
com->group_labels = gf_list_new();
com->labels = gf_list_new();
}
GF_MPD_Representation *gf_mpd_representation_new()
{
GF_MPD_Representation *rep;
GF_SAFEALLOC(rep, GF_MPD_Representation);
if (!rep) return NULL;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)rep);
rep->base_URLs = gf_list_new();
rep->sub_representations = gf_list_new();
rep->hls_max_seg_dur.den = 1;
return rep;
}
GF_MPD_Preselection* gf_mpd_preselection_new(u32 id)
{
GF_MPD_Preselection *p;
GF_SAFEALLOC(p, GF_MPD_Preselection);
if (!p) return NULL;
p->id = id;
p->x_children = gf_list_new();
p->x_attributes = gf_list_new();
p->accessibility = gf_list_new();
p->role = gf_list_new();
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)p);
return p;
}
GF_MPD_GroupLabel* gf_mpd_grouplabel_new(u32 id, const char *lang, const char *content)
{
GF_MPD_GroupLabel *l = NULL;
GF_SAFEALLOC(l, GF_MPD_GroupLabel);
if (!l) return NULL;
l->id = id;
if (lang) l->lang = gf_strdup(lang);
if (content) l->content = gf_strdup(content);
return l;
}
GF_MPD_Label* gf_mpd_label_new(u32 id, const char *lang, const char *content)
{
GF_MPD_Label *l = NULL;
GF_SAFEALLOC(l, GF_MPD_Label);
if (!l) return NULL;
l->id = id;
if (lang) l->lang = gf_strdup(lang);
if (content) l->content = gf_strdup(content);
return l;
}
static GF_DASH_SegmenterContext *gf_mpd_parse_dasher_context(GF_MPD *mpd, GF_XMLNode *root)
{
u32 i;
GF_DASH_SegmenterContext *dasher;
GF_XMLAttribute *att;
GF_SAFEALLOC(dasher, GF_DASH_SegmenterContext);
if (!dasher) return NULL;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "done")) dasher->done = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "init")) dasher->init_seg = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "template")) dasher->template_seg = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "index")) dasher->template_idx = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "url")) dasher->src_url = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "periodID")) dasher->period_id = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "segNumber")) dasher->seg_number = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "lastPacketIdx")) dasher->last_pck_idx = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "pidID")) dasher->pid_id = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "depID")) dasher->dep_pid_id = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "periodStart")) gf_parse_lfrac(att->value, &dasher->period_start);
else if (!strcmp(att->name, "periodDuration")) gf_parse_lfrac(att->value, &dasher->period_duration);
else if (!strcmp(att->name, "ownsSet")) dasher->owns_set = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "multiPIDInit")) dasher->multi_pids = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "dashDuration")) dasher->dash_dur = gf_mpd_parse_fraction(att->value);
else if (!strcmp(att->name, "nextSegmentStart")) dasher->next_seg_start = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "firstCTS")) dasher->first_cts = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "firstDTS")) dasher->first_dts = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "estimatedNextDTS")) dasher->est_next_dts = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "nbRepeat")) dasher->nb_repeat = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "tsOffset")) dasher->ts_offset = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "mpdTimescale")) dasher->mpd_timescale = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "sourcePID")) dasher->source_pid = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "cumulatedDur")) dasher->cumulated_dur = gf_mpd_parse_double(att->value);
else if (!strcmp(att->name, "cumulatedSubdur")) dasher->cumulated_subdur = gf_mpd_parse_double(att->value);
else if (!strcmp(att->name, "muxPIDs")) dasher->mux_pids = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "segsPurged")) dasher->segs_purged = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "durPurged")) dasher->dur_purged = gf_mpd_parse_double(att->value);
else if (!strcmp(att->name, "moofSN")) dasher->moof_sn = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "moofInc")) dasher->moof_sn_inc = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "lastDynPeriodID")) dasher->last_dyn_period_id = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "subdurForced")) dasher->subdur_forced = gf_mpd_parse_bool(att->value);
}
return dasher;
}
static GF_List *gf_mpd_parse_segments_context(GF_MPD *mpd, GF_XMLNode *root)
{
GF_List *res = NULL;
u32 i, j;
GF_XMLAttribute *att;
GF_XMLNode *child;
i=0;
while ((child = gf_list_enum(root->content, &i))) {
GF_DASH_SegmentContext *sctx;
if (!gf_mpd_valid_child(mpd, child)) continue;
if (strcmp(child->name, "segmentInfo")) continue;
if (!res) res = gf_list_new();
GF_SAFEALLOC(sctx, GF_DASH_SegmentContext);
if (!sctx) break;
gf_list_add(res, sctx);
j = 0;
while ( (att = gf_list_enum(child->attributes, &j)) ) {
if (!strcmp(att->name, "file")) sctx->filename = gf_mpd_parse_string(att->value);
if (!strcmp(att->name, "path")) sctx->filepath = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "time")) sctx->time = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "dur")) sctx->dur = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "size")) sctx->file_size = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "offset")) sctx->file_offset = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "idx_size")) sctx->index_size = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "idx_offset")) sctx->index_offset = gf_mpd_parse_long_int(att->value);
else if (!strcmp(att->name, "seg_num")) sctx->seg_num = gf_mpd_parse_int(att->value);
}
}
return res;
}
static GF_Err gf_mpd_parse_representation(GF_MPD *mpd, GF_List *container, GF_XMLNode *root)
{
u32 i, child_idx;
GF_MPD_Representation *rep;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_Err e;
rep = gf_mpd_representation_new();
e = gf_list_add(container, rep);
if (e) return e;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "id")) rep->id = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "bandwidth")) rep->bandwidth = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "qualityRanking")) rep->quality_ranking = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "dependencyId")) rep->dependency_id = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "mediaStreamStructureId")) rep->media_stream_structure_id = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "src")) rep->res_url = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "trackID")) rep->trackID = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "gpac:discontinuityID")) rep->discontinuity_id = gf_mpd_parse_int(att->value);
else gf_mpd_parse_common_representation_attr(mpd, (GF_MPD_CommonAttributes*)rep, root, att, &i);
}
i = 0;
child_idx = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child))
continue;
if (!strcmp(child->name, "BaseURL")) {
e = gf_mpd_parse_base_url(rep->base_URLs, child);
if (e) return e;
}
else if (!strcmp(child->name, "SegmentBase")) {
if (mpd->index_mode) return GF_NON_COMPLIANT_BITSTREAM;
rep->segment_base = gf_mpd_parse_segment_base(mpd, child);
}
else if (!strcmp(child->name, "SegmentList")) {
rep->segment_list = gf_mpd_parse_segment_list(mpd, child);
}
else if (!strcmp(child->name, "SegmentTemplate")) {
if (mpd->index_mode) return GF_NON_COMPLIANT_BITSTREAM;
rep->segment_template = gf_mpd_parse_segment_template(mpd, child);
}
/*TODO
else if (!strcmp(child->name, "SubRepresentation")) {
e = gf_mpd_parse_subrepresentation(rep->sub_representations, child);
if (e) return e;
}
*/
else if (!strcmp(child->name, "dasher")) {
gf_assert(!rep->dasher_ctx);
rep->dasher_ctx = gf_mpd_parse_dasher_context(mpd, child);
}
else if (!strcmp(child->name, "segments")) {
gf_assert(!rep->state_seg_list);
rep->state_seg_list = gf_mpd_parse_segments_context(mpd, child);
} else {
gf_mpd_parse_common_representation_child(mpd, (GF_MPD_CommonAttributes*)rep, root, child, &i, child_idx);
}
child_idx++;
}
return GF_OK;
}
GF_MPD_AdaptationSet *gf_mpd_adaptation_set_new() {
GF_MPD_AdaptationSet *set;
GF_SAFEALLOC(set, GF_MPD_AdaptationSet);
if (!set) return NULL;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)set);
set->accessibility = gf_list_new();
set->role = gf_list_new();
set->rating = gf_list_new();
set->viewpoint = gf_list_new();
set->content_component = gf_list_new();
set->inband_event = gf_list_new();
set->base_URLs = gf_list_new();
set->representations = gf_list_new();
GF_SAFEALLOC(set->par, GF_MPD_Fractional);
/*assign default ID and group*/
set->group = -1;
set->id = -1;
return set;
}
static GF_Err gf_mpd_parse_adaptation_set(GF_MPD *mpd, GF_List *container, GF_XMLNode *root)
{
u32 i, child_idx;
GF_MPD_AdaptationSet *set;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_Err e;
set = gf_mpd_adaptation_set_new();
if (!set) return GF_OUT_OF_MEM;
e = gf_list_add(container, set);
if (e) return e;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (strstr(att->name, "href")) set->xlink_href = gf_mpd_parse_string(att->value);
else if (strstr(att->name, "actuate")) set->xlink_actuate_on_load = !strcmp(att->value, "onLoad") ? GF_TRUE : GF_FALSE;
else if (!strcmp(att->name, "id")) set->id = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "group")) set->group = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "lang")) set->lang = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "contentType")) set->content_type = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "par")) {
if (set->par) gf_free(set->par);
set->par = gf_mpd_parse_frac(att->value, ':', NULL);
}
else if (!strcmp(att->name, "minBandwidth")) set->min_bandwidth = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "maxBandwidth")) set->max_bandwidth = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "minWidth")) set->min_width = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "maxWidth")) set->max_width = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "minHeight")) set->min_height = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "maxHeight")) set->max_height = gf_mpd_parse_int(att->value);
else if (!strcmp(att->name, "minFrameRate")) gf_mpd_parse_frac(att->value, '/', &set->min_framerate);
else if (!strcmp(att->name, "maxFrameRate")) gf_mpd_parse_frac(att->value, '/', &set->max_framerate);
else if (!strcmp(att->name, "segmentAlignment")) set->segment_alignment = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "bitstreamSwitching")) set->bitstream_switching = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "subsegmentAlignment")) set->subsegment_alignment = gf_mpd_parse_bool(att->value);
else if (!strcmp(att->name, "subsegmentStartsWithSAP")) {
if (!strcmp(att->value, "false")) set->subsegment_starts_with_sap = 0;
else set->subsegment_starts_with_sap = gf_mpd_parse_int(att->value);
}
else gf_mpd_parse_common_representation_attr(mpd, (GF_MPD_CommonAttributes*)set, root, att, &i);
}
child_idx = 0;
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child)) continue;
if (!strcmp(child->name, "Accessibility")) {
e = gf_mpd_parse_descriptor(set->accessibility, child);
if (e) return e;
}
else if (!strcmp(child->name, "Role")) {
e = gf_mpd_parse_descriptor(set->role, child);
if (e) return e;
}
else if (!strcmp(child->name, "Rating")) {
e = gf_mpd_parse_descriptor(set->rating, child);
if (e) return e;
}
else if (!strcmp(child->name, "Viewpoint")) {
e = gf_mpd_parse_descriptor(set->viewpoint, child);
if (e) return e;
}
else if (!strcmp(child->name, "BaseURL")) {
e = gf_mpd_parse_base_url(set->base_URLs, child);
if (e) return e;
}
else if (!strcmp(child->name, "ContentComponent")) {
e = gf_mpd_parse_content_component(set->content_component, child);
if (e) return e;
} else if(!strcmp(child->name, "InbandEventStream")) {
e = gf_mpd_parse_inband_event(set->inband_event, child);
if (e) return e;
} else if (!strcmp(child->name, "SegmentBase")) {
set->segment_base = gf_mpd_parse_segment_base(mpd, child);
}
else if (!strcmp(child->name, "SegmentList")) {
set->segment_list = gf_mpd_parse_segment_list(mpd, child);
}
else if (!strcmp(child->name, "SegmentTemplate")) {
set->segment_template = gf_mpd_parse_segment_template(mpd, child);
}
else if (!strcmp(child->name, "Representation")) {
e = gf_mpd_parse_representation(mpd, set->representations, child);
if (e) return e;
} else {
gf_mpd_parse_common_representation_child(mpd, (GF_MPD_CommonAttributes*)set, root, child, &i, child_idx);
}
child_idx++;
}
return GF_OK;
}
GF_MPD_Period *gf_mpd_period_new() {
GF_MPD_Period *period;
GF_SAFEALLOC(period, GF_MPD_Period);
if (!period) return NULL;
period->adaptation_sets = gf_list_new();
period->event_streams = gf_list_new();
period->base_URLs = gf_list_new();
period->subsets = gf_list_new();
period->preselections = gf_list_new();
return period;
}
GF_Err gf_mpd_parse_period(GF_MPD *mpd, GF_XMLNode *root)
{
u32 i, child_idx;
GF_MPD_Period *period;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_Err e;
Bool is_preperiod = !strcmp(root->name, "PrePeriod") ? GF_TRUE : GF_FALSE;
if (is_preperiod) {
period = gf_list_get(mpd->periods, 0);
if (period) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] At most one PrePeriod allowed, and it shall be the first period\n"));
return GF_NON_COMPLIANT_BITSTREAM;
}
}
period = gf_mpd_period_new();
if (!period) return GF_OUT_OF_MEM;
period->is_preroll = is_preperiod;
e = gf_list_add(mpd->periods, period);
if (e) return e;
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (strstr(att->name, "href")) period->xlink_href = gf_mpd_parse_string(att->value);
else if (strstr(att->name, "actuate")) period->xlink_actuate_on_load = !strcmp(att->value, "onLoad") ? 1 : 0;
else if (!strcmp(att->name, "id")) period->ID = gf_mpd_parse_string(att->value);
else if (!strcmp(att->name, "start")) {
if (is_preperiod) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] PrePeriod with @start not allowed, ignoring attribute\n"));
} else {
period->start = gf_mpd_parse_duration(att->value);
}
}
else if (!strcmp(att->name, "duration")) period->duration = gf_mpd_parse_duration(att->value);
else if (!strcmp(att->name, "bitstreamSwitching")) period->bitstream_switching = gf_mpd_parse_bool(att->value);
else {
MPD_STORE_EXTENSION_ATTR(period)
}
}
child_idx = 0;
i = 0;
while ( (child = gf_list_enum(root->content, &i))) {
if (!gf_mpd_valid_child(mpd, child))
continue;
if (!strcmp(child->name, "BaseURL")) {
e = gf_mpd_parse_base_url(period->base_URLs, child);
if (e) return e;
} else if(!strcmp(child->name, "EventStream")) {
e = gf_mpd_parse_event_streams(period->event_streams, child);
if (e) return e;
}
else if (!strcmp(child->name, "SegmentBase")) {
period->segment_base = gf_mpd_parse_segment_base(mpd, child);
}
else if (!strcmp(child->name, "SegmentList")) {
period->segment_list = gf_mpd_parse_segment_list(mpd, child);
}
else if (!strcmp(child->name, "SegmentTemplate")) {
period->segment_template = gf_mpd_parse_segment_template(mpd, child);
}
else if (!strcmp(child->name, "AdaptationSet")) {
e = gf_mpd_parse_adaptation_set(mpd, period->adaptation_sets, child);
if (e) return e;
}
else if (!strcmp(child->name, "SubSet")) {
}
else {
MPD_STORE_EXTENSION_NODE(period)
}
child_idx++;
}
return GF_OK;
}
GF_EXPORT
GF_MPD *gf_mpd_new()
{
GF_MPD *mpd;
GF_SAFEALLOC(mpd, GF_MPD);
return mpd;
}
void gf_mpd_del_list(GF_List *list, void (*__destructor)(void *), Bool reset_only)
{
if (!list) return;
while (gf_list_count(list)) {
void *item = gf_list_last(list);
gf_list_rem_last(list);
if (item && __destructor) __destructor(item);
}
if (!reset_only) gf_list_del(list);
}
void gf_mpd_base_url_free(void *_item)
{
GF_MPD_BaseURL *base_url = (GF_MPD_BaseURL *)_item;
if (base_url->service_location) gf_free(base_url->service_location);
if (base_url->URL) gf_free(base_url->URL);
if (base_url->redirection) gf_free(base_url->redirection);
gf_free(base_url);
}
void gf_mpd_url_free(void *_item)
{
GF_MPD_URL *ptr = (GF_MPD_URL*)_item;
if (ptr->sourceURL) gf_free(ptr->sourceURL);
if (ptr->byte_range) gf_free(ptr->byte_range);
gf_free(ptr);
}
void gf_mpd_string_free(void *_item)
{
if (_item) gf_free(_item);
}
void gf_mpd_prog_info_free(void *_item)
{
GF_MPD_ProgramInfo *ptr = (GF_MPD_ProgramInfo *)_item;
if (ptr->lang) gf_free(ptr->lang);
if (ptr->title) gf_free(ptr->title);
if (ptr->source) gf_free(ptr->source);
if (ptr->copyright) gf_free(ptr->copyright);
if (ptr->more_info_url) gf_free(ptr->more_info_url);
MPD_FREE_EXTENSION_NODE(ptr);
gf_free(ptr);
}
void gf_mpd_segment_url_free(void *_ptr)
{
GF_MPD_SegmentURL *ptr = (GF_MPD_SegmentURL *)_ptr;
if (ptr->index) gf_free(ptr->index);
if (ptr->index_range) gf_free(ptr->index_range);
if (ptr->media) gf_free(ptr->media);
if (ptr->media_range) gf_free(ptr->media_range);
if (ptr->key_url) gf_free(ptr->key_url);
if (ptr->hls_switch_uri) gf_free(ptr->hls_switch_uri);
gf_free(ptr);
}
void gf_mpd_segment_base_free(void *_item)
{
GF_MPD_SegmentBase *ptr = (GF_MPD_SegmentBase *)_item;
if (ptr->initialization_segment) gf_mpd_url_free(ptr->initialization_segment);
if (ptr->representation_index) gf_mpd_url_free(ptr->representation_index);
if (ptr->index_range) gf_free(ptr->index_range);
gf_free(ptr);
}
void gf_mpd_segment_entry_free(void *_item)
{
gf_free(_item);
}
void gf_mpd_segment_timeline_free(void *_item)
{
GF_MPD_SegmentTimeline *ptr = (GF_MPD_SegmentTimeline *)_item;
gf_mpd_del_list(ptr->entries, gf_mpd_segment_entry_free, 0);
gf_free(ptr);
}
#if 0 //unused
void gf_mpd_segment_url_list_free(GF_List *list)
{
gf_mpd_del_list(list, gf_mpd_segment_url_free, 0);
}
#endif
void gf_mpd_segment_list_free(void *_item)
{
GF_MPD_SegmentList *ptr = (GF_MPD_SegmentList *)_item;
if (ptr->xlink_href) gf_free(ptr->xlink_href);
if (ptr->initialization_segment) gf_mpd_url_free(ptr->initialization_segment);
if (ptr->bitstream_switching_url) gf_mpd_url_free(ptr->bitstream_switching_url);
if (ptr->representation_index) gf_mpd_url_free(ptr->representation_index);
if (ptr->segment_timeline) gf_mpd_segment_timeline_free(ptr->segment_timeline);
gf_mpd_del_list(ptr->segment_URLs, gf_mpd_segment_url_free, 0);
if (ptr->dasher_segment_name) gf_free(ptr->dasher_segment_name);
if (ptr->previous_xlink_href) gf_free(ptr->previous_xlink_href);
gf_free(ptr);
}
void gf_mpd_segment_template_free(void *_item)
{
GF_MPD_SegmentTemplate *ptr = (GF_MPD_SegmentTemplate *)_item;
if (ptr->initialization_segment) gf_mpd_url_free(ptr->initialization_segment);
if (ptr->bitstream_switching_url) gf_mpd_url_free(ptr->bitstream_switching_url);
if (ptr->representation_index) gf_mpd_url_free(ptr->representation_index);
if (ptr->segment_timeline) gf_mpd_segment_timeline_free(ptr->segment_timeline);
if (ptr->index) gf_free(ptr->index);
if (ptr->media) gf_free(ptr->media);
if (ptr->initialization) gf_free(ptr->initialization);
if (ptr->bitstream_switching) gf_free(ptr->bitstream_switching);
gf_free(ptr);
}
GF_MPD_Descriptor *gf_mpd_descriptor_new(const char *id, const char *schemeIdUri, const char *value) {
GF_MPD_Descriptor *mpd_desc;
GF_SAFEALLOC(mpd_desc, GF_MPD_Descriptor);
if (!mpd_desc) return NULL;
if (id) mpd_desc->id = gf_strdup(id);
if (schemeIdUri) mpd_desc->scheme_id_uri = gf_strdup(schemeIdUri);
if (value) mpd_desc->value = gf_strdup(value);
return mpd_desc;
}
void gf_mpd_descriptor_free(void *item)
{
GF_MPD_Descriptor *mpd_desc = (GF_MPD_Descriptor*) item;
if (mpd_desc->id) gf_free(mpd_desc->id);
if (mpd_desc->scheme_id_uri) gf_free(mpd_desc->scheme_id_uri);
if (mpd_desc->value) gf_free(mpd_desc->value);
MPD_FREE_EXTENSION_NODE(mpd_desc);
gf_free(mpd_desc);
}
void gf_mpd_content_component_free(void *item)
{
GF_MPD_ContentComponent *component_descriptor=(GF_MPD_ContentComponent*) item;
if (component_descriptor->type) gf_free(component_descriptor->type);
if (component_descriptor->lang) gf_free(component_descriptor->lang);
gf_free(item);
}
void gf_mpd_inband_event_free(void *item) {
GF_MPD_Inband_Event *inband_event_desc = (GF_MPD_Inband_Event *) item;
if (inband_event_desc->scheme_id_uri) gf_free(inband_event_desc->scheme_id_uri);
if (inband_event_desc->value) gf_free(inband_event_desc->value);
gf_free(item);
}
void gf_mpd_producer_reftime_free(void *item)
{
GF_MPD_ProducerReferenceTime *pref=(GF_MPD_ProducerReferenceTime*) item;
if (pref->scheme) gf_free(pref->scheme);
if (pref->wallclock) gf_free(pref->wallclock);
if (pref->utc_timing) gf_mpd_descriptor_free(pref->utc_timing);
gf_free(item);
}
void gf_mpd_common_attributes_free(GF_MPD_CommonAttributes *ptr)
{
if (ptr->profiles) gf_free(ptr->profiles);
if (ptr->sar) gf_free(ptr->sar);
if (ptr->framerate) gf_free(ptr->framerate);
if (ptr->mime_type) gf_free(ptr->mime_type);
if (ptr->segmentProfiles) gf_free(ptr->segmentProfiles);
if (ptr->codecs) gf_free(ptr->codecs);
gf_mpd_del_list(ptr->frame_packing, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->audio_channels, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->content_protection, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->essential_properties, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->supplemental_properties, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->producer_reference_time, gf_mpd_producer_reftime_free, 0);
gf_mpd_del_list(ptr->group_labels, gf_mpd_grouplabel_free, 0);
gf_mpd_del_list(ptr->labels, gf_mpd_label_free, 0);
}
void gf_mpd_representation_free(void *_item)
{
GF_MPD_Representation *ptr = (GF_MPD_Representation *)_item;
gf_mpd_common_attributes_free((GF_MPD_CommonAttributes *)ptr);
if (ptr->id) gf_free(ptr->id);
if (ptr->dependency_id) gf_free(ptr->dependency_id);
if (ptr->media_stream_structure_id) gf_free(ptr->media_stream_structure_id);
if (ptr->playback.cached_init_segment_url) {
//free blob created to hold base64 embedded data
if (ptr->playback.owned_gmem && !strnicmp(ptr->playback.cached_init_segment_url, "gmem://", 7)) {
GF_Blob *mem_blob;
if (sscanf(ptr->playback.cached_init_segment_url, "gmem://%p", &mem_blob) == 1) {
gf_blob_unregister(mem_blob);
if (mem_blob->data) gf_free(mem_blob->data);
gf_free(mem_blob);
}
}
gf_free(ptr->playback.cached_init_segment_url);
}
if (ptr->playback.init_segment.data) {
gf_blob_unregister(&ptr->playback.init_segment);
gf_free(ptr->playback.init_segment.data);
}
if (ptr->playback.key_url) gf_free(ptr->playback.key_url);
gf_mpd_del_list(ptr->base_URLs, gf_mpd_base_url_free, 0);
gf_mpd_del_list(ptr->sub_representations, NULL/*TODO*/, 0);
if (ptr->segment_base) gf_mpd_segment_base_free(ptr->segment_base);
if (ptr->segment_list) gf_mpd_segment_list_free(ptr->segment_list);
if (ptr->segment_template) gf_mpd_segment_template_free(ptr->segment_template);
MPD_FREE_EXTENSION_NODE(ptr);
if (ptr->m3u8_x_attributes) {
while (gf_list_count(ptr->m3u8_x_attributes)) {
GF_XMLAttribute *att = gf_list_pop_back(ptr->m3u8_x_attributes);
if (att->name) gf_free(att->name);
if (att->value) gf_free(att->value);
gf_free(att);
}
gf_list_del(ptr->m3u8_x_attributes);
}
if (ptr->dasher_ctx) {
gf_free(ptr->dasher_ctx->init_seg);
if (ptr->dasher_ctx->period_id)
gf_free(ptr->dasher_ctx->period_id);
gf_free(ptr->dasher_ctx->src_url);
gf_free(ptr->dasher_ctx->template_seg);
if (ptr->dasher_ctx->template_idx) gf_free(ptr->dasher_ctx->template_idx);
if (ptr->dasher_ctx->mux_pids) gf_free(ptr->dasher_ctx->mux_pids);
gf_free(ptr->dasher_ctx);
}
if (ptr->state_seg_list) {
while (gf_list_count(ptr->state_seg_list)) {
GF_DASH_SegmentContext *s = gf_list_pop_back(ptr->state_seg_list);
if (s->filename) gf_free(s->filename);
if (s->filepath) gf_free(s->filepath);
if (s->llhas_template) gf_free(s->llhas_template);
if (s->frags) gf_free(s->frags);
if (s->hls_key_uri) gf_free(s->hls_key_uri);
gf_free(s);
}
gf_list_del(ptr->state_seg_list);
}
if (ptr->m3u8_name) gf_free(ptr->m3u8_name);
if (ptr->hls_single_file_name) gf_free(ptr->hls_single_file_name);
if (ptr->m3u8_var_name) gf_free(ptr->m3u8_var_name);
if (ptr->m3u8_var_file) gf_fclose(ptr->m3u8_var_file);
if (ptr->res_url) gf_free(ptr->res_url);
gf_free(ptr);
}
void gf_mpd_adaptation_set_free(void *_item)
{
GF_MPD_AdaptationSet *ptr = (GF_MPD_AdaptationSet *)_item;
gf_mpd_common_attributes_free((GF_MPD_CommonAttributes *)ptr);
if (ptr->lang) gf_free(ptr->lang);
if (ptr->content_type) gf_free(ptr->content_type);
if (ptr->par) gf_free(ptr->par);
if (ptr->xlink_href) gf_free(ptr->xlink_href);
gf_mpd_del_list(ptr->accessibility, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->role, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->rating, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->viewpoint, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->content_component, gf_mpd_content_component_free, 0);
if (ptr->inband_event) gf_mpd_del_list(ptr->inband_event, gf_mpd_inband_event_free, 0);
if (ptr->segment_base) gf_mpd_segment_base_free(ptr->segment_base);
if (ptr->segment_list) gf_mpd_segment_list_free(ptr->segment_list);
if (ptr->segment_template) gf_mpd_segment_template_free(ptr->segment_template);
gf_mpd_del_list(ptr->base_URLs, gf_mpd_base_url_free, 0);
gf_mpd_del_list(ptr->representations, gf_mpd_representation_free, 0);
MPD_FREE_EXTENSION_NODE(ptr);
gf_free(ptr);
}
void gf_mpd_preselection_free(void *_item)
{
GF_MPD_Preselection *ptr = (GF_MPD_Preselection *)_item;
gf_mpd_common_attributes_free((GF_MPD_CommonAttributes *)ptr);
if (ptr->lang) gf_free(ptr->lang);
if (ptr->tag) gf_free(ptr->tag);
if (ptr->preselection_components) {
for (u32 i = 0; i < gf_list_count(ptr->preselection_components); i++) {
u32 *d = (u32 *)gf_list_get(ptr->preselection_components, i);
gf_free(d);
}
gf_list_del(ptr->preselection_components);
}
gf_mpd_del_list(ptr->accessibility, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->role, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->rating, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(ptr->viewpoint, gf_mpd_descriptor_free, 0);
MPD_FREE_EXTENSION_NODE(ptr);
gf_free(ptr);
}
void gf_mpd_grouplabel_free(void *_item)
{
GF_MPD_GroupLabel *ptr = (GF_MPD_GroupLabel *)_item;
if (ptr->lang) gf_free(ptr->lang);
if (ptr->content) gf_free(ptr->content);
gf_free(ptr);
}
void gf_mpd_label_free(void *_item)
{
GF_MPD_Label *ptr = (GF_MPD_Label *)_item;
if (ptr->lang) gf_free(ptr->lang);
if (ptr->content) gf_free(ptr->content);
gf_free(ptr);
}
static void gf_mpd_event_stream_entry_free(void *_item)
{
GF_MPD_EventStreamEntry *ptr = (GF_MPD_EventStreamEntry *)_item;
if (ptr->xmlns) gf_free(ptr->xmlns);
if (ptr->message) gf_free(ptr->message);
gf_free(ptr);
}
static void gf_mpd_event_stream_free(void *_item)
{
GF_MPD_EventStream *ptr = (GF_MPD_EventStream *)_item;
if (ptr->scheme_id_uri) gf_free(ptr->scheme_id_uri);
gf_mpd_del_list(ptr->entries, gf_mpd_event_stream_entry_free, 0);
gf_free(ptr);
}
void gf_mpd_period_free(void *_item)
{
GF_MPD_Period *ptr = (GF_MPD_Period *)_item;
if (ptr->ID) gf_free(ptr->ID);
if (ptr->origin_base_url) gf_free(ptr->origin_base_url);
if (ptr->broken_xlink) gf_free(ptr->broken_xlink);
if (ptr->xlink_href) gf_free(ptr->xlink_href);
if (ptr->segment_base) gf_mpd_segment_base_free(ptr->segment_base);
if (ptr->segment_list) gf_mpd_segment_list_free(ptr->segment_list);
if (ptr->segment_template) gf_mpd_segment_template_free(ptr->segment_template);
gf_mpd_del_list(ptr->base_URLs, gf_mpd_base_url_free, 0);
gf_mpd_del_list(ptr->adaptation_sets, gf_mpd_adaptation_set_free, 0);
gf_mpd_del_list(ptr->event_streams, gf_mpd_event_stream_free, 0);
MPD_FREE_EXTENSION_NODE(ptr);
gf_mpd_del_list(ptr->subsets, NULL/*TODO*/, 0);
if (ptr->preselections) {
while (gf_list_count(ptr->preselections)) {
GF_MPD_Preselection *p = gf_list_pop_back(ptr->preselections);
gf_mpd_preselection_free(p);
}
gf_list_del(ptr->preselections);
}
gf_free(ptr);
}
GF_EXPORT
void gf_mpd_del(GF_MPD *mpd)
{
if (!mpd) return;
#ifdef GPAC_ENABLE_COVERAGE
if (gf_sys_is_cov_mode()) {
gf_mpd_string_free(NULL);
}
#endif
gf_mpd_del_list(mpd->program_infos, gf_mpd_prog_info_free, 0);
gf_mpd_del_list(mpd->base_URLs, gf_mpd_base_url_free, 0);
gf_mpd_del_list(mpd->locations, gf_mpd_string_free, 0);
gf_mpd_del_list(mpd->metrics, NULL/*TODO*/, 0);
gf_mpd_del_list(mpd->periods, gf_mpd_period_free, 0);
if (mpd->profiles) gf_free(mpd->profiles);
if (mpd->ID) gf_free(mpd->ID);
if (mpd->segment_template) gf_free(mpd->segment_template);
gf_mpd_del_list(mpd->utc_timings, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(mpd->essential_properties, gf_mpd_descriptor_free, 0);
gf_mpd_del_list(mpd->supplemental_properties, gf_mpd_descriptor_free, 0);
MPD_FREE_EXTENSION_NODE(mpd);
gf_free(mpd);
}
GF_EXPORT
GF_Err gf_mpd_complete_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url)
{
GF_Err e;
u32 i, child_idx;
Bool ns_ok = GF_FALSE;
GF_XMLAttribute *att;
GF_XMLNode *child;
if (!root || !mpd) return GF_BAD_PARAM;
mpd->index_mode = 0;
i=0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "xmlns")) {
if (!root->ns && (!strcmp(att->value, "urn:mpeg:dash:schema:mpd:2011") || !strcmp(att->value, "urn:mpeg:DASH:schema:MPD:2011")) ) {
ns_ok = 1;
break;
}
if (!root->ns && !strcmp(att->value, "urn:gpac:dash:index:2023")) {
ns_ok = 1;
mpd->index_mode = 1;
break;
}
}
else if (!strncmp(att->name, "xmlns:", 6)) {
if (root->ns && !strcmp(att->name+6, root->ns) && (!strcmp(att->value, "urn:mpeg:dash:schema:mpd:2011") || !strcmp(att->value, "urn:mpeg:DASH:schema:MPD:2011")) ) {
ns_ok = 1;
if (!mpd->xml_namespace) mpd->xml_namespace = root->ns;
break;
}
}
}
if (!ns_ok) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Wrong namespace found for DASH MPD - cannot parse\n"));
}
if (!strcmp(root->name, "PrePeriod") || !strcmp(root->name, "Period")) {
return gf_mpd_parse_period(mpd, root);
}
i = 0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "id")) {
if (mpd->ID) gf_free(mpd->ID);
mpd->ID = gf_mpd_parse_string(att->value);
} else if (!strcmp(att->name, "profiles")) {
if (mpd->profiles) gf_free(mpd->profiles);
mpd->profiles = gf_mpd_parse_string(att->value);
} else if (!strcmp(att->name, "type")) {
if (!strcmp(att->value, "static")) mpd->type = GF_MPD_TYPE_STATIC;
else if (!strcmp(att->value, "dynamic")) mpd->type = GF_MPD_TYPE_DYNAMIC;
} else if (!strcmp(att->name, "availabilityStartTime")) {
mpd->availabilityStartTime = gf_mpd_parse_date(att->value);
} else if (!strcmp(att->name, "availabilityEndTime")) {
mpd->availabilityEndTime = gf_mpd_parse_date(att->value);
} else if (!strcmp(att->name, "publishTime")) {
mpd->publishTime = gf_mpd_parse_date(att->value);
} else if (!strcmp(att->name, "mediaPresentationDuration")) {
mpd->media_presentation_duration = gf_mpd_parse_duration(att->value);
} else if (!strcmp(att->name, "minimumUpdatePeriod")) {
mpd->minimum_update_period = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "minBufferTime")) {
mpd->min_buffer_time = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "timeShiftBufferDepth")) {
mpd->time_shift_buffer_depth = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "suggestedPresentationDelay")) {
mpd->suggested_presentation_delay = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "maxSegmentDuration")) {
mpd->max_segment_duration = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "maxSubsegmentDuration")) {
mpd->max_subsegment_duration = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "gpac:init_gen_time")) {
mpd->gpac_init_ntp_ms = gf_mpd_parse_long_int(att->value);
} else if (!strcmp(att->name, "gpac:next_gen_time")) {
mpd->gpac_next_ntp_ms = gf_mpd_parse_long_int(att->value);
} else if (!strcmp(att->name, "gpac:mpd_time")) {
mpd->gpac_mpd_time = gf_mpd_parse_long_int(att->value);
} else if (!strcmp(att->name, "segmentDuration")) {
mpd->segment_duration = gf_mpd_parse_duration_u32(att->value);
} else if (!strcmp(att->name, "template")) {
mpd->segment_template = gf_mpd_parse_string(att->value);
} else {
MPD_STORE_EXTENSION_ATTR(mpd)
}
}
if (mpd->type == GF_MPD_TYPE_STATIC)
mpd->minimum_update_period = mpd->time_shift_buffer_depth = 0;
child_idx = 0;
i = 0;
while ( ( child = gf_list_enum(root->content, &i )) ) {
if (! gf_mpd_valid_child(mpd, child))
continue;
if (!strcmp(child->name, "ProgramInformation")) {
e = gf_mpd_parse_program_info(mpd, child);
if (e) return e;
} else if (!strcmp(child->name, "Location")) {
char *str = gf_mpd_parse_text_content(child);
if (str) gf_list_add(mpd->locations, str);
} else if (!strcmp(child->name, "PrePeriod") || !strcmp(child->name, "Period")) {
e = gf_mpd_parse_period(mpd, child);
if (e) return e;
} else if (!strcmp(child->name, "Metrics")) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Metrics not implemented yet\n"));
} else if (!strcmp(child->name, "BaseURL")) {
e = gf_mpd_parse_base_url(mpd->base_URLs, child);
if (e) return e;
} else if (!strcmp(child->name, "UTCTiming")) {
gf_mpd_parse_descriptor(mpd->utc_timings, child);
} else if (!strcmp(child->name, "EssentialProperty")) {
gf_mpd_parse_descriptor(mpd->essential_properties, child);
}
else if (!strcmp(child->name, "SupplementalProperty")) {
gf_mpd_parse_descriptor(mpd->supplemental_properties, child);
} else {
MPD_STORE_EXTENSION_NODE(mpd)
}
child_idx++;
}
return GF_OK;
}
static void gf_mpd_init_struct(GF_MPD *mpd)
{
gf_assert(!mpd->periods);
mpd->periods = gf_list_new();
mpd->program_infos = gf_list_new();
mpd->base_URLs = gf_list_new();
mpd->locations = gf_list_new();
mpd->metrics = gf_list_new();
mpd->utc_timings = gf_list_new();
mpd->essential_properties = gf_list_new();
mpd->supplemental_properties = gf_list_new();
}
GF_EXPORT
GF_Err gf_mpd_init_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url)
{
if (!root || !mpd) return GF_BAD_PARAM;
gf_mpd_init_struct(mpd);
/*setup some defaults*/
mpd->type = GF_MPD_TYPE_STATIC;
mpd->time_shift_buffer_depth = (u32) -1; /*infinite by default*/
mpd->xml_namespace = NULL;
return gf_mpd_complete_from_dom(root, mpd, default_base_url);
}
//locate codec in renditions and try to extract bandwidth (we can't really)
static char *group_to_codecs(MasterPlaylist *pl, PlaylistElement *pe, u32 *bandwidth)
{
PlaylistElement *par_pe=NULL;
u32 stidx, k, nb_streams = gf_list_count(pl->streams);;
for (stidx=0; stidx<nb_streams; stidx++) {
Stream *astream = gf_list_get(pl->streams, stidx);
for (k=0; k<gf_list_count(astream->variants); k++) {
par_pe = gf_list_get(astream->variants, k);
if (par_pe->main_codecs && par_pe->audio_group && strstr(par_pe->audio_group, pe->audio_group))
break;
par_pe = NULL;
}
if (par_pe) break;
}
if (!par_pe) return NULL;
u32 grp_idx=0, target_idx=0;
char *group = par_pe->audio_group;
while (group) {
char *sep = strchr(group, ',');
if (sep) sep[0] = 0;
grp_idx++;
if (!strcmp(group, pe->audio_group)) target_idx = grp_idx;
if (sep) sep[0] = ',';
if (!sep || target_idx) break;
group = sep+1;
}
if (!target_idx) return NULL;
if (par_pe->alt_bandwidths && bandwidth && (target_idx<=par_pe->nb_alt_bandwidths)) {
u32 min_bw = par_pe->alt_bandwidths[0];
for (k=1; k<par_pe->nb_alt_bandwidths; k++) {
if (min_bw > par_pe->alt_bandwidths[k])
min_bw = par_pe->alt_bandwidths[k];
}
//assume default audio bandwidth is 64k
*bandwidth = 64000 + par_pe->alt_bandwidths[target_idx-1]-min_bw;
}
grp_idx = 0;
group = par_pe->codecs;
while (group) {
char *sep = strchr(group, ',');
if (sep) sep[0] = 0;
if (!strstr(par_pe->main_codecs, group)) {
grp_idx++;
if (grp_idx == target_idx) {
char *res = gf_strdup(group);
if (sep) sep[0] = ',';
return res;
}
}
if (sep) sep[0] = ',';
if (!sep) break;
group = sep+1;
}
return NULL;
}
static GF_Err gf_m3u8_fill_mpd_struct(MasterPlaylist *pl, const char *m3u8_file, const char *src_base_url, const char *mpd_file, char *title, Double update_interval, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, Bool use_segment_timeline, Bool is_end, u32 max_dur, GF_MPD *mpd, Bool parse_sub_playlist)
{
char *sep, *template_base=NULL, *template_ext;
u32 nb_streams, i, j, k, template_width, template_idx_start;
Stream *stream;
PlaylistElement *pe, *elt;
GF_MPD_ProgramInfo *info;
GF_MPD_Period *period;
GF_Err e;
if (!parse_sub_playlist) use_mpd_templates = GF_FALSE;
Bool all_template_used = use_mpd_templates;
char str[1024];
if (!mpd) return GF_BAD_PARAM;
gf_mpd_init_struct(mpd);
mpd->time_shift_buffer_depth = (u32) -1; /*infinite by default*/
mpd->type = is_end ? GF_MPD_TYPE_STATIC : GF_MPD_TYPE_DYNAMIC;
sep = strrchr(m3u8_file, '/');
if (!sep)
sep = strrchr(m3u8_file, '\\');
if (sep)
sep = sep + 1;
else
sep = (char *)m3u8_file;
mpd->ID = gf_strdup(sep);
if (update_interval) {
mpd->minimum_update_period = (u32) (update_interval*1000);
}
if (is_end) {
mpd->media_presentation_duration = (u64) (max_dur*1000);
}
if (mpd->type == GF_MPD_TYPE_STATIC)
mpd->minimum_update_period = mpd->time_shift_buffer_depth = 0;
mpd->min_buffer_time = 1500;
GF_SAFEALLOC(info, GF_MPD_ProgramInfo);
if (!info) return GF_OUT_OF_MEM;
info->more_info_url = gf_strdup("https://gpac.io");
info->title = gf_strdup(title);
sprintf(str, "Generated from URL %s", gf_file_basename(src_base_url));
info->source = gf_strdup(str);
if (!gf_sys_is_test_mode())
sprintf(str, "Generated by GPAC %s", gf_gpac_version());
else
sprintf(str, "Generated by GPAC");
info->copyright = gf_strdup(str);
gf_list_add(mpd->program_infos, info);
GF_SAFEALLOC(period, GF_MPD_Period);
if (!period) return GF_OUT_OF_MEM;
period->adaptation_sets = gf_list_new();
period->event_streams = gf_list_new();
period->base_URLs = gf_list_new();
period->subsets = gf_list_new();
e = gf_list_add(mpd->periods, period);
if (e) return e;
if (is_end) {
period->duration = max_dur*1000;
}
/*check if we use templates*/
template_base = NULL;
template_ext = NULL;
template_width = 0;
template_idx_start = 0;
elt = NULL;
pe = NULL;
nb_streams = gf_list_count(pl->streams);
for (i=0; i<nb_streams; i++) {
u32 count_variants;
u32 width, height, samplerate, num_channels;
GF_MPD_AdaptationSet *set;
Bool use_template = use_mpd_templates;
GF_SAFEALLOC(set, GF_MPD_AdaptationSet);
if (!set) return GF_OUT_OF_MEM;
set->id = -1;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)set);
set->accessibility = gf_list_new();
set->role = gf_list_new();
set->rating = gf_list_new();
set->viewpoint = gf_list_new();
set->content_component = gf_list_new();
set->inband_event = gf_list_new();
set->base_URLs = gf_list_new();
set->representations = gf_list_new();
/*assign default ID and group*/
set->group = -1;
set->segment_alignment = GF_TRUE;
e = gf_list_add(period->adaptation_sets, set);
if (e) return e;
/*check if we use templates*/
stream = gf_list_get(pl->streams, i);
count_variants = gf_list_count(stream->variants);
if (use_template) {
for (j=0; j<count_variants; j++) {
u32 count_elements;
pe = gf_list_get(stream->variants, j);
if (pe->element_type != TYPE_PLAYLIST)
continue;
count_elements = gf_list_count(pe->element.playlist.elements);
if (!count_elements)
continue;
if (!template_base && use_template) {
char *sub_url;
elt = gf_list_get(pe->element.playlist.elements, 0);
sub_url = strrchr(elt->url, '/');
if (!sub_url) {
sub_url = elt->url;
} else {
sub_url ++;
}
template_base = gf_strdup(sub_url);
template_ext = strrchr(template_base, '.');
k=0;
while (1) {
if (strchr("0123456789", template_base[k])) {
if (template_ext) {
template_ext[0] = 0;
template_width = (u32) strlen(template_base + k);
template_idx_start = atoi(template_base + k);
template_ext[0] = '.';
}
template_base[k] = 0;
break;
}
k++;
if (!template_base[k]) {
use_template = GF_FALSE;
break;
}
}
}
if (!template_ext) template_ext="";
if (use_template) {
for (k=0; k<count_elements; k++) {
char szURL[GF_MAX_PATH], *sub_url;
elt = gf_list_get(pe->element.playlist.elements, k);
if (template_width == 2)
sprintf(szURL, "%s%02d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 3)
sprintf(szURL, "%s%03d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 4)
sprintf(szURL, "%s%04d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 5)
sprintf(szURL, "%s%05d%s", template_base, template_idx_start + k, template_ext);
else if (template_width == 6)
sprintf(szURL, "%s%06d%s", template_base, template_idx_start + k, template_ext);
else
sprintf(szURL, "%s%d%s", template_base, template_idx_start + k, template_ext);
sub_url = strrchr(elt->url, '/');
if (!sub_url) sub_url = elt->url;
else sub_url ++;
if (strcmp(szURL, sub_url)) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[MPD] Cannot remap M3U8 to segment template MPD, using segment list\n"));
use_template = GF_FALSE;
break;
}
}
}
}
}
/*if we use templates, put the SegmentTemplate element at the adaptationSet level*/
if (use_template) {
GF_SAFEALLOC(set->segment_template, GF_MPD_SegmentTemplate);
if (!set->segment_template) return GF_OUT_OF_MEM;
if (pe)
set->segment_template->duration = (u32)pe->duration_info;
if (template_width > 1) {
sprintf(str, "%s$%%0%ddNumber$%s", template_base, template_width, template_ext);
} else {
sprintf(str, "%s$Number$%s", template_base, template_ext);
}
set->segment_template->media = gf_strdup(str);
set->segment_template->start_number = template_idx_start;
} else {
all_template_used = GF_FALSE;
}
for (j=0; j<count_variants; j++) {
char *variant_base_url=NULL;
u32 count_elements;
char szName[20];
#ifndef GPAC_DISABLE_MEDIA_IMPORT
Bool import_file = do_import;
#endif
char *byte_range_media_file = NULL;
GF_MPD_Representation *rep;
pe = gf_list_get(stream->variants, j);
if (pe->element_type == TYPE_MEDIA) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] NOT SUPPORTED: M3U8 Media\n"));
} else if (pe->load_error) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Error loading playlist element %s\n", pe->url));
} else if (pe->element_type != TYPE_PLAYLIST) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] NOT SUPPORTED: M3U8 unknown type for %s\n", pe->url));
}
count_elements = gf_list_count(pe->element.playlist.elements);
if (parse_sub_playlist && !count_elements)
continue;
if (pe->codecs && (pe->codecs[0] == '\"')) {
u32 len = (u32) strlen(pe->codecs);
if (len>1) {
memmove(pe->codecs, pe->codecs+1, len-1);
pe->codecs[len-2] = 0;
} else {
pe->codecs[0] = 0;
}
}
#ifndef GPAC_DISABLE_MEDIA_IMPORT
if (pe->bandwidth && pe->codecs && pe->width && pe->height) {
import_file = GF_FALSE;
}
#endif
if (pe->media_type==MEDIA_TYPE_SUBTITLES) {
#ifndef GPAC_DISABLE_MEDIA_IMPORT
import_file = GF_FALSE;
#endif
if (!pe->codecs) pe->codecs = gf_strdup("wvtt");
}
if (pe->media_type==MEDIA_TYPE_CLOSED_CAPTIONS) {
#ifndef GPAC_DISABLE_MEDIA_IMPORT
import_file = GF_FALSE;
#endif
if (!pe->codecs) pe->codecs = gf_strdup("wvtt");
}
k = 0;
#ifndef GPAC_DISABLE_MEDIA_IMPORT
try_next_segment:
#endif
elt = gf_list_get(pe->element.playlist.elements, k);
if (parse_sub_playlist && !elt)
break;
//get base URL of child manifest, only needed when injecting base URL
variant_base_url = gf_strdup(pe->url);
sep = strrchr(variant_base_url, '/');
if (!sep) sep = strrchr(variant_base_url, '\\');
/*keep final '/' */
if (sep) {
sep[1] = 0;
/* base_url is same as manifest dir, no add of MPD BaseURL */
if (!strcmp(variant_base_url, "./") || !strcmp(variant_base_url, ".\\")) {
gf_free(variant_base_url);
variant_base_url = NULL;
}
}
/* base_url is just a filename so is same as manifest dir, no add of MPD BaseURL */
else {
gf_free(variant_base_url);
variant_base_url = NULL;
}
width = pe->width;
height = pe->height;
samplerate = 0;
num_channels = pe->channels;
retry_import:
#ifndef GPAC_DISABLE_MEDIA_IMPORT
if (elt && import_file) {
char *elt_url = elt->init_segment_url ? elt->init_segment_url : elt->url;
char *tmp_file = NULL;
GF_MediaImporter *import = gf_malloc(sizeof(GF_MediaImporter));
if (!import) return GF_OUT_OF_MEM;
#ifndef GPAC_DISABLE_NETWORK
u64 br_start = elt->init_segment_url ? elt->init_byte_range_start : elt->byte_range_start;
u64 br_end = elt->init_segment_url ? elt->init_byte_range_end : elt->byte_range_end;
#endif
char *par_url = gf_url_concatenate(src_base_url, pe->url);
elt_url = gf_url_concatenate(par_url, elt_url);
gf_free(par_url);
memset(import, 0, sizeof(GF_MediaImporter));
import->trackID = 0;
import->flags = GF_IMPORT_PROBE_ONLY;
if (strstr(elt_url, "://") && !strstr(elt_url, "file://")) {
tmp_file = strrchr(elt_url, '/');
if (!tmp_file)
tmp_file = strrchr(elt_url, '\\');
if (tmp_file) {
tmp_file++;
#ifndef GPAC_DISABLE_NETWORK
e = gf_dm_wget(elt_url, tmp_file, br_start, br_end, NULL);
if (e == GF_OK) {
import->in_name = tmp_file;
}
#endif
}
} else {
import->in_name = elt_url;
}
if (!strstr(elt_url, "://") && !gf_file_exists(elt_url)) {
import_file = GF_FALSE;
if (elt_url) gf_free(elt_url);
goto retry_import;
}
e = gf_media_import(import);
if (e != GF_OK) {
k++;
if (elt_url) gf_free(elt_url);
gf_free(import);
goto try_next_segment;
}
if (import->in_name && !pe->bandwidth && !elt->init_segment_url && pe->duration_info) {
u64 pos = 0;
Double bw;
FILE *t = gf_fopen(import->in_name, "rb");
if (t) {
pos = gf_fsize(t);
gf_fclose(t);
}
bw = (Double) pos;
bw *= 8;
bw /= pe->duration_info;
pe->bandwidth = (u32) bw;
} else if (!pe->bandwidth) {
//unknown bandwidth, default to 128k ...
pe->bandwidth = 128000;
}
if (tmp_file)
gf_file_delete(tmp_file);
if (!pe->codecs) {
char *codecs = NULL;
for (k=0; k<MIN(import->nb_tracks, GF_IMPORT_MAX_TRACKS); k++) {
if (strlen(import->tk_info[k].szCodecProfile)) {
gf_dynstrcat(&codecs, import->tk_info[k].szCodecProfile, ",");
}
}
pe->codecs = codecs;
}
for (k=0; k<MIN(import->nb_tracks, GF_IMPORT_MAX_TRACKS); k++) {
switch (import->tk_info[k].stream_type) {
case GF_STREAM_VISUAL:
width = import->tk_info[k].video_info.width;
height = import->tk_info[k].video_info.height;
break;
case GF_STREAM_AUDIO:
samplerate = import->tk_info[k].audio_info.sample_rate;
num_channels = import->tk_info[k].audio_info.nb_channels;
break;
}
}
if (elt_url) gf_free(elt_url);
gf_free(import);
}
#endif
GF_SAFEALLOC(rep, GF_MPD_Representation);
if (!rep) return GF_OUT_OF_MEM;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)rep);
rep->base_URLs = gf_list_new();
rep->sub_representations = gf_list_new();
/*get rid of level 0 aac*/
if (elt && strstr(elt->url, ".aac"))
rep->playback.disabled = GF_TRUE;
//if hls advertises more than our importer, use hls info (some files signal always stereo in audio sample entry)
if (num_channels && pe->channels && (num_channels<pe->channels))
num_channels = pe->channels;
e = gf_list_add(set->representations, rep);
if (e) return e;
sprintf(szName, "R%d_%d", i+1, j+1);
rep->id = gf_strdup(szName);
rep->bandwidth = pe->bandwidth;
/* TODO : if mime-type is still unknown, don't try to add codec information since it would be wrong */
if (!strcmp(M3U8_UNKNOWN_MIME_TYPE, mimeTypeForM3U8Segments)) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Unknown mime-type when converting from M3U8 HLS playlist, setting %s\n", mimeTypeForM3U8Segments));
}
char *fext = (elt && elt->init_segment_url) ? gf_file_ext_start(elt->init_segment_url) : NULL;
if ((fext && (!stricmp(fext, ".mp4") || !stricmp(fext, ".m4s")) )
//default to MP4 for v6 or above
|| (pl->version>=6)
) {
rep->mime_type = gf_strdup(samplerate ? "audio/mp4" : "video/mp4");
} else {
rep->mime_type = gf_strdup(mimeTypeForM3U8Segments);
}
if (pe->main_codecs ) {
rep->codecs = gf_strdup(pe->main_codecs);
} else if (pe->codecs) {
rep->codecs = gf_strdup(pe->codecs);
}
else if (pe->audio_group) {
rep->codecs = group_to_codecs(pl, pe, &rep->bandwidth);
}
if (pe->language && !set->lang) {
set->lang = pe->language;
pe->language = NULL;
}
if (width && height) {
rep->width = width;
rep->height = height;
}
if (elt) {
if (elt->drm_method==DRM_AES_128)
rep->crypto_type = 1;
else if (elt->drm_method==DRM_CENC_CBCS)
rep->crypto_type = 2;
else if (elt->drm_method==DRM_CENC_CTR)
rep->crypto_type = 3;
}
if (samplerate) {
rep->samplerate = samplerate;
}
if (num_channels) {
GF_MPD_Descriptor *desc;
GF_SAFEALLOC(desc, GF_MPD_Descriptor);
if (desc) {
char szChan[10];
desc->scheme_id_uri = gf_strdup("urn:mpeg:dash:23003:3:audio_channel_configuration:2011");
sprintf(szChan, "%d", num_channels);
desc->value = gf_strdup(szChan);
if (!rep->audio_channels) rep->audio_channels = gf_list_new();
gf_list_add(rep->audio_channels, desc);
}
//audio bandwidth not announced ...
if (!pe->bandwidth && !rep->bandwidth)
rep->bandwidth = 64000 * num_channels/2;
}
if (pe->media_type==MEDIA_TYPE_SUBTITLES) {
//subs bandwidth not announced ...
if (!pe->bandwidth) rep->bandwidth = 10000;
}
//if parsing subplaylist with a MPD, we translate HLS to MPD directly
//compute relative url from source HLS to target MPD and inject it in base URL (SegmentTemplate or SegmentList)
//otherwise, we use the element url as is, and wait for xlink resolution, which will be done
//against the HLS base URL
char *rel_url = NULL;
if (parse_sub_playlist && mpd_file) {
u32 base_len;
//check we do not generate the MPD at the same location
char *f1 = gf_file_basename(mpd_file);
char *f2 = gf_file_basename(src_base_url);
base_len = (u32) (f1 - mpd_file);
if ((base_len == (u32) (f2 - src_base_url)) && !strncmp(src_base_url, mpd_file, base_len)) {
rel_url = NULL;
} else {
rel_url = gf_url_concatenate_parent(mpd_file, src_base_url);
}
}
if (use_template) {
//inject baseURL pointing to source playlist
if (rel_url || variant_base_url) {
GF_MPD_BaseURL *url;
GF_SAFEALLOC(url, GF_MPD_BaseURL);
if (! url) return GF_OUT_OF_MEM;
e = gf_list_add(rep->base_URLs, url);
if (e) return GF_OUT_OF_MEM;
if (rel_url) {
if (variant_base_url)
url->URL = gf_url_concatenate(rel_url, variant_base_url);
else
url->URL = gf_strdup(rel_url);
} else {
url->URL = gf_strdup(variant_base_url);
}
}
if (elt->init_segment_url) {
GF_SAFEALLOC(rep->segment_template, GF_MPD_SegmentTemplate);
if (!rep->segment_template) return GF_OUT_OF_MEM;
rep->segment_template->start_number = (u32) -1;
//no need to conctenate with rel_url since it is now in our MPD base url
rep->segment_template->initialization = gf_strdup(elt->init_segment_url);
}
if (rel_url) gf_free(rel_url);
if (variant_base_url) gf_free(variant_base_url);
if (use_segment_timeline) {
GF_MPD_SegmentTimelineEntry *prev_e = NULL;
u64 start_time = 0;
rep->segment_template->timescale = 1000;
GF_SAFEALLOC(rep->segment_template->segment_timeline, GF_MPD_SegmentTimeline);
if (!rep->segment_template->segment_timeline) return GF_OUT_OF_MEM;
rep->segment_template->segment_timeline->entries = gf_list_new();
for (k=0; k<count_elements; k++) {
u64 dur;
GF_MPD_SegmentTimelineEntry *se;
elt = gf_list_get(pe->element.playlist.elements, k);
dur = (u64) ( elt->duration_info * rep->segment_template->timescale);
if (prev_e && (prev_e->duration == dur)) {
prev_e->repeat_count++;
} else {
GF_SAFEALLOC(se, GF_MPD_SegmentTimelineEntry);
if (!se) return GF_OUT_OF_MEM;
se->duration = (u32) dur;
se->start_time = start_time;
gf_list_add(rep->segment_template->segment_timeline->entries, se);
prev_e = se;
}
start_time += dur;
}
}
continue;
}
byte_range_media_file = NULL;
elt = gf_list_get(pe->element.playlist.elements, 0);
if (parse_sub_playlist && elt && (elt->byte_range_end || elt->byte_range_start)) {
byte_range_media_file = elt->url;
}
//only if we parse subplaylist now (otherwise baseURL added when resolving xlink)
if (parse_sub_playlist && elt && (
//if byte-range media file, add base URL
byte_range_media_file
//otherwise if we have different location for HLS vs MPD url or if child playlist is not in same path as master HLS
|| (rel_url || variant_base_url || strpbrk(elt->url, "/\\"))
)) {
GF_MPD_BaseURL *url;
GF_SAFEALLOC(url, GF_MPD_BaseURL);
if (! url) {
if (rel_url) gf_free(rel_url);
if (variant_base_url) gf_free(variant_base_url);
return GF_OUT_OF_MEM;
}
e = gf_list_add(rep->base_URLs, url);
if (e) {
if (rel_url) gf_free(rel_url);
if (variant_base_url) gf_free(variant_base_url);
return GF_OUT_OF_MEM;
}
if (rel_url) {
if (variant_base_url) {
char *rel_base = gf_url_concatenate(rel_url, variant_base_url);
url->URL = gf_url_concatenate(rel_base, elt->url);
gf_free(rel_base);
} else {
url->URL = gf_strdup(rel_url);
}
} else if (variant_base_url) {
url->URL = gf_strdup(variant_base_url);
} else {
url->URL = gf_strdup(elt->url);
}
}
if (rel_url) gf_free(rel_url);
//no longer needed
if (variant_base_url) gf_free(variant_base_url);
GF_SAFEALLOC(rep->segment_list, GF_MPD_SegmentList);
if (!rep->segment_list) return GF_OUT_OF_MEM;
//Not parsing sub-playlists, save URL to these sub-playlist in xlink:href
//resolution will be done against the HLS base URL
//child playlist will be loaded if active and not modified since last load
if (!parse_sub_playlist) {
rep->segment_list->xlink_href = pe->url;
pe->url=NULL;
if (template_base) {
gf_free(template_base);
template_base = NULL;
}
continue;
}
rep->segment_list->segment_URLs = gf_list_new();
rep->segment_list->duration = (u64) (pe->duration_info * 1000);
rep->segment_list->timescale = 1000;
if (elt && elt->init_segment_url) {
GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
if (!rep->segment_list->initialization_segment) return GF_OUT_OF_MEM;
rep->segment_list->initialization_segment->sourceURL = gf_strdup(elt->init_segment_url);
if (elt->init_byte_range_end) {
GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
if (!rep->segment_list->initialization_segment->byte_range) return GF_OUT_OF_MEM;
rep->segment_list->initialization_segment->byte_range->start_range = elt->init_byte_range_start;
rep->segment_list->initialization_segment->byte_range->end_range = elt->init_byte_range_end;
}
}
Double avg_dur = 0;
Double cur_start=0;
Bool do_seg_timeline = use_segment_timeline;
for (k=0; k<count_elements; k++) {
Double diff, seg_start;
GF_MPD_SegmentURL *segment_url;
elt = gf_list_get(pe->element.playlist.elements, k);
if (!avg_dur) avg_dur = elt->duration_info;
else if (elt->duration_info) {
diff = elt->duration_info - avg_dur;
if (diff<0) diff = -diff;
if (diff > avg_dur/2) {
do_seg_timeline = GF_TRUE;
}
}
seg_start = ((Double)k*rep->segment_list->duration) / rep->segment_list->timescale;
if (k) {
diff = cur_start - seg_start;
if (diff<0) diff = -diff;
if (diff > avg_dur/2) {
do_seg_timeline = GF_TRUE;
}
}
cur_start += elt->duration_info;
GF_SAFEALLOC(segment_url, GF_MPD_SegmentURL);
if (!segment_url) return GF_OUT_OF_MEM;
gf_list_add(rep->segment_list->segment_URLs, segment_url);
if (byte_range_media_file) {
GF_SAFEALLOC(segment_url->media_range, GF_MPD_ByteRange);
if (!segment_url->media_range) return GF_OUT_OF_MEM;
segment_url->media_range->start_range = elt->byte_range_start;
segment_url->media_range->end_range = elt->byte_range_end;
if (strcmp(elt->url, byte_range_media_file)) {
segment_url->media = elt->url;
elt->url=NULL;
}
} else {
segment_url->media = elt->url;
elt->url=NULL;
}
//only signal duration if different from default one
segment_url->duration = (u64) (rep->segment_list->timescale * elt->duration_info);
if (segment_url->duration == rep->segment_list->duration)
segment_url->duration = 0;
if (elt->drm_method != DRM_NONE) {
//segment_url->key_url = "aes-128";
if (elt->key_uri) {
segment_url->key_url = elt->key_uri;
elt->key_uri=NULL;
memcpy(segment_url->key_iv, elt->key_iv, sizeof(bin128));
}
}
}
if (do_seg_timeline) {
u64 start_time = 0;
GF_SAFEALLOC(rep->segment_list->segment_timeline, GF_MPD_SegmentTimeline);
if (!rep->segment_list->segment_timeline) return GF_OUT_OF_MEM;
rep->segment_list->segment_timeline->entries = gf_list_new();
for (k=0; k<count_elements; k++) {
u64 dur;
GF_MPD_SegmentTimelineEntry *se;
elt = gf_list_get(pe->element.playlist.elements, k);
GF_SAFEALLOC(se, GF_MPD_SegmentTimelineEntry);
if (!se) return GF_OUT_OF_MEM;
dur = (u64) ( elt->duration_info * rep->segment_list->timescale);
se->duration = (u32) dur;
se->start_time = start_time;
start_time += dur;
gf_list_add(rep->segment_list->segment_timeline->entries, se);
}
}
}
if (template_base) {
gf_free(template_base);
template_base = NULL;
}
}
if (all_template_used) {
mpd->profiles = gf_strdup("urn:mpeg:dash:profile:isoff-live:2011");
} else {
mpd->profiles = gf_strdup("urn:mpeg:dash:profile:isoff-main:2011");
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_m3u8_to_mpd(const char *m3u8_file, const char *base_url,
const char *mpd_file,
u32 reload_count, char *mimeTypeForM3U8Segments, Bool do_import, Bool use_mpd_templates, Bool use_segment_timeline, GF_FileDownload *getter,
GF_MPD *mpd, Bool parse_sub_playlist, Bool keep_files)
{
GF_Err e;
char *title;
u32 i, j, k;
Double update_interval;
MasterPlaylist *pl = NULL;
Stream *stream;
PlaylistElement *pe, *the_pe;
Bool is_end;
u32 max_dur = 0;
// first, we always need to parse the master playlist
e = gf_m3u8_parse_master_playlist(m3u8_file, &pl, base_url);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Failed to parse root playlist '%s', error = %s\n", m3u8_file, gf_error_to_string(e)));
gf_m3u8_master_playlist_del(&pl);
return e;
}
if (mpd_file == NULL) {
if (!keep_files) gf_file_delete(m3u8_file);
mpd_file = m3u8_file;
}
mpd->xml_namespace = NULL;
the_pe = NULL;
pe = NULL;
i = 0;
gf_assert(pl);
gf_assert(pl->streams);
while ((stream = gf_list_enum(pl->streams, &i))) {
j = 0;
while (NULL != (pe = gf_list_enum(stream->variants, &j))) {
Bool found = GF_FALSE;
char *suburl;
if (!pe->url)
continue;
/* filter out duplicated entries (seen on M6 m3u8) */
for (k=0; k<j-1; ++k) {
PlaylistElement *a_pe = gf_list_get(stream->variants, k);
if (a_pe->url && pe->url && !strcmp(a_pe->url, pe->url)) {
found = GF_TRUE;
break;
}
}
if (found)
continue;
the_pe = pe;
suburl = NULL;
if (!parse_sub_playlist)
continue;
if (strcmp(base_url, pe->url))
suburl = gf_url_concatenate(base_url, pe->url);
if (!suburl || !strcmp(base_url, suburl)) {
if (suburl)
gf_free(suburl);
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Not downloading, programs are identical for %s...\n", pe->url));
continue;
}
if (getter && getter->new_session && getter->del_session && getter->get_cache_name) {
e = getter->new_session(getter, suburl);
if (e) {
gf_free(suburl);
pe->load_error = e;
continue;
}
if (e == GF_OK) {
pe->load_error = gf_m3u8_parse_sub_playlist(getter->get_cache_name(getter), &pl, suburl, stream, pe, GF_FALSE);
}
//getter->del_session(getter);
} else { /* for use in MP4Box */
if (strstr(suburl, "://")) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Downloading %s...\n", suburl));
#ifndef GPAC_DISABLE_NETWORK
e = gf_dm_wget(suburl, "tmp.m3u8", 0, 0, NULL);
if (e == GF_OK) {
e = gf_m3u8_parse_sub_playlist("tmp.m3u8", &pl, suburl, stream, pe, GF_FALSE);
} else
#endif
{
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Download failed for %s\n", suburl));
e = GF_OK;
}
gf_file_delete("tmp.m3u8");
} else {
e = gf_m3u8_parse_sub_playlist(suburl, &pl, suburl, stream, pe, GF_FALSE);
}
if (e) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] Failed to parse subplaylist %s\n", suburl));
}
}
gf_free(suburl);
}
if (max_dur < (u32) stream->computed_duration) {
max_dur = (u32) stream->computed_duration;
}
}
is_end = !pl->playlist_needs_refresh;
if (!the_pe) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] The M3U8 playlist is not correct.\n"));
gf_m3u8_master_playlist_del(&pl);
return GF_BAD_PARAM;
}
/*update interval is set to the duration of the last media file with rules defined in http live streaming RFC section 6.3.4*/
switch (reload_count) {
case 0:
update_interval = the_pe->duration_info;
break;
case 1:
update_interval = (Double)the_pe->duration_info / 2;
break;
case 2:
update_interval = 3 * ((Double)the_pe->duration_info / 2);
break;
default:
update_interval = 3 * the_pe->duration_info;
break;
}
if (is_end || ((the_pe->element_type == TYPE_PLAYLIST) && the_pe->element.playlist.is_ended)) {
update_interval = 0;
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] No need to refresh playlist!\n"));
mpd->type = GF_MPD_TYPE_STATIC;
} else {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Playlist will be refreshed every %g seconds, len=%d\n", update_interval, the_pe->duration_info));
mpd->type = GF_MPD_TYPE_DYNAMIC;
}
title = the_pe->title;
if (!title || strlen(title) < 2)
title = the_pe->url;
gf_assert(mpd_file);
gf_assert(mpd);
e = gf_m3u8_fill_mpd_struct(pl, m3u8_file, base_url, mpd_file, title, update_interval, mimeTypeForM3U8Segments, do_import, use_mpd_templates, use_segment_timeline, is_end, max_dur, mpd, parse_sub_playlist);
gf_m3u8_master_playlist_del(&pl);
return e;
}
GF_EXPORT
GF_Err gf_m3u8_solve_representation_xlink(GF_MPD_Representation *rep, const char *base_url, GF_FileDownload *getter, Bool *is_static, u64 *duration, u8 last_sig[GF_SHA1_DIGEST_SIZE])
{
GF_Err e;
MasterPlaylist *pl = NULL;
Stream *stream;
PlaylistElement *pe;
u32 k, count_elements;
u32 seq_num;
Bool has_full_seg_following = GF_FALSE;
Bool can_merge_parts = GF_FALSE;
Bool first_ll_part = GF_TRUE;
u8 signature[GF_SHA1_DIGEST_SIZE];
const char *loc_file = rep->segment_list->xlink_href;
char *full_url;
if (!rep->in_progress) {
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[M3U8] Solving m3u8 variant playlist %s\n", rep->segment_list->xlink_href));
}
if (!getter || !getter->new_session || !getter->del_session || !getter->get_cache_name) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] FileDownloader not found\n"));
return GF_BAD_PARAM;
}
//get absolute resource path
full_url = gf_url_concatenate(base_url, rep->segment_list->xlink_href);
if (!strncmp(loc_file, "gmem://", 7)) {
u8 *m3u8_payload;
u32 m3u8_size;
e = gf_blob_get(loc_file, &m3u8_payload, &m3u8_size, NULL);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH,("[M3U8] Cannot load m3u8 source blob %s\n", loc_file));
gf_free(full_url);
return e;
}
gf_blob_release(loc_file);
gf_sha1_csum(m3u8_payload, m3u8_size, signature);
} else if (gf_url_is_local(full_url)) {
if (!gf_file_exists(full_url)) {
gf_free(full_url);
return GF_URL_REMOVED;
}
loc_file = full_url;
gf_sha1_file(loc_file, signature);
} else {
if (!rep->in_progress) {
e = getter->new_session(getter, full_url);
if (e && (e!=GF_IP_NETWORK_EMPTY)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Download failed for %s: %s\n", rep->segment_list->xlink_href, gf_error_to_string(e) ));
gf_free(full_url);
return e;
}
}
e = getter->get_status(getter);
if (e == GF_NOT_READY) {
rep->in_progress = GF_TRUE;
gf_free(full_url);
return GF_NOT_READY;
}
rep->in_progress = GF_FALSE;
if (e<0)
return e;
loc_file = getter->get_cache_name(getter);
if (!loc_file) {
gf_free(full_url);
return GF_URL_ERROR;
}
gf_sha1_file(loc_file, signature);
}
if (! memcmp(signature, last_sig, GF_SHA1_DIGEST_SIZE)) {
gf_free(full_url);
return GF_EOS;
}
e = gf_m3u8_parse_sub_playlist(loc_file, &pl, rep->segment_list->xlink_href, NULL, NULL, GF_FALSE);
//no longer needed
gf_free(full_url);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Failed to parse playlist %s\n", rep->segment_list->xlink_href));
gf_m3u8_master_playlist_del(&pl);
return e;
}
gf_assert(pl);
gf_assert(pl->streams);
if (!gf_list_count(pl->streams)) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[M3U8] Playlist %s still empty\n", rep->segment_list->xlink_href));
gf_m3u8_master_playlist_del(&pl);
return GF_IP_NETWORK_EMPTY;
}
gf_assert(gf_list_count(pl->streams) == 1);
memcpy(last_sig, signature, GF_SHA1_DIGEST_SIZE);
if (is_static) {
*is_static = pl->playlist_needs_refresh ? GF_FALSE : GF_TRUE;
}
stream = (Stream *)gf_list_get(pl->streams, 0);
if (gf_list_count(stream->variants) != 1) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] Invalid playlist %s resolves to multiple variants %d, disabling it\n", rep->segment_list->xlink_href, gf_list_count(stream->variants)));
gf_m3u8_master_playlist_del(&pl);
rep->playback.disabled = GF_TRUE;
return GF_NON_COMPLIANT_BITSTREAM;
}
pe = (PlaylistElement *)gf_list_get(stream->variants, 0);
if (duration) {
*duration = (u32) (stream->computed_duration * 1000);
}
//create a base URL if not found, otherwise modify existing one, pointing to our unresolved base (xlink)
//if xlink is relative, it wil still be resolved against manifest base url when solving segment templates
//this allows using parent path (ie "../") in the master playlist
GF_MPD_BaseURL *burl = gf_list_get(rep->base_URLs, 0);
if (!burl) {
GF_SAFEALLOC(burl, GF_MPD_BaseURL);
gf_list_add(rep->base_URLs, burl);
} else {
gf_free(burl->URL);
}
burl->URL = gf_strdup(rep->segment_list->xlink_href);
if (pe->init_segment_url) {
if (!rep->segment_list->initialization_segment) {
GF_SAFEALLOC(rep->segment_list->initialization_segment, GF_MPD_URL);
if (!rep->segment_list->initialization_segment) return GF_OUT_OF_MEM;
if (strstr(pe->init_segment_url, "mp4") || strstr(pe->init_segment_url, "MP4")) {
if (rep->mime_type) gf_free(rep->mime_type);
rep->mime_type = gf_strdup("video/mp4");
}
rep->segment_list->initialization_segment->sourceURL = pe->init_segment_url;
pe->init_segment_url=NULL;
if (pe->init_byte_range_end) {
GF_SAFEALLOC(rep->segment_list->initialization_segment->byte_range, GF_MPD_ByteRange);
if (!rep->segment_list->initialization_segment->byte_range) return GF_OUT_OF_MEM;
rep->segment_list->initialization_segment->byte_range->start_range = pe->init_byte_range_start;
rep->segment_list->initialization_segment->byte_range->end_range = pe->init_byte_range_end;
}
}
}
rep->starts_with_sap = pl->independent_segments ? 1: 3;
if (pl->low_latency)
rep->m3u8_low_latency = GF_TRUE;
rep->segment_list->duration = (u64) (pe->duration_info * 1000);
rep->segment_list->timescale = 1000;
rep->m3u8_media_seq_min = pe->element.playlist.media_seq_min;
rep->m3u8_media_seq_max = pe->element.playlist.media_seq_max;
if (!rep->segment_list->segment_URLs)
rep->segment_list->segment_URLs = gf_list_new();
count_elements = gf_list_count(pe->element.playlist.elements);
seq_num = pe->element.playlist.media_seq_min;
seq_num += pe->element.playlist.discontinuity;
u64 seg_utc = 0;
for (k=0; k<count_elements; k++) {
GF_MPD_SegmentURL *segment_url;
PlaylistElement *elt = gf_list_get(pe->element.playlist.elements, k);
if (!elt)
continue;
//NOTE: for GPAC now, we disable stream AAC to avoid the problem when switching quality. It should be improved later !
if (strstr(elt->url, ".aac")) {
rep->playback.disabled = GF_TRUE;
gf_m3u8_master_playlist_del(&pl);
return GF_OK;
}
if (elt->drm_method==DRM_AES_128)
rep->crypto_type = 1;
else if (elt->drm_method==DRM_CENC_CBCS)
rep->crypto_type = 2;
else if (elt->drm_method==DRM_CENC_CTR)
rep->crypto_type = 3;
if (elt->low_lat_chunk && !has_full_seg_following) {
u32 j;
u64 last_end = 0;
if (elt->byte_range_end && first_ll_part) {
last_end = elt->byte_range_end;
can_merge_parts = GF_TRUE;
}
for (j=k+1; j<count_elements; j++) {
PlaylistElement *next_elt = gf_list_get(pe->element.playlist.elements, j);
if (next_elt->low_lat_chunk) {
Bool match = GF_TRUE;
if (!first_ll_part) continue;
if (strcmp(elt->url, next_elt->url))
match = GF_FALSE;
else if (!elt->byte_range_end && next_elt->byte_range_end)
match = GF_FALSE;
else if (elt->byte_range_end && !next_elt->byte_range_end)
match = GF_FALSE;
else if (last_end + 1 != next_elt->byte_range_start)
match = GF_FALSE;
else
last_end = next_elt->byte_range_end;
if (!match)
can_merge_parts = GF_FALSE;
continue;
}
has_full_seg_following = GF_TRUE;
if (strcmp(elt->url, next_elt->url))
can_merge_parts = GF_FALSE;
break;
}
}
GF_SAFEALLOC(segment_url, GF_MPD_SegmentURL);
if (!segment_url) {
return GF_OUT_OF_MEM;
}
gf_list_add(rep->segment_list->segment_URLs, segment_url);
segment_url->media = elt->url;
elt->url = NULL;
segment_url->duration = (u64) (rep->segment_list->timescale * elt->duration_info);
if (elt->utc_start_time) {
seg_utc = elt->utc_start_time;
}
segment_url->hls_utc_time = seg_utc;
if (seg_utc)
seg_utc += (u32) (elt->duration_info*1000);
//we keep the same seq num for each part
segment_url->hls_seq_num = seq_num;
segment_url->discontinuity_seq = elt->discontinuity;
if (elt->init_segment_url
&& rep && rep->segment_list && rep->segment_list->initialization_segment
&& rep->segment_list->initialization_segment->sourceURL
&& strcmp(rep->segment_list->initialization_segment->sourceURL, elt->init_segment_url)
) {
segment_url->hls_switch_uri = elt->init_segment_url;
elt->init_segment_url = NULL;
}
if (elt->low_lat_chunk) {
segment_url->hls_ll_chunk_type = (elt->independent_chunk || first_ll_part) ? 2 : 1;
segment_url->can_merge = can_merge_parts ? 1 : 0;
segment_url->is_first_part = first_ll_part;
rep->m3u8_low_latency = GF_TRUE;
first_ll_part = GF_FALSE;
if (segment_url->hls_ll_chunk_type==2)
rep->m3u8_media_seq_indep_last = gf_list_count(rep->segment_list->segment_URLs) - 1;
} else {
first_ll_part = GF_TRUE;
has_full_seg_following = GF_FALSE;
can_merge_parts = GF_FALSE;
rep->m3u8_media_seq_indep_last = gf_list_count(rep->segment_list->segment_URLs) - 1;
seq_num++;
}
if (elt->drm_method != DRM_NONE) {
if (elt->key_uri) {
segment_url->key_url = elt->key_uri;
elt->key_uri=NULL;
memcpy(segment_url->key_iv, elt->key_iv, sizeof(bin128));
}
}
if (elt->byte_range_end) {
GF_SAFEALLOC(segment_url->media_range, GF_MPD_ByteRange);
if (!segment_url->media_range) return GF_OUT_OF_MEM;
segment_url->media_range->start_range = elt->byte_range_start;
segment_url->media_range->end_range = elt->byte_range_end;
}
}
if (!gf_list_count(rep->segment_list->segment_URLs)) {
gf_list_del(rep->segment_list->segment_URLs);
rep->segment_list->segment_URLs = NULL;
}
if (rep->segment_list->previous_xlink_href) gf_free(rep->segment_list->previous_xlink_href);
rep->segment_list->previous_xlink_href = rep->segment_list->xlink_href;
rep->segment_list->xlink_href = NULL;
gf_m3u8_master_playlist_del(&pl);
return GF_OK;
}
GF_EXPORT
GF_MPD_SegmentList *gf_mpd_solve_segment_list_xlink(GF_MPD *mpd, GF_XMLNode *root)
{
return gf_mpd_parse_segment_list(mpd, root);
}
GF_EXPORT
void gf_mpd_delete_segment_list(GF_MPD_SegmentList *segment_list)
{
gf_mpd_segment_list_free(segment_list);
}
static GFINLINE void gf_mpd_lf(FILE *out, s32 indent)
{
if (indent>=0) gf_fprintf(out, "\n");
}
static GFINLINE void gf_mpd_nl(FILE *out, s32 indent)
{
if (indent>=0) {
u32 i=(u32)indent;
while (i) {
gf_fprintf(out, " ");
i--;
}
}
}
/*time is given in ms*/
void gf_mpd_print_date(FILE *out, char *name, u64 time)
{
time_t gtime;
struct tm *t;
u32 sec;
u32 ms;
gtime = time / 1000;
sec = (u32)(time / 1000);
ms = (u32)(time - ((u64)sec) * 1000);
if (name) {
gf_xml_dump_string(out, " ", name, "=\"");
}
t = gf_gmtime(&gtime);
sec = t->tm_sec;
//see issue #859, no clue how this happened...
if (sec > 60)
sec = 60;
gf_fprintf(out, "%d-%02d-%02dT%02d:%02d:%02d.%03dZ", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, sec, ms);
if (name) {
gf_fprintf(out, "\"");
}
}
void gf_mpd_print_duration(FILE *out, char *name, u64 duration_in_ms, Bool UseHoursAndMinutes)
{
u32 h, m, s, ms;
h = (u32) (duration_in_ms / 3600000);
m = (u32) (duration_in_ms/ 60000) - h*60;
s = (u32) (duration_in_ms/1000) - h*3600 - m*60;
ms = (u32) (duration_in_ms) - h*3600*1000 - m*60*1000 - s*1000;
gf_fprintf(out, " %s=\"PT", name);
if(UseHoursAndMinutes)
gf_fprintf(out, "%dH%dM", h, m);
gf_fprintf(out, "%d", s);
gf_fprintf(out, ".");
gf_fprintf(out, "%03dS\"", ms);
}
static void gf_mpd_print_base_url(FILE *out, GF_MPD_BaseURL *base_URL, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<BaseURL");
if (base_URL->service_location)
gf_xml_dump_string(out, " serviceLocation=\"", base_URL->service_location, "\"");
if (base_URL->byte_range)
gf_fprintf(out, " byteRange=\""LLD"-"LLD"\"", base_URL->byte_range->start_range, base_URL->byte_range->end_range);
gf_xml_dump_string(out, ">", base_URL->URL, "</BaseURL>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_base_urls(FILE *out, GF_List *base_URLs, s32 indent)
{
GF_MPD_BaseURL *url;
u32 i;
i=0;
while ((url = (GF_MPD_BaseURL *)gf_list_enum(base_URLs, &i))) {
gf_mpd_print_base_url(out, url, indent);
}
}
static void gf_mpd_print_url(FILE *out, GF_MPD_URL *url, char *name, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<%s", name);
if (url->byte_range) gf_fprintf(out, " range=\""LLD"-"LLD"\"", url->byte_range->start_range, url->byte_range->end_range);
if (url->sourceURL) gf_fprintf(out, " sourceURL=\"%s\"", url->sourceURL);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_segment_base_attr(FILE *out, GF_MPD_SegmentBase *s)
{
if (s->timescale) gf_fprintf(out, " timescale=\"%d\"", s->timescale);
if (s->presentation_time_offset) gf_fprintf(out, " presentationTimeOffset=\""LLU"\"", s->presentation_time_offset);
if (s->index_range && s->index_range->end_range) {
if (s->index_range_exact) gf_fprintf(out, " indexRangeExact=\"true\"");
if (s->index_range) gf_fprintf(out, " indexRange=\""LLD"-"LLD"\"", s->index_range->start_range, s->index_range->end_range);
}
if (s->availability_time_offset) {
gf_fprintf(out, " availabilityTimeOffset=\"%g\"", s->availability_time_offset);
gf_fprintf(out, " availabilityTimeComplete=\"%s\"", "false");
}
if ((s32) s->time_shift_buffer_depth > 0)
gf_mpd_print_duration(out, "timeShiftBufferDepth", s->time_shift_buffer_depth, GF_TRUE);
}
static void gf_mpd_print_segment_base(FILE *out, GF_MPD_SegmentBase *s, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<SegmentBase");
gf_mpd_print_segment_base_attr(out, s);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
if (s->initialization_segment) gf_mpd_print_url(out, s->initialization_segment, "Initialization", indent+1);
if (s->representation_index) gf_mpd_print_url(out, s->representation_index, "RepresentationIndex", indent+1);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</SegmentBase>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_segment_timeline(FILE *out, GF_MPD_SegmentTimeline *tl, s32 indent, u32 tsb_first_entry)
{
u32 i, count, rcount;
u64 start_time=0;
GF_MPD_SegmentTimelineEntry *se, *prev;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<SegmentTimeline>");
gf_mpd_lf(out, indent);
prev = gf_list_get(tl->entries, tsb_first_entry);
count = gf_list_count(tl->entries);
if (!prev) goto done_tl;
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<S");
gf_fprintf(out, " t=\""LLD"\"", prev->start_time);
if (prev->duration) gf_fprintf(out, " d=\"%d\"", prev->duration);
rcount = prev->repeat_count;
start_time = prev->start_time + (prev->repeat_count+1) * prev->duration;
for (i = tsb_first_entry+1; i<count && prev; i++) {
se = gf_list_get(tl->entries, i);
//close entry if not contiguous
//if ll edge entry, stop so that we can announce subparts in already published entries
if ((se->start_time != start_time) || (prev->duration!=se->duration) || se->is_ll_edge) {
if (rcount) gf_fprintf(out, " r=\"%d\"", rcount);
if (prev->nb_parts) gf_fprintf(out, " k=\"%d\"", prev->nb_parts);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
//start new one
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<S");
if (se->start_time != start_time) {
gf_fprintf(out, " t=\""LLD"\"", se->start_time);
start_time = se->start_time;
}
if (se->duration) gf_fprintf(out, " d=\"%d\"", se->duration);
rcount=0;
} else {
rcount++;
}
start_time += (se->repeat_count+1) * se->duration;
rcount += se->repeat_count;
prev = se;
}
//close last entry
if (rcount) gf_fprintf(out, " r=\"%d\"", rcount);
if (prev->nb_parts) gf_fprintf(out, " k=\"%d\"", prev->nb_parts);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
done_tl:
gf_mpd_nl(out, indent);
gf_fprintf(out, "</SegmentTimeline>");
gf_mpd_lf(out, indent);
}
GF_MPD_SegmentTimeline *gf_mpd_segmentimeline_new(void)
{
GF_MPD_SegmentTimeline *seg_tl;
GF_SAFEALLOC(seg_tl, GF_MPD_SegmentTimeline);
if (seg_tl && !seg_tl->entries) seg_tl->entries=gf_list_new();
return seg_tl;
}
static u32 gf_mpd_print_multiple_segment_base(FILE *out, GF_MPD_MultipleSegmentBase *ms, s32 indent, Bool close_if_no_child)
{
gf_mpd_print_segment_base_attr(out, (GF_MPD_SegmentBase *)ms);
if ((int)ms->start_number >= 0) gf_fprintf(out, " startNumber=\"%d\"", ms->start_number);
//"The attribute @duration and the element SegmentTimeline shall not be present at the same time" ...
if (ms->duration && !ms->segment_timeline) gf_fprintf(out, " duration=\""LLD"\"", ms->duration);
if (!ms->bitstream_switching_url && !ms->segment_timeline && !ms->initialization_segment && !ms->representation_index) {
if (close_if_no_child) gf_fprintf(out, "/");
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
return 1;
}
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
if (ms->initialization_segment) gf_mpd_print_url(out, ms->initialization_segment, "Initialization", indent+1);
if (ms->representation_index) gf_mpd_print_url(out, ms->representation_index, "RepresentationIndex", indent+1);
if (ms->segment_timeline) gf_mpd_print_segment_timeline(out, ms->segment_timeline, indent+1, ms->tsb_first_entry);
if (ms->bitstream_switching_url) gf_mpd_print_url(out, ms->bitstream_switching_url, "BitstreamSwitching", indent+1);
return 0;
}
static void gf_mpd_print_segment_list(FILE *out, GF_MPD_SegmentList *s, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<SegmentList");
if (s->xlink_href) {
gf_xml_dump_string(out, " xlink:href=\"", s->xlink_href, "\"");
if (s->xlink_actuate_on_load)
gf_fprintf(out, " actuate=\"onLoad\"");
}
if (s->index_mode) {
if (s->timescale) gf_fprintf(out, " timescale=\"%d\"", s->timescale);
if (s->presentation_time_offset) gf_fprintf(out, " presentationTimeOffset=\""LLU"\"", s->presentation_time_offset);
if (s->sample_duration) gf_fprintf(out, " sampleDuration=\"%u\"", s->sample_duration);
if (s->src_timescale) gf_fprintf(out, " srcTimescale=\"%u\"", s->src_timescale);
if (s->pid_delay) gf_fprintf(out, " srcDelay=\"%u\"", s->pid_delay);
if (s->first_cts_offset) gf_fprintf(out, " firstCTSOffset=\"%d\"", s->first_cts_offset);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
} else {
gf_mpd_print_multiple_segment_base(out, (GF_MPD_MultipleSegmentBase *)s, indent, GF_FALSE);
}
if (s->segment_URLs) {
u32 i;
GF_MPD_SegmentURL *url;
i = s->tsb_first_entry;
while ( (url = gf_list_enum(s->segment_URLs, &i))) {
gf_mpd_nl(out, indent+1);
if (s->index_mode) {
gf_fprintf(out, "<I t=\""LLU"\" n=\""LLU"\" d=\""LLU"\"", url->first_tfdt, url->first_pck_seq, url->duration);
if (url->split_first_dur || url->split_last_dur)
gf_fprintf(out, " f=\"%u\" l=\"%u\" ", url->split_first_dur, url->split_last_dur);
if (url->frag_start_offset) {
gf_fprintf(out, " s=\""LLU"\"", url->frag_start_offset);
if (url->first_tfdt && url->frag_tfdt && (url->frag_tfdt!=url->first_tfdt)) {
gf_fprintf(out, " dt=\""LLU"\" ", url->frag_tfdt);
}
}
} else {
gf_fprintf(out, "<SegmentURL");
if (url->media) gf_xml_dump_string(out, " media=\"", url->media, "\"");
if (url->duration)gf_fprintf(out, " duration=\""LLU"\"", url->duration);
if (url->index) gf_fprintf(out, " index=\"%s\"", url->index);
if (url->media_range && url->media_range->end_range!=0) gf_fprintf(out, " mediaRange=\""LLD"-"LLD"\"", url->media_range->start_range, url->media_range->end_range);
if (url->index_range && url->index_range->end_range!=0) gf_fprintf(out, " indexRange=\""LLD"-"LLD"\"", url->index_range->start_range, url->index_range->end_range);
if (url->key_url) {
u32 idx;
gf_fprintf(out, " hls:keyMethod=\"aes-128\" hls:KeyURL=%s hls:KeyIV=\"", url->key_url);
for (idx=0; idx<16; idx++) {
gf_fprintf(out, "%02x", url->key_iv[idx]);
}
gf_fprintf(out, "\"");
}
}
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
}
gf_mpd_nl(out, indent);
gf_fprintf(out, "</SegmentList>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_segment_template(FILE *out, GF_MPD_SegmentTemplate *s, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<SegmentTemplate");
if (s->media) gf_xml_dump_string(out, " media=\"", s->media, "\"");
if (s->index) gf_fprintf(out, " index=\"%s\"", s->index);
if (s->initialization) gf_xml_dump_string(out, " initialization=\"", s->initialization, "\"");
if (s->bitstream_switching) gf_fprintf(out, " bitstreamSwitching=\"%s\"", s->bitstream_switching);
if (s->nb_parts) gf_fprintf(out, " k=\"%d\"", s->nb_parts);
if (gf_mpd_print_multiple_segment_base(out, (GF_MPD_MultipleSegmentBase *)s, indent, GF_TRUE))
return;
gf_mpd_nl(out, indent);
gf_fprintf(out, "</SegmentTemplate>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_extensible_print_attr(FILE *out, GF_List *attributes)
{
if (!attributes) return;
u32 j=0;
GF_XMLAttribute *att;
while ((att = (GF_XMLAttribute *)gf_list_enum(attributes, &j))) {
if (!strcmp(att->name, "xmlns")) continue;
else if (!strcmp(att->name, "xmlns:gpac")) continue;
gf_fprintf(out, " %s=\"", att->name);
gf_xml_dump_string(out, NULL, att->value, "\"");
}
}
static void gf_mpd_extensible_print_nodes(FILE *out, GF_List *children, s32 indent, u32 *child_idx, Bool is_final)
{
u32 idx=0, i, count;
if (!children) return;
idx = *child_idx;
count = gf_list_count(children);
for (i=0; i<count; i++) {
char *txt;
GF_XMLNode *child = (GF_XMLNode *) gf_list_get(children, i);
if (child->orig_pos < idx)
continue;
if ((child->orig_pos > idx) && !is_final) {
*child_idx = idx+1;
return;
}
txt = gf_xml_dom_serialize(child, GF_FALSE, GF_TRUE);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "%s", txt);
gf_free(txt);
gf_mpd_lf(out, indent);
idx++;
}
if (!is_final) {
*child_idx = idx+1;
}
}
static void gf_mpd_print_desc(FILE *out, GF_MPD_Descriptor *desc, char *desc_name, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<%s", desc_name);
if (desc->id) gf_xml_dump_string(out, " id=\"", desc->id, "\"");
if (desc->scheme_id_uri) gf_xml_dump_string(out, " schemeIdUri=\"", desc->scheme_id_uri, "\"");
if (desc->value) gf_xml_dump_string(out, " value=\"", desc->value, "\"");
gf_mpd_extensible_print_attr(out, desc->x_attributes);
if (desc->x_children) {
u32 idx=0;
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
gf_mpd_extensible_print_nodes(out, desc->x_children, indent, &idx, GF_TRUE);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</%s>", desc_name);
gf_mpd_lf(out, indent);
} else {
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
}
static void gf_mpd_print_descriptors(FILE *out, GF_List *desc_list, char *desc_name, s32 indent, GF_List *x_children, u32 *child_idx)
{
u32 i=0;
GF_MPD_Descriptor *desc;
while ((desc = (GF_MPD_Descriptor *)gf_list_enum(desc_list, &i))) {
gf_mpd_extensible_print_nodes(out, x_children, indent, child_idx, GF_FALSE);
gf_mpd_print_desc(out, desc, desc_name, indent);
}
}
#include <gpac/iso639.h>
static void mpd_print_lang(FILE *out, const char *attVal, const char *attName)
{
if (!attVal) return;
if (!gf_sys_old_arch_compat()) {
if (!strcmp(attVal, "und")) return;
if (strlen(attVal)==3) {
s32 res = gf_lang_find(attVal);
if (res>0) {
const char *lang = gf_lang_get_2cc(res);
if (lang) attVal = lang;
}
}
}
gf_fprintf(out, " %s=\"%s\"", attName ? attName : "lang", attVal);
}
static void gf_mpd_print_content_component(FILE *out, GF_List *content_component , s32 indent)
{
u32 i=0;
GF_MPD_ContentComponent *cc;
while ((cc = gf_list_enum(content_component, &i))) {
gf_mpd_nl(out, indent);
gf_fprintf(out, "<ContentComponent id=\"%d\" contentType=\"%s\"", cc->id, cc->type);
mpd_print_lang(out, cc->lang, NULL);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
}
static void gf_mpd_print_inband_event(FILE *out, GF_List *inband_event, s32 indent) {
u32 i = 0;
GF_MPD_Inband_Event *ibe;
while ((ibe = gf_list_enum(inband_event, &i))) {
gf_mpd_nl(out, indent);
gf_fprintf(out, "<InbandEventStream schemeIdUri=\"%s\"", ibe->scheme_id_uri);
if (ibe->value)
gf_fprintf(out, " value=\"%s\"", ibe->value);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
}
static void gf_mpd_print_common_attributes(FILE *out, GF_MPD_CommonAttributes *ca)
{
if (ca->profiles) {
gf_xml_dump_string(out, " profiles=\"", ca->profiles, "\"");
}
if (ca->mime_type) gf_fprintf(out, " mimeType=\"%s\"", ca->mime_type);
if (ca->codecs) gf_fprintf(out, " codecs=\"%s\"", ca->codecs);
if (ca->width) gf_fprintf(out, " width=\"%d\"", ca->width);
if (ca->height) gf_fprintf(out, " height=\"%d\"", ca->height);
if (ca->framerate){
gf_fprintf(out, " frameRate=\"%d",ca->framerate->num);
if(ca->framerate->den>1)gf_fprintf(out, "/%d",ca->framerate->den);
gf_fprintf(out, "\"");
}
if (ca->sar) gf_fprintf(out, " sar=\"%d:%d\"", ca->sar->num, ca->sar->den);
if (ca->samplerate) gf_fprintf(out, " audioSamplingRate=\"%d\"", ca->samplerate);
if (ca->segmentProfiles) {
gf_xml_dump_string(out, " segmentProfiles=\"", ca->segmentProfiles, "\"");
}
if (ca->maximum_sap_period) gf_fprintf(out, " maximumSAPPeriod=\"%d\"", ca->maximum_sap_period);
if (ca->starts_with_sap) gf_fprintf(out, " startWithSAP=\"%d\"", ca->starts_with_sap);
if ((ca->max_playout_rate!=1.0)) gf_fprintf(out, " maxPlayoutRate=\"%g\"", ca->max_playout_rate);
if (ca->coding_dependency) gf_fprintf(out, " codingDependency=\"true\"");
if (ca->scan_type != GF_MPD_SCANTYPE_UNKNOWN) gf_fprintf(out, " scanType=\"%s\"", ca->scan_type == GF_MPD_SCANTYPE_PROGRESSIVE ? "progressive" : "interlaced");
if (ca->selection_priority) gf_fprintf(out, " selectionPriority=\"%d\"", ca->selection_priority);
if (ca->tag) gf_fprintf(out, " tag=\"%s\"", ca->tag);
}
static void gf_mpd_print_label(FILE *out, GF_List *list, s32 indent)
{
u32 i=0;
GF_MPD_Label *l;
while ((l = gf_list_enum(list, &i))) {
gf_mpd_nl(out, indent);
gf_fprintf(out, "<Label");
if (l->id) gf_fprintf(out, " id=\"%d\"", l->id);
gf_fprintf(out, " lang=\"%s\">%s</Label>", l->lang, l->content);
gf_mpd_lf(out, indent);
}
}
static void gf_mpd_print_group_label(FILE *out, GF_List *list, s32 indent)
{
u32 i=0;
GF_MPD_GroupLabel *l;
while ((l = gf_list_enum(list, &i))) {
gf_mpd_nl(out, indent);
gf_fprintf(out, "<GroupLabel id=\"%d\" lang=\"%s\">%s</GroupLabel>", l->id, l->lang, l->content);
gf_mpd_lf(out, indent);
}
}
static u32 gf_mpd_print_common_children(FILE *out, GF_MPD_CommonAttributes *ca, s32 indent, u32 *child_idx)
{
gf_mpd_print_descriptors(out, ca->frame_packing, "Framepacking", indent, ca->x_children, child_idx);
gf_mpd_print_descriptors(out, ca->audio_channels, "AudioChannelConfiguration", indent, ca->x_children, child_idx);
gf_mpd_print_descriptors(out, ca->content_protection, "ContentProtection", indent, ca->x_children, child_idx);
gf_mpd_print_descriptors(out, ca->essential_properties, "EssentialProperty", indent, ca->x_children, child_idx);
gf_mpd_print_descriptors(out, ca->supplemental_properties, "SupplementalProperty", indent, ca->x_children, child_idx);
gf_mpd_print_group_label(out, ca->group_labels, indent);
gf_mpd_print_label(out, ca->labels, indent);
if (ca->producer_reference_time) {
u32 i, count = gf_list_count(ca->producer_reference_time);
for (i=0; i<count; i++) {
GF_MPD_ProducerReferenceTime *pref = gf_list_get(ca->producer_reference_time, i);
gf_mpd_nl(out, indent);
gf_fprintf(out, "<ProducerReferenceTime id=\"%d\" presentationTime=\"%d\"", pref->ID, pref->presentation_time);
if (pref->inband) gf_fprintf(out, " inband=\"true\"");
if (pref->wallclock) gf_fprintf(out, " wallClockTime=\"%s\"", pref->wallclock);
switch (pref->type) {
case GF_MPD_PRODUCER_REF_ENCODER:
gf_fprintf(out, " type=\"encoder\"");
break;
case GF_MPD_PRODUCER_REF_CAPTURED:
gf_fprintf(out, " type=\"captured\"");
break;
case GF_MPD_PRODUCER_REF_APPLICATION:
gf_fprintf(out, " type=\"application\"");
if (pref->scheme) gf_fprintf(out, " applicationScheme=\"%s\"", pref->scheme);
break;
}
if (pref->utc_timing) {
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
gf_mpd_print_desc(out, pref->utc_timing, "UTCTiming", indent+1);
gf_fprintf(out, "</ProducerReferenceTime>");
gf_mpd_lf(out, indent);
} else {
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
}
}
if (ca->isobmf_tracks) {
u32 k=0;
GF_MPD_ISOBMFInfo *info;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<ISOBMFInfo>");
gf_mpd_lf(out, indent);
while ((info = (GF_MPD_ISOBMFInfo *) gf_list_enum(ca->isobmf_tracks, &k))) {
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<ISOBMFTrack");
if (info->trackID) gf_fprintf(out, " ID=\"%d\"", info->trackID);
if (info->stsd) gf_fprintf(out, " stsd=\"%s\"", info->stsd);
if (info->mediaOffset) gf_fprintf(out, " offset=\""LLD"\"", info->mediaOffset);
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
gf_mpd_nl(out, indent);
gf_fprintf(out, "</ISOBMFInfo>");
gf_mpd_lf(out, indent);
}
return 0;
}
static void gf_mpd_print_dasher_context(FILE *out, GF_DASH_SegmenterContext *dasher, s32 indent)
{
gf_mpd_nl(out, indent);
gf_fprintf(out, "<gpac:dasher ");
gf_fprintf(out, "done=\"%s\" ", dasher->done ? "true" : "false");
gf_fprintf(out, "init=\"%s\" ", dasher->init_seg);
gf_fprintf(out, "template=\"%s\" ", dasher->template_seg);
if (dasher->template_idx)
gf_fprintf(out, "index=\"%s\" ", dasher->template_idx);
gf_fprintf(out, "segNumber=\"%d\" ", dasher->seg_number);
gf_fprintf(out, "url=\"%s\" ", dasher->src_url);
gf_fprintf(out, "lastPacketIdx=\""LLU"\" ", dasher->last_pck_idx);
gf_fprintf(out, "pidID=\"%d\" ", dasher->pid_id);
if (dasher->dep_pid_id)
gf_fprintf(out, "depID=\"%d\" ", dasher->dep_pid_id);
if (dasher->period_id)
gf_fprintf(out, "periodID=\"%s\" ", dasher->period_id);
if (dasher->period_duration.num && dasher->period_duration.den)
gf_fprintf(out, "periodDuration=\""LLD"/"LLU"\" ", dasher->period_duration.num, dasher->period_duration.den);
if (dasher->period_start.num && dasher->period_start.den)
gf_fprintf(out, "periodStart=\""LLD"/"LLU"\" ", dasher->period_start.num, dasher->period_start.den);
gf_fprintf(out, "multiPIDInit=\"%s\" ", dasher->multi_pids ? "true" : "false");
gf_fprintf(out, "dashDuration=\"%d/%d\" ", dasher->dash_dur.num, dasher->dash_dur.den);
gf_fprintf(out, "nextSegmentStart=\""LLU"\" ", dasher->next_seg_start);
gf_fprintf(out, "firstCTS=\""LLU"\" ", dasher->first_cts);
gf_fprintf(out, "firstDTS=\""LLU"\" ", dasher->first_dts);
gf_fprintf(out, "mpdTimescale=\"%d\" ", dasher->mpd_timescale);
gf_fprintf(out, "sourcePID=\"%d\" ", dasher->source_pid);
gf_fprintf(out, "estimatedNextDTS=\""LLU"\" ", dasher->est_next_dts);
gf_fprintf(out, "cumulatedDur=\"%g\" ", dasher->cumulated_dur);
gf_fprintf(out, "cumulatedSubdur=\"%g\" ", dasher->cumulated_subdur);
gf_fprintf(out, "moofSN=\"%d\" ", dasher->moof_sn);
gf_fprintf(out, "moofInc=\"%d\" ", dasher->moof_sn_inc);
if (dasher->segs_purged)
gf_fprintf(out, "segsPurged=\"%d\" ", dasher->segs_purged);
if (dasher->dur_purged)
gf_fprintf(out, "durPurged=\"%g\" ", dasher->dur_purged);
if (dasher->nb_repeat)
gf_fprintf(out, "nbRepeat=\"%d\" ", dasher->nb_repeat);
if (dasher->ts_offset)
gf_fprintf(out, "tsOffset=\""LLU"\" ", dasher->ts_offset);
if (dasher->mux_pids)
gf_fprintf(out, "muxPIDs=\"%s\" ", dasher->mux_pids);
if (dasher->last_dyn_period_id) {
gf_fprintf(out, "lastDynPeriodID=\"%d\" ", dasher->last_dyn_period_id);
}
if (dasher->subdur_forced) {
gf_fprintf(out, "subdurForced=\"true\" ");
}
gf_fprintf(out, "ownsSet=\"%s\"/>", dasher->owns_set ? "true" : "false");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_dasher_segments(FILE *out, GF_List *segments, s32 indent)
{
u32 i, count = gf_list_count(segments);
if (!count) return;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<gpac:segments>\n");
for (i=0; i<count; i++) {
GF_DASH_SegmentContext *sctx = gf_list_get(segments, i);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<segmentInfo ");
gf_fprintf(out, "time=\""LLU"\" ", sctx->time);
gf_fprintf(out, "dur=\""LLU"\" ", sctx->dur);
gf_fprintf(out, "seg_num=\"%d\" ", sctx->seg_num);
if (sctx->filename) gf_fprintf(out, "file=\"%s\" ", sctx->filename);
if (sctx->filepath) gf_fprintf(out, "path=\"%s\" ", sctx->filepath);
if (sctx->file_size) {
gf_fprintf(out, "size=\"%d\" ", sctx->file_size);
if (sctx->file_offset) gf_fprintf(out, "offset=\""LLU"\" ", sctx->file_offset);
}
if (sctx->index_size) {
gf_fprintf(out, "idx_size=\"%d\" ", sctx->index_size);
if (sctx->index_offset) gf_fprintf(out, "idx_offset=\""LLU"\" ", sctx->index_offset);
}
//we don't store frag context because we can only serialize the dash state at segment boundaries, and we don't keep frag states for already published
//segments
gf_fprintf(out, "/>");
gf_mpd_lf(out, indent);
}
gf_mpd_nl(out, indent);
gf_fprintf(out, "</gpac:segments>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_representation(GF_MPD_Representation *rep, FILE *out, Bool write_context, s32 indent, u32 alt_mha_profile, Bool skip_mime)
{
u32 child_idx = 0;
char *bck_codecs = NULL;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<Representation");
if (rep->id) gf_fprintf(out, " id=\"%s\"", rep->id);
/* if (!gf_list_count(rep->base_URLs) && !rep->segment_base && !rep->segment_template && !rep->segment_list && !gf_list_count(rep->sub_representations)) {
can_close = 1;
}
*/
if (alt_mha_profile) {
char szTmp[15], *sep;
bck_codecs = rep->codecs;
rep->codecs = gf_strdup(bck_codecs);
snprintf(szTmp, 14, "0x%02X", alt_mha_profile-1);
szTmp[14] = 0;
sep = strstr(rep->codecs, ".0x");
if (sep) {
sep[1] = 0;
gf_dynstrcat(&rep->codecs, szTmp, NULL);
}
}
char *mime_type = rep->mime_type;
if (skip_mime) rep->mime_type = NULL;
gf_mpd_print_common_attributes(out, (GF_MPD_CommonAttributes*)rep);
rep->mime_type = mime_type;
if (rep->bandwidth) gf_fprintf(out, " bandwidth=\"%d\"", rep->bandwidth);
if (rep->quality_ranking) gf_fprintf(out, " qualityRanking=\"%d\"", rep->quality_ranking);
if (rep->dependency_id) gf_fprintf(out, " dependencyId=\"%s\"", rep->dependency_id);
if (rep->media_stream_structure_id) gf_fprintf(out, " mediaStreamStructureId=\"%s\"", rep->media_stream_structure_id);
if (rep->first_tfdt_plus_one) {
gf_fprintf(out, " gpac:first_tfdt=\""LLU"\"", rep->first_tfdt_plus_one-1);
gf_fprintf(out, " gpac:tfdt_timescale=\""LLU"\"", rep->first_tfdt_timescale);
}
if (rep->res_url) {
gf_fprintf(out, " src=\"%s\"", rep->res_url);
if (rep->trackID)
gf_fprintf(out, " trackID=\"%d\"", rep->trackID);
}
if (bck_codecs) {
gf_free(rep->codecs);
rep->codecs = bck_codecs;
}
if (write_context)
gf_fprintf(out, " gpac:discontinuityID=\"%u\"", rep->discontinuity_id);
gf_mpd_extensible_print_attr(out, rep->x_attributes);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
if (write_context) {
if (rep->dasher_ctx) {
gf_mpd_print_dasher_context(out, rep->dasher_ctx, indent+1);
}
if (rep->state_seg_list) {
gf_mpd_print_dasher_segments(out, rep->state_seg_list, indent+1);
}
}
gf_mpd_print_common_children(out, (GF_MPD_CommonAttributes*)rep, indent+1, &child_idx);
gf_mpd_print_base_urls(out, rep->base_URLs, indent+1);
if (rep->segment_base) {
gf_mpd_extensible_print_nodes(out, rep->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_base(out, rep->segment_base, indent+1);
}
if (rep->segment_list) {
gf_mpd_extensible_print_nodes(out, rep->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_list(out, rep->segment_list, indent+1);
}
if (rep->segment_template) {
gf_mpd_extensible_print_nodes(out, rep->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_template(out, rep->segment_template, indent+1);
}
/*TODO
e = gf_mpd_parse_subrepresentation(rep->sub_representations, child);
if (e) return e;
*/
gf_mpd_extensible_print_nodes(out, rep->x_children, indent, &child_idx, GF_TRUE);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</Representation>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_event_stream(FILE *out, GF_MPD_EventStream *event_stream, s32 indent)
{
GF_MPD_EventStreamEntry *evt;
u32 i=0;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<EventStream schemeIdUri=\"%s\" timescale=\"%u\">", event_stream->scheme_id_uri, event_stream->timescale);
gf_mpd_lf(out, indent);
while ( (evt = (GF_MPD_EventStreamEntry*) gf_list_enum(event_stream->entries, &i)) ) {
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<Event presentationTime=\"" LLD "\" duration=\"%u\" id=\"%d\">", evt->presentation_time, evt->duration, evt->id);
gf_mpd_lf(out, indent+1);
gf_mpd_nl(out, indent+2);
gf_fprintf(out, "<Signal xmlns=\"%s\">", evt->xmlns);
gf_mpd_lf(out, indent+2);
gf_mpd_nl(out, indent+3);
{
size_t sz = 2*evt->message_size + 3;
char *b64 = gf_malloc(sizeof(char) * sz);
if (b64) {
sz = gf_base64_encode(evt->message, evt->message_size, b64, (u32)sz);
b64[sz] = 0;
gf_fprintf(out, "<Binary>%s</Binary>", b64);
gf_free(b64);
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Allocation failure for base64 SCTE35 event"));
}
}
gf_mpd_lf(out, indent+3);
gf_mpd_nl(out, indent+2);
gf_fprintf(out, "</Signal>");
gf_mpd_lf(out, indent+2);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "</Event>");
gf_mpd_lf(out, indent+1);
}
gf_mpd_nl(out, indent);
gf_fprintf(out, "</EventStream>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_adaptation_set(GF_MPD_AdaptationSet *as, FILE *out, Bool write_context, s32 indent, u32 alt_mha_profile)
{
u32 i, child_idx=0;
GF_MPD_Representation *rep;
if (!alt_mha_profile && as->nb_alt_mha_profiles && as->alt_mha_profiles_only) {
for (i=0; i<as->nb_alt_mha_profiles; i++) {
gf_mpd_print_adaptation_set(as, out, write_context, indent, as->alt_mha_profiles[i] + 1);
}
return;
}
//check if all reps have the same mime, if so only write it at AS level
char *mime_type = NULL;
if (!as->mime_type && !gf_sys_old_arch_compat()) {
for (i=0; i<gf_list_count(as->representations); i++) {
GF_MPD_Representation *rep = gf_list_get(as->representations, i);
if (!i)
mime_type = rep->mime_type;
else if (mime_type && (!rep->mime_type || strcmp(rep->mime_type, mime_type)))
mime_type = NULL;
}
as->mime_type = mime_type;
}
gf_mpd_nl(out, indent);
gf_fprintf(out, "<AdaptationSet");
if (as->id>=0) gf_fprintf(out, " id=\"%d\"", as->id);
if (as->xlink_href) {
gf_xml_dump_string(out, " xlink:href=\"", as->xlink_href,"\"");
if (as->xlink_actuate_on_load)
gf_fprintf(out, " actuate=\"onLoad\"");
}
if (as->segment_alignment) gf_fprintf(out, " segmentAlignment=\"true\"");
if (as->group != (u32) -1) gf_fprintf(out, " group=\"%d\"", as->group);
if (as->min_bandwidth) gf_fprintf(out, " minBandwidth=\"%d\"", as->min_bandwidth);
if (as->max_bandwidth) gf_fprintf(out, " maxBandwidth=\"%d\"", as->max_bandwidth);
if (as->min_width) gf_fprintf(out, " minWidth=\"%d\"", as->min_width);
if (as->max_width) gf_fprintf(out, " maxWidth=\"%d\"", as->max_width);
if (as->min_height) gf_fprintf(out, " minHeight=\"%d\"", as->min_height);
if (as->max_height) gf_fprintf(out, " maxHeight=\"%d\"", as->max_height);
if ((as->min_framerate.num != 0) && (as->min_framerate.den != 0)) {
if (as->min_framerate.den==1)
gf_fprintf(out, " minFrameRate=\"%d\"", as->min_framerate.num);
else
gf_fprintf(out, " minFrameRate=\"%d/%d\"", as->min_framerate.num, as->min_framerate.den);
}
if ((as->max_framerate.num != 0) && (as->max_framerate.den != 0)) {
if (as->max_framerate.den==1)
gf_fprintf(out, " maxFrameRate=\"%d\"", as->max_framerate.num);
else
gf_fprintf(out, " maxFrameRate=\"%d/%d\"", as->max_framerate.num, as->max_framerate.den);
}
if (as->par && (as->par->num != 0) && (as->par->den != 0))
gf_fprintf(out, " par=\"%d:%d\"", as->par->num, as->par->den);
mpd_print_lang(out, as->lang, NULL);
if (as->bitstream_switching) gf_fprintf(out, " bitstreamSwitching=\"true\"");
gf_mpd_print_common_attributes(out, (GF_MPD_CommonAttributes*)as);
//backward compatibilty with old arch
if (as->subsegment_alignment) gf_fprintf(out, " subsegmentAlignment=\"true\"");
if (as->subsegment_starts_with_sap) gf_fprintf(out, " subsegmentStartsWithSAP=\"%d\"", as->subsegment_starts_with_sap);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
gf_mpd_print_common_children(out, (GF_MPD_CommonAttributes*)as, indent+1, &child_idx);
gf_mpd_print_base_urls(out, as->base_URLs, indent+1);
gf_mpd_print_descriptors(out, as->accessibility, "Accessibility", indent+1, as->x_children, &child_idx);
gf_mpd_print_descriptors(out, as->role, "Role", indent+1, as->x_children, &child_idx);
gf_mpd_print_descriptors(out, as->rating, "Rating", indent+1, as->x_children, &child_idx);
gf_mpd_print_descriptors(out, as->viewpoint, "Viewpoint", indent+1, as->x_children, &child_idx);
gf_mpd_print_content_component(out, as->content_component, indent+1);
gf_mpd_print_inband_event(out, as->inband_event, indent + 1);
if (as->segment_base) {
gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_base(out, as->segment_base, indent+1);
}
if (as->segment_list) {
gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_list(out, as->segment_list, indent+1);
}
if (as->segment_template) {
gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_template(out, as->segment_template, indent+1);
}
i=0;
while ((rep = (GF_MPD_Representation *)gf_list_enum(as->representations, &i))) {
gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_representation(rep, out, write_context, indent+1, alt_mha_profile, mime_type ? GF_TRUE : GF_FALSE);
}
gf_mpd_extensible_print_nodes(out, as->x_children, indent, &child_idx, GF_TRUE);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</AdaptationSet>");
gf_mpd_lf(out, indent);
if (mime_type) as->mime_type = NULL;
if (!alt_mha_profile) {
for (i=0; i<as->nb_alt_mha_profiles; i++) {
gf_mpd_print_adaptation_set(as, out, write_context, indent, as->alt_mha_profiles[i] + 1);
}
}
}
static void gf_mpd_print_preselection(GF_MPD_Preselection *pre, FILE *out, Bool write_context, s32 indent, u32 alt_mha_profile)
{
u32 i, child_idx=0;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<Preselection");
if (pre->id>=0) gf_fprintf(out, " id=\"%d\"", pre->id);
if (pre->preselection_components) {
gf_fprintf(out, " preselectionComponents=\"");
for (i = 0; i < gf_list_count(pre->preselection_components); i++) {
u32 *comp = gf_list_get(pre->preselection_components, i);
gf_fprintf(out, "%d", *comp);
if (i < gf_list_count(pre->preselection_components) - 1)
gf_fprintf(out, " ");
}
gf_fprintf(out, "\"");
}
if (pre->lang) mpd_print_lang(out, pre->lang, NULL);
gf_mpd_extensible_print_attr(out, pre->x_attributes);
gf_mpd_print_common_attributes(out, (GF_MPD_CommonAttributes*)pre);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
gf_mpd_print_common_children(out, (GF_MPD_CommonAttributes*)pre, indent+1, &child_idx);
gf_mpd_print_descriptors(out, pre->accessibility, "Accessibility", indent+1, pre->x_children, &child_idx);
gf_mpd_print_descriptors(out, pre->role, "Role", indent+1, pre->x_children, &child_idx);
gf_mpd_print_descriptors(out, pre->rating, "Rating", indent+1, pre->x_children, &child_idx);
gf_mpd_print_descriptors(out, pre->viewpoint, "Viewpoint", indent+1, pre->x_children, &child_idx);
gf_mpd_extensible_print_nodes(out, pre->x_children, indent, &child_idx, GF_TRUE);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</Preselection>");
gf_mpd_lf(out, indent);
}
static void gf_mpd_print_period(GF_MPD_Period const * const period, Bool is_dynamic, FILE *out, Bool write_context, s32 indent)
{
GF_MPD_AdaptationSet *as;
GF_MPD_EventStream *evte;
u32 i, child_idx=0;
gf_mpd_nl(out, indent);
gf_fprintf(out, "<Period");
if (period->xlink_href) {
gf_xml_dump_string(out, " xlink:href=\"", period->xlink_href, "\"");
if (period->xlink_actuate_on_load)
gf_fprintf(out, " actuate=\"onLoad\"");
}
if (period->ID)
gf_fprintf(out, " id=\"%s\"", period->ID);
if (is_dynamic || period->start)
gf_mpd_print_duration(out, "start", period->start, GF_TRUE);
if (period->duration)
gf_mpd_print_duration(out, "duration", period->duration, GF_TRUE);
if (period->bitstream_switching)
gf_fprintf(out, " bitstreamSwitching=\"true\"");
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
gf_mpd_print_base_urls(out, period->base_URLs, indent+1);
i=0;
while ( (evte = (GF_MPD_EventStream *) gf_list_enum(period->event_streams, &i)))
gf_mpd_print_event_stream(out, evte, indent+1);
if (period->segment_base) {
gf_mpd_extensible_print_nodes(out, period->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_base(out, period->segment_base, indent+1);
}
if (period->segment_list) {
gf_mpd_extensible_print_nodes(out, period->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_list(out, period->segment_list, indent+1);
}
if (period->segment_template) {
gf_mpd_extensible_print_nodes(out, period->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_segment_template(out, period->segment_template, indent+1);
}
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
gf_mpd_extensible_print_nodes(out, period->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_adaptation_set(as, out, write_context, indent+1, 0);
}
i=0;
GF_MPD_Preselection *p = NULL;
while ((p = (GF_MPD_Preselection *) gf_list_enum(period->preselections, &i))) {
gf_mpd_print_preselection(p, out, write_context, indent+1, 0);
}
gf_mpd_extensible_print_nodes(out, period->x_children, indent, &child_idx, GF_TRUE);
gf_mpd_nl(out, indent);
gf_fprintf(out, "</Period>");
gf_mpd_lf(out, indent);
}
static GF_Err mpd_write_generation_comment(GF_MPD const * const mpd, FILE *out)
{
u64 time_ms;
time_t gtime;
struct tm *t;
u32 sec;
time_ms = mpd->publishTime;
sec = (u32)(time_ms / 1000);
time_ms -= ((u64)sec) * 1000;
gf_assert(time_ms<1000);
gtime = sec;
t = gf_gmtime(&gtime);
if (! gf_sys_is_test_mode() ){
gf_fprintf(out, "<!-- MPD file Generated with GPAC version %s at %d-%02d-%02dT%02d:%02d:%02d.%03dZ -->\n", gf_gpac_version(), 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, (u32)time_ms);
}
if (!mpd->write_context) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[MPD] Generating MPD at time %d-%02d-%02dT%02d:%02d:%02d.%03dZ\n", 1900 + t->tm_year, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, (u32)time_ms));
}
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, 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 hls_version, const char *prim_group_id)
{
u32 i;
if (set && set->intra_only)
gf_fprintf(out, "#EXT-X-I-FRAME-STREAM-INF:");
else
gf_fprintf(out, "#EXT-X-STREAM-INF:");
gf_fprintf(out, "BANDWIDTH=%d,CODECS=\"%s", rep->bandwidth+max_alt_bandwidth , rep->codecs);
if (codec_ext)
gf_fprintf(out, ",%s", codec_ext);
gf_fprintf(out, "\"");
if (rep->width && rep->height)
gf_fprintf(out, ",RESOLUTION=%dx%d", rep->width, rep->height);
else if (max_alt_width && max_alt_height)
gf_fprintf(out, ",RESOLUTION=%dx%d", max_alt_width, max_alt_height);
if (set && set->intra_only) {
gf_fprintf(out, ",URI=\"%s\"\n", m3u8_name);
return;
}
if (rep->fps)
gf_fprintf(out,",FRAME-RATE=%.03g", rep->fps);
else if (max_alt_fps)
gf_fprintf(out,",FRAME-RATE=%.03g", max_alt_fps);
if (g_type && g_id_pref) {
gf_fprintf(out, ",%s=\"%s", g_type, g_id_pref);
gf_fprintf(out, "\"");
}
if (g2_type && g2_id_pref) {
gf_fprintf(out,",%s=\"%s", g2_type, g2_id_pref);
gf_fprintf(out,"\"");
}
Bool has_cc = GF_FALSE;
for (i=0; i<rep->nb_hls_master_tags; i++) {
gf_fprintf(out,",%s", rep->hls_master_tags[i]);
if (strstr(rep->hls_master_tags[i], "CLOSED-CAPTIONS"))
has_cc = GF_TRUE;
}
if (!has_cc && (hls_version>=6)) {
gf_fprintf(out, ",CLOSED-CAPTIONS=NONE");
}
if (prim_group_id) {
gf_fprintf(out, ",%s=\"%s\"", (rep->streamtype==GF_STREAM_VISUAL) ? "VIDEO" : "AUDIO", prim_group_id);
}
if (rep->m3u8_x_attributes) {
u32 j=0;
GF_XMLAttribute *att;
while ((att = (GF_XMLAttribute *)gf_list_enum(rep->m3u8_x_attributes, &j))) {
if (strstr(att->name, "VIDEO-RANGE")) {
gf_fprintf(out, ",%s=%s", att->name, att->value);
} else {
gf_fprintf(out, ",%s=\"%s\"", att->name, att->value);
}
}
}
gf_fprintf(out,"\n");
gf_fprintf(out, "%s\n",m3u8_name);
}
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 hls_version, const char *prim_group_id)
{
u32 i;
GF_MPD_AdaptationSet *r_as;
GF_MPD_Representation *r_rep;
if (!rep->mime_type) return;
//no period, this is a component description
if (!period) {
const char *g_type = NULL;
const char *g_id = NULL;
Bool has_name = GF_FALSE;
Bool has_lang = GF_FALSE;
Bool has_chan = GF_FALSE;
Bool has_autosel = GF_FALSE;
Bool has_forced = GF_TRUE;
if (rep->streamtype==GF_STREAM_AUDIO) {
g_type = "AUDIO";
g_id = "audio";
}
if (rep->streamtype==GF_STREAM_VISUAL) {
g_type = "VIDEO";
g_id = "video";
}
else if (rep->streamtype==GF_STREAM_TEXT) {
g_type = "SUBTITLES";
g_id = "subs";
has_forced = GF_FALSE;
}
if (!g_type || !g_id)
return;
gf_fprintf(out, "#EXT-X-MEDIA:TYPE=%s", g_type);
if (rep->groupID)
gf_fprintf(out, ",GROUP-ID=\"%s\"", rep->groupID);
else
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++) {
const char *tag = rep->hls_master_tags[i];
if (!strncmp(tag, "TYPE=", 5)) continue;
else if (!strncmp(tag, "GROUP-ID=", 9)) continue;
else if (!strncmp(tag, "URI=", 4)) continue;
if (!strncmp(tag, "NAME=", 5)) has_name = GF_TRUE;
else if (!strncmp(tag, "LANGUAGE=", 9)) has_lang = GF_TRUE;
else if (!strncmp(tag, "CHANNELS=", 9)) has_chan = GF_TRUE;
else if (!strncmp(tag, "FORCED=", 7)) has_forced = GF_TRUE;
//if default is present (regardless of value), don't inject an autoselect
if (!strncmp(tag, "AUTOSELECT=", 11) || !strncmp(tag, "DEFAULT=", 8)) {
has_autosel = GF_TRUE;
}
gf_fprintf(out,",%s", rep->hls_master_tags[i]);
}
if (!has_name && rep->id)
gf_fprintf(out, ",NAME=\"%s\"", rep->id);
//don't inject LANGUAGE if not known
if (!has_lang && as->lang && strcmp(as->lang, "und"))
gf_fprintf(out, ",LANGUAGE=\"%s\"", as->lang);
if (!has_autosel)
gf_fprintf(out, ",AUTOSELECT=YES");
if (!has_forced && rep->sub_forced)
gf_fprintf(out, ",FORCED=YES");
gf_fprintf(out, ",URI=\"%s\"", m3u8_name);
if (!has_chan) {
if (rep->nb_chan) gf_fprintf(out, ",CHANNELS=\"%d\"", rep->nb_chan);
else if (rep->str_chan[0] != '\0') gf_fprintf(out, ",CHANNELS=\"%s\"", rep->str_chan);
}
return;
}
//no other streams, directly write the entry
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, hls_version, prim_group_id);
return;
}
char *groups_done = NULL;
char *groups_subs_done = NULL;
char *grp_codecs;
const char *g_type, *g_id, *g_type_subs, *g_id_subs;
u32 g_m_bandwidth, g_m_bandwidth_subs, g_m_width, g_m_height;
Double g_m_fps;
re_dump:
//gather all codecs and max values for alternate stream type and subs
grp_codecs = NULL;
g_type = g_id = g_type_subs = g_id_subs = NULL;
g_m_bandwidth = g_m_bandwidth_subs = g_m_width = g_m_height = 0;
g_m_fps = 0;
i=0;
while ( (r_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
u32 j=0;
while ( (r_rep = (GF_MPD_Representation *) gf_list_enum(r_as->representations, &j))) {
const char *hls_group = NULL;
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;
}
//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;
}
const char *local_gid = r_rep->groupID;
if (r_rep->streamtype==GF_STREAM_AUDIO) {
if (!local_gid) local_gid = "audio";
}
else if (r_rep->streamtype==GF_STREAM_VISUAL) {
if (!local_gid) local_gid = "video";
}
else if (r_rep->streamtype==GF_STREAM_TEXT) {
if (!local_gid) local_gid = "subs";
} else {
if (!local_gid) local_gid = "other";
}
u32 k;
Bool exclude = GF_FALSE;
for (k=0;k<rep->nb_group_ids_rend; k++) {
if (!strcmp(rep->group_ids_rend[k], local_gid)) {
exclude = GF_TRUE;
break;
}
}
if (exclude)
continue;
if (groups_done && strstr(groups_done, local_gid)) continue;
if (groups_subs_done && strstr(groups_subs_done, local_gid)) continue;
if (r_rep->streamtype==GF_STREAM_AUDIO) {
if (!g_id) g_id = local_gid;
if (!g_type) g_type = "AUDIO";
g_m_bandwidth = MAX(g_m_bandwidth, r_rep->bandwidth);
} else if (r_rep->streamtype==GF_STREAM_VISUAL) {
if (!g_id) g_id = local_gid;
if (!g_type) g_type = "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_id_subs) g_id_subs = local_gid;
if (!g_type_subs) g_type_subs = "SUBTITLES";
g_m_bandwidth_subs = MAX(g_m_bandwidth_subs, r_rep->bandwidth);
}
//some reps do not have codecs (raw formats, onlly mime is used)
if (r_rep->codecs && (!grp_codecs || !strstr(grp_codecs, r_rep->codecs)))
gf_dynstrcat(&grp_codecs, r_rep->codecs, ",");
}
}
//done dumping all streams
if (!g_id && groups_done) {
if (grp_codecs) gf_free(grp_codecs);
if (groups_done) gf_free(groups_done);
if (groups_subs_done) gf_free(groups_subs_done);
return;
}
//done dumping all subs streams for the secondary media
if (groups_subs_done && !g_id_subs) {
if (grp_codecs) gf_free(grp_codecs);
if (groups_subs_done) gf_free(groups_subs_done);
groups_subs_done = NULL;
if (g_id) {
gf_dynstrcat(&groups_done, g_id, ",");
goto re_dump;
}
//if no g_id we are done
if (groups_done) gf_free(groups_done);
return;
}
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, hls_version, prim_group_id);
if (grp_codecs) gf_free(grp_codecs);
//remember this subs id and redumping for the secondary media
if (g_id_subs) {
gf_dynstrcat(&groups_subs_done, g_id_subs, ",");
goto re_dump;
}
//no more subs, we are done dumping the secondary media
if (g_id) {
if (groups_subs_done) gf_free(groups_subs_done);
groups_subs_done = NULL;
gf_dynstrcat(&groups_done, g_id, ",");
goto re_dump;
}
if (groups_done) gf_free(groups_done);
if (groups_subs_done) gf_free(groups_subs_done);
}
static const char *gf_mpd_m3u8_get_init_seg(const GF_MPD_Period *period, const GF_MPD_AdaptationSet *as, const GF_MPD_Representation *rep)
{
const char *url = NULL;
if (rep->segment_list && rep->segment_list->initialization_segment) url = rep->segment_list->initialization_segment->sourceURL;
else if (rep->segment_template && rep->segment_template->initialization) url = rep->segment_template->initialization;
else if (rep->segment_template && rep->segment_template->initialization_segment) url = rep->segment_template->initialization_segment->sourceURL;
if (as->segment_list && as->segment_list->initialization_segment) url = as->segment_list->initialization_segment->sourceURL;
else if (as->segment_template && as->segment_template->initialization) url = as->segment_template->initialization;
else if (as->segment_template && as->segment_template->initialization_segment) url = as->segment_template->initialization_segment->sourceURL;
if (period->segment_list && period->segment_list->initialization_segment) url = period->segment_list->initialization_segment->sourceURL;
else if (period->segment_template && period->segment_template->initialization) url = period->segment_template->initialization;
else if (period->segment_template && period->segment_template->initialization_segment) url = period->segment_template->initialization_segment->sourceURL;
return url;
}
static void hls_insert_crypt_info(FILE *out, GF_MPD_Representation *rep, GF_DASH_SegmentContext *sctx, const char **last_kms)
{
if (!rep->crypto_type) return;
const char *kms;
if (!sctx->encrypted)
kms = "NONE";
else if (sctx->hls_key_uri) kms = sctx->hls_key_uri;
else {
kms = "URI=\"gpac:hls:key:locator:null\"";
if (!rep->def_kms_used) {
rep->def_kms_used = 1;
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[HLS] Missing key URI in one or more keys - will use dummy one %s\n", kms));
}
}
if (! *last_kms || strcmp(kms, *last_kms)) {
if (!strcmp(kms, "NONE")) {
gf_fprintf(out,"#EXT-X-KEY:METHOD=NONE\n");
} else {
char *subkms = (char *) kms;
while (1) {
char *next = strstr(subkms, ",URI");
if (next) next[0] = 0;
if (rep->crypto_type==1) {
u32 k;
gf_fprintf(out,"#EXT-X-KEY:METHOD=AES-128,%s,IV=0x", subkms);
for (k=0; k<16; k++)
gf_fprintf(out, "%02X", sctx->hls_iv[k]);
gf_fprintf(out, "\n");
} else {
gf_fprintf(out,"#EXT-X-KEY:METHOD=SAMPLE-AES%s,%s\n", (rep->crypto_type==3) ? "-CTR" : "", subkms);
}
if (!next) break;
next[0] = ',';
subkms = next+1;
}
}
*last_kms = (rep->crypto_type>=2) ? kms : NULL;
}
}
static void hls_insert_scte35_info(FILE *out, u64 ast, GF_MPD_Period *period, GF_MPD_AdaptationSet *adaptation_set, GF_MPD_Representation *representation, GF_DASH_SegmentContext *sctx)
{
u64 segment_duration = 0;
u64 presentation_time_offset = 0;
u32 segment_timescale = 1;
GF_MPD_EventStream *es = NULL;
/* Segment contexts are presentation-relative, while SCTE-35 splice_time is
* carried on the source timeline. Resolve the inherited PTO once so both
* values are compared in the same clock domain. */
gf_mpd_resolve_segment_duration(representation, adaptation_set, period,
&segment_duration, &segment_timescale, &presentation_time_offset, NULL);
if (!segment_timescale) segment_timescale = 1;
u32 i = 0;
while ( (es = gf_list_enum(period->event_streams, &i)) ) {
GF_MPD_EventStreamEntry *ese = NULL;
u32 j = 0;
while ( (ese = gf_list_enum(es->entries, &j)) ) {
u64 event_time = ese->presentation_time;
u64 event_time_in_representation_timescale;
u64 event_duration_in_representation_timescale;
u64 presentation_time_offset_in_event_timescale = gf_timestamp_rescale(presentation_time_offset, segment_timescale, es->timescale);
if (event_time >= presentation_time_offset_in_event_timescale)
event_time -= presentation_time_offset_in_event_timescale;
event_time_in_representation_timescale = gf_timestamp_rescale(event_time, es->timescale, representation->timescale);
event_duration_in_representation_timescale = gf_timestamp_rescale(ese->duration, es->timescale, representation->timescale);
if (ese->state == 0 && sctx->time <= event_time_in_representation_timescale && event_time_in_representation_timescale < sctx->time+sctx->dur) {
gf_fprintf(out, "#EXT-X-DATERANGE:ID=\"%d-%04d\",", ese->id, ese->state);
gf_mpd_print_date(out, "START-DATE", ast + (event_time * 1000) / es->timescale);
gf_fprintf(out, ",PLANNED-DURATION=%g", ese->duration/(Double)es->timescale);
if (ese->message) {
gf_fprintf(out, ",SCTE35-OUT=0x");
for (u32 k=0; k<ese->message_size; ++k)
gf_fprintf(out, "%02X", ese->message[k]);
}
gf_fprintf(out, "\n");
gf_fprintf(out, "#EXT-X-CUE-OUT:%g\n", ese->duration/(Double)es->timescale);
ese->state = 1;
}
if (ese->state == 1 && event_time_in_representation_timescale+event_duration_in_representation_timescale <= sctx->time+sctx->dur) {
gf_fprintf(out, "#EXT-X-CUE-IN\n");
ese->state = 0;
}
}
}
}
static GF_Err gf_mpd_write_m3u8_playlist(const GF_MPD *mpd, const GF_MPD_Period *period, const GF_MPD_AdaptationSet *as, GF_MPD_Representation *rep, char *m3u8_name, u32 hls_version, Double max_part_dur_session, const char *force_base_url, Bool delta_update, Bool is_last, FILE* out_file)
{
u32 i, count;
GF_DASH_SegmentContext *sctx;
FILE *out = out_file;
char *force_url=NULL;
const char *last_kms = NULL;
Bool close_file = GF_FALSE;
if (!out_file) {
if (!strcmp(m3u8_name, "std")) out = stdout;
else if (mpd->create_m3u8_files) {
out = gf_fopen(m3u8_name, delta_update ? "ab" : "wb");
if (!out) return GF_IO_ERR;
close_file = GF_TRUE;
} else {
out = gf_file_temp(NULL);
if (rep->m3u8_var_file) gf_fclose(rep->m3u8_var_file);
rep->m3u8_var_file = out;
}
}
count = gf_list_count(rep->state_seg_list);
sctx = gf_list_get(rep->state_seg_list, rep->tsb_first_entry);
if (!delta_update) {
gf_fprintf(out,"#EXTM3U\n");
gf_fprintf(out,"#EXT-X-TARGETDURATION:%d\n", (u32) gf_ceil( ((Double) rep->hls_max_seg_dur.num) / rep->hls_max_seg_dur.den) );
gf_fprintf(out,"#EXT-X-VERSION:%d\n", hls_version);
gf_fprintf(out,"#EXT-X-MEDIA-SEQUENCE:%d\n", sctx ? sctx->seg_num : 0);
if (as->use_hls_ll) {
//PART-HOLD-BACK is REQUIRED if the Playlist contains the EXT-X-PART-INF tag
//we use the recommended (should) PART-TARGET x 3
if (mpd->llhls_part_holdback>0) {
gf_fprintf(out,"#EXT-X-SERVER-CONTROL:PART-HOLD-BACK=%g\n", mpd->llhls_part_holdback);
} else {
gf_fprintf(out,"#EXT-X-SERVER-CONTROL:PART-HOLD-BACK=%g\n", 3 * max_part_dur_session);
}
gf_fprintf(out,"#EXT-X-PART-INF:PART-TARGET=%g\n", rep->hls_ll_part_dur);
}
if (mpd->nb_past_discont)
gf_fprintf(out,"#EXT-X-DISCONTINUITY-SEQUENCE:%d\n", mpd->nb_past_discont);
}
for (i=0; i<rep->nb_hls_variant_tags; i++) {
gf_fprintf(out,"%s\n", rep->hls_variant_tags[i]);
}
if (!delta_update && (as->starts_with_sap<GF_FILTER_SAP_3))
gf_fprintf(out,"#EXT-X-INDEPENDENT-SEGMENTS\n");
if (mpd->m3u8_time && rep->timescale_mpd && (mpd->type == GF_MPD_TYPE_DYNAMIC)) {
u64 seg_ast = mpd->availabilityStartTime;
seg_ast += (sctx->time * 1000) / rep->timescale_mpd;
gf_fprintf(out, "#EXT-X-PROGRAM-DATE-TIME:");
gf_mpd_print_date(out, NULL, seg_ast);
gf_fprintf(out, "\n");
}
//things common to onDemand and live profiles
if (as->intra_only) {
gf_fprintf(out,"#EXT-X-I-FRAMES-ONLY\n");
}
//one file per segment
if (sctx && sctx->filename) {
if (rep->hls_single_file_name) {
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, rep->hls_single_file_name);
if (rep->init_base64) {
const char *mime = rep->mime_type;
if (!mime) mime = "video/mp4";
gf_fprintf(out,"#EXT-X-MAP:URI=\"data:%s;base64,%s\"\n", mime, rep->init_base64);
} else {
gf_fprintf(out,"#EXT-X-MAP:URI=\"%s\"\n", force_url ? force_url : rep->hls_single_file_name);
}
if (force_url) {
gf_free(force_url);
force_url = NULL;
}
}
for (i=rep->tsb_first_entry; i<count; i++) {
Double dur;
sctx = gf_list_get(rep->state_seg_list, i);
gf_assert(sctx->filename);
hls_insert_crypt_info(out, rep, sctx, &last_kms);
u64 next_br_start_plus_one=0;
u32 next_seg_idx=0;
//LL-HLS related
if ((mpd->type == GF_MPD_TYPE_DYNAMIC) && sctx->llhls_mode) {
u32 k, nb_parts=sctx->nb_frags;
//EXT-X-PART tags SHOULD be removed from the Playlist after they are greater than three Target Durations from the end of the Playlist.
if (i+4<count) nb_parts=0;
for (k=0; k<nb_parts; k++) {
Bool write_br = GF_FALSE;
dur = sctx->frags[k].duration;
dur /= rep->timescale;
if (mpd->force_llhls_mode==1) write_br = GF_TRUE;
else if (mpd->force_llhls_mode==2) write_br = GF_FALSE;
else if (sctx->llhls_mode==GF_DASH_LL_HLS_BR) write_br = GF_TRUE;
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, sctx->filename);
if (!write_br) {
u32 frag_idx = k;
char *res = gf_mpd_resolve_subnumber(sctx->llhas_template, force_url ? force_url : sctx->filename, frag_idx);
if (force_url ) gf_free(force_url);
force_url = res;
next_seg_idx = k+1;
}
gf_fprintf(out, "#EXT-X-PART:DURATION=%g,URI=\"%s", dur, force_url ? force_url : sctx->filename);
if (force_url ) {
gf_free(force_url);
force_url = NULL;
}
if (write_br) {
gf_fprintf(out, "\",BYTERANGE=\""LLU"@"LLU"\"", sctx->frags[k].size, sctx->frags[k].offset );
next_br_start_plus_one = 1 + sctx->frags[k].offset + sctx->frags[k].size;
} else {
gf_fprintf(out, "\"");
}
if (sctx->frags[k].independent)
gf_fprintf(out, ",INDEPENDENT=YES");
gf_fprintf(out, "\n");
}
//live edge not done yet
if (!sctx->llhls_mode) {
if (close_file)
gf_fclose(out);
return GF_OK;
}
}
//live edge seg not done yet, do not write EXTINF and stop writing
if (sctx->llhls_mode && (i+1==count) && !sctx->llhls_done) {
//write preload hint
if (mpd->llhls_preload) {
//last seg has no parts yet, we just started it (live edge), advertise first part
if (!sctx->nb_frags) {
gf_assert(sctx->filename);
if (sctx->llhls_mode==GF_DASH_LL_HLS_SF) next_seg_idx = 1;
else next_br_start_plus_one = 1;
}
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, sctx->filename);
if (next_seg_idx) {
char *res = gf_mpd_resolve_subnumber(sctx->llhas_template, force_url ? force_url : sctx->filename, next_seg_idx);
if (force_url ) gf_free(force_url);
force_url = res;;
gf_fprintf(out, "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\"\n", force_url);
} else if (next_br_start_plus_one)
gf_fprintf(out, "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"%s\",BYTERANGE-START="LLU"\n", force_url ? force_url : sctx->filename, next_br_start_plus_one-1);
if (force_url) {
gf_free(force_url);
force_url = NULL;
}
}
//generate rendition report
if (mpd->llhls_rendition_reports) {
char *par_dir= strchr(m3u8_name, '/');
u32 i_as=0;
const GF_MPD_AdaptationSet *o_as;
while ( (o_as = gf_list_enum(period->adaptation_sets, &i_as))) {
u32 i_rep=0;
GF_MPD_Representation *o_rep;
while ( (o_rep = gf_list_enum(o_as->representations, &i_rep))) {
GF_DASH_SegmentContext *o_sctx;
if (o_rep == rep) continue;
o_sctx = gf_list_last(o_rep->state_seg_list);
if (!o_sctx || !o_sctx->llhls_mode) continue;
//not clear in the spec, we assume what is listed must be the last PART completely produced
//if no frag and a segment exists before, use last part of that segment
if (!o_sctx->nb_frags) {
u32 idx = gf_list_count(o_rep->state_seg_list);
if (idx<2) continue;
o_sctx = gf_list_get(o_rep->state_seg_list, idx-2);
if (!o_sctx || !o_sctx->nb_frags) continue;
}
char *o_name = o_rep->m3u8_name;
if (!o_name) {
o_name = gf_file_basename(o_rep->m3u8_var_name);
}
char *par_url = NULL;
//we always produce from the same root, so just use ../
//if no parent dir we are producing at the root, and o_name is relative to the root
if (par_dir) {
gf_dynstrcat(&par_url, "../", NULL);
gf_dynstrcat(&par_url, o_name, NULL);
}
fprintf(out, "#EXT-X-RENDITION-REPORT:URI=\"%s\",LAST-MSN=%d,LAST-PART=%d\n", par_url ? par_url : o_name, o_sctx->seg_num, o_sctx->nb_frags);
if (par_url) gf_free(par_url);
}
}
}
if (close_file)
gf_fclose(out);
return GF_OK;
}
// 0-duration may be encountered in non-LL modes when the duration is not known yet, but players may not like (cf issue 3462)
if (!sctx->dur) continue;
hls_insert_scte35_info(out, mpd->availabilityStartTime, period, as, rep, sctx);
//signal discontinuity if needed
if (sctx && sctx->is_discontinuity)
gf_fprintf(out,"#EXT-X-DISCONTINUITY\n");
dur = (Double) sctx->dur;
dur /= rep->timescale;
gf_fprintf(out,"#EXTINF:%g,\n", dur);
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, sctx->filename);
gf_fprintf(out,"%s\n", force_url ? force_url : sctx->filename);
if (force_url) {
gf_free(force_url);
force_url = NULL;
}
}
}
//byte-range in single file
else {
GF_MPD_BaseURL *base_url=NULL;
const char *b_url=NULL;
GF_MPD_URL *init=NULL;
if (rep->segment_base && rep->segment_base->initialization_segment) init = rep->segment_base->initialization_segment;
if (as->segment_base && as->segment_base->initialization_segment) init = as->segment_base->initialization_segment;
if (period->segment_base && period->segment_base->initialization_segment) init = period->segment_base->initialization_segment;
if (rep->segment_list && rep->segment_list->initialization_segment) init = rep->segment_list->initialization_segment;
if (as->segment_list && as->segment_list->initialization_segment) init = as->segment_list->initialization_segment;
if (period->segment_list && period->segment_list->initialization_segment) init = period->segment_list->initialization_segment;
base_url = gf_list_get(rep->base_URLs, 0);
if (!base_url) {
if (close_file)
gf_fclose(out);
return GF_SERVICE_ERROR;
}
b_url = base_url->hls_vp_rel_url ? base_url->hls_vp_rel_url : base_url->URL;
if (init) {
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, b_url);
if (init->byte_range) {
gf_fprintf(out,"#EXT-X-MAP:URI=\"%s\",BYTERANGE=\"%d@"LLU"\"\n", force_url ? force_url : b_url, (u32) (1+init->byte_range->end_range - init->byte_range->start_range), init->byte_range->start_range);
} else {
gf_fprintf(out,"#EXT-X-MAP:URI=\"%s\"\n", force_url ? force_url : b_url);
}
if (force_url) {
gf_free(force_url);
force_url = NULL;
}
}
for (i=rep->tsb_first_entry; i<count; i++) {
Double dur;
sctx = gf_list_get(rep->state_seg_list, i);
gf_assert(!sctx->filename);
gf_assert(sctx->file_size);
// 0-duration may be encountered in non-LL modes when the duration is not known yet, but players may not like (cf issue 3462)
if (!sctx->dur) continue;
hls_insert_crypt_info(out, rep, sctx, &last_kms);
dur = (Double) sctx->dur;
dur /= rep->timescale;
gf_fprintf(out,"#EXTINF:%g\n", dur);
gf_fprintf(out,"#EXT-X-BYTERANGE:%d@"LLU"\n", sctx->file_size, sctx->file_offset);
if (force_base_url)
force_url = gf_url_concatenate(force_base_url, b_url);
gf_fprintf(out,"%s\n", force_url ? force_url : b_url);
if (force_url) {
gf_free(force_url);
force_url = NULL;
}
}
}
if ((mpd->type != GF_MPD_TYPE_DYNAMIC) && is_last)
gf_fprintf(out,"\n#EXT-X-ENDLIST\n");
if (close_file)
gf_fclose(out);
return GF_OK;
}
static char *get_rep_variant_filename(GF_MPD const * const mpd, GF_MPD_Representation *rep, const char * force_base_url)
{
char szSuffixName[GF_MAX_PATH+1];
char *name = rep->m3u8_name;
if (rep->m3u8_var_name) {
name = gf_file_basename(rep->m3u8_var_name);
}
if (mpd->force_llhls_mode==2) {
gf_strcpy(szSuffixName, name);
char *sep = gf_file_ext_start(szSuffixName);
if (sep) sep[0] = 0;
gf_strcat(szSuffixName, "_IF");
sep = gf_file_ext_start(name);
if (sep)
gf_strcat(szSuffixName, sep);
name = szSuffixName;
}
if (force_base_url && ((mpd->hls_abs_url==GF_DASH_ABS_URL_MASTER) || (mpd->hls_abs_url==GF_DASH_ABS_URL_BOTH)))
return gf_url_concatenate(force_base_url, name);
return gf_strdup(name);
}
GF_Err gf_mpd_write_m3u8_master_playlist(GF_MPD const * const mpd, FILE *out, const char* m3u8_name, GF_List *periods, GF_M3U8WriteMode mode)
{
u32 i, j, x, hls_version;
u32 var_idx;
GF_Err e;
GF_MPD_AdaptationSet *as;
GF_MPD_Representation *rep;
Bool use_range = GF_FALSE;
Bool use_intra_only = GF_FALSE;
Bool use_init = GF_FALSE;
Bool use_ind_segments = GF_TRUE;
Bool use_saes_crypto = GF_FALSE;
Bool is_fmp4 = GF_FALSE;
char *szVariantName;
char *m3u8_name_rad, *sep, *force_base_url=NULL;
u32 nb_video=0;
u32 nb_audio=0;
u32 nb_cc=0;
u32 nb_subs=0;
Bool has_muxed_comp = GF_FALSE;
Bool has_video = GF_FALSE;
Bool has_audio = GF_FALSE;
Bool has_cc = GF_FALSE;
Bool has_req = GF_FALSE;
GF_MPD_Period *period = gf_list_last(periods);
if (!m3u8_name || !period) return GF_BAD_PARAM;
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
if (gf_list_count(as->content_protection)) { /*use_crypt = GF_TRUE; */ }
if (as->starts_with_sap>2) use_ind_segments = GF_FALSE;
if (as->intra_only) use_intra_only = GF_TRUE;
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
GF_DASH_SegmentContext *sctx;
const char *init_seg;
if (!mpd->allow_empty_reps && (!rep->state_seg_list || !gf_list_count(rep->state_seg_list)) ) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] No segment state in representation, MPD cannot be translated to M3U8\n"));
return GF_BAD_PARAM;
}
sctx = gf_list_get(rep->state_seg_list, rep->tsb_first_entry);
if (sctx && !sctx->filename) use_range = GF_TRUE;
if (gf_list_count(rep->content_protection)) { /*use_crypt = GF_TRUE; */ }
init_seg = gf_mpd_m3u8_get_init_seg(period, as, rep);
if (init_seg) use_init = GF_TRUE;
if (rep->mime_type) {
if (strstr(rep->mime_type, "mp4")) is_fmp4 = GF_TRUE;
}
if (rep->streamtype==GF_STREAM_AUDIO) nb_audio++;
else if (rep->streamtype==GF_STREAM_TEXT) nb_subs++;
else if (rep->streamtype==GF_STREAM_VISUAL) nb_video++;
if (rep->crypto_type>=2) use_saes_crypto = GF_TRUE;
if (!has_cc) {
u32 k;
for (k=0; k<rep->nb_hls_master_tags; k++) {
if (strstr(rep->hls_master_tags[k], "CLOSED-CAPTIONS")) {
has_cc = GF_TRUE;
break;
}
}
}
// RFC 8216: EXT-X-VERSION of 12 or higher if it contains an attribute whose name starts with "REQ-"
for (x=0; x<gf_list_count(rep->m3u8_x_attributes); x++) {
GF_XMLAttribute *attr = gf_list_get(rep->m3u8_x_attributes, x);
if (!strncmp(attr->name, "REQ-", 4))
has_req = GF_TRUE;
}
}
}
//we by default use floating point durations
hls_version = 3;
if (use_range) hls_version = 4;
if (use_intra_only || use_saes_crypto) hls_version = 5;
if (is_fmp4 || use_init || has_cc) hls_version = 6;
if (has_req) hls_version = 12;
if (mode!=GF_M3U8_WRITE_CHILD) {
gf_fprintf(out, "#EXTM3U\n");
gf_fprintf(out, "#EXT-X-VERSION:%d\n", hls_version);
if (use_ind_segments)
gf_fprintf(out, "#EXT-X-INDEPENDENT-SEGMENTS\n");
for (i=0; i<mpd->nb_hls_ext_master; i++)
gf_fprintf(out, "%s\n", mpd->hls_ext_master[i]);
gf_fprintf(out, "\n");
}
if (!strncmp(m3u8_name, "gfio://", 7)) {
const char *fpath = gf_fileio_translate_url(m3u8_name);
if (!fpath) return GF_BAD_PARAM;
//use basename since this file will be created throught GF_FileIO factory (relative to the original gfio://)
m3u8_name_rad = gf_strdup(gf_file_basename(fpath));
} else {
m3u8_name_rad = gf_strdup(m3u8_name);
}
sep = strrchr(m3u8_name_rad, '.');
if (sep) sep[0] = 0;
szVariantName = gf_malloc(sizeof(char) * (100 + strlen(m3u8_name_rad)) );
if (mpd->hls_abs_url) {
GF_MPD_BaseURL *burl = gf_list_get(mpd->base_URLs, 0);
if (burl)
force_base_url = burl->URL;
}
//if live low lat, update parts dur
Double max_part_dur_session=0;
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
if (!as->use_hls_ll) continue;
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
u32 k=rep->tsb_first_entry;
//figure out max part duration for this version of the playlist
Double max_part_dur=0;
GF_DASH_SegmentContext *sctx;
while ( (sctx = (GF_DASH_SegmentContext *) gf_list_enum(rep->state_seg_list, &k))) {
u32 nseg;
//check max part dur for active ll segments, but use previously computed value
//this avoids changing the HOLD-BACK too often which throws error in apple tools/safari
Double dur = rep->hls_ll_part_dur;
if (!sctx->llhls_mode || sctx->llhls_done) continue;
for (nseg=0; nseg<sctx->nb_frags; nseg++) {
dur = sctx->frags[nseg].duration;
dur /= rep->timescale;
if (dur>max_part_dur)
max_part_dur = dur;
}
}
if (!max_part_dur) max_part_dur = as->hls_ll_target_frag_dur;
if (max_part_dur<rep->hls_ll_part_dur)
max_part_dur = rep->hls_ll_part_dur;
//default cdur in gpac
if (!max_part_dur) {
max_part_dur=1.0;
}
max_part_dur = ceil(max_part_dur*1000) / 1000.0;
rep->hls_ll_part_dur = max_part_dur;
if (max_part_dur_session < max_part_dur)
max_part_dur_session = max_part_dur;
}
}
//first pass, check if we have muxed components, or video or audio, generate playlists names
var_idx = 1;
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
if (as->content_component && gf_list_count(as->content_component)>1) {
has_muxed_comp = GF_TRUE;
}
if (as->max_width && as->max_height) {
has_video = GF_TRUE;
}
if (as->samplerate)
has_audio = GF_TRUE;
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
if (rep->mime_type) {
if (!strncmp(rep->mime_type, "video/", 6)) has_video = GF_TRUE;
else if (!strncmp(rep->mime_type, "audio/", 6)) has_audio = GF_TRUE;
}
if (!rep->m3u8_name) {
if (mpd->m3u8_use_repid) {
sprintf(szVariantName, "%s_%s.m3u8",m3u8_name_rad, rep->id);
} else {
sprintf(szVariantName, "%s_%d.m3u8",m3u8_name_rad, var_idx);
}
if (rep->m3u8_var_name) gf_free(rep->m3u8_var_name);
rep->m3u8_var_name = gf_strdup(szVariantName);
}
var_idx++;
}
}
//second pass, generate all subplaylists
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
if (!mpd->allow_empty_reps && (!rep->state_seg_list || !gf_list_count(rep->state_seg_list) )) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[M3U8] No segment state in representation, MPD cannot be translated to M3U8, ignoring representation\n"));
continue;
}
if (mode==GF_M3U8_WRITE_MASTER) break;
char *name = rep->m3u8_name;
if (!name) {
name = gf_file_basename(rep->m3u8_var_name);
}
//backtrack periods until non-discontinuity change or no segments within timeshift window
u32 m=gf_list_count(periods)-1;
u32 start_period_idx = m;
while (m>0 && !period->skip_serialize) {
GF_MPD_Period *prev_period = gf_list_get(periods, m-1);
// Find the representation
GF_MPD_Representation *prev_rep = NULL;
GF_MPD_AdaptationSet *prev_as;
u32 k=0;
while ( (prev_as = (GF_MPD_AdaptationSet *) gf_list_enum(prev_period->adaptation_sets, &k))) {
u32 l=0;
while ( (prev_rep = (GF_MPD_Representation *) gf_list_enum(prev_as->representations, &l))) {
if (prev_rep->discontinuity_id == rep->discontinuity_id)
break;
}
if (prev_rep) break;
}
// Couldn't find the same representation, continue with the current one
if (!prev_rep) break;
// Check if we still have segments in this representations
u32 segment_cnt = gf_list_count(prev_rep->state_seg_list);
if (segment_cnt == 0) break;
// It's safe to use this period
start_period_idx = --m;
}
//write all relevant periods
m=start_period_idx;
u32 period_count = gf_list_count(periods) - start_period_idx;
FILE* prev_file = NULL;
GF_MPD_Period *cur_period;
while ( (cur_period = (GF_MPD_Period *) gf_list_enum(periods, &m))) {
GF_MPD_Representation *cur_rep = NULL;
GF_MPD_AdaptationSet *cur_as = NULL;
u32 k=0;
while ( (cur_as = (GF_MPD_AdaptationSet *) gf_list_enum(cur_period->adaptation_sets, &k))) {
u32 l=0;
while ( (cur_rep = (GF_MPD_Representation *) gf_list_enum(cur_as->representations, &l))) {
if (cur_rep->discontinuity_id == rep->discontinuity_id)
break;
}
if (cur_rep) break;
}
if (!cur_as) continue;
if (!cur_rep) continue;
Bool last_period =(m==period_count) ? GF_TRUE : GF_FALSE;
e = gf_mpd_write_m3u8_playlist(mpd, cur_period, cur_as, cur_rep, name, hls_version, max_part_dur_session,
((mpd->hls_abs_url==GF_DASH_ABS_URL_VARIANT) || (mpd->hls_abs_url==GF_DASH_ABS_URL_BOTH)) ? force_base_url : NULL, m-1!=start_period_idx, last_period, prev_file);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[M3U8] IO error while opening m3u8 files\n"));
return GF_IO_ERR;
}
if (m-1==start_period_idx && period_count>1) {
//keep the file handle to append next periods
if (cur_rep->m3u8_var_file) {
rep->m3u8_var_file = cur_rep->m3u8_var_file;
prev_file = rep->m3u8_var_file;
cur_rep->m3u8_var_file = NULL;
}
}
}
}
}
//no muxed comp, no video, the audio is the main media we will list, force nb_audio=0 for gf_mpd_write_m3u8_playlist_tags
if (!has_video && !has_muxed_comp)
nb_audio = 0;
GF_List *all_primary_reps_done = NULL;
//third pass, generate master playlists with the right groups
i=0;
while ( (as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &i))) {
Bool is_video = GF_FALSE;
Bool is_audio = GF_FALSE;
Bool is_muxed_comp = GF_FALSE;
Bool is_primary = GF_TRUE;
u32 nb_alt_streams = nb_audio;
if (mode==GF_M3U8_WRITE_CHILD) break;
if (as->content_component && gf_list_count(as->content_component)>1) {
is_muxed_comp = GF_TRUE;
}
if (as->max_width && as->max_height) {
is_video = GF_TRUE;
}
if (as->samplerate) {
is_audio = GF_TRUE;
}
//check if we have audio or video
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
if (all_primary_reps_done && (gf_list_find(all_primary_reps_done, rep)>=0) )
continue;
if (rep->mime_type) {
if (!strncmp(rep->mime_type, "video/", 6)) is_video = GF_TRUE;
else if (!strncmp(rep->mime_type, "audio/", 6)) is_audio = GF_TRUE;
}
if (rep->width && rep->height) is_video = GF_TRUE;
if (rep->samplerate) is_audio = GF_TRUE;
}
//figure out if this is the primary media or an associated media with the content
if (has_muxed_comp) {
if (!is_muxed_comp) is_primary = GF_FALSE;
} else {
if (mpd->hls_audio_primary && has_audio && has_video) {
is_primary = is_video ? GF_FALSE : GF_TRUE;
nb_alt_streams = nb_video;
} else if (has_video) {
if (!is_video) is_primary = GF_FALSE;
} else if (has_audio) {
if (!is_audio) is_primary = GF_FALSE;
}
}
j=0;
while ( (rep = (GF_MPD_Representation *) gf_list_enum(as->representations, &j))) {
if (!mpd->allow_empty_reps && (!rep->state_seg_list || !gf_list_count(rep->state_seg_list)) ) {
continue;
}
if (all_primary_reps_done && (gf_list_find(all_primary_reps_done, rep)>=0) )
continue;
char *name = get_rep_variant_filename(mpd, rep, force_base_url);
if (!name) continue;
//if this is the primary media type and we have a groupID, check all reps in ALL sets with the same ID
//and generate #EXT-X-MEDIA:TYPE
const char *prim_group_id = NULL;
GF_List *all_reps_in_group = NULL;
if (is_primary && rep->groupID) {
u32 l, k=0;
GF_MPD_Representation *a_rep;
all_reps_in_group = gf_list_new();
GF_MPD_AdaptationSet *an_as;
while ( (an_as = (GF_MPD_AdaptationSet *) gf_list_enum(period->adaptation_sets, &k))) {
l=0;
while ( (a_rep = (GF_MPD_Representation *) gf_list_enum(an_as->representations, &l))) {
if (a_rep->groupID && !strcmp(a_rep->groupID, rep->groupID)) {
gf_list_add(all_reps_in_group, a_rep);
}
}
}
while (gf_list_count(all_reps_in_group)) {
a_rep = gf_list_pop_front(all_reps_in_group);
char *a_name = get_rep_variant_filename(mpd, a_rep, force_base_url);
if (!a_name) continue;
gf_mpd_write_m3u8_playlist_tags(as, i, a_rep, out, a_name, NULL, nb_alt_streams, nb_subs, nb_cc, 0, hls_version, NULL);
gf_fprintf(out, "\n");
gf_free(a_name);
if (!all_primary_reps_done) all_primary_reps_done = gf_list_new();
gf_list_add(all_primary_reps_done, a_rep);
//remember to add primary type in #EXT-X-STREAM-INF
prim_group_id = a_rep->groupID;
}
gf_list_del(all_reps_in_group);
all_reps_in_group = NULL;
}
gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, name, is_primary ? period : NULL, nb_alt_streams, nb_subs, nb_cc, 0, hls_version, prim_group_id);
gf_fprintf(out, "\n");
if (!is_primary && rep->hls_forced) {
gf_mpd_write_m3u8_playlist_tags(as, i, rep, out, name, period, 0, 0, 0, rep->hls_forced, hls_version, NULL);
gf_fprintf(out, "\n");
}
gf_free(name);
}
}
if (all_primary_reps_done) gf_list_del(all_primary_reps_done);
gf_free(m3u8_name_rad);
gf_free(szVariantName);
return GF_OK;
}
static Bool mpd_skip_serialization(GF_MPD const * const mpd)
{
u32 i = 0;
GF_MPD_Period *p;
while (( p = (GF_MPD_Period *) gf_list_enum(mpd->periods, &i))) {
if (!p->skip_serialize)
return GF_FALSE;
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[MPD] No serialized period in MPD. Don't serialize.\n"));
return GF_TRUE;
}
GF_Err gf_mpd_write(GF_MPD const * const mpd, FILE *out, Bool compact)
{
u32 i, count, child_idx;
s32 indent = compact ? GF_INT_MIN : 0;
GF_MPD_ProgramInfo *info;
char *text;
if (mpd_skip_serialization(mpd))
return GF_NOT_READY;
if (!mpd->xml_namespace) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] No namespace found while writing. Setting to default.\n"));
}
gf_fprintf(out, "<?xml version=\"1.0\"?>");
gf_mpd_lf(out, indent);
mpd_write_generation_comment(mpd, out);
if (mpd->index_mode) {
gf_fprintf(out, "<GHIX xmlns=\"urn:gpac:dash:index:2023\"");
gf_mpd_print_duration(out, "segmentDuration", mpd->segment_duration, GF_TRUE);
if (mpd->segment_template)
gf_fprintf(out, " template=\"%s\"", mpd->segment_template);
if (mpd->max_segment_duration)
gf_mpd_print_duration(out, "maxSegmentDuration", mpd->max_segment_duration, GF_TRUE);
if (mpd->media_presentation_duration)
gf_mpd_print_duration(out, "mediaPresentationDuration", mpd->media_presentation_duration, GF_TRUE);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
goto print_periods;
}
gf_xml_dump_string(out, "<MPD xmlns=\"", (mpd->xml_namespace ? mpd->xml_namespace : "urn:mpeg:dash:schema:mpd:2011"), "\"");
if (mpd->write_context || mpd->use_gpac_ext) {
gf_fprintf(out, " xmlns:gpac=\"urn:gpac:filters:dasher:2018\"" );
}
if (mpd->ID)
gf_xml_dump_string(out, " id=\"", mpd->ID, "\"");
if (mpd->min_buffer_time)
gf_mpd_print_duration(out, "minBufferTime", mpd->min_buffer_time, GF_FALSE);
gf_fprintf(out," type=\"%s\"",(mpd->type == GF_MPD_TYPE_STATIC ? "static" : "dynamic"));
if (mpd->type == GF_MPD_TYPE_DYNAMIC)
gf_mpd_print_date(out, "availabilityStartTime", mpd->availabilityStartTime);
if (mpd->availabilityEndTime)
gf_mpd_print_date(out, "availabilityEndTime", mpd->availabilityEndTime);
if (mpd->publishTime && mpd->type != GF_MPD_TYPE_STATIC)
gf_mpd_print_date(out, "publishTime", mpd->publishTime);
if (mpd->media_presentation_duration)
gf_mpd_print_duration(out, "mediaPresentationDuration", mpd->media_presentation_duration, GF_TRUE);
if (mpd->minimum_update_period)
gf_mpd_print_duration(out, "minimumUpdatePeriod", mpd->minimum_update_period, GF_TRUE);
if ((s32) mpd->time_shift_buffer_depth > 0)
gf_mpd_print_duration(out, "timeShiftBufferDepth", mpd->time_shift_buffer_depth, GF_TRUE);
if (mpd->suggested_presentation_delay)
gf_mpd_print_duration(out, "suggestedPresentationDelay", mpd->suggested_presentation_delay, GF_TRUE);
if (mpd->max_segment_duration)
gf_mpd_print_duration(out, "maxSegmentDuration", mpd->max_segment_duration, GF_TRUE);
if (mpd->max_subsegment_duration)
gf_mpd_print_duration(out, "maxSubsegmentDuration", mpd->max_subsegment_duration, GF_TRUE);
if (mpd->profiles) {
gf_xml_dump_string(out, " profiles=\"", mpd->profiles, "\"");
}
gf_mpd_extensible_print_attr(out, mpd->x_attributes);
if (mpd->write_context) {
if (mpd->gpac_init_ntp_ms)
gf_fprintf(out," gpac:init_gen_time=\""LLU"\"", mpd->gpac_init_ntp_ms);
if (mpd->gpac_next_ntp_ms)
gf_fprintf(out," gpac:next_gen_time=\""LLU"\"", mpd->gpac_next_ntp_ms);
if (mpd->gpac_mpd_time)
gf_fprintf(out," gpac:mpd_time=\""LLU"\"", mpd->gpac_mpd_time);
}
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
child_idx = 0;
i=0;
while ((info = (GF_MPD_ProgramInfo *)gf_list_enum(mpd->program_infos, &i))) {
u32 sub_child_idx=0;
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<ProgramInformation");
mpd_print_lang(out, info->lang, NULL);
if (info->more_info_url) {
gf_xml_dump_string(out, " moreInformationURL=\"", info->more_info_url, "\"");
}
gf_mpd_extensible_print_attr(out, info->x_attributes);
gf_fprintf(out, ">");
gf_mpd_lf(out, indent);
if (info->title) {
gf_mpd_extensible_print_nodes(out, info->x_children, indent+1, &sub_child_idx, GF_FALSE);
gf_mpd_nl(out, indent+2);
gf_xml_dump_string(out, "<Title>", info->title, "</Title>");
gf_mpd_lf(out, indent);
}
if (info->source) {
gf_mpd_extensible_print_nodes(out, info->x_children, indent+1, &sub_child_idx, GF_FALSE);
gf_mpd_nl(out, indent+2);
gf_xml_dump_string(out, "<Source>", info->source, "</Source>");
gf_mpd_lf(out, indent);
}
if (info->copyright) {
gf_mpd_extensible_print_nodes(out, info->x_children, indent+1, &sub_child_idx, GF_FALSE);
gf_mpd_nl(out, indent+2);
gf_xml_dump_string(out, "<Copyright>", info->copyright, "</Copyright>");
gf_mpd_lf(out, indent);
}
gf_mpd_extensible_print_nodes(out, info->x_children, indent+1, &sub_child_idx, GF_TRUE);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "</ProgramInformation>");
gf_mpd_lf(out, indent);
}
gf_mpd_print_base_urls(out, mpd->base_URLs, indent+1);
gf_mpd_lf(out, indent);
i=0;
while ((text = (char *)gf_list_enum(mpd->locations, &i))) {
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_nl(out, indent+1);
gf_xml_dump_string(out, "<Location>", text, "</Location>");
gf_mpd_lf(out, indent);
}
if (mpd->inject_service_desc) {
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "<ServiceDescription id=\"0\">");
gf_mpd_lf(out, indent);
gf_mpd_nl(out, indent+2);
gf_fprintf(out, "<Latency max=\"6000\" min=\"2000\" referenceId=\"0\" target=\"4000\"/>");
gf_mpd_lf(out, indent);
gf_mpd_nl(out, indent+2);
gf_fprintf(out, "<PlaybackRate max=\"1.04\" min=\"0.96\"/>");
gf_mpd_lf(out, indent);
gf_mpd_nl(out, indent+1);
gf_fprintf(out, "</ServiceDescription>");
gf_mpd_lf(out, indent);
}
/*
i=0;
while ((text = (char *)gf_list_enum(mpd->metrics, &i))) {
}
*/
print_periods:
count = gf_list_count(mpd->periods);
for (i=0; i<count; i++) {
Bool is_dynamic;
GF_MPD_Period *period = (GF_MPD_Period *)gf_list_get(mpd->periods, i);
//do not serialize this period and following ones as we are missing segment timeline entry
if (period->skip_serialize) break;
is_dynamic = (mpd->type==GF_MPD_TYPE_DYNAMIC) ? GF_TRUE : GF_FALSE;
//hack for backward compat with old arch, forces print period@start if 0
if (!i && count>1 && mpd->was_dynamic) is_dynamic = GF_TRUE;
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_period(period, is_dynamic, out, mpd->write_context, indent+1);
}
if (gf_list_count(mpd->utc_timings)) {
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_descriptors(out, mpd->utc_timings, "UTCTiming", indent+1, mpd->x_children, &child_idx);
}
if (gf_list_count(mpd->essential_properties)) {
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_descriptors(out, mpd->essential_properties, "EssentialProperty", indent+1, mpd->x_children, &child_idx);
}
if (gf_list_count(mpd->supplemental_properties)) {
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_FALSE);
gf_mpd_print_descriptors(out, mpd->supplemental_properties, "SupplementalProperty", indent+1, mpd->x_children, &child_idx);
}
gf_mpd_extensible_print_nodes(out, mpd->x_children, indent, &child_idx, GF_TRUE);
if (mpd->index_mode)
gf_fprintf(out, "</GHIX>");
else
gf_fprintf(out, "</MPD>");
return GF_OK;
}
GF_EXPORT
GF_Err gf_mpd_write_file(GF_MPD const * const mpd, const char *file_name)
{
GF_Err e;
FILE *out;
if (mpd_skip_serialization(mpd))
return GF_OK;
if (!strcmp(file_name, "std")) out = stdout;
else {
out = gf_fopen(file_name, "wb");
if (!out) return GF_IO_ERR;
}
e = gf_mpd_write(mpd, out, GF_FALSE);
gf_fclose(out);
return e;
}
GF_EXPORT
u32 gf_mpd_get_base_url_count(GF_MPD *mpd, GF_MPD_Period *period, GF_MPD_AdaptationSet *set, GF_MPD_Representation *rep)
{
u32 base_url_count, i;
base_url_count = 1;
i = gf_list_count(mpd->base_URLs);
if (i>1) base_url_count *= i;
i = gf_list_count(period->base_URLs);
if (i>1) base_url_count *= i;
i = gf_list_count(set->base_URLs);
if (i>1) base_url_count *= i;
i = gf_list_count(rep->base_URLs);
if (i>1) base_url_count *= i;
return base_url_count;
}
static char *gf_mpd_get_base_url(GF_List *baseURLs, char *parent_url, u32 *base_url_index)
{
GF_MPD_BaseURL *url_child;
u32 idx = 0;
u32 nb_base = gf_list_count(baseURLs);
if (nb_base>1) {
u32 nb_bits = gf_get_bit_size(nb_base-1);
u32 mask=0;
u32 i=0;
while (1) {
mask |= 1;
i++;
if (i>=nb_bits) break;
mask <<= 1;
}
idx = (*base_url_index) & mask;
(*base_url_index) = (*base_url_index) >> nb_bits;
} else {
idx = 0;
}
url_child = gf_list_get(baseURLs, idx);
if (url_child) {
char *t_url = gf_url_concatenate(parent_url, url_child->redirection ? url_child->redirection : url_child->URL);
gf_free(parent_url);
parent_url = t_url;
}
return parent_url;
}
Bool gf_mpd_check_print_format(const char *print_fmt)
{
if (!print_fmt) return GF_FALSE;
if (!print_fmt[0]) return GF_FALSE;
char *fmt = strchr(print_fmt, '%');
if (!fmt) return GF_FALSE;
fmt++;
u32 width = 0, nb_digits = 0;
while (fmt[0] && (fmt[0] >= '0') && (fmt[0] <= '9')) {
nb_digits++;
width = width*10 + (fmt[0] - '0');
if (nb_digits > 6) return GF_FALSE; // will catch %000..005d overflows
if (width > 32) return GF_FALSE; // will catch %500d overflows
fmt++;
}
if (strchr("duioxX", fmt[0]) == NULL)
return GF_FALSE;
if (fmt[1])
return GF_FALSE;
return GF_TRUE;
}
GF_EXPORT
GF_Err gf_mpd_resolve_url(GF_MPD *mpd, GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, const char *mpd_url, u32 base_url_index, GF_MPD_URLResolveType resolve_type, u32 item_index, u32 nb_segments_removed, char **out_url, u64 *out_range_start, u64 *out_range_end, u64 *segment_duration_in_ms, Bool *is_in_base_url, char **out_key_url, bin128 *out_key_iv, u32 *out_start_number, s32 subseg_index)
{
GF_MPD_SegmentTimeline *timeline = NULL;
u32 start_number = 1;
u32 timescale=0;
u64 duration=0;
u64 pto=0;
char *url;
char *url_to_solve, *solved_template, *first_sep, *media_url;
char *init_template, *index_template;
if (!out_range_start || !out_range_end || !out_url || !mpd_url || !segment_duration_in_ms)
return GF_BAD_PARAM;
*out_range_start = *out_range_end = 0;
*out_url = NULL;
if (out_key_url) *out_key_url = NULL;
/*resolve base URLs from document base (download location) to representation (media)*/
url = gf_strdup(mpd_url);
url = gf_mpd_get_base_url(mpd->base_URLs, url, &base_url_index);
url = gf_mpd_get_base_url(period->base_URLs, url, &base_url_index);
url = gf_mpd_get_base_url(set->base_URLs, url, &base_url_index);
url = gf_mpd_get_base_url(rep->base_URLs, url, &base_url_index);
gf_assert(url);
if (resolve_type == GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE_NO_BASE) {
resolve_type = GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE;
url[0] = 0;
}
/*single URL*/
if (!rep->segment_list && !set->segment_list && !period->segment_list && !rep->segment_template && !set->segment_template && !period->segment_template) {
GF_MPD_URL *res_url;
GF_MPD_SegmentBase *base_seg = NULL;
if (item_index > 0) {
gf_free(url);
return GF_EOS;
}
switch (resolve_type) {
case GF_MPD_RESOLVE_URL_MEDIA:
case GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE:
case GF_MPD_RESOLVE_URL_MEDIA_NOSTART:
if (!url)
return GF_NON_COMPLIANT_BITSTREAM;
*out_url = url;
return GF_OK;
case GF_MPD_RESOLVE_URL_INIT:
case GF_MPD_RESOLVE_URL_INDEX:
res_url = NULL;
base_seg = rep->segment_base;
if (!base_seg) base_seg = set->segment_base;
if (!base_seg) base_seg = period->segment_base;
if (base_seg) {
if (resolve_type == GF_MPD_RESOLVE_URL_INDEX) {
res_url = base_seg->representation_index;
} else {
res_url = base_seg->initialization_segment;
}
}
if (is_in_base_url) *is_in_base_url = 0;
/*no initialization segment / index, use base URL*/
if (res_url && res_url->sourceURL) {
if (res_url->is_resolved) {
*out_url = gf_strdup(res_url->sourceURL);
} else {
*out_url = gf_url_concatenate(url, res_url->sourceURL);
}
gf_free(url);
} else {
*out_url = url;
if (is_in_base_url) *is_in_base_url = 1;
}
if (res_url && res_url->byte_range) {
*out_range_start = res_url->byte_range->start_range;
*out_range_end = res_url->byte_range->end_range;
} else if (base_seg && base_seg->index_range && (resolve_type == GF_MPD_RESOLVE_URL_INDEX)) {
*out_range_start = base_seg->index_range->start_range;
*out_range_end = base_seg->index_range->end_range;
}
return GF_OK;
default:
break;
}
gf_free(url);
return GF_BAD_PARAM;
}
/*segmentList*/
if (rep->segment_list || set->segment_list || period->segment_list) {
GF_MPD_URL *init_url, *index_url;
GF_MPD_SegmentURL *segment;
GF_List *segments = NULL;
u32 segment_count;
init_url = index_url = NULL;
/*apply inheritance of attributes, lowest level having preceedence*/
if (period->segment_list) {
if (period->segment_list->initialization_segment) init_url = period->segment_list->initialization_segment;
if (period->segment_list->segment_URLs) segments = period->segment_list->segment_URLs;
if (!timescale && period->segment_list->timescale) timescale = period->segment_list->timescale;
}
if (set->segment_list) {
if (set->segment_list->initialization_segment) init_url = set->segment_list->initialization_segment;
if (set->segment_list->segment_URLs) segments = set->segment_list->segment_URLs;
if (!timescale && set->segment_list->timescale) timescale = set->segment_list->timescale;
}
if (rep->segment_list) {
if (rep->segment_list->initialization_segment) init_url = rep->segment_list->initialization_segment;
if (rep->segment_list->segment_URLs) segments = rep->segment_list->segment_URLs;
if (!timescale && rep->segment_list->timescale) timescale = rep->segment_list->timescale;
}
segment_count = gf_list_count(segments);
switch (resolve_type) {
case GF_MPD_RESOLVE_URL_INIT:
if (init_url) {
if (init_url->sourceURL) {
if (init_url->is_resolved) {
*out_url = gf_strdup(init_url->sourceURL);
} else {
*out_url = gf_url_concatenate(url, init_url->sourceURL);
}
gf_free(url);
} else {
*out_url = url;
}
if (init_url->byte_range) {
*out_range_start = init_url->byte_range->start_range;
*out_range_end = init_url->byte_range->end_range;
}
} else {
gf_free(url);
}
return GF_OK;
case GF_MPD_RESOLVE_URL_MEDIA:
case GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE:
case GF_MPD_RESOLVE_URL_MEDIA_NOSTART:
if (!url) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Media URL is not set in segment list\n"));
return GF_SERVICE_ERROR;
}
if ((item_index >= segment_count) || ((s32) item_index < 0)) {
gf_free(url);
return GF_EOS;
}
*out_url = url;
segment = gf_list_get(segments, item_index);
if (segment->key_url && out_key_url) {
//if ':', key URL is absolute or urn:
if (strlen(segment->key_url) > 2 && strchr(segment->key_url+2, ':'))
*out_key_url = gf_strdup(segment->key_url);
//otherwise key url is relative to url (base url)
else
*out_key_url = gf_url_concatenate(url, segment->key_url);
if (out_key_iv)
memcpy((*out_key_iv), segment->key_iv, sizeof(bin128) );
}
if (segment->media) {
*out_url = gf_url_concatenate(url, segment->media);
gf_free(url);
}
if (segment->media_range) {
*out_range_start = segment->media_range->start_range;
*out_range_end = segment->media_range->end_range;
}
if (segment->duration) {
*segment_duration_in_ms = (u32) ((Double) (segment->duration) * 1000.0 / timescale);
}
return GF_OK;
case GF_MPD_RESOLVE_URL_INDEX:
if (item_index >= segment_count) {
gf_free(url);
return GF_EOS;
}
*out_url = url;
segment = gf_list_get(segments, item_index);
if (segment->index) {
*out_url = gf_url_concatenate(url, segment->index);
gf_free(url);
}
if (segment->index_range) {
*out_range_start = segment->index_range->start_range;
*out_range_end = segment->index_range->end_range;
}
return GF_OK;
default:
break;
}
gf_free(url);
return GF_BAD_PARAM;
}
/*segmentTemplate*/
media_url = init_template = index_template = NULL;
/*apply inheritance of attributes, lowest level having preceedence*/
if (period->segment_template) {
if (period->segment_template->initialization) init_template = period->segment_template->initialization;
if (period->segment_template->index) index_template = period->segment_template->index;
if (period->segment_template->media) media_url = period->segment_template->media;
if (period->segment_template->start_number != (u32) -1) start_number = period->segment_template->start_number;
if (period->segment_template->segment_timeline) timeline = period->segment_template->segment_timeline;
if (!timescale && period->segment_template->timescale) timescale = period->segment_template->timescale;
if (!duration && period->segment_template->duration) duration = period->segment_template->duration;
pto = period->segment_template->presentation_time_offset;
}
if (set->segment_template) {
if (set->segment_template->initialization) init_template = set->segment_template->initialization;
if (set->segment_template->index) index_template = set->segment_template->index;
if (set->segment_template->media) media_url = set->segment_template->media;
if (set->segment_template->start_number != (u32) -1) start_number = set->segment_template->start_number;
if (set->segment_template->segment_timeline) timeline = set->segment_template->segment_timeline;
if (!timescale && set->segment_template->timescale) timescale = set->segment_template->timescale;
if (!duration && set->segment_template->duration) duration = set->segment_template->duration;
if (set->segment_template->presentation_time_offset) pto = set->segment_template->presentation_time_offset;
}
if (rep->segment_template) {
if (rep->segment_template->initialization) init_template = rep->segment_template->initialization;
if (rep->segment_template->index) index_template = rep->segment_template->index;
if (rep->segment_template->media) media_url = rep->segment_template->media;
if (rep->segment_template->start_number != (u32) -1) start_number = rep->segment_template->start_number;
if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
if (!timescale && rep->segment_template->timescale) timescale = rep->segment_template->timescale;
if (!duration && rep->segment_template->duration) duration = rep->segment_template->duration;
if (rep->segment_template->presentation_time_offset) pto = rep->segment_template->presentation_time_offset;
}
/*return segment duration in all cases*/
{
u64 out_duration;
u32 out_timescale;
gf_mpd_resolve_segment_duration(rep, set, period, &out_duration, &out_timescale, NULL, NULL);
*segment_duration_in_ms = (u64)((out_duration * 1000.0) / out_timescale);
}
/*offset the start_number with the number of discarded segments (no longer in our lists)*/
start_number += nb_segments_removed;
if (!media_url) {
GF_MPD_BaseURL *base = gf_list_get(rep->base_URLs, 0);
if (!base) {
gf_free(url);
return GF_BAD_PARAM;
}
media_url = base->URL;
}
url_to_solve = NULL;
switch (resolve_type) {
case GF_MPD_RESOLVE_URL_INIT:
url_to_solve = init_template;
break;
case GF_MPD_RESOLVE_URL_MEDIA:
case GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE:
case GF_MPD_RESOLVE_URL_MEDIA_NOSTART:
url_to_solve = media_url;
break;
case GF_MPD_RESOLVE_URL_INDEX:
url_to_solve = index_template;
break;
default:
gf_free(url);
return GF_BAD_PARAM;
}
if (!url_to_solve) {
gf_free(url);
return GF_OK;
}
/*let's solve the template*/
u32 solved_bufsize = (u32) (sizeof(char)*(strlen(url_to_solve) + (rep->id ? strlen(rep->id) : 0)) * 2);
solved_template = gf_malloc(solved_bufsize);
if (!solved_template) return GF_OUT_OF_MEM;
solved_template[solved_bufsize-1] = 0;
solved_template[0] = 0;
gf_strlcpy(solved_template, url_to_solve, solved_bufsize);
first_sep = strchr(solved_template, '$');
if (first_sep) first_sep[0] = 0;
first_sep = strchr(url_to_solve, '$');
while (first_sep) {
char szPrintFormat[50];
char szFormat[100];
char *format_tag;
char *second_sep = strchr(first_sep+1, '$');
if (!second_sep) {
gf_free(url);
gf_free(solved_template);
return GF_NON_COMPLIANT_BITSTREAM;
}
second_sep[0] = 0;
format_tag = strchr(first_sep+1, '%');
if (format_tag) {
if (!gf_mpd_check_print_format(format_tag)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Invalid format %s on representation - cannot solve template\n\n", format_tag));
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_NON_COMPLIANT_BITSTREAM;
}
gf_strcpy(szPrintFormat, format_tag);
format_tag[0] = 0;
} else {
gf_strcpy(szPrintFormat, "%d");
}
/* identifier is $$ -> replace by $*/
if (!strlen(first_sep+1)) {
gf_strlcat(solved_template, "$", solved_bufsize);
}
else if (!strcmp(first_sep+1, "RepresentationID")) {
if (rep->id) {
gf_strlcat(solved_template, rep->id, solved_bufsize);
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Missing ID on representation - cannot solve template\n\n"));
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_NON_COMPLIANT_BITSTREAM;
}
}
else if (!strcmp(first_sep+1, "Number")) {
if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) {
gf_strlcat(solved_template, "$Number", solved_bufsize);
if (format_tag)
gf_strlcat(solved_template, szPrintFormat, solved_bufsize);
gf_strlcat(solved_template, "$", solved_bufsize);
} else if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_NOSTART) {
if (out_start_number) *out_start_number = 0;
sprintf(szFormat, szPrintFormat, item_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
} else {
if (out_start_number) *out_start_number = start_number;
sprintf(szFormat, szPrintFormat, start_number + item_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
/*check start time is in period (start time is ~seg_duration * item_index, since startNumber seg has start time = 0 in the period
DO NOT do this if timeline is used, as the segment duration can likely vary a lot
*/
if (period->duration && !timeline
&& (item_index * (*segment_duration_in_ms) > period->duration)) {
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_EOS;
}
}
else if (!strcmp(first_sep+1, "SubNumber")) {
if ((resolve_type!=GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) && (subseg_index<0)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Invalid SubNumber template identifier with no SSR subsegment index\n"));
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_NON_COMPLIANT_BITSTREAM;
}
if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) {
gf_strlcat(solved_template, "$SubNumber", solved_bufsize);
if (format_tag)
gf_strlcat(solved_template, szPrintFormat, solved_bufsize);
gf_strlcat(solved_template, "$", solved_bufsize);
} else if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_NOSTART) {
sprintf(szFormat, szPrintFormat, subseg_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
} else {
sprintf(szFormat, szPrintFormat, subseg_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
}
else if (!strcmp(first_sep+1, "Index")) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Wrong template identifier Index detected - using Number instead\n\n"));
sprintf(szFormat, szPrintFormat, start_number + item_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
else if (!strcmp(first_sep+1, "Bandwidth")) {
sprintf(szFormat, szPrintFormat, rep->bandwidth);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
else if (!strcmp(first_sep+1, "Time")) {
if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_NOSTART) {
if (out_start_number) *out_start_number = 1;
sprintf(szFormat, szPrintFormat, item_index);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
else if (resolve_type==GF_MPD_RESOLVE_URL_MEDIA_TEMPLATE) {
gf_strlcat(solved_template, "$Time$", solved_bufsize);
} else if (timeline) {
/*uses segment timeline*/
u32 k, nb_seg, cur_idx, nb_repeat;
u64 time, start_time;
nb_seg = gf_list_count(timeline->entries);
cur_idx = 0;
start_time=0;
for (k=0; k<nb_seg; k++) {
GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, k);
if (item_index>cur_idx+ent->repeat_count) {
cur_idx += 1 + ent->repeat_count;
if (ent->start_time)
start_time = ent->start_time;
if (k<nb_seg-1) {
start_time += (u64) (ent->duration * (1 + ent->repeat_count));
continue;
} else {
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_EOS;
}
}
*segment_duration_in_ms = ent->duration;
*segment_duration_in_ms = (u32) ((Double) (*segment_duration_in_ms) * 1000.0 / timescale);
nb_repeat = item_index - cur_idx;
time = ent->start_time ? ent->start_time : start_time;
time += nb_repeat * ent->duration;
/*replace final 'd' with LLD (%lld or I64d)*/
szPrintFormat[strlen(szPrintFormat)-1] = 0;
gf_strcat(szPrintFormat, &LLU[1]);
sprintf(szFormat, szPrintFormat, time);
gf_strlcat(solved_template, szFormat, solved_bufsize);
break;
}
} else if (duration) {
u64 time = item_index * duration + pto;
szPrintFormat[strlen(szPrintFormat)-1] = 0;
gf_strlcat(szPrintFormat, &LLD[1], solved_bufsize);
sprintf(szFormat, szPrintFormat, time);
gf_strlcat(solved_template, szFormat, solved_bufsize);
}
}
else {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Unknown template identifier %s - disabling rep\n\n", first_sep+1));
*out_url = NULL;
gf_free(url);
gf_free(solved_template);
second_sep[0] = '$';
return GF_NON_COMPLIANT_BITSTREAM;
}
if (format_tag) format_tag[0] = '%';
second_sep[0] = '$';
/*look for next keyword - copy over remaining text if any*/
first_sep = strchr(second_sep+1, '$');
if (first_sep) first_sep[0] = 0;
if (strlen(second_sep+1)) {
gf_strlcat(solved_template, second_sep+1, solved_bufsize);
}
if (first_sep) first_sep[0] = '$';
}
*out_url = gf_url_concatenate(url, solved_template);
gf_free(url);
gf_free(solved_template);
return GF_OK;
}
GF_EXPORT
Double gf_mpd_get_duration(GF_MPD *mpd)
{
Double duration;
duration = (Double)mpd->media_presentation_duration;
if (!duration) {
u32 i;
for (i = 0; i<gf_list_count(mpd->periods); i++) {
GF_MPD_Period *period = gf_list_get(mpd->periods, i);
duration += (Double)period->duration;
}
}
return duration / 1000.0;
}
GF_EXPORT
void gf_mpd_resolve_segment_duration(GF_MPD_Representation *rep, GF_MPD_AdaptationSet *set, GF_MPD_Period *period, u64 *out_duration, u32 *out_timescale, u64 *out_pts_offset, GF_MPD_SegmentTimeline **out_segment_timeline)
{
u32 timescale = 0;
u64 pts_offset = 0;
GF_MPD_SegmentTimeline *segment_timeline;
GF_MPD_MultipleSegmentBase *mbase_rep, *mbase_set, *mbase_period;
if (!period) return;
if (out_segment_timeline) *out_segment_timeline = NULL;
if (out_pts_offset) *out_pts_offset = 0;
/*single media segment - duration is not known unless indicated in period*/
if (rep->segment_base || set->segment_base || period->segment_base) {
if (period->duration) {
*out_duration = period->duration;
timescale = 1000;
} else {
*out_duration = 0;
timescale = 0;
}
if (rep->segment_base && rep->segment_base->presentation_time_offset) pts_offset = rep->segment_base->presentation_time_offset;
if (rep->segment_base && rep->segment_base->timescale) timescale = rep->segment_base->timescale;
if (!pts_offset && set->segment_base && set->segment_base->presentation_time_offset) pts_offset = set->segment_base->presentation_time_offset;
if (!timescale && set->segment_base && set->segment_base->timescale) timescale = set->segment_base->timescale;
if (!pts_offset && period->segment_base && period->segment_base->presentation_time_offset) pts_offset = period->segment_base->presentation_time_offset;
if (!timescale && period->segment_base && period->segment_base->timescale) timescale = period->segment_base->timescale;
if (out_pts_offset) *out_pts_offset = pts_offset;
*out_timescale = timescale ? timescale : 1;
return;
}
/*we have a segment template list or template*/
mbase_rep = rep->segment_list ? (GF_MPD_MultipleSegmentBase *) rep->segment_list : (GF_MPD_MultipleSegmentBase *) rep->segment_template;
mbase_set = set->segment_list ? (GF_MPD_MultipleSegmentBase *)set->segment_list : (GF_MPD_MultipleSegmentBase *)set->segment_template;
mbase_period = period->segment_list ? (GF_MPD_MultipleSegmentBase *)period->segment_list : (GF_MPD_MultipleSegmentBase *)period->segment_template;
segment_timeline = NULL;
if (mbase_period) segment_timeline = mbase_period->segment_timeline;
if (mbase_set && mbase_set->segment_timeline) segment_timeline = mbase_set->segment_timeline;
if (mbase_rep && mbase_rep->segment_timeline) segment_timeline = mbase_rep->segment_timeline;
timescale = mbase_rep ? mbase_rep->timescale : 0;
if (!timescale && mbase_set && mbase_set->timescale) timescale = mbase_set->timescale;
if (!timescale && mbase_period && mbase_period->timescale) timescale = mbase_period->timescale;
if (!timescale) timescale = 1;
*out_timescale = timescale;
if (out_pts_offset) {
pts_offset = mbase_rep ? mbase_rep->presentation_time_offset : 0;
if (!pts_offset && mbase_set && mbase_set->presentation_time_offset) pts_offset = mbase_set->presentation_time_offset;
if (!pts_offset && mbase_period && mbase_period->presentation_time_offset) pts_offset = mbase_period->presentation_time_offset;
*out_pts_offset = pts_offset;
}
if (mbase_rep && mbase_rep->duration) *out_duration = mbase_rep->duration;
else if (mbase_set && mbase_set->duration) *out_duration = mbase_set->duration;
else if (mbase_period && mbase_period->duration) *out_duration = mbase_period->duration;
if (out_segment_timeline) *out_segment_timeline = segment_timeline;
/*for SegmentTimeline, just pick the first one as an indication (exact timeline solving is not done here)*/
if (segment_timeline) {
GF_MPD_SegmentTimelineEntry *ent = gf_list_get(segment_timeline->entries, 0);
if (ent) *out_duration = ent->duration;
}
else if (rep->segment_list) {
GF_MPD_SegmentURL *url = gf_list_get(rep->segment_list->segment_URLs, 0);
if (url && url->duration) *out_duration = url->duration;
}
}
static u64 gf_mpd_segment_timeline_start(GF_MPD_SegmentTimeline *timeline, u32 segment_index, u64 *segment_duration)
{
u64 start_time = 0;
u32 i, idx, k;
idx = 0;
for (i = 0; i<gf_list_count(timeline->entries); i++) {
GF_MPD_SegmentTimelineEntry *ent = gf_list_get(timeline->entries, i);
if (ent->start_time) start_time = ent->start_time;
for (k = 0; k<ent->repeat_count + 1; k++) {
if (idx == segment_index) {
if (segment_duration)
*segment_duration = ent->duration;
return start_time;
}
idx++;
start_time += ent->duration;
}
}
return start_time;
}
GF_EXPORT
GF_Err gf_mpd_get_segment_start_time_with_timescale(s32 in_segment_index,
GF_MPD_Period const * const period, GF_MPD_AdaptationSet const * const set, GF_MPD_Representation const * const rep,
u64 *out_segment_start_time, u64 *out_opt_segment_duration, u32 *out_opt_scale)
{
u64 duration = 0, start_time = 0, pto = 0;
u32 timescale = 0;
GF_List *seglist = NULL;
GF_MPD_SegmentTimeline *timeline = NULL;
if (!out_segment_start_time || !period || !set || !rep) {
return GF_BAD_PARAM;
}
/*single segment: return nothing*/
if (rep->segment_base || set->segment_base || period->segment_base) {
*out_segment_start_time = start_time;
return GF_OK;
}
if (rep->segment_list || set->segment_list || period->segment_list) {
if (period->segment_list) {
if (period->segment_list->duration) duration = period->segment_list->duration;
if (period->segment_list->timescale) timescale = period->segment_list->timescale;
if (period->segment_list->segment_timeline) timeline = period->segment_list->segment_timeline;
if (period->segment_list->presentation_time_offset) pto = period->segment_list->presentation_time_offset;
if (gf_list_count(period->segment_list->segment_URLs)) seglist = period->segment_list->segment_URLs;
}
if (set->segment_list) {
if (set->segment_list->duration) duration = set->segment_list->duration;
if (set->segment_list->timescale) timescale = set->segment_list->timescale;
if (set->segment_list->segment_timeline) timeline = set->segment_list->segment_timeline;
if (set->segment_list->presentation_time_offset) pto = set->segment_list->presentation_time_offset;
if (gf_list_count(set->segment_list->segment_URLs)) seglist = set->segment_list->segment_URLs;
}
if (rep->segment_list) {
if (rep->segment_list->duration) duration = rep->segment_list->duration;
if (rep->segment_list->timescale) timescale = rep->segment_list->timescale;
if (rep->segment_list->presentation_time_offset) pto = rep->segment_list->presentation_time_offset;
if (gf_list_count(rep->segment_list->segment_URLs)) seglist = rep->segment_list->segment_URLs;
}
if (!timescale) timescale = 1;
if (timeline) {
start_time = gf_mpd_segment_timeline_start(timeline, in_segment_index, &duration);
start_time -= pto;
}
else if (seglist && (in_segment_index >= 0)) {
u32 i;
start_time = 0;
for (i = 0; i <= (u32)in_segment_index; i++) {
GF_MPD_SegmentURL *url = gf_list_get(seglist, i);
if (!url) break;
duration = url->duration;
if (i < (u32)in_segment_index)
start_time += url->duration;
}
}
else if (duration) {
start_time = in_segment_index * duration;
}
if (out_opt_segment_duration) *out_opt_segment_duration = duration;
if (out_opt_scale) *out_opt_scale = timescale;
*out_segment_start_time = start_time;
return GF_OK;
}
if (!rep->segment_template && !set->segment_template && !period->segment_template) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Representation without any SegmentBase, SegmentList or SegmentTemplate (non compliant). Assuming default SegmentBase\n"));
*out_segment_start_time = start_time;
return GF_NON_COMPLIANT_BITSTREAM;
}
if (period->segment_template) {
if (period->segment_template->duration) duration = period->segment_template->duration;
if (period->segment_template->timescale) timescale = period->segment_template->timescale;
if (period->segment_template->segment_timeline) timeline = period->segment_template->segment_timeline;
if (period->segment_template->presentation_time_offset) pto = period->segment_template->presentation_time_offset;
}
if (set->segment_template) {
if (set->segment_template->duration) duration = set->segment_template->duration;
if (set->segment_template->timescale) timescale = set->segment_template->timescale;
if (set->segment_template->segment_timeline) timeline = set->segment_template->segment_timeline;
if (set->segment_template->presentation_time_offset) pto = set->segment_template->presentation_time_offset;
}
if (rep->segment_template) {
if (rep->segment_template->duration) duration = rep->segment_template->duration;
if (rep->segment_template->timescale) timescale = rep->segment_template->timescale;
if (rep->segment_template->segment_timeline) timeline = rep->segment_template->segment_timeline;
if (rep->segment_template->presentation_time_offset) pto = rep->segment_template->presentation_time_offset;
}
if (!timescale) timescale = 1;
if (timeline) {
start_time = gf_mpd_segment_timeline_start(timeline, in_segment_index, &duration);
start_time -= pto;
}
else {
start_time = in_segment_index * duration;
}
if (out_opt_segment_duration) *out_opt_segment_duration = duration;
if (out_opt_scale) *out_opt_scale = timescale;
*out_segment_start_time = start_time;
return GF_OK;
}
#if 0 //unused
static GF_Err mpd_seek_periods(Double seek_time, GF_MPD const * const in_mpd, GF_MPD_Period **out_period)
{
Double start_time;
u32 i;
start_time = 0;
for (i=0; i<gf_list_count(in_mpd->periods); i++) {
GF_MPD_Period *period = gf_list_get(in_mpd->periods, i);
Double dur;
if (period->xlink_href) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Period contains XLINKs. Not supported.\n", period->xlink_href));
return GF_NOT_SUPPORTED;
}
dur = (Double)period->duration;
dur /= 1000;
if (seek_time >= start_time) {
if ((seek_time < start_time + dur)
|| (i+1 == gf_list_count(in_mpd->periods) && dur == 0.0)) {
*out_period = period;
break;
} else {
return GF_EOS;
}
}
start_time += dur;
}
return GF_OK;
}
#endif
GF_EXPORT
GF_Err gf_mpd_seek_in_period(Double seek_time, MPDSeekMode seek_mode,
GF_MPD_Period const * const in_period, GF_MPD_AdaptationSet const * const in_set, GF_MPD_Representation const * const in_rep,
u32 *out_segment_index, Double *out_opt_seek_time, Double *out_seg_dur)
{
Double seg_start = 0.0;
u32 segment_idx = 0;
if (!out_segment_index) {
return GF_BAD_PARAM;
}
while (1) {
Double segment_duration;
u32 timescale=1000;
u64 segment_duration_in_scale=0, seg_start_in_scale;
//TODO this could be further optimized by directly querying the index for this start time ...
GF_Err e = gf_mpd_get_segment_start_time_with_timescale(segment_idx, in_period, in_set, in_rep, &seg_start_in_scale, &segment_duration_in_scale, &timescale);
if (e<0)
return e;
segment_duration = segment_duration_in_scale / (Double)timescale;
if (out_seg_dur) *out_seg_dur = segment_duration;
if (seek_mode == MPD_SEEK_PREV) {
if ((seek_time >= seg_start) && (seek_time < seg_start + segment_duration)) {
if (out_opt_seek_time) *out_opt_seek_time = seg_start;
break;
}
} else if (seek_mode == MPD_SEEK_NEAREST) {
if ((seek_time >= seg_start) && (seek_time < seg_start + segment_duration)) {
Double dist_to_prev = seek_time - seg_start;
Double dist_to_next = seg_start + segment_duration - seek_time;
if (dist_to_next < dist_to_prev) {
if (out_opt_seek_time) *out_opt_seek_time = seg_start + segment_duration;
segment_idx++;
} else {
if (out_opt_seek_time) *out_opt_seek_time = seg_start;
}
break;
}
} else {
return GF_NOT_SUPPORTED;
}
seg_start += segment_duration;
segment_idx++;
}
*out_segment_index = segment_idx;
return GF_OK;
}
/*
smooth streaming 2.1 support
this is still very basic - we miss support for:
* live streams
* sparse stream
* Custom Attributes in Url pattern
The smooth maifest is transformed into an MPD with
StreamIndex <=> AdaptationSet
Url Template <=> SegmentTemplate.media at adaptation set level
QualityLevel <=> Representation
Codecs info at quality level <=> SegmentTemplate.initialisation at representation level using internal "isobmff:// ..." scheme
chunks <=> Segment Timeline at adaptation set level
*/
static GF_Err smooth_parse_chunk(GF_MPD *mpd, GF_List *container, GF_XMLNode *root)
{
u32 i;
GF_MPD_SegmentTimelineEntry *chunk;
GF_XMLAttribute *att;
GF_SAFEALLOC(chunk, GF_MPD_SegmentTimelineEntry);
if (!chunk) return GF_OUT_OF_MEM;
gf_list_add(container, chunk);
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "r")) {
chunk->repeat_count = atoi(att->value);
//repeat count is one-based in smooth (number of repeats), 0-based in dash (number of additional repeats)
if (chunk->repeat_count)
chunk->repeat_count -= 1;
}
else if (!strcmp(att->name, "d"))
chunk->duration = atoi(att->value);
else if (!strcmp(att->name, "t")) {
sscanf(att->value, LLU, &chunk->start_time);
}
}
return GF_OK;
}
static GF_Err smooth_replace_string(char *src_str, char *str_match, char *str_replace, char **output)
{
u32 len;
char c, *res, *sep = strstr(src_str, str_match);
if (!sep) {
res = gf_strdup(src_str);
if (*output) gf_free(*output);
*output = res;
return GF_OK;
}
c = sep[0];
sep[0] = 0;
len = (u32) ( strlen(src_str) + strlen(str_replace) + strlen(sep+strlen(str_match)) + 1 );
res = gf_malloc(sizeof(char) * len);
gf_strlcpy(res, src_str, len);
gf_strlcat(res, str_replace, len);
gf_strlcat(res, sep+strlen(str_match), len);
sep[0] = c;
if (*output) gf_free(*output);
*output = res;
return GF_OK;
}
static GF_Err smooth_parse_quality_level(GF_MPD *mpd, GF_List *container, GF_XMLNode *root, u32 timescale)
{
u32 i;
Bool is_audio = GF_FALSE;
GF_MPD_Representation *rep;
GF_XMLAttribute *att;
GF_Err e;
char *szISOBMFFInit = NULL;
GF_SAFEALLOC(rep, GF_MPD_Representation);
if (!rep) return GF_OUT_OF_MEM;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)rep);
rep->base_URLs = gf_list_new();
rep->sub_representations = gf_list_new();
e = gf_list_add(container, rep);
if (e) return e;
gf_dynstrcat(&szISOBMFFInit, "isobmff://", NULL);
#define ISBMFFI_ADD_KEYWORD(_name, _value) \
if (_value != NULL) {\
gf_dynstrcat(&szISOBMFFInit, _name, NULL);\
gf_dynstrcat(&szISOBMFFInit, "=", NULL);\
gf_dynstrcat(&szISOBMFFInit, _value, NULL);\
gf_dynstrcat(&szISOBMFFInit, " ", NULL);\
}
#define ISBMFFI_ADD_KEYWORD_CONST(_name, _value) \
gf_dynstrcat(&szISOBMFFInit, _name, NULL);\
gf_dynstrcat(&szISOBMFFInit, "=", NULL);\
gf_dynstrcat(&szISOBMFFInit, _value, NULL);\
gf_dynstrcat(&szISOBMFFInit, " ", NULL);\
i = 0;
while ( (att = gf_list_enum(root->attributes, &i)) ) {
if (!strcmp(att->name, "Index")) rep->id = gf_strdup(att->value);
else if (!strcmp(att->name, "Bitrate")) rep->bandwidth = atoi(att->value);
else if (!strcmp(att->name, "MaxWidth")) {
rep->width = atoi(att->value);
ISBMFFI_ADD_KEYWORD("w", att->value)
}
else if (!strcmp(att->name, "MaxHeight")) {
rep->height = atoi(att->value);
ISBMFFI_ADD_KEYWORD("h", att->value)
}
else if (!strcmp(att->name, "FourCC")) {
ISBMFFI_ADD_KEYWORD("4cc", att->value)
}
else if (!strcmp(att->name, "CodecPrivateData")) {
ISBMFFI_ADD_KEYWORD("init", att->value)
}
else if (!strcmp(att->name, "NALUnitLengthField")) {
ISBMFFI_ADD_KEYWORD("nal", att->value)
}
else if (!strcmp(att->name, "BitsPerSample")) {
ISBMFFI_ADD_KEYWORD("bps", att->value)
is_audio = GF_TRUE;
}
else if (!strcmp(att->name, "AudioTag")) {
ISBMFFI_ADD_KEYWORD("atag", att->value)
is_audio = GF_TRUE;
}
else if (!strcmp(att->name, "Channels")) {
ISBMFFI_ADD_KEYWORD("ch", att->value)
is_audio = GF_TRUE;
}
else if (!strcmp(att->name, "SamplingRate")) {
ISBMFFI_ADD_KEYWORD("srate", att->value)
is_audio = GF_TRUE;
}
}
if (timescale != 10000000) {
char szTS[20], *v;
sprintf(szTS, "%d", timescale);
//prevent gcc warning
v = (char *)szTS;
ISBMFFI_ADD_KEYWORD("scale", v)
}
//reserve space for max u64 as hex, without 0x prefix
ISBMFFI_ADD_KEYWORD_CONST("tfdt", "0000000000000000")
//create a url for the IS to be reconstructed
rep->mime_type = gf_strdup(is_audio ? "audio/mp4" : "video/mp4");
GF_SAFEALLOC(rep->segment_template, GF_MPD_SegmentTemplate);
if (!rep->segment_template) return GF_OUT_OF_MEM;
rep->segment_template->initialization = szISOBMFFInit;
return GF_OK;
}
static GF_Err smooth_parse_stream_index(GF_MPD *mpd, GF_List *container, GF_XMLNode *root, u32 timescale)
{
u32 i;
GF_MPD_AdaptationSet *set;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_SAFEALLOC(set, GF_MPD_AdaptationSet);
if (!set) return GF_OUT_OF_MEM;
set->id = -1;
gf_mpd_init_common_attributes((GF_MPD_CommonAttributes *)set);
gf_list_add(container, set);
set->accessibility = gf_list_new();
set->role = gf_list_new();
set->rating = gf_list_new();
set->viewpoint = gf_list_new();
set->content_component = gf_list_new();
set->inband_event = gf_list_new();
set->base_URLs = gf_list_new();
set->representations = gf_list_new();
set->segment_alignment = GF_TRUE;
/*assign default ID and group*/
set->group = -1;
i=0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "Type")) {}
else if (!strcmp(att->name, "Name")) {}
else if (!strcmp(att->name, "Chunks")) {
set->smooth_max_chunks = atoi(att->value);
}
else if (!strcmp(att->name, "MaxWidth")) set->max_width = atoi(att->value);
else if (!strcmp(att->name, "MaxHeight")) set->max_height = atoi(att->value);
else if (!strcmp(att->name, "Url")) {
char *template_url=NULL;
smooth_replace_string(att->value, "{bitrate}", "$Bandwidth$", &template_url);
smooth_replace_string(template_url, "{Bitrate}", "$Bandwidth$", &template_url);
smooth_replace_string(template_url, "{start time}", "$Time$", &template_url);
smooth_replace_string(template_url, "{start_time}", "$Time$", &template_url);
//TODO handle track substitution and custom attrib
GF_SAFEALLOC(set->segment_template, GF_MPD_SegmentTemplate);
if (!set->segment_template) return GF_OUT_OF_MEM;
set->segment_template->media = template_url;
set->segment_template->timescale = timescale;
GF_SAFEALLOC(set->segment_template->segment_timeline, GF_MPD_SegmentTimeline);
if (!set->segment_template->segment_timeline) return GF_OUT_OF_MEM;
set->segment_template->segment_timeline->entries = gf_list_new();
}
}
i = 0;
while ( ( child = gf_list_enum(root->content, &i )) ) {
if (!strcmp(child->name, "QualityLevel")) {
smooth_parse_quality_level(mpd, set->representations, child, timescale);
}
if (!strcmp(child->name, "c") && child && set && set->segment_template && set->segment_template->segment_timeline && set->segment_template->segment_timeline->entries) {
smooth_parse_chunk(mpd, set->segment_template->segment_timeline->entries, child);
}
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_mpd_init_smooth_from_dom(GF_XMLNode *root, GF_MPD *mpd, const char *default_base_url)
{
GF_Err e;
u32 i, timescale;
GF_XMLAttribute *att;
GF_XMLNode *child;
GF_MPD_Period *period;
u64 tsb = 0;
if (!root || !mpd) return GF_BAD_PARAM;
gf_mpd_init_struct(mpd);
/*setup some defaults*/
mpd->type = GF_MPD_TYPE_STATIC;
mpd->time_shift_buffer_depth = (u32) -1; /*infinite by default*/
mpd->xml_namespace = NULL;
timescale = 10000000;
i=0;
while ((att = gf_list_enum(root->attributes, &i))) {
if (!strcmp(att->name, "TimeScale"))
timescale = atoi(att->value);
else if (!strcmp(att->name, "Duration"))
mpd->media_presentation_duration = atoi(att->value);
else if (!strcmp(att->name, "IsLive") && !stricmp(att->value, "true") )
mpd->type = GF_MPD_TYPE_DYNAMIC;
else if (!strcmp(att->name, "DVRWindowLength"))
sscanf(att->value, LLU, &tsb);
// else if (!strcmp(att->name, "LookaheadCount")) { }
}
mpd->media_presentation_duration = mpd->media_presentation_duration * 1000 / timescale;
tsb *= 1000;
tsb /= timescale;
mpd->time_shift_buffer_depth = (u32) tsb;
GF_SAFEALLOC(period, GF_MPD_Period);
if (!period) return GF_OUT_OF_MEM;
gf_list_add(mpd->periods, period);
period->adaptation_sets = gf_list_new();
if (!period->adaptation_sets) return GF_OUT_OF_MEM;
period->event_streams = gf_list_new();
if (!period->event_streams) return GF_OUT_OF_MEM;
i = 0;
while ( ( child = gf_list_enum(root->content, &i )) ) {
if (!strcmp(child->name, "StreamIndex")) {
e = smooth_parse_stream_index(mpd, period->adaptation_sets, child, timescale);
if (e) return e;
}
}
return GF_OK;
}
GF_EXPORT
GF_Err gf_mpd_smooth_to_mpd(char * smooth_file, GF_MPD *mpd, const char *default_base_url)
{
GF_DOMParser *dom = gf_xml_dom_new();
GF_Err e = gf_xml_dom_parse(dom, smooth_file, NULL, 0);
if (!e) {
e = gf_mpd_init_smooth_from_dom(gf_xml_dom_get_root(dom), mpd, default_base_url);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Failed to convert smooth manifest to MPD\n"));
}
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[MPD] Failed to load smooth manifest\n"));
}
gf_xml_dom_del(dom);
return e;
}
#define EXTRACT_FORMAT(_nb_chars) \
gf_strcpy(szFmt, "%d"); \
char_template+=_nb_chars; \
if (seg_rad_name[char_template]=='%') { \
char *sep = strchr(seg_rad_name+char_template, '$'); \
if (sep) { \
sep[0] = 0; \
if (gf_mpd_check_print_format(seg_rad_name+char_template)) {\
gf_strcpy(szFmt, seg_rad_name+char_template); \
}\
char_template += (u32) strlen(seg_rad_name+char_template); \
sep[0] = '$'; \
} \
} \
char_template+=1; \
GF_EXPORT
GF_Err gf_media_mpd_format_segment_name(GF_DashTemplateSegmentType seg_type, Bool is_bs_switching, char segment_name[GF_MAX_PATH], const char *rep_id, const char *base_url, const char *seg_rad_name, const char *seg_ext, u64 start_time, u32 bandwidth, u32 segment_number, Bool use_segment_timeline, Bool forced)
{
Bool has_number= GF_FALSE;
Bool force_path = GF_FALSE;
if (seg_type==GF_DASH_TEMPLATE_TEMPLATE_WITH_PATH) {
seg_type = GF_DASH_TEMPLATE_TEMPLATE;
force_path = GF_TRUE;
}
if (seg_type==GF_DASH_TEMPLATE_REPINDEX_TEMPLATE_WITH_PATH) {
seg_type = GF_DASH_TEMPLATE_REPINDEX_TEMPLATE;
force_path = GF_TRUE;
}
Bool is_index = (seg_type==GF_DASH_TEMPLATE_REPINDEX) ? GF_TRUE : GF_FALSE;
Bool is_init = (seg_type==GF_DASH_TEMPLATE_INITIALIZATION) ? GF_TRUE : GF_FALSE;
Bool is_template = (seg_type==GF_DASH_TEMPLATE_TEMPLATE) ? GF_TRUE : GF_FALSE;
Bool is_init_template = (seg_type==GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE) ? GF_TRUE : GF_FALSE;
Bool is_index_template = (seg_type==GF_DASH_TEMPLATE_REPINDEX_TEMPLATE) ? GF_TRUE : GF_FALSE;
//except in old arch compat mode, we always append "init" if no "$Init" template, even in BS switching
//this avoids confusion in init segment naming when BS switching is on - cf #2773
if (!gf_sys_old_arch_compat())
is_bs_switching = GF_FALSE;
Bool needs_init=((is_init || is_init_template) && !is_bs_switching) ? GF_TRUE : GF_FALSE;
u32 has_init_keyword = 0;
Bool needs_index = GF_FALSE;
u32 char_template = 0;
size_t seg_rad_name_len;
char tmp[100];
char segment_ext_override[65];
gf_strlcpy(segment_name, "", GF_MAX_PATH);
gf_strcpy(segment_ext_override, "");
if (is_index_template) is_template = GF_TRUE;
if (seg_type==GF_DASH_TEMPLATE_INITIALIZATION_SKIPINIT) {
seg_type = GF_DASH_TEMPLATE_INITIALIZATION;
needs_init = GF_FALSE;
is_init = GF_TRUE;
}
if (seg_type==GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE_SKIPINIT) {
seg_type = GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE;
is_init_template = GF_TRUE;
needs_init = GF_FALSE;
}
if (!seg_rad_name) return GF_BAD_PARAM;
seg_rad_name_len = strlen(seg_rad_name);
if (seg_type!=GF_DASH_TEMPLATE_REPINDEX) {
if ( (is_index || is_index_template) && !strstr(seg_rad_name, "$Index")) {
needs_index = GF_TRUE;
}
}
if (strstr(seg_rad_name, "$RepresentationID$") || strstr(seg_rad_name, "$Bandwidth$"))
needs_init = GF_FALSE;
if (strstr(seg_rad_name, "$Init="))
has_init_keyword = 1;
if (strstr(seg_rad_name, "$XInit="))
has_init_keyword = 2;
while (char_template <= seg_rad_name_len) {
char szFmt[20];
char char_val = seg_rad_name[char_template];
if (!is_template && !is_init_template && !strnicmp(& seg_rad_name[char_template], "$RepresentationID$", 18) ) {
char_template += 18;
if (rep_id)
gf_strlcat(segment_name, rep_id, GF_MAX_PATH);
else {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] representation id is null when trying to format segment name\n"));
}
needs_init = GF_FALSE;
}
else if (!is_template && !is_init_template && !strnicmp(& seg_rad_name[char_template], "$Bandwidth", 10)) {
EXTRACT_FORMAT(10);
sprintf(tmp, szFmt, bandwidth);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
needs_init = GF_FALSE;
}
else if (!is_template && !strnicmp(& seg_rad_name[char_template], "$Time", 5)) {
EXTRACT_FORMAT(5);
if (is_init || is_init_template) {
if (!has_init_keyword && needs_init) {
if (!forced) gf_strlcat(segment_name, "init", GF_MAX_PATH);
needs_init = GF_FALSE;
}
continue;
}
/*replace %d to LLD*/
szFmt[strlen(szFmt)-1]=0;
gf_strcat(szFmt, &LLD[1]);
sprintf(tmp, szFmt, start_time);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
has_number = GF_TRUE;
}
else if (!strnicmp(& seg_rad_name[char_template], "$SubNumber", 10)) {
char *sep = strchr(seg_rad_name + char_template+10, '$');
if (!sep) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[MPD] Missing final `$` separator\n"));
return GF_BAD_PARAM;
}
if ( (seg_type==GF_DASH_TEMPLATE_TEMPLATE)
|| (seg_type==GF_DASH_TEMPLATE_SEGMENT_SUBNUMBER)
) {
sep[0] = 0;
gf_strlcat(segment_name, seg_rad_name + char_template, GF_MAX_PATH);
gf_strlcat(segment_name, "$", GF_MAX_PATH);
sep[0] = '$';
char_template += 1 +(u32) (sep - (seg_rad_name + char_template));
} else {
char_template += 1 +(u32) (sep - (seg_rad_name + char_template));
}
}
else if (!is_template && !strnicmp(& seg_rad_name[char_template], "$Number", 7)) {
EXTRACT_FORMAT(7);
if (is_init || is_init_template) {
if (!has_init_keyword && needs_init) {
if (!forced) gf_strlcat(segment_name, "init", GF_MAX_PATH);
needs_init = GF_FALSE;
}
continue;
}
sprintf(tmp, szFmt, segment_number);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
has_number = GF_TRUE;
}
else if (!strnicmp(& seg_rad_name[char_template], "$Init=", 6)) {
char *sep = strchr(seg_rad_name + char_template+6, '$');
if (sep) sep[0] = 0;
if (is_init || is_init_template) {
gf_strlcat(segment_name, seg_rad_name + char_template+6, GF_MAX_PATH);
needs_init = GF_FALSE;
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$XInit=", 7)) {
char *sep = strchr(seg_rad_name + char_template+6, '$');
if (sep) sep[0] = 0;
if (is_init || is_init_template) {
gf_strlcpy(segment_name, seg_rad_name + char_template+7, GF_MAX_PATH);
needs_init = GF_FALSE;
if (sep) sep[0] = '$';
break;
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$InitExt=", 9)) {
char *sep = strchr(seg_rad_name + char_template+9, '$');
if (sep) sep[0] = 0;
if (is_init || is_init_template) {
gf_strcpy(segment_ext_override, seg_rad_name + char_template+9);
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$Index=", 7)) {
char *sep = strchr(seg_rad_name + char_template+7, '$');
if (sep) sep[0] = 0;
if (is_index) {
gf_strlcat(segment_name, seg_rad_name + char_template+6, GF_MAX_PATH);
needs_index = GF_FALSE;
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$Path=", 6)) {
char *sep = strchr(seg_rad_name + char_template+6, '$');
if (sep) sep[0] = 0;
if (force_path || (!is_template && !is_init_template)) {
gf_strlcat(segment_name, seg_rad_name + char_template+6, GF_MAX_PATH);
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$Segment=", 9)) {
char *sep = strchr(seg_rad_name + char_template+9, '$');
if (sep) sep[0] = 0;
if (!is_init && !is_init_template) {
gf_strlcat(segment_name, seg_rad_name + char_template+9, GF_MAX_PATH);
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else if (!strnicmp(& seg_rad_name[char_template], "$SegExt=", 8)) {
char *sep = strchr(seg_rad_name + char_template+8, '$');
if (sep) sep[0] = 0;
if (!is_init && !is_init_template) {
gf_strcpy(segment_ext_override, seg_rad_name + char_template+8);
}
char_template += (u32) strlen(seg_rad_name + char_template)+1;
if (sep) sep[0] = '$';
}
else {
char_template+=1;
if (char_val=='\\') char_val = '/';
sprintf(tmp, "%c", char_val);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
}
}
if (is_template && !forced && !strstr(seg_rad_name, "$Number") && !strstr(seg_rad_name, "$Time")) {
if (use_segment_timeline) {
gf_strlcat(segment_name, "$Time$", GF_MAX_PATH);
} else {
gf_strlcat(segment_name, "$Number$", GF_MAX_PATH);
}
}
if (needs_init && !forced)
gf_strlcat(segment_name, "init", GF_MAX_PATH);
if (needs_index && !forced)
gf_strlcat(segment_name, "idx", GF_MAX_PATH);
if (!is_init && !is_template && !is_init_template && !is_index && !has_number && !forced) {
if (use_segment_timeline) {
sprintf(tmp, LLU, start_time);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
}
else {
sprintf(tmp, "%d", segment_number);
gf_strlcat(segment_name, tmp, GF_MAX_PATH);
}
}
if (strlen(segment_ext_override) > 0) {
gf_strlcat(segment_name, ".", GF_MAX_PATH);
gf_strlcat(segment_name, segment_ext_override, GF_MAX_PATH);
}
else if (seg_ext && !forced) {
gf_strlcat(segment_name, ".", GF_MAX_PATH);
gf_strlcat(segment_name, seg_ext, GF_MAX_PATH);
}
if ((seg_type != GF_DASH_TEMPLATE_TEMPLATE)
&& (seg_type != GF_DASH_TEMPLATE_SEGMENT)
&& (seg_type != GF_DASH_TEMPLATE_INITIALIZATION)
&& (seg_type != GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE)
) {
char *sep = strrchr(segment_name, '/');
if (sep) {
char cv = sep[0];
sep[0] = 0;
if (!gf_dir_exists(segment_name)) {
gf_mkdir(segment_name);
}
sep[0] = cv;
}
}
return GF_OK;
}
GF_Err gf_mpd_load_cues(const char *cues_file, u32 stream_id, u32 *cues_timescale, Bool *use_edit_list, s32 *ts_offset, GF_DASHCueInfo **out_cues, u32 *nb_cues)
{
GF_XMLNode *root, *stream, *cue;
GF_XMLAttribute *att;
u32 i, j, k;
GF_DOMParser *parser = gf_xml_dom_new();
GF_Err e = gf_xml_dom_parse(parser, cues_file, NULL, NULL);
if (e != GF_OK) {
gf_xml_dom_del(parser);
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error loading cue file %s: %s\n", cues_file, gf_error_to_string(e)));
return GF_NON_COMPLIANT_BITSTREAM;
}
root = gf_xml_dom_get_root(parser);
if (e != GF_OK) {
gf_xml_dom_del(parser);
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error loading cue file, no root element found\n"));
return GF_NON_COMPLIANT_BITSTREAM;
}
if (strcmp(root->name, "DASHCues")) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Wrong cue file, expecting DASHCues got %s\n", root->name));
gf_xml_dom_del(parser);
return GF_NON_COMPLIANT_BITSTREAM;
}
i=0;
while ((stream = gf_list_enum(root->content, &i))) {
u32 id=0;
u32 cur_cue;
GF_DASHCueInfo *cues;
u32 timescale=1000;
if (stream->type != GF_XML_NODE_TYPE) continue;
if (strcmp(stream->name, "Stream")) continue;
*use_edit_list = GF_FALSE;
*ts_offset = 0;
j=0;
while ((att = gf_list_enum(stream->attributes, &j))) {
if (!strcmp(att->name, "id")) id = atoi(att->value);
else if (!strcmp(att->name, "timescale")) timescale = atoi(att->value);
else if (!strcmp(att->name, "mode") && !strcmp(att->value, "edit") ) *use_edit_list = GF_TRUE;
else if (!strcmp(att->name, "ts_offset")) *ts_offset = atoi(att->value);
}
if (id != stream_id) continue;
*cues_timescale = timescale;
*nb_cues = 0;
j=0;
while ((cue = gf_list_enum(stream->content, &j))) {
if (cue->type != GF_XML_NODE_TYPE) continue;
if (strcmp(cue->name, "Cue")) continue;
(*nb_cues)++;
}
cues = gf_malloc(sizeof(GF_DASHCueInfo)* (*nb_cues) );
if (!cues) {
gf_xml_dom_del(parser);
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Failed to allocate %d cues\n", (*nb_cues) ));
return GF_OUT_OF_MEM;
}
memset(cues, 0, sizeof(GF_DASHCueInfo)* (*nb_cues) );
*out_cues = cues;
j=0;
cur_cue = 0;
while ((cue = gf_list_enum(stream->content, &j))) {
if (cue->type != GF_XML_NODE_TYPE) continue;
if (strcmp(cue->name, "Cue")) continue;
k=0;
while ((att = gf_list_enum(cue->attributes, &k))) {
if (!strcmp(att->name, "sample")) cues[cur_cue].sample_num = atoi(att->value);
else if (!strcmp(att->name, "dts")) sscanf(att->value, LLD, &cues[cur_cue].dts);
else if (!strcmp(att->name, "cts")) sscanf(att->value, LLD, &cues[cur_cue].cts);
}
cur_cue++;
}
break;
}
gf_xml_dom_del(parser);
if (!stream) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] No cues found for requested stream %d\n", stream_id));
return GF_BAD_PARAM;
}
return GF_OK;
}
GF_Err gf_mpd_split_adaptation_sets(GF_MPD *mpd)
{
u32 i, nb_periods, next_as_id=0;
if (!mpd) return GF_BAD_PARAM;
nb_periods = gf_list_count(mpd->periods);
for (i=0; i<nb_periods; i++) {
u32 j, nb_as;
GF_MPD_Period *period = gf_list_get(mpd->periods, i);
nb_as = gf_list_count(period->adaptation_sets);
for (j=0; j<nb_as; j++) {
GF_MPD_AdaptationSet *set = gf_list_get(period->adaptation_sets, j);
if (set->id > (s32) next_as_id)
next_as_id = (u32) set->id;
}
}
next_as_id++;
for (i=0; i<nb_periods; i++) {
u32 j, nb_as;
GF_MPD_Period *period = gf_list_get(mpd->periods, i);
GF_List *new_as = gf_list_new();
nb_as = gf_list_count(period->adaptation_sets);
for (j=0; j<nb_as; j++) {
GF_MPD_AdaptationSet *set = gf_list_get(period->adaptation_sets, j);
GF_List *reps = set->representations;
u32 nb_reps = gf_list_count(set->representations);
set->group = -1;
gf_list_add(new_as, set);
if (nb_reps<=1) {
continue;
}
while (gf_list_count(set->representations)>1) {
FILE *f = gf_file_temp(NULL);
u32 size;
char *data, *blob_add;
GF_Blob blob;
GF_DOMParser *dom;
GF_XMLNode *root;
GF_MPD_Representation *rep = gf_list_get(reps, 1);
gf_list_rem(reps, 1);
set->representations = gf_list_new();
gf_list_add(set->representations, rep);
if (rep->dependency_id) {
gf_free(rep->dependency_id);
rep->dependency_id = NULL;
}
if (set->id) {
set->id = next_as_id;
next_as_id++;
}
//serialize
gf_mpd_print_adaptation_set(set, f, GF_FALSE, 0, 0);
size = (u32) gf_ftell(f);
data = gf_malloc(size+1);
gf_fseek(f, 0, SEEK_SET);
size = (u32) gf_fread(data, size, f);
data[size]=0;
memset(&blob, 0, sizeof(GF_Blob));
blob.data = data;
blob.size = size;
blob_add = gf_blob_register(&blob);
//parse
dom = gf_xml_dom_new();
gf_xml_dom_parse(dom, blob_add, NULL, NULL);
root = gf_xml_dom_get_root(dom);
gf_mpd_parse_adaptation_set(mpd, new_as, root);
gf_xml_dom_del(dom);
gf_free(data);
if (blob_add) gf_free(blob_add);
gf_blob_unregister(&blob);
gf_fclose(f);
gf_mpd_representation_free(rep);
gf_list_del(set->representations);
set->representations = reps;
}
}
gf_list_del(period->adaptation_sets);
period->adaptation_sets = new_as;
}
return GF_OK;
}
GF_MPD_Descriptor *gf_mpd_get_descriptor(GF_List *desclist, char *scheme_id)
{
u32 i, count = gf_list_count(desclist);
for (i=0; i<count; i++) {
GF_MPD_Descriptor *desc = gf_list_get(desclist, i);
if (desc->scheme_id_uri && !strcmp(desc->scheme_id_uri, scheme_id)) return desc;
}
return NULL;
}
GF_EXPORT
char *gf_mpd_resolve_subnumber(char *llhas_template, char *segment_filename, u32 part_idx)
{
char *res = NULL;
char szTmp[20];
sprintf(szTmp, "%d", part_idx);
res = gf_strdup(segment_filename);
if (!llhas_template) {
gf_dynstrcat(&res, szTmp, ".");
return res;
}
char *sep = strchr(llhas_template, '$');
u32 subnum_len = (u32) (sep - llhas_template);
sep[0] = 0;
char *name = strstr(res, llhas_template);
name[subnum_len] = 0;
sep[0] = '$';
char *sep_fmt = strchr(sep, '%');
if (sep_fmt) {
char *final = strchr(sep_fmt, '$');
if (final) final[0] = 0;
if (gf_mpd_check_print_format(sep_fmt))
sprintf(szTmp, sep_fmt, part_idx);
if (final) final[0] = '$';
}
gf_dynstrcat(&res, szTmp, NULL);
sep = strchr(sep+1, '$');
gf_dynstrcat(&res, sep+1, NULL);
return res;
}
#endif /*GPAC_DISABLE_MPD*/