static void m3u8_builder_append_iframe_string(void* context, uint32_t segment_index, uint32_t frame_duration, uint32_t frame_start, uint32_t frame_size) { write_segment_context_t* ctx = (write_segment_context_t*)context; ctx->p = m3u8_builder_append_extinf_tag(ctx->p, frame_duration, 1000); ctx->p = vod_sprintf(ctx->p, byte_range_tag_format, frame_size, frame_start); ctx->p = m3u8_builder_append_segment_name( ctx->p, ctx->base_url, ctx->segment_file_name_prefix, segment_index, &ctx->tracks_spec); }
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_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, bool_t encryption_enabled, 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 + m3u8_builder_get_int_print_len(vod_div_ceil(mpeg_metadata->duration_millis, 1000)) + segments_base_url->len + conf->segment_file_name_prefix.len + 1 + m3u8_builder_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_enabled) { result_size += sizeof(encryption_key_tag_prefix) - 1 + base_url->len + conf->encryption_key_file_name.len + sizeof("-f") - 1 + VOD_INT32_LEN + sizeof(encryption_key_tag_postfix) - 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_enabled) { p = vod_copy(p, encryption_key_tag_prefix, sizeof(encryption_key_tag_prefix) - 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_postfix, sizeof(encryption_key_tag_postfix)-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; }