Esempio n. 1
0
static u_char*
m3u8_builder_append_extinf_tag(u_char* p, uint32_t duration, uint32_t scale)
{
	p = vod_copy(p, "#EXTINF:", sizeof("#EXTINF:") - 1);
	p = m3u8_builder_format_double(p, duration, scale);
	*p++ = ',';
	*p++ = '\n';
	return p;
}
Esempio n. 2
0
static u_char*
edash_packager_write_pssh(void* context, u_char* p)
{
	mp4_encrypt_system_info_array_t* pssh_array = (mp4_encrypt_system_info_array_t*)context;
	mp4_encrypt_system_info_t* cur_info;
	size_t pssh_atom_size;

	for (cur_info = pssh_array->first; cur_info < pssh_array->last; cur_info++)
	{
		pssh_atom_size = ATOM_HEADER_SIZE + sizeof(pssh_atom_t) + cur_info->data.len;

		write_atom_header(p, pssh_atom_size, 'p', 's', 's', 'h');
		write_dword(p, 0);						// version + flags
		p = vod_copy(p, cur_info->system_id, MP4_ENCRYPT_SYSTEM_ID_SIZE);	// system id
		write_dword(p, cur_info->data.len);		// data size
		p = vod_copy(p, cur_info->data.data, cur_info->data.len);
	}

	return p;
}
static u_char*
mss_playready_audio_write_uuid_piff_atom(u_char* p, mp4_encrypt_state_t* state, media_sequence_t* sequence, size_t atom_size)
{
	write_atom_header(p, atom_size, 'u', 'u', 'i', 'd');
	p = vod_copy(p, piff_uuid, sizeof(piff_uuid));
	write_be32(p, 0);
	write_be32(p, sequence->total_frame_count);
	p = mp4_encrypt_audio_write_auxiliary_data(state, p);

	return p;
}
Esempio n. 4
0
static u_char*
mss_write_uuid_tfxd_atom(u_char* p, segment_timing_info_t* timing_info)
{
	size_t atom_size = ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t);

	write_atom_header(p, atom_size, 'u', 'u', 'i', 'd');
	p = vod_copy(p, tfxd_uuid, sizeof(tfxd_uuid));
	write_be32(p, 0x01000000);		// version / flags
	write_be64(p, timing_info->timestamp);
	write_be64(p, timing_info->duration);
	return p;
}
static u_char*
mss_playready_write_protection_tag(void* context, u_char* p, media_set_t* media_set)
{
	// Note: taking only the first sequence, in mss all renditions must have the same key
	drm_info_t* drm_info = (drm_info_t*)media_set->sequences[0].drm_info;
	drm_system_info_t* cur_info;
	vod_str_t base64;

	p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_PREFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_PREFIX) - 1);
	for (cur_info = drm_info->pssh_array.first; cur_info < drm_info->pssh_array.last; cur_info++)
	{
		p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_PREFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_PREFIX) - 1);
		p = mp4_encrypt_write_guid(p, cur_info->system_id);
		p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_DELIMITER, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_DELIMITER) - 1);
		base64.data = p;
		vod_encode_base64(&base64, &cur_info->data);
		p += base64.len;
		p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_SUFFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_SUFFIX) - 1);
	}
	p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_SUFFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_SUFFIX) - 1);
	return p;
}
Esempio n. 6
0
static u_char*
mss_write_uuid_tfxd_atom(u_char* p, mpeg_stream_metadata_t* stream_metadata)
{
	size_t atom_size = ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t);
	uint64_t timestamp = rescale_time(stream_metadata->first_frame_time_offset, stream_metadata->media_info.timescale, MSS_TIMESCALE);
	uint64_t duration = rescale_time(stream_metadata->total_frames_duration, stream_metadata->media_info.timescale, MSS_TIMESCALE);

	write_atom_header(p, atom_size, 'u', 'u', 'i', 'd');
	p = vod_copy(p, tfxd_uuid, sizeof(tfxd_uuid));
	write_dword(p, 0x01000000);		// version / flags
	write_qword(p, timestamp);
	write_qword(p, duration);
	return p;
}
static u_char*
mss_playready_passthrough_write_encryption_atoms(void* ctx, u_char* p, size_t mdat_atom_start)
{
	mp4_encrypt_passthrough_context_t* context = ctx;
	media_clip_filtered_t* cur_clip;
	media_sequence_t* sequence = context->sequence;
	media_track_t* cur_track;
	size_t auxiliary_data_offset;
	size_t uuid_atom_size;
	uint32_t flags;

	// uuid piff
	uuid_atom_size = ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t) + context->auxiliary_info_size;
	write_atom_header(p, uuid_atom_size, 'u', 'u', 'i', 'd');
	p = vod_copy(p, piff_uuid, sizeof(piff_uuid));
	flags = context->use_subsamples ? 0x2 : 0x0;
	write_be32(p, flags);		// flags
	write_be32(p, sequence->total_frame_count);
	for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++)
	{
		cur_track = cur_clip->first_track;
		p = vod_copy(p,
			cur_track->encryption_info.auxiliary_info,
			cur_track->encryption_info.auxiliary_info_end - cur_track->encryption_info.auxiliary_info);
	}

	// saiz / saio
	auxiliary_data_offset = mdat_atom_start -
		(context->auxiliary_info_size +
		context->saiz_atom_size +
		context->saio_atom_size);

	p = mp4_encrypt_passthrough_write_saiz_saio(ctx, p, auxiliary_data_offset);

	return p;
}
static u_char* 
dash_packager_write_segment_template(
	u_char* p, 
	dash_manifest_config_t* conf,
	segmenter_conf_t* segmenter_conf,
	vod_str_t* base_url,
	segment_durations_t* segment_durations)
{
	segment_duration_item_t* cur_item;
	segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count;
	uint32_t duration;

	if (!conf->segment_timeline)
	{
		return vod_sprintf(p,
			VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED,
			base_url,
			&conf->fragment_file_name_prefix,
			base_url,
			&conf->init_file_name_prefix,
			segmenter_conf->segment_duration);
	}

	p = vod_sprintf(p,
		VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER,
		base_url,
		&conf->fragment_file_name_prefix,
		base_url,
		&conf->init_file_name_prefix);

	for (cur_item = segment_durations->items; cur_item < last_item; cur_item++)
	{
		duration = (uint32_t)rescale_time(cur_item->duration, segment_durations->timescale, 1000);

		if (cur_item->repeat_count == 1)
		{
			p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT, duration);
		}
		else if (cur_item->repeat_count > 1)
		{
			p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_REPEAT, duration, cur_item->repeat_count - 1);
		}
	}

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

	return p;
}
Esempio n. 9
0
static u_char* 
dash_packager_write_segment_timeline(
	u_char* p, 
	dash_manifest_config_t* conf,
	segment_durations_t* segment_durations,
	segment_duration_item_t** cur_item_ptr,
	vod_str_t* base_url)
{
	segment_duration_item_t* cur_item;
	segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count;
	uint32_t duration;
	bool_t first_time = TRUE;

	p = vod_sprintf(p,
		VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER,
		base_url,
		&conf->fragment_file_name_prefix,
		base_url,
		&conf->init_file_name_prefix);

	for (cur_item = *cur_item_ptr; cur_item < last_item; cur_item++)
	{
		// stop on discontinuity, will get called again for the next period
		if (cur_item->discontinuity && !first_time)
		{
			break;
		}

		first_time = FALSE;

		duration = (uint32_t)rescale_time(cur_item->duration, segment_durations->timescale, 1000);

		if (cur_item->repeat_count == 1)
		{
			p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT, duration);
		}
		else if (cur_item->repeat_count > 1)
		{
			p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_REPEAT, duration, cur_item->repeat_count - 1);
		}
	}

	*cur_item_ptr = cur_item;

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

	return p;
}
Esempio n. 10
0
u_char*
mp4_encrypt_audio_write_auxiliary_data(mp4_encrypt_state_t* state, u_char* p)
{
	u_char* end_pos = p + sizeof(state->iv) * state->sequence->total_frame_count;
	u_char iv[MP4_AES_CTR_IV_SIZE];

	vod_memcpy(iv, state->iv, sizeof(iv));

	while (p < end_pos)
	{
		p = vod_copy(p, iv, sizeof(iv));
		mp4_aes_ctr_increment_be64(iv);
	}

	return p;
}
static u_char* 
edash_packager_write_content_protection(void* context, u_char* p, media_track_t* track)
{
	drm_info_t* drm_info = (drm_info_t*)track->file_info.drm_info;
	drm_system_info_t* cur_info;
	vod_str_t base64;
	vod_str_t pssh;

	p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC) - 1);
	for (cur_info = drm_info->pssh_array.first; cur_info < drm_info->pssh_array.last; cur_info++)
	{
		if (vod_memcmp(cur_info->system_id, edash_playready_system_id, sizeof(edash_playready_system_id)) == 0)
		{
			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART1, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART1) - 1);
			p = mp4_encrypt_write_guid(p, cur_info->system_id);
			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART2, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART2) - 1);

			base64.data = p;
			vod_encode_base64(&base64, &cur_info->data);
			p += base64.len;

			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3) - 1);
		}
		else
		{
			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1) - 1);
			p = mp4_encrypt_write_guid(p, cur_info->system_id);
			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2) - 1);
			p = mp4_encrypt_write_guid(p, drm_info->key_id);
			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3) - 1);

			pssh.data = (u_char*)context;
			pssh.len = edash_packager_write_pssh(pssh.data, cur_info) - pssh.data;

			base64.data = p;
			vod_encode_base64(&base64, &pssh);
			p += base64.len;

			p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4) - 1);
		}
	}
	return p;
}
Esempio n. 12
0
static u_char*
edash_packager_video_write_encryption_atoms(void* context, u_char* p, size_t mdat_atom_start)
{
	mp4_encrypt_video_state_t* state = (mp4_encrypt_video_state_t*)context;
	size_t senc_data_size = state->auxiliary_data.pos - state->auxiliary_data.start;
	size_t senc_atom_size = ATOM_HEADER_SIZE + sizeof(senc_atom_t) + senc_data_size;

	// saiz / saio
	p = mp4_encrypt_video_write_saiz_saio(state, p, mdat_atom_start - senc_data_size);

	// senc
	write_atom_header(p, senc_atom_size, 's', 'e', 'n', 'c');
	write_be32(p, 0x2);		// flags
	write_be32(p, state->base.sequence->total_frame_count);
	p = vod_copy(p, state->auxiliary_data.start, senc_data_size);

	return p;
}
Esempio n. 13
0
u_char*
mp4_encrypt_video_write_saiz_saio(mp4_encrypt_video_state_t* state, u_char* p, size_t auxiliary_data_offset)
{
	// moof.traf.saiz
	write_atom_header(p, state->base.saiz_atom_size, 's', 'a', 'i', 'z');
	write_be32(p, 0);			// version, flags
	*p++ = state->default_auxiliary_sample_size;
	write_be32(p, state->saiz_sample_count);
	if (state->default_auxiliary_sample_size == 0)
	{
		p = vod_copy(p, state->auxiliary_sample_sizes, state->saiz_sample_count);
	}

	// moof.traf.saio
	write_atom_header(p, state->base.saio_atom_size, 's', 'a', 'i', 'o');
	write_be32(p, 0);			// version, flags
	write_be32(p, 1);			// entry count
	write_be32(p, auxiliary_data_offset);

	return p;
}
Esempio n. 14
0
static u_char*
hds_muxer_write_codec_config(u_char* p, hds_muxer_state_t* state, uint64_t cur_frame_dts)
{
	media_track_t* cur_track;
	hds_muxer_stream_state_t* cur_stream;
	uint32_t packet_size;
	u_char* packet_start;

	for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++)
	{
		cur_track = cur_stream->track;
		packet_start = p;
		switch (cur_track->media_info.media_type)
		{
		case MEDIA_TYPE_VIDEO:
			p = hds_write_video_tag_header(
				p,
				cur_track->media_info.extra_data_size,
				cur_frame_dts,
				FRAME_TYPE_KEY_FRAME,
				AVC_PACKET_TYPE_SEQUENCE_HEADER,
				0);
			break;

		case MEDIA_TYPE_AUDIO:
			p = hds_write_audio_tag_header(
				p,
				cur_track->media_info.extra_data_size,
				cur_frame_dts,
				cur_stream->sound_info,
				AAC_PACKET_TYPE_SEQUENCE_HEADER);
			break;
		}
		p = vod_copy(p, cur_track->media_info.extra_data, cur_track->media_info.extra_data_size);
		packet_size = p - packet_start;
		write_be32(p, packet_size);
	}
	return p;
}
Esempio n. 15
0
static u_char*
hds_muxer_write_codec_config(u_char* p, hds_muxer_state_t* state, uint64_t cur_frame_dts)
{
	mpeg_stream_metadata_t* cur_stream;
	hds_muxer_stream_state_t* stream_state;
	size_t packet_size;

	for (stream_state = state->first_stream; stream_state < state->last_stream; stream_state++)
	{
		cur_stream = stream_state->metadata;
		packet_size = sizeof(adobe_mux_packet_header_t)+cur_stream->media_info.extra_data_size;
		switch (cur_stream->media_info.media_type)
		{
		case MEDIA_TYPE_VIDEO:
			p = hds_write_video_tag_header(
				p,
				cur_stream->media_info.extra_data_size,
				cur_frame_dts,
				FRAME_TYPE_KEY_FRAME,
				AVC_PACKET_TYPE_SEQUENCE_HEADER,
				0);
			packet_size += sizeof(video_tag_header_avc);
			break;

		case MEDIA_TYPE_AUDIO:
			p = hds_write_audio_tag_header(
				p,
				cur_stream->media_info.extra_data_size,
				cur_frame_dts,
				stream_state->sound_info,
				AAC_PACKET_TYPE_SEQUENCE_HEADER);
			packet_size += sizeof(audio_tag_header_aac);
			break;
		}
		p = vod_copy(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size);
		write_dword(p, packet_size);
	}
	return p;
}
Esempio n. 16
0
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;
}
static vod_status_t
manifest_utils_build_request_params_string_per_sequence_tracks(
	request_context_t* request_context,
	uint32_t segment_index,
	uint32_t sequences_mask,
	uint32_t* sequence_tracks_mask,
	vod_str_t* result)
{
	u_char* p;
	size_t result_size;
	uint32_t* tracks_mask;
	uint32_t i;

	result_size = 0;

	// segment index
	if (segment_index != INVALID_SEGMENT_INDEX)
	{
		result_size += 1 + vod_get_int_print_len(segment_index + 1);
	}

	for (i = 0, tracks_mask = sequence_tracks_mask;
		i < MAX_SEQUENCES;
		i++, tracks_mask += MEDIA_TYPE_COUNT)
	{
		if ((sequences_mask & (1 << i)) == 0)
		{
			continue;
		}

		// sequence
		result_size += sizeof("-f32") - 1;

		// video tracks
		if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff)
		{
			result_size += sizeof("-v0") - 1;
		}
		else
		{
			result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1);
		}

		// audio tracks
		if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff)
		{
			result_size += sizeof("-a0") - 1;
		}
		else
		{
			result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1);
		}
	}

	p = vod_alloc(request_context->pool, result_size + 1);
	if (p == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"manifest_utils_build_request_params_string_per_sequence_tracks: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}
	result->data = p;

	// segment index
	if (segment_index != INVALID_SEGMENT_INDEX)
	{
		p = vod_sprintf(p, "-%uD", segment_index + 1);
	}

	for (i = 0, tracks_mask = sequence_tracks_mask;
		i < MAX_SEQUENCES;
		i++, tracks_mask += MEDIA_TYPE_COUNT)
	{
		if ((sequences_mask & (1 << i)) == 0)
		{
			continue;
		}

		// sequence
		p = vod_sprintf(p, "-f%uD", i + 1);

		// video tracks
		switch (tracks_mask[MEDIA_TYPE_VIDEO])
		{
		case 0xffffffff:
			p = vod_copy(p, "-v0", sizeof("-v0") - 1);
			break;

		case 0:
			break;

		default:
			p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_VIDEO], 'v');
			break;
		}

		// audio tracks
		switch (tracks_mask[MEDIA_TYPE_AUDIO])
		{
		case 0xffffffff:
			p = vod_copy(p, "-a0", sizeof("-a0") - 1);
			break;

		case 0:
			break;

		default:
			p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_AUDIO], 'a');
			break;
		}
	}

	result->len = p - result->data;

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

	return VOD_OK;
}
vod_status_t
manifest_utils_build_request_params_string(
	request_context_t* request_context,
	uint32_t* has_tracks,
	uint32_t segment_index,
	uint32_t sequences_mask,
	uint32_t* sequence_tracks_mask,
	uint32_t* tracks_mask,
	vod_str_t* suffix,
	vod_str_t* args_str,
	vod_str_t* result)
{
	u_char* p;
	size_t result_size;
	bool_t newline_shift = FALSE;

	if (sequence_tracks_mask != NULL)
	{
		return manifest_utils_build_request_params_string_per_sequence_tracks(
			request_context,
			segment_index,
			sequences_mask,
			sequence_tracks_mask,
			result);
	}

	result_size = suffix->len + args_str->len;

	if (suffix->data[suffix->len - 1] == '\n')
	{
		newline_shift = TRUE;
	}

	// segment index
	if (segment_index != INVALID_SEGMENT_INDEX)
	{
		result_size += 1 + vod_get_int_print_len(segment_index + 1);
	}

	// sequence mask
	if (sequences_mask != 0xffffffff)
	{
		 result_size += vod_get_number_of_set_bits(sequences_mask) * (sizeof("-f32") - 1);
	}

	// video tracks
	if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff)
	{
		result_size += sizeof("-v0") - 1;
	}
	else
	{
		result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1);
	}

	// audio tracks
	if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff)
	{
		result_size += sizeof("-a0") - 1;
	}
	else
	{
		result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1);
	}

	p = vod_alloc(request_context->pool, result_size + 1);
	if (p == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"manifest_utils_build_request_params_string: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}
	result->data = p;

	// segment index
	if (segment_index != INVALID_SEGMENT_INDEX)
	{
		p = vod_sprintf(p, "-%uD", segment_index + 1);
	}

	// sequence mask
	if (sequences_mask != 0xffffffff)
	{
		p = manifest_utils_write_bitmask(p, sequences_mask, 'f');
	}

	// video tracks
	if (has_tracks[MEDIA_TYPE_VIDEO])
	{
		if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff)
		{
			p = vod_copy(p, "-v0", sizeof("-v0") - 1);
		}
		else
		{
			p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_VIDEO], 'v');
		}
	}

	// audio tracks
	if (has_tracks[MEDIA_TYPE_AUDIO])
	{
		if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff)
		{
			p = vod_copy(p, "-a0", sizeof("-a0") - 1);
		}
		else
		{
			p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_AUDIO], 'a');
		}
	}

	p = vod_copy(p, suffix->data, newline_shift ? suffix->len - 1 : suffix->len);
	p = vod_copy(p, args_str->data, args_str->len);

	if (newline_shift)
	{
		*p++ = '\n';
	}

	result->len = p - result->data;

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

	return VOD_OK;
}
Esempio n. 19
0
vod_status_t
m3u8_builder_build_master_playlist(
	request_context_t* request_context,
	m3u8_config_t* conf,
	vod_str_t* base_url,
	media_set_t* media_set,
	vod_str_t* result)
{
	media_sequence_t* cur_sequence;	
	media_info_t* video;
	media_info_t* audio = NULL;
	uint32_t sequence_index;
	uint32_t bitrate;
	u_char* p;
	size_t max_video_stream_inf;
	size_t result_size;
	uint32_t main_media_type;
	media_track_t* cur_track;
	media_track_t* audio_track;

	// calculate the result size
	max_video_stream_inf =
		sizeof(m3u8_stream_inf_video) - 1 + 3 * VOD_INT32_LEN + MAX_CODEC_NAME_SIZE +
		MAX_CODEC_NAME_SIZE + 1 +
		sizeof(m3u8_stream_inf_suffix) - 1;
	result_size = sizeof(m3u8_header);	
	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		main_media_type = cur_sequence->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO;
		for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < cur_sequence->filtered_clips[0].last_track; cur_track++)
		{			
			if (cur_track->media_info.media_type != main_media_type)
			{
				continue;
			}
			if (base_url->len != 0)
			{
				result_size += base_url->len + 1;
				if (cur_track->file_info.uri.len > 0)
				{
					result_size += cur_track->file_info.uri.len;
				}
				else
				{
					result_size += media_set->uri.len;
				}
			}
			result_size += max_video_stream_inf; // using only video since it's larger than audio
			result_size += conf->index_file_name_prefix.len;
			result_size += sizeof("-f-v-a") - 1 + VOD_INT32_LEN * 3;
			result_size += sizeof(m3u8_url_suffix) - 1;
		}
	}

	// 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,
			"m3u8_builder_build_master_playlist: vod_alloc failed (2)");
		return VOD_ALLOC_FAILED;
	}

	// write the header

	p = vod_copy(result->data, m3u8_header, sizeof(m3u8_header) - 1);
	// write the streams
	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		audio_track = cur_sequence->filtered_clips[0].longest_track[MEDIA_TYPE_AUDIO];
		main_media_type = cur_sequence->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO;
		for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < cur_sequence->filtered_clips[0].last_track; cur_track++)
		{
			if (cur_track->media_info.media_type != main_media_type)
			{
				continue;
			}
			// write the track information
			if (main_media_type == MEDIA_TYPE_VIDEO)
			{
				video = &cur_track->media_info;
				bitrate = video->bitrate;
				if (audio_track != NULL)
				{
					audio = &audio_track->media_info;
					bitrate += audio->bitrate;
				}
				p = vod_sprintf(p, m3u8_stream_inf_video,
						bitrate,
						(uint32_t)video->u.video.width,
						(uint32_t)video->u.video.height,
						&video->codec_name);
				if (audio_track != NULL)
				{
					*p++ = ',';
					p = vod_copy(p, audio->codec_name.data, audio->codec_name.len);
				}
			}
			else
			{
				audio = &cur_track->media_info;
				p = vod_sprintf(p, m3u8_stream_inf_audio, audio->bitrate, &audio->codec_name);
			}
			p = vod_copy(p, m3u8_stream_inf_suffix, sizeof(m3u8_stream_inf_suffix) - 1);
			// write the track url
			sequence_index = cur_sequence->index;
			if (base_url->len != 0)
			{
				// absolute url only
				p = vod_copy(p, base_url->data, base_url->len);
				if (cur_track->file_info.uri.len != 0)
				{
					p = vod_copy(p, 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
				{
					p = vod_copy(p, media_set->uri.data, media_set->uri.len);
				}
				*p++ = '/';
			}

			p = vod_copy(p, conf->index_file_name_prefix.data, conf->index_file_name_prefix.len);
			if (media_set->has_multi_sequences && sequence_index != INVALID_SEQUENCE_INDEX)
			{
				p = vod_sprintf(p, "-f%uD", cur_sequence->index + 1);
			}

			if (main_media_type == MEDIA_TYPE_VIDEO)
			{
				p = vod_sprintf(p, "-v%uD", cur_track->index + 1);
			}

			if (audio_track != NULL)
			{
				p = vod_sprintf(p, "-a%uD", audio_track->index + 1);
			}

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

		}
	}

	result->len = p - result->data;

	if (result->len > result_size)
	{
		vod_log_error(VOD_LOG_ERR, request_context->log, 0,
			"m3u8_builder_build_master_playlist: result length %uz exceeded allocated length %uz",
			result->len, result_size);
		return VOD_UNEXPECTED;
	}
	return VOD_OK;
}
Esempio n. 20
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;
}
Esempio n. 21
0
vod_status_t
hds_packager_build_manifest(
	request_context_t* request_context,
	hds_manifest_config_t* conf,
	vod_str_t* manifest_id,
	segmenter_conf_t* segmenter_conf,
	media_set_t* media_set,
	vod_str_t* result)
{
	media_track_t** cur_sequence_tracks;
	media_sequence_t* cur_sequence;
	media_track_t* track;
	segment_durations_t* segment_durations;
	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;

	segment_durations = vod_alloc(
		request_context->pool, 
		media_set->sequence_count * sizeof(*segment_durations));
	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 + 2 * VOD_INT32_LEN + manifest_id->len + 
		sizeof(HDS_MANIFEST_FOOTER);

	index = 0;
	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track;

		rc = segmenter_conf->get_segment_durations(
			request_context,
			segmenter_conf,
			media_set,
			cur_sequence,
			MEDIA_TYPE_NONE,
			&segment_durations[index]);
		if (rc != VOD_OK)
		{
			return rc;
		}

		abst_atom_size = ABST_BASE_ATOM_SIZE + (segment_durations[index].item_count + 1) * sizeof(afrt_entry_t);
		if (abst_atom_size > max_abst_atom_size)
		{
			max_abst_atom_size = abst_atom_size;
		}

		result_size += 
			sizeof(HDS_BOOTSTRAP_HEADER) - 1 + VOD_INT32_LEN + 
				vod_base64_encoded_length(abst_atom_size) +
			sizeof(HDS_BOOTSTRAP_FOOTER) - 1;
		
		result_size += 
			sizeof(HDS_MEDIA_HEADER_PREFIX_VIDEO) - 1 + 3 * VOD_INT32_LEN +
			conf->fragment_file_name_prefix.len + sizeof("-f-v-a-") - 1 + 3 * VOD_INT32_LEN + 
			sizeof(HDS_MEDIA_HEADER_SUFFIX) - 1 + VOD_INT32_LEN + 
				vod_base64_encoded_length(amf0_max_total_size) +
			sizeof(HDS_MEDIA_FOOTER) - 1;

		index++;
	}

	// 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 (1)");
		return VOD_ALLOC_FAILED;
	}

	// print the manifest header
	p = vod_sprintf(result->data, HDS_MANIFEST_HEADER,
		manifest_id,
		(uint32_t)(media_set->total_duration / 1000),
		(uint32_t)(media_set->total_duration % 1000));

	// bootstrap tags
	index = 0;
	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track;

		p = vod_sprintf(p, HDS_BOOTSTRAP_HEADER, index);

		p = hds_write_base64_abst_atom(p, temp_buffer, &segment_durations[index]);

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

		index++;

	}

	// media tags
	index = 0;
	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track;

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

			p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_VIDEO,
				bitrate / 1000,
				(uint32_t)track->media_info.u.video.width,
				(uint32_t)track->media_info.u.video.height);
		}
		else
		{
			track = cur_sequence_tracks[MEDIA_TYPE_AUDIO];
			p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO,
				track->media_info.bitrate / 1000);
		}

		// url
		p = vod_copy(p, conf->fragment_file_name_prefix.data, conf->fragment_file_name_prefix.len);
		if (media_set->has_multi_sequences)
		{
			p = vod_sprintf(p, "-f%uD", cur_sequence->index + 1);
		}

		if (cur_sequence_tracks[MEDIA_TYPE_VIDEO] != NULL)
		{
			p = vod_sprintf(p, "-v%uD", cur_sequence_tracks[MEDIA_TYPE_VIDEO]->index + 1);
		}

		if (cur_sequence_tracks[MEDIA_TYPE_AUDIO] != NULL)
		{
			p = vod_sprintf(p, "-a%uD", cur_sequence_tracks[MEDIA_TYPE_AUDIO]->index + 1);
		}
		*p++ = '-';

		p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX, index++);

		p = hds_amf0_write_base64_metadata(p, temp_buffer, cur_sequence_tracks);

		p = vod_copy(p, HDS_MEDIA_FOOTER, sizeof(HDS_MEDIA_FOOTER) - 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;
}
Esempio n. 22
0
}

