static u_char* dash_packager_write_segment_list( u_char* p, write_period_context_t* context, uint32_t start_number, u_char* clip_spec, media_sequence_t* cur_sequence, media_track_t* cur_track, uint32_t segment_count) { dash_manifest_config_t* conf = context->conf; media_set_t* media_set = context->media_set; vod_str_t track_spec; vod_str_t cur_base_url; u_char track_spec_buffer[MAX_TRACK_SPEC_LENGTH]; uint32_t sequence_index = cur_sequence->index; uint32_t i; track_spec.data = track_spec_buffer; // build the base url dash_packager_get_segment_list_base_url(context, cur_track, &cur_base_url, &sequence_index); // get the track specification dash_packager_get_track_spec( &track_spec, media_set, sequence_index, cur_track->index, cur_track->media_info.media_type); // write the header p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_LIST_HEADER, media_set->segmenter_conf->segment_duration, context->clip_index == 0 ? media_set->initial_segment_clip_relative_index + 1 : 1, &cur_base_url, &conf->init_file_name_prefix, clip_spec, &track_spec, &dash_codecs[cur_track->media_info.codec_id].init_file_ext); // write the urls for (i = 0; i < segment_count; i++) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_URL, &cur_base_url, &conf->fragment_file_name_prefix, start_number + i + 1, &track_spec, &dash_codecs[cur_track->media_info.codec_id].frag_file_ext); } p = vod_copy(p, VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER, sizeof(VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER) - 1); return p; }
static u_char* dash_packager_write_mpd_period( u_char* p, u_char* base_url_temp_buffer, segment_durations_t* segment_durations, segment_duration_item_t** cur_duration_items, uint32_t clip_index, dash_manifest_config_t* conf, vod_str_t* base_url, segmenter_conf_t* segmenter_conf, media_set_t* media_set, write_tags_callback_t write_representation_tags, void* representation_tags_writer_context) { media_clip_filtered_t* cur_clip; media_sequence_t* cur_sequence; media_track_t* last_track; media_track_t* cur_track; vod_str_t representation_id; u_char representation_id_buffer[MAX_TRACK_SPEC_LENGTH]; uint32_t filtered_clip_index; uint32_t max_width = 0; uint32_t max_height = 0; uint32_t max_framerate_duration = 0; uint32_t max_framerate_timescale = 0; uint32_t segment_count = 0; representation_id.data = representation_id_buffer; if (media_set->use_discontinuity) { p = vod_sprintf(p, VOD_DASH_MANIFEST_PERIOD_DURATION_HEADER, clip_index, media_set->durations[clip_index] / 1000, media_set->durations[clip_index] % 1000); } else { p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_HEADER, sizeof(VOD_DASH_MANIFEST_PERIOD_HEADER) - 1); } // Note: clip_index can be greater than clip count when consistentSequenceMediaInfo is true filtered_clip_index = clip_index < media_set->clip_count ? clip_index : 0; // video adaptation set if (media_set->track_count[MEDIA_TYPE_VIDEO] != 0) { // get the max width, height and frame rate cur_track = media_set->filtered_tracks + filtered_clip_index * media_set->total_track_count; last_track = cur_track + media_set->total_track_count; for (; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } if (cur_track->media_info.u.video.width > max_width) { max_width = cur_track->media_info.u.video.width; } if (cur_track->media_info.u.video.height > max_height) { max_height = cur_track->media_info.u.video.height; } if (max_framerate_duration == 0 || cur_track->media_info.timescale * max_framerate_duration > max_framerate_timescale * cur_track->media_info.min_frame_duration) { max_framerate_duration = cur_track->media_info.min_frame_duration; max_framerate_timescale = cur_track->media_info.timescale; } } // print the header p = vod_sprintf(p, VOD_DASH_MANIFEST_VIDEO_HEADER, max_width, max_height, (uint32_t)max_framerate_timescale / max_framerate_duration, (uint32_t)(((uint64_t)max_framerate_timescale * 1000) / max_framerate_duration % 1000)); // print the segment template switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: p = dash_packager_write_segment_template( p, conf, segmenter_conf, base_url); break; case FORMAT_SEGMENT_TIMELINE: p = dash_packager_write_segment_timeline( p, conf, &segment_durations[MEDIA_TYPE_VIDEO], &cur_duration_items[MEDIA_TYPE_VIDEO], base_url); break; case FORMAT_SEGMENT_LIST: if (media_set->use_discontinuity) { segment_count = dash_packager_get_cur_clip_segment_count( &segment_durations[MEDIA_TYPE_VIDEO], &cur_duration_items[MEDIA_TYPE_VIDEO]); } else { segment_count = segment_durations[MEDIA_TYPE_VIDEO].segment_count; } break; } // print the representations for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_clip = &cur_sequence->filtered_clips[filtered_clip_index]; last_track = cur_clip->last_track; for (cur_track = cur_clip->first_track; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } dash_packager_get_track_spec( &representation_id, media_set, clip_index, cur_sequence->index, cur_track->index, 'v'); p = vod_sprintf(p, VOD_DASH_MANIFEST_VIDEO_PREFIX, &representation_id, &cur_track->media_info.codec_name, (uint32_t)cur_track->media_info.u.video.width, (uint32_t)cur_track->media_info.u.video.height, (uint32_t)(cur_track->media_info.timescale / cur_track->media_info.min_frame_duration), (uint32_t)(((uint64_t)cur_track->media_info.timescale * 1000) / cur_track->media_info.min_frame_duration % 1000), cur_track->media_info.bitrate ); if (conf->manifest_format == FORMAT_SEGMENT_LIST) { p = dash_packager_write_segment_list( p, conf, media_set, segmenter_conf, base_url, base_url_temp_buffer, clip_index, cur_sequence, cur_track, segment_count); } // write any additional tags if (write_representation_tags != NULL) { p = write_representation_tags(representation_tags_writer_context, p, cur_track); } p = vod_copy(p, VOD_DASH_MANIFEST_VIDEO_SUFFIX, sizeof(VOD_DASH_MANIFEST_VIDEO_SUFFIX) - 1); } } // print the footer p = vod_copy(p, VOD_DASH_MANIFEST_VIDEO_FOOTER, sizeof(VOD_DASH_MANIFEST_VIDEO_FOOTER) - 1); } // audio adaptation set if (media_set->track_count[MEDIA_TYPE_AUDIO] != 0) { // print the header p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_HEADER, sizeof(VOD_DASH_MANIFEST_AUDIO_HEADER) - 1); // print the segment template switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: p = dash_packager_write_segment_template( p, conf, segmenter_conf, base_url); break; case FORMAT_SEGMENT_TIMELINE: p = dash_packager_write_segment_timeline( p, conf, &segment_durations[MEDIA_TYPE_AUDIO], &cur_duration_items[MEDIA_TYPE_AUDIO], base_url); break; case FORMAT_SEGMENT_LIST: if (media_set->use_discontinuity) { segment_count = dash_packager_get_cur_clip_segment_count( &segment_durations[MEDIA_TYPE_AUDIO], &cur_duration_items[MEDIA_TYPE_AUDIO]); } else { segment_count = segment_durations[MEDIA_TYPE_AUDIO].segment_count; } break; } // print the representations for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_clip = &cur_sequence->filtered_clips[filtered_clip_index]; last_track = cur_clip->last_track; for (cur_track = cur_clip->first_track; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_AUDIO) { continue; } dash_packager_get_track_spec( &representation_id, media_set, clip_index, cur_sequence->index, cur_track->index, 'a'); p = vod_sprintf(p, VOD_DASH_MANIFEST_AUDIO_PREFIX, &representation_id, &cur_track->media_info.codec_name, cur_track->media_info.u.audio.sample_rate, cur_track->media_info.bitrate); if (conf->manifest_format == FORMAT_SEGMENT_LIST) { p = dash_packager_write_segment_list( p, conf, media_set, segmenter_conf, base_url, base_url_temp_buffer, clip_index, cur_sequence, cur_track, segment_count); } // write any additional tags if (write_representation_tags != NULL) { p = write_representation_tags(representation_tags_writer_context, p, cur_track); } p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_SUFFIX, sizeof(VOD_DASH_MANIFEST_AUDIO_SUFFIX) - 1); } } // print the footer p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_FOOTER, sizeof(VOD_DASH_MANIFEST_AUDIO_FOOTER) - 1); } p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_FOOTER, sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1); return p; }
static u_char* dash_packager_write_segment_list( u_char* p, dash_manifest_config_t* conf, media_set_t* media_set, segmenter_conf_t* segmenter_conf, vod_str_t* base_url, u_char* base_url_temp_buffer, uint32_t clip_index, media_sequence_t* cur_sequence, media_track_t* cur_track, uint32_t segment_count) { int media_type_char = cur_track->media_info.media_type == MEDIA_TYPE_VIDEO ? 'v' : 'a'; vod_str_t track_spec; vod_str_t cur_base_url; u_char track_spec_buffer[MAX_TRACK_SPEC_LENGTH]; uint32_t sequence_index = cur_sequence->index; uint32_t i; track_spec.data = track_spec_buffer; // build the base url if (base_url->len != 0) { cur_base_url.data = base_url_temp_buffer; base_url_temp_buffer = vod_copy(base_url_temp_buffer, base_url->data, base_url->len); if (cur_track->file_info.uri.len != 0) { base_url_temp_buffer = vod_copy(base_url_temp_buffer, cur_track->file_info.uri.data, cur_track->file_info.uri.len); sequence_index = INVALID_SEQUENCE_INDEX; // no need to pass the sequence index since we have a direct uri } else { base_url_temp_buffer = vod_copy(base_url_temp_buffer, media_set->uri.data, media_set->uri.len); } *base_url_temp_buffer++ = '/'; cur_base_url.len = base_url_temp_buffer - cur_base_url.data; } else { cur_base_url.data = NULL; cur_base_url.len = 0; } // get the track specification dash_packager_get_track_spec( &track_spec, media_set, clip_index, sequence_index, cur_track->index, media_type_char); // write the header p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_LIST_HEADER, segmenter_conf->segment_duration, &cur_base_url, &conf->init_file_name_prefix, &track_spec); // write the urls for (i = 0; i < segment_count; i++) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_URL, &cur_base_url, &conf->fragment_file_name_prefix, i + 1, &track_spec); } p = vod_copy(p, VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER, sizeof(VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER) - 1); return p; }
static u_char* dash_packager_write_mpd_period( u_char* p, write_period_context_t* context) { segment_duration_item_t** cur_duration_items; media_sequence_t* cur_sequence; adaptation_set_t* adaptation_set; media_track_t* reference_track = NULL; media_track_t** cur_track_ptr; media_track_t* cur_track; media_set_t* media_set = context->media_set; const char* lang_code; vod_str_t representation_id; vod_str_t cur_base_url; vod_str_t frame_rate; u_char representation_id_buffer[MAX_TRACK_SPEC_LENGTH]; u_char frame_rate_buffer[VOD_DASH_MAX_FRAME_RATE_LEN]; u_char clip_spec[MAX_CLIP_SPEC_LENGTH]; uint64_t clip_start_offset; uint32_t clip_duration; uint32_t filtered_clip_offset; uint32_t max_width = 0; uint32_t max_height = 0; uint32_t max_framerate_duration = 0; uint32_t segment_count = 0; uint32_t start_number; uint32_t media_type; uint32_t adapt_id = 1; uint32_t subtitle_adapt_id = 0; uint32_t sequence_index; frame_rate.data = frame_rate_buffer; representation_id.data = representation_id_buffer; if (media_set->use_discontinuity) { clip_duration = media_set->timing.durations[context->clip_index]; switch (media_set->type) { case MEDIA_SET_VOD: p = vod_sprintf(p, VOD_DASH_MANIFEST_PERIOD_HEADER_DURATION, media_set->initial_clip_index + context->clip_index, clip_duration / 1000, clip_duration % 1000); break; case MEDIA_SET_LIVE: clip_start_offset = context->clip_start_time - context->segment_base_time; if (context->clip_index + 1 < media_set->timing.total_count && media_set->timing.times[context->clip_index] + clip_duration != media_set->timing.times[context->clip_index + 1]) { // there is a gap after this clip, output start time and duration clip_duration += media_set->timing.times[context->clip_index] - context->clip_start_time; p = vod_sprintf(p, VOD_DASH_MANIFEST_PERIOD_HEADER_START_DURATION, media_set->initial_clip_index + context->clip_index, clip_start_offset / 1000, clip_start_offset % 1000, clip_duration / 1000, clip_duration % 1000); } else { // last clip / no gap, output only the start time p = vod_sprintf(p, VOD_DASH_MANIFEST_PERIOD_HEADER_START, media_set->initial_clip_index + context->clip_index, clip_start_offset / 1000, clip_start_offset % 1000); } break; } } else { switch (media_set->type) { case MEDIA_SET_VOD: p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_HEADER, sizeof(VOD_DASH_MANIFEST_PERIOD_HEADER) - 1); break; case MEDIA_SET_LIVE: p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_HEADER_START_ZERO, sizeof(VOD_DASH_MANIFEST_PERIOD_HEADER_START_ZERO) - 1); break; } } // Note: clip_index can be greater than clip count when consistentSequenceMediaInfo is true filtered_clip_offset = context->clip_index < media_set->clip_count ? context->clip_index * media_set->total_track_count : 0; dash_packager_get_clip_spec(clip_spec, media_set, context->clip_index); // print the adaptation sets for (adaptation_set = context->adaptation_sets.first, cur_duration_items = context->cur_duration_items; adaptation_set < context->adaptation_sets.last; adaptation_set++, cur_duration_items++) { media_type = adaptation_set->type; switch (media_type) { case MEDIA_TYPE_VIDEO: // get the max width, height and frame rate for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr++) { cur_track = (*cur_track_ptr) + filtered_clip_offset; if (cur_track->media_info.u.video.width > max_width) { max_width = cur_track->media_info.u.video.width; } if (cur_track->media_info.u.video.height > max_height) { max_height = cur_track->media_info.u.video.height; } if (max_framerate_duration == 0 || max_framerate_duration > cur_track->media_info.min_frame_duration) { max_framerate_duration = cur_track->media_info.min_frame_duration; } } reference_track = adaptation_set->last[-1] + filtered_clip_offset; // Note: taking the last track only for compatiblity with past versions of this module // print the header dash_packager_write_frame_rate( max_framerate_duration, DASH_TIMESCALE, &frame_rate); p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_HEADER_VIDEO, adapt_id++, max_width, max_height, &frame_rate); break; case MEDIA_TYPE_AUDIO: reference_track = (*adaptation_set->first) + filtered_clip_offset; if (context->adaptation_sets.multi_audio) { p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_HEADER_AUDIO_LANG, adapt_id++, lang_get_rfc_5646_name(reference_track->media_info.language), &reference_track->media_info.label); } else { p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_HEADER_AUDIO, adapt_id++); } break; case MEDIA_TYPE_SUBTITLE: cur_track = (*adaptation_set->first) + filtered_clip_offset; cur_sequence = cur_track->file_info.source->sequence; sequence_index = cur_sequence->index; if (context->conf->manifest_format == FORMAT_SEGMENT_LIST) { dash_packager_get_segment_list_base_url(context, cur_track, &cur_base_url, &sequence_index); } else { cur_base_url = context->base_url; } dash_packager_get_track_spec( &representation_id, media_set, sequence_index, cur_track->index, cur_track->media_info.media_type); if (representation_id.len > 0 && representation_id.data[representation_id.len - 1] == '-') { representation_id.len--; } lang_code = lang_get_rfc_5646_name(cur_track->media_info.language); p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE, lang_code, &cur_track->media_info.label, lang_code, subtitle_adapt_id++, &cur_base_url, &context->conf->subtitle_file_name_prefix, clip_spec, &representation_id); continue; } if (context->extensions.adaptation_set.write != NULL) { p = context->extensions.adaptation_set.write( context->extensions.adaptation_set.context, p, reference_track); } // get the segment index start number start_number = (*cur_duration_items)[0].segment_index; // print the segment template switch (context->conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: // increment cur_duration_items (don't really need the count) dash_packager_get_cur_clip_segment_count( &context->segment_durations[media_type], cur_duration_items); p = dash_packager_write_segment_template( p, context->conf, start_number, context->clip_index == 0 ? media_set->initial_segment_clip_relative_index : 0, clip_spec, media_set, reference_track, &context->base_url); break; case FORMAT_SEGMENT_TIMELINE: p = dash_packager_write_segment_timeline( p, context->conf, start_number, context->clip_start_time, clip_spec, reference_track, &context->segment_durations[media_type], cur_duration_items, &context->base_url); break; case FORMAT_SEGMENT_LIST: if (media_set->use_discontinuity) { segment_count = dash_packager_get_cur_clip_segment_count( &context->segment_durations[media_type], cur_duration_items); } else { segment_count = context->segment_durations[media_type].segment_count; } break; } // print the representations for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr++) { cur_track = (*cur_track_ptr) + filtered_clip_offset; cur_sequence = cur_track->file_info.source->sequence; dash_packager_get_track_spec( &representation_id, media_set, cur_sequence->index, cur_track->index, cur_track->media_info.media_type); switch (media_type) { case MEDIA_TYPE_VIDEO: dash_packager_write_frame_rate( cur_track->media_info.min_frame_duration, DASH_TIMESCALE, &frame_rate); p = vod_sprintf(p, VOD_DASH_MANIFEST_REPRESENTATION_HEADER_VIDEO, &representation_id, &dash_codecs[cur_track->media_info.codec_id].mime_type, &cur_track->media_info.codec_name, (uint32_t)cur_track->media_info.u.video.width, (uint32_t)cur_track->media_info.u.video.height, &frame_rate, cur_track->media_info.bitrate ); break; case MEDIA_TYPE_AUDIO: p = vod_sprintf(p, VOD_DASH_MANIFEST_REPRESENTATION_HEADER_AUDIO, &representation_id, &dash_codecs[cur_track->media_info.codec_id].mime_type, &cur_track->media_info.codec_name, cur_track->media_info.u.audio.sample_rate, cur_track->media_info.bitrate); break; } if (context->conf->manifest_format == FORMAT_SEGMENT_LIST) { p = dash_packager_write_segment_list( p, context, start_number, clip_spec, cur_sequence, cur_track, segment_count); } // write any additional tags if (context->extensions.representation.write != NULL) { p = context->extensions.representation.write( context->extensions.representation.context, p, cur_track); } p = vod_copy(p, VOD_DASH_MANIFEST_REPRESENTATION_FOOTER, sizeof(VOD_DASH_MANIFEST_REPRESENTATION_FOOTER) - 1); } // print the footer p = vod_copy(p, VOD_DASH_MANIFEST_ADAPTATION_FOOTER, sizeof(VOD_DASH_MANIFEST_ADAPTATION_FOOTER) - 1); } p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_FOOTER, sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1); return p; }