static u_char* m3u8_builder_append_extinf_tag(u_char* p, uint32_t duration, uint32_t scale) { p = vod_copy(p, "#EXTINF:", sizeof("#EXTINF:") - 1); p = m3u8_builder_format_double(p, duration, scale); *p++ = ','; *p++ = '\n'; return p; }
static u_char* edash_packager_write_pssh(void* context, u_char* p) { mp4_encrypt_system_info_array_t* pssh_array = (mp4_encrypt_system_info_array_t*)context; mp4_encrypt_system_info_t* cur_info; size_t pssh_atom_size; for (cur_info = pssh_array->first; cur_info < pssh_array->last; cur_info++) { pssh_atom_size = ATOM_HEADER_SIZE + sizeof(pssh_atom_t) + cur_info->data.len; write_atom_header(p, pssh_atom_size, 'p', 's', 's', 'h'); write_dword(p, 0); // version + flags p = vod_copy(p, cur_info->system_id, MP4_ENCRYPT_SYSTEM_ID_SIZE); // system id write_dword(p, cur_info->data.len); // data size p = vod_copy(p, cur_info->data.data, cur_info->data.len); } return p; }
static u_char* mss_playready_audio_write_uuid_piff_atom(u_char* p, mp4_encrypt_state_t* state, media_sequence_t* sequence, size_t atom_size) { write_atom_header(p, atom_size, 'u', 'u', 'i', 'd'); p = vod_copy(p, piff_uuid, sizeof(piff_uuid)); write_be32(p, 0); write_be32(p, sequence->total_frame_count); p = mp4_encrypt_audio_write_auxiliary_data(state, p); return p; }
static u_char* mss_write_uuid_tfxd_atom(u_char* p, segment_timing_info_t* timing_info) { size_t atom_size = ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t); write_atom_header(p, atom_size, 'u', 'u', 'i', 'd'); p = vod_copy(p, tfxd_uuid, sizeof(tfxd_uuid)); write_be32(p, 0x01000000); // version / flags write_be64(p, timing_info->timestamp); write_be64(p, timing_info->duration); return p; }
static u_char* mss_playready_write_protection_tag(void* context, u_char* p, media_set_t* media_set) { // Note: taking only the first sequence, in mss all renditions must have the same key drm_info_t* drm_info = (drm_info_t*)media_set->sequences[0].drm_info; drm_system_info_t* cur_info; vod_str_t base64; p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_PREFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_PREFIX) - 1); for (cur_info = drm_info->pssh_array.first; cur_info < drm_info->pssh_array.last; cur_info++) { p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_PREFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_PREFIX) - 1); p = mp4_encrypt_write_guid(p, cur_info->system_id); p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_DELIMITER, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_DELIMITER) - 1); base64.data = p; vod_encode_base64(&base64, &cur_info->data); p += base64.len; p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_HEADER_SUFFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_HEADER_SUFFIX) - 1); } p = vod_copy(p, VOD_MSS_PLAYREADY_PROTECTION_SUFFIX, sizeof(VOD_MSS_PLAYREADY_PROTECTION_SUFFIX) - 1); return p; }
static u_char* mss_write_uuid_tfxd_atom(u_char* p, mpeg_stream_metadata_t* stream_metadata) { size_t atom_size = ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t); uint64_t timestamp = rescale_time(stream_metadata->first_frame_time_offset, stream_metadata->media_info.timescale, MSS_TIMESCALE); uint64_t duration = rescale_time(stream_metadata->total_frames_duration, stream_metadata->media_info.timescale, MSS_TIMESCALE); write_atom_header(p, atom_size, 'u', 'u', 'i', 'd'); p = vod_copy(p, tfxd_uuid, sizeof(tfxd_uuid)); write_dword(p, 0x01000000); // version / flags write_qword(p, timestamp); write_qword(p, duration); return p; }
static u_char* mss_playready_passthrough_write_encryption_atoms(void* ctx, u_char* p, size_t mdat_atom_start) { mp4_encrypt_passthrough_context_t* context = ctx; media_clip_filtered_t* cur_clip; media_sequence_t* sequence = context->sequence; media_track_t* cur_track; size_t auxiliary_data_offset; size_t uuid_atom_size; uint32_t flags; // uuid piff uuid_atom_size = ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t) + context->auxiliary_info_size; write_atom_header(p, uuid_atom_size, 'u', 'u', 'i', 'd'); p = vod_copy(p, piff_uuid, sizeof(piff_uuid)); flags = context->use_subsamples ? 0x2 : 0x0; write_be32(p, flags); // flags write_be32(p, sequence->total_frame_count); for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++) { cur_track = cur_clip->first_track; p = vod_copy(p, cur_track->encryption_info.auxiliary_info, cur_track->encryption_info.auxiliary_info_end - cur_track->encryption_info.auxiliary_info); } // saiz / saio auxiliary_data_offset = mdat_atom_start - (context->auxiliary_info_size + context->saiz_atom_size + context->saio_atom_size); p = mp4_encrypt_passthrough_write_saiz_saio(ctx, p, auxiliary_data_offset); return p; }
static u_char* dash_packager_write_segment_template( u_char* p, dash_manifest_config_t* conf, segmenter_conf_t* segmenter_conf, vod_str_t* base_url, segment_durations_t* segment_durations) { segment_duration_item_t* cur_item; segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count; uint32_t duration; if (!conf->segment_timeline) { return vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED, base_url, &conf->fragment_file_name_prefix, base_url, &conf->init_file_name_prefix, segmenter_conf->segment_duration); } p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER, base_url, &conf->fragment_file_name_prefix, base_url, &conf->init_file_name_prefix); for (cur_item = segment_durations->items; cur_item < last_item; cur_item++) { duration = (uint32_t)rescale_time(cur_item->duration, segment_durations->timescale, 1000); if (cur_item->repeat_count == 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT, duration); } else if (cur_item->repeat_count > 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_REPEAT, duration, cur_item->repeat_count - 1); } } p = vod_copy(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER, sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER) - 1); return p; }
static u_char* dash_packager_write_segment_timeline( u_char* p, dash_manifest_config_t* conf, segment_durations_t* segment_durations, segment_duration_item_t** cur_item_ptr, vod_str_t* base_url) { segment_duration_item_t* cur_item; segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count; uint32_t duration; bool_t first_time = TRUE; p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER, base_url, &conf->fragment_file_name_prefix, base_url, &conf->init_file_name_prefix); for (cur_item = *cur_item_ptr; cur_item < last_item; cur_item++) { // stop on discontinuity, will get called again for the next period if (cur_item->discontinuity && !first_time) { break; } first_time = FALSE; duration = (uint32_t)rescale_time(cur_item->duration, segment_durations->timescale, 1000); if (cur_item->repeat_count == 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT, duration); } else if (cur_item->repeat_count > 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_REPEAT, duration, cur_item->repeat_count - 1); } } *cur_item_ptr = cur_item; p = vod_copy(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER, sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER) - 1); return p; }
u_char* mp4_encrypt_audio_write_auxiliary_data(mp4_encrypt_state_t* state, u_char* p) { u_char* end_pos = p + sizeof(state->iv) * state->sequence->total_frame_count; u_char iv[MP4_AES_CTR_IV_SIZE]; vod_memcpy(iv, state->iv, sizeof(iv)); while (p < end_pos) { p = vod_copy(p, iv, sizeof(iv)); mp4_aes_ctr_increment_be64(iv); } return p; }
static u_char* edash_packager_write_content_protection(void* context, u_char* p, media_track_t* track) { drm_info_t* drm_info = (drm_info_t*)track->file_info.drm_info; drm_system_info_t* cur_info; vod_str_t base64; vod_str_t pssh; p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC, 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) { p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART1, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART1) - 1); p = mp4_encrypt_write_guid(p, cur_info->system_id); p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART2, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART2) - 1); base64.data = p; vod_encode_base64(&base64, &cur_info->data); p += base64.len; p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_PLAYREADY_PART3) - 1); } else { p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART1) - 1); p = mp4_encrypt_write_guid(p, cur_info->system_id); p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART2) - 1); p = mp4_encrypt_write_guid(p, drm_info->key_id); p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART3) - 1); pssh.data = (u_char*)context; pssh.len = edash_packager_write_pssh(pssh.data, cur_info) - pssh.data; base64.data = p; vod_encode_base64(&base64, &pssh); p += base64.len; p = vod_copy(p, VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4, sizeof(VOD_EDASH_MANIFEST_CONTENT_PROTECTION_CENC_PART4) - 1); } } return p; }
static u_char* edash_packager_video_write_encryption_atoms(void* context, u_char* p, size_t mdat_atom_start) { mp4_encrypt_video_state_t* state = (mp4_encrypt_video_state_t*)context; size_t senc_data_size = state->auxiliary_data.pos - state->auxiliary_data.start; size_t senc_atom_size = ATOM_HEADER_SIZE + sizeof(senc_atom_t) + senc_data_size; // saiz / saio p = mp4_encrypt_video_write_saiz_saio(state, p, mdat_atom_start - senc_data_size); // senc write_atom_header(p, senc_atom_size, 's', 'e', 'n', 'c'); write_be32(p, 0x2); // flags write_be32(p, state->base.sequence->total_frame_count); p = vod_copy(p, state->auxiliary_data.start, senc_data_size); return p; }
u_char* mp4_encrypt_video_write_saiz_saio(mp4_encrypt_video_state_t* state, u_char* p, size_t auxiliary_data_offset) { // moof.traf.saiz write_atom_header(p, state->base.saiz_atom_size, 's', 'a', 'i', 'z'); write_be32(p, 0); // version, flags *p++ = state->default_auxiliary_sample_size; write_be32(p, state->saiz_sample_count); if (state->default_auxiliary_sample_size == 0) { p = vod_copy(p, state->auxiliary_sample_sizes, state->saiz_sample_count); } // moof.traf.saio write_atom_header(p, state->base.saio_atom_size, 's', 'a', 'i', 'o'); write_be32(p, 0); // version, flags write_be32(p, 1); // entry count write_be32(p, auxiliary_data_offset); return p; }
static u_char* hds_muxer_write_codec_config(u_char* p, hds_muxer_state_t* state, uint64_t cur_frame_dts) { media_track_t* cur_track; hds_muxer_stream_state_t* cur_stream; uint32_t packet_size; u_char* packet_start; for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++) { cur_track = cur_stream->track; packet_start = p; switch (cur_track->media_info.media_type) { case MEDIA_TYPE_VIDEO: p = hds_write_video_tag_header( p, cur_track->media_info.extra_data_size, cur_frame_dts, FRAME_TYPE_KEY_FRAME, AVC_PACKET_TYPE_SEQUENCE_HEADER, 0); break; case MEDIA_TYPE_AUDIO: p = hds_write_audio_tag_header( p, cur_track->media_info.extra_data_size, cur_frame_dts, cur_stream->sound_info, AAC_PACKET_TYPE_SEQUENCE_HEADER); break; } p = vod_copy(p, cur_track->media_info.extra_data, cur_track->media_info.extra_data_size); packet_size = p - packet_start; write_be32(p, packet_size); } return p; }
static u_char* hds_muxer_write_codec_config(u_char* p, hds_muxer_state_t* state, uint64_t cur_frame_dts) { mpeg_stream_metadata_t* cur_stream; hds_muxer_stream_state_t* stream_state; size_t packet_size; for (stream_state = state->first_stream; stream_state < state->last_stream; stream_state++) { cur_stream = stream_state->metadata; packet_size = sizeof(adobe_mux_packet_header_t)+cur_stream->media_info.extra_data_size; switch (cur_stream->media_info.media_type) { case MEDIA_TYPE_VIDEO: p = hds_write_video_tag_header( p, cur_stream->media_info.extra_data_size, cur_frame_dts, FRAME_TYPE_KEY_FRAME, AVC_PACKET_TYPE_SEQUENCE_HEADER, 0); packet_size += sizeof(video_tag_header_avc); break; case MEDIA_TYPE_AUDIO: p = hds_write_audio_tag_header( p, cur_stream->media_info.extra_data_size, cur_frame_dts, stream_state->sound_info, AAC_PACKET_TYPE_SEQUENCE_HEADER); packet_size += sizeof(audio_tag_header_aac); break; } p = vod_copy(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size); write_dword(p, packet_size); } return p; }
vod_status_t dash_packager_build_mpd( request_context_t* request_context, dash_manifest_config_t* conf, vod_str_t* base_url, segmenter_conf_t* segmenter_conf, media_set_t* media_set, size_t representation_tags_size, write_tags_callback_t write_representation_tags, void* representation_tags_writer_context, vod_str_t* result) { segment_durations_t segment_durations[MEDIA_TYPE_COUNT]; segment_duration_item_t* cur_duration_items[MEDIA_TYPE_COUNT]; size_t base_url_temp_buffer_size = 0; size_t base_period_size; size_t result_size; size_t urls_length; uint32_t period_count = media_set->use_discontinuity ? media_set->total_clip_count : 1; uint32_t clip_index; uint32_t media_type; vod_status_t rc; u_char* base_url_temp_buffer = NULL; u_char* p; // get segment durations and count for each media type for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, NULL, media_type, &segment_durations[media_type]); if (rc != VOD_OK) { return rc; } } // remove redundant tracks dash_packager_remove_redundant_tracks( conf->duplicate_bitrate_threshold, media_set); // calculate the total size urls_length = 2 * base_url->len + conf->init_file_name_prefix.len + conf->fragment_file_name_prefix.len; base_period_size = sizeof(VOD_DASH_MANIFEST_PERIOD_DURATION_HEADER) - 1 + 3 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_VIDEO_HEADER) - 1 + 4 * VOD_INT32_LEN + media_set->track_count[MEDIA_TYPE_VIDEO] * ( sizeof(VOD_DASH_MANIFEST_VIDEO_PREFIX) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_CODEC_NAME_SIZE + 5 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_VIDEO_SUFFIX) - 1) + sizeof(VOD_DASH_MANIFEST_VIDEO_FOOTER) - 1 + sizeof(VOD_DASH_MANIFEST_AUDIO_HEADER) - 1 + media_set->track_count[MEDIA_TYPE_AUDIO] * ( sizeof(VOD_DASH_MANIFEST_AUDIO_PREFIX) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_CODEC_NAME_SIZE + 2 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_AUDIO_SUFFIX) - 1) + sizeof(VOD_DASH_MANIFEST_AUDIO_FOOTER) - 1 + sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1 + representation_tags_size; result_size = sizeof(VOD_DASH_MANIFEST_HEADER) - 1 + 3 * VOD_INT32_LEN + base_period_size * period_count + sizeof(VOD_DASH_MANIFEST_FOOTER); switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED) - 1 + urls_length + VOD_INT64_LEN) * MEDIA_TYPE_COUNT * period_count; break; case FORMAT_SEGMENT_TIMELINE: for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER) - 1 + urls_length + sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FOOTER) - 1) * period_count + (sizeof(VOD_DASH_MANIFEST_SEGMENT_REPEAT) - 1 + 2 * VOD_INT32_LEN) * segment_durations[media_type].item_count; } break; case FORMAT_SEGMENT_LIST: result_size += dash_packager_get_segment_list_total_size( conf, media_set, segment_durations, base_url, &base_url_temp_buffer_size); break; } // 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, "dash_packager_build_mpd: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } if (base_url_temp_buffer_size != 0) { base_url_temp_buffer = vod_alloc(request_context->pool, base_url_temp_buffer_size); if (base_url_temp_buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_mpd: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } } // print the manifest header p = vod_sprintf(result->data, VOD_DASH_MANIFEST_HEADER, (uint32_t)(media_set->total_duration / 1000), (uint32_t)(media_set->total_duration % 1000), (uint32_t)(segmenter_conf->max_segment_duration / 1000)); cur_duration_items[MEDIA_TYPE_VIDEO] = segment_durations[MEDIA_TYPE_VIDEO].items; cur_duration_items[MEDIA_TYPE_AUDIO] = segment_durations[MEDIA_TYPE_AUDIO].items; for (clip_index = 0; clip_index < period_count; clip_index++) { p = dash_packager_write_mpd_period( p, base_url_temp_buffer, segment_durations, cur_duration_items, clip_index, conf, base_url, segmenter_conf, media_set, write_representation_tags, representation_tags_writer_context); } p = vod_copy(p, VOD_DASH_MANIFEST_FOOTER, sizeof(VOD_DASH_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "dash_packager_build_mpd: 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; }
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; }
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 hds_packager_build_manifest( request_context_t* request_context, hds_manifest_config_t* conf, vod_str_t* base_url, vod_str_t* manifest_id, media_set_t* media_set, bool_t drm_enabled, vod_str_t* result) { hds_segment_durations_t* segment_durations; adaptation_sets_t adaptation_sets; adaptation_set_t* adaptation_set; segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; media_sequence_t* cur_sequence; media_track_t** tracks; media_track_t** cur_track_ptr; media_track_t* tracks_array[MEDIA_TYPE_COUNT]; media_track_t* track; vod_str_t* drm_metadata; uint32_t initial_muxed_tracks; uint32_t muxed_tracks; uint32_t media_count; uint32_t bitrate; uint32_t index; uint32_t abst_atom_size; uint32_t max_abst_atom_size = 0; size_t result_size; vod_status_t rc; u_char* temp_buffer; u_char* p; // get the adaptations sets rc = manifest_utils_get_adaptation_sets( request_context, media_set, ADAPTATION_SETS_FLAG_FORCE_MUXED | ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK | ADAPTATION_SETS_FLAG_AVOID_AUDIO_ONLY, &adaptation_sets); if (rc != VOD_OK) { return rc; } // allocate the segment durations media_count = adaptation_sets.first->count + adaptation_sets.total_count - 1; segment_durations = vod_alloc(request_context->pool, sizeof(segment_durations[0]) * media_count); if (segment_durations == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } // calculate the result size result_size = sizeof(HDS_MANIFEST_HEADER) - 1 + manifest_id->len + sizeof(HDS_MANIFEST_HEADER_BASE_URL) - 1 + base_url->len + sizeof(HDS_MANIFEST_HEADER_LANG) - 1 + LANG_ISO639_2_LEN + sizeof(HDS_MANIFEST_FOOTER); switch (media_set->type) { case MEDIA_SET_VOD: result_size += sizeof(HDS_MANIFEST_HEADER_VOD) - 1 + 2 * VOD_INT32_LEN + (sizeof(HDS_BOOTSTRAP_VOD_HEADER) - 1 + VOD_INT32_LEN + sizeof(HDS_BOOTSTRAP_VOD_FOOTER) - 1) * media_count; break; case MEDIA_SET_LIVE: result_size += sizeof(HDS_MANIFEST_HEADER_LIVE) - 1 + (sizeof(HDS_BOOTSTRAP_LIVE_PREFIX) - 1 + VOD_INT32_LEN + conf->bootstrap_file_name_prefix.len + MANIFEST_UTILS_TRACKS_SPEC_MAX_SIZE + sizeof(HDS_BOOTSTRAP_LIVE_SUFFIX) - 1) * media_count; break; } if (drm_enabled) { result_size += (sizeof(HDS_DRM_ADDITIONAL_HEADER_PREFIX) - 1 + VOD_INT32_LEN + sizeof(HDS_DRM_ADDITIONAL_HEADER_SUFFIX) - 1) * media_count; } result_size += (vod_max(sizeof(HDS_MEDIA_HEADER_PREFIX_VIDEO) - 1 + 3 * VOD_INT32_LEN, sizeof(HDS_MEDIA_HEADER_PREFIX_AUDIO_LANG) - 1 + VOD_INT32_LEN + LANG_ISO639_2_LEN) + conf->fragment_file_name_prefix.len + MANIFEST_UTILS_TRACKS_SPEC_MAX_SIZE + 1 + // 1 = - sizeof(HDS_MEDIA_HEADER_SUFFIX_DRM) - 1 + 2 * VOD_INT32_LEN + vod_base64_encoded_length(amf0_max_total_size) + sizeof(HDS_MEDIA_FOOTER) - 1) * media_count; initial_muxed_tracks = adaptation_sets.first->type == ADAPTATION_TYPE_MUXED ? MEDIA_TYPE_COUNT : 1; muxed_tracks = initial_muxed_tracks; index = 0; for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++) { if (adaptation_set->type == ADAPTATION_TYPE_MUXED) { if (adaptation_set->first[MEDIA_TYPE_AUDIO] != NULL) { result_size += adaptation_set->first[MEDIA_TYPE_AUDIO]->media_info.label.len; } } else { result_size += adaptation_set->first[0]->media_info.label.len; } for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks) { if (cur_track_ptr[0] != NULL) { cur_sequence = cur_track_ptr[0]->file_info.source->sequence; } else { cur_sequence = cur_track_ptr[1]->file_info.source->sequence; } switch (media_set->type) { case MEDIA_SET_VOD: rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, cur_sequence, // XXXXX change to work with tracks instead of sequence MEDIA_TYPE_NONE, &segment_durations[index].durations); if (rc != VOD_OK) { return rc; } hds_scale_segment_durations(&segment_durations[index]); abst_atom_size = hds_get_abst_atom_size(media_set, &segment_durations[index]); if (abst_atom_size > max_abst_atom_size) { max_abst_atom_size = abst_atom_size; } result_size += vod_base64_encoded_length(abst_atom_size); index++; break; } if (drm_enabled) { drm_metadata = &((drm_info_t*)cur_sequence->drm_info)->pssh_array.first->data; result_size += drm_metadata->len; } } muxed_tracks = 1; } // allocate the buffers result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } temp_buffer = vod_alloc(request_context->pool, vod_max(amf0_max_total_size, max_abst_atom_size)); if (temp_buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (3)"); return VOD_ALLOC_FAILED; } // print the manifest header p = vod_sprintf(result->data, HDS_MANIFEST_HEADER, manifest_id); if (base_url->len != 0) { p = vod_sprintf(p, HDS_MANIFEST_HEADER_BASE_URL, base_url); } switch (media_set->type) { case MEDIA_SET_VOD: p = vod_sprintf(p, HDS_MANIFEST_HEADER_VOD, (uint32_t)(media_set->timing.total_duration / 1000), (uint32_t)(media_set->timing.total_duration % 1000)); break; case MEDIA_SET_LIVE: p = vod_copy(p, HDS_MANIFEST_HEADER_LIVE, sizeof(HDS_MANIFEST_HEADER_LIVE) - 1); break; } if (adaptation_sets.total_count > 1) { if (adaptation_sets.first->type == ADAPTATION_TYPE_MUXED) { track = adaptation_sets.first->first[MEDIA_TYPE_AUDIO]; } else { track = adaptation_sets.first->first[0]; } p = vod_sprintf(p, HDS_MANIFEST_HEADER_LANG, &track->media_info.label, lang_get_iso639_2t_name(track->media_info.language)); } // bootstrap tags muxed_tracks = initial_muxed_tracks; tracks_array[MEDIA_TYPE_SUBTITLE] = NULL; index = 0; for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++) { for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks) { switch (media_set->type) { case MEDIA_SET_VOD: p = vod_sprintf(p, HDS_BOOTSTRAP_VOD_HEADER, index); p = hds_write_base64_abst_atom(p, temp_buffer, media_set, &segment_durations[index]); p = vod_copy(p, HDS_BOOTSTRAP_VOD_FOOTER, sizeof(HDS_BOOTSTRAP_VOD_FOOTER) - 1); break; case MEDIA_SET_LIVE: // get the tracks if (muxed_tracks == 1) { if (cur_track_ptr[0]->media_info.media_type == MEDIA_TYPE_VIDEO) { tracks_array[MEDIA_TYPE_VIDEO] = cur_track_ptr[0]; tracks_array[MEDIA_TYPE_AUDIO] = NULL; } else { tracks_array[MEDIA_TYPE_VIDEO] = NULL; tracks_array[MEDIA_TYPE_AUDIO] = cur_track_ptr[0]; } tracks = tracks_array; } else { tracks = cur_track_ptr; } p = vod_sprintf(p, HDS_BOOTSTRAP_LIVE_PREFIX, index); p = vod_copy(p, conf->bootstrap_file_name_prefix.data, conf->bootstrap_file_name_prefix.len); p = manifest_utils_append_tracks_spec(p, tracks, MEDIA_TYPE_COUNT, media_set->has_multi_sequences); p = vod_copy(p, HDS_BOOTSTRAP_LIVE_SUFFIX, sizeof(HDS_BOOTSTRAP_LIVE_SUFFIX) - 1); break; } index++; } muxed_tracks = 1; } if (drm_enabled) { muxed_tracks = initial_muxed_tracks; index = 0; for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++) { for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks) { if (cur_track_ptr[0] != NULL) { cur_sequence = cur_track_ptr[0]->file_info.source->sequence; } else { cur_sequence = cur_track_ptr[1]->file_info.source->sequence; } drm_metadata = &((drm_info_t*)cur_sequence->drm_info)->pssh_array.first->data; p = vod_sprintf(p, HDS_DRM_ADDITIONAL_HEADER_PREFIX, index); p = vod_copy(p, drm_metadata->data, drm_metadata->len); p = vod_copy(p, HDS_DRM_ADDITIONAL_HEADER_SUFFIX, sizeof(HDS_DRM_ADDITIONAL_HEADER_SUFFIX) - 1); index++; } muxed_tracks = 1; } } // media tags muxed_tracks = initial_muxed_tracks; index = 0; for (adaptation_set = adaptation_sets.first; adaptation_set < adaptation_sets.last; adaptation_set++) { for (cur_track_ptr = adaptation_set->first; cur_track_ptr < adaptation_set->last; cur_track_ptr += muxed_tracks) { // get the tracks if (muxed_tracks == 1) { if (cur_track_ptr[0]->media_info.media_type == MEDIA_TYPE_VIDEO) { tracks_array[MEDIA_TYPE_VIDEO] = cur_track_ptr[0]; tracks_array[MEDIA_TYPE_AUDIO] = NULL; } else { tracks_array[MEDIA_TYPE_VIDEO] = NULL; tracks_array[MEDIA_TYPE_AUDIO] = cur_track_ptr[0]; } tracks = tracks_array; } else { tracks = cur_track_ptr; } if (tracks[MEDIA_TYPE_VIDEO] != NULL) { bitrate = tracks[MEDIA_TYPE_VIDEO]->media_info.bitrate; if (tracks[MEDIA_TYPE_AUDIO] != NULL) { bitrate += tracks[MEDIA_TYPE_AUDIO]->media_info.bitrate; } p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_VIDEO, bitrate / 1000, (uint32_t)tracks[MEDIA_TYPE_VIDEO]->media_info.u.video.width, (uint32_t)tracks[MEDIA_TYPE_VIDEO]->media_info.u.video.height); } else { bitrate = tracks[MEDIA_TYPE_AUDIO]->media_info.bitrate; if (adaptation_sets.total_count > 1 && adaptation_set > adaptation_sets.first) { p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO_LANG, bitrate / 1000, &tracks[MEDIA_TYPE_AUDIO]->media_info.label, lang_get_iso639_2t_name(tracks[MEDIA_TYPE_AUDIO]->media_info.language)); } else { p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO, bitrate / 1000); } } // url p = vod_copy(p, conf->fragment_file_name_prefix.data, conf->fragment_file_name_prefix.len); p = manifest_utils_append_tracks_spec(p, tracks, MEDIA_TYPE_COUNT, media_set->has_multi_sequences); *p++ = '-'; if (drm_enabled) { p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX_DRM, index, index); } else { p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX, index); } p = hds_amf0_write_base64_metadata(p, temp_buffer, media_set, tracks); p = vod_copy(p, HDS_MEDIA_FOOTER, sizeof(HDS_MEDIA_FOOTER) - 1); index++; } muxed_tracks = 1; } // manifest footer p = vod_copy(p, HDS_MANIFEST_FOOTER, sizeof(HDS_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "hds_packager_build_manifest: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } vod_free(request_context->pool, temp_buffer); return VOD_OK; }
vod_status_t hds_packager_build_manifest( request_context_t* request_context, hds_manifest_config_t* conf, vod_str_t* manifest_id, segmenter_conf_t* segmenter_conf, media_set_t* media_set, vod_str_t* result) { media_track_t** cur_sequence_tracks; media_sequence_t* cur_sequence; media_track_t* track; segment_durations_t* segment_durations; uint32_t bitrate; uint32_t index; uint32_t abst_atom_size; uint32_t max_abst_atom_size = 0; size_t result_size; vod_status_t rc; u_char* temp_buffer; u_char* p; segment_durations = vod_alloc( request_context->pool, media_set->sequence_count * sizeof(*segment_durations)); if (segment_durations == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } // calculate the result size result_size = sizeof(HDS_MANIFEST_HEADER) - 1 + 2 * VOD_INT32_LEN + manifest_id->len + sizeof(HDS_MANIFEST_FOOTER); index = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track; rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, media_set, cur_sequence, MEDIA_TYPE_NONE, &segment_durations[index]); if (rc != VOD_OK) { return rc; } abst_atom_size = ABST_BASE_ATOM_SIZE + (segment_durations[index].item_count + 1) * sizeof(afrt_entry_t); if (abst_atom_size > max_abst_atom_size) { max_abst_atom_size = abst_atom_size; } result_size += sizeof(HDS_BOOTSTRAP_HEADER) - 1 + VOD_INT32_LEN + vod_base64_encoded_length(abst_atom_size) + sizeof(HDS_BOOTSTRAP_FOOTER) - 1; result_size += sizeof(HDS_MEDIA_HEADER_PREFIX_VIDEO) - 1 + 3 * VOD_INT32_LEN + conf->fragment_file_name_prefix.len + sizeof("-f-v-a-") - 1 + 3 * VOD_INT32_LEN + sizeof(HDS_MEDIA_HEADER_SUFFIX) - 1 + VOD_INT32_LEN + vod_base64_encoded_length(amf0_max_total_size) + sizeof(HDS_MEDIA_FOOTER) - 1; index++; } // allocate the buffers result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } temp_buffer = vod_alloc(request_context->pool, vod_max(amf0_max_total_size, max_abst_atom_size)); if (temp_buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_packager_build_manifest: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } // print the manifest header p = vod_sprintf(result->data, HDS_MANIFEST_HEADER, manifest_id, (uint32_t)(media_set->total_duration / 1000), (uint32_t)(media_set->total_duration % 1000)); // bootstrap tags index = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track; p = vod_sprintf(p, HDS_BOOTSTRAP_HEADER, index); p = hds_write_base64_abst_atom(p, temp_buffer, &segment_durations[index]); p = vod_copy(p, HDS_BOOTSTRAP_FOOTER, sizeof(HDS_BOOTSTRAP_FOOTER) - 1); index++; } // media tags index = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_sequence_tracks = cur_sequence->filtered_clips[0].longest_track; if (cur_sequence_tracks[MEDIA_TYPE_VIDEO] != NULL) { track = cur_sequence_tracks[MEDIA_TYPE_VIDEO]; bitrate = track->media_info.bitrate; if (cur_sequence_tracks[MEDIA_TYPE_AUDIO] != NULL) { bitrate += cur_sequence_tracks[MEDIA_TYPE_AUDIO]->media_info.bitrate; } p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_VIDEO, bitrate / 1000, (uint32_t)track->media_info.u.video.width, (uint32_t)track->media_info.u.video.height); } else { track = cur_sequence_tracks[MEDIA_TYPE_AUDIO]; p = vod_sprintf(p, HDS_MEDIA_HEADER_PREFIX_AUDIO, track->media_info.bitrate / 1000); } // url p = vod_copy(p, conf->fragment_file_name_prefix.data, conf->fragment_file_name_prefix.len); if (media_set->has_multi_sequences) { p = vod_sprintf(p, "-f%uD", cur_sequence->index + 1); } if (cur_sequence_tracks[MEDIA_TYPE_VIDEO] != NULL) { p = vod_sprintf(p, "-v%uD", cur_sequence_tracks[MEDIA_TYPE_VIDEO]->index + 1); } if (cur_sequence_tracks[MEDIA_TYPE_AUDIO] != NULL) { p = vod_sprintf(p, "-a%uD", cur_sequence_tracks[MEDIA_TYPE_AUDIO]->index + 1); } *p++ = '-'; p = vod_sprintf(p, HDS_MEDIA_HEADER_SUFFIX, index++); p = hds_amf0_write_base64_metadata(p, temp_buffer, cur_sequence_tracks); p = vod_copy(p, HDS_MEDIA_FOOTER, sizeof(HDS_MEDIA_FOOTER) - 1); } // manifest footer p = vod_copy(p, HDS_MANIFEST_FOOTER, sizeof(HDS_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "hds_packager_build_manifest: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } vod_free(request_context->pool, temp_buffer); return VOD_OK; }
} static vod_inline u_char* hds_amf0_append_boolean(u_char* p, bool_t value) { *p++ = AMF0_TYPE_BOOLEAN; *p++ = (value ? 0x01 : 0x00); return p; } static vod_inline u_char* hds_amf0_append_raw_string(u_char* p, const vod_str_t* str) { *p++ = (str->len >> 8) & 0xFF; *p++ = str->len & 0xFF; p = vod_copy(p, str->data, str->len); return p; } static vod_inline u_char* hds_amf0_append_string(u_char* p, const vod_str_t* str) { *p++ = AMF0_TYPE_STRING; return hds_amf0_append_raw_string(p, str); } static vod_inline u_char* hds_amf0_append_array_header(u_char* p, uint32_t count) { *p++ = AMF0_TYPE_ECMA_ARRAY; write_be32(p, count);
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; }
static u_char* dash_packager_write_mpd_period( u_char* p, u_char* base_url_temp_buffer, segment_durations_t* segment_durations, segment_duration_item_t** cur_duration_items, uint32_t clip_index, dash_manifest_config_t* conf, vod_str_t* base_url, segmenter_conf_t* segmenter_conf, media_set_t* media_set, write_tags_callback_t write_representation_tags, void* representation_tags_writer_context) { media_clip_filtered_t* cur_clip; media_sequence_t* cur_sequence; media_track_t* last_track; media_track_t* cur_track; vod_str_t representation_id; u_char representation_id_buffer[MAX_TRACK_SPEC_LENGTH]; uint32_t filtered_clip_index; uint32_t max_width = 0; uint32_t max_height = 0; uint32_t max_framerate_duration = 0; uint32_t max_framerate_timescale = 0; uint32_t segment_count = 0; representation_id.data = representation_id_buffer; if (media_set->use_discontinuity) { p = vod_sprintf(p, VOD_DASH_MANIFEST_PERIOD_DURATION_HEADER, clip_index, media_set->durations[clip_index] / 1000, media_set->durations[clip_index] % 1000); } else { p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_HEADER, sizeof(VOD_DASH_MANIFEST_PERIOD_HEADER) - 1); } // Note: clip_index can be greater than clip count when consistentSequenceMediaInfo is true filtered_clip_index = clip_index < media_set->clip_count ? clip_index : 0; // video adaptation set if (media_set->track_count[MEDIA_TYPE_VIDEO] != 0) { // get the max width, height and frame rate cur_track = media_set->filtered_tracks + filtered_clip_index * media_set->total_track_count; last_track = cur_track + media_set->total_track_count; for (; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } if (cur_track->media_info.u.video.width > max_width) { max_width = cur_track->media_info.u.video.width; } if (cur_track->media_info.u.video.height > max_height) { max_height = cur_track->media_info.u.video.height; } if (max_framerate_duration == 0 || cur_track->media_info.timescale * max_framerate_duration > max_framerate_timescale * cur_track->media_info.min_frame_duration) { max_framerate_duration = cur_track->media_info.min_frame_duration; max_framerate_timescale = cur_track->media_info.timescale; } } // print the header p = vod_sprintf(p, VOD_DASH_MANIFEST_VIDEO_HEADER, max_width, max_height, (uint32_t)max_framerate_timescale / max_framerate_duration, (uint32_t)(((uint64_t)max_framerate_timescale * 1000) / max_framerate_duration % 1000)); // print the segment template switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: p = dash_packager_write_segment_template( p, conf, segmenter_conf, base_url); break; case FORMAT_SEGMENT_TIMELINE: p = dash_packager_write_segment_timeline( p, conf, &segment_durations[MEDIA_TYPE_VIDEO], &cur_duration_items[MEDIA_TYPE_VIDEO], base_url); break; case FORMAT_SEGMENT_LIST: if (media_set->use_discontinuity) { segment_count = dash_packager_get_cur_clip_segment_count( &segment_durations[MEDIA_TYPE_VIDEO], &cur_duration_items[MEDIA_TYPE_VIDEO]); } else { segment_count = segment_durations[MEDIA_TYPE_VIDEO].segment_count; } break; } // print the representations for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_clip = &cur_sequence->filtered_clips[filtered_clip_index]; last_track = cur_clip->last_track; for (cur_track = cur_clip->first_track; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } dash_packager_get_track_spec( &representation_id, media_set, clip_index, cur_sequence->index, cur_track->index, 'v'); p = vod_sprintf(p, VOD_DASH_MANIFEST_VIDEO_PREFIX, &representation_id, &cur_track->media_info.codec_name, (uint32_t)cur_track->media_info.u.video.width, (uint32_t)cur_track->media_info.u.video.height, (uint32_t)(cur_track->media_info.timescale / cur_track->media_info.min_frame_duration), (uint32_t)(((uint64_t)cur_track->media_info.timescale * 1000) / cur_track->media_info.min_frame_duration % 1000), cur_track->media_info.bitrate ); if (conf->manifest_format == FORMAT_SEGMENT_LIST) { p = dash_packager_write_segment_list( p, conf, media_set, segmenter_conf, base_url, base_url_temp_buffer, clip_index, cur_sequence, cur_track, segment_count); } // write any additional tags if (write_representation_tags != NULL) { p = write_representation_tags(representation_tags_writer_context, p, cur_track); } p = vod_copy(p, VOD_DASH_MANIFEST_VIDEO_SUFFIX, sizeof(VOD_DASH_MANIFEST_VIDEO_SUFFIX) - 1); } } // print the footer p = vod_copy(p, VOD_DASH_MANIFEST_VIDEO_FOOTER, sizeof(VOD_DASH_MANIFEST_VIDEO_FOOTER) - 1); } // audio adaptation set if (media_set->track_count[MEDIA_TYPE_AUDIO] != 0) { // print the header p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_HEADER, sizeof(VOD_DASH_MANIFEST_AUDIO_HEADER) - 1); // print the segment template switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: p = dash_packager_write_segment_template( p, conf, segmenter_conf, base_url); break; case FORMAT_SEGMENT_TIMELINE: p = dash_packager_write_segment_timeline( p, conf, &segment_durations[MEDIA_TYPE_AUDIO], &cur_duration_items[MEDIA_TYPE_AUDIO], base_url); break; case FORMAT_SEGMENT_LIST: if (media_set->use_discontinuity) { segment_count = dash_packager_get_cur_clip_segment_count( &segment_durations[MEDIA_TYPE_AUDIO], &cur_duration_items[MEDIA_TYPE_AUDIO]); } else { segment_count = segment_durations[MEDIA_TYPE_AUDIO].segment_count; } break; } // print the representations for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { cur_clip = &cur_sequence->filtered_clips[filtered_clip_index]; last_track = cur_clip->last_track; for (cur_track = cur_clip->first_track; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != MEDIA_TYPE_AUDIO) { continue; } dash_packager_get_track_spec( &representation_id, media_set, clip_index, cur_sequence->index, cur_track->index, 'a'); p = vod_sprintf(p, VOD_DASH_MANIFEST_AUDIO_PREFIX, &representation_id, &cur_track->media_info.codec_name, cur_track->media_info.u.audio.sample_rate, cur_track->media_info.bitrate); if (conf->manifest_format == FORMAT_SEGMENT_LIST) { p = dash_packager_write_segment_list( p, conf, media_set, segmenter_conf, base_url, base_url_temp_buffer, clip_index, cur_sequence, cur_track, segment_count); } // write any additional tags if (write_representation_tags != NULL) { p = write_representation_tags(representation_tags_writer_context, p, cur_track); } p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_SUFFIX, sizeof(VOD_DASH_MANIFEST_AUDIO_SUFFIX) - 1); } } // print the footer p = vod_copy(p, VOD_DASH_MANIFEST_AUDIO_FOOTER, sizeof(VOD_DASH_MANIFEST_AUDIO_FOOTER) - 1); } p = vod_copy(p, VOD_DASH_MANIFEST_PERIOD_FOOTER, sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1); return p; }
static u_char* dash_packager_write_segment_list( u_char* p, dash_manifest_config_t* conf, media_set_t* media_set, segmenter_conf_t* segmenter_conf, vod_str_t* base_url, u_char* base_url_temp_buffer, uint32_t clip_index, media_sequence_t* cur_sequence, media_track_t* cur_track, uint32_t segment_count) { int media_type_char = cur_track->media_info.media_type == MEDIA_TYPE_VIDEO ? 'v' : 'a'; vod_str_t track_spec; vod_str_t cur_base_url; u_char track_spec_buffer[MAX_TRACK_SPEC_LENGTH]; uint32_t sequence_index = cur_sequence->index; uint32_t i; track_spec.data = track_spec_buffer; // build the base url if (base_url->len != 0) { cur_base_url.data = base_url_temp_buffer; base_url_temp_buffer = vod_copy(base_url_temp_buffer, base_url->data, base_url->len); if (cur_track->file_info.uri.len != 0) { base_url_temp_buffer = vod_copy(base_url_temp_buffer, 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 { base_url_temp_buffer = vod_copy(base_url_temp_buffer, media_set->uri.data, media_set->uri.len); } *base_url_temp_buffer++ = '/'; cur_base_url.len = base_url_temp_buffer - cur_base_url.data; } else { cur_base_url.data = NULL; cur_base_url.len = 0; } // get the track specification dash_packager_get_track_spec( &track_spec, media_set, clip_index, sequence_index, cur_track->index, media_type_char); // write the header p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_LIST_HEADER, segmenter_conf->segment_duration, &cur_base_url, &conf->init_file_name_prefix, &track_spec); // write the urls for (i = 0; i < segment_count; i++) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_URL, &cur_base_url, &conf->fragment_file_name_prefix, i + 1, &track_spec); } p = vod_copy(p, VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER, sizeof(VOD_DASH_MANIFEST_SEGMENT_LIST_FOOTER) - 1); return p; }
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 dash_packager_build_fragment_header( request_context_t* request_context, media_set_t* media_set, uint32_t segment_index, uint32_t sample_description_index, dash_fragment_header_extensions_t* extensions, bool_t size_only, vod_str_t* result, size_t* total_fragment_size) { media_sequence_t* sequence = &media_set->sequences[0]; media_track_t* first_track = sequence->filtered_clips[0].first_track; uint64_t earliest_pres_time = dash_packager_get_earliest_pres_time(media_set, first_track); sidx_params_t sidx_params; size_t first_frame_offset; size_t mdat_atom_size; size_t trun_atom_size; size_t tfhd_atom_size; size_t moof_atom_size; size_t traf_atom_size; size_t result_size; u_char* p; // calculate sizes dash_packager_init_sidx_params(media_set, sequence, &sidx_params); mdat_atom_size = ATOM_HEADER_SIZE + sequence->total_frame_size; if (extensions->mdat_prefix_writer != NULL) { mdat_atom_size += extensions->mdat_prefix_writer->atom_size; } trun_atom_size = mp4_builder_get_trun_atom_size(first_track->media_info.media_type, sequence->total_frame_count); tfhd_atom_size = ATOM_HEADER_SIZE + sizeof(tfhd_atom_t); if (sample_description_index > 0) { tfhd_atom_size += sizeof(uint32_t); } traf_atom_size = ATOM_HEADER_SIZE + tfhd_atom_size + ATOM_HEADER_SIZE + (earliest_pres_time > UINT_MAX ? sizeof(tfdt64_atom_t) : sizeof(tfdt_atom_t)) + trun_atom_size + extensions->extra_traf_atoms_size; moof_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(mfhd_atom_t)+ traf_atom_size; *total_fragment_size = sizeof(styp_atom) + ATOM_HEADER_SIZE + (sidx_params.earliest_pres_time > UINT_MAX ? sizeof(sidx64_atom_t) : sizeof(sidx_atom_t)) + moof_atom_size + mdat_atom_size; result_size = *total_fragment_size - sequence->total_frame_size; // head request optimization if (size_only) { return VOD_OK; } // allocate the buffer p = vod_alloc(request_context->pool, result_size); if (p == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "dash_packager_build_fragment_header: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->data = p; // styp p = vod_copy(p, styp_atom, sizeof(styp_atom)); // sidx if (sidx_params.earliest_pres_time > UINT_MAX) { p = dash_packager_write_sidx64_atom(p, &sidx_params, moof_atom_size + mdat_atom_size); } else { p = dash_packager_write_sidx_atom(p, &sidx_params, moof_atom_size + mdat_atom_size); } // moof write_atom_header(p, moof_atom_size, 'm', 'o', 'o', 'f'); // moof.mfhd p = mp4_builder_write_mfhd_atom(p, segment_index); // moof.traf write_atom_header(p, traf_atom_size, 't', 'r', 'a', 'f'); // moof.traf.tfhd p = dash_packager_write_tfhd_atom(p, first_track->media_info.track_id, sample_description_index); // moof.traf.tfdt if (earliest_pres_time > UINT_MAX) { p = dash_packager_write_tfdt64_atom(p, earliest_pres_time); } else { p = dash_packager_write_tfdt_atom(p, (uint32_t)earliest_pres_time); } // moof.traf.trun first_frame_offset = moof_atom_size + ATOM_HEADER_SIZE; if (extensions->mdat_prefix_writer != NULL) { first_frame_offset += extensions->mdat_prefix_writer->atom_size; } p = mp4_builder_write_trun_atom( p, sequence, first_frame_offset); // moof.traf.xxx if (extensions->write_extra_traf_atoms_callback != NULL) { p = extensions->write_extra_traf_atoms_callback(extensions->write_extra_traf_atoms_context, p, moof_atom_size + ATOM_HEADER_SIZE); } // mdat write_atom_header(p, mdat_atom_size, 'm', 'd', 'a', 't'); // mdat prefix if (extensions->mdat_prefix_writer != NULL) { p = extensions->mdat_prefix_writer->write(extensions->mdat_prefix_writer->context, p); } result->len = p - result->data; if (result->len != result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "dash_packager_build_fragment_header: result length %uz exceeded allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
static u_char* dash_packager_init_mp4_write( u_char* p, request_context_t* request_context, media_set_t* media_set, init_mp4_sizes_t* sizes, atom_writer_t* extra_moov_atoms_writer, atom_writer_t* stsd_atom_writer) { media_track_t* first_track = media_set->sequences[0].filtered_clips[0].first_track; // ftyp p = vod_copy(p, ftyp_atom, sizeof(ftyp_atom)); // moov write_atom_header(p, sizes->moov_atom_size, 'm', 'o', 'o', 'v'); // moov.mvhd if (media_set->sequences[0].filtered_clips[0].mvhd_atom.ptr != NULL) { // TODO: adjust the mvhd duration (can change in case of clipping or sequencing) p = vod_copy_atom(p, media_set->sequences[0].filtered_clips[0].mvhd_atom); } else if (media_set->total_duration > UINT_MAX) { p = dash_packager_write_mvhd64_atom(p, 1000, media_set->total_duration); } else { p = dash_packager_write_mvhd_atom(p, 1000, media_set->total_duration); } // moov.mvex write_atom_header(p, sizes->mvex_atom_size, 'm', 'v', 'e', 'x'); // moov.mvex.trex p = dash_packager_write_trex_atom(p, first_track->media_info.track_id); // moov.trak write_atom_header(p, sizes->track_sizes.track_trak_size, 't', 'r', 'a', 'k'); p = vod_copy_atom(p, first_track->raw_atoms[RTA_TKHD]); // moov.trak.mdia write_atom_header(p, sizes->track_sizes.track_mdia_size, 'm', 'd', 'i', 'a'); p = vod_copy_atom(p, first_track->raw_atoms[RTA_MDHD]); p = vod_copy_atom(p, first_track->raw_atoms[RTA_HDLR]); // moov.trak.minf write_atom_header(p, sizes->track_sizes.track_minf_size, 'm', 'i', 'n', 'f'); switch (first_track->media_info.media_type) { case MEDIA_TYPE_VIDEO: p = vod_copy(p, vmhd_atom, sizeof(vmhd_atom)); break; case MEDIA_TYPE_AUDIO: p = vod_copy(p, smhd_atom, sizeof(smhd_atom)); break; } p = vod_copy_atom(p, first_track->raw_atoms[RTA_DINF]); // moov.trak.minf.stbl write_atom_header(p, sizes->track_sizes.track_stbl_size, 's', 't', 'b', 'l'); if (stsd_atom_writer != NULL) { p = stsd_atom_writer->write(stsd_atom_writer->context, p); } else { p = vod_copy_atom(p, first_track->raw_atoms[RTA_STSD]); } p = vod_copy(p, fixed_stbl_atoms, sizeof(fixed_stbl_atoms)); // moov.xxx if (extra_moov_atoms_writer != NULL) { p = extra_moov_atoms_writer->write(extra_moov_atoms_writer->context, p); } return p; }
vod_status_t mss_packager_build_manifest( request_context_t* request_context, segmenter_conf_t* segmenter_conf, mpeg_metadata_t* mpeg_metadata, size_t extra_tags_size, mss_write_tags_callback_t write_extra_tags, void* extra_tags_writer_context, vod_str_t* result) { mpeg_stream_metadata_t* cur_stream; segment_durations_t segment_durations[MEDIA_TYPE_COUNT]; uint64_t duration_100ns; uint32_t media_type; uint32_t stream_index; uint32_t bitrate; vod_status_t rc; size_t result_size; u_char* p; // calculate the result size result_size = sizeof(MSS_MANIFEST_HEADER) - 1 + VOD_INT64_LEN + extra_tags_size + sizeof(MSS_MANIFEST_FOOTER); for (media_type = 0; media_type < MEDIA_TYPE_COUNT; media_type++) { if (mpeg_metadata->longest_stream[media_type] == NULL) { continue; } rc = segmenter_conf->get_segment_durations( request_context, segmenter_conf, &mpeg_metadata->longest_stream[media_type], 1, &segment_durations[media_type]); if (rc != VOD_OK) { return rc; } result_size += sizeof(MSS_STREAM_INDEX_HEADER) - 1 + 2 * sizeof(MSS_STREAM_TYPE_VIDEO) + 2 * VOD_INT32_LEN + sizeof(MSS_STREAM_INDEX_FOOTER); result_size += segment_durations[media_type].segment_count * (sizeof(MSS_CHUNK_TAG) + VOD_INT32_LEN + VOD_INT64_LEN); } for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++) { switch (cur_stream->media_info.media_type) { case MEDIA_TYPE_VIDEO: result_size += sizeof(MSS_VIDEO_QUALITY_LEVEL_HEADER) - 1 + 4 * VOD_INT32_LEN + cur_stream->media_info.extra_data_size * 2 + sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1; break; case MEDIA_TYPE_AUDIO: result_size += sizeof(MSS_AUDIO_QUALITY_LEVEL_HEADER) - 1 + 6 * VOD_INT32_LEN + cur_stream->media_info.extra_data_size * 2 + sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1; break; } } // allocate the result result->data = vod_alloc(request_context->pool, result_size); if (result->data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "mss_packager_build_manifest: vod_alloc failed"); return VOD_ALLOC_FAILED; } duration_100ns = rescale_time(mpeg_metadata->duration, mpeg_metadata->timescale, MSS_TIMESCALE); p = vod_sprintf(result->data, MSS_MANIFEST_HEADER, duration_100ns); if (mpeg_metadata->longest_stream[MEDIA_TYPE_VIDEO] != NULL) { p = vod_sprintf(p, MSS_STREAM_INDEX_HEADER, MSS_STREAM_TYPE_VIDEO, mpeg_metadata->stream_count[MEDIA_TYPE_VIDEO], segment_durations[MEDIA_TYPE_VIDEO].segment_count, MSS_STREAM_TYPE_VIDEO); stream_index = 0; for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++) { if (cur_stream->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } bitrate = cur_stream->media_info.bitrate; bitrate = mss_encode_indexes(bitrate, cur_stream->file_info.file_index, cur_stream->track_index); p = vod_sprintf(p, MSS_VIDEO_QUALITY_LEVEL_HEADER, stream_index++, bitrate, (uint32_t)cur_stream->media_info.u.video.width, (uint32_t)cur_stream->media_info.u.video.height); p = mss_append_hex_string(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size); p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1); } p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_VIDEO]); p = vod_copy(p, MSS_STREAM_INDEX_FOOTER, sizeof(MSS_STREAM_INDEX_FOOTER) - 1); } if (mpeg_metadata->longest_stream[MEDIA_TYPE_AUDIO] != NULL) { p = vod_sprintf(p, MSS_STREAM_INDEX_HEADER, MSS_STREAM_TYPE_AUDIO, mpeg_metadata->stream_count[MEDIA_TYPE_AUDIO], segment_durations[MEDIA_TYPE_AUDIO].segment_count, MSS_STREAM_TYPE_AUDIO); stream_index = 0; for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++) { if (cur_stream->media_info.media_type != MEDIA_TYPE_AUDIO) { continue; } bitrate = cur_stream->media_info.bitrate; bitrate = mss_encode_indexes(bitrate, cur_stream->file_info.file_index, cur_stream->track_index); p = vod_sprintf(p, MSS_AUDIO_QUALITY_LEVEL_HEADER, stream_index++, bitrate, cur_stream->media_info.u.audio.sample_rate, (uint32_t)cur_stream->media_info.u.audio.channels, (uint32_t)cur_stream->media_info.u.audio.bits_per_sample, (uint32_t)cur_stream->media_info.u.audio.packet_size); p = mss_append_hex_string(p, cur_stream->media_info.extra_data, cur_stream->media_info.extra_data_size); p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1); } p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_AUDIO]); p = vod_copy(p, MSS_STREAM_INDEX_FOOTER, sizeof(MSS_STREAM_INDEX_FOOTER) - 1); } if (write_extra_tags != NULL) { p = write_extra_tags(extra_tags_writer_context, p, mpeg_metadata); } p = vod_copy(p, MSS_MANIFEST_FOOTER, sizeof(MSS_MANIFEST_FOOTER) - 1); result->len = p - result->data; if (result->len > result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "mss_packager_build_manifest: 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; }