示例#1
0
vod_status_t
mss_packager_build_fragment_header(
	request_context_t* request_context,
	mpeg_stream_metadata_t* stream_metadata,
	uint32_t segment_index,
	size_t extra_traf_atoms_size,
	write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback,
	void* write_extra_traf_atoms_context,
	bool_t size_only,
	vod_str_t* result,
	size_t* total_fragment_size)
{
	input_frame_t* last_frame;
	input_frame_t* cur_frame;
	size_t mdat_atom_size;
	size_t trun_atom_size;
	size_t moof_atom_size;
	size_t traf_atom_size;
	size_t result_size;
	u_char* p;

	// calculate sizes
	mdat_atom_size = ATOM_HEADER_SIZE + stream_metadata->total_frames_size;
	trun_atom_size = mp4_builder_get_trun_atom_size(stream_metadata->media_info.media_type, stream_metadata->frame_count);

	traf_atom_size =
		ATOM_HEADER_SIZE +
		ATOM_HEADER_SIZE + sizeof(tfhd_atom_t) +
		trun_atom_size +
		ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t) + 
		extra_traf_atoms_size;

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

	result_size =
		moof_atom_size +
		ATOM_HEADER_SIZE;		// mdat

	*total_fragment_size = result_size + stream_metadata->total_frames_size;

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

	// 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,
			"mss_packager_build_fragment_header: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}
	p = result->data;

	// 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
	switch (stream_metadata->media_info.media_type)
	{
	case MEDIA_TYPE_VIDEO:
		p = mss_write_tfhd_atom(p, stream_metadata->media_info.track_id, 0x01010000);
		break;

	case MEDIA_TYPE_AUDIO:
		p = mss_write_tfhd_atom(p, stream_metadata->media_info.track_id, 0x02000000);
		break;
	}

	// moof.traf.trun
	last_frame = stream_metadata->frames + stream_metadata->frame_count;
	for (cur_frame = stream_metadata->frames; cur_frame < last_frame; cur_frame++)
	{
		cur_frame->duration = rescale_time(cur_frame->duration, stream_metadata->media_info.timescale, MSS_TIMESCALE);
	}

	p = mp4_builder_write_trun_atom(
		p,
		stream_metadata->media_info.media_type,
		stream_metadata->frames,
		stream_metadata->frame_count,
		moof_atom_size + ATOM_HEADER_SIZE);

	p = mss_write_uuid_tfxd_atom(p, stream_metadata);

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

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

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

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

	return VOD_OK;
}
示例#2
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;
}
vod_status_t
hds_muxer_init_fragment(
	request_context_t* request_context,
	hds_fragment_config_t* conf,
	uint32_t segment_index,
	media_sequence_t* sequence,
	write_callback_t write_callback,
	void* write_context,
	bool_t size_only,
	vod_str_t* header, 
	size_t* total_fragment_size,
	hds_muxer_state_t** processor_state)
{
	media_clip_filtered_t* cur_clip;
	media_track_t* cur_track;
	hds_muxer_stream_state_t* cur_stream;
	input_frame_t* cur_frame;
	input_frame_t* frames_end;
	hds_muxer_state_t* state;
	vod_status_t rc;
	uint32_t track_id = 1;
	uint32_t* output_offset;
	uint32_t frame_metadata_size;
	uint32_t video_key_frame_count;
	uint32_t codec_config_size;
	size_t afra_atom_size;
	size_t moof_atom_size;
	size_t traf_atom_size;
	size_t mdat_atom_size;
	size_t result_size;
	u_char* p;

	// initialize the muxer state
	rc = hds_muxer_init_state(
		request_context, 
		sequence,
		write_callback, 
		write_context, 
		&state);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_muxer_init_fragment: hds_muxer_init_state failed %i", rc);
		return rc;
	}

	// get moof atom size
	mdat_atom_size = ATOM_HEADER_SIZE;
	for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++)
	{
		codec_config_size = 0;
		video_key_frame_count = 0;

		for (cur_track = cur_clip->first_track; cur_track < cur_clip->last_track; cur_track++)
		{
			frame_metadata_size = tag_size_by_media_type[cur_track->media_info.media_type] + sizeof(uint32_t);
			codec_config_size += frame_metadata_size + cur_track->media_info.extra_data_size;

			mdat_atom_size += cur_track->total_frames_size + cur_track->frame_count * frame_metadata_size;

			if (cur_track->media_info.media_type == MEDIA_TYPE_VIDEO)
			{
				video_key_frame_count += cur_track->key_frame_count;
			}
		}

		mdat_atom_size += video_key_frame_count * codec_config_size;
	}

	// get the fragment header size
	if (conf->generate_moof_atom)
	{
		afra_atom_size = ATOM_HEADER_SIZE + sizeof(afra_atom_t) + sizeof(afra_entry_t) * sequence->video_key_frame_count;
		moof_atom_size =
			ATOM_HEADER_SIZE +
			ATOM_HEADER_SIZE + sizeof(mfhd_atom_t);

		for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++)
		{
			moof_atom_size += hds_get_traf_atom_size(cur_stream);
		}
	}
	else
	{
		afra_atom_size = 0;
		moof_atom_size = 0;
	}

	result_size =
		afra_atom_size +
		moof_atom_size +
		ATOM_HEADER_SIZE;		// mdat

	// audio only - output the codec config up front, video - output the codec config before every key frame
	if (sequence->video_key_frame_count == 0)
	{
		result_size += state->codec_config_size;
		mdat_atom_size += state->codec_config_size;
	}

	*total_fragment_size =
		afra_atom_size +
		moof_atom_size +
		mdat_atom_size;

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

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

	p = header->data;

	if (conf->generate_moof_atom)
	{
		// afra
		p = hds_write_afra_atom_header(p, afra_atom_size, sequence->video_key_frame_count);

		rc = hds_calculate_output_offsets_and_write_afra_entries(state, ATOM_HEADER_SIZE, afra_atom_size + moof_atom_size, &p);
		if (rc != VOD_OK)
		{
			return rc;
		}

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

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

		for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++)
		{
			// moof.traf
			traf_atom_size = hds_get_traf_atom_size(cur_stream);
			write_atom_header(p, traf_atom_size, 't', 'r', 'a', 'f');

			// moof.traf.tfhd
			p = hds_write_tfhd_atom(p, track_id, ATOM_HEADER_SIZE + sizeof(afra_atom_t) + moof_atom_size);

			// moof.traf.trun
			switch (cur_stream->media_type)
			{
			case MEDIA_TYPE_VIDEO:
				for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++)
				{
					cur_track = cur_clip->first_track + cur_stream->index;
					frames_end = cur_track->last_frame;
					for (cur_frame = cur_track->first_frame, output_offset = cur_stream->first_frame_output_offset;
						cur_frame < frames_end;
						cur_frame++, output_offset++)
					{
						p = hds_write_single_video_frame_trun_atom(p, cur_frame, *output_offset);
					}
				}
				break;

			case MEDIA_TYPE_AUDIO:
				for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++)
				{
					cur_track = cur_clip->first_track + cur_stream->index;
					frames_end = cur_track->last_frame;
					for (cur_frame = cur_track->first_frame, output_offset = cur_stream->first_frame_output_offset;
						cur_frame < frames_end;
						cur_frame++, output_offset++)
					{
						p = hds_write_single_audio_frame_trun_atom(p, cur_frame, *output_offset);
					}
				}
				break;
			}
		}
	}
	else
	{
		// calculate the output offsets
		rc = hds_calculate_output_offsets_and_write_afra_entries(state, 0, 0, NULL);
		if (rc != VOD_OK)
		{
			return rc;
		}
	}

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

	if (sequence->video_key_frame_count == 0)
	{
		p = hds_muxer_write_codec_config(
			p, 
			state, 
			state->first_stream->next_frame_dts + state->first_stream->clip_start_time);
	}

	header->len = p - header->data;

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

	rc = hds_muxer_start_frame(state);
	if (rc != VOD_OK)
	{
		if (rc == VOD_NOT_FOUND)
		{
			*processor_state = NULL;		// no frames, nothing to do
			return VOD_OK;
		}

		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_muxer_init_fragment: hds_muxer_start_frame failed %i", rc);
		return rc;
	}

	*processor_state = state;
	return VOD_OK;
}
示例#4
0
vod_status_t
mss_packager_build_fragment_header(
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	size_t extra_traf_atoms_size,
	mss_write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback,
	void* write_extra_traf_atoms_context,
	bool_t size_only,
	vod_str_t* result,
	size_t* total_fragment_size)
{
	segment_timing_info_t timing_info;
	media_clip_filtered_t* cur_clip;
	media_sequence_t* sequence = media_set->sequences;
	input_frame_t* last_frame;
	input_frame_t* cur_frame;
	media_track_t* first_track = sequence->filtered_clips[0].first_track;
	media_track_t* track;
	uint32_t media_type = sequence->media_type;
	size_t mdat_atom_size;
	size_t trun_atom_size;
	size_t moof_atom_size;
	size_t traf_atom_size;
	size_t result_size;
	u_char* p;

	// calculate sizes
	mdat_atom_size = ATOM_HEADER_SIZE + sequence->total_frame_size;
	trun_atom_size = mp4_builder_get_trun_atom_size(media_type, sequence->total_frame_count);

	traf_atom_size =
		ATOM_HEADER_SIZE +
		ATOM_HEADER_SIZE + sizeof(tfhd_atom_t) +
		trun_atom_size +
		ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t) + 
		extra_traf_atoms_size;

	if (media_set->type == MEDIA_SET_LIVE)
	{
		traf_atom_size += TFRF_ATOM_SIZE;
	}

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

	result_size =
		moof_atom_size +
		ATOM_HEADER_SIZE;		// mdat

	*total_fragment_size = result_size + sequence->total_frame_size;

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

	// 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,
			"mss_packager_build_fragment_header: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}
	p = result->data;

	// 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
	switch (media_type)
	{
	case MEDIA_TYPE_VIDEO:
		p = mss_write_tfhd_atom(p, first_track->media_info.track_id, 0x01010000);
		break;

	case MEDIA_TYPE_AUDIO:
		p = mss_write_tfhd_atom(p, first_track->media_info.track_id, 0x02000000);
		break;
	}

	// moof.traf.trun
	for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++)
	{
		track = cur_clip->first_track;
		cur_frame = track->first_frame;
		last_frame = track->last_frame;
		for (; cur_frame < last_frame; cur_frame++)
		{
			cur_frame->duration = rescale_time(cur_frame->duration, track->media_info.timescale, MSS_TIMESCALE);
			cur_frame->pts_delay = rescale_time(cur_frame->pts_delay, track->media_info.timescale, MSS_TIMESCALE);
		}
	}

	p = mp4_builder_write_trun_atom(
		p,
		sequence,
		moof_atom_size + ATOM_HEADER_SIZE);


	if (media_set->type == MEDIA_SET_LIVE)
	{
		// using only estimate timing info in live, since we don't have the accurate timing
		//	for the lookahead segments. the timestamp has to be consistent between segments/manifest
		//	otherwise some segments may be pulled more than once
		timing_info.timestamp = mss_rescale_millis(media_set->segment_start_time);
		timing_info.duration = mss_rescale_millis(media_set->segmenter_conf->segment_duration);

		p = mss_write_uuid_tfxd_atom(p, &timing_info);

		p = mss_write_uuid_tfrf_atom(p, &timing_info);
	}
	else
	{
		mss_get_segment_timing_info(sequence, &timing_info);

		p = mss_write_uuid_tfxd_atom(p, &timing_info);
	}

	// moof.traf.xxx
	if (write_extra_traf_atoms_callback != NULL)
	{
		p = write_extra_traf_atoms_callback(write_extra_traf_atoms_context, p, moof_atom_size);
	}

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

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

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

	return VOD_OK;
}
示例#5
0
vod_status_t
hds_muxer_init_fragment(
	request_context_t* request_context,
	uint32_t segment_index,
	mpeg_metadata_t *mpeg_metadata,
	read_cache_state_t* read_cache_state,
	write_callback_t write_callback,
	void* write_context,
	bool_t size_only,
	vod_str_t* header, 
	size_t* total_fragment_size,
	hds_muxer_state_t** processor_state)
{
	mpeg_stream_metadata_t* cur_stream;
	hds_muxer_stream_state_t* stream_state;
	input_frame_t* cur_frame;
	input_frame_t* frames_end;
	hds_muxer_state_t* state;
	vod_status_t rc;
	uint32_t track_id = 1;
	uint32_t* output_offset;
	uint32_t frame_metadata_size;
	size_t afra_atom_size;
	size_t moof_atom_size;
	size_t traf_atom_size;
	size_t mdat_atom_size;
	size_t result_size;
	u_char* p;

	// initialize the muxer state
	rc = hds_muxer_init_state(request_context, mpeg_metadata, read_cache_state, write_callback, write_context, &state);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_muxer_init_fragment: hds_muxer_init_state failed %i", rc);
		return rc;
	}

	// get moof atom size
	moof_atom_size = 
		ATOM_HEADER_SIZE + 
		ATOM_HEADER_SIZE + sizeof(mfhd_atom_t);
	mdat_atom_size = ATOM_HEADER_SIZE;
	state->codec_config_size = 0;
	for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++)
	{
		moof_atom_size += hds_get_traf_atom_size(cur_stream);

		frame_metadata_size = tag_size_by_media_type[cur_stream->media_info.media_type] + sizeof(uint32_t);
		state->codec_config_size += frame_metadata_size + cur_stream->media_info.extra_data_size;

		mdat_atom_size += cur_stream->total_frames_size + cur_stream->frame_count * frame_metadata_size;
	}

	mdat_atom_size += mpeg_metadata->video_key_frame_count * state->codec_config_size;

	// get the fragment header size
	afra_atom_size = ATOM_HEADER_SIZE + sizeof(afra_atom_t) + sizeof(afra_entry_t) * mpeg_metadata->video_key_frame_count;

	result_size =
		afra_atom_size +
		moof_atom_size +
		ATOM_HEADER_SIZE;		// mdat

	// audio only - output the codec config up front, video - output the codec config before every key frame
	if (mpeg_metadata->video_key_frame_count == 0)
	{
		result_size += state->codec_config_size;
		mdat_atom_size += state->codec_config_size;
	}

	*total_fragment_size =
		afra_atom_size +
		moof_atom_size +
		mdat_atom_size;

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

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

	// afra
	p = hds_write_afra_atom_header(header->data, afra_atom_size, mpeg_metadata->video_key_frame_count);

	p = hds_calculate_output_offsets_and_write_afra_entries(state, ATOM_HEADER_SIZE, afra_atom_size + moof_atom_size, p);

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

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

	for (stream_state = state->first_stream; stream_state < state->last_stream; stream_state++)
	{
		cur_stream = stream_state->metadata;

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

		// moof.traf.tfhd
		p = hds_write_tfhd_atom(p, track_id, ATOM_HEADER_SIZE + sizeof(afra_atom_t) + moof_atom_size);

		// moof.traf.trun
		frames_end = cur_stream->frames + cur_stream->frame_count;
		switch (cur_stream->media_info.media_type)
		{
		case MEDIA_TYPE_VIDEO:
			for (cur_frame = cur_stream->frames, output_offset = stream_state->first_frame_output_offset; 
				cur_frame < frames_end; 
				cur_frame++, output_offset++)
			{
				p = hds_write_single_video_frame_trun_atom(p, cur_frame, *output_offset);
			}
			break;

		case MEDIA_TYPE_AUDIO:
			for (cur_frame = cur_stream->frames, output_offset = stream_state->first_frame_output_offset;
				cur_frame < frames_end;
				cur_frame++, output_offset++)
			{
				p = hds_write_single_audio_frame_trun_atom(p, cur_frame, *output_offset);
			}
			break;
		}
	}

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

	if (mpeg_metadata->video_key_frame_count == 0)
	{
		p = hds_muxer_write_codec_config(p, state, state->first_stream->next_frame_dts);
	}

	header->len = p - header->data;

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

	*processor_state = state;

	return VOD_OK;
}
vod_status_t
dash_packager_build_fragment_header(
	request_context_t* request_context,
	mpeg_stream_metadata_t* stream_metadata,
	uint32_t segment_index,
	uint32_t sample_description_index,
	size_t extra_traf_atoms_size,
	write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback,
	void* write_extra_traf_atoms_context,
	atom_writer_t* mdat_prefix_writer,
	bool_t size_only,
	vod_str_t* result,
	size_t* total_fragment_size)
{
	uint64_t earliest_pres_time = dash_packager_get_earliest_pres_time(stream_metadata);
	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
	mdat_atom_size = ATOM_HEADER_SIZE + stream_metadata->total_frames_size;
	if (mdat_prefix_writer != NULL)
	{
		mdat_atom_size += mdat_prefix_writer->atom_size;
	}
	trun_atom_size = mp4_builder_get_trun_atom_size(stream_metadata->media_info.media_type, stream_metadata->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 + 
		extra_traf_atoms_size;

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

	result_size =
		sizeof(styp_atom)+
		ATOM_HEADER_SIZE + (earliest_pres_time > UINT_MAX ? sizeof(sidx64_atom_t) : sizeof(sidx_atom_t)) + 
		moof_atom_size +
		mdat_atom_size - stream_metadata->total_frames_size;		// mdat

	*total_fragment_size = result_size + stream_metadata->total_frames_size;

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

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

	// styp
	p = vod_copy(result->data, styp_atom, sizeof(styp_atom));

	// sidx
	if (earliest_pres_time > UINT_MAX)
	{
		p = dash_packager_write_sidx64_atom(p, stream_metadata, earliest_pres_time, moof_atom_size + mdat_atom_size);
	}
	else
	{
		p = dash_packager_write_sidx_atom(p, stream_metadata, (uint32_t)earliest_pres_time, 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, stream_metadata->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 (mdat_prefix_writer != NULL)
	{
		first_frame_offset += mdat_prefix_writer->atom_size;
	}

	p = mp4_builder_write_trun_atom(
		p, 
		stream_metadata->media_info.media_type, 
		stream_metadata->frames, 
		stream_metadata->frame_count, 
		first_frame_offset);

	// moof.traf.xxx
	if (write_extra_traf_atoms_callback != NULL)
	{
		p = write_extra_traf_atoms_callback(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 (mdat_prefix_writer != NULL)
	{
		p = mdat_prefix_writer->write(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;
}