diff --git a/ngx_http_vod_hls.c b/ngx_http_vod_hls.c index 7d48dca1..db484921 100644 --- a/ngx_http_vod_hls.c +++ b/ngx_http_vod_hls.c @@ -97,7 +97,7 @@ ngx_http_vod_hls_init_encryption_iv(u_char* iv, uint32_t segment_index) static ngx_int_t ngx_http_vod_hls_get_iv_seed( - ngx_http_vod_submodule_context_t* submodule_context, + ngx_http_vod_submodule_context_t* submodule_context, media_sequence_t* sequence, ngx_str_t* result) { @@ -258,7 +258,45 @@ ngx_http_vod_hls_handle_master_playlist( { ngx_http_vod_loc_conf_t* conf = submodule_context->conf; ngx_str_t base_url = ngx_null_string; - vod_status_t rc; + hls_encryption_params_t encryption_params; + vod_status_t rc; + + ngx_uint_t container_format; + + + +#if (NGX_HAVE_OPENSSL_EVP) + container_format = ngx_http_vod_hls_get_container_format( + &conf->hls, + &submodule_context->media_set); + rc = ngx_http_vod_hls_init_encryption_params(&encryption_params, submodule_context, container_format); + if (rc != NGX_OK) + { + return rc; + } + + if (encryption_params.type != HLS_ENC_NONE) + { + if (conf->hls.encryption_key_uri != NULL) + { + if (ngx_http_complex_value( + submodule_context->r, + conf->hls.encryption_key_uri, + &encryption_params.key_uri) != NGX_OK) + { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0, + "ngx_http_vod_hls_handle_index_playlist: ngx_http_complex_value failed"); + return NGX_ERROR; + } + } + else + { + encryption_params.key_uri.len = 0; + } + } +#else + encryption_params.type = HLS_ENC_NONE; +#endif // NGX_HAVE_OPENSSL_EVP if (conf->hls.absolute_master_urls) { @@ -275,6 +313,8 @@ ngx_http_vod_hls_handle_master_playlist( conf->hls.encryption_method, &base_url, &submodule_context->media_set, + &conf->hls.mpegts_muxer_config, + &encryption_params, response); if (rc != VOD_OK) { @@ -285,11 +325,11 @@ ngx_http_vod_hls_handle_master_playlist( content_type->data = m3u8_content_type; content_type->len = sizeof(m3u8_content_type) - 1; - + return NGX_OK; } -static ngx_int_t +static ngx_int_t ngx_http_vod_hls_handle_index_playlist( ngx_http_vod_submodule_context_t* submodule_context, ngx_str_t* response, @@ -329,7 +369,7 @@ ngx_http_vod_hls_handle_index_playlist( } container_format = ngx_http_vod_hls_get_container_format( - &conf->hls, + &conf->hls, &submodule_context->media_set); #if (NGX_HAVE_OPENSSL_EVP) @@ -380,7 +420,7 @@ ngx_http_vod_hls_handle_index_playlist( content_type->data = m3u8_content_type; content_type->len = sizeof(m3u8_content_type) - 1; - + return NGX_OK; } @@ -393,7 +433,7 @@ ngx_http_vod_hls_handle_iframe_playlist( ngx_http_vod_loc_conf_t* conf = submodule_context->conf; ngx_str_t base_url = ngx_null_string; vod_status_t rc; - + if (conf->hls.encryption_method != HLS_ENC_NONE) { ngx_log_error(NGX_LOG_ERR, submodule_context->request_context.log, 0, @@ -442,7 +482,7 @@ ngx_http_vod_hls_handle_iframe_playlist( content_type->data = m3u8_content_type; content_type->len = sizeof(m3u8_content_type) - 1; - + return NGX_OK; } @@ -521,7 +561,7 @@ ngx_http_vod_hls_init_ts_frame_processor( segment_writer->write_tail, segment_writer->context, reuse_output_buffers, - response_size, + response_size, output_buffer, &state); if (rc != VOD_OK) @@ -531,7 +571,7 @@ ngx_http_vod_hls_init_ts_frame_processor( return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc); } - if (encryption_params.type == HLS_ENC_AES_128 && + if (encryption_params.type == HLS_ENC_AES_128 && *response_size != 0) { *response_size = aes_round_up_to_block(*response_size); @@ -853,7 +893,7 @@ ngx_http_vod_hls_handle_vtt_segment( ngx_str_t* content_type) { vod_status_t rc; - + rc = webvtt_builder_build( &submodule_context->request_context, &submodule_context->media_set, @@ -872,14 +912,16 @@ ngx_http_vod_hls_handle_vtt_segment( return NGX_OK; } + static const ngx_http_vod_request_t hls_master_request = { - 0, - PARSE_FLAG_DURATION_LIMITS_AND_TOTAL_SIZE | PARSE_FLAG_KEY_FRAME_BITRATE | PARSE_FLAG_CODEC_NAME | PARSE_FLAG_PARSED_EXTRA_DATA_SIZE | PARSE_FLAG_CODEC_TRANSFER_CHAR, - REQUEST_CLASS_OTHER, - SUPPORTED_CODECS | VOD_CODEC_FLAG(WEBVTT), - HLS_TIMESCALE, - ngx_http_vod_hls_handle_master_playlist, - NULL, + REQUEST_FLAG_PARSE_ALL_CLIPS, + PARSE_FLAG_DURATION_LIMITS_AND_TOTAL_SIZE | PARSE_FLAG_KEY_FRAME_BITRATE | PARSE_FLAG_CODEC_NAME | + PARSE_FLAG_PARSED_EXTRA_DATA_SIZE | PARSE_FLAG_CODEC_TRANSFER_CHAR | PARSE_FLAG_FRAMES_ALL_EXCEPT_OFFSETS, + REQUEST_CLASS_OTHER, + SUPPORTED_CODECS | VOD_CODEC_FLAG(WEBVTT), + HLS_TIMESCALE, + ngx_http_vod_hls_handle_master_playlist, + NULL, }; static const ngx_http_vod_request_t hls_index_request = { @@ -1022,12 +1064,12 @@ ngx_http_vod_hls_merge_loc_conf( ngx_conf_merge_value(conf->mpegts_muxer_config.align_frames, prev->mpegts_muxer_config.align_frames, 1); ngx_conf_merge_value(conf->mpegts_muxer_config.output_id3_timestamps, prev->mpegts_muxer_config.output_id3_timestamps, 0); ngx_conf_merge_value(conf->mpegts_muxer_config.align_pts, prev->mpegts_muxer_config.align_pts, 0); - + ngx_conf_merge_uint_value(conf->encryption_method, prev->encryption_method, HLS_ENC_NONE); m3u8_builder_init_config( &conf->m3u8_config, - base->segmenter.max_segment_duration, + base->segmenter.max_segment_duration, conf->encryption_method); switch (conf->encryption_method) @@ -1058,7 +1100,7 @@ ngx_http_vod_hls_merge_loc_conf( return NGX_CONF_OK; } -static int +static int ngx_http_vod_hls_get_file_path_components(ngx_str_t* uri) { return 1; diff --git a/vod/hds/hds_amf0_encoder.c b/vod/hds/hds_amf0_encoder.c index d20b79d2..f587b1c4 100644 --- a/vod/hds/hds_amf0_encoder.c +++ b/vod/hds/hds_amf0_encoder.c @@ -127,7 +127,6 @@ hds_amf0_write_metadata(u_char* p, media_set_t* media_set, media_track_t** track uint64_t duration; uint32_t timescale; uint32_t count; - uint32_t bitrate = 0; uint8_t sound_format; count = AMF0_COMMON_FIELDS_COUNT; @@ -155,7 +154,6 @@ hds_amf0_write_metadata(u_char* p, media_set_t* media_set, media_track_t** track if (tracks[MEDIA_TYPE_VIDEO] != NULL) { media_info = &tracks[MEDIA_TYPE_VIDEO]->media_info; - bitrate += media_info->bitrate; p = hds_amf0_append_array_number_value(p, &amf0_width, media_info->u.video.width); p = hds_amf0_append_array_number_value(p, &amf0_height, media_info->u.video.height); p = hds_amf0_append_array_number_value(p, &amf0_videodatarate, (double)media_info->bitrate / 1000.0); @@ -166,7 +164,6 @@ hds_amf0_write_metadata(u_char* p, media_set_t* media_set, media_track_t** track if (tracks[MEDIA_TYPE_AUDIO] != NULL) { media_info = &tracks[MEDIA_TYPE_AUDIO]->media_info; - bitrate += media_info->bitrate; p = hds_amf0_append_array_number_value(p, &amf0_audiodatarate, (double)media_info->bitrate / 1000.0); p = hds_amf0_append_array_number_value(p, &amf0_audiosamplerate, media_info->u.audio.sample_rate); p = hds_amf0_append_array_number_value(p, &amf0_audiosamplesize, media_info->u.audio.bits_per_sample); diff --git a/vod/hls/hls_muxer.c b/vod/hls/hls_muxer.c index 26630ddc..db32b692 100644 --- a/vod/hls/hls_muxer.c +++ b/vod/hls/hls_muxer.c @@ -196,7 +196,7 @@ hls_muxer_init_id3_stream( if (sequence_id->len != 0) { sequence_id_escape = vod_escape_json(NULL, sequence_id->data, sequence_id->len); - data_size = sizeof(ID3_TEXT_JSON_SEQUENCE_ID_PREFIX_FORMAT) + VOD_INT64_LEN + + data_size = sizeof(ID3_TEXT_JSON_SEQUENCE_ID_PREFIX_FORMAT) + VOD_INT64_LEN + sequence_id->len + sequence_id_escape + sizeof(ID3_TEXT_JSON_SEQUENCE_ID_SUFFIX); } @@ -907,7 +907,7 @@ hls_muxer_simulation_write_frame(hls_muxer_stream_state_t* selected_stream, inpu selected_stream->filter.simulated_flush_frame(&selected_stream->filter_context, last_frame); } -static void +static void hls_muxer_simulation_set_segment_limit( hls_muxer_state_t* state, uint64_t segment_end, @@ -1270,3 +1270,198 @@ hls_muxer_simulation_reset(hls_muxer_state_t* state) state->cur_frame = NULL; } + +vod_status_t +hls_muxer_simulate_get_segment_sizes( + request_context_t* request_context, + segment_durations_t* segment_durations, + hls_mpegts_muxer_conf_t* muxer_conf, + hls_encryption_params_t* encryption_params, + media_set_t* media_set, + uint32_t* bandwidth, + uint32_t* avg_bandwidth) +{ + hls_muxer_stream_state_t* selected_stream; + segment_duration_item_t* cur_item; + segment_duration_item_t* last_item; + hls_muxer_state_t state; + input_frame_t* cur_frame; + uint32_t segment_index = 0; + uint32_t segment_bandwidth[segment_durations->segment_count]; + + uint64_t cur_frame_dts; + uint32_t repeat_count; + uint64_t segment_end; + bool_t simulation_supported; + bool_t last_frame; + vod_status_t rc; + + uint32_t largest_segment = 0; + uint32_t sum_segments = 0; + uint32_t valid_segments = 0; + +#if (VOD_DEBUG) + off_t cur_frame_start; +#endif // VOD_DEBUG + + cur_item = segment_durations->items; + last_item = segment_durations->items + segment_durations->item_count; + + if (cur_item >= last_item) + { + return VOD_OK; + } + + // initialize the muxer + rc = hls_muxer_init_base( + &state, + request_context, + muxer_conf, + encryption_params, + 0, + media_set, + &simulation_supported, + NULL); + if (rc != VOD_OK) + { + return rc; + } + + if (!simulation_supported) + { + vod_log_error(VOD_LOG_ERR, request_context->log, 0, + "hls_muxer_simulate_get_segment_sizes: simulation not supported for this file, cant create iframe playlist"); + return VOD_BAD_REQUEST; + } + + // initialize the repeat count, segment end, and the per stream limit + repeat_count = cur_item->repeat_count - 1; + segment_end = cur_item->duration; + + if (repeat_count <= 0 && (cur_item + 1 >= last_item || cur_item[1].discontinuity)) + { + hls_muxer_simulation_set_segment_limit_unlimited(&state); + } + else + { + hls_muxer_simulation_set_segment_limit(&state, segment_end, segment_durations->timescale); + } + + mpegts_encoder_simulated_start_segment(&state.queue); + for (;;) + { + + // get a frame + for (;;) + { + // choose a stream for the current frame + rc = hls_muxer_choose_stream(&state, &selected_stream); + if (rc == VOD_OK) + { + break; + } + + if (rc != VOD_NOT_FOUND) + { + return rc; + } + // update the limit for the next segment + segment_bandwidth[segment_index] = state.queue.cur_offset / ((float)(cur_item->duration) / (float)(segment_durations->timescale)); +// vod_log_error(VOD_LOG_ERR, state.request_context->log, 0, +// "hls_muxer_simulate_get_segment_sizes: segment %12L %12L %12f %12f %12f", segment_index + 1, state.queue.cur_offset * 8, (float)(cur_item->duration) / (float)(segment_durations->timescale) , (state.queue.cur_offset * 8) / ((float)(cur_item->duration) / (float)(segment_durations->timescale)), segment_bandwidth[segment_index]); + + if (repeat_count <= 0) + { + cur_item++; + vod_log_error(VOD_LOG_ERR, state.request_context->log, 0, + "hls_muxer_simulate_get_segment_sizes: next segment duration"); + + if (cur_item >= last_item) + { + vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, + "goto done: done"); + goto done; + } + + repeat_count = cur_item->repeat_count; + } + + repeat_count--; + segment_end += cur_item->duration; + + + if (repeat_count <= 0 && (cur_item + 1 >= last_item || cur_item[1].discontinuity)) + { + hls_muxer_simulation_set_segment_limit_unlimited(&state); + } + else + { + hls_muxer_simulation_set_segment_limit(&state, segment_end, segment_durations->timescale); + } + + + + + mpegts_encoder_simulated_start_segment(&state.queue); + segment_index++; + + } + // update the stream state + cur_frame = selected_stream->cur_frame; + selected_stream->cur_frame++; + cur_frame_dts = selected_stream->next_frame_time_offset; + selected_stream->next_frame_time_offset += cur_frame->duration; + + // flush any buffered frames if their delay becomes too big + hls_muxer_simulation_flush_delayed_streams(&state, selected_stream, cur_frame_dts); + + // check whether this is the last frame of the selected stream in this segment + last_frame = ((selected_stream->cur_frame >= selected_stream->cur_frame_part.last_frame && + selected_stream->cur_frame_part.next == NULL) || + selected_stream->next_frame_time_offset >= selected_stream->segment_limit); + + // write the frame +#if (VOD_DEBUG) + cur_frame_start = state.queue.cur_offset; +#endif // VOD_DEBUG + + hls_muxer_simulation_write_frame( + selected_stream, + cur_frame, + cur_frame_dts, + last_frame); + +#if (VOD_DEBUG) + if (cur_frame_start != state.queue.cur_offset) + { + vod_log_debug4(VOD_LOG_DEBUG_LEVEL, state.request_context->log, 0, + "hls_muxer_simulate_get_segment_sizes: wrote frame segment %uD packets %uD-%uD dts %L", + segment_index + 1, + (uint32_t)(cur_frame_start / MPEGTS_PACKET_SIZE + 1), + (uint32_t)(state.queue.cur_offset / MPEGTS_PACKET_SIZE + 1), + cur_frame_dts); + } +#endif // VOD_DEBUG + } + + done: + + + + for (uint32_t i = 0; i < segment_durations->segment_count; i++) { + if (segment_bandwidth[i] > largest_segment) { + largest_segment = segment_bandwidth[i]; + } + if (segment_bandwidth[i] > 100) { + sum_segments += segment_bandwidth[i]; + valid_segments++; + } + +// vod_log_error(VOD_LOG_ERR, state.request_context->log, 0, +// "hls_muxer_simulate_get_segment_sizes: segment %L %L kbit/s", i + 1, segment_bandwidth[i] / 1024 * 8); + } + + *avg_bandwidth = sum_segments / valid_segments * 8 ; + *bandwidth = largest_segment * 8; + return VOD_OK; +} diff --git a/vod/hls/hls_muxer.h b/vod/hls/hls_muxer.h index 6684d343..9228b784 100644 --- a/vod/hls/hls_muxer.h +++ b/vod/hls/hls_muxer.h @@ -112,4 +112,14 @@ vod_status_t hls_muxer_simulate_get_iframes( hls_get_iframe_positions_callback_t callback, void* context); + +vod_status_t hls_muxer_simulate_get_segment_sizes( + request_context_t* request_context, + segment_durations_t* segment_durations, + hls_mpegts_muxer_conf_t* muxer_conf, + hls_encryption_params_t* encryption_params, + media_set_t* media_set, + uint32_t* bandwidth, + uint32_t* avg_bandwidth); + #endif // __HLS_MUXER_H__ diff --git a/vod/hls/m3u8_builder.c b/vod/hls/m3u8_builder.c index 2ab9c1a2..5fe9fe3a 100644 --- a/vod/hls/m3u8_builder.c +++ b/vod/hls/m3u8_builder.c @@ -1,4 +1,5 @@ #include "m3u8_builder.h" +#include "hls_muxer.h" #include "../manifest_utils.h" #include "../mp4/mp4_defs.h" @@ -1071,15 +1072,17 @@ m3u8_builder_write_variants( m3u8_config_t* conf, vod_str_t* base_url, media_set_t* media_set, - media_track_t* group_audio_track) + media_track_t* group_audio_track, + hls_mpegts_muxer_conf_t* muxer_conf, + segment_durations_t* segment_durations, + hls_encryption_params_t* encryption_params, + request_context_t* request_context) { adaptation_set_t* adaptation_set = adaptation_sets->first; media_track_t** cur_track_ptr; media_track_t* tracks[MEDIA_TYPE_COUNT]; media_info_t* video = NULL; media_info_t* audio = NULL; - uint32_t bitrate; - uint32_t avg_bitrate; uint32_t muxed_tracks = adaptation_set->type == ADAPTATION_TYPE_MUXED ? MEDIA_TYPE_COUNT : 1; vod_memzero(tracks, sizeof(tracks)); @@ -1088,7 +1091,11 @@ m3u8_builder_write_variants( cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks) { - // get the audio / video tracks + uint32_t bandwidth; + uint32_t avg_bandwidth; + + + // get the audio / video tracks if (muxed_tracks == MEDIA_TYPE_COUNT) { tracks[MEDIA_TYPE_VIDEO] = cur_track_ptr[MEDIA_TYPE_VIDEO]; @@ -1100,12 +1107,19 @@ m3u8_builder_write_variants( tracks[adaptation_set->type] = cur_track_ptr[0]; } - // output EXT-X-STREAM-INF + + media_set->filtered_tracks = cur_track_ptr[0]; + media_set->filtered_tracks_end = cur_track_ptr[0]; + media_set->total_track_count = 1; + hls_muxer_simulate_get_segment_sizes(request_context, segment_durations, muxer_conf, encryption_params, media_set, &bandwidth, &avg_bandwidth); + vod_log_error(VOD_LOG_ERR, request_context->log, 0, + "m3u8_builder_write_variants (bandwidth avg_bandwidth): %L %L", bandwidth, avg_bandwidth); + + + // output EXT-X-STREAM-INF if (tracks[MEDIA_TYPE_VIDEO] != NULL) { video = &tracks[MEDIA_TYPE_VIDEO]->media_info; - bitrate = video->bitrate; - avg_bitrate = video->avg_bitrate; if (group_audio_track != NULL) { audio = &group_audio_track->media_info; @@ -1119,17 +1133,9 @@ m3u8_builder_write_variants( audio = NULL; } - if (audio != NULL) - { - bitrate += audio->bitrate; - if (avg_bitrate != 0) - { - avg_bitrate += audio->avg_bitrate; - } - } p = vod_sprintf(p, m3u8_stream_inf_video, - bitrate, + bandwidth, (uint32_t)video->u.video.width, (uint32_t)video->u.video.height, (uint32_t)(video->timescale / video->min_frame_duration), @@ -1152,16 +1158,14 @@ m3u8_builder_write_variants( audio = &tracks[MEDIA_TYPE_AUDIO]->media_info; } - avg_bitrate = audio->avg_bitrate; - p = vod_sprintf(p, m3u8_stream_inf_audio, audio->bitrate, &audio->codec_name); + + p = vod_sprintf(p, m3u8_stream_inf_audio, bandwidth, &audio->codec_name); } *p++ = '\"'; - if (avg_bitrate != 0) - { - p = vod_sprintf(p, m3u8_average_bandwidth, avg_bitrate); - } + p = vod_sprintf(p, m3u8_average_bandwidth, avg_bandwidth); + if (tracks[MEDIA_TYPE_VIDEO] != NULL) { @@ -1282,6 +1286,8 @@ m3u8_builder_build_master_playlist( vod_uint_t encryption_method, vod_str_t* base_url, media_set_t* media_set, + hls_mpegts_muxer_conf_t* muxer_conf, + hls_encryption_params_t* encryption_params, vod_str_t* result) { adaptation_sets_t adaptation_sets; @@ -1300,6 +1306,9 @@ m3u8_builder_build_master_playlist( size_t result_size; u_char* p; bool_t alternative_audio; + segment_durations_t segment_durations; + segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; + // get the adaptations sets flags = ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK | ADAPTATION_SETS_FLAG_MULTI_AUDIO_CODEC; @@ -1465,6 +1474,36 @@ m3u8_builder_build_master_playlist( p = m3u8_builder_closed_captions_write(p, media_set); } + + 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) + { + vod_log_error(VOD_LOG_ERR, request_context->log, 0, + "ngx_http_vod_thumb_get_url: result length %uz exceeded allocated length %uz", + result->len, result_size); + return rc; + } // output variants if (variant_set_count > 1) { @@ -1479,7 +1518,11 @@ m3u8_builder_build_master_playlist( conf, base_url, media_set, - *cur_track_ptr); + *cur_track_ptr, + muxer_conf, + &segment_durations, + encryption_params, + request_context); } } else @@ -1490,7 +1533,11 @@ m3u8_builder_build_master_playlist( conf, base_url, media_set, - alternative_audio ? adaptation_sets.first_by_type[ADAPTATION_TYPE_AUDIO]->first[0] : NULL); + alternative_audio ? adaptation_sets.first_by_type[ADAPTATION_TYPE_AUDIO]->first[0] : NULL, + muxer_conf, + &segment_durations, + encryption_params, + request_context); } // iframes diff --git a/vod/hls/m3u8_builder.h b/vod/hls/m3u8_builder.h index 5b3dff95..7923c52d 100644 --- a/vod/hls/m3u8_builder.h +++ b/vod/hls/m3u8_builder.h @@ -41,6 +41,8 @@ vod_status_t m3u8_builder_build_master_playlist( vod_uint_t encryption_method, vod_str_t* base_url, media_set_t* media_set, + hls_mpegts_muxer_conf_t* muxer_conf, + hls_encryption_params_t* encryption_params, vod_str_t* result); vod_status_t m3u8_builder_build_index_playlist(