static u_char* mss_write_manifest_chunks_live(u_char* p, 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 repeat_count; bool_t first_time = TRUE; for (cur_item = segment_durations->items; cur_item < last_item; cur_item++) { repeat_count = cur_item->repeat_count; // output the timestamp in the first chunk if (first_time) { p = vod_sprintf(p, MSS_CHUNK_TAG_LIVE_FIRST, mss_rescale_millis(segment_durations->start_time), rescale_time(cur_item->duration, segment_durations->timescale, MSS_TIMESCALE)); repeat_count--; first_time = FALSE; } // output only the duration in subsequent chunks for (; repeat_count > 0; repeat_count--) { p = vod_sprintf(p, MSS_CHUNK_TAG_LIVE, rescale_time(cur_item->duration, segment_durations->timescale, MSS_TIMESCALE)); } } return p; }
static u_char* hds_calculate_output_offsets_and_write_afra_entries( hds_muxer_state_t* state, uint32_t initial_value, uint32_t afra_entries_base, u_char* p) { hds_muxer_stream_state_t* selected_stream; hds_muxer_stream_state_t* cur_stream; uint32_t cur_offset = initial_value; for (;;) { // choose a stream selected_stream = hds_muxer_choose_stream(state); if (selected_stream == NULL) { break; } // video key frames start with the codec info if (selected_stream->media_type == MEDIA_TYPE_VIDEO && selected_stream->cur_frame->key_frame) { p = hds_write_afra_atom_entry(p, rescale_time(selected_stream->next_frame_dts, selected_stream->timescale, HDS_TIMESCALE), cur_offset + afra_entries_base); cur_offset += state->codec_config_size; } // skip the tag size cur_offset += tag_size_by_media_type[selected_stream->media_type]; // set the offset (points to the beginning of the actual data) *selected_stream->cur_frame_output_offset = cur_offset; selected_stream->cur_frame_output_offset++; // move to the end of the frame cur_offset += selected_stream->cur_frame->size; cur_offset += sizeof(uint32_t); // move to the next frame selected_stream->next_frame_time_offset += selected_stream->cur_frame->duration; selected_stream->next_frame_dts = rescale_time(selected_stream->next_frame_time_offset, selected_stream->timescale, HDS_TIMESCALE); selected_stream->cur_frame++; } // reset the state for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++) { cur_stream->cur_frame = cur_stream->first_frame; cur_stream->cur_frame_output_offset = cur_stream->first_frame_output_offset; cur_stream->next_frame_time_offset = cur_stream->first_frame_time_offset; cur_stream->next_frame_dts = rescale_time(cur_stream->next_frame_time_offset, cur_stream->timescale, HDS_TIMESCALE); } 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 void mss_get_segment_timing_info(media_sequence_t* sequence, segment_timing_info_t* result) { media_clip_filtered_t* cur_clip; media_track_t* track; cur_clip = sequence->filtered_clips; track = cur_clip->first_track; result->timestamp = rescale_time(track->first_frame_time_offset, track->media_info.timescale, MSS_TIMESCALE) + mss_rescale_millis(track->clip_start_time); result->duration = rescale_time(track->total_frames_duration, track->media_info.timescale, MSS_TIMESCALE); cur_clip++; for (; cur_clip < sequence->filtered_clips_end; cur_clip++) { track = cur_clip->first_track; result->duration += rescale_time(track->total_frames_duration, track->media_info.timescale, MSS_TIMESCALE); } }
static void dash_packager_init_sidx_params(media_set_t* media_set, media_sequence_t* sequence, sidx_params_t* result) { media_clip_filtered_t* cur_clip; media_track_t* track; uint64_t earliest_pres_time; uint64_t total_frames_duration; uint32_t timescale; // initialize according to the first clip cur_clip = sequence->filtered_clips; track = cur_clip->first_track; total_frames_duration = track->total_frames_duration; earliest_pres_time = dash_packager_get_earliest_pres_time(media_set, track); timescale = track->media_info.timescale; cur_clip++; for (; cur_clip < sequence->filtered_clips_end; cur_clip++) { track = cur_clip->first_track; if (timescale > track->media_info.timescale) { // scale the track duration to timescale total_frames_duration += rescale_time(track->total_frames_duration, track->media_info.timescale, timescale); continue; } if (timescale < track->media_info.timescale) { // rescale to track->media_info.timescale total_frames_duration = rescale_time(total_frames_duration, timescale, track->media_info.timescale); earliest_pres_time = rescale_time(earliest_pres_time, timescale, track->media_info.timescale); timescale = track->media_info.timescale; } // same timescale, just add total_frames_duration += track->total_frames_duration; } result->total_frames_duration = total_frames_duration; result->earliest_pres_time = earliest_pres_time; result->timescale = timescale; }
static void hds_scale_segment_durations(hds_segment_durations_t* segments) { segment_durations_t* segment_durations = &segments->durations; segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count; segment_duration_item_t* cur_item; segments->zero_segments = 0; segment_durations->end_time = hds_rescale_millis(segment_durations->end_time); for (cur_item = segment_durations->items; cur_item < last_item; cur_item++) { cur_item->time = segment_durations->timescale == 1000 ? hds_rescale_millis(cur_item->time) : // special case for 1000 to prevent overflow in time rescale_time(cur_item->time, segment_durations->timescale, HDS_TIMESCALE); cur_item->duration = rescale_time(cur_item->duration, segment_durations->timescale, HDS_TIMESCALE); if (cur_item->duration == 0) { segments->zero_segments++; } } }
void rate_filter_scale_track_timestamps( media_track_t* track, uint32_t speed_nom, uint32_t speed_denom) { input_frame_t* last_frame; input_frame_t* cur_frame; frame_list_part_t* part; // TODO: remove this (added temporarily in order to avoid changing existing responses) if (speed_nom % 10 == 0 && speed_denom % 10 == 0) { speed_nom /= 10; speed_denom /= 10; } track->media_info.timescale *= speed_nom; track->media_info.duration *= speed_denom; track->media_info.full_duration *= speed_denom; track->media_info.duration_millis = rescale_time(track->media_info.duration, track->media_info.timescale, 1000); track->first_frame_time_offset *= speed_denom; track->total_frames_duration *= speed_denom; track->media_info.min_frame_duration *= speed_denom; if (track->media_info.media_type == MEDIA_TYPE_AUDIO) { return; // should not change the frame durations for audio, since they will be filtered by libavcodec } track->media_info.bitrate = (uint32_t)((track->total_frames_size * track->media_info.timescale * 8) / track->media_info.full_duration); part = &track->frames; last_frame = part->last_frame; for (cur_frame = part->first_frame;; cur_frame++) { if (cur_frame >= last_frame) { if (part->next == NULL) break; part = part->next; cur_frame = part->first_frame; last_frame = part->last_frame; } cur_frame->duration *= speed_denom; cur_frame->pts_delay *= speed_denom; } }
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; }
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* mss_write_manifest_chunks(u_char* p, 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 last_segment_index; uint32_t segment_index; 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; for (; segment_index < last_segment_index; segment_index++) { p = vod_sprintf(p, MSS_CHUNK_TAG, segment_index, rescale_time(cur_item->duration, segment_durations->timescale, MSS_TIMESCALE)); } } return p; }
static vod_status_t hds_muxer_init_track( hds_muxer_state_t* state, hds_muxer_stream_state_t* cur_stream, media_track_t* cur_track) { vod_status_t rc; cur_stream->track = cur_track; cur_stream->media_type = cur_track->media_info.media_type; cur_stream->timescale = cur_track->media_info.timescale; cur_stream->frames_source = cur_track->frames_source; cur_stream->frames_source_context = cur_track->frames_source_context; cur_stream->first_frame = cur_track->first_frame; cur_stream->last_frame = cur_track->last_frame; cur_stream->clip_start_time = hds_rescale_millis(cur_track->clip_start_time); cur_stream->first_frame_time_offset = cur_track->first_frame_time_offset; cur_stream->next_frame_time_offset = cur_stream->first_frame_time_offset; cur_stream->next_frame_dts = rescale_time(cur_stream->next_frame_time_offset, cur_stream->timescale, HDS_TIMESCALE); cur_stream->cur_frame = cur_stream->first_frame; cur_stream->first_frame_input_offset = cur_track->frame_offsets; cur_stream->cur_frame_input_offset = cur_stream->first_frame_input_offset; if (cur_track->media_info.media_type == MEDIA_TYPE_AUDIO) { rc = hds_get_sound_info(state->request_context, &cur_track->media_info, &cur_stream->sound_info); if (rc != VOD_OK) { vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "hds_muxer_init_track: hds_get_sound_info failed %i", rc); return rc; } } return VOD_OK; }
static u_char* hds_write_abst_atom( u_char* p, segment_durations_t* segment_durations) { segment_duration_item_t* cur_item; segment_duration_item_t* last_item = segment_durations->items + segment_durations->item_count; uint64_t start_offset = 0; uint64_t timestamp; uint32_t duration; size_t afrt_atom_size = AFRT_BASE_ATOM_SIZE; size_t asrt_atom_size = ASRT_ATOM_SIZE; size_t abst_atom_size = ABST_BASE_ATOM_SIZE; afrt_atom_size += (segment_durations->item_count + 1) * sizeof(afrt_entry_t); abst_atom_size += (segment_durations->item_count + 1) * sizeof(afrt_entry_t); // abst write_atom_header(p, abst_atom_size, 'a', 'b', 's', 't'); write_dword(p, 0); // version + flags write_dword(p, 1); // bootstrap info version *p++ = 0; // profile, live, update write_dword(p, HDS_TIMESCALE); // timescale write_dword(p, 0); // current media time - high write_dword(p, segment_durations->duration_millis); // current media time - low write_qword(p, 0LL); // smpte offset *p++ = 0; // movie identifier *p++ = 0; // server entries *p++ = 0; // quality entries *p++ = 0; // drm data *p++ = 0; // metadata *p++ = 1; // segment run table count // abst.asrt write_atom_header(p, asrt_atom_size, 'a', 's', 'r', 't'); write_dword(p, 0); // version + flags *p++ = 0; // quality entries write_dword(p, 1); // segment run entries // entry #1 write_dword(p, 1); // first segment write_dword(p, segment_durations->segment_count); // fragments per segment // abst *p++ = 1; // fragment run table count // abst.afrt write_atom_header(p, afrt_atom_size, 'a', 'f', 'r', 't'); write_dword(p, 0); // version + flags write_dword(p, HDS_TIMESCALE); // timescale *p++ = 0; // quality entries write_dword(p, segment_durations->item_count + 1); // fragment run entries // write the afrt entries for (cur_item = segment_durations->items; cur_item < last_item; cur_item++) { timestamp = rescale_time(start_offset, segment_durations->timescale, HDS_TIMESCALE); duration = rescale_time(cur_item->duration, segment_durations->timescale, HDS_TIMESCALE); write_dword(p, cur_item->segment_index + 1); // first fragment write_qword(p, timestamp); // first fragment timestamp write_dword(p, duration); // fragment duration start_offset += cur_item->duration * cur_item->repeat_count; } // last entry write_dword(p, 0); // first fragment write_qword(p, 0LL); // first fragment timestamp write_dword(p, 0); // fragment duration *p++ = 0; // discontinuity indicator (0 = end of presentation) return p; }
static u_char* dash_packager_write_segment_timeline( u_char* p, dash_manifest_config_t* conf, uint32_t start_number, uint64_t clip_start_time, u_char* clip_spec, media_track_t* reference_track, 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; uint64_t start_time; uint32_t duration; bool_t first_time = TRUE; if (segment_durations->start_time > clip_start_time) { start_time = segment_durations->start_time - clip_start_time; } else { start_time = 0; } // Note: SegmentTemplate is currently printed in the adaptation set level, so it is not possible // to mix mp4 and webm representations for the same media type p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_HEADER, base_url, &conf->fragment_file_name_prefix, &dash_codecs[reference_track->media_info.codec_id].frag_file_ext, base_url, &conf->init_file_name_prefix, clip_spec, &dash_codecs[reference_track->media_info.codec_id].init_file_ext, start_number + 1); 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; } duration = (uint32_t)rescale_time(cur_item->duration, segment_durations->timescale, 1000); if (first_time && start_time != 0) { // output the time if (cur_item->repeat_count == 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_TIME, start_time, duration); } else if (cur_item->repeat_count > 1) { p = vod_sprintf(p, VOD_DASH_MANIFEST_SEGMENT_REPEAT_TIME, start_time, duration, cur_item->repeat_count - 1); } } else { // don't output the time 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); } } first_time = FALSE; } *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; }
vod_status_t segmenter_get_segment_durations_accurate( request_context_t* request_context, segmenter_conf_t* conf, media_set_t* media_set, media_sequence_t* sequence, uint32_t media_type, segment_durations_t* result) { segmenter_boundary_iterator_context_t boundary_iterator; media_track_t* cur_track; media_track_t* last_track; media_track_t* main_track = NULL; media_track_t* longest_track = NULL; segment_duration_item_t* cur_item; media_sequence_t* sequences_end; media_sequence_t* cur_sequence; input_frame_t* last_frame; input_frame_t* cur_frame; uint64_t total_duration; uint32_t segment_index = 0; uint64_t accum_duration = 0; uint64_t segment_start = 0; uint64_t segment_limit_millis; uint64_t segment_limit; uint64_t cur_duration; uint32_t duration_millis; bool_t align_to_key_frames; SEGMENT_CHOOSE_HEADER(conf); if (media_set->durations != NULL) { // in case of a playlist fall back to estimate return segmenter_get_segment_durations_estimate( request_context, conf, media_set, sequence, media_type, result); } // get the maximum duration and main track (=first video track if exists, or first audio track otherwise) if (sequence != NULL) { cur_sequence = sequence; sequences_end = sequence + 1; } else { cur_sequence = media_set->sequences; sequences_end = media_set->sequences_end; } duration_millis = 0; for (; cur_sequence < sequences_end; cur_sequence++) { last_track = cur_sequence->filtered_clips[0].last_track; for (cur_track = cur_sequence->filtered_clips[0].first_track; cur_track < last_track; cur_track++) { if (media_type != MEDIA_TYPE_NONE && cur_track->media_info.media_type != media_type) { continue; } if (main_track == NULL || (cur_track->media_info.media_type < main_track->media_info.media_type)) { main_track = cur_track; } if (cur_track->media_info.duration_millis > duration_millis) { longest_track = cur_track; duration_millis = cur_track->media_info.duration_millis; } } } if (main_track == NULL) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_accurate: didnt get any tracks"); return VOD_UNEXPECTED; } // if the main track is not audio/video, or main track is audio and requires filtering, fall back to estimate switch (main_track->media_info.media_type) { case MEDIA_TYPE_VIDEO: break; case MEDIA_TYPE_AUDIO: if (!media_set->audio_filtering_needed) { break; } default: return segmenter_get_segment_durations_estimate( request_context, conf, media_set, sequence, media_type, result); } // get the segment count result->segment_count = conf->get_segment_count(conf, duration_millis); if (result->segment_count > MAX_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_accurate: segment count %uD is invalid", result->segment_count); return VOD_BAD_DATA; } // allocate the result buffer result->items = vod_alloc(request_context->pool, sizeof(*result->items) * result->segment_count); if (result->items == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "segmenter_get_segment_durations_accurate: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->timescale = main_track->media_info.timescale; result->discontinuities = 0; // Note: assuming a single frame list part cur_item = result->items - 1; last_frame = main_track->frames.last_frame; cur_frame = main_track->frames.first_frame; align_to_key_frames = conf->align_to_key_frames && main_track->media_info.media_type == MEDIA_TYPE_VIDEO; // bootstrap segments if (conf->bootstrap_segments_count > 0) { segment_limit = rescale_time(conf->bootstrap_segments_end[0], 1000, result->timescale); for (; cur_frame < last_frame; cur_frame++) { while (accum_duration >= segment_limit && segment_index + 1 < result->segment_count && (!align_to_key_frames || cur_frame->key_frame)) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_start = accum_duration; segment_index++; if (segment_index >= conf->bootstrap_segments_count) { goto post_bootstrap; } segment_limit = rescale_time(conf->bootstrap_segments_end[segment_index], 1000, result->timescale); } accum_duration += cur_frame->duration; } } post_bootstrap: // remaining segments segment_limit_millis = conf->bootstrap_segments_total_duration + conf->segment_duration; segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); for (; cur_frame < last_frame; cur_frame++) { while (accum_duration >= segment_limit && segment_index + 1 < result->segment_count && (!align_to_key_frames || cur_frame->key_frame)) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; segment_limit_millis += conf->segment_duration; segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); } accum_duration += cur_frame->duration; } // in case the main video track is shorter than the audio track, add the estimated durations of the remaining audio-only segments if (main_track->media_info.duration_millis < duration_millis && !align_to_key_frames) { segmenter_boundary_iterator_init(&boundary_iterator, conf, result->segment_count); segmenter_boundary_iterator_skip(&boundary_iterator, segment_index); total_duration = rescale_time(longest_track->media_info.duration, longest_track->media_info.timescale, result->timescale); while (accum_duration < total_duration && segment_index + 1 < result->segment_count) { segment_limit_millis = segmenter_boundary_iterator_next(&boundary_iterator); segment_limit = rescale_time(segment_limit_millis, 1000, result->timescale); segment_limit = vod_min(segment_limit, total_duration); accum_duration = segment_limit; cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; } accum_duration = total_duration; } // add the last segment / empty segments after the last keyframe (in case align to key frames is on) while (segment_index < result->segment_count) { // get the current duration and update to array cur_duration = accum_duration - segment_start; if (cur_item < result->items || cur_duration != cur_item->duration) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = cur_duration; cur_item->discontinuity = FALSE; } cur_item->repeat_count++; // move to the next segment segment_index++; segment_start = accum_duration; } result->item_count = cur_item + 1 - result->items; // remove any empty segments from the end if (result->item_count > 0 && cur_item->duration == 0) { result->item_count--; result->segment_count -= cur_item->repeat_count; } result->start_time = 0; result->end_time = duration_millis; return VOD_OK; }
static vod_status_t hds_muxer_init_state( request_context_t* request_context, mpeg_metadata_t *mpeg_metadata, read_cache_state_t* read_cache_state, write_callback_t write_callback, void* write_context, hds_muxer_state_t** result) { mpeg_stream_metadata_t* cur_stream; hds_muxer_stream_state_t* stream_state; hds_muxer_state_t* state; vod_status_t rc; // allocate the state and stream states state = vod_alloc(request_context->pool, sizeof(*state)); if (state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_muxer_init_state: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } state->first_stream = vod_alloc( request_context->pool, sizeof(state->first_stream[0]) * mpeg_metadata->streams.nelts); if (state->first_stream == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_muxer_init_state: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } state->last_stream = state->first_stream + mpeg_metadata->streams.nelts; state->request_context = request_context; state->cur_frame = NULL; state->read_cache_state = read_cache_state; write_buffer_init(&state->write_buffer_state, request_context, write_callback, write_context); stream_state = state->first_stream; for (cur_stream = mpeg_metadata->first_stream; cur_stream < mpeg_metadata->last_stream; cur_stream++, stream_state++) { // initialize the stream stream_state->metadata = cur_stream; stream_state->media_type = cur_stream->media_info.media_type; stream_state->timescale = cur_stream->media_info.timescale; stream_state->first_frame = cur_stream->frames; stream_state->last_frame = cur_stream->frames + cur_stream->frame_count; stream_state->first_frame_time_offset = cur_stream->first_frame_time_offset; stream_state->next_frame_time_offset = cur_stream->first_frame_time_offset; stream_state->next_frame_dts = rescale_time(stream_state->next_frame_time_offset, stream_state->timescale, HDS_TIMESCALE); stream_state->cur_frame = stream_state->first_frame; stream_state->first_frame_input_offset = cur_stream->frame_offsets; stream_state->cur_frame_input_offset = stream_state->first_frame_input_offset; stream_state->first_frame_output_offset = vod_alloc( request_context->pool, cur_stream->frame_count * sizeof(uint32_t)); if (stream_state->first_frame_output_offset == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_muxer_init_state: vod_alloc failed (3)"); return VOD_ALLOC_FAILED; } stream_state->cur_frame_output_offset = stream_state->first_frame_output_offset; if (cur_stream->media_info.media_type == MEDIA_TYPE_AUDIO) { rc = hds_get_sound_info(request_context, &cur_stream->media_info, &stream_state->sound_info); if (rc != VOD_OK) { vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hds_muxer_init_state: hds_get_sound_info failed %i", rc); return rc; } } } *result = state; return VOD_OK; }
vod_status_t mss_packager_build_fragment_header( request_context_t* request_context, mpeg_stream_metadata_t* stream_metadata, uint32_t segment_index, size_t extra_traf_atoms_size, write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback, void* write_extra_traf_atoms_context, bool_t size_only, vod_str_t* result, size_t* total_fragment_size) { input_frame_t* last_frame; input_frame_t* cur_frame; size_t mdat_atom_size; size_t trun_atom_size; size_t moof_atom_size; size_t traf_atom_size; size_t result_size; u_char* p; // calculate sizes mdat_atom_size = ATOM_HEADER_SIZE + stream_metadata->total_frames_size; trun_atom_size = mp4_builder_get_trun_atom_size(stream_metadata->media_info.media_type, stream_metadata->frame_count); traf_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(tfhd_atom_t) + trun_atom_size + ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t) + extra_traf_atoms_size; moof_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(mfhd_atom_t)+ traf_atom_size; result_size = moof_atom_size + ATOM_HEADER_SIZE; // mdat *total_fragment_size = result_size + stream_metadata->total_frames_size; // head request optimization if (size_only) { return VOD_OK; } // 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, "mss_packager_build_fragment_header: vod_alloc failed"); return VOD_ALLOC_FAILED; } p = result->data; // 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 switch (stream_metadata->media_info.media_type) { case MEDIA_TYPE_VIDEO: p = mss_write_tfhd_atom(p, stream_metadata->media_info.track_id, 0x01010000); break; case MEDIA_TYPE_AUDIO: p = mss_write_tfhd_atom(p, stream_metadata->media_info.track_id, 0x02000000); break; } // moof.traf.trun last_frame = stream_metadata->frames + stream_metadata->frame_count; for (cur_frame = stream_metadata->frames; cur_frame < last_frame; cur_frame++) { cur_frame->duration = rescale_time(cur_frame->duration, stream_metadata->media_info.timescale, MSS_TIMESCALE); } p = mp4_builder_write_trun_atom( p, stream_metadata->media_info.media_type, stream_metadata->frames, stream_metadata->frame_count, moof_atom_size + ATOM_HEADER_SIZE); p = mss_write_uuid_tfxd_atom(p, stream_metadata); // moof.traf.xxx if (write_extra_traf_atoms_callback != NULL) { p = write_extra_traf_atoms_callback(write_extra_traf_atoms_context, p, moof_atom_size + ATOM_HEADER_SIZE); } // mdat write_atom_header(p, mdat_atom_size, 'm', 'd', 'a', 't'); result->len = p - result->data; if (result->len != result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "mss_packager_build_fragment_header: result length %uz is different than allocated length %uz", result->len, result_size); return VOD_UNEXPECTED; } return VOD_OK; }
static vod_status_t audio_filter_update_track(audio_filter_state_t* state) { media_track_t* output = state->output; input_frame_t* cur_frame; input_frame_t* last_frame; uint32_t old_timescale; vod_status_t rc; u_char* new_extra_data; bool_t has_frames; if (state->sink.encoder->time_base.num != 1) { vod_log_error(VOD_LOG_ERR, state->request_context->log, 0, "audio_filter_update_track: unexpected encoder time base %d/%d", state->sink.encoder->time_base.num, state->sink.encoder->time_base.den); return VOD_UNEXPECTED; } // decrement the old frame count and size state->sequence->total_frame_count -= output->frame_count; state->sequence->total_frame_size -= output->total_frames_size; output->total_frames_size = 0; output->total_frames_duration = 0; // update frames output->frame_count = state->frames_array.nelts; output->frames.first_frame = state->frames_array.elts; output->frames.last_frame = output->frames.first_frame + output->frame_count; output->frames.next = NULL; // check whether there are any frames with duration has_frames = FALSE; // Note: always a single part here last_frame = output->frames.last_frame; for (cur_frame = output->frames.first_frame; cur_frame < last_frame; cur_frame++) { if (cur_frame->duration != 0) { has_frames = TRUE; break; } } if (!has_frames) { output->frames.first_frame = NULL; output->frames.last_frame = NULL; output->frame_count = 0; return VOD_OK; } // update the frames source to memory rc = frames_source_memory_init(state->request_context, &output->frames.frames_source_context); if (rc != VOD_OK) { return rc; } output->frames.frames_source = &frames_source_memory; // calculate the total frames size and duration output->media_info.min_frame_duration = 0; for (cur_frame = output->frames.first_frame; cur_frame < last_frame; cur_frame++) { output->total_frames_size += cur_frame->size; output->total_frames_duration += cur_frame->duration; if (cur_frame->duration != 0 && (output->media_info.min_frame_duration == 0 || cur_frame->duration < output->media_info.min_frame_duration)) { output->media_info.min_frame_duration = cur_frame->duration; } } // update media info old_timescale = output->media_info.timescale; output->media_info.timescale = state->sink.encoder->time_base.den; output->media_info.duration = rescale_time(output->media_info.duration, old_timescale, output->media_info.timescale); output->media_info.bitrate = state->sink.encoder->bit_rate; output->media_info.u.audio.object_type_id = 0x40; // ffmpeg always writes 0x40 (ff_mp4_obj_type) output->media_info.u.audio.channels = state->sink.encoder->channels; output->media_info.u.audio.bits_per_sample = ENCODER_BITS_PER_SAMPLE; output->media_info.u.audio.packet_size = 0; // ffmpeg always writes 0 (mov_write_audio_tag) output->media_info.u.audio.sample_rate = state->sink.encoder->sample_rate; output->key_frame_count = 0; output->first_frame_time_offset = rescale_time(output->first_frame_time_offset, old_timescale, output->media_info.timescale); new_extra_data = vod_alloc(state->request_context->pool, state->sink.encoder->extradata_size); if (new_extra_data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "audio_filter_update_track: vod_alloc failed"); return VOD_ALLOC_FAILED; } vod_memcpy(new_extra_data, state->sink.encoder->extradata, state->sink.encoder->extradata_size); output->media_info.extra_data.data = new_extra_data; output->media_info.extra_data.len = state->sink.encoder->extradata_size; if (output->media_info.codec_name.data != NULL) { rc = codec_config_get_audio_codec_name(state->request_context, &output->media_info); if (rc != VOD_OK) { return rc; } } // add the new frame count and size state->sequence->total_frame_count += output->frame_count; state->sequence->total_frame_size += output->total_frames_size; // TODO: update raw_atoms return VOD_OK; }
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 mss_packager_build_fragment_header( request_context_t* request_context, media_set_t* media_set, uint32_t segment_index, size_t extra_traf_atoms_size, mss_write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback, void* write_extra_traf_atoms_context, bool_t size_only, vod_str_t* result, size_t* total_fragment_size) { segment_timing_info_t timing_info; media_clip_filtered_t* cur_clip; media_sequence_t* sequence = media_set->sequences; input_frame_t* last_frame; input_frame_t* cur_frame; media_track_t* first_track = sequence->filtered_clips[0].first_track; media_track_t* track; uint32_t media_type = sequence->media_type; size_t mdat_atom_size; size_t trun_atom_size; size_t moof_atom_size; size_t traf_atom_size; size_t result_size; u_char* p; // calculate sizes mdat_atom_size = ATOM_HEADER_SIZE + sequence->total_frame_size; trun_atom_size = mp4_builder_get_trun_atom_size(media_type, sequence->total_frame_count); traf_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(tfhd_atom_t) + trun_atom_size + ATOM_HEADER_SIZE + sizeof(uuid_tfxd_atom_t) + extra_traf_atoms_size; if (media_set->type == MEDIA_SET_LIVE) { traf_atom_size += TFRF_ATOM_SIZE; } moof_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(mfhd_atom_t)+ traf_atom_size; result_size = moof_atom_size + ATOM_HEADER_SIZE; // mdat *total_fragment_size = result_size + sequence->total_frame_size; // head request optimization if (size_only) { return VOD_OK; } // 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, "mss_packager_build_fragment_header: vod_alloc failed"); return VOD_ALLOC_FAILED; } p = result->data; // 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 switch (media_type) { case MEDIA_TYPE_VIDEO: p = mss_write_tfhd_atom(p, first_track->media_info.track_id, 0x01010000); break; case MEDIA_TYPE_AUDIO: p = mss_write_tfhd_atom(p, first_track->media_info.track_id, 0x02000000); break; } // moof.traf.trun for (cur_clip = sequence->filtered_clips; cur_clip < sequence->filtered_clips_end; cur_clip++) { track = cur_clip->first_track; cur_frame = track->first_frame; last_frame = track->last_frame; for (; cur_frame < last_frame; cur_frame++) { cur_frame->duration = rescale_time(cur_frame->duration, track->media_info.timescale, MSS_TIMESCALE); cur_frame->pts_delay = rescale_time(cur_frame->pts_delay, track->media_info.timescale, MSS_TIMESCALE); } } p = mp4_builder_write_trun_atom( p, sequence, moof_atom_size + ATOM_HEADER_SIZE); if (media_set->type == MEDIA_SET_LIVE) { // using only estimate timing info in live, since we don't have the accurate timing // for the lookahead segments. the timestamp has to be consistent between segments/manifest // otherwise some segments may be pulled more than once timing_info.timestamp = mss_rescale_millis(media_set->segment_start_time); timing_info.duration = mss_rescale_millis(media_set->segmenter_conf->segment_duration); p = mss_write_uuid_tfxd_atom(p, &timing_info); p = mss_write_uuid_tfrf_atom(p, &timing_info); } else { mss_get_segment_timing_info(sequence, &timing_info); p = mss_write_uuid_tfxd_atom(p, &timing_info); } // moof.traf.xxx if (write_extra_traf_atoms_callback != NULL) { p = write_extra_traf_atoms_callback(write_extra_traf_atoms_context, p, moof_atom_size); } // mdat write_atom_header(p, mdat_atom_size, 'm', 'd', 'a', 't'); result->len = p - result->data; if (result->len != result_size) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "mss_packager_build_fragment_header: result length %uz is different than 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, 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 vod_status_t hds_muxer_start_frame(hds_muxer_state_t* state) { hds_muxer_stream_state_t* selected_stream; uint64_t cur_frame_offset; uint64_t cur_frame_dts; size_t alloc_size; u_char* p; vod_status_t rc; rc = hds_muxer_choose_stream(state, &selected_stream); if (rc != VOD_OK) { return rc; } // init the frame state->cur_frame = selected_stream->cur_frame; state->frames_source = selected_stream->frames_source; state->frames_source_context = selected_stream->frames_source_context; selected_stream->cur_frame++; cur_frame_offset = *selected_stream->cur_frame_input_offset; selected_stream->cur_frame_input_offset++; selected_stream->cur_frame_output_offset++; selected_stream->next_frame_time_offset += state->cur_frame->duration; cur_frame_dts = selected_stream->next_frame_dts + selected_stream->clip_start_time; selected_stream->next_frame_dts = rescale_time(selected_stream->next_frame_time_offset, selected_stream->timescale, HDS_TIMESCALE); state->cache_slot_id = selected_stream->media_type; // allocate room for the mux packet header state->frame_header_size = tag_size_by_media_type[selected_stream->media_type]; alloc_size = state->frame_header_size; if (selected_stream->media_type == MEDIA_TYPE_VIDEO && state->cur_frame->key_frame) { alloc_size += state->codec_config_size; } rc = write_buffer_get_bytes(&state->write_buffer_state, alloc_size, NULL, &p); if (rc != VOD_OK) { vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "hds_muxer_start_frame: write_buffer_get_bytes failed %i", rc); return rc; } // write the mux packet header and optionally codec config if (selected_stream->media_type == MEDIA_TYPE_VIDEO && state->cur_frame->key_frame) { p = hds_muxer_write_codec_config(p, state, cur_frame_dts); } switch (selected_stream->media_type) { case MEDIA_TYPE_VIDEO: hds_write_video_tag_header( p, state->cur_frame->size, cur_frame_dts, state->cur_frame->key_frame ? FRAME_TYPE_KEY_FRAME : FRAME_TYPE_INTER_FRAME, AVC_PACKET_TYPE_NALU, rescale_time(state->cur_frame->pts_delay, selected_stream->timescale, HDS_TIMESCALE)); break; case MEDIA_TYPE_AUDIO: hds_write_audio_tag_header( p, state->cur_frame->size, cur_frame_dts, selected_stream->sound_info, AAC_PACKET_TYPE_RAW); } rc = state->frames_source->start_frame(state->frames_source_context, state->cur_frame, cur_frame_offset); if (rc != VOD_OK) { return rc; } return VOD_OK; }
vod_status_t audio_filter_update_stream_metadata(audio_filter_state_t* state) { mpeg_stream_metadata_t* output = state->output; input_frame_t* cur_frame; input_frame_t* last_frame; uint32_t old_timescale; vod_status_t rc; u_char* new_extra_data; if (state->encoder->time_base.num != 1) { vod_log_error(VOD_LOG_ERR, state->request_context->log, 0, "audio_filter_update_stream_metadata: unexpected encoder time base num %d", state->encoder->time_base.num); return VOD_UNEXPECTED; } output->frames = state->frames_array.elts; output->frame_count = state->frames_array.nelts; output->frame_offsets = state->frame_offsets_array.elts; output->total_frames_size = 0; output->total_frames_duration = 0; output->media_info.min_frame_duration = 0; output->media_info.max_frame_duration = 0; last_frame = output->frames + output->frame_count; for (cur_frame = output->frames; cur_frame < last_frame; cur_frame++) { output->total_frames_size += cur_frame->size; output->total_frames_duration += cur_frame->duration; if (cur_frame->duration != 0 && (output->media_info.min_frame_duration == 0 || cur_frame->duration < output->media_info.min_frame_duration)) { output->media_info.min_frame_duration = cur_frame->duration; } if (cur_frame->duration > output->media_info.max_frame_duration) { output->media_info.max_frame_duration = cur_frame->duration; } } if (output->media_info.min_frame_duration == 0) { vod_log_error(VOD_LOG_ERR, state->request_context->log, 0, "audio_filter_update_stream_metadata: min frame duration is zero"); return VOD_UNEXPECTED; } old_timescale = output->media_info.timescale; output->media_info.timescale = state->encoder->time_base.den; output->media_info.duration = rescale_time(output->media_info.duration, old_timescale, output->media_info.timescale); output->media_info.bitrate = state->encoder->bit_rate; output->media_info.speed_nom = 1; output->media_info.speed_denom = 1; output->media_info.u.audio.object_type_id = 0x40; // ffmpeg always writes 0x40 (ff_mp4_obj_type) output->media_info.u.audio.channels = state->encoder->channels; output->media_info.u.audio.bits_per_sample = ENCODER_BITS_PER_SAMPLE; output->media_info.u.audio.packet_size = 0; // ffmpeg always writes 0 (mov_write_audio_tag) output->media_info.u.audio.sample_rate = state->encoder->sample_rate; output->key_frame_count = 0; output->first_frame_time_offset = rescale_time(output->first_frame_time_offset, old_timescale, output->media_info.timescale); new_extra_data = vod_alloc(state->request_context->pool, state->encoder->extradata_size); if (new_extra_data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "audio_filter_update_stream_metadata: vod_alloc failed"); return VOD_ALLOC_FAILED; } vod_memcpy(new_extra_data, state->encoder->extradata, state->encoder->extradata_size); output->media_info.extra_data = new_extra_data; output->media_info.extra_data_size = state->encoder->extradata_size; if (output->media_info.codec_name.data != NULL) { rc = codec_config_get_audio_codec_name(state->request_context, &output->media_info); if (rc != VOD_OK) { return rc; } } output->frames_file_index = INVALID_FILE_INDEX; // the frames are now in memory // TODO: update raw_atoms 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; }
vod_status_t hls_muxer_simulate_get_iframes( request_context_t* request_context, segment_durations_t* segment_durations, hls_muxer_conf_t* muxer_conf, hls_encryption_params_t* encryption_params, media_set_t* media_set, hls_get_iframe_positions_callback_t callback, void* context) { hls_muxer_stream_state_t* selected_stream; segment_duration_item_t* cur_item; segment_duration_item_t* last_item; hls_muxer_state_t state; input_frame_t* cur_frame; uint32_t cur_frame_time; uint32_t frame_start = 0; uint32_t frame_size = 0; uint32_t frame_start_time = 0; uint32_t first_frame_time = 0; uint32_t end_time; uint32_t frame_segment_index = 0; uint32_t segment_index = 0; uint64_t cur_frame_dts; uint64_t cur_frame_time_offset; uint32_t repeat_count; uint64_t segment_end; bool_t simulation_supported; bool_t last_frame; vod_status_t rc; #if (VOD_DEBUG) off_t cur_frame_start; #endif cur_item = segment_durations->items; last_item = segment_durations->items + segment_durations->item_count; if (cur_item >= last_item) { return VOD_OK; } // initialize the muxer rc = hls_muxer_init_base( &state, request_context, muxer_conf, encryption_params, 0, media_set, NULL, NULL, &simulation_supported, NULL); if (rc != VOD_OK) { return rc; } if (!simulation_supported) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "hls_muxer_simulate_get_iframes: simulation not supported for this file, cant create iframe playlist"); return VOD_BAD_REQUEST; } // initialize the repeat count, segment end, and the per stream limit repeat_count = cur_item->repeat_count - 1; segment_end = cur_item->duration; if (repeat_count <= 0 && (cur_item + 1 >= last_item || cur_item[1].discontinuity)) { hls_muxer_simulation_set_segment_limit_unlimited(&state); } else { hls_muxer_simulation_set_segment_limit(&state, segment_end, segment_durations->timescale); } mpegts_encoder_simulated_start_segment(&state.queue); for (;;) { // get a frame for (;;) { // choose a stream for the current frame rc = hls_muxer_choose_stream(&state, &selected_stream); if (rc == VOD_OK) { break; } if (rc != VOD_NOT_FOUND) { return rc; } // update the limit for the next segment if (repeat_count <= 0) { cur_item++; if (cur_item >= last_item) { goto done; } repeat_count = cur_item->repeat_count; } repeat_count--; segment_end += cur_item->duration; if (repeat_count <= 0 && (cur_item + 1 >= last_item || cur_item[1].discontinuity)) { hls_muxer_simulation_set_segment_limit_unlimited(&state); } else { hls_muxer_simulation_set_segment_limit(&state, segment_end, segment_durations->timescale); } // start the next segment mpegts_encoder_simulated_start_segment(&state.queue); segment_index++; } // update the stream state cur_frame = selected_stream->cur_frame; selected_stream->cur_frame++; cur_frame_time_offset = selected_stream->next_frame_time_offset; cur_frame_dts = selected_stream->next_frame_time_offset; selected_stream->next_frame_time_offset += cur_frame->duration; // flush any buffered frames if their delay becomes too big hls_muxer_simulation_flush_delayed_streams(&state, selected_stream, cur_frame_dts); // check whether this is the last frame of the selected stream in this segment last_frame = ((selected_stream->cur_frame >= selected_stream->cur_frame_part.last_frame && selected_stream->cur_frame_part.next == NULL) || selected_stream->next_frame_time_offset >= selected_stream->segment_limit); // write the frame #if (VOD_DEBUG) cur_frame_start = state.queue.cur_offset; #endif // VOD_DEBUG hls_muxer_simulation_write_frame( selected_stream, cur_frame, cur_frame_dts, last_frame); #if (VOD_DEBUG) if (cur_frame_start != state.queue.cur_offset) { vod_log_debug4(VOD_LOG_DEBUG_LEVEL, state.request_context->log, 0, "hls_muxer_simulate_get_iframes: wrote frame segment %uD packets %uD-%uD dts %L", segment_index + 1, (uint32_t)(cur_frame_start / MPEGTS_PACKET_SIZE + 1), (uint32_t)(state.queue.cur_offset / MPEGTS_PACKET_SIZE + 1), cur_frame_dts); } #endif // VOD_DEBUG // only care about video key frames if (selected_stream->media_type != MEDIA_TYPE_VIDEO) { continue; } if (!selected_stream->is_first_segment_frame && selected_stream->prev_key_frame) { // get the frame time cur_frame_time = rescale_time(selected_stream->prev_frame_pts, HLS_TIMESCALE, 1000); // in millis if (frame_size != 0) { if (cur_frame_time > frame_start_time) { callback(context, frame_segment_index, cur_frame_time - frame_start_time, frame_start, frame_size); } } else { first_frame_time = cur_frame_time; } // save the info of the current keyframe frame_start = selected_stream->mpegts_encoder_state.last_frame_start_pos; frame_size = selected_stream->mpegts_encoder_state.last_frame_end_pos - selected_stream->mpegts_encoder_state.last_frame_start_pos; frame_start_time = cur_frame_time; frame_segment_index = segment_index; } if (last_frame && cur_frame[0].key_frame) { // get the frame time cur_frame_time = rescale_time(cur_frame_time_offset + cur_frame[0].pts_delay, HLS_TIMESCALE, 1000); // in millis if (frame_size != 0) { if (cur_frame_time > frame_start_time) { callback(context, frame_segment_index, cur_frame_time - frame_start_time, frame_start, frame_size); } } else { first_frame_time = cur_frame_time; } // save the info of the current keyframe frame_start = selected_stream->mpegts_encoder_state.cur_frame_start_pos; frame_size = selected_stream->mpegts_encoder_state.cur_frame_end_pos - selected_stream->mpegts_encoder_state.cur_frame_start_pos; frame_start_time = cur_frame_time; frame_segment_index = segment_index; } selected_stream->prev_key_frame = cur_frame->key_frame; selected_stream->prev_frame_pts = cur_frame_time_offset + cur_frame->pts_delay; selected_stream->is_first_segment_frame = FALSE; } done: // call the callback for the last frame end_time = first_frame_time + state.video_duration; if (frame_size != 0 && end_time > frame_start_time) { callback(context, frame_segment_index, end_time - frame_start_time, frame_start, frame_size); } return VOD_OK; }
static vod_status_t hds_calculate_output_offsets_and_write_afra_entries( hds_muxer_state_t* state, uint32_t initial_value, uint32_t afra_entries_base, u_char** p) { hds_muxer_stream_state_t* selected_stream; hds_muxer_stream_state_t* cur_stream; uint32_t cur_offset = initial_value; vod_status_t rc; for (;;) { // choose a stream rc = hds_muxer_choose_stream(state, &selected_stream); if (rc != VOD_OK) { if (rc == VOD_NOT_FOUND) { break; // done } return rc; } // video key frames start with the codec info if (selected_stream->cur_frame->key_frame && selected_stream->media_type == MEDIA_TYPE_VIDEO && p != NULL) { *p = hds_write_afra_atom_entry( *p, selected_stream->next_frame_dts + selected_stream->clip_start_time, cur_offset + afra_entries_base); cur_offset += state->codec_config_size; } // skip the tag size cur_offset += tag_size_by_media_type[selected_stream->media_type]; // set the offset (points to the beginning of the actual data) *selected_stream->cur_frame_output_offset = cur_offset; selected_stream->cur_frame_output_offset++; // move to the end of the frame cur_offset += selected_stream->cur_frame->size; cur_offset += sizeof(uint32_t); // move to the next frame selected_stream->next_frame_time_offset += selected_stream->cur_frame->duration; selected_stream->next_frame_dts = rescale_time(selected_stream->next_frame_time_offset, selected_stream->timescale, HDS_TIMESCALE); selected_stream->cur_frame++; } // reset the state if (state->clips_end > state->clips_start + 1) { state->cur_clip = state->clips_start; rc = hds_muxer_reinit_tracks(state); if (rc != VOD_OK) { vod_log_error(VOD_LOG_ERR, state->request_context->log, 0, "hds_calculate_output_offsets_and_write_afra_entries: unexpected - hds_muxer_reinit_tracks failed %i", rc); return rc; } for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++) { cur_stream->cur_frame_output_offset = cur_stream->first_frame_output_offset; } } else { for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++) { cur_stream->cur_frame = cur_stream->first_frame; cur_stream->cur_frame_output_offset = cur_stream->first_frame_output_offset; cur_stream->next_frame_time_offset = cur_stream->first_frame_time_offset; cur_stream->next_frame_dts = rescale_time(cur_stream->next_frame_time_offset, cur_stream->timescale, HDS_TIMESCALE); } } return VOD_OK; }