vod_status_t manifest_utils_build_request_params_string( request_context_t* request_context, uint32_t* has_tracks, uint32_t segment_index, uint32_t sequences_mask, uint32_t* sequence_tracks_mask, uint32_t* tracks_mask, vod_str_t* suffix, vod_str_t* args_str, vod_str_t* result) { u_char* p; size_t result_size; bool_t newline_shift = FALSE; if (sequence_tracks_mask != NULL) { return manifest_utils_build_request_params_string_per_sequence_tracks( request_context, segment_index, sequences_mask, sequence_tracks_mask, result); } result_size = suffix->len + args_str->len; if (suffix->data[suffix->len - 1] == '\n') { newline_shift = TRUE; } // segment index if (segment_index != INVALID_SEGMENT_INDEX) { result_size += 1 + vod_get_int_print_len(segment_index + 1); } // sequence mask if (sequences_mask != 0xffffffff) { result_size += vod_get_number_of_set_bits(sequences_mask) * (sizeof("-f32") - 1); } // video tracks if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff) { result_size += sizeof("-v0") - 1; } else { result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1); } // audio tracks if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff) { result_size += sizeof("-a0") - 1; } else { result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1); } p = vod_alloc(request_context->pool, result_size + 1); if (p == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_build_request_params_string: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->data = p; // segment index if (segment_index != INVALID_SEGMENT_INDEX) { p = vod_sprintf(p, "-%uD", segment_index + 1); } // sequence mask if (sequences_mask != 0xffffffff) { p = manifest_utils_write_bitmask(p, sequences_mask, 'f'); } // video tracks if (has_tracks[MEDIA_TYPE_VIDEO]) { if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff) { p = vod_copy(p, "-v0", sizeof("-v0") - 1); } else { p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_VIDEO], 'v'); } } // audio tracks if (has_tracks[MEDIA_TYPE_AUDIO]) { if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff) { p = vod_copy(p, "-a0", sizeof("-a0") - 1); } else { p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_AUDIO], 'a'); } } p = vod_copy(p, suffix->data, newline_shift ? suffix->len - 1 : suffix->len); p = vod_copy(p, args_str->data, args_str->len); if (newline_shift) { *p++ = '\n'; } result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "manifest_utils_build_request_params_string: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
static vod_status_t manifest_utils_build_request_params_string_per_sequence_tracks( request_context_t* request_context, uint32_t segment_index, uint32_t sequences_mask, uint32_t* sequence_tracks_mask, vod_str_t* result) { u_char* p; size_t result_size; uint32_t* tracks_mask; uint32_t i; result_size = 0; // segment index if (segment_index != INVALID_SEGMENT_INDEX) { result_size += 1 + vod_get_int_print_len(segment_index + 1); } for (i = 0, tracks_mask = sequence_tracks_mask; i < MAX_SEQUENCES; i++, tracks_mask += MEDIA_TYPE_COUNT) { if ((sequences_mask & (1 << i)) == 0) { continue; } // sequence result_size += sizeof("-f32") - 1; // video tracks if (tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff) { result_size += sizeof("-v0") - 1; } else { result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1); } // audio tracks if (tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff) { result_size += sizeof("-a0") - 1; } else { result_size += vod_get_number_of_set_bits(tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1); } } p = vod_alloc(request_context->pool, result_size + 1); if (p == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_build_request_params_string_per_sequence_tracks: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->data = p; // segment index if (segment_index != INVALID_SEGMENT_INDEX) { p = vod_sprintf(p, "-%uD", segment_index + 1); } for (i = 0, tracks_mask = sequence_tracks_mask; i < MAX_SEQUENCES; i++, tracks_mask += MEDIA_TYPE_COUNT) { if ((sequences_mask & (1 << i)) == 0) { continue; } // sequence p = vod_sprintf(p, "-f%uD", i + 1); // video tracks switch (tracks_mask[MEDIA_TYPE_VIDEO]) { case 0xffffffff: p = vod_copy(p, "-v0", sizeof("-v0") - 1); break; case 0: break; default: p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_VIDEO], 'v'); break; } // audio tracks switch (tracks_mask[MEDIA_TYPE_AUDIO]) { case 0xffffffff: p = vod_copy(p, "-a0", sizeof("-a0") - 1); break; case 0: break; default: p = manifest_utils_write_bitmask(p, tracks_mask[MEDIA_TYPE_AUDIO], 'a'); break; } } result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "manifest_utils_build_request_params_string_per_sequence_tracks: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
static vod_status_t m3u8_builder_build_required_tracks_string( request_context_t* request_context, media_set_t* media_set, uint32_t sequence_index, request_params_t* request_params, vod_str_t* tracks_spec) { u_char* p; size_t result_size; uint32_t i; result_size = 0; if (request_params->tracks_mask[MEDIA_TYPE_VIDEO] != 0xffffffff) { result_size += vod_get_number_of_set_bits(request_params->tracks_mask[MEDIA_TYPE_VIDEO]) * (sizeof("-v32") - 1); } if (request_params->tracks_mask[MEDIA_TYPE_AUDIO] != 0xffffffff) { result_size += vod_get_number_of_set_bits(request_params->tracks_mask[MEDIA_TYPE_AUDIO]) * (sizeof("-a32") - 1); } if (sequence_index != INVALID_SEQUENCE_INDEX) { result_size += sizeof("-f") - 1 + vod_get_int_print_len(sequence_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; } tracks_spec->data = p; if (sequence_index != INVALID_SEQUENCE_INDEX) { p = vod_sprintf(p, "-f%uD", sequence_index + 1); } if (media_set->track_count[MEDIA_TYPE_VIDEO] != 0) { if (request_params->tracks_mask[MEDIA_TYPE_VIDEO] == 0xffffffff) { p = vod_copy(p, "-v0", sizeof("-v0") - 1); } else { for (i = 0; i < 32; i++) { if ((request_params->tracks_mask[MEDIA_TYPE_VIDEO] & (1 << i)) == 0) { continue; } p = vod_sprintf(p, "-v%uD", i + 1); } } } if (media_set->track_count[MEDIA_TYPE_AUDIO] != 0) { if (request_params->tracks_mask[MEDIA_TYPE_AUDIO] == 0xffffffff) { p = vod_copy(p, "-a0", sizeof("-a0") - 1); } else { for (i = 0; i < 32; i++) { if ((request_params->tracks_mask[MEDIA_TYPE_AUDIO] & (1 << i)) == 0) { continue; } p = vod_sprintf(p, "-a%uD", i + 1); } } } tracks_spec->len = p - tracks_spec->data; if (tracks_spec->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", tracks_spec->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 = m3u8_builder_build_required_tracks_string( request_context, media_set, sequence_index, request_params, &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_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 (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_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 (sequence_index != INVALID_SEQUENCE_INDEX) { p = vod_sprintf(p, "-f%uD", sequence_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, 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->type == MEDIA_SET_VOD) { 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 = 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; }
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; }
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; }