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 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 = track->first_frame_time_offset + mss_rescale_millis(track->clip_start_time); result->duration = track->total_frames_duration; cur_clip++; for (; cur_clip < sequence->filtered_clips_end; cur_clip++) { track = cur_clip->first_track; result->duration += track->total_frames_duration; } }
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 mss_packager_build_manifest( request_context_t* request_context, mss_manifest_config_t* conf, media_set_t* media_set, size_t extra_tags_size, mss_write_tags_callback_t write_extra_tags, void* extra_tags_writer_context, vod_str_t* result) { segmenter_conf_t* segmenter_conf = media_set->segmenter_conf; media_sequence_t* cur_sequence; media_track_t* last_track; media_track_t* cur_track; 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; if (media_set->use_discontinuity) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "mss_packager_build_manifest: discontinuity is not supported in MSS"); return VOD_BAD_MAPPING; } // calculate the result size result_size = sizeof(MSS_MANIFEST_HEADER_PREFIX) - 1 + VOD_INT64_LEN + sizeof(MSS_MANIFEST_HEADER_SUFFIX) - 1 + extra_tags_size + sizeof(MSS_MANIFEST_FOOTER); if (media_set->type == MEDIA_SET_LIVE) { result_size += sizeof(MSS_MANIFEST_HEADER_LIVE_ATTRIBUTES) - 1 + VOD_INT64_LEN + VOD_INT32_LEN; } 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; } result_size += sizeof(MSS_STREAM_INDEX_HEADER) - 1 + 2 * sizeof(MSS_STREAM_TYPE_VIDEO) + 2 * VOD_INT32_LEN + sizeof(MSS_STREAM_INDEX_FOOTER); switch (media_set->type) { case MEDIA_SET_VOD: result_size += segment_durations[media_type].segment_count * (sizeof(MSS_CHUNK_TAG) + VOD_INT32_LEN + VOD_INT64_LEN); break; case MEDIA_SET_LIVE: result_size += segment_durations[media_type].segment_count * (sizeof(MSS_CHUNK_TAG_LIVE) + VOD_INT64_LEN) + sizeof(MSS_CHUNK_TAG_LIVE_FIRST) + 2 * VOD_INT64_LEN; break; } } mss_packager_remove_redundant_tracks(conf->duplicate_bitrate_threshold, media_set); cur_track = media_set->filtered_tracks; last_track = cur_track + media_set->total_track_count; for (; cur_track < last_track; cur_track++) { switch (cur_track->media_info.media_type) { case MEDIA_TYPE_VIDEO: result_size += sizeof(MSS_VIDEO_QUALITY_LEVEL_HEADER) - 1 + 4 * VOD_INT32_LEN + cur_track->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_track->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; } // header if (media_set->type != MEDIA_SET_LIVE) { duration_100ns = mss_rescale_millis(media_set->total_duration); } else { duration_100ns = 0; } p = vod_sprintf(result->data, MSS_MANIFEST_HEADER_PREFIX, duration_100ns); if (media_set->type == MEDIA_SET_LIVE) { media_type = media_set->track_count[MEDIA_TYPE_VIDEO] != 0 ? MEDIA_TYPE_VIDEO : MEDIA_TYPE_AUDIO; p = vod_sprintf(p, MSS_MANIFEST_HEADER_LIVE_ATTRIBUTES, mss_rescale_millis(segment_durations[media_type].segment_count * segmenter_conf->segment_duration), MSS_LOOK_AHEAD_COUNT); } p = vod_copy(p, MSS_MANIFEST_HEADER_SUFFIX, sizeof(MSS_MANIFEST_HEADER_SUFFIX) - 1); if (media_set->track_count[MEDIA_TYPE_VIDEO] != 0) { p = vod_sprintf(p, MSS_STREAM_INDEX_HEADER, MSS_STREAM_TYPE_VIDEO, media_set->track_count[MEDIA_TYPE_VIDEO], segment_durations[MEDIA_TYPE_VIDEO].segment_count, MSS_STREAM_TYPE_VIDEO); stream_index = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->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 (cur_track->media_info.media_type != MEDIA_TYPE_VIDEO) { continue; } bitrate = cur_track->media_info.bitrate; bitrate = mss_encode_indexes(bitrate, cur_sequence->index, cur_track->index); p = vod_sprintf(p, MSS_VIDEO_QUALITY_LEVEL_HEADER, stream_index++, bitrate, (uint32_t)cur_track->media_info.u.video.width, (uint32_t)cur_track->media_info.u.video.height); p = mss_append_hex_string(p, cur_track->media_info.extra_data, cur_track->media_info.extra_data_size); p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1); } } switch (media_set->type) { case MEDIA_SET_VOD: p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_VIDEO]); break; case MEDIA_SET_LIVE: p = mss_write_manifest_chunks_live(p, &segment_durations[MEDIA_TYPE_VIDEO]); break; } p = vod_copy(p, MSS_STREAM_INDEX_FOOTER, sizeof(MSS_STREAM_INDEX_FOOTER) - 1); } if (media_set->track_count[MEDIA_TYPE_AUDIO] != 0) { p = vod_sprintf(p, MSS_STREAM_INDEX_HEADER, MSS_STREAM_TYPE_AUDIO, media_set->track_count[MEDIA_TYPE_AUDIO], segment_durations[MEDIA_TYPE_AUDIO].segment_count, MSS_STREAM_TYPE_AUDIO); stream_index = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->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 (cur_track->media_info.media_type != MEDIA_TYPE_AUDIO) { continue; } bitrate = cur_track->media_info.bitrate; bitrate = mss_encode_indexes(bitrate, cur_sequence->index, cur_track->index); p = vod_sprintf(p, MSS_AUDIO_QUALITY_LEVEL_HEADER, stream_index++, bitrate, cur_track->media_info.u.audio.sample_rate, (uint32_t)cur_track->media_info.u.audio.channels, (uint32_t)cur_track->media_info.u.audio.bits_per_sample, (uint32_t)cur_track->media_info.u.audio.packet_size); p = mss_append_hex_string(p, cur_track->media_info.extra_data, cur_track->media_info.extra_data_size); p = vod_copy(p, MSS_QUALITY_LEVEL_FOOTER, sizeof(MSS_QUALITY_LEVEL_FOOTER) - 1); } } switch (media_set->type) { case MEDIA_SET_VOD: p = mss_write_manifest_chunks(p, &segment_durations[MEDIA_TYPE_AUDIO]); break; case MEDIA_SET_LIVE: p = mss_write_manifest_chunks_live(p, &segment_durations[MEDIA_TYPE_AUDIO]); break; } 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, media_set); } 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; }