in src/media_tools/dash_segmenter.c [5247:6163]
GF_Err gf_dasher_process(GF_DASHSegmenter *dasher, Double sub_duration)
{
u32 i, j, max_period, cur_period;
Bool has_role = GF_FALSE;
char *sep, szSolvedSegName[GF_MAX_PATH], szTempMPD[GF_MAX_PATH], szOpt[GF_MAX_PATH];
const char *opt;
GF_Err e;
Bool uses_xlink = GF_FALSE;
Bool has_mpeg2 = GF_FALSE;
Bool none_supported = GF_TRUE;
u32 valid_inputs = 0;
u32 cur_adaptation_set;
u32 max_adaptation_set = 0;
u32 cur_group_id = 0;
u32 max_sap_type = 0;
u32 nb_sap_type_greater_than_3 = 0;
u32 nb_sap_type_greater_than_4 = 0;
u32 max_comp_per_input;
Bool use_cenc = GF_FALSE;
Bool segment_alignment = GF_TRUE;
GF_List *period_links = NULL;
Double presentation_duration = 0;
Double active_period_start = 0;
u32 last_period_rep_idx_plus_one = 0;
FILE *mpd;
PeriodEntry *p;
if (!dasher) return GF_BAD_PARAM;
dasher->force_period_end = GF_FALSE;
dasher->content_protection_in_adaptation_set = GF_FALSE;
if (dasher->dash_mode && !dasher->mpd_update_time && !dasher->mpd_live_duration) {
if (dasher->dash_mode == GF_DASH_DYNAMIC_LAST) {
dasher->force_period_end = GF_TRUE;
dasher->dash_mode = GF_DASH_DYNAMIC;
} else {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Either MPD refresh (update) period or MPD duration shall be set in dynamic mode.\n"));
return GF_BAD_PARAM;
}
}
if (dasher->real_time && (dasher->nb_inputs>1)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] real-time simulation is only supported with a single representation.\n"));
return GF_BAD_PARAM;
}
/*update dash context*/
if (dasher->dash_ctx) {
Bool regenerate;
e = gf_dasher_init_context(dasher->dash_ctx, &dasher->dash_mode, &dasher->time_shift_depth, NULL, dasher->ast_offset_ms);
if (e) return e;
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "MaxSegmentDuration");
if (opt) {
Double seg_dur = atof(opt);
dasher->segment_duration = seg_dur;
} else {
sprintf(szOpt, "%f", dasher->segment_duration);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "MaxSegmentDuration", szOpt);
}
sprintf(szOpt, "%u", dasher->dash_scale);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "TimeScale", szOpt);
/*peform all file cleanup*/
regenerate = gf_dasher_cleanup(dasher);
if (!regenerate) return GF_OK;
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "HasXLINK");
if (opt && !strcmp(opt, "yes")) uses_xlink = GF_TRUE;
}
max_period = 0;
for (i=0; i<dasher->nb_inputs; i++) {
GF_DashSegInput *dash_input = &dasher->inputs[i];
dash_input->period = 0;
dash_input->moof_seqnum_increase = 2 + (u32) (dasher->segments_start_with_rap / dasher->fragments_in_memory );
/*layered coded representation*/
if (dash_input->idx_representations) {
continue;
}
if (dash_input->xlink) uses_xlink = GF_TRUE;
if (dash_input->role && strcmp(dash_input->role, "main"))
has_role = GF_TRUE;
if (dash_input->period_id_not_specified) {
max_period = 1;
}
if (!strcmp(dash_input->szMime, "video/mp2t")) has_mpeg2 = GF_TRUE;
valid_inputs++;
}
if (!valid_inputs) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: no suitable file found for dashing.\n"));
return GF_BAD_PARAM;
}
//assign periods
for (i=0; i<dasher->nb_inputs; i++) {
GF_DashSegInput *dash_input = &dasher->inputs[i];
/*set all default roles to main if needed*/
if (has_role && !dash_input->role)
dash_input->role = "main";
//reset adaptation classifier
dash_input->adaptation_set = 0;
if (dash_input->period)
continue;
if (dash_input->period_id_not_specified) {
dash_input->period = 1;
}
else if (dash_input->periodID) {
max_period++;
dash_input->period = max_period;
for (j=i+1; j<dasher->nb_inputs; j++) {
if (!strcmp(dasher->inputs[j].periodID, dasher->inputs[i].periodID))
dasher->inputs[j].period = dasher->inputs[i].period;
}
}
}
if (dasher->dash_mode && (max_period > 1) ) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Multiple periods in live mode not supported\n"));
return GF_NOT_SUPPORTED;
}
for (cur_period=0; cur_period<max_period; cur_period++) {
Double seg_duration_in_period = 0;
/*classify all input in possible adaptation sets*/
for (i=0; i<dasher->nb_inputs; i++) {
GF_DashSegInput *dash_input = &dasher->inputs[i];
/*this file does not belong to our current period*/
if (dash_input->period != cur_period+1) continue;
if (!seg_duration_in_period) {
seg_duration_in_period = dash_input->segment_duration ? dash_input->segment_duration : dasher->segment_duration;
} else if (dash_input->segment_duration && (dash_input->segment_duration != seg_duration_in_period)) {
segment_alignment = GF_FALSE;
}
/*this file already belongs to an adaptation set*/
if (dash_input->adaptation_set) continue;
/*we cannot fragment the input file*/
if (!dash_input->dasher_input_classify || !dash_input->dasher_segment_file) {
//this is a remote period insertion
if (dash_input->xlink) {
//assign a AS for this fake source (otherwise first active period detection will fail)
dash_input->adaptation_set = 1;
none_supported = GF_FALSE;
}
continue;
}
max_adaptation_set++;
dash_input->adaptation_set = max_adaptation_set;
dash_input->nb_rep_in_adaptation_set = 1;
e = dash_input->dasher_input_classify(dasher->inputs, dasher->nb_inputs, i, &cur_group_id, &max_sap_type);
if (e) return e;
none_supported = GF_FALSE;
if (max_sap_type>=3) nb_sap_type_greater_than_3++;
if (max_sap_type>3) nb_sap_type_greater_than_4++;
}
if (none_supported) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: None of the input types are supported for DASHing - nothing to do ...\n"));
return GF_OK;
}
}
/*check requested profiles can be generated, or adjust them*/
if (nb_sap_type_greater_than_4 || (nb_sap_type_greater_than_3 > 1)) {
if (dasher->profile) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: WARNING! Max SAP type %d detected\n\tswitching to FULL profile\n", max_sap_type));
}
dasher->profile = GF_DASH_PROFILE_FULL;
}
/*adjust params based on profiles*/
switch (dasher->profile) {
case GF_DASH_PROFILE_LIVE:
dasher->segments_start_with_rap = GF_TRUE;
dasher->use_url_template = 1;
dasher->single_segment = dasher->single_file = GF_FALSE;
break;
case GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE: {
dasher->bitstream_switching_mode = GF_DASH_BSMODE_MULTIPLE_ENTRIES;
for (i=0; i<dasher->nb_inputs; i++) {
if (dasher->inputs[i].role && !strcmp(dasher->inputs[i].role, "main"))
break;
}
if (i == dasher->nb_inputs) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] HbbTV 1.5 ISO live profile requires to have at least one Adaptation Set\nlabelled with a Role@value of \"main\". Consider adding \":role=main\" to your inputs.\n"));
e = GF_BAD_PARAM;
goto exit;
}
}
case GF_DASH_PROFILE_AVC264_LIVE:
dasher->segments_start_with_rap = GF_TRUE;
dasher->no_fragments_defaults = GF_TRUE;
dasher->use_url_template = 1;
dasher->single_segment = dasher->single_file = GF_FALSE;
dasher->content_protection_in_adaptation_set = GF_TRUE;
break;
case GF_DASH_PROFILE_AVC264_ONDEMAND:
dasher->segments_start_with_rap = GF_TRUE;
dasher->no_fragments_defaults = GF_TRUE;
dasher->content_protection_in_adaptation_set = GF_TRUE;
if (dasher->seg_rad_name) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Segment-name not allowed in DASH-AVC/264 onDemand profile.\n"));
return GF_BAD_PARAM;
}
case GF_DASH_PROFILE_ONDEMAND:
dasher->segments_start_with_rap = GF_TRUE;
dasher->single_segment = GF_TRUE;
if ((dasher->bitstream_switching_mode != GF_DASH_BSMODE_DEFAULT) && (dasher->bitstream_switching_mode != GF_DASH_BSMODE_NONE)) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] onDemand profile, bitstream switching mode cannot be used.\n"));
return GF_BAD_PARAM;
}
/*BS switching is meaningless in onDemand profile*/
dasher->bitstream_switching_mode = GF_DASH_BSMODE_NONE;
dasher->use_url_template = dasher->single_file = GF_FALSE;
break;
case GF_DASH_PROFILE_MAIN:
dasher->segments_start_with_rap = GF_TRUE;
dasher->single_segment = GF_FALSE;
break;
default:
break;
}
if (dasher->bitstream_switching_mode == GF_DASH_BSMODE_DEFAULT) {
dasher->bitstream_switching_mode = GF_DASH_BSMODE_INBAND;
}
//we allow using inband param set when not time aligned. set the option now before overriding flags for non time aligned
dasher->inband_param_set = ((dasher->bitstream_switching_mode == GF_DASH_BSMODE_INBAND) || (dasher->bitstream_switching_mode == GF_DASH_BSMODE_SINGLE) ) ? GF_TRUE : GF_FALSE;
if (!segment_alignment) {
if (dasher->profile) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: Segments are not time-aligned in each representation of each period\n\tswitching to FULL profile\n"));
dasher->profile = GF_DASH_PROFILE_FULL;
}
if (dasher->bitstream_switching_mode != GF_DASH_BSMODE_NONE) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: Segments are not time-aligned in each representation of each period\n\tdisabling bitstream switching\n"));
dasher->bitstream_switching_mode = GF_DASH_BSMODE_NONE;
}
}
//check we have a segment template
if (dasher->use_url_template && !dasher->seg_rad_name) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: WARNING! DASH Live profile requested but no -segment-name\n\tusing \"%%s_dash\" by default\n\n"));
dasher_format_seg_name(dasher, "%s_dash");
}
if (dasher->single_segment) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH-ing file%s - single segment\nSubsegment duration %.3f - Fragment duration: %.3f secs\n", (dasher->nb_inputs>1) ? "s" : "", dasher->segment_duration, dasher->fragment_duration));
dasher->subsegs_per_sidx = 0;
} else {
if (!dasher->seg_ext) dasher->seg_ext = "m4s";
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASH-ing file%s: %.2fs segments %.2fs fragments ", (dasher->nb_inputs>1) ? "s" : "", dasher->segment_duration, dasher->fragment_duration));
if (!dasher->enable_sidx) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("no sidx used"));
} else if (dasher->subsegs_per_sidx) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("%d subsegments per sidx", dasher->subsegs_per_sidx));
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("single sidx per segment"));
}
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("\n"));
}
if (dasher->fragments_start_with_rap) dasher->segments_start_with_rap = GF_TRUE;
if (dasher->segments_start_with_rap) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("Spliting segments %sat GOP boundaries\n", dasher->fragments_start_with_rap ? "and fragments " : ""));
}
dasher->single_file_mode = (dasher->single_segment) ? 1 : (dasher->single_file ? 2 : 0);
dasher->subduration = sub_duration * 1000 / dasher->dash_scale;
max_comp_per_input = 0;
for (cur_period=0; cur_period<max_period; cur_period++) {
u32 first_in_period = 0;
Double period_duration=0;
for (i=0; i<dasher->nb_inputs; i++) {
Double dur = 0;
GF_DashSegInput *dash_input = &dasher->inputs[i];
/*this file does not belongs to any adaptation set*/
if (dash_input->period != cur_period+1) continue;
/*this file does not belongs to any adaptation set*/
if (!dash_input->adaptation_set) continue;
if (!first_in_period) {
first_in_period = i+1;
last_period_rep_idx_plus_one = first_in_period;
}
//only get component info once
if (!dash_input->get_component_info_done) {
dash_input->get_component_info_done = GF_TRUE;
if (dash_input->dasher_get_components_info) {
e = dash_input->dasher_get_components_info(dash_input, dasher);
if (e) return e;
}
}
dur = dash_input->duration;
if (sub_duration && (sub_duration < dur * dasher->dash_scale) ) {
dur = dasher->subduration;
}
if (dur > period_duration)
period_duration = dur;
switch (dash_input->protection_scheme_type) {
case GF_ISOM_CENC_SCHEME:
case GF_ISOM_CBC_SCHEME:
use_cenc = GF_TRUE;
break;
}
if (max_comp_per_input < dash_input->nb_components)
max_comp_per_input = dash_input->nb_components;
}
if (first_in_period) {
dasher->inputs[first_in_period-1].period_duration = period_duration;
}
presentation_duration += period_duration;
}
if (max_comp_per_input > 1) {
if (dasher->profile == GF_DASH_PROFILE_AVC264_LIVE) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Muxed representations not allowed in DASH-IF AVC264 live profile\n\tswitching to regular live profile\n"));
dasher->profile = GF_DASH_PROFILE_LIVE;
} else if (dasher->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Muxed representations not allowed in HbbTV 1.5 ISOBMF live profile\n\tswitching to regular live profile\n"));
dasher->profile = GF_DASH_PROFILE_LIVE;
} else if (dasher->profile == GF_DASH_PROFILE_AVC264_ONDEMAND) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Muxed representations not allowed in DASH-IF AVC264 onDemand profile\n\tswitching to regular onDemand profile\n"));
dasher->profile = GF_DASH_PROFILE_ONDEMAND;
}
}
/*HbbTV 1.5 ISO live specific checks*/
if (dasher->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) {
if (max_adaptation_set > 16) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max 16 adaptation sets in HbbTV 1.5 ISO live profile\n\tswitching to DASH AVC/264 live profile\n"));
dasher->profile = GF_DASH_PROFILE_AVC264_LIVE;
}
if (max_period > 32) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max 32 periods in HbbTV 1.5 ISO live profile\n\tswitching to regular DASH AVC/264 live profile\n"));
dasher->profile = GF_DASH_PROFILE_AVC264_LIVE;
}
if (dasher->segment_duration < 1.0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Min segment duration 1s in HbbTV 1.5 ISO live profile\n\tcapping to 1s\n"));
dasher->segment_duration = 1.0;
}
if (dasher->segment_duration > 15.0) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Max segment duration 15s in HbbTV 1.5 ISO live profile\n\tcapping to 15s\n"));
dasher->segment_duration = 15.0;
}
}
active_period_start = 0;
if (dasher->dash_ctx) {
Double duration;
char *id = "";
if (last_period_rep_idx_plus_one) id = dasher->inputs[last_period_rep_idx_plus_one-1].periodID;
if (!id) id = "";
//restore max segment duration
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "MaxSegmentDuration");
if (opt) dasher->max_segment_duration = atof(opt);
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastActivePeriod");
//period has changed
if (opt && strcmp(opt, id)) {
Bool prev_period_not_done = GF_FALSE;
const char *prev_id = opt;
Double last_period_dur = 0;
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "CumulatedPastPeriodsDuration");
duration = opt ? atof(opt) : 0.0;
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastPeriodDuration");
if (opt) last_period_dur = atof(opt);
if (!last_period_dur) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] last period has no known duration, cannot insert new period. Try re-dashing previous period and specifying its duration\n"));
return GF_BAD_PARAM;
}
//we may have called the segmenter with the end of the previous active period and the start of the new one - locate the
//previous period and update cumulated duration
for (i=0; i<dasher->nb_inputs; i++) {
char *p_id = dasher->inputs[i].periodID;
if (!p_id) p_id = "";
if (!strcmp(p_id, prev_id)) {
last_period_dur += dasher->inputs[last_period_rep_idx_plus_one-1].period_duration;
presentation_duration -= dasher->inputs[last_period_rep_idx_plus_one-1].period_duration;;
prev_period_not_done = GF_TRUE;
break;
}
}
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastPeriodDuration", NULL);
duration += last_period_dur;
presentation_duration += duration;
sprintf(szOpt, "%g", duration);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "CumulatedPastPeriodsDuration", szOpt);
//get active period start
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastActivePeriodStart");
if (opt) active_period_start = atof(opt);
active_period_start += last_period_dur;
//and add period to past periods, storing their duration and xlink
if (! prev_period_not_done) {
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastPeriodXLINK");
sprintf(szOpt, "%g,%s", last_period_dur, opt ? opt : "INLINE");
gf_cfg_set_key(dasher->dash_ctx, "PastPeriods", prev_id, szOpt);
if (opt) {
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastPeriodXLINK", NULL);
}
//update active period start
sprintf(szOpt, "%g", active_period_start);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastActivePeriodStart", szOpt);
purge_dash_context(dasher->dash_ctx);
}
//and finally switch active period
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastActivePeriod", id);
sprintf(szOpt, "%g", dasher->inputs[last_period_rep_idx_plus_one-1].period_duration);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastPeriodDuration", szOpt);
} else {
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastPeriodDuration");
if (opt) {
duration = atof(opt);
dasher->inputs[last_period_rep_idx_plus_one-1].period_duration += duration;
presentation_duration += duration;
}
sprintf(szOpt, "%g", dasher->inputs[last_period_rep_idx_plus_one-1].period_duration);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastPeriodDuration", szOpt);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastActivePeriod", id);
opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "CumulatedPastPeriodsDuration");
presentation_duration += opt ? atof(opt) : 0.0;
}
}
strcpy(szTempMPD, dasher->mpd_name);
if (dasher->dash_mode) strcat(szTempMPD, ".tmp");
mpd = gf_fopen(szTempMPD, "wt");
if (!mpd) {
GF_LOG(GF_LOG_INFO, GF_LOG_AUTHOR, ("[MPD] Cannot open MPD file %s for writing\n", szTempMPD));
return GF_IO_ERR;
}
period_links = gf_list_new();
if (dasher->dash_ctx) {
u32 count = gf_cfg_get_key_count(dasher->dash_ctx, "PastPeriods");
for (i=0; i<count; i++) {
const char *id = gf_cfg_get_key_name(dasher->dash_ctx, "PastPeriods", i);
const char *xlink = gf_cfg_get_key(dasher->dash_ctx, "PastPeriods", id);
char *sep = (char *)strchr(xlink, ',');
if (!sep) continue;
GF_SAFEALLOC(p, PeriodEntry);
gf_list_add(period_links, p);
p->id = id;
sep[0] = 0;
p->period_duration = (Double) atof(xlink);
sep[0]=',';
xlink = &sep[1];
if (!strcmp(xlink, "INLINE")) {
strcpy(p->szPeriodXML, dasher->mpd_name);
sep = strrchr(p->szPeriodXML, '.');
if (sep) sep[0] = 0;
strcat(p->szPeriodXML, "-Period_");
strcat(p->szPeriodXML, id);
strcat(p->szPeriodXML, ".mpd");
} else {
char *url = gf_url_concatenate(dasher->mpd_name, xlink);
strcpy(p->szPeriodXML, url ? url : xlink);
if (url) gf_free(url);
p->xlink = xlink;
p->is_xlink = GF_TRUE;
}
}
}
for (cur_period=0; cur_period<max_period; cur_period++) {
Bool flush_period = GF_FALSE;
FILE *period_mpd;
Double period_duration = 0;
const char *id=NULL;
const char *xlink = NULL;
/* Find period information for this period */
for (i=0; i< dasher->nb_inputs; i++) {
if (dasher->inputs[i].adaptation_set && (dasher->inputs[i].period==cur_period+1)) {
period_duration = dasher->inputs[i].period_duration;
id = dasher->inputs[i].periodID;
if (dasher->inputs[i].xlink)
xlink = dasher->inputs[i].xlink;
break;
}
}
GF_SAFEALLOC(p, PeriodEntry);
gf_list_add(period_links, p);
p->period_duration = period_duration;
p->period_idx = cur_period+1;
p->id = id;
if (xlink) {
char *url = gf_url_concatenate(dasher->mpd_name, xlink);
strcpy(p->szPeriodXML, url ? url : xlink);
if (url) gf_free(url);
p->xlink = xlink;
p->is_xlink = GF_TRUE;
} else {
strcpy(p->szPeriodXML, dasher->mpd_name);
sep = strrchr(p->szPeriodXML, '.');
if (sep) sep[0] = 0;
strcat(p->szPeriodXML, "-Period_");
strcat(p->szPeriodXML, id ? id : "");
strcat(p->szPeriodXML, ".mpd");
}
period_mpd = NULL;
//only open file if we are to dash something (max_adaptation_set=0: no source file, only period xlink insertion)
if (max_adaptation_set) {
period_mpd = gf_fopen(p->szPeriodXML, "wb");
if (!period_mpd) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Cannot open period MPD %s for writing, aborting\n", p->szPeriodXML));
e = GF_IO_ERR;
goto exit;
}
dasher->mpd = period_mpd;
e = write_period_header(dasher, period_mpd, id, 0.0, period_duration, NULL, cur_period+1, (xlink!=NULL) ? GF_TRUE : GF_FALSE);
if (e) goto exit;
}
//keep track of the last period xlink
if (dasher->dash_ctx)
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastPeriodXLINK", xlink);
/*for each identified adaptationSets, write MPD and perform segmentation of input files*/
for (cur_adaptation_set=0; cur_adaptation_set < max_adaptation_set; cur_adaptation_set++) {
char szInit[GF_MAX_PATH], tmp[GF_MAX_PATH];
u32 first_rep_in_set=0;
u32 max_width = 0;
u32 max_height = 0;
u32 fps_num = 0;
u32 fps_denum = 0;
Double seg_duration_in_as = 0;
Bool has_scalability = GF_FALSE;
Bool use_bs_switching = (dasher->bitstream_switching_mode==GF_DASH_BSMODE_NONE) ? GF_FALSE : GF_TRUE;
char *lang;
char szFPS[100];
Bool is_first_rep = GF_FALSE;
Bool skip_init_segment_creation = GF_FALSE;
dasher->segment_alignment_disabled = GF_FALSE;
while (dasher->inputs[first_rep_in_set].adaptation_set!=cur_adaptation_set+1)
first_rep_in_set++;
/*not in our period*/
if (dasher->inputs[first_rep_in_set].period != cur_period+1)
continue;
if (!dasher->inputs[first_rep_in_set].init_segment_generated) {
strcpy(tmp, dasher->mpd_name);
sep = strrchr(tmp, '.');
if (sep) sep[0] = 0;
if (max_adaptation_set==1) {
strcpy(szInit, tmp);
strcat(szInit, "_init.mp4");
} else {
sprintf(szInit, "%s_set%d_init.mp4", tmp, cur_adaptation_set+1);
}
/*unless asked to do BS switching on single rep, don't do it ...*/
if ((dasher->bitstream_switching_mode < GF_DASH_BSMODE_SINGLE) && dasher->inputs[first_rep_in_set].nb_rep_in_adaptation_set==1)
use_bs_switching = GF_FALSE;
if (! use_bs_switching) {
skip_init_segment_creation = GF_TRUE;
}
else if (! dasher->inputs[first_rep_in_set].dasher_create_init_segment) {
skip_init_segment_creation = GF_TRUE;
strcpy(szInit, "");
}
/*if we already wrote the InitializationSegment, get rid of it (no overwrite) and let the dashing routine open it*/
else if (dasher->dash_ctx) {
char RepSecName[GF_MAX_PATH];
const char *init_seg_name;
sprintf(RepSecName, "Representation_%s", dasher->inputs[first_rep_in_set].representationID);
if ((init_seg_name = gf_cfg_get_key(dasher->dash_ctx, RepSecName, "InitializationSegment")) != NULL) {
skip_init_segment_creation = GF_TRUE;
}
}
if (!skip_init_segment_creation) {
Bool disable_bs_switching = GF_FALSE;
e = dasher->inputs[first_rep_in_set].dasher_create_init_segment(dasher->inputs, dasher->nb_inputs, cur_adaptation_set+1, szInit, dasher->tmpdir, dasher, dasher->bitstream_switching_mode, &disable_bs_switching);
if (e) goto exit;
if (disable_bs_switching)
use_bs_switching = GF_FALSE;
}
dasher->inputs[first_rep_in_set].init_seg_url = use_bs_switching ? gf_strdup(szInit) : NULL;
dasher->inputs[first_rep_in_set].use_bs_switching = use_bs_switching;
dasher->inputs[first_rep_in_set].init_segment_generated = GF_TRUE;
} else {
use_bs_switching = dasher->inputs[first_rep_in_set].use_bs_switching;
}
dasher->bs_switch_segment_file = use_bs_switching ? dasher->inputs[first_rep_in_set].init_seg_url : NULL;
dasher->segment_alignment_disabled = GF_FALSE;
szFPS[0] = 0;
lang = NULL;
/*compute some max defaults*/
for (i=0; i<dasher->nb_inputs && !e; i++) {
GF_DashSegInput *dash_input = &dasher->inputs[i];
if (dash_input->adaptation_set!=cur_adaptation_set+1)
continue;
if (dash_input->idx_representations)
has_scalability = GF_TRUE;
for (j=0; j<dash_input->nb_components; j++) {
if (dash_input->components[j].width > max_width)
max_width = dash_input->components[j].width;
if (dash_input->components[j].height > max_height)
max_height = dash_input->components[j].height;
if (! fps_num || !fps_denum) {
fps_num = dash_input->components[j].fps_num;
fps_denum = dash_input->components[j].fps_denum;
}
else if (fps_num * dash_input->components[j].fps_denum < dash_input->components[j].fps_num * fps_denum) {
fps_num = dash_input->components[j].fps_num;
fps_denum = dash_input->components[j].fps_denum;
}
if (dash_input->components[j].lang) {
if (lang && strcmp(lang, dash_input->components[j].lang) ) {
if (!strcmp(lang, "und") || !strcmp(dash_input->components[j].lang, "lang")) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] two languages in adaptation set: %s will be kept %s will be ignored\n", lang, dash_input->components[j].lang));
}
} else if (!lang) {
lang = gf_strdup(dash_input->components[j].lang);
}
}
if (dash_input->segment_duration) {
if (!seg_duration_in_as)
seg_duration_in_as = dash_input->segment_duration;
else if (dash_input->segment_duration != seg_duration_in_as)
dasher->segment_alignment_disabled = GF_TRUE;
}
}
}
if (max_width && max_height) {
gf_media_get_reduced_frame_rate(&fps_num, &fps_denum);
if (fps_denum>1)
sprintf(szFPS, "%d/%d", fps_num, fps_denum);
else if (fps_num)
sprintf(szFPS, "%d", fps_num);
}
e = write_adaptation_header(period_mpd, dasher->profile, dasher->use_url_template, dasher->single_file_mode, dasher->inputs, dasher->nb_inputs, cur_period+1, cur_adaptation_set+1, first_rep_in_set,
use_bs_switching, max_width, max_height, szFPS, lang, szInit, dasher->segment_alignment_disabled, dasher->mpd_name);
if (e) goto exit;
is_first_rep = GF_TRUE;
for (i=0; i<dasher->nb_inputs && !e; i++) {
char szOutName[GF_MAX_PATH], *segment_name, *orig_seg_name;
GF_DashSegInput *dash_input = &dasher->inputs[i];
Double segdur, fragdur;
if (dash_input->adaptation_set!=cur_adaptation_set+1)
continue;
orig_seg_name = segment_name = dasher->seg_rad_name;
strcpy(szOutName, gf_url_get_resource_name(dash_input->file_name));
sep = strrchr(szOutName, '.');
if (sep) sep[0] = 0;
/*in scalable case: seg_name is variable*/
dasher->variable_seg_rad_name = has_scalability ? GF_TRUE : GF_FALSE;
if (segment_name) {
if (strstr(segment_name, "%s")) {
sprintf(szSolvedSegName, segment_name, szOutName);
dasher->variable_seg_rad_name = GF_TRUE;
if (dash_input->single_track_num) {
char szTkNum[50];
sprintf(szTkNum, "_track%d_", dash_input->single_track_num);
strcat(szSolvedSegName, szTkNum);
}
} else {
strcpy(szSolvedSegName, segment_name);
}
segment_name = szSolvedSegName;
}
/* user added a relative-path baseURL */
if (dash_input->nb_baseURL) {
if (gf_url_is_local(dash_input->baseURL[0])) {
const char *name;
char tmp_segment_name[GF_MAX_PATH];
if (dash_input->nb_baseURL > 1)
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH] Several relative path baseURL for input %s: selecting \"%s\"\n", dash_input->file_name, dash_input->baseURL[0]));
name = gf_url_get_resource_name(szOutName);
strcpy(tmp_segment_name, name);
gf_url_get_resource_path(dash_input->baseURL[0], szOutName);
if (szOutName[strlen(szOutName)] != GF_PATH_SEPARATOR) {
char ext[2]; ext[0] = GF_PATH_SEPARATOR; ext[1] = 0;
strcat(szOutName, ext);
}
strcat(szOutName, tmp_segment_name);
} else {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Found baseURL for input %s but not a relative path (%s)\n", dash_input->file_name, dash_input->baseURL[0]));
}
}
if ((dash_input->trackNum || dash_input->single_track_num) && (!dasher->seg_rad_name || !strstr(dasher->seg_rad_name, "$RepresentationID$") ) ) {
char tmp[10];
sprintf(tmp, "_track%d", dash_input->trackNum ? dash_input->trackNum : dash_input->single_track_num);
strcat(szOutName, tmp);
}
strcat(szOutName, "_dash");
if (gf_url_get_resource_path(dasher->mpd_name, tmp)) {
strcat(tmp, szOutName);
strcpy(szOutName, tmp);
}
if (segment_name && dash_input->trackNum && !strstr(dasher->seg_rad_name, "$RepresentationID$") ) {
char tmp[10];
sprintf(tmp, "_track%d_", dash_input->trackNum);
strcat(segment_name, tmp);
}
dasher->seg_rad_name = segment_name;
/*in scalable case, we need also the bandwidth of dependent representation*/
if (dash_input->dependencyID) {
dash_input->dependency_bandwidth = gf_dash_get_dependency_bandwidth(dasher->inputs, dasher->nb_inputs, dash_input->file_name, dash_input->lower_layer_track);
}
segdur = dasher->segment_duration;
fragdur = dasher->fragment_duration;
if (dash_input->segment_duration)
dasher->segment_duration = dash_input->segment_duration;
if (dash_input->segment_duration && (dasher->fragment_duration * dasher->dash_scale > dash_input->segment_duration)) {
dasher->fragment_duration = dasher->segment_duration;
}
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("DASHing file %s\n", dash_input->file_name));
e = dash_input->dasher_segment_file(dash_input, szOutName, dasher, is_first_rep);
dasher->seg_rad_name = orig_seg_name;
dasher->segment_duration = segdur;
dasher->fragment_duration = fragdur;
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("Error while DASH-ing file: %s\n", gf_error_to_string(e)));
goto exit;
}
is_first_rep = GF_FALSE;
}
/*close adaptation set*/
fprintf(period_mpd, " </AdaptationSet>\n");
}
if (period_mpd) {
fprintf(period_mpd, " </Period>\n");
gf_fclose(period_mpd);
}
dasher->mpd = period_mpd;
//and add periods done to past periods, storing their start time
if (!id) id="";
if (last_period_rep_idx_plus_one && dasher->inputs[last_period_rep_idx_plus_one-1].periodID && strcmp(id, dasher->inputs[last_period_rep_idx_plus_one-1].periodID) ) {
flush_period = GF_TRUE;
}
else if (dasher->force_period_end) {
flush_period = GF_TRUE;
}
if (flush_period) {
sprintf(szOpt, "%g", active_period_start);
gf_cfg_set_key(dasher->dash_ctx, "PastPeriods", id, szOpt);
active_period_start += period_duration;
sprintf(szOpt, "%g", active_period_start);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "LastActivePeriodStart", szOpt);
if (dasher->dash_ctx) purge_dash_context(dasher->dash_ctx);
}
}
if (dasher->dash_ctx) {
sprintf(szOpt, "%g", dasher->max_segment_duration);
gf_cfg_set_key(dasher->dash_ctx, "DASH", "MaxSegmentDuration", szOpt);
}
/*ready to write the MPD*/
dasher->mpd = mpd;
if (dasher->dash_mode && !dasher->mpd_update_time) {
presentation_duration = dasher->mpd_live_duration;
}
if (dasher->force_period_end && !dasher->mpd_update_time && !presentation_duration) {
presentation_duration = 0;
i=0;
while ((p = (PeriodEntry *) gf_list_enum(period_links, &i))) {
presentation_duration = p->period_duration;
}
}
e = write_mpd_header(dasher, mpd, has_mpeg2, presentation_duration, use_cenc, uses_xlink);
if (e) goto exit;
i=0;
while ((p = (PeriodEntry *) gf_list_enum(period_links, &i))) {
if (!p->period_idx) {
if (p->is_xlink) {
write_period_header(dasher, mpd, p->id, 0.0, p->period_duration, p->xlink, 0, GF_FALSE);
} else {
dash_insert_period_xml(mpd, p->szPeriodXML);
}
} else {
if (p->is_xlink) {
write_period_header(dasher, mpd, p->id, 0.0, p->period_duration, p->xlink, p->period_idx, GF_FALSE);
} else {
dash_insert_period_xml(mpd, p->szPeriodXML);
}
if (!dasher->dash_ctx && !p->is_xlink) {
gf_delete_file(p->szPeriodXML);
}
}
}
fprintf(mpd, "</MPD>");
if (dasher->dash_ctx && dasher->dash_mode) {
const char *opt = gf_cfg_get_key(dasher->dash_ctx, "DASH", "LastPeriodDuration");
if (opt) {
GF_LOG(GF_LOG_INFO, GF_LOG_DASH, ("[DASH] Current Period Duration: %s\n", opt) );
}
}
if (dasher->profile == GF_DASH_PROFILE_HBBTV_1_5_ISOBMF_LIVE) {
if (gf_ftell(mpd) > 100*1024)
GF_LOG(GF_LOG_WARNING, GF_LOG_AUTHOR, ("[DASH] Manifest MPD is too big for HbbTV 1.5. Limit is 100kB, current size is "LLU"kB\n", gf_ftell(mpd)/1024));
}
exit:
if (mpd) {
gf_fclose(mpd);
if (!e && dasher->dash_mode) {
gf_delete_file(dasher->mpd_name);
e = gf_move_file(szTempMPD, dasher->mpd_name);
if (e) {
GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[DASH] Error moving file %s to %s: %s\n", szTempMPD, dasher->mpd_name, gf_error_to_string(e) ));
}
}
}
if (period_links) {
while (gf_list_count(period_links)) {
PeriodEntry *p = (PeriodEntry *) gf_list_pop_back(period_links);
if (p) gf_free(p);
}
}
return e;
}