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;
}
static vod_status_t
hds_muxer_start_frame(hds_muxer_state_t* state)
{
	hds_muxer_stream_state_t* selected_stream;
	uint64_t cur_frame_offset;
	uint64_t cur_frame_dts;
	size_t alloc_size;
	u_char* p;
	vod_status_t rc;

	rc = hds_muxer_choose_stream(state, &selected_stream);
	if (rc != VOD_OK)
	{
		return rc;
	}

	// init the frame
	state->cur_frame = selected_stream->cur_frame;
	state->frames_source = selected_stream->frames_source;
	state->frames_source_context = selected_stream->frames_source_context;
	selected_stream->cur_frame++;
	cur_frame_offset = *selected_stream->cur_frame_input_offset;
	selected_stream->cur_frame_input_offset++;
	selected_stream->cur_frame_output_offset++;

	selected_stream->next_frame_time_offset += state->cur_frame->duration;
	cur_frame_dts = selected_stream->next_frame_dts + selected_stream->clip_start_time;
	selected_stream->next_frame_dts = rescale_time(selected_stream->next_frame_time_offset, selected_stream->timescale, HDS_TIMESCALE);

	state->cache_slot_id = selected_stream->media_type;

	// allocate room for the mux packet header
	state->frame_header_size = tag_size_by_media_type[selected_stream->media_type];

	alloc_size = state->frame_header_size;
	if (selected_stream->media_type == MEDIA_TYPE_VIDEO && state->cur_frame->key_frame)
	{
		alloc_size += state->codec_config_size;
	}

	rc = write_buffer_get_bytes(&state->write_buffer_state, alloc_size, NULL, &p);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"hds_muxer_start_frame: write_buffer_get_bytes failed %i", rc);
		return rc;
	}

	// write the mux packet header and optionally codec config
	if (selected_stream->media_type == MEDIA_TYPE_VIDEO && state->cur_frame->key_frame)
	{
		p = hds_muxer_write_codec_config(p, state, cur_frame_dts);
	}

	switch (selected_stream->media_type)
	{
	case MEDIA_TYPE_VIDEO:
		hds_write_video_tag_header(
			p,
			state->cur_frame->size,
			cur_frame_dts,
			state->cur_frame->key_frame ? FRAME_TYPE_KEY_FRAME : FRAME_TYPE_INTER_FRAME,
			AVC_PACKET_TYPE_NALU,
			rescale_time(state->cur_frame->pts_delay, selected_stream->timescale, HDS_TIMESCALE));
		break;

	case MEDIA_TYPE_AUDIO:
		hds_write_audio_tag_header(
			p,
			state->cur_frame->size,
			cur_frame_dts,
			selected_stream->sound_info,
			AAC_PACKET_TYPE_RAW);
	}

	rc = state->frames_source->start_frame(state->frames_source_context, state->cur_frame, cur_frame_offset);
	if (rc != VOD_OK)
	{
		return rc;
	}

	return VOD_OK;
}
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;
}