vod_status_t
edash_packager_build_init_mp4(
	request_context_t* request_context,
	media_set_t* media_set,
	bool_t has_clear_lead,
	bool_t size_only,
	vod_str_t* result)
{
	media_track_t* first_track = media_set->sequences[0].filtered_clips[0].first_track;
	mp4_encrypt_info_t* drm_info = (mp4_encrypt_info_t*)media_set->sequences[0].drm_info;
	stsd_writer_context_t stsd_writer_context;
	atom_writer_t pssh_atom_writer;
	atom_writer_t stsd_atom_writer;
	mp4_encrypt_system_info_t* cur_info;
	vod_status_t rc;

	rc = edash_packager_init_stsd_writer_context(
		request_context,
		first_track->media_info.media_type,
		&first_track->raw_atoms[RTA_STSD],
		has_clear_lead,
		drm_info->key_id,
		&stsd_writer_context);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_build_init_mp4: edash_packager_init_stsd_writer_context failed %i", rc);
		return rc;
	}

	// build the pssh writer
	pssh_atom_writer.atom_size = 0;
	for (cur_info = drm_info->pssh_array.first; cur_info < drm_info->pssh_array.last; cur_info++)
	{
		pssh_atom_writer.atom_size += ATOM_HEADER_SIZE + sizeof(pssh_atom_t) + cur_info->data.len;
	}
	pssh_atom_writer.write = edash_packager_write_pssh;
	pssh_atom_writer.context = &drm_info->pssh_array;

	// build the stsd writer
	stsd_atom_writer.atom_size = stsd_writer_context.stsd_atom_size;
	stsd_atom_writer.write = edash_packager_write_stsd;
	stsd_atom_writer.context = &stsd_writer_context;

	rc = dash_packager_build_init_mp4(
		request_context,
		media_set,
		size_only,
		&pssh_atom_writer,
		&stsd_atom_writer,
		result);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_build_init_mp4: dash_packager_build_init_mp4 failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
mp4_encrypt_video_start_frame(mp4_encrypt_video_state_t* state)
{
	vod_status_t rc;

	// add an auxiliary data entry
	rc = vod_dynamic_buf_reserve(&state->auxiliary_data, sizeof(cenc_sample_auxiliary_data_t));
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"mp4_encrypt_video_start_frame: vod_dynamic_buf_reserve failed %i", rc);
		return rc;
	}

	state->auxiliary_data.pos = vod_copy(state->auxiliary_data.pos, state->base.iv, sizeof(state->base.iv));
	state->auxiliary_data.pos += sizeof(uint16_t);		// write the subsample count on frame end
	state->subsample_count = 0;

	// call the base start frame
	rc = mp4_encrypt_start_frame(&state->base);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"mp4_encrypt_video_start_frame: mp4_encrypt_start_frame failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
edash_packager_video_write_fragment_header(mp4_encrypt_video_state_t* state)
{
	dash_fragment_header_extensions_t header_extensions;
	atom_writer_t auxiliary_data_writer;
	vod_str_t fragment_header;
	vod_status_t rc;
	size_t total_fragment_size;
	bool_t reuse_buffer;

	// get the auxiliary data writer
	auxiliary_data_writer.atom_size = state->auxiliary_data.pos - state->auxiliary_data.start;
	auxiliary_data_writer.write = edash_packager_video_write_auxiliary_data;
	auxiliary_data_writer.context = state;

	header_extensions.extra_traf_atoms_size = state->base.saiz_atom_size + state->base.saio_atom_size;
	header_extensions.write_extra_traf_atoms_callback = (write_extra_traf_atoms_callback_t)mp4_encrypt_video_write_saiz_saio;
	header_extensions.write_extra_traf_atoms_context = state;
	header_extensions.mdat_prefix_writer = &auxiliary_data_writer;

	// build the fragment header
	rc = dash_packager_build_fragment_header(
		state->base.request_context,
		state->base.media_set,
		state->base.segment_index,
		0,
		&header_extensions,
		FALSE,
		&fragment_header,
		&total_fragment_size);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"edash_packager_video_write_fragment_header: dash_packager_build_fragment_header failed %i", rc);
		return rc;
	}

	rc = state->base.segment_writer.write_head(
		state->base.segment_writer.context,
		fragment_header.data, 
		fragment_header.len, 
		&reuse_buffer);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"edash_packager_video_write_fragment_header: write_head failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
vod_status_t 
write_buffer_queue_flush(write_buffer_queue_t* queue)
{
	buffer_header_t* cur_buffer;
	vod_status_t rc;

	while (!is_list_empty(&queue->buffers))
	{
		cur_buffer = (buffer_header_t*)queue->buffers.next;
		remove_entry_list(&cur_buffer->link);

		if (cur_buffer->cur_pos <= cur_buffer->start_pos)
		{
			continue;
		}

		rc = queue->write_callback(queue->write_context, cur_buffer->start_pos, cur_buffer->cur_pos - cur_buffer->start_pos);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, queue->request_context->log, 0,
				"write_buffer_queue_flush: write_callback failed %i", rc);
			return rc;
		}

		// no reason to reuse the buffer here
	}

	return VOD_OK;
}
vod_status_t
mp4_encrypt_audio_get_fragment_writer(
	segment_writer_t* result,
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	segment_writer_t* segment_writer,
	const u_char* iv)
{
	mp4_encrypt_state_t* state;
	vod_status_t rc;

	// allocate the state
	state = vod_alloc(request_context->pool, sizeof(*state));
	if (state == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"mp4_encrypt_audio_get_fragment_writer: vod_alloc failed");
		return VOD_ALLOC_FAILED;
	}

	rc = mp4_encrypt_init_state(state, request_context, media_set, segment_index, segment_writer, iv);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"mp4_encrypt_audio_get_fragment_writer: mp4_encrypt_init_state failed %i", rc);
		return rc;
	}

	result->write_tail = mp4_encrypt_audio_write_buffer;
	result->write_head = NULL;
	result->context = state;

	return VOD_OK;
}
static vod_status_t
mss_playready_audio_build_fragment_header(
	mp4_encrypt_state_t* state,
	bool_t size_only,
	vod_str_t* fragment_header,
	size_t* total_fragment_size)
{
	vod_status_t rc;
	mss_playready_audio_extra_traf_atoms_context writer_context;

	writer_context.uuid_piff_atom_size = ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t) + mp4_encrypt_audio_get_auxiliary_data_size(state);
	writer_context.state = state;

	rc = mss_packager_build_fragment_header(
		state->request_context,
		state->media_set,
		state->segment_index,
		writer_context.uuid_piff_atom_size + state->saiz_atom_size + state->saio_atom_size,
		mss_playready_audio_write_extra_traf_atoms,
		&writer_context,
		size_only,
		fragment_header,
		total_fragment_size);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"mss_playready_audio_build_fragment_header: mss_packager_build_fragment_header failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
mss_playready_video_write_fragment_header(mp4_encrypt_video_state_t* state)
{
	mss_playready_video_extra_traf_atoms_context writer_context;
	vod_str_t fragment_header;
	size_t total_fragment_size;
	bool_t reuse_buffer;
	vod_status_t rc;

	writer_context.uuid_piff_atom_size = ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t) + state->auxiliary_data.pos - state->auxiliary_data.start;
	writer_context.state = state;

	rc = mss_packager_build_fragment_header(
		state->base.request_context,
		state->base.sequence,
		state->base.segment_index,
		writer_context.uuid_piff_atom_size + state->base.saiz_atom_size + state->base.saio_atom_size,
		mss_playready_video_write_extra_traf_atoms,
		&writer_context,
		FALSE,
		&fragment_header,
		&total_fragment_size);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"mss_playready_video_write_fragment_header: mss_packager_build_fragment_header failed %i", rc);
		return rc;
	}

	rc = state->base.segment_writer.write_head(
		state->base.segment_writer.context,
		fragment_header.data,
		fragment_header.len,
		&reuse_buffer);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"mss_playready_video_write_fragment_header: write_head failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
vod_status_t
edash_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,
	vod_str_t* result)
{
	media_sequence_t* cur_sequence;
	mp4_encrypt_info_t* drm_info;
	size_t representation_tags_size;
	size_t cur_drm_tags_size;
	vod_status_t rc;

	representation_tags_size = 0;

	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		drm_info = (mp4_encrypt_info_t*)cur_sequence->drm_info;

		cur_drm_tags_size = 
			sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC) - 1 + 
			(sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PREFIX) - 1 +
				VOD_GUID_LENGTH +
			sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_SUFFIX) - 1) * drm_info->pssh_array.count;
		representation_tags_size += cur_drm_tags_size * cur_sequence->total_track_count;
	}

	rc = dash_packager_build_mpd(
		request_context,
		conf,
		base_url,
		segmenter_conf,
		media_set,
		representation_tags_size,
		edash_packager_write_content_protection,
		NULL,
		result);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_build_mpd: dash_packager_build_mpd failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
mp4_encrypt_video_add_subsample(mp4_encrypt_video_state_t* state, uint16_t bytes_of_clear_data, uint32_t bytes_of_encrypted_data)
{
	vod_status_t rc;

	rc = vod_dynamic_buf_reserve(&state->auxiliary_data, sizeof(cenc_sample_auxiliary_data_subsample_t));
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
			"mp4_encrypt_video_add_subsample: vod_dynamic_buf_reserve failed %i", rc);
		return rc;
	}
	write_be16(state->auxiliary_data.pos, bytes_of_clear_data);
	write_be32(state->auxiliary_data.pos, bytes_of_encrypted_data);
	state->subsample_count++;

	return VOD_OK;
}
vod_status_t
write_buffer_queue_send(write_buffer_queue_t* queue, off_t max_offset)
{
	buffer_header_t* cur_buffer;
	vod_status_t rc;

	while (!is_list_empty(&queue->buffers))
	{
		cur_buffer = (buffer_header_t*)queue->buffers.next;
		if (cur_buffer->cur_pos <= cur_buffer->start_pos)
		{
			break;
		}

		if (cur_buffer->end_offset > max_offset)
		{
			break;
		}

		remove_entry_list(&cur_buffer->link);
		if (cur_buffer == queue->cur_write_buffer)
		{
			queue->cur_write_buffer = NULL;
		}

		rc = queue->write_callback(queue->write_context, cur_buffer->start_pos, cur_buffer->cur_pos - cur_buffer->start_pos);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, queue->request_context->log, 0,
				"write_buffer_queue_send: write_callback failed %i", rc);
			return rc;
		}

		if (!queue->reuse_buffers)
		{
			cur_buffer->start_pos = NULL;
		}
		cur_buffer->cur_pos = cur_buffer->start_pos;
		insert_tail_list(&queue->buffers, &cur_buffer->link);
	}

	return VOD_OK;
}
static vod_status_t
hds_muxer_end_frame(hds_muxer_state_t* state)
{
	uint32_t packet_size = state->frame_header_size + state->cur_frame->size;
	vod_status_t rc;
	u_char* p;

	// write the frame size
	rc = write_buffer_get_bytes(&state->write_buffer_state, sizeof(uint32_t), NULL, &p);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"hds_muxer_end_frame: write_buffer_get_bytes failed %i", rc);
		return rc;
	}
	write_be32(p, packet_size);

	return VOD_OK;
}
Exemple #12
0
vod_status_t
write_buffer_flush(write_buffer_state_t* state, bool_t reallocate)
{
	vod_status_t rc;

	if (state->cur_pos > state->start_pos)
	{
		rc = state->write_callback(state->write_context, state->start_pos, state->cur_pos - state->start_pos);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"write_buffer_flush: write_callback failed %i", rc);
			return rc;
		}

		if (state->reuse_buffers)
		{
			state->cur_pos = state->start_pos;
			return VOD_OK;
		}
	}

	if (reallocate)
	{
		state->start_pos = vod_alloc(state->request_context->pool, WRITE_BUFFER_SIZE);
		if (state->start_pos == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"write_buffer_flush: vod_alloc failed");
			return VOD_ALLOC_FAILED;
		}
		state->end_pos = state->start_pos + WRITE_BUFFER_SIZE;
		state->cur_pos = state->start_pos;
	}
	else
	{
		state->start_pos = state->end_pos = state->cur_pos = NULL;
	}

	return VOD_OK;
}
static vod_status_t
hds_muxer_init_track(
	hds_muxer_state_t* state,
	hds_muxer_stream_state_t* cur_stream,
	media_track_t* cur_track)
{
	vod_status_t rc;

	cur_stream->track = cur_track;
	cur_stream->media_type = cur_track->media_info.media_type;
	cur_stream->timescale = cur_track->media_info.timescale;
	cur_stream->frames_source = cur_track->frames_source;
	cur_stream->frames_source_context = cur_track->frames_source_context;
	cur_stream->first_frame = cur_track->first_frame;
	cur_stream->last_frame = cur_track->last_frame;

	cur_stream->clip_start_time = hds_rescale_millis(cur_track->clip_start_time);
	cur_stream->first_frame_time_offset = cur_track->first_frame_time_offset;
	cur_stream->next_frame_time_offset = cur_stream->first_frame_time_offset;
	cur_stream->next_frame_dts = rescale_time(cur_stream->next_frame_time_offset, cur_stream->timescale, HDS_TIMESCALE);

	cur_stream->cur_frame = cur_stream->first_frame;

	cur_stream->first_frame_input_offset = cur_track->frame_offsets;
	cur_stream->cur_frame_input_offset = cur_stream->first_frame_input_offset;

	if (cur_track->media_info.media_type == MEDIA_TYPE_AUDIO)
	{
		rc = hds_get_sound_info(state->request_context, &cur_track->media_info, &cur_stream->sound_info);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"hds_muxer_init_track: hds_get_sound_info failed %i", rc);
			return rc;
		}
	}

	return VOD_OK;
}
Exemple #14
0
static vod_status_t
edash_packager_audio_build_fragment_header(
	mp4_encrypt_state_t* state,
	bool_t size_only,
	vod_str_t* fragment_header,
	size_t* total_fragment_size)
{
	dash_fragment_header_extensions_t header_extensions;
	atom_writer_t auxiliary_data_writer;
	vod_status_t rc;

	auxiliary_data_writer.atom_size = MP4_ENCRYPT_IV_SIZE * state->sequence->total_frame_count;
	auxiliary_data_writer.write = (atom_writer_func_t)mp4_encrypt_audio_write_auxiliary_data;
	auxiliary_data_writer.context = state;

	header_extensions.extra_traf_atoms_size = state->saiz_atom_size + state->saio_atom_size;
	header_extensions.write_extra_traf_atoms_callback = (write_extra_traf_atoms_callback_t)mp4_encrypt_audio_write_saiz_saio;
	header_extensions.write_extra_traf_atoms_context = state;
	header_extensions.mdat_prefix_writer = &auxiliary_data_writer;

	rc = dash_packager_build_fragment_header(
		state->request_context,
		state->media_set,
		state->segment_index,
		0,
		&header_extensions,
		size_only,
		fragment_header,
		total_fragment_size);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"edash_packager_audio_build_fragment_header: dash_packager_build_fragment_header failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
edash_packager_audio_build_fragment_header(
	mp4_encrypt_state_t* state,
	bool_t size_only,
	vod_str_t* fragment_header,
	size_t* total_fragment_size)
{
	dash_fragment_header_extensions_t header_extensions;
	vod_status_t rc;

	// get the header extensions
	header_extensions.extra_traf_atoms_size =
		state->saiz_atom_size + 
		state->saio_atom_size + 
		ATOM_HEADER_SIZE + sizeof(senc_atom_t) + MP4_AES_CTR_IV_SIZE * state->sequence->total_frame_count;
	header_extensions.write_extra_traf_atoms_callback = edash_packager_audio_write_encryption_atoms;
	header_extensions.write_extra_traf_atoms_context = state;

	// build the fragment header
	rc = dash_packager_build_fragment_header(
		state->request_context,
		state->media_set,
		state->segment_index,
		0,
		&header_extensions,
		size_only,
		fragment_header,
		total_fragment_size);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"edash_packager_audio_build_fragment_header: dash_packager_build_fragment_header failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
static vod_status_t
mp4_metadata_reader_read(
    void* ctx,
    uint64_t offset,
    vod_str_t* buffer,
    media_format_read_metadata_result_t* result)
{
    mp4_read_metadata_state_t* state = ctx;
    const u_char* ftyp_ptr;
    size_t ftyp_size;
    u_char* uncomp_buffer;
    off_t moov_offset;
    size_t moov_size;
    vod_status_t rc;

    if (state->state == STATE_READ_MOOV_DATA)
    {
        // make sure we got the whole moov atom
        moov_size = state->parts[MP4_METADATA_PART_MOOV].len;
        if (buffer->len < moov_size)
        {
            vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
                          "mp4_metadata_reader_read: buffer size %uz is smaller than moov size %uz",
                          buffer->len, moov_size);
            return VOD_BAD_DATA;
        }
        moov_offset = 0;

        goto done;
    }

    if (state->parts[MP4_METADATA_PART_FTYP].len == 0)
    {
        // try to find the ftyp atom
        rc = mp4_parser_get_ftyp_atom_into(
                 state->request_context,
                 buffer->data,
                 buffer->len,
                 &ftyp_ptr,
                 &ftyp_size);
        if (rc != VOD_OK)
        {
            vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                           "mp4_metadata_reader_read: mp4_parser_get_ftyp_atom_into failed %i", rc);
            return rc;
        }

        if (ftyp_size > 0 &&
                ftyp_ptr + ftyp_size <= buffer->len + buffer->data)
        {
            // got a full ftyp atom
            state->parts[MP4_METADATA_PART_FTYP].data = vod_alloc(state->request_context->pool, ftyp_size);
            if (state->parts[MP4_METADATA_PART_FTYP].data == NULL)
            {
                vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                               "mp4_metadata_reader_read: vod_alloc failed");
                return VOD_ALLOC_FAILED;
            }

            vod_memcpy(state->parts[MP4_METADATA_PART_FTYP].data, ftyp_ptr, ftyp_size);
            state->parts[MP4_METADATA_PART_FTYP].len = ftyp_size;
        }
        else
        {
            vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                           "mp4_metadata_reader_read: ftyp atom not found");
        }
    }

    // get moov atom offset and size
    rc = mp4_parser_get_moov_atom_info(
             state->request_context,
             buffer->data,
             buffer->len,
             &moov_offset,
             &moov_size);
    if (rc != VOD_OK)
    {
        vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                       "mp4_metadata_reader_read: mp4_parser_get_moov_atom_info failed %i", rc);
        return rc;
    }

    if (moov_size <= 0)
    {
        // moov not found
        if ((size_t)moov_offset < buffer->len)
        {
            vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
                          "mp4_metadata_reader_read: moov start offset %O is smaller than the buffer size %uz",
                          moov_offset, buffer->len);
            return VOD_BAD_DATA;
        }

        if (state->moov_start_reads <= 0)
        {
            vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
                          "mp4_metadata_reader_read: exhausted all moov read attempts");
            return VOD_BAD_DATA;
        }

        state->moov_start_reads--;

        // perform another read attempt
        result->read_req.read_offset = offset + moov_offset;
        result->read_req.read_size = 0;
        result->read_req.realloc_buffer = FALSE;

        return VOD_AGAIN;
    }

    // save the moov size
    state->parts[MP4_METADATA_PART_MOOV].len = moov_size;

    // check whether we already have the whole atom
    if (moov_offset + moov_size <= buffer->len)
    {
        vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                       "mp4_metadata_reader_read: already read the full moov atom");
        goto done;
    }

    // validate the moov size
    if (moov_size > state->max_moov_size)
    {
        vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
                      "mp4_metadata_reader_read: moov size %uD exceeds the max %uz",
                      moov_size, state->max_moov_size);
        return VOD_BAD_DATA;
    }

    state->state = STATE_READ_MOOV_DATA;
    result->read_req.read_offset = offset + moov_offset;
    result->read_req.read_size = moov_size;
    result->read_req.realloc_buffer = FALSE;

    return VOD_AGAIN;

done:

    state->parts[MP4_METADATA_PART_MOOV].data = buffer->data + moov_offset;

    // uncompress the moov atom if needed
    rc = mp4_parser_uncompress_moov(
             state->request_context,
             state->parts[MP4_METADATA_PART_MOOV].data,
             moov_size,
             state->max_moov_size,
             &uncomp_buffer,
             &moov_offset,
             &moov_size);
    if (rc != VOD_OK)
    {
        vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
                       "mp4_metadata_reader_read: mp4_parser_uncompress_moov failed %i", rc);
        return rc;
    }

    if (uncomp_buffer != NULL)
    {
        state->parts[MP4_METADATA_PART_MOOV].data = uncomp_buffer + moov_offset;
        state->parts[MP4_METADATA_PART_MOOV].len = moov_size;
    }

    result->parts = state->parts;
    result->part_count = MP4_METADATA_PART_COUNT;

    return VOD_OK;
}
static vod_status_t
mp4_encrypt_video_write_buffer(void* context, u_char* buffer, uint32_t size)
{
	mp4_encrypt_video_state_t* state = (mp4_encrypt_video_state_t*)context;
	vod_str_t fragment_header;
	u_char* buffer_end = buffer + size;
	u_char* cur_pos = buffer;
	u_char* output;
	uint32_t write_size;
	int32_t cur_shift;
	size_t ignore;
	bool_t init_track;
	vod_status_t rc;

	while (cur_pos < buffer_end)
	{
		switch (state->cur_state)
		{
		case STATE_PACKET_SIZE:
			if (state->base.frame_size_left <= 0)
			{
				rc = mp4_encrypt_video_start_frame(state);
				if (rc != VOD_OK)
				{
					return rc;
				}

				if (state->base.frame_size_left <= 0)
				{
					state->cur_state = STATE_PACKET_DATA;
					break;
				}
			}

			for (; state->length_bytes_left && cur_pos < buffer_end; state->length_bytes_left--)
			{
				state->packet_size_left = (state->packet_size_left << 8) | *cur_pos++;
			}

			if (cur_pos >= buffer_end)
			{
				break;
			}

			if (state->base.frame_size_left < state->nal_packet_size_length + state->packet_size_left)
			{
				vod_log_error(VOD_LOG_ERR, state->base.request_context->log, 0,
					"mp4_encrypt_video_write_buffer: frame size %uD too small, nalu size %uD packet size %uD",
					state->base.frame_size_left, state->nal_packet_size_length, state->packet_size_left);
				return VOD_BAD_DATA;
			}

			state->base.frame_size_left -= state->nal_packet_size_length + state->packet_size_left;

			state->cur_state++;
			// fall through

		case STATE_NAL_TYPE:

			// write the packet size and nal type
			rc = write_buffer_get_bytes(&state->base.write_buffer, state->nal_packet_size_length + 1, NULL, &output);
			if (rc != VOD_OK)
			{
				return rc;
			}

			for (cur_shift = (state->nal_packet_size_length - 1) * 8; cur_shift >= 0; cur_shift -= 8)
			{
				*output++ = (state->packet_size_left >> cur_shift) & 0xff;
			}

			*output++ = *cur_pos++;		// nal type

			// update the packet size
			if (state->packet_size_left <= 0)
			{
				vod_log_error(VOD_LOG_ERR, state->base.request_context->log, 0,
					"mp4_encrypt_video_write_buffer: zero size packet");
				return VOD_BAD_DATA;
			}
			state->packet_size_left--;

			// add the subsample
			rc = mp4_encrypt_video_add_subsample(state, state->nal_packet_size_length + 1, state->packet_size_left);
			if (rc != VOD_OK)
			{
				return rc;
			}

			state->cur_state++;
			// fall through

		case STATE_PACKET_DATA:
			write_size = (uint32_t)(buffer_end - cur_pos);
			write_size = vod_min(write_size, state->packet_size_left);
			
			rc = mp4_encrypt_write_encrypted(&state->base, cur_pos, write_size);
			if (rc != VOD_OK)
			{
				return rc;
			}

			cur_pos += write_size;
			state->packet_size_left -= write_size;
			if (state->packet_size_left > 0)
			{
				break;
			}

			// finished a packet
			state->cur_state = STATE_PACKET_SIZE;
			state->length_bytes_left = state->nal_packet_size_length;
			state->packet_size_left = 0;

			if (state->base.frame_size_left > 0)
			{
				break;
			}

			// finished a frame
			rc = mp4_encrypt_video_end_frame(state);
			if (rc != VOD_OK)
			{
				return rc;
			}

			// move to the next frame
			if (mp4_encrypt_move_to_next_frame(&state->base, &init_track))
			{
				if (init_track)
				{
					rc = mp4_encrypt_video_init_track(state, state->base.cur_clip->first_track);
					if (rc != VOD_OK)
					{
						return rc;
					}
				}

				break;
			}

			// finished all frames
			rc = write_buffer_flush(&state->base.write_buffer, FALSE);
			if (rc != VOD_OK)
			{
				return rc;
			}

			mp4_encrypt_video_prepare_saiz_saio(state);

			rc = state->build_fragment_header(state, &fragment_header, &ignore);
			if (rc != VOD_OK)
			{
				return rc;
			}

			rc = state->base.segment_writer.write_head(
				state->base.segment_writer.context,
				fragment_header.data,
				fragment_header.len);
			if (rc != VOD_OK)
			{
				vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->base.request_context->log, 0,
					"mp4_encrypt_video_write_buffer: write_head failed %i", rc);
				return rc;
			}

			break;
		}
	}

	return VOD_OK;
}
vod_status_t
mp4_encrypt_video_get_fragment_writer(
	segment_writer_t* result,
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	bool_t single_nalu_per_frame,
	mp4_encrypt_video_build_fragment_header_t build_fragment_header,
	segment_writer_t* segment_writer,
	const u_char* iv, 
	vod_str_t* fragment_header, 
	size_t* total_fragment_size)
{
	media_sequence_t* sequence = &media_set->sequences[0];
	mp4_encrypt_video_state_t* state;
	vod_status_t rc;
	uint32_t initial_size;
	bool_t ignore;

	// allocate the state
	state = vod_alloc(request_context->pool, sizeof(*state));
	if (state == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"mp4_encrypt_video_get_fragment_writer: vod_alloc failed (1)");
		return VOD_ALLOC_FAILED;
	}

	rc = mp4_encrypt_init_state(&state->base, request_context, media_set, segment_index, segment_writer, iv);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"mp4_encrypt_video_get_fragment_writer: mp4_encrypt_init_state failed %i", rc);
		return rc;
	}

	if (!mp4_encrypt_move_to_next_frame(&state->base, &ignore))
	{
		// an empty segment - write won't be called so we need to write the header here
		state->auxiliary_data.start = NULL;
		state->auxiliary_data.pos = NULL;
		state->auxiliary_sample_sizes = NULL;
		state->auxiliary_sample_sizes_pos = NULL;
		state->default_auxiliary_sample_size = 0;
		state->saiz_sample_count = 0;

		rc = build_fragment_header(state, fragment_header, total_fragment_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"mp4_encrypt_video_get_fragment_writer: write_fragment_header failed %i", rc);
			return rc;
		}

		return VOD_OK;
	}
	
	if (single_nalu_per_frame)
	{
		// each frame is a single nal unit, can generate the auxiliary data and write the header now
		state->build_fragment_header = NULL;
		state->single_nalu_warning_printed = FALSE;

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

		rc = build_fragment_header(state, fragment_header, total_fragment_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"mp4_encrypt_video_get_fragment_writer: write_fragment_header failed %i", rc);
			return rc;
		}

		result->write_tail = mp4_encrypt_video_snpf_write_buffer;
	}
	else
	{
		state->build_fragment_header = build_fragment_header;

		// for progressive AVC a frame usually contains a single nalu, except the first frame which may contain codec copyright info
		initial_size =
			(sizeof(cenc_sample_auxiliary_data_t) + sizeof(cenc_sample_auxiliary_data_subsample_t)) * sequence->total_frame_count +
			sizeof(cenc_sample_auxiliary_data_subsample_t);
		rc = vod_dynamic_buf_init(&state->auxiliary_data, request_context, initial_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"mp4_encrypt_video_get_fragment_writer: vod_dynamic_buf_init failed %i", rc);
			return rc;
		}

		state->auxiliary_sample_sizes = vod_alloc(request_context->pool, sequence->total_frame_count);
		if (state->auxiliary_sample_sizes == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"mp4_encrypt_video_get_fragment_writer: vod_alloc failed (2)");
			return VOD_ALLOC_FAILED;
		}
		state->auxiliary_sample_sizes_pos = state->auxiliary_sample_sizes;

		result->write_tail = mp4_encrypt_video_write_buffer;
	}

	// init writing for the first track
	rc = mp4_encrypt_video_init_track(state, state->base.cur_clip->first_track);
	if (rc != VOD_OK)
	{
		return rc;
	}

	result->write_head = NULL;
	result->context = state;

	return VOD_OK;
}
vod_status_t
hds_muxer_process_frames(hds_muxer_state_t* state)
{
	u_char* read_buffer;
	uint32_t read_size;
	vod_status_t rc;
	bool_t wrote_data = FALSE;
	bool_t frame_done;

	for (;;)
	{
		// read some data from the frame
		rc = state->frames_source->read(state->frames_source_context, &read_buffer, &read_size, &frame_done);
		if (rc != VOD_OK)
		{
			if (rc != VOD_AGAIN)
			{
				return rc;
			}

			if (!wrote_data && !state->first_time)
			{
				vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
					"hds_muxer_process_frames: no data was handled, probably a truncated file");
				return VOD_BAD_DATA;
			}

			state->first_time = FALSE;

			return VOD_AGAIN;
		}

		wrote_data = TRUE;

		// write the frame
		rc = write_buffer_write(&state->write_buffer_state, read_buffer, read_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"hds_muxer_process_frames: write_buffer_write failed %i", rc);
			return rc;
		}

		// if not done, ask the cache for more data
		if (!frame_done)
		{
			continue;
		}

		// end the frame and start a new one
		rc = hds_muxer_end_frame(state);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"hds_muxer_process_frames: write_buffer_write failed %i", rc);
			return rc;
		}

		rc = hds_muxer_start_frame(state);
		if (rc != VOD_OK)
		{
			if (rc == VOD_NOT_FOUND)
			{
				break;		// done
			}

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

	// flush the buffer
	rc = write_buffer_flush(&state->write_buffer_state, FALSE);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"hds_muxer_process_frames: write_buffer_flush failed %i", rc);
		return rc;
	}
	return VOD_OK;
}
static vod_status_t
hds_muxer_init_state(
	request_context_t* request_context,
	mpeg_metadata_t *mpeg_metadata,
	read_cache_state_t* read_cache_state,
	write_callback_t write_callback,
	void* write_context,
	hds_muxer_state_t** result)
{
	mpeg_stream_metadata_t* cur_stream;
	hds_muxer_stream_state_t* stream_state;
	hds_muxer_state_t* state;
	vod_status_t rc;

	// allocate the state and stream states
	state = vod_alloc(request_context->pool, sizeof(*state));
	if (state == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_muxer_init_state: vod_alloc failed (1)");
		return VOD_ALLOC_FAILED;
	}

	state->first_stream = vod_alloc(
		request_context->pool, 
		sizeof(state->first_stream[0]) * mpeg_metadata->streams.nelts);
	if (state->first_stream == NULL)
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"hds_muxer_init_state: vod_alloc failed (2)");
		return VOD_ALLOC_FAILED;
	}
	state->last_stream = state->first_stream + mpeg_metadata->streams.nelts;
	state->request_context = request_context;
	state->cur_frame = NULL;

	state->read_cache_state = read_cache_state;
	write_buffer_init(&state->write_buffer_state, request_context, write_callback, write_context);

	stream_state = state->first_stream;
	for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++, stream_state++)
	{
		// initialize the stream
		stream_state->metadata = cur_stream;
		stream_state->media_type = cur_stream->media_info.media_type;
		stream_state->timescale = cur_stream->media_info.timescale;
		stream_state->first_frame = cur_stream->frames;
		stream_state->last_frame = cur_stream->frames + cur_stream->frame_count;

		stream_state->first_frame_time_offset = cur_stream->first_frame_time_offset;
		stream_state->next_frame_time_offset = cur_stream->first_frame_time_offset;
		stream_state->next_frame_dts = rescale_time(stream_state->next_frame_time_offset, stream_state->timescale, HDS_TIMESCALE);

		stream_state->cur_frame = stream_state->first_frame;

		stream_state->first_frame_input_offset = cur_stream->frame_offsets;
		stream_state->cur_frame_input_offset = stream_state->first_frame_input_offset;

		stream_state->first_frame_output_offset = vod_alloc(
			request_context->pool,
			cur_stream->frame_count * sizeof(uint32_t));
		if (stream_state->first_frame_output_offset == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"hds_muxer_init_state: vod_alloc failed (3)");
			return VOD_ALLOC_FAILED;
		}
		stream_state->cur_frame_output_offset = stream_state->first_frame_output_offset;

		if (cur_stream->media_info.media_type == MEDIA_TYPE_AUDIO)
		{
			rc = hds_get_sound_info(request_context, &cur_stream->media_info, &stream_state->sound_info);
			if (rc != VOD_OK)
			{
				vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
					"hds_muxer_init_state: hds_get_sound_info failed %i", rc);
				return rc;
			}
		}
	}

	*result = 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,
	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;
}
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
hds_muxer_process_frames(hds_muxer_state_t* state, uint64_t* required_offset)
{
	u_char* read_buffer;
	uint32_t read_size;
	uint32_t write_size;
	uint64_t offset;
	vod_status_t rc;
	bool_t first_time = (state->cur_frame == NULL);
	bool_t wrote_data = FALSE;

	for (;;)
	{
		// start a new frame if we don't have a frame
		if (state->cur_frame == NULL)
		{
			rc = hds_muxer_start_frame(state);
			if (rc != VOD_OK)
			{
				vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
					"hds_muxer_process_frames: hds_muxer_start_frame failed %i", rc);
				return rc;
			}

			if (state->cur_frame == NULL)
			{
				break;		// done
			}
		}

		// read some data from the frame
		offset = state->cur_frame_offset + state->cur_frame_pos;
		if (!read_cache_get_from_cache(state->read_cache_state, state->cache_slot_id, offset, &read_buffer, &read_size))
		{
			if (!wrote_data && !first_time)
			{
				vod_log_error(VOD_LOG_ERR, state->request_context->log, 0,
					"hds_muxer_process_frames: no data was handled, probably a truncated file");
				return VOD_BAD_DATA;
			}
			*required_offset = offset;
			return VOD_AGAIN;
		}

		wrote_data = TRUE;

		// write the frame
		write_size = MIN(state->cur_frame->size - state->cur_frame_pos, read_size);
		rc = write_buffer_write(&state->write_buffer_state, read_buffer, write_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
				"hds_muxer_process_frames: write_buffer_write failed %i", rc);
			return rc;
		}
		state->cur_frame_pos += write_size;

		// flush the frame if we finished writing it
		if (state->cur_frame_pos >= state->cur_frame->size)
		{
			rc = hds_muxer_end_frame(state);
			if (rc != VOD_OK)
			{
				vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
					"hds_muxer_process_frames: write_buffer_write failed %i", rc);
				return rc;
			}

			state->cur_frame = NULL;
		}
	}

	// flush the buffer
	rc = write_buffer_flush(&state->write_buffer_state, FALSE);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0,
			"hds_muxer_process_frames: write_buffer_flush failed %i", rc);
		return rc;
	}
	return VOD_OK;
}
vod_status_t
edash_packager_build_mpd(
	request_context_t* request_context,
	dash_manifest_config_t* conf,
	vod_str_t* base_url,
	media_set_t* media_set,
	vod_str_t* result)
{
	media_sequence_t* cur_sequence;
	drm_system_info_t* cur_info;
	drm_info_t* drm_info;
	u_char* pssh_temp_buffer = NULL;
	size_t representation_tags_size;
	size_t cur_drm_tags_size;
	size_t cur_pssh_size;
	size_t max_pssh_size = 0;
	vod_status_t rc;

	representation_tags_size = 0;

	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		drm_info = (drm_info_t*)cur_sequence->drm_info;

		cur_drm_tags_size = 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)
			{
				cur_drm_tags_size +=
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART1) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART2) - 1 +
					vod_base64_encoded_length(cur_info->data.len) +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3) - 1;
			}
			else
			{
				cur_pssh_size = ATOM_HEADER_SIZE + sizeof(pssh_atom_t) + cur_info->data.len;
				if (cur_pssh_size > max_pssh_size)
				{
					max_pssh_size = cur_pssh_size;
				}

				cur_drm_tags_size +=
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3) - 1 +
					vod_base64_encoded_length(cur_pssh_size) +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4) - 1;

				continue;
			}
		}

		representation_tags_size += cur_drm_tags_size * cur_sequence->total_track_count;
	}

	if (max_pssh_size > 0)
	{
		pssh_temp_buffer = vod_alloc(request_context->pool, max_pssh_size);
		if (pssh_temp_buffer == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"edash_packager_build_mpd: vod_alloc failed");
			return VOD_ALLOC_FAILED;
		}
	}

	rc = dash_packager_build_mpd(
		request_context,
		conf,
		base_url,
		media_set,
		representation_tags_size,
		edash_packager_write_content_protection,
		pssh_temp_buffer,
		result);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_build_mpd: dash_packager_build_mpd failed %i", rc);
		return rc;
	}

	return VOD_OK;
}
vod_status_t
edash_packager_get_fragment_writer(
	segment_writer_t* result,
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	bool_t single_nalu_per_frame,
	segment_writer_t* segment_writer,
	const u_char* iv,
	bool_t size_only,
	vod_str_t* fragment_header,
	size_t* total_fragment_size)
{
	dash_fragment_header_extensions_t header_extensions;
	mp4_encrypt_passthrough_context_t passthrough_context;
	uint32_t media_type = media_set->sequences[0].media_type;
	vod_status_t rc;

	if (mp4_encrypt_passthrough_init(&passthrough_context, media_set->sequences))
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_get_fragment_writer: using encryption passthrough");

		// get the header extensions
		header_extensions.extra_traf_atoms_size = passthrough_context.total_size + ATOM_HEADER_SIZE + sizeof(senc_atom_t);
		header_extensions.write_extra_traf_atoms_callback = edash_packager_passthrough_write_encryption_atoms;
		header_extensions.write_extra_traf_atoms_context = &passthrough_context;

		// build the fragment header
		rc = dash_packager_build_fragment_header(
			request_context,
			media_set,
			segment_index,
			0,
			&header_extensions,
			size_only,
			fragment_header,
			total_fragment_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"edash_packager_get_fragment_writer: dash_packager_build_fragment_header failed %i", rc);
			return rc;
		}

		// use original writer
		vod_memzero(result, sizeof(*result));

		return VOD_OK;
	}

	switch (media_type)
	{
	case MEDIA_TYPE_VIDEO:
		return mp4_encrypt_video_get_fragment_writer(
			result, 
			request_context, 
			media_set, 
			segment_index, 
			single_nalu_per_frame,
			edash_packager_video_build_fragment_header,
			segment_writer, 
			iv, 
			fragment_header,
			total_fragment_size);

	case MEDIA_TYPE_AUDIO:
		rc = mp4_encrypt_audio_get_fragment_writer(
			result, 
			request_context, 
			media_set,
			segment_index, 
			segment_writer, 
			iv);
		if (rc != VOD_OK)
		{
			return rc;
		}

		rc = edash_packager_audio_build_fragment_header(
			result->context,
			size_only,
			fragment_header,
			total_fragment_size);
		if (rc != VOD_OK)
		{
			return rc;
		}

		return VOD_OK;
	}

	vod_log_error(VOD_LOG_ERR, request_context->log, 0,
		"edash_packager_get_fragment_writer: invalid media type %uD", media_type);
	return VOD_UNEXPECTED;
}
vod_status_t
mss_playready_get_fragment_writer(
	segment_writer_t* result,
	request_context_t* request_context,
	media_set_t* media_set,
	uint32_t segment_index,
	segment_writer_t* segment_writer,
	const u_char* iv,
	bool_t size_only,
	vod_str_t* fragment_header,
	size_t* total_fragment_size)
{
	mp4_encrypt_passthrough_context_t passthrough_context;
	uint32_t media_type = media_set->sequences[0].media_type;
	vod_status_t rc;

	if (mp4_encrypt_passthrough_init(&passthrough_context, media_set->sequences))
	{
		vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"mss_playready_get_fragment_writer: using encryption passthrough");

		// build the fragment header
		rc = mss_packager_build_fragment_header(
			request_context,
			media_set,
			segment_index,
			passthrough_context.total_size + ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t),
			mss_playready_passthrough_write_encryption_atoms, 
			&passthrough_context,
			size_only,
			fragment_header,
			total_fragment_size);
		if (rc != VOD_OK)
		{
			vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"mss_playready_get_fragment_writer: mss_packager_build_fragment_header failed %i", rc);
			return rc;
		}

		// use original writer
		vod_memzero(result, sizeof(*result));

		return VOD_OK;
	}

	switch (media_type)
	{
	case MEDIA_TYPE_VIDEO:
		return mp4_encrypt_video_get_fragment_writer(
			result,
			request_context,
			media_set,
			segment_index,
			mss_playready_video_write_fragment_header,
			segment_writer,
			iv);

	case MEDIA_TYPE_AUDIO:
		rc = mp4_encrypt_audio_get_fragment_writer(
			result,
			request_context,
			media_set,
			segment_index,
			segment_writer,
			iv);
		if (rc != VOD_OK)
		{
			return rc;
		}

		rc = mss_playready_audio_build_fragment_header(
			result->context,
			size_only,
			fragment_header,
			total_fragment_size);
		if (rc != VOD_OK)
		{
			return rc;
		}

		return VOD_OK;
	}

	vod_log_error(VOD_LOG_ERR, request_context->log, 0,
		"mss_playready_get_fragment_writer: invalid media type %uD", media_type);
	return VOD_UNEXPECTED;
}
Exemple #28
0
vod_status_t
edash_packager_build_mpd(
	request_context_t* request_context,
	dash_manifest_config_t* conf,
	vod_str_t* base_url,
	media_set_t* media_set,
	bool_t drm_single_key,
	vod_str_t* result)
{
	write_content_protection_context_t context;
	dash_manifest_extensions_t extensions;
	media_sequence_t* cur_sequence;
	drm_system_info_t* cur_info;
	tags_writer_t content_prot_writer;
	drm_info_t* drm_info;
	size_t representation_tags_size;
	size_t cur_drm_tags_size;
	size_t cur_pssh_size;
	size_t max_pssh_size = 0;
	vod_status_t rc;

	representation_tags_size = 0;

	for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++)
	{
		drm_info = (drm_info_t*)cur_sequence->drm_info;

		cur_drm_tags_size = 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)
			{
				cur_drm_tags_size +=
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_V2_PART1) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_V2_PART2) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3) - 1 +
					vod_base64_encoded_length(cur_info->data.len) +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART4) - 1;
			}
			else
			{
				cur_pssh_size = ATOM_HEADER_SIZE + sizeof(pssh_atom_t) + cur_info->data.len;
				if (cur_pssh_size > max_pssh_size)
				{
					max_pssh_size = cur_pssh_size;
				}

				cur_drm_tags_size +=
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2) - 1 +
					VOD_GUID_LENGTH +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3) - 1 +
					vod_base64_encoded_length(cur_pssh_size) +
					sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4) - 1;

				continue;
			}
		}

		representation_tags_size += cur_drm_tags_size * cur_sequence->total_track_count;
	}

	context.write_playready_kid = conf->write_playready_kid;
	if (max_pssh_size > 0)
	{
		context.temp_buffer = vod_alloc(request_context->pool, max_pssh_size);
		if (context.temp_buffer == NULL)
		{
			vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
				"edash_packager_build_mpd: vod_alloc failed");
			return VOD_ALLOC_FAILED;
		}
	}

	content_prot_writer.size = representation_tags_size;
	content_prot_writer.write = edash_packager_write_content_protection;
	content_prot_writer.context = &context;

	if (drm_single_key)
	{
		// write the ContentProtection tags under AdaptationSet
		extensions.adaptation_set = content_prot_writer;
		vod_memzero(&extensions.representation, sizeof(extensions.representation));
	}
	else
	{
		// write the ContentProtection tags under Representation
		vod_memzero(&extensions.adaptation_set, sizeof(extensions.adaptation_set));
		extensions.representation = content_prot_writer;
	}

	rc = dash_packager_build_mpd(
		request_context,
		conf,
		base_url,
		media_set,
		&extensions,
		result);
	if (rc != VOD_OK)
	{
		vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0,
			"edash_packager_build_mpd: dash_packager_build_mpd failed %i", rc);
		return rc;
	}

	return VOD_OK;
}