in src/media_tools/dash_segmenter.c [711:2308]
static GF_Err gf_media_isom_segment_file(GF_ISOFile *input, const char *output_file, GF_DASHSegmenter *dash_cfg, GF_DashSegInput *dash_input, Bool first_in_set)
{
u8 NbBits;
u32 i, TrackNum, descIndex, j, count, nb_sync, ref_track_id, nb_tracks_done;
u32 defaultDuration, defaultSize, defaultDescriptionIndex, defaultRandomAccess, nb_samp, nb_done;
u32 nb_video, nb_audio, nb_text, nb_scene, mpd_timescale;
u8 defaultPadding;
u16 defaultDegradationPriority;
GF_Err e;
char sOpt[100], sKey[100];
char szCodecs[200], szCodec[100];
u32 cur_seg, fragment_index, max_sap_type;
GF_ISOFile *output, *bs_switch_segment;
GF_ISOSample *sample, *next;
GF_List *fragmenters;
u64 MaxFragmentDuration, MaxSegmentDuration, SegmentDuration, maxFragDurationOverSegment, segment_start_time, period_duration;
u64 presentationTimeOffset = 0;
Double file_duration, max_segment_duration;
u32 nb_segments, width, height, sample_rate, nb_channels, sar_w, sar_h, fps_num, fps_denum, startNumber;
char *langCode = NULL;
u32 index_start_range, index_end_range;
Bool force_switch_segment = GF_FALSE;
Bool switch_segment = GF_FALSE;
Bool split_seg_at_rap = dash_cfg->segments_start_with_rap;
Bool split_at_rap = GF_FALSE;
Bool has_rap = GF_FALSE;
Bool next_sample_rap = GF_FALSE;
Bool flush_all_samples = GF_FALSE;
Bool simulation_pass = GF_FALSE;
Bool init_segment_deleted = GF_FALSE;
Bool first_segment_in_timeline = GF_TRUE;
Bool store_utc = GF_FALSE;
u64 previous_segment_duration = 0;
u32 segment_timeline_repeat_count = 0;
//u64 last_ref_cts = 0;
u64 start_range, end_range, file_size, init_seg_size, ref_track_first_dts, ref_track_next_cts;
u32 tfref_timescale = 0;
u32 bandwidth = 0;
GF_ISOMTrackFragmenter *tf, *tfref;
GF_BitStream *mpd_bs = NULL;
GF_BitStream *mpd_timeline_bs = NULL;
char szMPDTempLine[2048];
char SegmentName[GF_MAX_PATH];
char RepSecName[200];
char RepURLsSecName[200];
const char *opt;
Double max_track_duration = 0;
Bool bs_switching_is_output = GF_FALSE;
Bool store_dash_params = GF_FALSE;
Bool dash_moov_setup = GF_FALSE;
Bool segments_start_with_sap = GF_TRUE;
Bool first_sample_in_segment = GF_FALSE;
u32 *segments_info = NULL;
u32 nb_segments_info = 0;
u32 protected_track = 0;
u64 min_seg_dur, max_seg_dur, total_seg_dur, last_seg_dur;
Bool audio_only = GF_TRUE;
Bool is_bs_switching = GF_FALSE;
Bool use_url_template = dash_cfg->use_url_template;
const char *seg_rad_name = dash_cfg->seg_rad_name;
const char *seg_ext = dash_cfg->seg_ext;
const char *bs_switching_segment_name = NULL;
u64 generation_start_ntp = 0;
SegmentName[0] = 0;
SegmentDuration = 0;
nb_samp = 0;
fragmenters = NULL;
if (!seg_ext) seg_ext = "m4s";
if (dash_cfg->real_time && dash_cfg->dash_ctx) {
u32 sec, frac;
opt = gf_cfg_get_key(dash_cfg->dash_ctx, "DASH", "GenerationNTP");
sscanf(opt, "%u:%u", &sec, &frac);
generation_start_ntp = sec;
generation_start_ntp <<= 32;
generation_start_ntp |= frac;
}
bs_switch_segment = NULL;
if (dash_cfg->bs_switch_segment_file) {
bs_switch_segment = gf_isom_open(dash_cfg->bs_switch_segment_file, GF_ISOM_OPEN_READ, NULL);
if (bs_switch_segment) {
bs_switching_segment_name = gf_dasher_strip_output_dir(dash_cfg->mpd_name, dash_cfg->bs_switch_segment_file);
is_bs_switching = GF_TRUE;
}
}
sprintf(RepSecName, "Representation_%s", dash_input ? dash_input->representationID : "");
sprintf(RepURLsSecName, "URLs_%s", dash_input ? dash_input->representationID : "");
bandwidth = dash_input ? dash_input->bandwidth : 0;
file_duration = 0;
startNumber = 1;
//create output file
/*need to precompute bandwidth*/
if (!bandwidth && seg_rad_name && strstr(seg_rad_name, "$Bandwidth$")) {
for (i=0; i<gf_isom_get_track_count(input); i++) {
Double tk_dur = (Double) gf_isom_get_media_duration(input, i+1);
tk_dur /= gf_isom_get_media_timescale(input, i+1);
if (file_duration < tk_dur ) {
file_duration = tk_dur;
}
}
bandwidth = (u32) (gf_isom_get_file_size(input) / file_duration * 8);
}
if (dash_cfg->dash_ctx) {
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "Setup");
if (!opt || stricmp(opt, "yes") ) {
store_dash_params=GF_TRUE;
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "ID", dash_input->representationID);
}
}
opt = dash_cfg->dash_ctx ? gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "InitializationSegment") : NULL;
if (opt) {
output = gf_isom_open(opt, GF_ISOM_OPEN_CAT_FRAGMENTS, dash_cfg->tmpdir);
dash_moov_setup = GF_TRUE;
} else {
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION, is_bs_switching, SegmentName, output_file, dash_input->representationID, dash_input->nb_baseURL ? dash_input->baseURL[0] : NULL, seg_rad_name, !stricmp(seg_ext, "null") ? NULL : "mp4", 0, bandwidth, 0, dash_cfg->use_segment_timeline);
output = gf_isom_open(SegmentName, GF_ISOM_OPEN_WRITE, dash_cfg->tmpdir);
}
if (!output) {
GF_LOG(GF_LOG_ERROR, GF_LOG_AUTHOR, ("[ISOBMF DASH] Cannot open %s for writing\n", opt ? opt : SegmentName));
e = gf_isom_last_error(NULL);
goto err_exit;
}
if (store_dash_params) {
const char *name;
if (!gf_cfg_get_key(dash_cfg->dash_ctx, "DASH", "SegmentTemplate"))
gf_cfg_set_key(dash_cfg->dash_ctx, "DASH", "SegmentTemplate", seg_rad_name);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "Source", gf_isom_get_filename(input) );
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "Setup", "yes");
name = SegmentName;
if (bs_switch_segment) name = gf_isom_get_filename(bs_switch_segment);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "InitializationSegment", name);
/*store BS flag per rep - it should be stored per adaptation set but we dson't have a key for adaptation sets right now*/
if (/*first_in_set && */ is_bs_switching)
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "BitstreamSwitching", "yes");
} else if (dash_cfg->dash_ctx) {
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "BitstreamSwitching");
if (opt && !strcmp(opt, "yes")) {
is_bs_switching = GF_TRUE;
if (bs_switch_segment)
gf_isom_delete(bs_switch_segment);
bs_switch_segment = output;
bs_switching_is_output = GF_TRUE;
bs_switching_segment_name = gf_dasher_strip_output_dir(dash_cfg->mpd_name, gf_isom_get_filename(bs_switch_segment));
}
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "Bandwidth");
if (opt) sscanf(opt, "%u", &bandwidth);
if (dash_cfg->use_segment_timeline) {
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "NbSegmentsRemoved");
if (opt) {
u32 nb_removed = atoi(opt);
startNumber = 1 + nb_removed;
}
}
}
mpd_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
//if segment alignment not set or if first in AS, create SegmentTimeline
if (dash_cfg->use_segment_timeline && (first_in_set || dash_cfg->segment_alignment_disabled) ) {
mpd_timeline_bs = gf_bs_new(NULL, 0, GF_BITSTREAM_WRITE);
sprintf(szMPDTempLine, " <SegmentTimeline>\n");
gf_bs_write_data(mpd_timeline_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
if (dash_cfg->dash_ctx) {
gf_dash_load_segment_timeline(dash_cfg, mpd_timeline_bs, dash_input->representationID, &previous_segment_duration, &first_segment_in_timeline, &segment_timeline_repeat_count);
}
}
nb_sync = 0;
nb_samp = 0;
fragmenters = gf_list_new();
if (! dash_moov_setup) {
e = gf_isom_clone_movie(input, output, GF_FALSE, GF_FALSE, !dash_cfg->pssh_moof );
if (e) goto err_exit;
/*because of movie fragments MOOF based offset, ISOM <4 is forbidden*/
gf_isom_set_brand_info(output, GF_4CC('i','s','o','5'), 1);
gf_isom_modify_alternate_brand(output, GF_4CC('i','s','o','m'), 0);
gf_isom_modify_alternate_brand(output, GF_4CC('i','s','o','1'), 0);
gf_isom_modify_alternate_brand(output, GF_4CC('i','s','o','2'), 0);
gf_isom_modify_alternate_brand(output, GF_4CC('i','s','o','3'), 0);
gf_isom_modify_alternate_brand(output, GF_ISOM_BRAND_MP41, 0);
gf_isom_modify_alternate_brand(output, GF_ISOM_BRAND_MP42, 0);
}
MaxFragmentDuration = (u32) (dash_cfg->dash_scale * dash_cfg->fragment_duration);
MaxSegmentDuration = (u32) (dash_cfg->dash_scale * dash_cfg->segment_duration);
/*in single segment mode, only one big SIDX is written between the end of the moov and the first fragment.
To speed-up writing, we do a first fragmentation pass without writing any sample to compute the number of segments and fragments per segment
in order to allocate / write to file the sidx before the fragmentation. The sidx will then be rewritten when closing the last segment*/
if (dash_cfg->single_file_mode==1) {
simulation_pass = GF_TRUE;
seg_rad_name = NULL;
}
/*if single file is requested, store all segments in the same file*/
else if (dash_cfg->single_file_mode==2) {
seg_rad_name = NULL;
}
index_start_range = index_end_range = 0;
tf = tfref = NULL;
file_duration = 0;
width = height = sample_rate = nb_channels = sar_w = sar_h = fps_num = fps_denum = 0;
langCode = NULL;
szCodecs[0] = 0;
mpd_timescale = nb_video = nb_audio = nb_text = nb_scene = 0;
//duplicates all tracks
for (i=0; i<gf_isom_get_track_count(input); i++) {
u32 _w, _h, _sr, _nb_ch, vidtype;
u32 mtype = gf_isom_get_media_type(input, i+1);
if (mtype == GF_ISOM_MEDIA_HINT) continue;
if (dash_input->trackNum && ((i+1) != dash_input->trackNum))
continue;
if (dash_input->single_track_num && ((i+1) != dash_input->single_track_num))
continue;
if (!dash_moov_setup) {
e = gf_isom_clone_track(input, i+1, output, GF_FALSE, &TrackNum);
if (e) goto err_exit;
if (gf_isom_is_track_in_root_od(input, i+1)) gf_isom_add_track_to_root_od(output, TrackNum);
/*remove sgpd in stbl; it would be in traf*/
if (dash_cfg->samplegroups_in_traf) {
GF_TrackBox *trak = (GF_TrackBox *)gf_isom_get_track_from_file(output, TrackNum);
if (!trak) continue;
while (gf_list_count(trak->Media->information->sampleTable->sampleGroupsDescription)) {
GF_Box* box = (GF_Box*)gf_list_get(trak->Media->information->sampleTable->sampleGroupsDescription, 0);
gf_isom_box_del(box);
gf_list_rem(trak->Media->information->sampleTable->sampleGroupsDescription, 0);
}
}
// Commenting it the code for Timed Text tracks, it may happen that we have only one long sample, fragmentation is useful
#if 0
//if only one sample, don't fragment track
if (gf_isom_get_sample_count(input, i+1) == 1) {
sample = gf_isom_get_sample(input, i+1, 1, &descIndex);
e = gf_isom_add_sample(output, TrackNum, 1, sample);
gf_isom_sample_del(&sample);
if (e) goto err_exit;
continue;
}
#endif
} else {
TrackNum = gf_isom_get_track_by_id(output, gf_isom_get_track_id(input, i+1));
}
/*set extraction mode whether setup or not*/
vidtype = gf_isom_get_avc_svc_type(input, i+1, 1);
if (vidtype==GF_ISOM_AVCTYPE_AVC_ONLY) {
/*for AVC we concatenate SPS/PPS unless SVC base*/
if (!dash_input->trackNum && dash_cfg->inband_param_set && !dash_input->disable_inband_param_set)
gf_isom_set_nalu_extract_mode(input, i+1, GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG);
}
else if (vidtype > GF_ISOM_AVCTYPE_AVC_ONLY) {
/*for SVC we don't want any rewrite of extractors, and we don't concatenate SPS/PPS*/
gf_isom_set_nalu_extract_mode(input, i+1, GF_ISOM_NALU_EXTRACT_INSPECT);
}
vidtype = gf_isom_get_hevc_shvc_type(input, i+1, 1);
if (vidtype == GF_ISOM_HEVCTYPE_HEVC_ONLY) {
u32 mode = GF_ISOM_NALU_EXTRACT_INSPECT; //because of tile tracks
/*concatenate SPS/PPS unless SHVC base*/
if (!dash_input->trackNum && dash_cfg->inband_param_set && !dash_input->disable_inband_param_set)
mode |= GF_ISOM_NALU_EXTRACT_INBAND_PS_FLAG;
gf_isom_set_nalu_extract_mode(input, i+1, mode);
}
else if (vidtype > GF_ISOM_HEVCTYPE_HEVC_ONLY) {
gf_isom_set_nalu_extract_mode(input, i+1, GF_ISOM_NALU_EXTRACT_INSPECT);
}
if (mtype == GF_ISOM_MEDIA_VISUAL) nb_video++;
else if (mtype == GF_ISOM_MEDIA_AUDIO) nb_audio++;
else if (mtype == GF_ISOM_MEDIA_TEXT || mtype == GF_ISOM_MEDIA_MPEG_SUBT || mtype == GF_ISOM_MEDIA_SUBT) nb_text++;
else if (mtype == GF_ISOM_MEDIA_SCENE) nb_scene++;
else if (mtype == GF_ISOM_MEDIA_DIMS) nb_scene++;
if (mtype != GF_ISOM_MEDIA_AUDIO) audio_only = GF_FALSE;
//setup fragmenters
if (! dash_moov_setup) {
//new initialization segment, setup fragmentation
gf_isom_get_fragment_defaults(input, i+1,
&defaultDuration, &defaultSize, &defaultDescriptionIndex,
&defaultRandomAccess, &defaultPadding, &defaultDegradationPriority);
if (! dash_cfg->no_fragments_defaults) {
e = gf_isom_setup_track_fragment(output, gf_isom_get_track_id(output, TrackNum),
defaultDescriptionIndex, defaultDuration,
defaultSize, (u8) defaultRandomAccess,
defaultPadding, defaultDegradationPriority);
} else {
e = gf_isom_setup_track_fragment(output, gf_isom_get_track_id(output, TrackNum), defaultDescriptionIndex, 0, 0, 0, 0, 0);
}
if (e) goto err_exit;
} else {
gf_isom_get_fragment_defaults(output, TrackNum,
&defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority);
}
gf_media_get_rfc_6381_codec_name(input, i+1, szCodec, bs_switch_segment ? GF_TRUE : GF_FALSE, GF_TRUE);
if (strlen(szCodecs)) strcat(szCodecs, ",");
strcat(szCodecs, szCodec);
GF_SAFEALLOC(tf, GF_ISOMTrackFragmenter);
tf->TrackID = gf_isom_get_track_id(output, TrackNum);
tf->SampleCount = gf_isom_get_sample_count(input, i+1);
tf->OriginalTrack = i+1;
tf->TimeScale = gf_isom_get_media_timescale(input, i+1);
tf->MediaType = gf_isom_get_media_type(input, i+1);
tf->DefaultDuration = defaultDuration;
mpd_timescale = tf->TimeScale;
if (max_track_duration < gf_isom_get_track_duration(input, i+1)) {
max_track_duration = (Double) gf_isom_get_track_duration(input, i+1);
}
if (gf_isom_get_sync_point_count(input, i+1)>nb_sync) {
tfref = tf;
nb_sync = gf_isom_get_sync_point_count(input, i+1);
} else if (!gf_isom_has_sync_points(input, i+1)) {
tf->all_sample_raps = GF_TRUE;
}
tf->finalSampleDescriptionIndex = 1;
/*figure out if we have an initial TS*/
if (!dash_moov_setup) {
u32 j, edit_count = gf_isom_get_edit_segment_count(input, i+1);
Double scale = tf->TimeScale;
scale /= gf_isom_get_timescale(input);
/*remove all segments*/
gf_isom_remove_edit_segments(output, TrackNum);
tf->media_time_to_pres_time_shift = 0;
//clone all edits before first nomal, and stop at first normal setting duration to 0
for (j=0; j<edit_count; j++) {
u64 EditTime, SegDuration, MediaTime;
u8 EditMode;
/*get first edit*/
gf_isom_get_edit_segment(input, i+1, j+1, &EditTime, &SegDuration, &MediaTime, &EditMode);
if (EditMode==GF_ISOM_EDIT_NORMAL)
SegDuration = 0;
gf_isom_set_edit_segment(output, TrackNum, EditTime, SegDuration, MediaTime, EditMode);
if (EditMode==GF_ISOM_EDIT_NORMAL) {
tf->media_time_to_pres_time_shift -= (s32) MediaTime;
break;
}
tf->media_time_to_pres_time_shift += (u32) (scale*SegDuration);
}
gf_isom_set_brand_info(output, GF_4CC('i','s','o','5'), 1);
/*DASH self-init media segment*/
if (dash_cfg->single_file_mode==1) {
gf_isom_modify_alternate_brand(output, GF_4CC('d','s','m','s'), 1);
} else {
gf_isom_modify_alternate_brand(output, GF_4CC('d','a','s','h'), 1);
}
/*locate sample description in list if given*/
if (bs_switch_segment) {
u32 s_count;
u32 sample_descs_track = gf_isom_get_track_by_id(bs_switch_segment, tf->TrackID);
if (!sample_descs_track) {
e = GF_BAD_PARAM;
goto err_exit;
}
//the initialization segment is not yet setup for fragmentation
if (! gf_isom_is_track_fragmented(bs_switch_segment, tf->TrackID)) {
e = GF_BAD_PARAM;
if (e) goto err_exit;
}
/*otherwise override the fragment defauls so that we are consistent with the shared init segment*/
else {
e = gf_isom_get_fragment_defaults(bs_switch_segment, sample_descs_track,
&defaultDuration, &defaultSize, &defaultDescriptionIndex, &defaultRandomAccess, &defaultPadding, &defaultDegradationPriority);
if (e) goto err_exit;
e = gf_isom_change_track_fragment_defaults(output, tf->TrackID,
defaultDescriptionIndex, defaultDuration, defaultSize, defaultRandomAccess, defaultPadding, defaultDegradationPriority);
if (e) goto err_exit;
}
/*and search in new ones the new index*/
s_count = gf_isom_get_sample_description_count(bs_switch_segment, sample_descs_track);
/*more than one sampleDesc, we will need to indicate all the sample descs in the file in case we produce a single file,
otherwise the file would not be compliant*/
if (s_count > 1) {
gf_isom_clone_sample_descriptions(output, TrackNum, bs_switch_segment, sample_descs_track, GF_TRUE);
}
/*and search in new ones the new index*/
s_count = gf_isom_get_sample_description_count(bs_switch_segment, sample_descs_track);
if (s_count>1) {
u32 k;
/*remove all sample descs*/
for (k=0; k<s_count; k++) {
/*search in original file if we have the sample desc*/
if (gf_isom_is_same_sample_description(input, TrackNum, 1, bs_switch_segment, sample_descs_track, k+1)) {
tf->finalSampleDescriptionIndex = k+1;
}
}
}
}
}
/*restore track decode times*/
else {
char *opt, sKey[100];
sprintf(sKey, "TKID_%d_NextDecodingTime", tf->TrackID);
opt = (char *)gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, sKey);
if (opt) sscanf(opt, LLU, & tf->InitialTSOffset);
/*store presentationTimeOffset on the first rep*/
if (store_dash_params)
presentationTimeOffset = tf->InitialTSOffset;
}
/*get language, width/height/layout info, audio info*/
switch (mtype) {
case GF_ISOM_MEDIA_TEXT:
case GF_ISOM_MEDIA_SUBT:
case GF_ISOM_MEDIA_MPEG_SUBT:
tf->splitable = GF_TRUE;
gf_isom_get_media_language(input, i+1, &langCode);
case GF_ISOM_MEDIA_VISUAL:
case GF_ISOM_MEDIA_SCENE:
case GF_ISOM_MEDIA_DIMS:
e = gf_isom_get_visual_info(input, i+1, 1, &_w, &_h);
if (e == GF_OK) {
if (_w>width) width = _w;
if (_h>height) height = _h;
} else {
width = 0;
height = 0;
}
break;
case GF_ISOM_MEDIA_AUDIO:
gf_isom_get_audio_info(input, i+1, 1, &_sr, &_nb_ch, NULL);
if (_sr>sample_rate) sample_rate=_sr;
if (_nb_ch>nb_channels) nb_channels = _nb_ch;
gf_isom_get_media_language(input, i+1, &langCode);
break;
}
if (mtype==GF_ISOM_MEDIA_VISUAL) {
/*get duration of 2nd sample*/
u32 sample_dur = gf_isom_get_sample_duration(input, i+1, 2);
gf_isom_get_pixel_aspect_ratio(input, i+1, 1, &sar_w, &sar_h);
if (! fps_num || ! fps_denum) {
fps_num = tf->TimeScale;
fps_denum = sample_dur;
}
else if (fps_num *sample_dur < tf->TimeScale * fps_denum) {
fps_num = tf->TimeScale;
fps_denum = sample_dur;
}
}
if (file_duration < ((Double) gf_isom_get_media_duration(input, i+1)) / tf->TimeScale ) {
file_duration = ((Double) gf_isom_get_media_duration(input, i+1)) / tf->TimeScale;
}
gf_list_add(fragmenters, tf);
if (gf_isom_is_media_encrypted(input, i+1, 1)) {
protected_track = i+1;
}
nb_samp += tf->SampleCount;
}
if (gf_list_count(fragmenters)>1)
mpd_timescale = 1000;
if (!tfref) {
tfref = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, 0);
assert(tfref);
} else {
//put tfref in first pos
gf_list_del_item(fragmenters, tfref);
gf_list_insert(fragmenters, tfref, 0);
}
tfref->is_ref_track = GF_TRUE;
tfref_timescale = tfref->TimeScale;
ref_track_id = tfref->TrackID;
if (tfref->all_sample_raps)
split_seg_at_rap = GF_TRUE;
if (!dash_moov_setup) {
max_track_duration /= gf_isom_get_timescale(input);
max_track_duration *= gf_isom_get_timescale(output);
gf_isom_set_movie_duration(output, (u64) max_track_duration);
}
//if single segment, add msix brand if we use indexes
gf_isom_modify_alternate_brand(output, GF_4CC('m','s','i','x'), ((dash_cfg->single_file_mode==1) && dash_cfg->enable_sidx) ? 1 : 0);
//flush movie
e = gf_isom_finalize_for_fragment(output, 1);
if (e) goto err_exit;
start_range = 0;
file_size = gf_isom_get_file_size(bs_switch_segment ? bs_switch_segment : output);
end_range = file_size - 1;
init_seg_size = file_size;
if (dash_cfg->dash_ctx) {
if (store_dash_params) {
char szVal[1024];
sprintf(szVal, LLU, init_seg_size);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "InitializationSegmentSize", szVal);
} else {
const char *opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "InitializationSegmentSize");
if (opt) init_seg_size = atoi(opt);
}
}
restart_fragmentation_pass:
for (i=0; i<gf_list_count(fragmenters); i++) {
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
tf->split_sample_dts_shift = 0;
}
segment_start_time = 0;
nb_segments = 0;
nb_tracks_done = 0;
ref_track_first_dts = (u64) -1;
nb_done = 0;
maxFragDurationOverSegment=0;
switch_segment=GF_TRUE;
if (!seg_rad_name) use_url_template = GF_FALSE;
cur_seg=1;
fragment_index=1;
if (dash_input->moof_seqnum_increase) {
fragment_index = dash_input->moof_seqnum_increase * dash_input->idx_representations + 1;
}
period_duration = 0;
split_at_rap = GF_FALSE;
has_rap = GF_FALSE;
next_sample_rap = GF_FALSE;
flush_all_samples = GF_FALSE;
force_switch_segment = GF_FALSE;
max_sap_type = 0;
ref_track_next_cts = 0;
/*setup previous URL list*/
if (dash_cfg->dash_ctx) {
const char *opt;
char sKey[100];
count = gf_cfg_get_key_count(dash_cfg->dash_ctx, RepURLsSecName);
for (i=0; i<count; i++) {
const char *key_name = gf_cfg_get_key_name(dash_cfg->dash_ctx, RepURLsSecName, i);
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepURLsSecName, key_name);
sprintf(szMPDTempLine, " %s\n", opt);
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "NextSegmentIndex");
if (opt) cur_seg = atoi(opt);
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "NextFragmentIndex");
if (opt) fragment_index = atoi(opt);
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, "CumulatedDuration");
if (opt) sscanf(opt, LLU, &period_duration);
for (i=0; i<gf_list_count(fragmenters); i++) {
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
sprintf(sKey, "TKID_%d_NextSampleNum", tf->TrackID);
opt = (char *)gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, sKey);
if (opt) tf->SampleNum = atoi(opt);
sprintf(sKey, "TKID_%d_LastSampleCTS", tf->TrackID);
opt = (char *)gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, sKey);
if (opt) sscanf(opt, LLU, &tf->last_sample_cts);
sprintf(sKey, "TKID_%d_NextSampleDTS", tf->TrackID);
opt = (char *)gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, sKey);
if (opt) {
sscanf(opt, LLU, &tf->next_sample_dts);
}
sprintf(sKey, "TKID_%d_MediaTimeToPresTime", tf->TrackID);
opt = gf_cfg_get_key(dash_cfg->dash_ctx, RepSecName, sKey);
if (opt) tf->media_time_to_pres_time_shift = atoi(opt);
}
}
gf_isom_set_next_moof_number(output, dash_cfg->initial_moof_sn + fragment_index);
max_segment_duration = 0;
min_seg_dur = max_seg_dur = total_seg_dur = last_seg_dur = 0;
while ( (count = gf_list_count(fragmenters)) ) {
Bool store_pssh = GF_FALSE;
if (switch_segment) {
if (dash_cfg->subduration && (segment_start_time + MaxSegmentDuration/2 >= dash_cfg->dash_scale * dash_cfg->subduration)) {
/*done with file (next segment will exceed of more than half the requested subduration : store all fragmenters state and abord*/
break;
}
SegmentDuration = 0;
switch_segment = GF_FALSE;
first_sample_in_segment = GF_TRUE;
if (simulation_pass) {
segments_info = (u32 *)gf_realloc(segments_info, sizeof(u32) * (nb_segments_info+1) );
segments_info[nb_segments_info] = 0;
nb_segments_info++;
e = GF_OK;
} else {
start_range = gf_isom_get_file_size(output);
if (seg_rad_name) {
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_SEGMENT, is_bs_switching, SegmentName, output_file, dash_input->representationID, dash_input->baseURL ? dash_input->baseURL[0] : NULL, seg_rad_name, !stricmp(seg_ext, "null") ? NULL : seg_ext, period_duration + segment_start_time, bandwidth, cur_seg, dash_cfg->use_segment_timeline);
e = gf_isom_start_segment(output, SegmentName, dash_cfg->fragments_in_memory);
/*we are in bitstream switching mode, delete init segment*/
if (is_bs_switching && !init_segment_deleted) {
init_segment_deleted = GF_TRUE;
if (dash_cfg->bs_switch_segment_file && strcmp(dash_cfg->bs_switch_segment_file, gf_isom_get_filename(output))) {
gf_delete_file(gf_isom_get_filename(output));
}
}
if (!use_url_template) {
const char *name = gf_dasher_strip_output_dir(dash_cfg->mpd_name, SegmentName);
sprintf(szMPDTempLine, " <SegmentURL media=\"%s\"/>\n", name );
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
if (dash_cfg->dash_ctx) {
char szKey[100], szVal[4046];
sprintf(szKey, "UrlInfo%d", cur_seg );
sprintf(szVal, "<SegmentURL media=\"%s\"/>", name);
gf_cfg_set_key(dash_cfg->dash_ctx, RepURLsSecName, szKey, szVal);
}
}
} else {
e = gf_isom_start_segment(output, NULL, dash_cfg->fragments_in_memory);
}
if (dash_cfg->pssh_moof)
store_pssh = GF_TRUE;
if (tfref && dash_cfg->insert_utc) {
store_utc = GF_TRUE;
}
}
cur_seg++;
if (e) goto err_exit;
}
maxFragDurationOverSegment=0;
sample = NULL;
if (simulation_pass) {
segments_info[nb_segments_info-1] ++;
e = GF_OK;
} else {
e = gf_isom_start_fragment(output, GF_TRUE);
if (e) goto err_exit;
for (i=0; i<count; i++) {
u64 tfdt;
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
if (tf->done) continue;
if (dash_cfg->initial_tfdt && (tf->TimeScale != dash_cfg->dash_scale)) {
Double scale = tf->TimeScale;
scale /= dash_cfg->dash_scale;
tfdt = (u64) (dash_cfg->initial_tfdt*scale) + tf->InitialTSOffset + tf->next_sample_dts;
} else {
tfdt = tf->InitialTSOffset + tf->next_sample_dts;
}
gf_isom_set_traf_base_media_decode_time(output, tf->TrackID, tfdt);
if (!SegmentDuration) tf->min_cts_in_segment = (u64)-1;
}
if (store_pssh) {
store_pssh = GF_FALSE;
e = gf_isom_clone_pssh(output, input, GF_TRUE);
}
}
//process track by track
for (i=0; i<count; i++) {
Bool has_roll, is_rap;
s32 roll_distance;
u32 SAP_type = 0;
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
if (tf->is_ref_track)
has_rap = GF_FALSE;
if (tf->done) continue;
//if not the first TRAF in our moof and single traf per moof is requested, start a new moof
if (!simulation_pass && dash_cfg->single_traf_per_moof && (i>0) ) {
e = gf_isom_start_fragment(output, GF_TRUE);
if (e) goto err_exit;
}
//ok write samples
while (1) {
Bool stop_frag = GF_FALSE;
Bool is_redundant_sample = GF_FALSE;
u32 split_sample_duration = 0;
/*first sample*/
if (!sample) {
sample = gf_isom_get_sample(input, tf->OriginalTrack, tf->SampleNum + 1, &descIndex);
if (!sample) {
e = gf_isom_last_error(input);
goto err_exit;
}
/*FIXME - use negative ctts to indicate "past" DTS for splitted sample*/
if (tf->split_sample_dts_shift) {
sample->DTS += tf->split_sample_dts_shift;
is_redundant_sample = GF_TRUE;
}
/*also get SAP type - this is not needed if sample is not NULL as SAP type was computed for "next sample" in previous loop*/
if (sample->IsRAP) {
SAP_type = sample->IsRAP;
} else {
SAP_type = 0;
e = gf_isom_get_sample_rap_roll_info(input, tf->OriginalTrack, tf->SampleNum + 1, &is_rap, &has_roll, &roll_distance);
if (e==GF_OK) {
if (is_rap) SAP_type = 3;
else if (has_roll && (roll_distance>=0) ) SAP_type = 4;
}
}
}
gf_isom_get_sample_padding_bits(input, tf->OriginalTrack, tf->SampleNum+1, &NbBits);
next = gf_isom_get_sample(input, tf->OriginalTrack, tf->SampleNum + 2, &j);
if (next) {
defaultDuration = (u32) (next->DTS - sample->DTS);
} else {
defaultDuration = (u32) (gf_isom_get_media_duration(input, tf->OriginalTrack) - sample->DTS);
}
if (tf->splitable) {
if (tf->is_ref_track) {
u64 frag_dur = (tf->FragmentLength + defaultDuration) * dash_cfg->dash_scale / tf->TimeScale;
/*if media segment about to be produced is longer than max segment length, force segment split*/
if (SegmentDuration + frag_dur > MaxSegmentDuration) {
split_sample_duration = defaultDuration;
defaultDuration = (u32) (tf->TimeScale * (MaxSegmentDuration - SegmentDuration) / dash_cfg->dash_scale - tf->FragmentLength);
assert(defaultDuration);
split_sample_duration -= defaultDuration;
nb_samp++;
}
} else if (tfref /* tfref set to NULL after the last sample of tfref is processed */
/*&& next do not split if no next sample */
/*next sample DTS */
&& ((tf->next_sample_dts + defaultDuration) * tfref_timescale > tfref->next_sample_dts * tf->TimeScale)) {
split_sample_duration = defaultDuration;
defaultDuration = (u32) ( (tfref->next_sample_dts * tf->TimeScale)/tfref_timescale - tf->next_sample_dts );
split_sample_duration -= defaultDuration;
/*since we split this sample we have to stop fragmenting afterwards*/
stop_frag = GF_TRUE;
nb_samp++;
}
}
if (tf->is_ref_track) {
if (segments_start_with_sap && first_sample_in_segment) {
first_sample_in_segment = GF_FALSE;
if (!SAP_type) segments_start_with_sap = GF_FALSE;
}
if (ref_track_first_dts > sample->DTS)
ref_track_first_dts = sample->DTS;
if (next) {
u64 next_cts = next->DTS + next->CTS_Offset;
if (split_sample_duration)
next_cts -= split_sample_duration;
if (ref_track_next_cts<next_cts) {
ref_track_next_cts = next_cts;
}
} else {
u64 cts = gf_isom_get_media_duration(input, tf->OriginalTrack);
if (cts>ref_track_next_cts) ref_track_next_cts = cts;
else ref_track_next_cts += defaultDuration;
}
}
if (SAP_type > max_sap_type) max_sap_type = SAP_type;
if (simulation_pass) {
e = GF_OK;
} else {
if (tf->is_ref_track && store_utc) {
u64 ntpts = gf_net_get_ntp_ts();
gf_isom_set_fragment_reference_time(output, tf->TrackID, ntpts, sample->DTS + sample->CTS_Offset + tf->media_time_to_pres_time_shift);
store_utc = GF_FALSE;
}
/*override descIndex with final index used in file*/
descIndex = tf->finalSampleDescriptionIndex;
e = gf_isom_fragment_add_sample(output, tf->TrackID, sample, descIndex,
defaultDuration, NbBits, 0, is_redundant_sample);
if (e)
goto err_exit;
if (sample->DTS + sample->CTS_Offset < tf->min_cts_in_segment)
tf->min_cts_in_segment = sample->DTS + sample->CTS_Offset;
e = gf_isom_fragment_add_sai(output, input, tf->TrackID, tf->SampleNum + 1);
if (e) goto err_exit;
/*copy subsample information*/
e = gf_isom_fragment_copy_subsample(output, tf->TrackID, input, tf->OriginalTrack, tf->SampleNum + 1, dash_cfg->samplegroups_in_traf);
if (e)
goto err_exit;
gf_set_progress("ISO File Fragmenting", nb_done, nb_samp);
nb_done++;
}
tf->last_sample_cts = sample->DTS + sample->CTS_Offset;
tf->next_sample_dts = sample->DTS + defaultDuration;
if (split_sample_duration) {
gf_isom_sample_del(&next);
sample->DTS += defaultDuration;
} else {
gf_isom_sample_del(&sample);
sample = next;
tf->SampleNum += 1;
tf->split_sample_dts_shift = 0;
}
tf->FragmentLength += defaultDuration;
/*compute SAP type*/
if (sample) {
if (sample->IsRAP) {
SAP_type = sample->IsRAP;
} else {
SAP_type = 0;
e = gf_isom_get_sample_rap_roll_info(input, tf->OriginalTrack, tf->SampleNum + 1, &is_rap, &has_roll, NULL);
if (e==GF_OK) {
if (is_rap)
SAP_type = 3;
else if (has_roll && (roll_distance>=0) )
SAP_type = 4;
}
}
}
if (next && SAP_type) {
if (tf->is_ref_track) {
if (split_sample_duration) {
stop_frag = GF_TRUE;
}
else if (split_seg_at_rap) {
u64 next_sap_time;
u64 frag_dur, next_dur;
next_dur = gf_isom_get_sample_duration(input, tf->OriginalTrack, tf->SampleNum + 1);
if (!next_dur) next_dur = defaultDuration;
/*duration of fragment if we add this rap*/
frag_dur = (tf->FragmentLength+next_dur)*dash_cfg->dash_scale / tf->TimeScale;
next_sample_rap = GF_TRUE;
next_sap_time = isom_get_next_sap_time(input, tf->OriginalTrack, tf->SampleCount, tf->SampleNum + 2);
/*if no more SAP after this one, do not switch segment*/
if (next_sap_time) {
u32 scaler;
/*this is the fragment duration from last sample added to next SAP*/
frag_dur += (s64) (next_sap_time - tf->next_sample_dts - next_dur) * dash_cfg->dash_scale / tf->TimeScale;
/*if media segment about to be produced is longer than max segment length, force segment split*/
if (!tf->splitable && (SegmentDuration + frag_dur > MaxSegmentDuration)) {
split_at_rap = GF_TRUE;
/*force new segment*/
force_switch_segment = GF_TRUE;
stop_frag = GF_TRUE;
}
if (! tf->all_sample_raps) {
/*if adding this SAP will result in stoping the fragment "soon" after it, stop now and start with SAP
if all samples are RAPs, just stop fragment if we exceed the requested duration by adding the next sample
otherwise, take 3 samples (should be refined of course)*/
scaler = 3;
if ( (tf->FragmentLength + scaler * next_dur) * dash_cfg->dash_scale >= MaxFragmentDuration * tf->TimeScale)
stop_frag = GF_TRUE;
}
}
} else if (!has_rap) {
if (tf->FragmentLength == defaultDuration) has_rap = 2;
else has_rap = 1;
}
}
} else {
next_sample_rap = GF_FALSE;
}
if (tf->SampleNum==tf->SampleCount) {
stop_frag = GF_TRUE;
} else if (tf->is_ref_track) {
/*fragmenting on "clock" track: no drift control*/
if (!dash_cfg->fragments_start_with_rap || ( tf->splitable && split_sample_duration ) || ( (next && next->IsRAP) || split_at_rap) ) {
if ((tf->FragmentLength * dash_cfg->dash_scale >= MaxFragmentDuration * tf->TimeScale)
/* if we don't split segment at rap and if the current fragment makes the segment longer than required, stop the current fragment */
|| (!split_seg_at_rap && (SegmentDuration + (tf->FragmentLength * dash_cfg->dash_scale / tf->TimeScale) >= MaxSegmentDuration))
) {
stop_frag = GF_TRUE;
}
}
}
/*do not abort fragment if ref track is done, put everything in the last fragment*/
else if (!flush_all_samples) {
u64 ept_next;
u64 tref_ept_next = get_presentation_time(ref_track_next_cts, tfref->media_time_to_pres_time_shift);
/*get next sample dts: if greater than tref EPT, abort. This ensures that we have at most
one au wihth EPT less than ref EPT*/
u64 next_dur = gf_isom_get_sample_duration(input, tf->OriginalTrack, tf->SampleNum + 1);
if (!next_dur) next_dur = defaultDuration;
ept_next = get_presentation_time(tf->next_sample_dts + next_dur, tf->media_time_to_pres_time_shift);
/*fragmenting on "non-clock" track: drift control*/
if (ept_next * tfref_timescale > tref_ept_next * tf->TimeScale)
stop_frag = GF_TRUE;
}
if (stop_frag) {
gf_isom_sample_del(&sample);
sample = next = NULL;
//only compute max dur over segment for the track used for indexing / deriving MPD start time
if (!tfref || (tf->is_ref_track)) {
u64 f_dur;
f_dur = ( tf->FragmentLength ) * dash_cfg->dash_scale / tf->TimeScale;
if (maxFragDurationOverSegment <= f_dur) {
maxFragDurationOverSegment = f_dur;
}
}
tf->FragmentLength = 0;
if (split_sample_duration)
tf->split_sample_dts_shift += defaultDuration;
//if (tf==tfref)
//last_ref_cts = tf->last_sample_cts;
break;
}
}
if (tf->SampleNum==tf->SampleCount) {
tf->done = GF_TRUE;
nb_tracks_done++;
if (tf->is_ref_track) {
tfref = NULL;
flush_all_samples = GF_TRUE;
}
}
}
SegmentDuration += maxFragDurationOverSegment;
maxFragDurationOverSegment=0;
/*if no simulation and no SIDX or realtime is used, flush fragments as we write them*/
if (!simulation_pass && (!dash_cfg->enable_sidx || dash_cfg->real_time) ) {
if (tfref && dash_cfg->real_time) {
u64 end_time = tfref->InitialTSOffset + tfref->next_sample_dts - tfref->DefaultDuration;
end_time *= 1000;
end_time /= tfref->TimeScale;
while (1) {
s32 diff_ms = gf_net_get_ntp_diff_ms(generation_start_ntp);
if (diff_ms >= end_time) break;
gf_sleep(1);
}
GF_LOG(GF_LOG_DEBUG, GF_LOG_DASH, ("[DASH] Flushing fragment at "LLU" us\n", gf_sys_clock_high_res() ));
}
e = gf_isom_flush_fragments(output, flush_all_samples ? GF_TRUE : GF_FALSE);
if (e) goto err_exit;
}
//this is now disabled - we try to keep segment duration as constant and close to the requested duration as possible
//this code was producing much shorter segments in case segment duration was way greater than fragment duration
//this means we will typically end up with one shorter fragment at the end of the segment
#if 0
/*next fragment will exceed segment length, abort fragment now (all samples RAPs)*/
if (tfref && tfref->all_sample_raps && (SegmentDuration + MaxFragmentDuration >= MaxSegmentDuration)) {
force_switch_segment = GF_TRUE;
}
#endif
if (force_switch_segment || ((SegmentDuration >= MaxSegmentDuration) && (!split_seg_at_rap || !next || next_sample_rap || tf->splitable))) {
if (!min_seg_dur || (min_seg_dur>SegmentDuration))
min_seg_dur = SegmentDuration;
if (max_seg_dur < SegmentDuration)
max_seg_dur = SegmentDuration;
total_seg_dur += SegmentDuration;
last_seg_dur = SegmentDuration;
if (mpd_timeline_bs) {
u32 tick_adjust = 0;
//since dash scale and ref track used for segmentation may not have the same timescale we will have drift in segment timelines. Adjust it
if (tfref) {
s64 seg_start_time_min_cts = (s64) (tfref->min_cts_in_segment + tfref->media_time_to_pres_time_shift) * dash_cfg->dash_scale;
u64 seg_start_time_mpd = (period_duration + segment_start_time) * tfref->TimeScale;
//if first CTS in segment is less than 0, this means that we have AUs that are not presented due to edit list.
//adjust the segment duration accordingly.
if (seg_start_time_min_cts<0) {
s64 mpd_time_adjust = seg_start_time_min_cts / tfref->TimeScale;
if ((s64) SegmentDuration < - mpd_time_adjust) {
GF_LOG(GF_LOG_ERROR, GF_LOG_DASH, ("[DASH] Error: Segment duration is less than media time from edit list (%d vs %d)\n", MaxSegmentDuration, -tfref->media_time_to_pres_time_shift));
e = GF_BAD_PARAM;
goto err_exit;
}
SegmentDuration += mpd_time_adjust;
seg_start_time_min_cts=0;
}
if (seg_start_time_mpd != seg_start_time_min_cts) {
//compute diff in dash timescale
Double diff = (Double) seg_start_time_min_cts;
diff -= (Double) seg_start_time_mpd;
diff /= tfref->TimeScale;
//if we are ahead we will adjust keep track of how many ticks we miss
if (diff >= 1) {
tick_adjust = (u32) diff;
if (tick_adjust > 1) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: Drift between minCTS of segment and MPD start time is %g s\n", diff/dash_cfg->dash_scale));
}
}
}
}
//adjust
gf_dash_append_segment_timeline(mpd_timeline_bs, period_duration + segment_start_time, SegmentDuration + tick_adjust, &previous_segment_duration, &first_segment_in_timeline, &segment_timeline_repeat_count);
period_duration += tick_adjust;
}
if (dash_cfg->max_segment_duration * dash_cfg->dash_scale < SegmentDuration) {
dash_cfg->max_segment_duration = (Double) SegmentDuration;
dash_cfg->max_segment_duration /= dash_cfg->dash_scale;
}
gf_dasher_store_segment_info(dash_cfg, dash_input->representationID, SegmentName, period_duration + segment_start_time, SegmentDuration);
segment_start_time += SegmentDuration;
nb_segments++;
/*if (max_segment_duration * dash_cfg->dash_scale <= SegmentDuration) {
max_segment_duration = (Double) (s64) SegmentDuration;
max_segment_duration /= dash_cfg->dash_scale;
}*/
force_switch_segment = GF_FALSE;
switch_segment = GF_TRUE;
SegmentDuration = 0;
split_at_rap = GF_FALSE;
has_rap = GF_FALSE;
/*restore fragment duration*/
MaxFragmentDuration = (u32) (dash_cfg->fragment_duration * dash_cfg->dash_scale);
if (!simulation_pass) {
u64 idx_start_range, idx_end_range;
Bool last_segment = flush_all_samples ? GF_TRUE : GF_FALSE;
if (dash_cfg->subduration && (segment_start_time + MaxSegmentDuration/2 >= dash_cfg->dash_scale * dash_cfg->subduration)) {
//onDemand with subdur: if we are done dashing the content set last segment to TRUE to flush sidx
//live and end of period forced, force last segment
if (dash_cfg->force_period_end || (dash_cfg->single_file_mode==1) )
last_segment = GF_TRUE;
}
gf_isom_close_segment(output, dash_cfg->enable_sidx ? dash_cfg->subsegs_per_sidx : 0, dash_cfg->enable_sidx ? ref_track_id : 0, ref_track_first_dts, tfref ? tfref->media_time_to_pres_time_shift : tf->media_time_to_pres_time_shift, ref_track_next_cts, dash_cfg->daisy_chain_sidx, last_segment, dash_cfg->segment_marker_4cc, &idx_start_range, &idx_end_range);
//take care of scalable reps
if (dash_input->moof_seqnum_increase) {
u32 frag_index = gf_isom_get_next_moof_number(output) + dash_input->nb_representations * dash_input->moof_seqnum_increase;
gf_isom_set_next_moof_number(output, dash_cfg->initial_moof_sn + frag_index);
}
ref_track_first_dts = (u64) -1;
if (!seg_rad_name) {
file_size = gf_isom_get_file_size(output);
end_range = file_size - 1;
if (dash_cfg->single_file_mode!=1) {
sprintf(szMPDTempLine, " <SegmentURL mediaRange=\""LLD"-"LLD"\"", start_range, end_range);
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
if (idx_start_range || idx_end_range) {
sprintf(szMPDTempLine, " indexRange=\""LLD"-"LLD"\"", idx_start_range, idx_end_range);
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
gf_bs_write_data(mpd_bs, "/>\n", 3);
if (dash_cfg->dash_ctx) {
char szKey[100], szVal[4046];
sprintf(szKey, "UrlInfo%d", cur_seg );
sprintf(szVal, "<SegmentURL mediaRange=\""LLD"-"LLD"\" indexRange=\""LLD"-"LLD"\"/>", start_range, end_range, idx_start_range, idx_end_range);
gf_cfg_set_key(dash_cfg->dash_ctx, RepURLsSecName, szKey, szVal);
}
}
} else {
file_size += gf_isom_get_file_size(output);
}
}
/*next fragment will exceed segment length, abort fragment at next rap (possibly after MaxSegmentDuration)*/
if (split_seg_at_rap && SegmentDuration && (SegmentDuration + MaxFragmentDuration >= MaxSegmentDuration)) {
if (!split_at_rap) {
split_at_rap = GF_TRUE;
}
}
}
if (nb_tracks_done==count) break;
}
if (simulation_pass) {
/*OK, we have all segments and frags per segments*/
gf_isom_allocate_sidx(output, dash_cfg->subsegs_per_sidx, dash_cfg->daisy_chain_sidx, nb_segments_info, segments_info, &index_start_range, &index_end_range );
gf_free(segments_info);
segments_info = NULL;
simulation_pass = GF_FALSE;
/*reset fragmenters*/
for (i=0; i<gf_list_count(fragmenters); i++) {
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
tf->done = GF_FALSE;
tf->last_sample_cts = 0;
tf->next_sample_dts = 0;
tf->FragmentLength = 0;
tf->SampleNum = 0;
if (tf->is_ref_track)
tfref = tf;
}
goto restart_fragmentation_pass;
}
/*flush last segment*/
if (!switch_segment) {
u64 idx_start_range, idx_end_range;
total_seg_dur += SegmentDuration;
last_seg_dur = SegmentDuration;
gf_isom_close_segment(output, dash_cfg->enable_sidx ? dash_cfg->subsegs_per_sidx : 0, dash_cfg->enable_sidx ? ref_track_id : 0, ref_track_first_dts, tfref ? tfref->media_time_to_pres_time_shift : tf->media_time_to_pres_time_shift, ref_track_next_cts, dash_cfg->daisy_chain_sidx, GF_TRUE, dash_cfg->segment_marker_4cc, &idx_start_range, &idx_end_range);
nb_segments++;
if (!seg_rad_name) {
file_size = gf_isom_get_file_size(output);
end_range = file_size - 1;
if (dash_cfg->single_file_mode!=1) {
sprintf(szMPDTempLine, " <SegmentURL mediaRange=\""LLD"-"LLD"\"", start_range, end_range);
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
if (idx_start_range || idx_end_range) {
sprintf(szMPDTempLine, " indexRange=\""LLD"-"LLD"\"", idx_start_range, idx_end_range);
gf_bs_write_data(mpd_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
gf_bs_write_data(mpd_bs, "/>\n", 3);
if (dash_cfg->dash_ctx) {
char szKey[100], szVal[4046];
sprintf(szKey, "UrlInfo%d", cur_seg );
sprintf(szVal, "<SegmentURL mediaRange=\""LLD"-"LLD"\" indexRange=\""LLD"-"LLD"\"/>", start_range, end_range, idx_start_range, idx_end_range);
gf_cfg_set_key(dash_cfg->dash_ctx, RepURLsSecName, szKey, szVal);
}
}
} else {
file_size += gf_isom_get_file_size(output);
}
}
//close timeline
if (mpd_timeline_bs) {
if (previous_segment_duration == SegmentDuration) {
segment_timeline_repeat_count ++;
sprintf(szMPDTempLine, " r=\"%d\"/>\n", segment_timeline_repeat_count);
gf_bs_write_data(mpd_timeline_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
} else {
if (previous_segment_duration) {
if (segment_timeline_repeat_count) {
sprintf(szMPDTempLine, " r=\"%d\"/>\n", segment_timeline_repeat_count);
} else {
sprintf(szMPDTempLine, "/>\n");
}
gf_bs_write_data(mpd_timeline_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
if (SegmentDuration) {
if (first_segment_in_timeline) {
sprintf(szMPDTempLine, " <S t=\""LLU"\" d=\""LLU"\"/>\n", segment_start_time, SegmentDuration );
first_segment_in_timeline = GF_FALSE;
} else {
sprintf(szMPDTempLine, " <S d=\""LLU"\"/>\n", (u64) SegmentDuration);
}
gf_bs_write_data(mpd_timeline_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
}
sprintf(szMPDTempLine, " </SegmentTimeline>\n");
gf_bs_write_data(mpd_timeline_bs, szMPDTempLine, (u32) strlen(szMPDTempLine));
}
else if (!dash_cfg->use_segment_timeline) {
if (dash_cfg->dash_ctx) {
max_segment_duration = dash_cfg->segment_duration;
} else {
if (3*min_seg_dur < max_seg_dur) {
GF_LOG(GF_LOG_WARNING, GF_LOG_DASH, ("[DASH]: Segment duration variation is higher than the +/- 50%% allowed by DASH-IF (min %g, max %g) - please reconsider encoding\n", (Double) min_seg_dur / dash_cfg->dash_scale, (Double) max_seg_dur / dash_cfg->dash_scale));
}
if (nb_segments == 1) {
max_segment_duration = (Double) total_seg_dur;
max_segment_duration /= nb_segments * dash_cfg->dash_scale;
} else {
max_segment_duration = (Double) (total_seg_dur - last_seg_dur);
max_segment_duration /= (nb_segments - 1) * dash_cfg->dash_scale;
}
}
}
if (!bandwidth)
bandwidth = (u32) (file_size * 8 / file_duration);
bandwidth += dash_input->dependency_bandwidth;
dash_input->bandwidth = bandwidth;
if (use_url_template) {
/*segment template does not depend on file name, write the template at the adaptationSet level*/
if (!dash_cfg->variable_seg_rad_name && first_in_set) {
const char *rad_name = gf_dasher_strip_output_dir(dash_cfg->mpd_name, seg_rad_name);
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_TEMPLATE, is_bs_switching, SegmentName, output_file, dash_input->representationID, NULL, rad_name, !stricmp(seg_ext, "null") ? NULL : seg_ext, 0, 0, 0, dash_cfg->use_segment_timeline);
fprintf(dash_cfg->mpd, " <SegmentTemplate timescale=\"%d\" media=\"%s\" startNumber=\"%d\"", mpd_timeline_bs ? dash_cfg->dash_scale : mpd_timescale, SegmentName, startNumber);
if (dash_cfg->ast_offset_ms<0) {
fprintf(dash_cfg->mpd, " availabilityTimeOffset=\"%g\"", - (Double) dash_cfg->ast_offset_ms / 1000.0);
}
if (!dash_cfg->use_segment_timeline) {
if (!max_segment_duration)
max_segment_duration = dash_cfg->segment_duration;
fprintf(dash_cfg->mpd, " duration=\"%d\"", (u32) (max_segment_duration * mpd_timescale));
}
/*in BS switching we share the same IS for all reps*/
if (is_bs_switching) {
strcpy(SegmentName, bs_switching_segment_name);
} else {
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE, is_bs_switching, SegmentName, output_file, dash_input->representationID, NULL, rad_name, !stricmp(seg_ext, "null") ? NULL : "mp4", 0, 0, 0, dash_cfg->use_segment_timeline);
}
fprintf(dash_cfg->mpd, " initialization=\"%s\"", SegmentName);
if (presentationTimeOffset)
fprintf(dash_cfg->mpd, " presentationTimeOffset=\""LLD"\"", presentationTimeOffset);
if (mpd_timeline_bs) {
char *mpd_seg_info = NULL;
u32 size;
fprintf(dash_cfg->mpd, ">\n");
gf_bs_get_content(mpd_timeline_bs, &mpd_seg_info, &size);
gf_fwrite(mpd_seg_info, 1, size, dash_cfg->mpd);
gf_free(mpd_seg_info);
fprintf(dash_cfg->mpd, " </SegmentTemplate>\n");
} else {
fprintf(dash_cfg->mpd, "/>\n");
}
}
/*in BS switching we share the same IS for all reps, write the SegmentTemplate for the init segment*/
else if ((is_bs_switching || mpd_timeline_bs) && first_in_set && !dash_cfg->segment_alignment_disabled) {
fprintf(dash_cfg->mpd, " <SegmentTemplate");
if (is_bs_switching) {
fprintf(dash_cfg->mpd, " initialization=\"%s\"", bs_switching_segment_name);
if (presentationTimeOffset)
fprintf(dash_cfg->mpd, " presentationTimeOffset=\""LLD"\"", presentationTimeOffset);
}
if (dash_cfg->ast_offset_ms<0) {
fprintf(dash_cfg->mpd, " availabilityTimeOffset=\"%g\"", - (Double) dash_cfg->ast_offset_ms / 1000.0);
}
if (mpd_timeline_bs) {
char *mpd_seg_info = NULL;
u32 size;
fprintf(dash_cfg->mpd, " timescale=\"%d\">\n", dash_cfg->dash_scale);
gf_bs_get_content(mpd_timeline_bs, &mpd_seg_info, &size);
gf_fwrite(mpd_seg_info, 1, size, dash_cfg->mpd);
gf_free(mpd_seg_info);
fprintf(dash_cfg->mpd, " </SegmentTemplate>\n");
} else {
fprintf(dash_cfg->mpd, "/>\n");
}
}
}
else if ((dash_cfg->single_file_mode!=1) && mpd_timeline_bs) {
char *mpd_seg_info = NULL;
u32 size;
fprintf(dash_cfg->mpd, " <SegmentList timescale=\"%d\"", dash_cfg->dash_scale);
if (dash_cfg->ast_offset_ms<0) {
fprintf(dash_cfg->mpd, " availabilityTimeOffset=\"%g\"", - (Double) dash_cfg->ast_offset_ms / 1000.0);
}
fprintf(dash_cfg->mpd, "\n");
gf_bs_get_content(mpd_timeline_bs, &mpd_seg_info, &size);
gf_fwrite(mpd_seg_info, 1, size, dash_cfg->mpd);
gf_free(mpd_seg_info);
fprintf(dash_cfg->mpd, " </SegmentList>\n");
}
/* Write adaptation set content protection element */
if (protected_track && first_in_set && dash_cfg->content_protection_in_adaptation_set) {
gf_isom_write_content_protection(input, dash_cfg->mpd, protected_track, 3);
}
fprintf(dash_cfg->mpd, " <Representation ");
if ( strlen(dash_input->representationID) ) fprintf(dash_cfg->mpd, "id=\"%s\"", dash_input->representationID);
else fprintf(dash_cfg->mpd, "id=\"%p\"", output);
fprintf(dash_cfg->mpd, " mimeType=\"%s/mp4\" codecs=\"%s\"", audio_only ? "audio" : "video", szCodecs);
if (width && height) {
fprintf(dash_cfg->mpd, " width=\"%u\" height=\"%u\"", width, height);
/*this is a video track*/
if (fps_num || fps_denum) {
gf_media_get_reduced_frame_rate(&fps_num, &fps_denum);
if (fps_denum>1)
fprintf(dash_cfg->mpd, " frameRate=\"%d/%d\"", fps_num, fps_denum);
else
fprintf(dash_cfg->mpd, " frameRate=\"%d\"", fps_num);
if (!sar_w) sar_w = 1;
if (!sar_h) sar_h = 1;
fprintf(dash_cfg->mpd, " sar=\"%d:%d\"", sar_w, sar_h);
}
}
if (sample_rate) fprintf(dash_cfg->mpd, " audioSamplingRate=\"%d\"", sample_rate);
//single segment (onDemand profiles, assumes we always start with an IDR)
if (dash_cfg->single_file_mode==1) {
fprintf(dash_cfg->mpd, " startWithSAP=\"1\"");
}
//regular segmenting
else {
if (segments_start_with_sap || split_seg_at_rap) {
fprintf(dash_cfg->mpd, " startWithSAP=\"%d\"", max_sap_type);
} else {
fprintf(dash_cfg->mpd, " startWithSAP=\"0\"");
}
}
//only appears at AdaptationSet level - need to rewrite the DASH segementer to allow writing this at the proper place
// if ((single_file_mode==1) && segments_start_with_sap) fprintf(dash_cfg->mpd, " subsegmentStartsWithSAP=\"%d\"", max_sap_type);
fprintf(dash_cfg->mpd, " bandwidth=\"%d\"", bandwidth);
if (dash_input->dependencyID)
fprintf(dash_cfg->mpd, " dependencyId=\"%s\"", dash_input->dependencyID);
fprintf(dash_cfg->mpd, ">\n");
/* baseURLs */
if (dash_input->nb_baseURL) {
for (i=0; i<dash_input->nb_baseURL; i++) {
fprintf(dash_cfg->mpd, " <BaseURL>%s</BaseURL>\n", dash_input->baseURL[i]);
}
}
/* writing Representation level descriptors */
if (dash_input->nb_rep_descs) {
for (i=0; i<dash_input->nb_rep_descs; i++) {
fprintf(dash_cfg->mpd, " %s\n", dash_input->rep_descs[i]);
}
}
if (nb_channels && !is_bs_switching)
fprintf(dash_cfg->mpd, " <AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\"/>\n", nb_channels);
/* Write content protection element in representation */
if (protected_track) {
gf_isom_write_content_protection(input, dash_cfg->mpd, protected_track, 4);
}
if (dash_cfg->dash_ctx) {
Double seg_dur;
opt = gf_cfg_get_key(dash_cfg->dash_ctx, "DASH", "MaxSegmentDuration");
if (opt) {
seg_dur = atof(opt);
if (seg_dur < max_segment_duration) {
sprintf(sOpt, "%f", max_segment_duration);
gf_cfg_set_key(dash_cfg->dash_ctx, "DASH", "MaxSegmentDuration", sOpt);
seg_dur = max_segment_duration;
} else {
max_segment_duration = seg_dur;
}
} else {
sprintf(sOpt, "%f", max_segment_duration);
gf_cfg_set_key(dash_cfg->dash_ctx, "DASH", "MaxSegmentDuration", sOpt);
}
}
if (use_url_template) {
/*segment template depends on file name, but the template at the representation level*/
if (dash_cfg->variable_seg_rad_name) {
const char *rad_name = gf_dasher_strip_output_dir(dash_cfg->mpd_name, seg_rad_name);
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_TEMPLATE, is_bs_switching, SegmentName, output_file, dash_input->representationID, NULL, rad_name, !stricmp(seg_ext, "null") ? NULL : seg_ext, 0, bandwidth, 0, dash_cfg->use_segment_timeline);
fprintf(dash_cfg->mpd, " <SegmentTemplate timescale=\"%d\" media=\"%s\" startNumber=\"%d\"", dash_cfg->use_segment_timeline ? dash_cfg->dash_scale : mpd_timescale, SegmentName, startNumber);
if (!dash_cfg->use_segment_timeline) {
if (!max_segment_duration)
max_segment_duration = dash_cfg->segment_duration;
fprintf(dash_cfg->mpd, " duration=\"%d\"", (u32) (max_segment_duration * mpd_timescale));
}
if (!is_bs_switching) {
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION_TEMPLATE, is_bs_switching, SegmentName, output_file, dash_input->representationID, NULL, rad_name, !stricmp(seg_ext, "null") ? NULL : "mp4", 0, 0, 0, dash_cfg->use_segment_timeline);
fprintf(dash_cfg->mpd, " initialization=\"%s\"", SegmentName);
}
if (presentationTimeOffset)
fprintf(dash_cfg->mpd, " presentationTimeOffset=\""LLD"\"", presentationTimeOffset);
if (dash_cfg->ast_offset_ms<0) {
fprintf(dash_cfg->mpd, " availabilityTimeOffset=\"%g\"", - (Double) dash_cfg->ast_offset_ms / 1000.0);
}
if (mpd_timeline_bs && (!first_in_set || dash_cfg->segment_alignment_disabled) ) {
char *mpd_seg_info = NULL;
u32 size;
fprintf(dash_cfg->mpd, ">\n");
gf_bs_get_content(mpd_timeline_bs, &mpd_seg_info, &size);
gf_fwrite(mpd_seg_info, 1, size, dash_cfg->mpd);
gf_free(mpd_seg_info);
fprintf(dash_cfg->mpd, " </SegmentTemplate>\n");
} else {
fprintf(dash_cfg->mpd, "/>\n");
}
}
} else if (dash_cfg->single_file_mode==1) {
fprintf(dash_cfg->mpd, " <BaseURL>%s</BaseURL>\n", gf_dasher_strip_output_dir(dash_cfg->mpd_name, gf_isom_get_filename(output) ) );
fprintf(dash_cfg->mpd, " <SegmentBase indexRangeExact=\"true\" indexRange=\"%d-%d\"", index_start_range, index_end_range);
if (presentationTimeOffset)
fprintf(dash_cfg->mpd, " presentationTimeOffset=\""LLD"\"", presentationTimeOffset);
if (!is_bs_switching) {
fprintf(dash_cfg->mpd, ">\n");
fprintf(dash_cfg->mpd, " <Initialization range=\"%d-%d\"/>\n", 0, index_start_range-1);
fprintf(dash_cfg->mpd, " </SegmentBase>\n");
} else {
fprintf(dash_cfg->mpd, "/>\n");
}
} else {
if (!seg_rad_name) {
fprintf(dash_cfg->mpd, " <BaseURL>%s</BaseURL>\n", gf_dasher_strip_output_dir(dash_cfg->mpd_name, gf_isom_get_filename(output) ) );
}
fprintf(dash_cfg->mpd, " <SegmentList");
if (!mpd_timeline_bs) {
fprintf(dash_cfg->mpd, " timescale=\"%d\" duration=\"%d\"", mpd_timescale, (u32) (max_segment_duration * mpd_timescale));
}
if (presentationTimeOffset) {
fprintf(dash_cfg->mpd, " presentationTimeOffset=\""LLD"\"", presentationTimeOffset);
}
if (dash_cfg->ast_offset_ms<0) {
fprintf(dash_cfg->mpd, " availabilityTimeOffset=\"%g\"", - (Double) dash_cfg->ast_offset_ms / 1000.0);
}
fprintf(dash_cfg->mpd, ">\n");
/*we are not in bitstreamSwitching mode*/
if (!is_bs_switching) {
fprintf(dash_cfg->mpd, " <Initialization");
if (!seg_rad_name) {
fprintf(dash_cfg->mpd, " range=\"0-"LLD"\"", init_seg_size-1);
} else {
gf_media_mpd_format_segment_name(GF_DASH_TEMPLATE_INITIALIZATION, is_bs_switching, SegmentName, output_file, dash_input->representationID, NULL, gf_dasher_strip_output_dir(dash_cfg->mpd_name, seg_rad_name) , !stricmp(seg_ext, "null") ? NULL : "mp4", 0, bandwidth, 0, dash_cfg->use_segment_timeline);
fprintf(dash_cfg->mpd, " sourceURL=\"%s\"", SegmentName);
}
fprintf(dash_cfg->mpd, "/>\n");
}
}
if (mpd_bs) {
char *mpd_seg_info = NULL;
u32 size;
gf_bs_get_content(mpd_bs, &mpd_seg_info, &size);
gf_fwrite(mpd_seg_info, 1, size, dash_cfg->mpd);
gf_free(mpd_seg_info);
}
if (!use_url_template && (dash_cfg->single_file_mode!=1)) {
fprintf(dash_cfg->mpd, " </SegmentList>\n");
}
fprintf(dash_cfg->mpd, " </Representation>\n");
/*store context*/
if (dash_cfg->dash_ctx) {
period_duration += segment_start_time;
for (i=0; i<gf_list_count(fragmenters); i++) {
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, i);
/*InitialTSOffset is used when joining different files - if we are still in the same file , do not update it*/
if (tf->done) {
sprintf(sKey, "TKID_%d_NextDecodingTime", tf->TrackID);
sprintf(sOpt, LLU, tf->InitialTSOffset + tf->next_sample_dts);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, sKey, sOpt);
}
if (dash_cfg->subduration) {
sprintf(sKey, "TKID_%d_NextSampleNum", tf->TrackID);
sprintf(sOpt, "%d", tf->SampleNum);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, sKey, tf->done ? NULL : sOpt);
sprintf(sKey, "TKID_%d_LastSampleCTS", tf->TrackID);
sprintf(sOpt, LLU, tf->last_sample_cts);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, sKey, tf->done ? NULL : sOpt);
sprintf(sKey, "TKID_%d_NextSampleDTS", tf->TrackID);
sprintf(sOpt, LLU, tf->next_sample_dts);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, sKey, tf->done ? NULL : sOpt);
sprintf(sKey, "TKID_%d_MediaTimeToPresTime", tf->TrackID);
sprintf(sOpt, "%d", tf->media_time_to_pres_time_shift);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, sKey, sOpt);
}
}
sprintf(sOpt, "%d", cur_seg);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "NextSegmentIndex", sOpt);
fragment_index = gf_isom_get_next_moof_number(output);
sprintf(sOpt, "%d", fragment_index);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "NextFragmentIndex", sOpt);
sprintf(sOpt, LLU, period_duration);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "CumulatedDuration", sOpt);
if (store_dash_params) {
sprintf(sOpt, "%u", bandwidth);
gf_cfg_set_key(dash_cfg->dash_ctx, RepSecName, "Bandwidth", sOpt);
}
}
err_exit:
if (langCode) {
gf_free(langCode);
}
if (fragmenters) {
while (gf_list_count(fragmenters)) {
tf = (GF_ISOMTrackFragmenter *)gf_list_get(fragmenters, 0);
gf_free(tf);
gf_list_rem(fragmenters, 0);
}
gf_list_del(fragmenters);
}
if (output) {
if (e) gf_isom_delete(output);
else gf_isom_close(output);
}
if (!bs_switching_is_output && bs_switch_segment)
gf_isom_delete(bs_switch_segment);
gf_set_progress("ISO File Fragmenting", nb_samp, nb_samp);
if (mpd_bs) gf_bs_del(mpd_bs);
if (mpd_timeline_bs) gf_bs_del(mpd_timeline_bs);
return e;
}