vod_status_t read_cache_get_read_buffer(read_cache_state_t* state, uint32_t* file_index, uint64_t* out_offset, u_char** buffer, uint32_t* size) { cache_buffer_t* target_buffer; cache_buffer_t* cur_buffer; cache_buffer_t* buffers_end = state->buffers + CACHED_BUFFERS; uint32_t read_size; // select a buffer target_buffer = state->target_buffer; // make sure the buffer is allocated if (target_buffer->buffer == NULL) { target_buffer->buffer = vod_memalign(state->request_context->pool, state->buffer_size + 1, state->alignment); if (target_buffer->buffer == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "read_cache_get_read_buffer: vod_memalign failed"); return VOD_ALLOC_FAILED; } } // make sure we don't read anything we already have in cache read_size = state->buffer_size; for (cur_buffer = state->buffers; cur_buffer < buffers_end; cur_buffer++) { if (cur_buffer != target_buffer && cur_buffer->start_offset > target_buffer->start_offset) { read_size = MIN(read_size, cur_buffer->start_offset - target_buffer->start_offset); } } // return the target buffer pointer and size *file_index = target_buffer->file_index; *out_offset = target_buffer->start_offset; *buffer = target_buffer->buffer; *size = read_size; return VOD_OK; }
vod_status_t write_buffer_flush(write_buffer_state_t* state, bool_t reallocate) { vod_status_t rc; if (state->cur_pos > state->start_pos) { rc = state->write_callback(state->write_context, state->start_pos, state->cur_pos - state->start_pos); if (rc != VOD_OK) { vod_log_debug1(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "write_buffer_flush: write_callback failed %i", rc); return rc; } if (state->reuse_buffers) { state->cur_pos = state->start_pos; return VOD_OK; } } if (reallocate) { state->start_pos = vod_alloc(state->request_context->pool, WRITE_BUFFER_SIZE); if (state->start_pos == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "write_buffer_flush: vod_alloc failed"); return VOD_ALLOC_FAILED; } state->end_pos = state->start_pos + WRITE_BUFFER_SIZE; state->cur_pos = state->start_pos; } else { state->start_pos = state->end_pos = state->cur_pos = NULL; } return VOD_OK; }
vod_status_t audio_encoder_update_media_info( void* context, media_info_t* media_info) { audio_encoder_state_t* state = context; AVCodecContext *encoder = state->encoder; u_char* new_extra_data; if (encoder->time_base.num != 1) { vod_log_error(VOD_LOG_ERR, state->request_context->log, 0, "audio_encoder_update_media_info: unexpected encoder time base %d/%d", encoder->time_base.num, encoder->time_base.den); return VOD_UNEXPECTED; } media_info->timescale = encoder->time_base.den; media_info->bitrate = encoder->bit_rate; media_info->u.audio.object_type_id = 0x40; // ffmpeg always writes 0x40 (ff_mp4_obj_type) media_info->u.audio.channels = encoder->channels; media_info->u.audio.bits_per_sample = AUDIO_ENCODER_BITS_PER_SAMPLE; media_info->u.audio.packet_size = 0; // ffmpeg always writes 0 (mov_write_audio_tag) media_info->u.audio.sample_rate = encoder->sample_rate; new_extra_data = vod_alloc(state->request_context->pool, encoder->extradata_size); if (new_extra_data == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "audio_encoder_update_media_info: vod_alloc failed"); return VOD_ALLOC_FAILED; } vod_memcpy(new_extra_data, encoder->extradata, encoder->extradata_size); media_info->extra_data.data = new_extra_data; media_info->extra_data.len = encoder->extradata_size; return VOD_OK; }
void* buffer_pool_alloc(request_context_t* request_context, buffer_pool_t* buffer_pool, size_t* buffer_size) { buffer_pool_cleanup_t* buf_cln; vod_pool_cleanup_t* cln; void* result; if (buffer_pool == NULL) { return vod_alloc(request_context->pool, *buffer_size); } if (buffer_pool->head == NULL) { *buffer_size = buffer_pool->size; return vod_alloc(request_context->pool, *buffer_size); } cln = vod_pool_cleanup_add(request_context->pool, sizeof(buffer_pool_cleanup_t)); if (cln == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "buffer_pool_alloc: vod_pool_cleanup_add failed"); return NULL; } result = buffer_pool->head; buffer_pool->head = next_buffer(result); cln->handler = buffer_pool_buffer_cleanup; buf_cln = cln->data; buf_cln->buffer = result; buf_cln->buffer_pool = buffer_pool; *buffer_size = buffer_pool->size; return result; }
static vod_status_t mp4_metadata_reader_init( request_context_t* request_context, vod_str_t* buffer, size_t initial_read_size, size_t max_metadata_size, void** ctx) { mp4_read_metadata_state_t* state; bool_t atom_found = FALSE; mp4_parser_parse_atoms( request_context, buffer->data, buffer->len, FALSE, mp4_reader_identify_callback, &atom_found); if (!atom_found) { return VOD_NOT_FOUND; } state = vod_alloc(request_context->pool, sizeof(*state)); if (state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "mp4_metadata_reader_init: vod_alloc failed"); return VOD_ALLOC_FAILED; } state->request_context = request_context; state->moov_start_reads = MAX_MOOV_START_READS; state->max_moov_size = max_metadata_size; state->state = STATE_READ_MOOV_HEADER; state->parts[MP4_METADATA_PART_FTYP].len = 0; *ctx = state; return VOD_OK; }
vod_status_t frame_joiner_init( media_filter_t* filter, media_filter_context_t* context) { frame_joiner_t* state; request_context_t* request_context = context->request_context; // allocate state state = vod_alloc(request_context->pool, sizeof(*state)); if (state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "frame_joiner_init: vod_alloc failed"); return VOD_ALLOC_FAILED; } state->frame_dts = NO_TIMESTAMP; // save required functions state->start_frame = filter->start_frame; state->flush_frame = filter->flush_frame; state->simulated_start_frame = filter->simulated_start_frame; state->simulated_flush_frame = filter->simulated_flush_frame; // override functions filter->start_frame = frame_joiner_start_frame; filter->flush_frame = frame_joiner_flush_frame; filter->simulated_start_frame = frame_joiner_simulated_start_frame; filter->simulated_flush_frame = frame_joiner_simulated_flush_frame; // save the context context->context[THIS_FILTER] = state; return VOD_OK; }
vod_status_t manifest_utils_get_adaptation_sets( request_context_t* request_context, media_set_t* media_set, uint32_t flags, adaptation_sets_t* output) { label_track_count_t* last_label; label_track_count_t temp_label; label_track_count_array_t labels[MEDIA_TYPE_COUNT]; vod_status_t rc; rc = manifest_utils_get_unique_labels( request_context, media_set, MEDIA_TYPE_AUDIO, &labels[MEDIA_TYPE_AUDIO]); if (rc != VOD_OK) { return rc; } if (media_set->track_count[MEDIA_TYPE_SUBTITLE] > 0 && media_set->track_count[MEDIA_TYPE_VIDEO] > 0) // ignore subtitles if there is no video { rc = manifest_utils_get_unique_labels( request_context, media_set, MEDIA_TYPE_SUBTITLE, &labels[MEDIA_TYPE_SUBTITLE]); if (rc != VOD_OK) { return rc; } } else { labels[MEDIA_TYPE_SUBTITLE].first = NULL; labels[MEDIA_TYPE_SUBTITLE].last = NULL; labels[MEDIA_TYPE_SUBTITLE].count = 0; } if (labels[MEDIA_TYPE_AUDIO].count > 1) { if ((flags & ADAPTATION_SETS_FLAG_DEFAULT_LANG_LAST) != 0) { last_label = labels[MEDIA_TYPE_AUDIO].last - 1; temp_label = *labels[MEDIA_TYPE_AUDIO].first; *labels[MEDIA_TYPE_AUDIO].first = *last_label; *last_label = temp_label; } rc = manifest_utils_get_multilingual_adaptation_sets( request_context, media_set, flags, labels, output); } else if ((flags & (ADAPTATION_SETS_FLAG_MUXED | ADAPTATION_SETS_FLAG_FORCE_MUXED)) != 0) { // cannot generate muxed media set if there are only subtitles if (media_set->track_count[MEDIA_TYPE_VIDEO] + media_set->track_count[MEDIA_TYPE_AUDIO] <= 0) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "manifest_utils_get_adaptation_sets: no audio/video tracks"); return VOD_BAD_REQUEST; } output->count[ADAPTATION_TYPE_MUXED] = 1; output->count[ADAPTATION_TYPE_AUDIO] = 0; output->count[ADAPTATION_TYPE_VIDEO] = 0; output->count[ADAPTATION_TYPE_SUBTITLE] = labels[MEDIA_TYPE_SUBTITLE].count; output->total_count = 1 + output->count[ADAPTATION_TYPE_SUBTITLE]; output->first = vod_alloc(request_context->pool, output->total_count * sizeof(output->first[0])); if (output->first == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_get_adaptation_sets: vod_alloc failed"); return VOD_ALLOC_FAILED; } output->last = output->first + output->total_count; output->first_by_type[ADAPTATION_TYPE_MUXED] = output->first; rc = manifest_utils_get_muxed_adaptation_set( request_context, media_set, flags, NULL, output->first); if (rc != VOD_OK) { return rc; } if (labels[MEDIA_TYPE_SUBTITLE].count > 0) { output->first_by_type[ADAPTATION_TYPE_SUBTITLE] = output->first + 1; manifest_utils_add_subtitle_adaptation_sets( media_set, &labels[MEDIA_TYPE_SUBTITLE], output->first_by_type[ADAPTATION_TYPE_SUBTITLE], output->first->last); } } else { rc = manifest_utils_get_unmuxed_adaptation_sets( request_context, media_set, &labels[MEDIA_TYPE_SUBTITLE], output); if (rc != VOD_OK) { return rc; } } return VOD_OK; }
static vod_status_t manifest_utils_get_multilingual_adaptation_sets( request_context_t* request_context, media_set_t* media_set, uint32_t flags, label_track_count_array_t* labels, adaptation_sets_t* output) { adaptation_set_t* cur_adaptation_set; adaptation_set_t* adaptation_sets; media_track_t** cur_track_ptr; media_track_t* last_track; media_track_t* cur_track; label_track_count_t* cur_label; vod_status_t rc; uint32_t media_type; size_t adaptation_sets_count; size_t index; // get the number of adaptation sets adaptation_sets_count = labels[MEDIA_TYPE_AUDIO].count + labels[MEDIA_TYPE_SUBTITLE].count; output->count[ADAPTATION_TYPE_SUBTITLE] = labels[MEDIA_TYPE_SUBTITLE].count; if (media_set->track_count[MEDIA_TYPE_VIDEO] > 0) { if ((flags & ADAPTATION_SETS_FLAG_FORCE_MUXED) != 0) { output->count[ADAPTATION_TYPE_MUXED] = 1; output->count[ADAPTATION_TYPE_VIDEO] = 0; output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count - 1; } else { adaptation_sets_count++; output->count[ADAPTATION_TYPE_MUXED] = 0; output->count[ADAPTATION_TYPE_VIDEO] = 1; output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count; } } else { output->count[ADAPTATION_TYPE_MUXED] = 0; output->count[ADAPTATION_TYPE_VIDEO] = 0; output->count[ADAPTATION_TYPE_AUDIO] = labels[MEDIA_TYPE_AUDIO].count; } // allocate the adaptation sets and tracks adaptation_sets = vod_alloc(request_context->pool, sizeof(adaptation_sets[0]) * adaptation_sets_count + sizeof(adaptation_sets[0].first[0]) * media_set->total_track_count); if (adaptation_sets == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_get_multilingual_adaptation_sets: vod_alloc failed"); return VOD_ALLOC_FAILED; } cur_track_ptr = (void*)(adaptation_sets + adaptation_sets_count); cur_adaptation_set = adaptation_sets; // initialize the video adaptation set if (media_set->track_count[MEDIA_TYPE_VIDEO] > 0) { if (output->count[ADAPTATION_TYPE_MUXED] > 0) { output->first_by_type[ADAPTATION_TYPE_MUXED] = cur_adaptation_set; rc = manifest_utils_get_muxed_adaptation_set( request_context, media_set, flags, &labels[MEDIA_TYPE_AUDIO].first->label, cur_adaptation_set); if (rc != VOD_OK) { return rc; } cur_adaptation_set++; labels[MEDIA_TYPE_AUDIO].first++; // do not output this label separately } else { output->first_by_type[ADAPTATION_TYPE_VIDEO] = cur_adaptation_set; cur_adaptation_set->first = cur_track_ptr; cur_adaptation_set->count = 0; cur_adaptation_set->type = ADAPTATION_TYPE_VIDEO; cur_track_ptr += media_set->track_count[MEDIA_TYPE_VIDEO]; cur_adaptation_set->last = cur_track_ptr; cur_adaptation_set++; } } // initialize the audio/subtitle adaptation sets for (media_type = MEDIA_TYPE_AUDIO; media_type <= MEDIA_TYPE_SUBTITLE; media_type++) { output->first_by_type[media_type] = cur_adaptation_set; for (cur_label = labels[media_type].first; cur_label < labels[media_type].last; cur_label++) { cur_adaptation_set->first = cur_track_ptr; cur_adaptation_set->count = 0; cur_adaptation_set->type = media_type; if (media_type != MEDIA_TYPE_AUDIO || (flags & ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK) != 0) { cur_track_ptr++; } else { cur_track_ptr += cur_label->track_count; } cur_adaptation_set->last = cur_track_ptr; cur_adaptation_set++; } } last_track = media_set->filtered_tracks + media_set->total_track_count; for (cur_track = media_set->filtered_tracks; cur_track < last_track; cur_track++) { media_type = cur_track->media_info.media_type; switch (media_type) { case MEDIA_TYPE_AUDIO: case MEDIA_TYPE_SUBTITLE: if (cur_track->media_info.label.len == 0) { continue; } // find the label index cur_label = manifest_utils_find_label( &cur_track->media_info.label, &labels[media_type], &index); if (cur_label == NULL) { continue; } // find the adaptation set cur_adaptation_set = output->first_by_type[media_type] + index; if ((media_type != MEDIA_TYPE_AUDIO || (flags & ADAPTATION_SETS_FLAG_SINGLE_LANG_TRACK) != 0) && cur_adaptation_set->count != 0) { continue; } break; case MEDIA_TYPE_VIDEO: // in forced muxed mode, all video tracks were already added if (output->count[ADAPTATION_TYPE_MUXED] > 0) { continue; } cur_adaptation_set = adaptation_sets; break; default: continue; } // add the track to the adaptation set cur_adaptation_set->first[cur_adaptation_set->count++] = cur_track; } output->first = adaptation_sets; output->last = adaptation_sets + adaptation_sets_count; output->total_count = adaptation_sets_count; 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; }
static vod_status_t hls_muxer_init_base( hls_muxer_state_t* state, request_context_t* request_context, hls_muxer_conf_t* conf, hls_encryption_params_t* encryption_params, uint32_t segment_index, media_set_t* media_set, write_callback_t write_callback, void* write_context, bool_t* simulation_supported, vod_str_t* response_header) { mpegts_encoder_init_streams_state_t init_streams_state; media_track_t* track; hls_muxer_stream_state_t* cur_stream; const media_filter_t* next_filter; void* next_filter_context; vod_status_t rc; bool_t reuse_buffers; *simulation_supported = hls_muxer_simulation_supported(media_set, encryption_params); state->request_context = request_context; state->cur_frame = NULL; state->video_duration = 0; state->first_time = TRUE; state->media_set = media_set; state->use_discontinuity = media_set->use_discontinuity; if (encryption_params->type == HLS_ENC_AES_128) { rc = aes_cbc_encrypt_init( &state->encrypted_write_context, request_context, write_callback, write_context, encryption_params->key, encryption_params->iv); write_callback = (write_callback_t)aes_cbc_encrypt_write; write_context = state->encrypted_write_context; reuse_buffers = TRUE; // aes_cbc_encrypt allocates new buffers } else { state->encrypted_write_context = NULL; reuse_buffers = FALSE; } // init the write queue write_buffer_queue_init( &state->queue, request_context, write_callback, write_context, reuse_buffers); // init the packetizer streams and get the packet ids / stream ids rc = mpegts_encoder_init_streams( request_context, encryption_params, &state->queue, &init_streams_state, segment_index); if (rc != VOD_OK) { return rc; } // allocate the streams state->first_stream = vod_alloc(request_context->pool, sizeof(*state->first_stream) * (media_set->total_track_count + 1)); if (state->first_stream == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_base: vod_alloc failed (1)"); return VOD_ALLOC_FAILED; } state->last_stream = state->first_stream + media_set->total_track_count; track = media_set->filtered_tracks; for (cur_stream = state->first_stream; cur_stream < state->last_stream; cur_stream++, track++) { cur_stream->segment_limit = ULLONG_MAX; rc = mpegts_encoder_init( &cur_stream->mpegts_encoder_state, &init_streams_state, track, &state->queue, conf->interleave_frames, conf->align_frames); if (rc != VOD_OK) { return rc; } switch (track->media_info.media_type) { case MEDIA_TYPE_VIDEO: if (track->media_info.duration_millis > state->video_duration) { state->video_duration = track->media_info.duration_millis; } cur_stream->buffer_state = NULL; cur_stream->top_filter_context = vod_alloc(request_context->pool, sizeof(mp4_to_annexb_state_t)); if (cur_stream->top_filter_context == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_base: vod_alloc failed (2)"); return VOD_ALLOC_FAILED; } rc = mp4_to_annexb_init( cur_stream->top_filter_context, request_context, encryption_params, &mpegts_encoder, &cur_stream->mpegts_encoder_state); if (rc != VOD_OK) { return rc; } cur_stream->top_filter = &mp4_to_annexb; break; case MEDIA_TYPE_AUDIO: if (conf->interleave_frames) { // frame interleaving enabled, just join several audio frames according to timestamp cur_stream->buffer_state = NULL; next_filter_context = vod_alloc(request_context->pool, sizeof(frame_joiner_t)); if (next_filter_context == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_base: vod_alloc failed (3)"); return VOD_ALLOC_FAILED; } frame_joiner_init(next_filter_context, &mpegts_encoder, &cur_stream->mpegts_encoder_state); next_filter = &frame_joiner; } else { // no frame interleaving, buffer the audio until it reaches a certain size / delay from video cur_stream->buffer_state = vod_alloc(request_context->pool, sizeof(buffer_filter_t)); if (cur_stream->buffer_state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_base: vod_alloc failed (4)"); return VOD_ALLOC_FAILED; } rc = buffer_filter_init( cur_stream->buffer_state, request_context, &mpegts_encoder, &cur_stream->mpegts_encoder_state, conf->align_frames, DEFAULT_PES_PAYLOAD_SIZE); if (rc != VOD_OK) { return rc; } next_filter = &buffer_filter; next_filter_context = cur_stream->buffer_state; } if (track->media_info.codec_id == VOD_CODEC_ID_AAC) { cur_stream->top_filter_context = vod_alloc(request_context->pool, sizeof(adts_encoder_state_t)); if (cur_stream->top_filter_context == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_base: vod_alloc failed (5)"); return VOD_ALLOC_FAILED; } rc = adts_encoder_init( cur_stream->top_filter_context, request_context, encryption_params, next_filter, next_filter_context); if (rc != VOD_OK) { return rc; } cur_stream->top_filter = &adts_encoder; } else { if (encryption_params->type == HLS_ENC_SAMPLE_AES) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "hls_muxer_init_base: sample aes encryption is supported only for aac"); return VOD_BAD_REQUEST; } cur_stream->top_filter = next_filter; cur_stream->top_filter_context = next_filter_context; } break; } rc = hls_muxer_init_track(cur_stream, track); if (rc != VOD_OK) { return rc; } } state->first_clip_track = track; // init the id3 stream rc = hls_muxer_init_id3_stream(state, conf, &init_streams_state); if (rc != VOD_OK) { return rc; } mpegts_encoder_finalize_streams(&init_streams_state, response_header); if (media_set->timing.durations != NULL) { state->video_duration = media_set->timing.total_duration; } 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; }
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 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 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 segmenter_get_segment_durations_estimate_internal( request_context_t* request_context, segmenter_conf_t* conf, media_set_t* media_set, media_sequence_t* sequence, uint32_t* clip_durations, uint32_t total_clip_count, uint64_t cur_clip_duration, segment_durations_t* result) { align_to_key_frames_context_t align_context; segment_duration_item_t* cur_item; uint64_t clip_start_offset; uint64_t ignore; uint64_t next_clip_offset; uint64_t next_aligned_offset; uint64_t aligned_offset = 0; uint64_t clip_offset = 0; uint32_t* end_duration = clip_durations + total_clip_count; uint32_t* cur_duration = clip_durations; uint32_t bootstrap_segment_limit; uint32_t segment_index = media_set->initial_segment_index; uint32_t clip_segment_limit; uint32_t segment_duration; uint32_t alloc_count; bool_t discontinuity; if (sequence->key_frame_durations != NULL) { align_context.request_context = request_context; align_context.part = sequence->key_frame_durations; align_context.offset = sequence->first_key_frame_offset; align_context.cur_pos = align_context.part->first; alloc_count = conf->bootstrap_segments_count + total_clip_count + vod_div_ceil(result->end_time - result->start_time, conf->segment_duration); } else { vod_memzero(&align_context, sizeof(align_context)); alloc_count = conf->bootstrap_segments_count + 2 * total_clip_count; } // allocate the result buffer result->items = vod_alloc(request_context->pool, sizeof(result->items[0]) * alloc_count); if (result->items == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "segmenter_get_segment_durations_estimate_internal: vod_alloc failed"); return VOD_ALLOC_FAILED; } cur_item = result->items - 1; discontinuity = FALSE; for (;;) { // find the clip start offset segmenter_get_start_end_offsets(conf, segment_index, &clip_start_offset, &ignore); // get segment limit for the current clip clip_segment_limit = conf->get_segment_count(conf, clip_start_offset + cur_clip_duration); if (clip_segment_limit == INVALID_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_estimate_internal: segment count is invalid"); return VOD_BAD_DATA; } if (clip_segment_limit <= segment_index) { clip_segment_limit = segment_index + 1; } next_clip_offset = clip_offset + cur_clip_duration; // bootstrap segments bootstrap_segment_limit = vod_min(clip_segment_limit - 1, conf->bootstrap_segments_count); for (; segment_index < bootstrap_segment_limit; segment_index++) { segment_duration = conf->bootstrap_segments_durations[segment_index]; clip_offset += segment_duration; if (sequence->key_frame_durations != NULL) { next_aligned_offset = segmenter_align_to_key_frames(&align_context, clip_offset, next_clip_offset); segment_duration = next_aligned_offset - aligned_offset; aligned_offset = next_aligned_offset; } if (cur_item < result->items || segment_duration != cur_item->duration || discontinuity) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = segment_duration; cur_item->discontinuity = discontinuity; discontinuity = FALSE; } cur_item->repeat_count++; } // remaining segments if (sequence->key_frame_durations != NULL) { for (; segment_index + 1 < clip_segment_limit; segment_index++) { clip_offset += conf->segment_duration; next_aligned_offset = segmenter_align_to_key_frames(&align_context, clip_offset, next_clip_offset); segment_duration = next_aligned_offset - aligned_offset; aligned_offset = next_aligned_offset; if (cur_item < result->items || segment_duration != cur_item->duration || discontinuity) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = segment_duration; cur_item->discontinuity = discontinuity; discontinuity = FALSE; } cur_item->repeat_count++; } clip_offset = aligned_offset; // the last segment duration should be calcuated according to the aligned offset } else if (segment_index + 1 < clip_segment_limit) { segment_duration = conf->segment_duration; if (cur_item < result->items || segment_duration != cur_item->duration || discontinuity) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = segment_duration; cur_item->discontinuity = discontinuity; discontinuity = FALSE; } cur_item->repeat_count += clip_segment_limit - segment_index - 1; clip_offset += (uint64_t)segment_duration * (clip_segment_limit - segment_index - 1); segment_index = clip_segment_limit - 1; } // last segment if (segment_index < clip_segment_limit && clip_offset < next_clip_offset) { segment_duration = next_clip_offset - clip_offset; if (cur_item < result->items || segment_duration != cur_item->duration || discontinuity) { cur_item++; cur_item->repeat_count = 0; cur_item->segment_index = segment_index; cur_item->duration = segment_duration; cur_item->discontinuity = discontinuity; } cur_item->repeat_count++; segment_index = clip_segment_limit; } // move to the next clip cur_duration++; if (cur_duration >= end_duration) { break; } clip_offset = next_clip_offset; cur_clip_duration = *cur_duration; // update clip_start_offset discontinuity = TRUE; } // finalize the result result->segment_count = clip_segment_limit - media_set->initial_segment_index; if (result->segment_count > MAX_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_segment_durations_estimate_internal: segment count %uD is invalid", result->segment_count); return VOD_BAD_MAPPING; } result->item_count = cur_item + 1 - result->items; result->timescale = 1000; result->discontinuities = total_clip_count - 1; 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; }
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 vod_status_t hls_muxer_init_id3_stream( hls_muxer_state_t* state, hls_muxer_conf_t* conf, mpegts_encoder_init_streams_state_t* init_streams_state) { hls_muxer_stream_state_t* cur_stream; hls_muxer_stream_state_t* reference_stream; id3_context_t* context; vod_status_t rc; cur_stream = state->last_stream; // init the mpeg ts encoder rc = mpegts_encoder_init( &cur_stream->mpegts_encoder_state, init_streams_state, NULL, &state->queue, conf->interleave_frames, conf->align_frames); if (rc != VOD_OK) { return rc; } if (!conf->output_id3_timestamps) { return VOD_OK; } // get the stream that has the first frame rc = hls_muxer_choose_stream(state, &reference_stream); if (rc != VOD_OK) { if (rc == VOD_NOT_FOUND) { return VOD_OK; } return rc; } // allocate the context context = vod_alloc(state->request_context->pool, sizeof(*context)); if (context == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, state->request_context->log, 0, "hls_muxer_init_id3_stream: vod_alloc failed"); return VOD_ALLOC_FAILED; } // init the memory frames source rc = frames_source_memory_init(state->request_context, &cur_stream->cur_frame_part.frames_source_context); if (rc != VOD_OK) { return rc; } cur_stream->cur_frame_part.frames_source = &frames_source_memory; // base initialization cur_stream->media_type = MEDIA_TYPE_NONE; cur_stream->segment_limit = ULLONG_MAX; cur_stream->buffer_state = NULL; // init the id3 encoder id3_encoder_init(&context->encoder, &mpegts_encoder, &cur_stream->mpegts_encoder_state); cur_stream->top_filter = &id3_encoder; cur_stream->top_filter_context = &context->encoder; // copy the time stamps cur_stream->first_frame_time_offset = reference_stream->first_frame_time_offset; cur_stream->next_frame_time_offset = reference_stream->next_frame_time_offset; cur_stream->clip_from_frame_offset = reference_stream->clip_from_frame_offset; // init the frame part cur_stream->cur_frame = &context->frame; cur_stream->first_frame_part = &cur_stream->cur_frame_part; cur_stream->cur_frame_part.next = NULL; cur_stream->cur_frame_part.first_frame = &context->frame; cur_stream->cur_frame_part.last_frame = &context->frame + 1; cur_stream->source = NULL; // init the frame context->frame.size = vod_sprintf(context->data, ID3_TEXT_JSON_FORMAT, hls_rescale_to_millis(cur_stream->first_frame_time_offset)) - context->data; context->frame.duration = 0; context->frame.key_frame = 1; context->frame.pts_delay = 0; context->frame.offset = (uintptr_t)&context->data; state->last_stream++; return VOD_OK; }
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; }
vod_status_t hls_muxer_init_segment( request_context_t* request_context, hls_muxer_conf_t* conf, hls_encryption_params_t* encryption_params, uint32_t segment_index, media_set_t* media_set, write_callback_t write_callback, void* write_context, size_t* response_size, vod_str_t* response_header, hls_muxer_state_t** processor_state) { hls_muxer_state_t* state; bool_t simulation_supported; vod_status_t rc; state = vod_alloc(request_context->pool, sizeof(*state)); if (state == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "hls_muxer_init_segment: vod_alloc failed"); return VOD_ALLOC_FAILED; } rc = hls_muxer_init_base( state, request_context, conf, encryption_params, segment_index, media_set, write_callback, write_context, &simulation_supported, response_header); if (rc != VOD_OK) { return rc; } if (simulation_supported) { rc = hls_muxer_simulate_get_segment_size(state, response_size); if (rc != VOD_OK) { return rc; } hls_muxer_simulation_reset(state); } rc = hls_muxer_start_frame(state); if (rc != VOD_OK) { if (rc != VOD_NOT_FOUND) { return rc; } *processor_state = NULL; // no frames, nothing to do } else { *processor_state = state; } if (state->encrypted_write_context != NULL) { rc = aes_cbc_encrypt( state->encrypted_write_context, response_header, response_header, *processor_state == NULL); if (rc != VOD_OK) { return rc; } } 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; }
static vod_status_t manifest_utils_get_unique_labels( request_context_t* request_context, media_set_t* media_set, uint32_t media_type, label_track_count_array_t* output) { vod_str_t* cur_track_label; label_track_count_t* first_label; label_track_count_t* last_label; label_track_count_t* cur_label; media_track_t* last_track; media_track_t* cur_track; bool_t label_found; first_label = vod_alloc(request_context->pool, media_set->total_track_count * sizeof(first_label[0])); if (first_label == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_get_unique_labels: vod_alloc failed"); return VOD_ALLOC_FAILED; } last_label = first_label; last_track = media_set->filtered_tracks + media_set->total_track_count; for (cur_track = media_set->filtered_tracks; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type != media_type || cur_track->media_info.label.len == 0) { continue; } cur_track_label = &cur_track->media_info.label; label_found = FALSE; for (cur_label = first_label; cur_label < last_label; cur_label++) { if (vod_str_equals(*cur_track_label, cur_label->label)) { label_found = TRUE; break; } } if (label_found) { cur_label->track_count++; continue; } last_label->label = *cur_track_label; last_label->track_count = 1; last_label++; } output->first = first_label; output->last = last_label; output->count = last_label - first_label; 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 manifest_utils_get_muxed_adaptation_set( request_context_t* request_context, media_set_t* media_set, uint32_t flags, vod_str_t* label, adaptation_set_t* output) { media_sequence_t* cur_sequence; media_track_t** cur_track_ptr; media_track_t* audio_track; media_track_t* last_track; media_track_t* cur_track; uint32_t main_media_type; // allocate the tracks array cur_track_ptr = vod_alloc(request_context->pool, sizeof(output->first[0]) * media_set->total_track_count * MEDIA_TYPE_COUNT); if (cur_track_ptr == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_get_muxed_adaptation_set: vod_alloc failed"); return VOD_ALLOC_FAILED; } output->type = ADAPTATION_TYPE_MUXED; output->first = cur_track_ptr; output->count = 0; for (cur_sequence = media_set->sequences; cur_sequence < media_set->sequences_end; cur_sequence++) { // find the main media type if (cur_sequence->track_count[MEDIA_TYPE_VIDEO] > 0) { main_media_type = MEDIA_TYPE_VIDEO; } else if (cur_sequence->track_count[MEDIA_TYPE_AUDIO] > 0) { if ((flags & ADAPTATION_SETS_FLAG_AVOID_AUDIO_ONLY) != 0 && media_set->track_count[MEDIA_TYPE_VIDEO] > 0) { continue; } main_media_type = MEDIA_TYPE_AUDIO; } else { continue; } // find the audio track audio_track = cur_sequence->filtered_clips[0].longest_track[MEDIA_TYPE_AUDIO]; if ((audio_track == NULL || (label != NULL && !vod_str_equals(*label, audio_track->media_info.label))) && media_set->track_count[MEDIA_TYPE_AUDIO] > 0) { if (cur_sequence->track_count[MEDIA_TYPE_VIDEO] <= 0) { continue; } // find some audio track from another sequence to mux with this video last_track = media_set->filtered_tracks + media_set->total_track_count; for (cur_track = media_set->filtered_tracks; cur_track < last_track; cur_track++) { if (cur_track->media_info.media_type == MEDIA_TYPE_AUDIO && (label == NULL || vod_str_equals(*label, cur_track->media_info.label))) { audio_track = cur_track; break; } } } 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; } // add the track if (main_media_type == MEDIA_TYPE_VIDEO) { cur_track_ptr[MEDIA_TYPE_VIDEO] = cur_track; cur_track_ptr[MEDIA_TYPE_AUDIO] = audio_track; } else { cur_track_ptr[MEDIA_TYPE_VIDEO] = NULL; cur_track_ptr[MEDIA_TYPE_AUDIO] = cur_track; } cur_track_ptr[MEDIA_TYPE_SUBTITLE] = NULL; cur_track_ptr += MEDIA_TYPE_COUNT; output->count++; } } output->last = cur_track_ptr; return VOD_OK; }
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 manifest_utils_get_unmuxed_adaptation_sets( request_context_t* request_context, media_set_t* media_set, label_track_count_array_t* subtitle_labels, adaptation_sets_t* output) { label_track_count_t* cur_label; adaptation_set_t* cur_adaptation_set; adaptation_set_t* adaptation_sets; media_track_t** cur_track_ptr; media_track_t* last_track; media_track_t* cur_track; uint32_t media_type; size_t adaptation_sets_count; size_t index; // get the number of adaptation sets adaptation_sets_count = subtitle_labels->count; output->count[ADAPTATION_TYPE_MUXED] = 0; output->count[ADAPTATION_TYPE_SUBTITLE] = subtitle_labels->count; if (media_set->track_count[MEDIA_TYPE_VIDEO] > 0) { adaptation_sets_count++; output->count[ADAPTATION_TYPE_VIDEO] = 1; } else { output->count[ADAPTATION_TYPE_VIDEO] = 0; } if (media_set->track_count[MEDIA_TYPE_AUDIO] > 0) { adaptation_sets_count++; output->count[ADAPTATION_TYPE_AUDIO] = 1; } else { output->count[ADAPTATION_TYPE_AUDIO] = 0; } // allocate the adaptation sets adaptation_sets = vod_alloc(request_context->pool, sizeof(adaptation_sets[0]) * adaptation_sets_count + sizeof(adaptation_sets[0].first[0]) * media_set->total_track_count); if (adaptation_sets == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "manifest_utils_get_unmuxed_adaptation_sets: vod_alloc failed"); return VOD_ALLOC_FAILED; } cur_track_ptr = (void*)(adaptation_sets + adaptation_sets_count); // initialize the audio/video adaptation sets cur_adaptation_set = adaptation_sets; for (media_type = 0; media_type < MEDIA_TYPE_SUBTITLE; media_type++) { if (media_set->track_count[media_type] == 0) { continue; } output->first_by_type[media_type] = cur_adaptation_set; cur_adaptation_set->first = cur_track_ptr; cur_adaptation_set->count = 0; cur_adaptation_set->type = media_type; cur_track_ptr += media_set->track_count[media_type]; cur_adaptation_set->last = cur_track_ptr; cur_adaptation_set++; } // initialize the subtitle adaptation sets output->first_by_type[MEDIA_TYPE_SUBTITLE] = cur_adaptation_set; for (cur_label = subtitle_labels->first; cur_label < subtitle_labels->last; cur_label++) { cur_adaptation_set->first = cur_track_ptr; cur_adaptation_set->count = 0; cur_adaptation_set->type = MEDIA_TYPE_SUBTITLE; cur_track_ptr++; cur_adaptation_set->last = cur_track_ptr; cur_adaptation_set++; } // add the tracks to the adaptation sets last_track = media_set->filtered_tracks + media_set->total_track_count; for (cur_track = media_set->filtered_tracks; cur_track < last_track; cur_track++) { media_type = cur_track->media_info.media_type; switch (media_type) { case MEDIA_TYPE_AUDIO: case MEDIA_TYPE_VIDEO: cur_adaptation_set = output->first_by_type[media_type]; break; case MEDIA_TYPE_SUBTITLE: if (cur_track->media_info.label.len == 0) { continue; } // find the label index cur_label = manifest_utils_find_label( &cur_track->media_info.label, subtitle_labels, &index); if (cur_label == NULL) { continue; } // find the adaptation set cur_adaptation_set = output->first_by_type[MEDIA_TYPE_SUBTITLE] + index; if (cur_adaptation_set->count != 0) { continue; } break; default: // MEDIA_TYPE_NONE continue; } cur_adaptation_set->first[cur_adaptation_set->count++] = cur_track; } output->first = adaptation_sets; output->last = adaptation_sets + adaptation_sets_count; output->total_count = adaptation_sets_count; return VOD_OK; }
vod_status_t segmenter_get_start_end_ranges_no_discontinuity( get_clip_ranges_params_t* params, get_clip_ranges_result_t* result) { align_to_key_frames_context_t align_context; request_context_t* request_context = params->request_context; uint64_t start_time = params->start_time; uint64_t clip_start_offset = start_time; uint64_t next_start_offset; uint64_t start; uint64_t end; media_range_t* cur_clip_range; uint32_t* clip_durations = params->clip_durations; uint32_t* end_duration = clip_durations + params->total_clip_count; uint32_t* cur_duration; uint32_t segment_count; uint32_t index; SEGMENT_CHOOSE_HEADER(params->conf); result->clip_index_segment_index = 0; result->first_clip_segment_index = 0; // get the segment count segment_count = params->conf->get_segment_count(params->conf, params->end_time); if (segment_count == INVALID_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_no_discontinuity: segment count is invalid for total duration %uL", params->end_time); return VOD_BAD_DATA; } if (params->segment_index >= segment_count) { result->clip_count = 0; result->min_clip_index = 1; result->max_clip_index = 0; return VOD_OK; } // get the start / end offsets segmenter_get_start_end_offsets( params->conf, params->segment_index, &start, &end); if (start < start_time) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_no_discontinuity: segment start time %uL is less than sequence start time %uL", start, start_time); return VOD_BAD_REQUEST; } if (params->key_frame_durations != NULL) { align_context.request_context = request_context; align_context.part = params->key_frame_durations; align_context.offset = start_time + params->first_key_frame_offset; align_context.cur_pos = align_context.part->first; start = segmenter_align_to_key_frames(&align_context, start, params->last_segment_end); end = segmenter_align_to_key_frames(&align_context, end, params->last_segment_end); } if (params->segment_index + 1 >= segment_count) { end = params->last_segment_end; } // find min/max clip indexes and initial sequence offset result->min_clip_index = INVALID_CLIP_INDEX; result->max_clip_index = params->total_clip_count - 1; for (cur_duration = clip_durations; cur_duration < end_duration; cur_duration++, clip_start_offset = next_start_offset) { next_start_offset = clip_start_offset + *cur_duration; if (start >= next_start_offset) { continue; } if (start >= clip_start_offset) { result->min_clip_index = cur_duration - clip_durations; result->initial_sequence_offset = clip_start_offset; } if (end <= next_start_offset) { result->max_clip_index = cur_duration - clip_durations; break; } } if (result->min_clip_index == INVALID_CLIP_INDEX) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_no_discontinuity: invalid segment index %uD", params->segment_index); return VOD_BAD_REQUEST; } // allocate the clip ranges result->clip_count = result->max_clip_index - result->min_clip_index + 1; cur_clip_range = vod_alloc(request_context->pool, sizeof(result->clip_ranges[0]) * result->clip_count); if (cur_clip_range == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "segmenter_get_start_end_ranges_no_discontinuity: vod_alloc failed"); return VOD_ALLOC_FAILED; } result->clip_ranges = cur_clip_range; // initialize the clip ranges start -= result->initial_sequence_offset; end -= result->initial_sequence_offset; for (index = result->min_clip_index;; index++, cur_clip_range++) { cur_clip_range->timescale = 1000; cur_clip_range->start = start; if (index >= result->max_clip_index) { cur_clip_range->end = end; break; } cur_clip_range->end = clip_durations[index]; start = 0; end -= clip_durations[index]; } result->initial_sequence_offset -= start_time; return VOD_OK; }
vod_status_t mss_playready_get_fragment_writer( segment_writer_t* result, request_context_t* request_context, media_set_t* media_set, uint32_t segment_index, segment_writer_t* segment_writer, const u_char* iv, bool_t size_only, vod_str_t* fragment_header, size_t* total_fragment_size) { mp4_encrypt_passthrough_context_t passthrough_context; uint32_t media_type = media_set->sequences[0].media_type; vod_status_t rc; if (mp4_encrypt_passthrough_init(&passthrough_context, media_set->sequences)) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "mss_playready_get_fragment_writer: using encryption passthrough"); // build the fragment header rc = mss_packager_build_fragment_header( request_context, media_set, segment_index, passthrough_context.total_size + ATOM_HEADER_SIZE + sizeof(uuid_piff_atom_t), mss_playready_passthrough_write_encryption_atoms, &passthrough_context, size_only, fragment_header, total_fragment_size); if (rc != VOD_OK) { vod_log_debug1(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "mss_playready_get_fragment_writer: mss_packager_build_fragment_header failed %i", rc); return rc; } // use original writer vod_memzero(result, sizeof(*result)); return VOD_OK; } switch (media_type) { case MEDIA_TYPE_VIDEO: return mp4_encrypt_video_get_fragment_writer( result, request_context, media_set, segment_index, mss_playready_video_write_fragment_header, segment_writer, iv); case MEDIA_TYPE_AUDIO: rc = mp4_encrypt_audio_get_fragment_writer( result, request_context, media_set, segment_index, segment_writer, iv); if (rc != VOD_OK) { return rc; } rc = mss_playready_audio_build_fragment_header( result->context, size_only, fragment_header, total_fragment_size); if (rc != VOD_OK) { return rc; } return VOD_OK; } vod_log_error(VOD_LOG_ERR, request_context->log, 0, "mss_playready_get_fragment_writer: invalid media type %uD", media_type); return VOD_UNEXPECTED; }
vod_status_t segmenter_get_start_end_ranges_discontinuity( get_clip_ranges_params_t* params, get_clip_ranges_result_t* result) { align_to_key_frames_context_t align_context; request_context_t* request_context = params->request_context; segmenter_conf_t* conf = params->conf; uint64_t clip_start_offset; uint64_t start; uint64_t end; uint64_t ignore; uint32_t* end_duration = params->clip_durations + params->total_clip_count; uint32_t* cur_duration; uint32_t clip_index_segment_index = 0; uint32_t last_segment_limit = params->initial_segment_index; uint32_t cur_segment_limit; uint32_t segment_index = params->segment_index; uint32_t clip_index = params->clip_index; media_range_t* cur_clip_range; uint64_t prev_clips_duration = 0; SEGMENT_CHOOSE_HEADER(conf); for (cur_duration = params->clip_durations;; cur_duration++) { if (cur_duration >= end_duration) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_discontinuity: invalid segment index %uD or clip index", segment_index); return VOD_BAD_REQUEST; } // get the clip start offset segmenter_get_start_end_offsets(conf, last_segment_limit, &clip_start_offset, &ignore); // get segment limit for the current clip cur_segment_limit = conf->get_segment_count(conf, clip_start_offset + *cur_duration); if (cur_segment_limit == INVALID_SEGMENT_COUNT) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_discontinuity: invalid segment count"); return VOD_BAD_DATA; } if (cur_segment_limit <= last_segment_limit) { cur_segment_limit = last_segment_limit + 1; } if (clip_index == 1) { clip_index_segment_index = cur_segment_limit - params->initial_segment_index; segment_index += clip_index_segment_index; } if (clip_index > 0 && clip_index != INVALID_CLIP_INDEX) { clip_index--; } else if (segment_index < cur_segment_limit) { // the segment index is within this clip, break break; } // move to the next clip prev_clips_duration += *cur_duration; last_segment_limit = cur_segment_limit; } if (segment_index < last_segment_limit) { vod_log_error(VOD_LOG_ERR, request_context->log, 0, "segmenter_get_start_end_ranges_discontinuity: segment index %uD smaller than last segment index %uD", segment_index, last_segment_limit); return VOD_BAD_REQUEST; } // get start / end position relative to the clip start segmenter_get_start_end_offsets( conf, segment_index, &start, &end); start -= clip_start_offset; if (segment_index + 1 >= cur_segment_limit) { end = *cur_duration; // last segment in clip } else { end -= clip_start_offset; } if (params->key_frame_durations != NULL) { align_context.request_context = request_context; align_context.part = params->key_frame_durations; align_context.offset = params->first_key_frame_offset - prev_clips_duration; align_context.cur_pos = align_context.part->first; start = segmenter_align_to_key_frames(&align_context, start, *cur_duration); end = segmenter_align_to_key_frames(&align_context, end, *cur_duration); } // initialize the clip range cur_clip_range = vod_alloc(request_context->pool, sizeof(cur_clip_range[0])); if (cur_clip_range == NULL) { vod_log_debug0(VOD_LOG_DEBUG_LEVEL, request_context->log, 0, "segmenter_get_start_end_ranges_discontinuity: vod_alloc failed"); return VOD_ALLOC_FAILED; } cur_clip_range->timescale = 1000; cur_clip_range->start = start; cur_clip_range->end = end; // initialize the result result->initial_sequence_offset = prev_clips_duration; result->min_clip_index = result->max_clip_index = cur_duration - params->clip_durations; result->clip_count = 1; result->clip_ranges = cur_clip_range; result->clip_index_segment_index = clip_index_segment_index; result->first_clip_segment_index = last_segment_limit; return VOD_OK; }