static vod_inline u_char*
hds_amf0_append_boolean(u_char* p, bool_t value)
{
	*p++ = AMF0_TYPE_BOOLEAN;
	*p++ = (value ? 0x01 : 0x00);
	return p;
}

static vod_inline u_char*
hds_amf0_append_raw_string(u_char* p, const vod_str_t* str)
{
	*p++ = (str->len >> 8) & 0xFF;
	*p++ = str->len & 0xFF;
	p = vod_copy(p, str->data, str->len);
	return p;
}

static vod_inline u_char*
hds_amf0_append_string(u_char* p, const vod_str_t* str)
{
	*p++ = AMF0_TYPE_STRING;
	return hds_amf0_append_raw_string(p, str);
}

static vod_inline u_char*
hds_amf0_append_array_header(u_char* p, uint32_t count)
{
	*p++ = AMF0_TYPE_ECMA_ARRAY;
	write_be32(p, count);
Esempio n. 23
0
vod_status_t
m3u8_builder_build_index_playlist(
	request_context_t* request_context,
	m3u8_config_t* conf,
	vod_str_t* base_url,
	vod_str_t* segments_base_url,
	request_params_t* request_params,
	hls_encryption_params_t* encryption_params,
	media_set_t* media_set,
	vod_str_t* result)
{
	segment_durations_t segment_durations;
	segment_duration_item_t* cur_item;
	segment_duration_item_t* last_item;
	segmenter_conf_t* segmenter_conf = media_set->segmenter_conf;
	uint64_t duration_millis;
	uint32_t sequence_index;
	vod_str_t extinf;
	uint32_t segment_index;
	uint32_t last_segment_index;
	vod_str_t tracks_spec;
	uint32_t scale;
	size_t segment_length;
	size_t result_size;
	vod_status_t rc;
	u_char* p;

	sequence_index = media_set->has_multi_sequences ? media_set->sequences[0].index : INVALID_SEQUENCE_INDEX;

	// build the required tracks string
	rc = m3u8_builder_build_required_tracks_string(
		request_context,
		media_set,
		sequence_index,
		request_params,
		&tracks_spec);
	if (rc != VOD_OK)
	{
		return rc;
	}

	// get the segment durations
	rc = segmenter_conf->get_segment_durations(
		request_context,
		segmenter_conf,
		media_set,
		NULL,
		MEDIA_TYPE_NONE,
		&segment_durations);
	if (rc != VOD_OK)
	{
		return rc;
	}

	// get the required buffer length
	duration_millis = segment_durations.end_time - segment_durations.start_time;
	last_segment_index = media_set->initial_segment_index + segment_durations.segment_count;
	segment_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(duration_millis, 1000)) +
		segments_base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(last_segment_index) + tracks_spec.len + sizeof(".ts\n") - 1;

	result_size =
		sizeof(M3U8_HEADER_PART1) + VOD_INT64_LEN +
		sizeof(M3U8_HEADER_VOD) +
		sizeof(M3U8_HEADER_PART2) + VOD_INT64_LEN + VOD_INT32_LEN +
		segment_length * segment_durations.segment_count +
		segment_durations.discontinuities * (sizeof(m3u8_discontinuity) - 1) +
		sizeof(m3u8_footer);

	if (encryption_params->type != HLS_ENC_NONE)
	{
		result_size +=
			sizeof(encryption_key_tag_part1) - 1 +
			sizeof(encryption_type_sample_aes) - 1 +
			sizeof(encryption_key_tag_part2) - 1 +
			base_url->len +
			conf->encryption_key_file_name.len +
			sizeof("-f") - 1 + VOD_INT32_LEN +
			sizeof(encryption_key_tag_part3) - 1;
	}

	// 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,
			"m3u8_builder_build_index_playlist: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	// write the header
	p = vod_sprintf(
		result->data,
		M3U8_HEADER_PART1,
		(segmenter_conf->max_segment_duration + 500) / 1000);

	if (media_set->type == MEDIA_SET_VOD)
	{
		p = vod_copy(p, M3U8_HEADER_VOD, sizeof(M3U8_HEADER_VOD) - 1);
	}

	if (encryption_params->type != HLS_ENC_NONE)
	{
		p = vod_copy(p, encryption_key_tag_part1, sizeof(encryption_key_tag_part1) - 1);
		switch (encryption_params->type)
		{
		case HLS_ENC_SAMPLE_AES:
			p = vod_copy(p, encryption_type_sample_aes, sizeof(encryption_type_sample_aes) - 1);
			break;

		default:		// HLS_ENC_AES_128
			p = vod_copy(p, encryption_type_aes_128, sizeof(encryption_type_aes_128) - 1);
			break;
		}
		p = vod_copy(p, encryption_key_tag_part2, sizeof(encryption_key_tag_part2) - 1);
		p = vod_copy(p, base_url->data, base_url->len);
		p = vod_copy(p, conf->encryption_key_file_name.data, conf->encryption_key_file_name.len);
		if (sequence_index != INVALID_SEQUENCE_INDEX)
		{
			p = vod_sprintf(p, "-f%uD", sequence_index + 1);
		}
		p = vod_copy(p, encryption_key_tag_part3, sizeof(encryption_key_tag_part3) - 1);
	}

	p = vod_sprintf(
		p,
		M3U8_HEADER_PART2,
		conf->m3u8_version, 
		media_set->initial_segment_index + 1);

	// write the segments
	scale = conf->m3u8_version >= 3 ? 1000 : 1;
	last_item = segment_durations.items + segment_durations.item_count;

	for (cur_item = segment_durations.items; cur_item < last_item; cur_item++)
	{
		segment_index = cur_item->segment_index;
		last_segment_index = segment_index + cur_item->repeat_count;

		if (cur_item->discontinuity)
		{
			p = vod_copy(p, m3u8_discontinuity, sizeof(m3u8_discontinuity) - 1);
		}

		// write the first segment
		extinf.data = p;
		p = m3u8_builder_append_extinf_tag(p, rescale_time(cur_item->duration, segment_durations.timescale, scale), scale);
		extinf.len = p - extinf.data;
		p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &tracks_spec);
		segment_index++;

		// write any additional segments
		for (; segment_index < last_segment_index; segment_index++)
		{
			p = vod_copy(p, extinf.data, extinf.len);
			p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &tracks_spec);
		}
	}

	// write the footer
	if (media_set->type == MEDIA_SET_VOD)
	{
		p = vod_copy(p, m3u8_footer, sizeof(m3u8_footer) - 1);
	}

	result->len = p - result->data;

	if (result->len > result_size)
	{
		vod_log_error(VOD_LOG_ERR, request_context->log, 0,
			"m3u8_builder_build_index_playlist: result length %uz exceeded allocated length %uz", 
			result->len, result_size);
		return VOD_UNEXPECTED;
	}
	
	return VOD_OK;
}
Esempio n. 24
0
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;
}
Esempio n. 25
0
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;
}
Esempio n. 26
0
static vod_status_t
m3u8_builder_build_required_tracks_string(
	request_context_t* request_context, 
	media_set_t* media_set,
	uint32_t sequence_index,
	request_params_t* request_params,
	vod_str_t* tracks_spec)
{
	u_char* p;
	size_t result_size;
	uint32_t i;

	result_size = 0;
	if (request_params->tracks_mask[MEDIA_TYPE_VIDEO] != 0xffffffff)
	{
		result_size += vod_get_number_of_set_bits(request_params->tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1);
	}
	if (request_params->tracks_mask[MEDIA_TYPE_AUDIO] != 0xffffffff)
	{
		result_size += vod_get_number_of_set_bits(request_params->tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1);
	}
	if (sequence_index != INVALID_SEQUENCE_INDEX)
	{
		result_size += sizeof("-f") - 1 + vod_get_int_print_len(sequence_index + 1);
	}

	p = vod_alloc(request_context->pool, result_size + 1);
	if (p == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"m3u8_builder_build_required_tracks_string: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}
	tracks_spec->data = p;

	if (sequence_index != INVALID_SEQUENCE_INDEX)
	{
		p = vod_sprintf(p, "-f%uD", sequence_index + 1);
	}

	if (media_set->track_count[MEDIA_TYPE_VIDEO] != 0)
	{
		if (request_params->tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff)
		{
			p = vod_copy(p, "-v0", sizeof("-v0") - 1);
		}
		else
		{
			for (i = 0; i < 32; i++)
			{
				if ((request_params->tracks_mask[MEDIA_TYPE_VIDEO] & (1 << i)) == 0)
				{
					continue;
				}

				p = vod_sprintf(p, "-v%uD", i + 1);
			}
		}
	}
	
	if (media_set->track_count[MEDIA_TYPE_AUDIO] != 0)
	{
		if (request_params->tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff)
		{
			p = vod_copy(p, "-a0", sizeof("-a0") - 1);
		}
		else
		{
			for (i = 0; i < 32; i++)
			{
				if ((request_params->tracks_mask[MEDIA_TYPE_AUDIO] & (1 << i)) == 0)
				{
					continue;
				}

				p = vod_sprintf(p, "-a%uD", i + 1);
			}
		}
	}

	tracks_spec->len = p - tracks_spec->data;

	if (tracks_spec->len > result_size)
	{
		vod_log_error(VOD_LOG_ERR, request_context->log, 0,
			"m3u8_builder_build_required_tracks_string: result length %uz exceeded allocated length %uz", 
			tracks_spec->len, result_size);
		return VOD_UNEXPECTED;
	}
	
	return VOD_OK;
}
Esempio n. 27
0
vod_status_t
dash_packager_build_fragment_header(
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	uint32_t sample_description_index,
	dash_fragment_header_extensions_t* extensions,
	bool_t size_only,
	vod_str_t* result,
	size_t* total_fragment_size)
{
	media_sequence_t* sequence = &media_set->sequences[0];
	media_track_t* first_track = sequence->filtered_clips[0].first_track;
	uint64_t earliest_pres_time = dash_packager_get_earliest_pres_time(media_set, first_track);
	sidx_params_t sidx_params;
	size_t first_frame_offset;
	size_t mdat_atom_size;
	size_t trun_atom_size;
	size_t tfhd_atom_size;
	size_t moof_atom_size;
	size_t traf_atom_size;
	size_t result_size;
	u_char* p;

	// calculate sizes
	dash_packager_init_sidx_params(media_set, sequence, &sidx_params);

	mdat_atom_size = ATOM_HEADER_SIZE + sequence->total_frame_size;
	if (extensions->mdat_prefix_writer != NULL)
	{
		mdat_atom_size += extensions->mdat_prefix_writer->atom_size;
	}
	trun_atom_size = mp4_builder_get_trun_atom_size(first_track->media_info.media_type, sequence->total_frame_count);

	tfhd_atom_size = ATOM_HEADER_SIZE + sizeof(tfhd_atom_t);
	if (sample_description_index > 0)
	{
		tfhd_atom_size += sizeof(uint32_t);
	}

	traf_atom_size =
		ATOM_HEADER_SIZE +
		tfhd_atom_size +
		ATOM_HEADER_SIZE + (earliest_pres_time > UINT_MAX ? sizeof(tfdt64_atom_t) : sizeof(tfdt_atom_t)) +
		trun_atom_size + 
		extensions->extra_traf_atoms_size;

	moof_atom_size =
		ATOM_HEADER_SIZE +
		ATOM_HEADER_SIZE + sizeof(mfhd_atom_t)+
		traf_atom_size;

	*total_fragment_size = 
		sizeof(styp_atom) +
		ATOM_HEADER_SIZE + (sidx_params.earliest_pres_time > UINT_MAX ? sizeof(sidx64_atom_t) : sizeof(sidx_atom_t)) +
		moof_atom_size +
		mdat_atom_size;

	result_size = *total_fragment_size - sequence->total_frame_size;

	// head request optimization
	if (size_only)
	{
		return VOD_OK;
	}

	// allocate the buffer
	p = vod_alloc(request_context->pool, result_size);
	if (p == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"dash_packager_build_fragment_header: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	result->data = p;

	// styp
	p = vod_copy(p, styp_atom, sizeof(styp_atom));

	// sidx
	if (sidx_params.earliest_pres_time > UINT_MAX)
	{
		p = dash_packager_write_sidx64_atom(p, &sidx_params, moof_atom_size + mdat_atom_size);
	}
	else
	{
		p = dash_packager_write_sidx_atom(p, &sidx_params, moof_atom_size + mdat_atom_size);
	}

	// moof
	write_atom_header(p, moof_atom_size, 'm', 'o', 'o', 'f');

	// moof.mfhd
	p = mp4_builder_write_mfhd_atom(p, segment_index);

	// moof.traf
	write_atom_header(p, traf_atom_size, 't', 'r', 'a', 'f');

	// moof.traf.tfhd
	p = dash_packager_write_tfhd_atom(p, first_track->media_info.track_id, sample_description_index);

	// moof.traf.tfdt
	if (earliest_pres_time > UINT_MAX)
	{
		p = dash_packager_write_tfdt64_atom(p, earliest_pres_time);
	}
	else
	{
		p = dash_packager_write_tfdt_atom(p, (uint32_t)earliest_pres_time);
	}

	// moof.traf.trun
	first_frame_offset = moof_atom_size + ATOM_HEADER_SIZE;
	if (extensions->mdat_prefix_writer != NULL)
	{
		first_frame_offset += extensions->mdat_prefix_writer->atom_size;
	}

	p = mp4_builder_write_trun_atom(
		p, 
		sequence, 
		first_frame_offset);

	// moof.traf.xxx
	if (extensions->write_extra_traf_atoms_callback != NULL)
	{
		p = extensions->write_extra_traf_atoms_callback(extensions->write_extra_traf_atoms_context, p, moof_atom_size + ATOM_HEADER_SIZE);
	}

	// mdat
	write_atom_header(p, mdat_atom_size, 'm', 'd', 'a', 't');

	// mdat prefix
	if (extensions->mdat_prefix_writer != NULL)
	{
		p = extensions->mdat_prefix_writer->write(extensions->mdat_prefix_writer->context, p);
	}

	result->len = p - result->data;

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

	return VOD_OK;
}
Esempio n. 28
0
static u_char*
dash_packager_init_mp4_write(
	u_char* p,
	request_context_t* request_context, 
	media_set_t* media_set, 
	init_mp4_sizes_t* sizes,
	atom_writer_t* extra_moov_atoms_writer,
	atom_writer_t* stsd_atom_writer)
{
	media_track_t* first_track = media_set->sequences[0].filtered_clips[0].first_track;

	// ftyp
	p = vod_copy(p, ftyp_atom, sizeof(ftyp_atom));

	// moov
	write_atom_header(p, sizes->moov_atom_size, 'm', 'o', 'o', 'v');

	// moov.mvhd
	if (media_set->sequences[0].filtered_clips[0].mvhd_atom.ptr != NULL)
	{
		// TODO: adjust the mvhd duration (can change in case of clipping or sequencing)
		p = vod_copy_atom(p, media_set->sequences[0].filtered_clips[0].mvhd_atom);
	}
	else if (media_set->total_duration > UINT_MAX)
	{
		p = dash_packager_write_mvhd64_atom(p, 1000, media_set->total_duration);
	}
	else
	{
		p = dash_packager_write_mvhd_atom(p, 1000, media_set->total_duration);
	}

	// moov.mvex
	write_atom_header(p, sizes->mvex_atom_size, 'm', 'v', 'e', 'x');

	// moov.mvex.trex
	p = dash_packager_write_trex_atom(p, first_track->media_info.track_id);

	// moov.trak
	write_atom_header(p, sizes->track_sizes.track_trak_size, 't', 'r', 'a', 'k');
	p = vod_copy_atom(p, first_track->raw_atoms[RTA_TKHD]);

	// moov.trak.mdia
	write_atom_header(p, sizes->track_sizes.track_mdia_size, 'm', 'd', 'i', 'a');
	p = vod_copy_atom(p, first_track->raw_atoms[RTA_MDHD]);
	p = vod_copy_atom(p, first_track->raw_atoms[RTA_HDLR]);

	// moov.trak.minf
	write_atom_header(p, sizes->track_sizes.track_minf_size, 'm', 'i', 'n', 'f');
	switch (first_track->media_info.media_type)
	{
	case MEDIA_TYPE_VIDEO:
		p = vod_copy(p, vmhd_atom, sizeof(vmhd_atom));
		break;
	case MEDIA_TYPE_AUDIO:
		p = vod_copy(p, smhd_atom, sizeof(smhd_atom));
		break;
	}
	p = vod_copy_atom(p, first_track->raw_atoms[RTA_DINF]);

	// moov.trak.minf.stbl
	write_atom_header(p, sizes->track_sizes.track_stbl_size, 's', 't', 'b', 'l');
	if (stsd_atom_writer != NULL)
	{
		p = stsd_atom_writer->write(stsd_atom_writer->context, p);
	}
	else
	{
		p = vod_copy_atom(p, first_track->raw_atoms[RTA_STSD]);
	}
	p = vod_copy(p, fixed_stbl_atoms, sizeof(fixed_stbl_atoms));

	// moov.xxx
	if (extra_moov_atoms_writer != NULL)
	{
		p = extra_moov_atoms_writer->write(extra_moov_atoms_writer->context, p);
	}

	return p;
}
Esempio n. 29
0
vod_status_t 
mss_packager_build_manifest(
	request_context_t* request_context, 
	segmenter_conf_t* segmenter_conf, 
	mpeg_metadata_t* mpeg_metadata, 
	size_t extra_tags_size,
	mss_write_tags_callback_t write_extra_tags,
	void* extra_tags_writer_context,
	vod_str_t* result)
{
	mpeg_stream_metadata_t* cur_stream;
	segment_durations_t segment_durations[MEDIA_TYPE_COUNT];
	uint64_t duration_100ns;
	uint32_t media_type;
	uint32_t stream_index;
	uint32_t bitrate;
	vod_status_t rc;
	size_t result_size;
	u_char* p;

	// calculate the result size
	result_size = 
		sizeof(MSS_MANIFEST_HEADER) - 1 + VOD_INT64_LEN + 
		extra_tags_size +
		sizeof(MSS_MANIFEST_FOOTER);
	for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++)
	{
		if (mpeg_metadata->longest_stream[media_type] == NULL)
		{
			continue;
		}

		rc = segmenter_conf->get_segment_durations(
			request_context,
			segmenter_conf,
			&mpeg_metadata->longest_stream[media_type],
			1,
			&segment_durations[media_type]);
		if (rc != VOD_OK)
		{
			return rc;
		}

		result_size +=
			sizeof(MSS_STREAM_INDEX_HEADER) - 1 + 2 * sizeof(MSS_STREAM_TYPE_VIDEO) + 2 * VOD_INT32_LEN +
			sizeof(MSS_STREAM_INDEX_FOOTER);

		result_size += segment_durations[media_type].segment_count * (sizeof(MSS_CHUNK_TAG) + VOD_INT32_LEN + VOD_INT64_LEN);
	}

	for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++)
	{
		switch (cur_stream->media_info.media_type)
		{
		case MEDIA_TYPE_VIDEO:
			result_size += 
				sizeof(MSS_VIDEO_QUALITY_LEVEL_HEADER) - 1 + 4 * VOD_INT32_LEN + cur_stream->media_info.extra_data_size * 2 + 
				sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1;
			break;

		case MEDIA_TYPE_AUDIO:
			result_size += 
				sizeof(MSS_AUDIO_QUALITY_LEVEL_HEADER) - 1 + 6 * VOD_INT32_LEN + cur_stream->media_info.extra_data_size * 2 +
				sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1;
			break;
		}
	}

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

	duration_100ns = rescale_time(mpeg_metadata->duration, mpeg_metadata->timescale, MSS_TIMESCALE);
	p = vod_sprintf(result->data, MSS_MANIFEST_HEADER, duration_100ns);

	if (mpeg_metadata->longest_stream[MEDIA_TYPE_VIDEO] != NULL)
	{
		p = vod_sprintf(p, 
			MSS_STREAM_INDEX_HEADER, 
			MSS_STREAM_TYPE_VIDEO,
			mpeg_metadata->stream_count[MEDIA_TYPE_VIDEO],
			segment_durations[MEDIA_TYPE_VIDEO].segment_count,
			MSS_STREAM_TYPE_VIDEO);

		stream_index = 0;
		for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++)
		{
			if (cur_stream->media_info.media_type != MEDIA_TYPE_VIDEO)
			{
				continue;
			}

			bitrate = cur_stream->media_info.bitrate;
			bitrate = mss_encode_indexes(bitrate, cur_stream->file_info.file_index, cur_stream->track_index);
			p = vod_sprintf(p, MSS_VIDEO_QUALITY_LEVEL_HEADER,
				stream_index++,
				bitrate,
				(uint32_t)cur_stream->media_info.u.video.width,
				(uint32_t)cur_stream->media_info.u.video.height);

			p = mss_append_hex_string(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size);

			p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1);
		}

		p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_VIDEO]);

		p = vod_copy(p, MSS_STREAM_INDEX_FOOTER, sizeof(MSS_STREAM_INDEX_FOOTER) - 1);
	}

	if (mpeg_metadata->longest_stream[MEDIA_TYPE_AUDIO] != NULL)
	{
		p = vod_sprintf(p, MSS_STREAM_INDEX_HEADER, 
			MSS_STREAM_TYPE_AUDIO,
			mpeg_metadata->stream_count[MEDIA_TYPE_AUDIO],
			segment_durations[MEDIA_TYPE_AUDIO].segment_count,
			MSS_STREAM_TYPE_AUDIO);

		stream_index = 0;
		for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++)
		{
			if (cur_stream->media_info.media_type != MEDIA_TYPE_AUDIO)
			{
				continue;
			}

			bitrate = cur_stream->media_info.bitrate;
			bitrate = mss_encode_indexes(bitrate, cur_stream->file_info.file_index, cur_stream->track_index);
			p = vod_sprintf(p, MSS_AUDIO_QUALITY_LEVEL_HEADER,
				stream_index++,
				bitrate,
				cur_stream->media_info.u.audio.sample_rate,
				(uint32_t)cur_stream->media_info.u.audio.channels,
				(uint32_t)cur_stream->media_info.u.audio.bits_per_sample,
				(uint32_t)cur_stream->media_info.u.audio.packet_size);

			p = mss_append_hex_string(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size);

			p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1);
		}

		p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_AUDIO]);

		p = vod_copy(p, MSS_STREAM_INDEX_FOOTER, sizeof(MSS_STREAM_INDEX_FOOTER) - 1);
	}

	if (write_extra_tags != NULL)
	{
		p = write_extra_tags(extra_tags_writer_context, p, mpeg_metadata);
	}

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

	result->len = p - result->data;
	
	if (result->len > result_size)
	{
		vod_log_error(VOD_LOG_ERR, request_context->log, 0,
			"mss_packager_build_manifest: result length %uz exceeded allocated length %uz",
			result->len, result_size);
		return VOD_UNEXPECTED;
	}
	
	return VOD_OK;
}
Esempio n. 30
0
vod_status_t
m3u8_builder_build_iframe_playlist(
	request_context_t* request_context,
	m3u8_config_t* conf,
	hls_muxer_conf_t* muxer_conf,
	vod_str_t* base_url,
	request_params_t* request_params,
	media_set_t* media_set,
	vod_str_t* result)
{
	hls_encryption_params_t encryption_params;
	write_segment_context_t ctx;
	segment_durations_t segment_durations;
	segmenter_conf_t* segmenter_conf = media_set->segmenter_conf;
	size_t iframe_length;
	size_t result_size;
	uint64_t duration_millis;
	uint32_t sequence_index;
	vod_status_t rc; 

	sequence_index = media_set->has_multi_sequences ? media_set->sequences[0].index : INVALID_SEQUENCE_INDEX;

	// iframes list is not supported with encryption, since:
	// 1. AES-128 - the IV of each key frame is not known in advance
	// 2. SAMPLE-AES - the layout of the TS files is not known in advance due to emulation prevention
	encryption_params.type = HLS_ENC_NONE;
	encryption_params.key = NULL;
	encryption_params.iv = NULL;

	// build the required tracks string
	rc = m3u8_builder_build_required_tracks_string(
		request_context, 
		media_set,
		sequence_index,
		request_params,
		&ctx.tracks_spec);
	if (rc != VOD_OK)
	{
		return rc;
	}

	// get segment durations
	if (segmenter_conf->align_to_key_frames)
	{
		rc = segmenter_get_segment_durations_accurate(
			request_context,
			segmenter_conf,
			media_set,
			NULL,
			MEDIA_TYPE_NONE,
			&segment_durations);
	}
	else
	{
		rc = segmenter_get_segment_durations_estimate(
			request_context,
			segmenter_conf,
			media_set,
			NULL,
			MEDIA_TYPE_NONE,
			&segment_durations);
	}

	if (rc != VOD_OK)
	{
		return rc;
	}

	duration_millis = segment_durations.end_time - segment_durations.start_time;
	iframe_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(duration_millis, 1000)) +
		sizeof(byte_range_tag_format) + VOD_INT32_LEN + vod_get_int_print_len(MAX_FRAME_SIZE) - (sizeof("%uD%uD") - 1) +
		base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(segment_durations.segment_count) + ctx.tracks_spec.len + sizeof(".ts\n") - 1;

	result_size =
		conf->iframes_m3u8_header_len +
		iframe_length * media_set->sequences[0].video_key_frame_count +
		sizeof(m3u8_footer);

	// 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,
			"m3u8_builder_build_iframe_playlist: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	// fill out the buffer
	ctx.p = vod_copy(result->data, conf->iframes_m3u8_header, conf->iframes_m3u8_header_len);

	if (media_set->sequences[0].video_key_frame_count > 0)
	{
		ctx.base_url = base_url;
		ctx.segment_file_name_prefix = &conf->segment_file_name_prefix;
	
		rc = hls_muxer_simulate_get_iframes(
			request_context,
			&segment_durations, 
			muxer_conf,
			&encryption_params,
			media_set, 
			m3u8_builder_append_iframe_string, 
			&ctx);
		if (rc != VOD_OK)
		{
			return rc;
		}
	}

	ctx.p = vod_copy(ctx.p, m3u8_footer, sizeof(m3u8_footer) - 1);
	result->len = ctx.p - result->data;

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