vod_status_t dash_packager_build_mpd( request_context_t* request_context, dash_manifest_config_t* conf, vod_str_t* base_url, segmenter_conf_t* segmenter_conf, media_set_t* media_set, size_t representation_tags_size, write_tags_callback_t write_representation_tags, void* representation_tags_writer_context, vod_str_t* result) { segment_durations_t segment_durations[MEDIA_TYPE_COUNT]; segment_duration_item_t* cur_duration_items[MEDIA_TYPE_COUNT]; size_t base_url_temp_buffer_size = 0; size_t base_period_size; size_t result_size; size_t urls_length; uint32_t period_count = media_set->use_discontinuity ? media_set->total_clip_count : 1; uint32_t clip_index; uint32_t media_type; vod_status_t rc; u_char* base_url_temp_buffer = NULL; u_char* p; // get segment durations and count for each media type for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, NULL, media_type, &segment_durations[media_type]); if (rc != VOD_OK) { return rc; } } // remove redundant tracks dash_packager_remove_redundant_tracks( conf->duplicate_bitrate_threshold, media_set); // calculate the total size urls_length = 2 * base_url->len + conf->init_file_name_prefix.len + conf->fragment_file_name_prefix.len; base_period_size = sizeof(VOD_DASH_MANIFEST_PERIOD_DURATION_HEADER) - 1 + 3 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_VIDEO_HEADER) - 1 + 4 * VOD_INT32_LEN + media_set->track_count[MEDIA_TYPE_VIDEO] * ( sizeof(VOD_DASH_MANIFEST_VIDEO_PREFIX) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_CODEC_NAME_SIZE + 5 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_VIDEO_SUFFIX) - 1) + sizeof(VOD_DASH_MANIFEST_VIDEO_FOOTER) - 1 + sizeof(VOD_DASH_MANIFEST_AUDIO_HEADER) - 1 + media_set->track_count[MEDIA_TYPE_AUDIO] * ( sizeof(VOD_DASH_MANIFEST_AUDIO_PREFIX) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_CODEC_NAME_SIZE + 2 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_AUDIO_SUFFIX) - 1) + sizeof(VOD_DASH_MANIFEST_AUDIO_FOOTER) - 1 + sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1 + representation_tags_size; result_size = sizeof(VOD_DASH_MANIFEST_HEADER) - 1 + 3 * VOD_INT32_LEN + base_period_size * period_count + sizeof(VOD_DASH_MANIFEST_FOOTER); switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED) - 1 + urls_length + VOD_INT64_LEN) * MEDIA_TYPE_COUNT * period_count; break; case FORMAT_SEGMENT_TIMELINE: for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER) - 1 + urls_length + sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER) - 1) * period_count + (sizeof(VOD_DASH_MANIFEST_SEGMENT_REPEAT) - 1 + 2 * VOD_INT32_LEN) * segment_durations[media_type].item_count; } break; case FORMAT_SEGMENT_LIST: result_size += dash_packager_get_segment_list_total_size( conf, media_set, segment_durations, base_url, &base_url_temp_buffer_size); break; } // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_mpd: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } if (base_url_temp_buffer_size != 0) { base_url_temp_buffer = vod_alloc(request_context->pool, base_url_temp_buffer_size); if (base_url_temp_buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_mpd: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } } // print the manifest header p = vod_sprintf(result->data, VOD_DASH_MANIFEST_HEADER, (uint32_t)(media_set->total_duration / 1000), (uint32_t)(media_set->total_duration % 1000), (uint32_t)(segmenter_conf->max_segment_duration / 1000)); cur_duration_items[MEDIA_TYPE_VIDEO] = segment_durations[MEDIA_TYPE_VIDEO].items; cur_duration_items[MEDIA_TYPE_AUDIO] = segment_durations[MEDIA_TYPE_AUDIO].items; for (clip_index = 0; clip_index < period_count; clip_index++) { p = dash_packager_write_mpd_period( p, base_url_temp_buffer, segment_durations, cur_duration_items, clip_index, conf, base_url, segmenter_conf, media_set, write_representation_tags, representation_tags_writer_context); } p = vod_copy(p, VOD_DASH_MANIFEST_FOOTER, sizeof(VOD_DASH_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "dash_packager_build_mpd: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t dash_packager_build_mpd( request_context_t* request_context, dash_manifest_config_t* conf, vod_str_t* base_url, media_set_t* media_set, dash_manifest_extensions_t* extensions, vod_str_t* result) { segment_duration_item_t** cur_duration_items; write_period_context_t context; adaptation_set_t* adaptation_set; segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; media_track_t* cur_track; vod_tm_t publish_time_gmt; vod_tm_t avail_time_gmt; vod_tm_t cur_time_gmt; time_t current_time; size_t base_url_temp_buffer_size = 0; size_t base_period_size; size_t result_size = 0; size_t urls_length; uint32_t filtered_clip_offset; uint32_t presentation_delay; uint32_t min_update_period; uint32_t window_size; uint32_t period_count = media_set->use_discontinuity ? media_set->timing.total_count : 1; uint32_t media_type; uint32_t clip_index; vod_status_t rc; u_char* p = NULL; // remove redundant tracks dash_packager_remove_redundant_tracks( conf->duplicate_bitrate_threshold, media_set); // get the adaptation sets rc = manifest_utils_get_adaptation_sets( request_context, media_set, ADAPTATION_SETS_FLAG_MULTI_CODEC, &context.adaptation_sets); if (rc != VOD_OK) { return rc; } // get segment durations and count for each media type for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, NULL, media_type, &context.segment_durations[media_type]); if (rc != VOD_OK) { return rc; } } // get the base url if (base_url->len != 0) { if (conf->use_base_url_tag) { result_size += sizeof(VOD_DASH_MANIFEST_BASEURL) - 1 + base_url->len; context.base_url.data = NULL; context.base_url.len = 0; } else { context.base_url = *base_url; } } else { context.base_url.data = NULL; context.base_url.len = 0; } // calculate the total size urls_length = 2 * context.base_url.len + 2 * MAX_FILE_EXT_SIZE + conf->init_file_name_prefix.len + MAX_CLIP_SPEC_LENGTH + conf->fragment_file_name_prefix.len; base_period_size = sizeof(VOD_DASH_MANIFEST_PERIOD_HEADER_START_DURATION) - 1 + 5 * VOD_INT32_LEN + // video adaptations (sizeof(VOD_DASH_MANIFEST_ADAPTATION_HEADER_VIDEO) - 1 + 3 * VOD_INT32_LEN + VOD_DASH_MAX_FRAME_RATE_LEN + sizeof(VOD_DASH_MANIFEST_ADAPTATION_FOOTER) - 1) * context.adaptation_sets.count[ADAPTATION_TYPE_VIDEO] + // video representations (sizeof(VOD_DASH_MANIFEST_REPRESENTATION_HEADER_VIDEO) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_MIME_TYPE_SIZE + MAX_CODEC_NAME_SIZE + 3 * VOD_INT32_LEN + VOD_DASH_MAX_FRAME_RATE_LEN + sizeof(VOD_DASH_MANIFEST_REPRESENTATION_FOOTER) - 1) * media_set->track_count[MEDIA_TYPE_VIDEO] + // audio adaptations (sizeof(VOD_DASH_MANIFEST_ADAPTATION_HEADER_AUDIO_LANG) - 1 + VOD_INT32_LEN + LANG_ISO639_3_LEN + sizeof(VOD_DASH_MANIFEST_ADAPTATION_FOOTER) - 1) * context.adaptation_sets.count[ADAPTATION_TYPE_AUDIO] + // audio representations (sizeof(VOD_DASH_MANIFEST_REPRESENTATION_HEADER_AUDIO) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_MIME_TYPE_SIZE + MAX_CODEC_NAME_SIZE + 2 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_REPRESENTATION_FOOTER) - 1) * media_set->track_count[MEDIA_TYPE_AUDIO] + // subtitle adaptations (sizeof(VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE) - 1 + 2 * LANG_ISO639_3_LEN + VOD_INT32_LEN + context.base_url.len + conf->subtitle_file_name_prefix.len + MAX_CLIP_SPEC_LENGTH + MAX_TRACK_SPEC_LENGTH) * context.adaptation_sets.count[ADAPTATION_TYPE_SUBTITLE] + sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1 + extensions->representation.size + extensions->adaptation_set.size; switch (media_set->type) { case MEDIA_SET_VOD: result_size += sizeof(VOD_DASH_MANIFEST_HEADER_VOD) - 1 + 3 * VOD_INT32_LEN + conf->profiles.len; break; case MEDIA_SET_LIVE: result_size += sizeof(VOD_DASH_MANIFEST_HEADER_LIVE) - 1 + 8 * VOD_INT32_LEN + 18 * VOD_INT64_LEN + conf->profiles.len; break; } result_size += base_period_size * period_count + sizeof(VOD_DASH_MANIFEST_FOOTER); for (clip_index = 0; clip_index < period_count; clip_index++) { filtered_clip_offset = clip_index < media_set->clip_count ? clip_index * media_set->total_track_count : 0; for (adaptation_set = context.adaptation_sets.first; adaptation_set < context.adaptation_sets.last; adaptation_set++) { switch (adaptation_set->type) { case MEDIA_TYPE_AUDIO: case MEDIA_TYPE_SUBTITLE: cur_track = (*adaptation_set->first) + filtered_clip_offset; result_size += cur_track->media_info.label.len; break; } } } switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED) - 1 + VOD_INT32_LEN + VOD_INT64_LEN + MAX_INDEX_SHIFT_LENGTH + urls_length) * (context.adaptation_sets.count[MEDIA_TYPE_VIDEO] + context.adaptation_sets.count[MEDIA_TYPE_AUDIO]) * period_count; break; case FORMAT_SEGMENT_TIMELINE: for (media_type = 0; media_type < MEDIA_TYPE_SUBTITLE; media_type++) { if (context.adaptation_sets.count[media_type] == 0) { continue; } result_size += ((sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER) - 1 + VOD_INT32_LEN + urls_length + sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER) - 1 + sizeof(VOD_DASH_MANIFEST_SEGMENT_REPEAT_TIME) - 1 + VOD_INT64_LEN) * period_count + (sizeof(VOD_DASH_MANIFEST_SEGMENT_REPEAT) - 1 + 2 * VOD_INT32_LEN) * context.segment_durations[media_type].item_count) * context.adaptation_sets.count[media_type]; } break; case FORMAT_SEGMENT_LIST: result_size += dash_packager_get_segment_list_total_size( conf, media_set, context.segment_durations, &context.base_url, &base_url_temp_buffer_size); break; } // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_mpd: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } context.base_url_temp_buffer = vod_alloc(request_context->pool, base_url_temp_buffer_size + sizeof(context.cur_duration_items[0]) * context.adaptation_sets.total_count); if (context.base_url_temp_buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_mpd: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } // initialize the duration items pointers to the beginning (according to the media type) context.cur_duration_items = (void*)(context.base_url_temp_buffer + base_url_temp_buffer_size); 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++) { *cur_duration_items = context.segment_durations[adaptation_set->type].items; } // initialize the context if (media_set->timing.segment_base_time != SEGMENT_BASE_TIME_RELATIVE) { context.segment_base_time = media_set->timing.segment_base_time; } else { context.segment_base_time = 0; } if (media_set->use_discontinuity) { context.clip_start_time = media_set->timing.original_first_time; } else { context.clip_start_time = context.segment_base_time; } context.clip_index = 0; context.conf = conf; context.media_set = media_set; context.extensions = *extensions; // print the manifest header switch (media_set->type) { case MEDIA_SET_VOD: p = vod_sprintf(result->data, VOD_DASH_MANIFEST_HEADER_VOD, (uint32_t)(media_set->timing.total_duration / 1000), (uint32_t)(media_set->timing.total_duration % 1000), (uint32_t)(segmenter_conf->max_segment_duration / 1000), &conf->profiles); break; case MEDIA_SET_LIVE: media_type = media_set->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO; window_size = context.segment_durations[media_type].duration; min_update_period = segmenter_conf->segment_duration / 2; vod_gmtime(context.segment_base_time / 1000, &avail_time_gmt); vod_gmtime(context.segment_durations[media_type].end_time / 1000, &publish_time_gmt); current_time = vod_time(request_context); vod_gmtime(current_time, &cur_time_gmt); presentation_delay = dash_packager_get_presentation_delay( (uint64_t)current_time * 1000, &context.segment_durations[media_type]); p = vod_sprintf(result->data, VOD_DASH_MANIFEST_HEADER_LIVE, (uint32_t)(min_update_period / 1000), (uint32_t)(min_update_period % 1000), avail_time_gmt.vod_tm_year, avail_time_gmt.vod_tm_mon, avail_time_gmt.vod_tm_mday, avail_time_gmt.vod_tm_hour, avail_time_gmt.vod_tm_min, avail_time_gmt.vod_tm_sec, publish_time_gmt.vod_tm_year, publish_time_gmt.vod_tm_mon, publish_time_gmt.vod_tm_mday, publish_time_gmt.vod_tm_hour, publish_time_gmt.vod_tm_min, publish_time_gmt.vod_tm_sec, (uint32_t)(window_size / 1000), (uint32_t)(window_size % 1000), (uint32_t)(segmenter_conf->max_segment_duration / 1000), (uint32_t)(segmenter_conf->max_segment_duration % 1000), (uint32_t)(presentation_delay / 1000), (uint32_t)(presentation_delay % 1000), &conf->profiles, cur_time_gmt.vod_tm_year, cur_time_gmt.vod_tm_mon, cur_time_gmt.vod_tm_mday, cur_time_gmt.vod_tm_hour, cur_time_gmt.vod_tm_min, cur_time_gmt.vod_tm_sec); break; } if (conf->use_base_url_tag && base_url->len != 0) { p = vod_sprintf(p, VOD_DASH_MANIFEST_BASEURL, base_url); } for (;;) { p = dash_packager_write_mpd_period( p, &context); context.clip_index++; if (context.clip_index >= period_count) { break; } context.clip_start_time = media_set->timing.times[context.clip_index]; } p = vod_copy(p, VOD_DASH_MANIFEST_FOOTER, sizeof(VOD_DASH_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "dash_packager_build_mpd: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }