vod_status_t hds_muxer_init_fragment( request_context_t* request_context, hds_fragment_config_t* conf, 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 if (conf->generate_moof_atom) { afra_atom_size = ATOM_HEADER_SIZE + sizeof(afra_atom_t) + sizeof(afra_entry_t) * mpeg_metadata->video_key_frame_count; } 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 (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; } p = header->data; if (conf->generate_moof_atom) { // afra p = hds_write_afra_atom_header(p, 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; } } } else { // calculate the output offsets hds_calculate_output_offsets_and_write_afra_entries(state, 0, 0, NULL); } // 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 rate_filter_parse( void* ctx, vod_json_value_t* element, void** result) { media_filter_parse_context_t* context = ctx; media_clip_rate_filter_t* filter; media_range_t* new_range; media_range_t* old_range; vod_json_value_t* params[RATE_FILTER_PARAM_COUNT]; vod_json_value_t* source; vod_json_value_t* rate; uint32_t old_duration; vod_status_t rc; vod_log_debug0(VOD_LOG_DEBUG_LEVEL, context->request_context->log, 0, "rate_filter_parse: started"); vod_memzero(params, sizeof(params)); vod_json_get_object_values( element, &rate_filter_hash, params); rate = params[RATE_FILTER_PARAM_RATE]; source = params[RATE_FILTER_PARAM_SOURCE]; if (rate == NULL || source == NULL) { vod_log_error(VOD_LOG_ERR, context->request_context->log, 0, "rate_filter_parse: \"rate\" and \"source\" are mandatory for rate filter"); return VOD_BAD_MAPPING; } if (rate->v.num.denom > 100) { vod_log_error(VOD_LOG_ERR, context->request_context->log, 0, "rate_filter_parse: invalid rate, only 2 decimal points are allowed"); return VOD_BAD_MAPPING; } if (rate->v.num.nom < 0 || rate->v.num.denom > (uint64_t)rate->v.num.nom * 2 || (uint64_t)rate->v.num.nom > rate->v.num.denom * 2) { vod_log_error(VOD_LOG_ERR, context->request_context->log, 0, "rate_filter_parse: invalid rate %L/%uL, must be between 0.5 and 2", rate->v.num.nom, rate->v.num.denom); return VOD_BAD_MAPPING; } filter = vod_alloc(context->request_context->pool, sizeof(*filter) + sizeof(filter->base.sources[0])); if (filter == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, context->request_context->log, 0, "rate_filter_parse: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } filter->base.sources = (void*)(filter + 1); filter->base.source_count = 1; filter->base.type = MEDIA_CLIP_RATE_FILTER; filter->base.audio_filter = &rate_filter; filter->rate.nom = rate->v.num.nom; filter->rate.denom = rate->v.num.denom; old_range = context->range; if (old_range != NULL) { new_range = vod_alloc(context->request_context->pool, sizeof(*new_range)); if (new_range == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, context->request_context->log, 0, "rate_filter_parse: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } new_range->start = (old_range->start * filter->rate.nom) / filter->rate.denom; new_range->end = (old_range->end * filter->rate.nom) / filter->rate.denom; new_range->timescale = old_range->timescale; context->range = new_range; } old_duration = context->duration; context->duration = ((uint64_t)old_duration * filter->rate.nom) / filter->rate.denom; rc = media_set_parse_clip( context, source, &filter->base, &filter->base.sources[0]); if (rc != VOD_JSON_OK) { return rc; } context->range = old_range; context->duration = old_duration; *result = &filter->base; vod_log_debug2(VOD_LOG_DEBUG_LEVEL, context->request_context->log, 0, "rate_filter_parse: done, rate=%uD/%uD", filter->rate.nom, filter->rate.denom); return VOD_OK; }
static vod_status_t m3u8_builder_build_required_tracks_string( request_context_t* request_context, bool_t include_file_index, mpeg_metadata_t* mpeg_metadata, vod_str_t* required_tracks) { mpeg_stream_metadata_t* cur_stream; uint32_t printed_file_indexes; uint32_t file_index; u_char* p; size_t result_size; result_size = mpeg_metadata->streams.nelts * (sizeof("-v") - 1 + vod_get_int_print_len(mpeg_metadata->max_track_index + 1)); if (include_file_index) { result_size += mpeg_metadata->streams.nelts * (sizeof("-f") - 1 + vod_get_int_print_len(mpeg_metadata->first_stream->file_info.file_index + 1)); } p = vod_alloc(request_context->pool, result_size + 1); if (p == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_required_tracks_string: vod_alloc failed"); return VOD_ALLOC_FAILED; } required_tracks->data = p; if (include_file_index) { printed_file_indexes = 0; for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++) { file_index = cur_stream->file_info.file_index; if ((printed_file_indexes & (1 << file_index)) != 0) { continue; } p = vod_sprintf(p, "-f%uD", file_index + 1); printed_file_indexes |= (1 << file_index); } } for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++) { *p++ = '-'; switch (cur_stream->media_info.media_type) { case MEDIA_TYPE_VIDEO: *p++ = 'v'; break; case MEDIA_TYPE_AUDIO: *p++ = 'a'; break; default: continue; } p = vod_sprintf(p, "%uD", cur_stream->track_index + 1); } required_tracks->len = p - required_tracks->data; if (required_tracks->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_required_tracks_string: result length %uz exceeded allocated length %uz", required_tracks->len, result_size); return VOD_UNEXPECTED; } 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->frames_file_index = cur_stream->frames_file_index; 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; }
vod_status_t m3u8_builder_build_index_playlist( request_context_t* request_context, m3u8_config_t* conf, vod_str_t* base_url, vod_str_t* segments_base_url, bool_t include_file_index, hls_encryption_params_t* encryption_params, segmenter_conf_t* segmenter_conf, mpeg_metadata_t* mpeg_metadata, vod_str_t* result) { segment_durations_t segment_durations; segment_duration_item_t* cur_item; segment_duration_item_t* last_item; vod_str_t extinf; uint32_t segment_index; uint32_t last_segment_index; vod_str_t required_tracks; uint32_t scale; size_t segment_length; size_t result_size; vod_status_t rc; u_char* p; // build the required tracks string rc = m3u8_builder_build_required_tracks_string( request_context, include_file_index, mpeg_metadata, &required_tracks); if (rc != VOD_OK) { return rc; } // get the segment durations rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, mpeg_metadata->longest_stream, MEDIA_TYPE_COUNT, &segment_durations); if (rc != VOD_OK) { return rc; } // get the required buffer length segment_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(mpeg_metadata->duration_millis, 1000)) + segments_base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(segment_durations.segment_count) + required_tracks.len + sizeof(".ts\n") - 1; result_size = sizeof(M3U8_HEADER_PART1) + VOD_INT64_LEN + sizeof(M3U8_HEADER_PART2) + VOD_INT64_LEN + segment_length * segment_durations.segment_count + sizeof(m3u8_footer); if (encryption_params->type != HLS_ENC_NONE) { result_size += sizeof(encryption_key_tag_part1) - 1 + sizeof(encryption_type_sample_aes) - 1 + sizeof(encryption_key_tag_part2) - 1 + base_url->len + conf->encryption_key_file_name.len + sizeof("-f") - 1 + VOD_INT32_LEN + sizeof(encryption_key_tag_part3) - 1; } // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_index_playlist: vod_alloc failed"); return VOD_ALLOC_FAILED; } // write the header p = vod_sprintf( result->data, M3U8_HEADER_PART1, (segmenter_conf->max_segment_duration + 500) / 1000); if (encryption_params->type != HLS_ENC_NONE) { p = vod_copy(p, encryption_key_tag_part1, sizeof(encryption_key_tag_part1) - 1); switch (encryption_params->type) { case HLS_ENC_SAMPLE_AES: p = vod_copy(p, encryption_type_sample_aes, sizeof(encryption_type_sample_aes) - 1); break; default: // HLS_ENC_AES_128 p = vod_copy(p, encryption_type_aes_128, sizeof(encryption_type_aes_128) - 1); break; } p = vod_copy(p, encryption_key_tag_part2, sizeof(encryption_key_tag_part2) - 1); p = vod_copy(p, base_url->data, base_url->len); p = vod_copy(p, conf->encryption_key_file_name.data, conf->encryption_key_file_name.len); if (include_file_index) { p = vod_sprintf(p, "-f%uD", mpeg_metadata->first_stream->file_info.file_index + 1); } p = vod_copy(p, encryption_key_tag_part3, sizeof(encryption_key_tag_part3) - 1); } p = vod_sprintf( p, M3U8_HEADER_PART2, conf->m3u8_version); // write the segments scale = conf->m3u8_version >= 3 ? 1000 : 1; last_item = segment_durations.items + segment_durations.item_count; for (cur_item = segment_durations.items; cur_item < last_item; cur_item++) { segment_index = cur_item->segment_index; last_segment_index = segment_index + cur_item->repeat_count; // write the first segment extinf.data = p; p = m3u8_builder_append_extinf_tag(p, rescale_time(cur_item->duration, segment_durations.timescale, scale), scale); extinf.len = p - extinf.data; p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &required_tracks); segment_index++; // write any additional segments for (; segment_index < last_segment_index; segment_index++) { p = vod_copy(p, extinf.data, extinf.len); p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &required_tracks); } } // write the footer p = vod_copy(p, m3u8_footer, sizeof(m3u8_footer) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_index_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t m3u8_builder_build_master_playlist( request_context_t* request_context, m3u8_config_t* conf, vod_str_t* base_url, bool_t include_file_index, mpeg_metadata_t* mpeg_metadata, vod_str_t* result) { WALK_STREAMS_BY_FILES_VARS(cur_file_streams); mpeg_stream_metadata_t* stream; media_info_t* video; media_info_t* audio = NULL; uint32_t bitrate; u_char* p; size_t max_video_stream_inf; size_t max_audio_stream_inf; size_t result_size; // calculate the result size max_video_stream_inf = sizeof(m3u8_stream_inf_video) - 1 + 3 * VOD_INT32_LEN + MAX_CODEC_NAME_SIZE + MAX_CODEC_NAME_SIZE + 1 + sizeof(m3u8_stream_inf_suffix) - 1; max_audio_stream_inf = sizeof(m3u8_stream_inf_audio) + VOD_INT32_LEN + MAX_CODEC_NAME_SIZE + sizeof(m3u8_stream_inf_suffix) - 1; result_size = sizeof(m3u8_header) + mpeg_metadata->stream_count[MEDIA_TYPE_VIDEO] * max_video_stream_inf + mpeg_metadata->stream_count[MEDIA_TYPE_AUDIO] * max_audio_stream_inf; WALK_STREAMS_BY_FILES_START(cur_file_streams, mpeg_metadata) stream = (cur_file_streams[MEDIA_TYPE_VIDEO] != NULL ? cur_file_streams[MEDIA_TYPE_VIDEO] : cur_file_streams[MEDIA_TYPE_AUDIO]); if (base_url->len != 0) { result_size += base_url->len; result_size += stream->file_info.uri.len + 1; } result_size += conf->index_file_name_prefix.len; result_size += sizeof("-f-v-a") - 1 + VOD_INT32_LEN * 3; result_size += sizeof(m3u8_url_suffix) - 1; WALK_STREAMS_BY_FILES_END(cur_file_streams, mpeg_metadata) // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_master_playlist: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } // write the header p = vod_copy(result->data, m3u8_header, sizeof(m3u8_header) - 1); // write the streams WALK_STREAMS_BY_FILES_START(cur_file_streams, mpeg_metadata) // write the stream information if (cur_file_streams[MEDIA_TYPE_VIDEO] != NULL) { stream = cur_file_streams[MEDIA_TYPE_VIDEO]; video = &stream->media_info; bitrate = video->bitrate; if (cur_file_streams[MEDIA_TYPE_AUDIO] != NULL) { audio = &cur_file_streams[MEDIA_TYPE_AUDIO]->media_info; bitrate += audio->bitrate; } p = vod_sprintf(p, m3u8_stream_inf_video, bitrate, (uint32_t)video->u.video.width, (uint32_t)video->u.video.height, &video->codec_name); if (cur_file_streams[MEDIA_TYPE_AUDIO] != NULL) { *p++ = ','; p = vod_copy(p, audio->codec_name.data, audio->codec_name.len); } } else { stream = cur_file_streams[MEDIA_TYPE_AUDIO]; audio = &stream->media_info; p = vod_sprintf(p, m3u8_stream_inf_audio, audio->bitrate, &audio->codec_name); } p = vod_copy(p, m3u8_stream_inf_suffix, sizeof(m3u8_stream_inf_suffix) - 1); // write the stream url if (base_url->len != 0) { // absolute url only p = vod_copy(p, base_url->data, base_url->len); p = vod_copy(p, stream->file_info.uri.data, stream->file_info.uri.len); *p++ = '/'; } p = vod_copy(p, conf->index_file_name_prefix.data, conf->index_file_name_prefix.len); if (base_url->len == 0 && include_file_index) { p = vod_sprintf(p, "-f%uD", stream->file_info.file_index + 1); } if (cur_file_streams[MEDIA_TYPE_VIDEO] != NULL) { p = vod_sprintf(p, "-v%uD", cur_file_streams[MEDIA_TYPE_VIDEO]->track_index + 1); } if (cur_file_streams[MEDIA_TYPE_AUDIO] != NULL) { p = vod_sprintf(p, "-a%uD", cur_file_streams[MEDIA_TYPE_AUDIO]->track_index + 1); } p = vod_copy(p, m3u8_url_suffix, sizeof(m3u8_url_suffix) - 1); WALK_STREAMS_BY_FILES_END(cur_file_streams, mpeg_metadata) result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_master_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t audio_encoder_init( request_context_t* request_context, audio_encoder_params_t* params, vod_array_t* frames_array, void** result) { audio_encoder_state_t* state; AVCodecContext* encoder; int avrc; if (!initialized) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "audio_encoder_init: module failed to initialize successfully"); return VOD_UNEXPECTED; } state = vod_alloc(request_context->pool, sizeof(*state)); if (state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "audio_encoder_init: vod_alloc failed"); return VOD_ALLOC_FAILED; } // init the encoder encoder = avcodec_alloc_context3(encoder_codec); if (encoder == NULL) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "audio_encoder_init: avcodec_alloc_context3 failed"); return VOD_ALLOC_FAILED; } state->encoder = encoder; encoder->sample_fmt = AUDIO_ENCODER_INPUT_SAMPLE_FORMAT; encoder->time_base.num = 1; encoder->time_base.den = params->timescale; encoder->sample_rate = params->sample_rate; encoder->channel_layout = params->channel_layout; encoder->channels = params->channels; encoder->bit_rate = params->bitrate; encoder->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // make the codec generate the extra data avrc = avcodec_open2(encoder, encoder_codec, NULL); if (avrc < 0) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "audio_encoder_init: avcodec_open2 failed %d", avrc); audio_encoder_free(state); return VOD_UNEXPECTED; } state->request_context = request_context; state->frames_array = frames_array; *result = state; return VOD_OK; }
vod_status_t m3u8_builder_build_iframe_playlist( request_context_t* request_context, m3u8_config_t* conf, hls_muxer_conf_t* muxer_conf, vod_str_t* base_url, bool_t include_file_index, segmenter_conf_t* segmenter_conf, mpeg_metadata_t* mpeg_metadata, vod_str_t* result) { hls_encryption_params_t encryption_params; write_segment_context_t ctx; size_t iframe_length; size_t result_size; hls_muxer_state_t muxer_state; bool_t simulation_supported; vod_status_t rc; uint32_t segment_count; // iframes list is not supported with encryption, since: // 1. AES-128 - the IV of each key frame is not known in advance // 2. SAMPLE-AES - the layout of the TS files is not known in advance due to emulation prevention encryption_params.type = HLS_ENC_NONE; encryption_params.key = NULL; encryption_params.iv = NULL; // initialize the muxer rc = hls_muxer_init( &muxer_state, request_context, muxer_conf, &encryption_params, 0, mpeg_metadata, NULL, NULL, NULL, &simulation_supported); if (rc != VOD_OK) { return rc; } if (!simulation_supported) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_iframe_playlist: simulation not supported for this file, cant create iframe playlist"); return VOD_BAD_REQUEST; } // build the required tracks string rc = m3u8_builder_build_required_tracks_string( request_context, include_file_index, mpeg_metadata, &ctx.required_tracks); if (rc != VOD_OK) { return rc; } // calculate the required buffer length segment_count = segmenter_conf->get_segment_count(segmenter_conf, mpeg_metadata->duration_millis); if (segment_count == INVALID_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_iframe_playlist: segment count is invalid"); return VOD_BAD_DATA; } iframe_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(mpeg_metadata->duration_millis, 1000)) + sizeof(byte_range_tag_format) + VOD_INT32_LEN + vod_get_int_print_len(MAX_FRAME_SIZE) - (sizeof("%uD%uD") - 1) + base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(segment_count) + ctx.required_tracks.len + sizeof(".ts\n") - 1; result_size = conf->iframes_m3u8_header_len + iframe_length * mpeg_metadata->video_key_frame_count + sizeof(m3u8_footer); // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_iframe_playlist: vod_alloc failed"); return VOD_ALLOC_FAILED; } // fill out the buffer ctx.p = vod_copy(result->data, conf->iframes_m3u8_header, conf->iframes_m3u8_header_len); if (mpeg_metadata->video_key_frame_count > 0) { ctx.base_url = base_url; ctx.segment_file_name_prefix = &conf->segment_file_name_prefix; rc = hls_muxer_simulate_get_iframes(&muxer_state, segmenter_conf, mpeg_metadata, m3u8_builder_append_iframe_string, &ctx); if (rc != VOD_OK) { return rc; } } ctx.p = vod_copy(ctx.p, m3u8_footer, sizeof(m3u8_footer) - 1); result->len = ctx.p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_iframe_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t m3u8_builder_build_master_playlist( request_context_t* request_context, m3u8_config_t* conf, vod_str_t* base_url, media_set_t* media_set, vod_str_t* result) { media_sequence_t* cur_sequence; media_info_t* video; media_info_t* audio = NULL; uint32_t sequence_index; uint32_t bitrate; u_char* p; size_t max_video_stream_inf; size_t result_size; uint32_t main_media_type; media_track_t* cur_track; media_track_t* audio_track; // calculate the result size max_video_stream_inf = sizeof(m3u8_stream_inf_video) - 1 + 3 * VOD_INT32_LEN + MAX_CODEC_NAME_SIZE + MAX_CODEC_NAME_SIZE + 1 + sizeof(m3u8_stream_inf_suffix) - 1; result_size = sizeof(m3u8_header); for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { main_media_type = cur_sequence->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO; for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < cur_sequence->filtered_clips[0].last_track; cur_track++) { if (cur_track->media_info.media_type != main_media_type) { continue; } if (base_url->len != 0) { result_size += base_url->len + 1; if (cur_track->file_info.uri.len > 0) { result_size += cur_track->file_info.uri.len; } else { result_size += media_set->uri.len; } } result_size += max_video_stream_inf; // using only video since it's larger than audio result_size += conf->index_file_name_prefix.len; result_size += sizeof("-f-v-a") - 1 + VOD_INT32_LEN * 3; result_size += sizeof(m3u8_url_suffix) - 1; } } // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_master_playlist: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } // write the header p = vod_copy(result->data, m3u8_header, sizeof(m3u8_header) - 1); // write the streams for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { audio_track = cur_sequence->filtered_clips[0].longest_track[MEDIA_TYPE_AUDIO]; main_media_type = cur_sequence->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO; for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < cur_sequence->filtered_clips[0].last_track; cur_track++) { if (cur_track->media_info.media_type != main_media_type) { continue; } // write the track information if (main_media_type == MEDIA_TYPE_VIDEO) { video = &cur_track->media_info; bitrate = video->bitrate; if (audio_track != NULL) { audio = &audio_track->media_info; bitrate += audio->bitrate; } p = vod_sprintf(p, m3u8_stream_inf_video, bitrate, (uint32_t)video->u.video.width, (uint32_t)video->u.video.height, &video->codec_name); if (audio_track != NULL) { *p++ = ','; p = vod_copy(p, audio->codec_name.data, audio->codec_name.len); } } else { audio = &cur_track->media_info; p = vod_sprintf(p, m3u8_stream_inf_audio, audio->bitrate, &audio->codec_name); } p = vod_copy(p, m3u8_stream_inf_suffix, sizeof(m3u8_stream_inf_suffix) - 1); // write the track url sequence_index = cur_sequence->index; if (base_url->len != 0) { // absolute url only p = vod_copy(p, base_url->data, base_url->len); if (cur_track->file_info.uri.len != 0) { p = vod_copy(p, cur_track->file_info.uri.data, cur_track->file_info.uri.len); sequence_index = INVALID_SEQUENCE_INDEX; // no need to pass the sequence index since we have a direct uri } else { p = vod_copy(p, media_set->uri.data, media_set->uri.len); } *p++ = '/'; } p = vod_copy(p, conf->index_file_name_prefix.data, conf->index_file_name_prefix.len); if (media_set->has_multi_sequences && sequence_index != INVALID_SEQUENCE_INDEX) { p = vod_sprintf(p, "-f%uD", cur_sequence->index + 1); } if (main_media_type == MEDIA_TYPE_VIDEO) { p = vod_sprintf(p, "-v%uD", cur_track->index + 1); } if (audio_track != NULL) { p = vod_sprintf(p, "-a%uD", audio_track->index + 1); } p = vod_copy(p, m3u8_url_suffix, sizeof(m3u8_url_suffix) - 1); } } result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_master_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t m3u8_builder_build_index_playlist( request_context_t* request_context, m3u8_config_t* conf, vod_str_t* base_url, vod_str_t* segments_base_url, request_params_t* request_params, hls_encryption_params_t* encryption_params, media_set_t* media_set, vod_str_t* result) { segment_durations_t segment_durations; segment_duration_item_t* cur_item; segment_duration_item_t* last_item; segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; uint64_t duration_millis; uint32_t sequence_index; vod_str_t extinf; uint32_t segment_index; uint32_t last_segment_index; vod_str_t tracks_spec; uint32_t scale; size_t segment_length; size_t result_size; vod_status_t rc; u_char* p; sequence_index = media_set->has_multi_sequences ? media_set->sequences[0].index : INVALID_SEQUENCE_INDEX; // build the required tracks string rc = manifest_utils_build_request_params_string( request_context, media_set->track_count, INVALID_SEGMENT_INDEX, sequence_index, request_params->tracks_mask, &tracks_spec); if (rc != VOD_OK) { return rc; } // get the segment durations rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, NULL, MEDIA_TYPE_NONE, &segment_durations); if (rc != VOD_OK) { return rc; } // get the required buffer length duration_millis = segment_durations.end_time - segment_durations.start_time; last_segment_index = media_set->initial_segment_index + segment_durations.segment_count; segment_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(duration_millis, 1000)) + segments_base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(last_segment_index) + tracks_spec.len + sizeof(".ts\n") - 1; result_size = sizeof(M3U8_HEADER_PART1) + VOD_INT64_LEN + sizeof(M3U8_HEADER_VOD) + sizeof(M3U8_HEADER_PART2) + VOD_INT64_LEN + VOD_INT32_LEN + segment_length * segment_durations.segment_count + segment_durations.discontinuities * (sizeof(m3u8_discontinuity) - 1) + sizeof(m3u8_footer); if (encryption_params->type != HLS_ENC_NONE) { result_size += sizeof(encryption_key_tag_method) - 1 + sizeof(encryption_type_sample_aes) - 1 + sizeof(encryption_key_tag_uri) - 1 + 2; // '"', '\n' if (encryption_params->key_uri.len != 0) { result_size += encryption_params->key_uri.len; } else { result_size += base_url->len + conf->encryption_key_file_name.len + sizeof("-f") - 1 + VOD_INT32_LEN + sizeof(encryption_key_extension) - 1; } if (conf->encryption_key_format.len != 0) { result_size += sizeof(encryption_key_tag_key_format) + // '"' conf->encryption_key_format.len; } if (conf->encryption_key_format_versions.len != 0) { result_size += sizeof(encryption_key_tag_key_format_versions) + // '"' conf->encryption_key_format_versions.len; } } // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_index_playlist: vod_alloc failed"); return VOD_ALLOC_FAILED; } // write the header p = vod_sprintf( result->data, M3U8_HEADER_PART1, (segmenter_conf->max_segment_duration + 500) / 1000); if (media_set->type == MEDIA_SET_VOD) { p = vod_copy(p, M3U8_HEADER_VOD, sizeof(M3U8_HEADER_VOD) - 1); } if (encryption_params->type != HLS_ENC_NONE) { p = vod_copy(p, encryption_key_tag_method, sizeof(encryption_key_tag_method) - 1); switch (encryption_params->type) { case HLS_ENC_SAMPLE_AES: p = vod_copy(p, encryption_type_sample_aes, sizeof(encryption_type_sample_aes) - 1); break; default: // HLS_ENC_AES_128 p = vod_copy(p, encryption_type_aes_128, sizeof(encryption_type_aes_128) - 1); break; } // uri p = vod_copy(p, encryption_key_tag_uri, sizeof(encryption_key_tag_uri) - 1); if (encryption_params->key_uri.len != 0) { p = vod_copy(p, encryption_params->key_uri.data, encryption_params->key_uri.len); } else { p = vod_copy(p, base_url->data, base_url->len); p = vod_copy(p, conf->encryption_key_file_name.data, conf->encryption_key_file_name.len); if (sequence_index != INVALID_SEQUENCE_INDEX) { p = vod_sprintf(p, "-f%uD", sequence_index + 1); } p = vod_copy(p, encryption_key_extension, sizeof(encryption_key_extension) - 1); } *p++ = '"'; // keyformat if (conf->encryption_key_format.len != 0) { p = vod_copy(p, encryption_key_tag_key_format, sizeof(encryption_key_tag_key_format) - 1); p = vod_copy(p, conf->encryption_key_format.data, conf->encryption_key_format.len); *p++ = '"'; } // keyformatversions if (conf->encryption_key_format_versions.len != 0) { p = vod_copy(p, encryption_key_tag_key_format_versions, sizeof(encryption_key_tag_key_format_versions) - 1); p = vod_copy(p, conf->encryption_key_format_versions.data, conf->encryption_key_format_versions.len); *p++ = '"'; } *p++ = '\n'; } p = vod_sprintf( p, M3U8_HEADER_PART2, conf->m3u8_version, media_set->initial_segment_index + 1); // write the segments scale = conf->m3u8_version >= 3 ? 1000 : 1; last_item = segment_durations.items + segment_durations.item_count; for (cur_item = segment_durations.items; cur_item < last_item; cur_item++) { segment_index = cur_item->segment_index; last_segment_index = segment_index + cur_item->repeat_count; if (cur_item->discontinuity) { p = vod_copy(p, m3u8_discontinuity, sizeof(m3u8_discontinuity) - 1); } // write the first segment extinf.data = p; p = m3u8_builder_append_extinf_tag(p, rescale_time(cur_item->duration, segment_durations.timescale, scale), scale); extinf.len = p - extinf.data; p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &tracks_spec); segment_index++; // write any additional segments for (; segment_index < last_segment_index; segment_index++) { p = vod_copy(p, extinf.data, extinf.len); p = m3u8_builder_append_segment_name(p, segments_base_url, &conf->segment_file_name_prefix, segment_index, &tracks_spec); } } // write the footer if (media_set->presentation_end) { p = vod_copy(p, m3u8_footer, sizeof(m3u8_footer) - 1); } result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_index_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
vod_status_t m3u8_builder_build_iframe_playlist( request_context_t* request_context, m3u8_config_t* conf, hls_muxer_conf_t* muxer_conf, vod_str_t* base_url, request_params_t* request_params, media_set_t* media_set, vod_str_t* result) { hls_encryption_params_t encryption_params; write_segment_context_t ctx; segment_durations_t segment_durations; segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; size_t iframe_length; size_t result_size; uint64_t duration_millis; uint32_t sequence_index; vod_status_t rc; sequence_index = media_set->has_multi_sequences ? media_set->sequences[0].index : INVALID_SEQUENCE_INDEX; // iframes list is not supported with encryption, since: // 1. AES-128 - the IV of each key frame is not known in advance // 2. SAMPLE-AES - the layout of the TS files is not known in advance due to emulation prevention encryption_params.type = HLS_ENC_NONE; encryption_params.key = NULL; encryption_params.iv = NULL; // build the required tracks string rc = manifest_utils_build_request_params_string( request_context, media_set->track_count, INVALID_SEGMENT_INDEX, sequence_index, request_params->tracks_mask, &ctx.tracks_spec); if (rc != VOD_OK) { return rc; } // get segment durations if (segmenter_conf->align_to_key_frames) { rc = segmenter_get_segment_durations_accurate( request_context, segmenter_conf, media_set, NULL, MEDIA_TYPE_NONE, &segment_durations); } else { rc = segmenter_get_segment_durations_estimate( request_context, segmenter_conf, media_set, NULL, MEDIA_TYPE_NONE, &segment_durations); } if (rc != VOD_OK) { return rc; } duration_millis = segment_durations.end_time - segment_durations.start_time; iframe_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(duration_millis, 1000)) + sizeof(byte_range_tag_format) + VOD_INT32_LEN + vod_get_int_print_len(MAX_FRAME_SIZE) - (sizeof("%uD%uD") - 1) + base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(segment_durations.segment_count) + ctx.tracks_spec.len + sizeof(".ts\n") - 1; result_size = conf->iframes_m3u8_header_len + iframe_length * media_set->sequences[0].video_key_frame_count + sizeof(m3u8_footer); // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_iframe_playlist: vod_alloc failed"); return VOD_ALLOC_FAILED; } // fill out the buffer ctx.p = vod_copy(result->data, conf->iframes_m3u8_header, conf->iframes_m3u8_header_len); if (media_set->sequences[0].video_key_frame_count > 0) { ctx.base_url = base_url; ctx.segment_file_name_prefix = &conf->segment_file_name_prefix; rc = hls_muxer_simulate_get_iframes( request_context, &segment_durations, muxer_conf, &encryption_params, media_set, m3u8_builder_append_iframe_string, &ctx); if (rc != VOD_OK) { return rc; } } ctx.p = vod_copy(ctx.p, m3u8_footer, sizeof(m3u8_footer) - 1); result->len = ctx.p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_iframe_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
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 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; }