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_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; }