vod_status_t segmenter_get_segment_durations_accurate( request_context_t* request_context, segmenter_conf_t* conf, media_set_t* media_set, media_sequence_t* sequence, uint32_t media_type, segment_durations_t* result) { segmenter_boundary_iterator_context_t boundary_iterator; media_track_t* cur_track; media_track_t* last_track; media_track_t* main_track = NULL; media_track_t* longest_track = NULL; segment_duration_item_t* cur_item; media_sequence_t* sequences_end; media_sequence_t* cur_sequence; input_frame_t* last_frame; input_frame_t* cur_frame; uint64_t total_duration; uint32_t segment_index = 0; uint64_t accum_duration = 0; uint64_t segment_start = 0; uint64_t segment_limit_millis; uint64_t segment_limit; uint64_t cur_duration; uint32_t duration_millis; bool_t align_to_key_frames; SEGMENT_CHOOSE_HEADER(conf); if (media_set->durations != NULL) { // in case of a playlist fall back to estimate return segmenter_get_segment_durations_estimate( request_context, conf, media_set, sequence, media_type, result); } // get the maximum duration and main track (=first video track if exists, or first audio track otherwise) if (sequence != NULL) { cur_sequence = sequence; sequences_end = sequence + 1; } else { cur_sequence = media_set->sequences; sequences_end = media_set->sequences_end; } duration_millis = 0; for (; cur_sequence < sequences_end; cur_sequence++) { last_track = cur_sequence->filtered_clips[0].last_track; for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < last_track; cur_track++) { if (media_type != MEDIA_TYPE_NONE && cur_track->media_info.media_type != media_type) { continue; } if (main_track == NULL || (cur_track->media_info.media_type < main_track->media_info.media_type)) { main_track = cur_track; } if (cur_track->media_info.duration_millis > duration_millis) { longest_track = cur_track; duration_millis = cur_track->media_info.duration_millis; } } } if (main_track == NULL) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_accurate: didnt get any tracks"); return VOD_UNEXPECTED; } // if the main track is not audio/video, or main track is audio and requires filtering, fall back to estimate switch (main_track->media_info.media_type) { case MEDIA_TYPE_VIDEO: break; case MEDIA_TYPE_AUDIO: if (!media_set->audio_filtering_needed) { break; } default: return segmenter_get_segment_durations_estimate( request_context, conf, media_set, sequence, media_type, result); } // get the segment count result->segment_count = conf->get_segment_count(conf, duration_millis); if (result->segment_count > MAX_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_accurate: segment count %uD is invalid", result->segment_count); return VOD_BAD_DATA; } // allocate the result buffer result->items = vod_alloc(request_context->pool, sizeof(*result->items) * result->segment_count); if (result->items == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "segmenter_get_segment_durations_accurate: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->timescale = main_track->media_info.timescale; result->discontinuities = 0; // Note: assuming a single frame list part cur_item = result->items - 1; last_frame = main_track->frames.last_frame; cur_frame = main_track->frames.first_frame; align_to_key_frames = conf->align_to_key_frames && main_track->media_info.media_type == MEDIA_TYPE_VIDEO; // bootstrap segments if (conf->bootstrap_segments_count > 0) { segment_limit = rescale_time(conf->bootstrap_segments_end[0], 1000, result->timescale); for (; cur_frame < last_frame; cur_frame++) { while (accum_duration >= segment_limit && segment_index + 1 < result->segment_count && (!align_to_key_frames || cur_frame->key_frame)) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_start = accum_duration; segment_index++; if (segment_index >= conf->bootstrap_segments_count) { goto post_bootstrap; } segment_limit = rescale_time(conf->bootstrap_segments_end[segment_index], 1000, result->timescale); } accum_duration += cur_frame->duration; } } post_bootstrap: // remaining segments segment_limit_millis = conf->bootstrap_segments_total_duration + conf->segment_duration; segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); for (; cur_frame < last_frame; cur_frame++) { while (accum_duration >= segment_limit && segment_index + 1 < result->segment_count && (!align_to_key_frames || cur_frame->key_frame)) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; segment_limit_millis += conf->segment_duration; segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); } accum_duration += cur_frame->duration; } // in case the main video track is shorter than the audio track, add the estimated durations of the remaining audio-only segments if (main_track->media_info.duration_millis < duration_millis && !align_to_key_frames) { segmenter_boundary_iterator_init(&boundary_iterator, conf, result->segment_count); segmenter_boundary_iterator_skip(&boundary_iterator, segment_index); total_duration = rescale_time(longest_track->media_info.duration, longest_track->media_info.timescale, result->timescale); while (accum_duration < total_duration && segment_index + 1 < result->segment_count) { segment_limit_millis = segmenter_boundary_iterator_next(&boundary_iterator); segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); segment_limit = vod_min(segment_limit, total_duration); accum_duration = segment_limit; cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; } accum_duration = total_duration; } // add the last segment / empty segments after the last keyframe (in case align to key frames is on) while (segment_index < result->segment_count) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; } result->item_count = cur_item + 1 - result->items; // remove any empty segments from the end if (result->item_count > 0 && cur_item->duration == 0) { result->item_count--; result->segment_count -= cur_item->repeat_count; } result->start_time = 0; result->end_time = duration_millis; 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 = m3u8_builder_build_required_tracks_string( request_context, media_set, sequence_index, request_params, &ctx.tracks_spec); if (rc != VOD_OK) { return rc; } // get segment durations if (segmenter_conf->align_to_key_frames) { rc = segmenter_get_segment_durations_accurate( request_context, segmenter_conf, media_set, NULL, MEDIA_TYPE_NONE, &segment_durations); } else { rc = segmenter_get_segment_durations_estimate( request_context, segmenter_conf, media_set, NULL, MEDIA_TYPE_NONE, &segment_durations); } if (rc != VOD_OK) { return rc; } duration_millis = segment_durations.end_time - segment_durations.start_time; iframe_length = sizeof("#EXTINF:.000,\n") - 1 + vod_get_int_print_len(vod_div_ceil(duration_millis, 1000)) + sizeof(byte_range_tag_format) + VOD_INT32_LEN + vod_get_int_print_len(MAX_FRAME_SIZE) - (sizeof("%uD%uD") - 1) + base_url->len + conf->segment_file_name_prefix.len + 1 + vod_get_int_print_len(segment_durations.segment_count) + ctx.tracks_spec.len + sizeof(".ts\n") - 1; result_size = conf->iframes_m3u8_header_len + iframe_length * media_set->sequences[0].video_key_frame_count + sizeof(m3u8_footer); // allocate the buffer result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "m3u8_builder_build_iframe_playlist: vod_alloc failed"); return VOD_ALLOC_FAILED; } // fill out the buffer ctx.p = vod_copy(result->data, conf->iframes_m3u8_header, conf->iframes_m3u8_header_len); if (media_set->sequences[0].video_key_frame_count > 0) { ctx.base_url = base_url; ctx.segment_file_name_prefix = &conf->segment_file_name_prefix; rc = hls_muxer_simulate_get_iframes( request_context, &segment_durations, muxer_conf, &encryption_params, media_set, m3u8_builder_append_iframe_string, &ctx); if (rc != VOD_OK) { return rc; } } ctx.p = vod_copy(ctx.p, m3u8_footer, sizeof(m3u8_footer) - 1); result->len = ctx.p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "m3u8_builder_build_iframe_playlist: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }