예제 #1
0
vod_status_t
hds_packager_build_manifest(
	request_context_t* request_context,
	hds_manifest_config_t* conf,
	vod_str_t* base_url,
	vod_str_t* manifest_id,
	media_set_t* media_set,
	bool_t drm_enabled,
	vod_str_t* result)
{
	hds_segment_durations_t* segment_durations;
	adaptation_sets_t adaptation_sets;
	adaptation_set_t* adaptation_set;
	segmenter_conf_t* segmenter_conf = media_set->segmenter_conf;
	media_sequence_t* cur_sequence;
	media_track_t** tracks;
	media_track_t** cur_track_ptr;
	media_track_t* tracks_array[MEDIA_TYPE_COUNT];
	media_track_t* track;
	vod_str_t* drm_metadata;
	uint32_t initial_muxed_tracks;
	uint32_t muxed_tracks;
	uint32_t media_count;
	uint32_t bitrate;
	uint32_t index;
	uint32_t abst_atom_size;
	uint32_t max_abst_atom_size = 0;
	size_t result_size;
	vod_status_t rc;
	u_char* temp_buffer;
	u_char* p;

	// get the adaptations sets
	rc = manifest_utils_get_adaptation_sets(
		request_context, 
		media_set, 
		ADAPTATION_SETS_FLAG_FORCE_MUXED | 
		ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK | 
		ADAPTATION_SETS_FLAG_AVOID_AUDIO_ONLY,
		&adaptation_sets);
	if (rc != VOD_OK)
	{
		return rc;
	}

	// allocate the segment durations
	media_count = adaptation_sets.first->count + adaptation_sets.total_count - 1;
	segment_durations = vod_alloc(request_context->pool, sizeof(segment_durations[0]) * media_count);
	if (segment_durations == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_packager_build_manifest: vod_alloc failed (1)");
		return VOD_ALLOC_FAILED;
	}

	// calculate the result size
	result_size = 
		sizeof(HDS_MANIFEST_HEADER) - 1 + manifest_id->len + 
		sizeof(HDS_MANIFEST_HEADER_BASE_URL) - 1 + base_url->len +
		sizeof(HDS_MANIFEST_HEADER_LANG) - 1 + LANG_ISO639_2_LEN +
		sizeof(HDS_MANIFEST_FOOTER);

	switch (media_set->type)
	{
	case MEDIA_SET_VOD:
		result_size += 
			sizeof(HDS_MANIFEST_HEADER_VOD) - 1 + 2 * VOD_INT32_LEN + 
			(sizeof(HDS_BOOTSTRAP_VOD_HEADER) - 1 + VOD_INT32_LEN +
			sizeof(HDS_BOOTSTRAP_VOD_FOOTER) - 1) * media_count;
		break;

	case MEDIA_SET_LIVE:
		result_size += 
			sizeof(HDS_MANIFEST_HEADER_LIVE) - 1 + 
			(sizeof(HDS_BOOTSTRAP_LIVE_PREFIX) - 1 + VOD_INT32_LEN +
			conf->bootstrap_file_name_prefix.len +
			MANIFEST_UTILS_TRACKS_SPEC_MAX_SIZE +
			sizeof(HDS_BOOTSTRAP_LIVE_SUFFIX) - 1) * media_count;
		break;
	}

	if (drm_enabled)
	{
		result_size += 
			(sizeof(HDS_DRM_ADDITIONAL_HEADER_PREFIX) - 1 + VOD_INT32_LEN +
			sizeof(HDS_DRM_ADDITIONAL_HEADER_SUFFIX) - 1) * media_count;
	}

	result_size +=
		(vod_max(sizeof(HDS_MEDIA_HEADER_PREFIX_VIDEO) - 1 + 3 * VOD_INT32_LEN, 
			sizeof(HDS_MEDIA_HEADER_PREFIX_AUDIO_LANG) - 1 + VOD_INT32_LEN + LANG_ISO639_2_LEN) +
		conf->fragment_file_name_prefix.len +
		MANIFEST_UTILS_TRACKS_SPEC_MAX_SIZE + 1 +		// 1 = -
		sizeof(HDS_MEDIA_HEADER_SUFFIX_DRM) - 1 + 2 * VOD_INT32_LEN +
		vod_base64_encoded_length(amf0_max_total_size) +
		sizeof(HDS_MEDIA_FOOTER) - 1) * media_count;

	initial_muxed_tracks = adaptation_sets.first->type == ADAPTATION_TYPE_MUXED ? MEDIA_TYPE_COUNT : 1;

	muxed_tracks = initial_muxed_tracks;
	index = 0;
	for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++)
	{
		if (adaptation_set->type == ADAPTATION_TYPE_MUXED)
		{
			if (adaptation_set->first[MEDIA_TYPE_AUDIO] != NULL)
			{
				result_size += adaptation_set->first[MEDIA_TYPE_AUDIO]->media_info.label.len;
			}
		}
		else
		{
			result_size += adaptation_set->first[0]->media_info.label.len;
		}

		for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks)
		{
			if (cur_track_ptr[0] != NULL)
			{
				cur_sequence = cur_track_ptr[0]->file_info.source->sequence;
			}
			else
			{
				cur_sequence = cur_track_ptr[1]->file_info.source->sequence;
			}

			switch (media_set->type)
			{
			case MEDIA_SET_VOD:
				rc = segmenter_conf->get_segment_durations(
					request_context,
					segmenter_conf,
					media_set,
					cur_sequence,		// XXXXX change to work with tracks instead of sequence
					MEDIA_TYPE_NONE,
					&segment_durations[index].durations);
				if (rc != VOD_OK)
				{
					return rc;
				}

				hds_scale_segment_durations(&segment_durations[index]);

				abst_atom_size = hds_get_abst_atom_size(media_set, &segment_durations[index]);
				if (abst_atom_size > max_abst_atom_size)
				{
					max_abst_atom_size = abst_atom_size;
				}

				result_size += vod_base64_encoded_length(abst_atom_size);

				index++;
				break;
			}

			if (drm_enabled)
			{
				drm_metadata = &((drm_info_t*)cur_sequence->drm_info)->pssh_array.first->data;

				result_size += drm_metadata->len;
			}
		}

		muxed_tracks = 1;
	}

	// allocate the buffers
	result->data = vod_alloc(request_context->pool, result_size);
	if (result->data == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_packager_build_manifest: vod_alloc failed (2)");
		return VOD_ALLOC_FAILED;
	}

	temp_buffer = vod_alloc(request_context->pool, vod_max(amf0_max_total_size, max_abst_atom_size));
	if (temp_buffer == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_packager_build_manifest: vod_alloc failed (3)");
		return VOD_ALLOC_FAILED;
	}

	// print the manifest header
	p = vod_sprintf(result->data, HDS_MANIFEST_HEADER, manifest_id);

	if (base_url->len != 0)
	{
		p = vod_sprintf(p, HDS_MANIFEST_HEADER_BASE_URL, base_url);
	}

	switch (media_set->type)
	{
	case MEDIA_SET_VOD:
		p = vod_sprintf(p, HDS_MANIFEST_HEADER_VOD, 
			(uint32_t)(media_set->timing.total_duration / 1000),
			(uint32_t)(media_set->timing.total_duration % 1000));
		break;

	case MEDIA_SET_LIVE:
		p = vod_copy(p, HDS_MANIFEST_HEADER_LIVE, sizeof(HDS_MANIFEST_HEADER_LIVE) - 1);
		break;
	}

	if (adaptation_sets.total_count > 1)
	{
		if (adaptation_sets.first->type == ADAPTATION_TYPE_MUXED)
		{
			track = adaptation_sets.first->first[MEDIA_TYPE_AUDIO];
		}
		else
		{
			track = adaptation_sets.first->first[0];
		}

		p = vod_sprintf(p, HDS_MANIFEST_HEADER_LANG, 
			&track->media_info.label,
			lang_get_iso639_2t_name(track->media_info.language));
	}

	// bootstrap tags
	muxed_tracks = initial_muxed_tracks;
	tracks_array[MEDIA_TYPE_SUBTITLE] = NULL;
	index = 0;
	for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++)
	{
		for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks)
		{
			switch (media_set->type)
			{
			case MEDIA_SET_VOD:
				p = vod_sprintf(p, HDS_BOOTSTRAP_VOD_HEADER, index);
				p = hds_write_base64_abst_atom(p, temp_buffer, media_set, &segment_durations[index]);
				p = vod_copy(p, HDS_BOOTSTRAP_VOD_FOOTER, sizeof(HDS_BOOTSTRAP_VOD_FOOTER) - 1);
				break;

			case MEDIA_SET_LIVE:
				// get the tracks
				if (muxed_tracks == 1)
				{
					if (cur_track_ptr[0]->media_info.media_type == MEDIA_TYPE_VIDEO)
					{
						tracks_array[MEDIA_TYPE_VIDEO] = cur_track_ptr[0];
						tracks_array[MEDIA_TYPE_AUDIO] = NULL;
					}
					else
					{
						tracks_array[MEDIA_TYPE_VIDEO] = NULL;
						tracks_array[MEDIA_TYPE_AUDIO] = cur_track_ptr[0];
					}
					tracks = tracks_array;
				}
				else
				{
					tracks = cur_track_ptr;
				}

				p = vod_sprintf(p, HDS_BOOTSTRAP_LIVE_PREFIX, index);
				p = vod_copy(p, conf->bootstrap_file_name_prefix.data, conf->bootstrap_file_name_prefix.len);
				p = manifest_utils_append_tracks_spec(p, tracks, MEDIA_TYPE_COUNT, media_set->has_multi_sequences);
				p = vod_copy(p, HDS_BOOTSTRAP_LIVE_SUFFIX, sizeof(HDS_BOOTSTRAP_LIVE_SUFFIX) - 1);
				break;
			}

			index++;
		}

		muxed_tracks = 1;
	}

	if (drm_enabled)
	{
		muxed_tracks = initial_muxed_tracks;
		index = 0;
		for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++)
		{
			for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks)
			{
				if (cur_track_ptr[0] != NULL)
				{
					cur_sequence = cur_track_ptr[0]->file_info.source->sequence;
				}
				else
				{
					cur_sequence = cur_track_ptr[1]->file_info.source->sequence;
				}

				drm_metadata = &((drm_info_t*)cur_sequence->drm_info)->pssh_array.first->data;

				p = vod_sprintf(p, HDS_DRM_ADDITIONAL_HEADER_PREFIX, index);
				p = vod_copy(p, drm_metadata->data, drm_metadata->len);
				p = vod_copy(p, HDS_DRM_ADDITIONAL_HEADER_SUFFIX, sizeof(HDS_DRM_ADDITIONAL_HEADER_SUFFIX) - 1);

				index++;
			}

			muxed_tracks = 1;
		}
	}

	// media tags
	muxed_tracks = initial_muxed_tracks;
	index = 0;
	for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++)
	{
		for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks)
		{
			// get the tracks
			if (muxed_tracks == 1)
			{
				if (cur_track_ptr[0]->media_info.media_type == MEDIA_TYPE_VIDEO)
				{
					tracks_array[MEDIA_TYPE_VIDEO] = cur_track_ptr[0];
					tracks_array[MEDIA_TYPE_AUDIO] = NULL;
				}
				else
				{
					tracks_array[MEDIA_TYPE_VIDEO] = NULL;
					tracks_array[MEDIA_TYPE_AUDIO] = cur_track_ptr[0];
				}
				tracks = tracks_array;
			}
			else
			{
				tracks = cur_track_ptr;
			}

			if (tracks[MEDIA_TYPE_VIDEO] != NULL)
			{
				bitrate = tracks[MEDIA_TYPE_VIDEO]->media_info.bitrate;
				if (tracks[MEDIA_TYPE_AUDIO] != NULL)
				{
					bitrate += tracks[MEDIA_TYPE_AUDIO]->media_info.bitrate;
				}

				p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_VIDEO,
					bitrate / 1000,
					(uint32_t)tracks[MEDIA_TYPE_VIDEO]->media_info.u.video.width,
					(uint32_t)tracks[MEDIA_TYPE_VIDEO]->media_info.u.video.height);
			}
			else
			{
				bitrate = tracks[MEDIA_TYPE_AUDIO]->media_info.bitrate;
				if (adaptation_sets.total_count > 1 && adaptation_set > adaptation_sets.first)
				{
					p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO_LANG,
						bitrate / 1000, 
						&tracks[MEDIA_TYPE_AUDIO]->media_info.label, 
						lang_get_iso639_2t_name(tracks[MEDIA_TYPE_AUDIO]->media_info.language));
				}
				else
				{
					p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO,
						bitrate / 1000);
				}
			}

			// url
			p = vod_copy(p, conf->fragment_file_name_prefix.data, conf->fragment_file_name_prefix.len);
			p = manifest_utils_append_tracks_spec(p, tracks, MEDIA_TYPE_COUNT, media_set->has_multi_sequences);
			*p++ = '-';

			if (drm_enabled)
			{
				p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX_DRM, index, index);
			}
			else
			{
				p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX, index);
			}

			p = hds_amf0_write_base64_metadata(p, temp_buffer, media_set, tracks);

			p = vod_copy(p, HDS_MEDIA_FOOTER, sizeof(HDS_MEDIA_FOOTER) - 1);

			index++;
		}

		muxed_tracks = 1;
	}

	// manifest footer
	p = vod_copy(p, HDS_MANIFEST_FOOTER, sizeof(HDS_MANIFEST_FOOTER) - 1);

	result->len = p - result->data;
	
	if (result->len > result_size)
	{
		vod_log_error(VOD_LOG_ERR, request_context->log, 0,
			"hds_packager_build_manifest: result length %uz exceeded allocated length %uz",
			result->len, result_size);
		return VOD_UNEXPECTED;
	}

	vod_free(request_context->pool, temp_buffer);

	return VOD_OK;
}
예제 #2
0
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;
}