diff --git a/README.md b/README.md index 64a9e971..a018732e 100644 --- a/README.md +++ b/README.md @@ -1433,6 +1433,15 @@ Sets the MPD format, available options are: * `segmenttemplate` - uses SegmentTemplate, reporting a single duration for all fragments * `segmenttimeline` - uses SegmentTemplate and SegmentTimeline to explicitly set the duration of the fragments +#### vod_dash_subtitle_format +* **syntax**: `vod_dash_subtitle_format format` +* **default**: `webvtt` +* **context**: `http`, `server`, `location` + +Sets the format of the subtitles returned in the MPD, available options are: +* `webvtt` - WebVTT +* `smpte-tt` - SMPTE Timed Text + #### vod_dash_init_mp4_pssh * **syntax**: `vod_dash_init_mp4_pssh on/off` * **default**: `on` diff --git a/ngx_http_vod_dash.c b/ngx_http_vod_dash.c index eed5d8d4..a913529b 100644 --- a/ngx_http_vod_dash.c +++ b/ngx_http_vod_dash.c @@ -7,6 +7,7 @@ #include "vod/mp4/mp4_fragment.h" #include "vod/mp4/mp4_init_segment.h" #include "vod/subtitle/webvtt_builder.h" +#include "vod/subtitle/ttml_builder.h" #include "vod/udrm.h" #if (NGX_HAVE_OPENSSL_EVP) @@ -25,6 +26,12 @@ ngx_conf_enum_t dash_manifest_formats[] = { { ngx_null_string, 0 } }; +ngx_conf_enum_t dash_subtitle_formats[] = { + { ngx_string("webvtt"), SUBTITLE_FORMAT_WEBVTT }, + { ngx_string("smpte-tt"), SUBTITLE_FORMAT_SMPTE_TT }, + { ngx_null_string, 0 } +}; + // content types static u_char mpd_content_type[] = "application/dash+xml"; static u_char webm_audio_content_type[] = "audio/webm"; @@ -37,6 +44,7 @@ static const u_char init_segment_file_ext[] = ".mp4"; static const u_char fragment_file_ext[] = ".m4s"; static const u_char webm_file_ext[] = ".webm"; static const u_char vtt_file_ext[] = ".vtt"; +static const u_char ttml_file_ext[] = ".ttml"; static ngx_int_t ngx_http_vod_dash_handle_manifest( @@ -386,7 +394,7 @@ ngx_http_vod_dash_handle_vtt_file( ngx_str_t* content_type) { vod_status_t rc; - + rc = webvtt_builder_build( &submodule_context->request_context, &submodule_context->media_set, @@ -404,6 +412,42 @@ ngx_http_vod_dash_handle_vtt_file( return NGX_OK; } +static ngx_int_t +ngx_http_vod_dash_handle_ttml_fragment( + ngx_http_vod_submodule_context_t* submodule_context, + ngx_str_t* response, + ngx_str_t* content_type) +{ + dash_fragment_header_extensions_t header_extensions; + vod_status_t rc; + size_t ignore; + + ngx_memzero(&header_extensions, sizeof(header_extensions)); + + header_extensions.mdat_atom_max_size = ttml_builder_get_max_size(&submodule_context->media_set); + header_extensions.write_mdat_atom_callback = (dash_write_mdat_atom_callback_t)ttml_builder_write; + header_extensions.write_mdat_atom_context = &submodule_context->media_set; + + rc = dash_packager_build_fragment_header( + &submodule_context->request_context, + &submodule_context->media_set, + submodule_context->request_params.segment_index, + 0, + &header_extensions, + FALSE, + response, + &ignore); + if (rc != VOD_OK) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, submodule_context->request_context.log, 0, + "ngx_http_vod_dash_handle_ttml_fragment: dash_packager_build_fragment_header failed %i", rc); + return ngx_http_vod_status_to_ngx_error(submodule_context->r, rc); + } + + mp4_fragment_get_content_type(TRUE, content_type); + return NGX_OK; +} + static const ngx_http_vod_request_t dash_manifest_request = { REQUEST_FLAG_TIME_DEPENDENT_ON_LIVE, PARSE_FLAG_DURATION_LIMITS_AND_TOTAL_SIZE | PARSE_FLAG_INITIAL_PTS_DELAY | PARSE_FLAG_CODEC_NAME, @@ -418,7 +462,7 @@ static const ngx_http_vod_request_t dash_mp4_init_request = { REQUEST_FLAG_SINGLE_TRACK, PARSE_BASIC_METADATA_ONLY | PARSE_FLAG_SAVE_RAW_ATOMS, REQUEST_CLASS_OTHER, - SUPPORTED_CODECS_MP4, + SUPPORTED_CODECS_MP4 | VOD_CODEC_FLAG(WEBVTT), DASH_TIMESCALE, ngx_http_vod_dash_mp4_handle_init_segment, NULL, @@ -474,6 +518,16 @@ static const ngx_http_vod_request_t dash_webvtt_file_request = { NULL, }; +static const ngx_http_vod_request_t dash_ttml_request = { + REQUEST_FLAG_SINGLE_TRACK, + PARSE_FLAG_FRAMES_ALL | PARSE_FLAG_EXTRA_DATA, + REQUEST_CLASS_SEGMENT, + VOD_CODEC_FLAG(WEBVTT), + TTML_TIMESCALE, + ngx_http_vod_dash_handle_ttml_fragment, + NULL, +}; + static void ngx_http_vod_dash_create_loc_conf( ngx_conf_t *cf, @@ -482,6 +536,7 @@ ngx_http_vod_dash_create_loc_conf( conf->absolute_manifest_urls = NGX_CONF_UNSET; conf->init_mp4_pssh = NGX_CONF_UNSET; conf->mpd_config.manifest_format = NGX_CONF_UNSET_UINT; + conf->mpd_config.subtitle_format = NGX_CONF_UNSET_UINT; conf->mpd_config.duplicate_bitrate_threshold = NGX_CONF_UNSET_UINT; conf->mpd_config.write_playready_kid = NGX_CONF_UNSET; conf->mpd_config.use_base_url_tag = NGX_CONF_UNSET; @@ -503,6 +558,7 @@ ngx_http_vod_dash_merge_loc_conf( ngx_conf_merge_str_value(conf->mpd_config.fragment_file_name_prefix, prev->mpd_config.fragment_file_name_prefix, "fragment"); ngx_conf_merge_str_value(conf->mpd_config.subtitle_file_name_prefix, prev->mpd_config.subtitle_file_name_prefix, "sub"); ngx_conf_merge_uint_value(conf->mpd_config.manifest_format, prev->mpd_config.manifest_format, FORMAT_SEGMENT_TIMELINE); + ngx_conf_merge_uint_value(conf->mpd_config.subtitle_format, prev->mpd_config.subtitle_format, SUBTITLE_FORMAT_WEBVTT); ngx_conf_merge_uint_value(conf->mpd_config.duplicate_bitrate_threshold, prev->mpd_config.duplicate_bitrate_threshold, 4096); ngx_conf_merge_value(conf->mpd_config.write_playready_kid, prev->mpd_config.write_playready_kid, 0); ngx_conf_merge_value(conf->mpd_config.use_base_url_tag, prev->mpd_config.use_base_url_tag, 0); @@ -568,6 +624,14 @@ ngx_http_vod_dash_parse_uri_file_name( *request = &dash_manifest_request; flags = PARSE_FILE_NAME_MULTI_STREAMS_PER_TYPE; } + // smpte fragment + else if (ngx_http_vod_match_prefix_postfix(start_pos, end_pos, &conf->dash.mpd_config.fragment_file_name_prefix, ttml_file_ext)) + { + start_pos += conf->dash.mpd_config.fragment_file_name_prefix.len; + end_pos -= (sizeof(ttml_file_ext) - 1); + *request = &dash_ttml_request; + flags = PARSE_FILE_NAME_EXPECT_SEGMENT_INDEX; + } // webvtt file else if (ngx_http_vod_match_prefix_postfix(start_pos, end_pos, &conf->dash.mpd_config.subtitle_file_name_prefix, vtt_file_ext)) { diff --git a/ngx_http_vod_dash_commands.h b/ngx_http_vod_dash_commands.h index 99c0a6a6..3e086c63 100644 --- a/ngx_http_vod_dash_commands.h +++ b/ngx_http_vod_dash_commands.h @@ -56,6 +56,13 @@ BASE_OFFSET + offsetof(ngx_http_vod_dash_loc_conf_t, mpd_config.manifest_format), dash_manifest_formats }, + { ngx_string("vod_dash_subtitle_format"), + NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, + ngx_conf_set_enum_slot, + NGX_HTTP_LOC_CONF_OFFSET, + BASE_OFFSET + offsetof(ngx_http_vod_dash_loc_conf_t, mpd_config.subtitle_format), + dash_subtitle_formats }, + { ngx_string("vod_dash_duplicate_bitrate_threshold"), NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1, ngx_conf_set_num_slot, diff --git a/ngx_http_vod_dash_conf.h b/ngx_http_vod_dash_conf.h index 71ff5004..a530d3b1 100644 --- a/ngx_http_vod_dash_conf.h +++ b/ngx_http_vod_dash_conf.h @@ -17,4 +17,6 @@ typedef struct // globals extern ngx_conf_enum_t dash_manifest_formats[]; +extern ngx_conf_enum_t dash_subtitle_formats[]; + #endif // _NGX_HTTP_VOD_DASH_CONF_H_INCLUDED_ diff --git a/ngx_http_vod_module.c b/ngx_http_vod_module.c index a46c46c9..d642c407 100644 --- a/ngx_http_vod_module.c +++ b/ngx_http_vod_module.c @@ -1620,6 +1620,7 @@ ngx_http_vod_init_parse_params_frames( ctx->submodule_context.media_set.segment_duration = clip_ranges.clip_ranges->end - clip_ranges.clip_ranges->start; } + ctx->submodule_context.media_set.segment_start_time = clip_ranges.clip_ranges->start; parse_params->range = clip_ranges.clip_ranges; parse_params->range->start = (parse_params->range->start * rate.num) / rate.denom; diff --git a/vod/dash/dash_packager.c b/vod/dash/dash_packager.c index 6f64cab6..865a4ed2 100644 --- a/vod/dash/dash_packager.c +++ b/vod/dash/dash_packager.c @@ -106,13 +106,27 @@ " startWithSAP=\"1\"\n" \ " bandwidth=\"%uD\">\n" +#define VOD_DASH_MANIFEST_ADAPTATION_HEADER_SUBTITLE_SMPTE_TT \ + " \n" + +#define VOD_DASH_MANIFEST_REPRESENTATION_HEADER_SUBTITLE_SMPTE_TT \ + " \n" + #define VOD_DASH_MANIFEST_REPRESENTATION_FOOTER \ " \n" #define VOD_DASH_MANIFEST_ADAPTATION_FOOTER \ " \n" -#define VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE \ +#define VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE_VTT \ " \n" \ - " %V%V-%s%V.vtt\n" \ + " %V%V-%s%V.vtt\n" \ " \n" \ " \n" + // SegmentTemplate #define VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED \ " conf->subtitle_format == SUBTITLE_FORMAT_SMPTE_TT) + { + reference_track = (*adaptation_set->first) + filtered_clip_offset; + p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_HEADER_SUBTITLE_SMPTE_TT, + lang_get_rfc_5646_name(reference_track->media_info.language), + &reference_track->media_info.label); + break; + } + cur_track = (*adaptation_set->first) + filtered_clip_offset; cur_sequence = cur_track->file_info.source->sequence; @@ -837,7 +865,7 @@ dash_packager_write_mpd_period( } lang_code = lang_get_rfc_5646_name(cur_track->media_info.language); - p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE, + p = vod_sprintf(p, VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE_VTT, lang_code, &cur_track->media_info.label, lang_code, @@ -951,6 +979,17 @@ dash_packager_write_mpd_period( cur_track->media_info.u.audio.sample_rate, cur_track->media_info.bitrate); break; + + case MEDIA_TYPE_SUBTITLE: + if (representation_id.len > 0 && representation_id.data[representation_id.len - 1] == '-') + { + representation_id.len--; + } + + p = vod_sprintf(p, + VOD_DASH_MANIFEST_REPRESENTATION_HEADER_SUBTITLE_SMPTE_TT, + &representation_id); + break; } if (context->conf->manifest_format == FORMAT_SEGMENT_LIST) @@ -998,6 +1037,7 @@ dash_packager_get_segment_list_total_size( media_track_t* last_track; media_track_t* cur_track; uint32_t filtered_clip_offset; + uint32_t max_media_type; uint32_t period_count = media_set->use_discontinuity ? media_set->timing.total_count : 1; uint32_t segment_count; uint32_t clip_index; @@ -1005,7 +1045,18 @@ dash_packager_get_segment_list_total_size( size_t base_url_len = 0; size_t result = 0; - for (media_type = 0; media_type < MEDIA_TYPE_SUBTITLE; media_type++) + switch (conf->subtitle_format) + { + case SUBTITLE_FORMAT_WEBVTT: + max_media_type = MEDIA_TYPE_SUBTITLE; + break; + + default: // SUBTITLE_FORMAT_SMPTE_TT + max_media_type = MEDIA_TYPE_COUNT; + break; + } + + for (media_type = 0; media_type < max_media_type; media_type++) { if (media_set->track_count[media_type] == 0) { @@ -1109,7 +1160,7 @@ dash_packager_remove_redundant_tracks( } // remove the track from all clips media_set->track_count[remove->media_info.media_type]--; - + for (clip_index = 0; clip_index < media_set->clip_count; clip_index++) { remove[clip_index * media_set->total_track_count].media_info.media_type = MEDIA_TYPE_NONE; @@ -1196,8 +1247,10 @@ dash_packager_build_mpd( uint32_t filtered_clip_offset; uint32_t presentation_delay; uint32_t min_update_period; - uint32_t window_size; + uint32_t adaptation_count; + uint32_t max_media_type; uint32_t period_count = media_set->use_discontinuity ? media_set->timing.total_count : 1; + uint32_t window_size; uint32_t media_type; uint32_t clip_index; vod_status_t rc; @@ -1279,14 +1332,31 @@ dash_packager_build_mpd( // audio representations (sizeof(VOD_DASH_MANIFEST_REPRESENTATION_HEADER_AUDIO) - 1 + MAX_TRACK_SPEC_LENGTH + MAX_MIME_TYPE_SIZE + MAX_CODEC_NAME_SIZE + 2 * VOD_INT32_LEN + sizeof(VOD_DASH_MANIFEST_REPRESENTATION_FOOTER) - 1) * media_set->track_count[MEDIA_TYPE_AUDIO] + - // subtitle adaptations - (sizeof(VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE) - 1 + 2 * LANG_ISO639_3_LEN + VOD_INT32_LEN + - context.base_url.len + conf->subtitle_file_name_prefix.len + MAX_CLIP_SPEC_LENGTH + MAX_TRACK_SPEC_LENGTH) * - context.adaptation_sets.count[ADAPTATION_TYPE_SUBTITLE] + sizeof(VOD_DASH_MANIFEST_PERIOD_FOOTER) - 1 + extensions->representation.size + extensions->adaptation_set.size; + switch (conf->subtitle_format) + { + case SUBTITLE_FORMAT_WEBVTT: + base_period_size += + // subtitle adaptations + (sizeof(VOD_DASH_MANIFEST_ADAPTATION_SUBTITLE_VTT) - 1 + 2 * LANG_ISO639_3_LEN + VOD_INT32_LEN + + context.base_url.len + conf->subtitle_file_name_prefix.len + MAX_CLIP_SPEC_LENGTH + MAX_TRACK_SPEC_LENGTH) * + context.adaptation_sets.count[ADAPTATION_TYPE_SUBTITLE]; + break; + + default: // SUBTITLE_FORMAT_SMPTE_TT + base_period_size += + // subtitle adaptations + (sizeof(VOD_DASH_MANIFEST_ADAPTATION_HEADER_SUBTITLE_SMPTE_TT) - 1 + LANG_ISO639_3_LEN + + sizeof(VOD_DASH_MANIFEST_ADAPTATION_FOOTER) - 1) * context.adaptation_sets.count[ADAPTATION_TYPE_SUBTITLE] + + // subtitle representations + (sizeof(VOD_DASH_MANIFEST_REPRESENTATION_HEADER_SUBTITLE_SMPTE_TT) - 1 + MAX_TRACK_SPEC_LENGTH + + sizeof(VOD_DASH_MANIFEST_REPRESENTATION_FOOTER) - 1) * media_set->track_count[MEDIA_TYPE_SUBTITLE]; + break; + } + switch (media_set->type) { case MEDIA_SET_VOD: @@ -1322,15 +1392,37 @@ dash_packager_build_mpd( switch (conf->manifest_format) { case FORMAT_SEGMENT_TEMPLATE: + + switch (conf->subtitle_format) + { + case SUBTITLE_FORMAT_WEBVTT: + adaptation_count = context.adaptation_sets.count[MEDIA_TYPE_VIDEO] + context.adaptation_sets.count[MEDIA_TYPE_AUDIO]; + break; + + default: // SUBTITLE_FORMAT_SMPTE_TT + adaptation_count = context.adaptation_sets.total_count; + break; + } + result_size += (sizeof(VOD_DASH_MANIFEST_SEGMENT_TEMPLATE_FIXED) - 1 + VOD_INT32_LEN + VOD_INT64_LEN + - MAX_INDEX_SHIFT_LENGTH + urls_length) * - (context.adaptation_sets.count[MEDIA_TYPE_VIDEO] + context.adaptation_sets.count[MEDIA_TYPE_AUDIO]) * - period_count; + MAX_INDEX_SHIFT_LENGTH + urls_length) * adaptation_count * period_count; break; case FORMAT_SEGMENT_TIMELINE: - for (media_type = 0; media_type < MEDIA_TYPE_SUBTITLE; media_type++) + + switch (conf->subtitle_format) + { + case SUBTITLE_FORMAT_WEBVTT: + max_media_type = MEDIA_TYPE_SUBTITLE; + break; + + default: // SUBTITLE_FORMAT_SMPTE_TT + max_media_type = MEDIA_TYPE_COUNT; + break; + } + + for (media_type = 0; media_type < max_media_type; media_type++) { if (context.adaptation_sets.count[media_type] == 0) { @@ -1499,7 +1591,7 @@ dash_packager_get_earliest_pres_time(media_set_t* media_set, media_track_t* trac { uint64_t result; uint64_t clip_start_time; - + if (media_set->use_discontinuity) { clip_start_time = media_set->timing.original_first_time; @@ -1582,6 +1674,14 @@ dash_packager_init_sidx_params(media_set_t* media_set, media_sequence_t* sequenc uint64_t total_frames_duration; bool_t frame_found = FALSE; + if (sequence->media_type == MEDIA_TYPE_SUBTITLE) + { + result->timescale = DASH_TIMESCALE; + result->earliest_pres_time = rescale_time(media_set->segment_start_time, 1000, DASH_TIMESCALE); + result->total_frames_duration = rescale_time(media_set->segment_duration, 1000, DASH_TIMESCALE); + return; + } + // initialize according to the first clip cur_clip = sequence->filtered_clips; track = cur_clip->first_track; @@ -1620,6 +1720,7 @@ dash_packager_build_fragment_header( media_sequence_t* sequence = &media_set->sequences[0]; media_track_t* first_track = sequence->filtered_clips[0].first_track; sidx_params_t sidx_params; + uint32_t duration; size_t first_frame_offset; size_t mdat_atom_size; size_t trun_atom_size; @@ -1627,7 +1728,9 @@ dash_packager_build_fragment_header( size_t moof_atom_size; size_t traf_atom_size; size_t result_size; + u_char* mdat_start; u_char* p; + u_char* sample_size; // calculate sizes dash_packager_init_sidx_params(media_set, sequence, &sidx_params); @@ -1645,21 +1748,21 @@ dash_packager_build_fragment_header( ATOM_HEADER_SIZE + tfhd_atom_size + ATOM_HEADER_SIZE + (sidx_params.earliest_pres_time > UINT_MAX ? sizeof(tfdt64_atom_t) : sizeof(tfdt_atom_t)) + - trun_atom_size + + trun_atom_size + extensions->extra_traf_atoms_size; moof_atom_size = ATOM_HEADER_SIZE + - ATOM_HEADER_SIZE + sizeof(mfhd_atom_t)+ + ATOM_HEADER_SIZE + sizeof(mfhd_atom_t) + traf_atom_size; - *total_fragment_size = + *total_fragment_size = (media_set->version >= 2 ? sizeof(styp_atom_v2) : 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; + result_size = *total_fragment_size - sequence->total_frame_size + extensions->mdat_atom_max_size; // head request optimization if (size_only) @@ -1723,11 +1826,23 @@ dash_packager_build_fragment_header( // moof.traf.trun first_frame_offset = moof_atom_size + ATOM_HEADER_SIZE; - p = mp4_fragment_write_trun_atom( - p, - sequence, - first_frame_offset, - media_set->version >= 2 ? 1 : 0); + sample_size = NULL; + + switch (sequence->media_type) + { + case MEDIA_TYPE_VIDEO: + p = mp4_fragment_write_video_trun_atom(p, sequence, first_frame_offset, media_set->version >= 2 ? 1 : 0); + break; + + case MEDIA_TYPE_AUDIO: + p = mp4_fragment_write_audio_trun_atom(p, sequence, first_frame_offset); + break; + + case MEDIA_TYPE_SUBTITLE: + duration = rescale_time(media_set->segment_duration, 1000, DASH_TIMESCALE); + p = mp4_fragment_write_subtitle_trun_atom(p, first_frame_offset, duration, &sample_size); + break; + } // moof.traf.xxx if (extensions->write_extra_traf_atoms_callback != NULL) @@ -1736,11 +1851,24 @@ dash_packager_build_fragment_header( } // mdat + mdat_start = p; write_atom_header(p, mdat_atom_size, 'm', 'd', 'a', 't'); + if (extensions->write_mdat_atom_callback != NULL) + { + p = extensions->write_mdat_atom_callback(extensions->write_mdat_atom_context, p); + mdat_atom_size = p - mdat_start; + write_be32(mdat_start, mdat_atom_size); + + if (sample_size != NULL) + { + write_be32(sample_size, mdat_atom_size - ATOM_HEADER_SIZE); + } + } + result->len = p - result->data; - if (result->len != result_size) + 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", diff --git a/vod/dash/dash_packager.h b/vod/dash/dash_packager.h index 6eae60eb..f36610c9 100644 --- a/vod/dash/dash_packager.h +++ b/vod/dash/dash_packager.h @@ -16,8 +16,15 @@ enum { FORMAT_SEGMENT_TEMPLATE, }; +enum { + SUBTITLE_FORMAT_WEBVTT, + SUBTITLE_FORMAT_SMPTE_TT, +}; + typedef u_char* (*dash_write_extra_traf_atoms_callback_t)(void* context, u_char* p, size_t mdat_atom_start); +typedef u_char* (*dash_write_mdat_atom_callback_t)(void* context, u_char* p); + typedef u_char* (*write_tags_callback_t)(void* context, u_char* p, media_track_t* track); typedef struct { @@ -32,6 +39,7 @@ typedef struct { vod_str_t fragment_file_name_prefix; vod_str_t subtitle_file_name_prefix; vod_uint_t manifest_format; + vod_uint_t subtitle_format; vod_uint_t duplicate_bitrate_threshold; bool_t write_playready_kid; // TODO: remove bool_t use_base_url_tag; // TODO: remove - if supported by all devices, always use BaseURL @@ -46,6 +54,10 @@ typedef struct { size_t extra_traf_atoms_size; dash_write_extra_traf_atoms_callback_t write_extra_traf_atoms_callback; void* write_extra_traf_atoms_context; + + size_t mdat_atom_max_size; + dash_write_mdat_atom_callback_t write_mdat_atom_callback; + void* write_mdat_atom_context; } dash_fragment_header_extensions_t; // functions diff --git a/vod/dash/edash_packager.c b/vod/dash/edash_packager.c index 410de808..2767ec9d 100644 --- a/vod/dash/edash_packager.c +++ b/vod/dash/edash_packager.c @@ -369,6 +369,8 @@ edash_packager_video_build_fragment_header( dash_fragment_header_extensions_t header_extensions; // get the header extensions + vod_memzero(&header_extensions, sizeof(header_extensions)); + header_extensions.extra_traf_atoms_size = state->base.saiz_atom_size + state->base.saio_atom_size + @@ -420,6 +422,8 @@ edash_packager_audio_build_fragment_header( vod_status_t rc; // get the header extensions + vod_memzero(&header_extensions, sizeof(header_extensions)); + header_extensions.extra_traf_atoms_size = state->saiz_atom_size + state->saio_atom_size + @@ -502,6 +506,8 @@ edash_packager_get_fragment_writer( "edash_packager_get_fragment_writer: using encryption passthrough"); // get the header extensions + vod_memzero(&header_extensions, sizeof(header_extensions)); + header_extensions.extra_traf_atoms_size = passthrough_context.total_size + ATOM_HEADER_SIZE + sizeof(senc_atom_t); header_extensions.write_extra_traf_atoms_callback = edash_packager_passthrough_write_encryption_atoms; header_extensions.write_extra_traf_atoms_context = &passthrough_context; diff --git a/vod/mp4/mp4_fragment.c b/vod/mp4/mp4_fragment.c index 31b5e2e9..73a095f7 100644 --- a/vod/mp4/mp4_fragment.c +++ b/vod/mp4/mp4_fragment.c @@ -72,14 +72,17 @@ mp4_fragment_get_trun_atom_size(uint32_t media_type, uint32_t frame_count) case MEDIA_TYPE_AUDIO: return ATOM_HEADER_SIZE + sizeof(trun_atom_t) + frame_count * sizeof(trun_audio_frame_t); + + case MEDIA_TYPE_SUBTITLE: + return ATOM_HEADER_SIZE + sizeof(trun_atom_t) + sizeof(trun_audio_frame_t); } return 0; } -static u_char* +u_char* mp4_fragment_write_video_trun_atom( - u_char* p, - media_sequence_t* sequence, + u_char* p, + media_sequence_t* sequence, uint32_t first_frame_offset, uint32_t version) { @@ -139,8 +142,11 @@ mp4_fragment_write_video_trun_atom( return p; } -static u_char* -mp4_fragment_write_audio_trun_atom(u_char* p, media_sequence_t* sequence, uint32_t first_frame_offset) +u_char* +mp4_fragment_write_audio_trun_atom( + u_char* p, + media_sequence_t* sequence, + uint32_t first_frame_offset) { media_clip_filtered_t* cur_clip; frame_list_part_t* part; @@ -180,22 +186,24 @@ mp4_fragment_write_audio_trun_atom(u_char* p, media_sequence_t* sequence, uint32 } u_char* -mp4_fragment_write_trun_atom( - u_char* p, - media_sequence_t* sequence, +mp4_fragment_write_subtitle_trun_atom( + u_char* p, uint32_t first_frame_offset, - uint32_t version) + uint32_t duration, + u_char** sample_size) { - switch (sequence->media_type) - { - case MEDIA_TYPE_VIDEO: - p = mp4_fragment_write_video_trun_atom(p, sequence, first_frame_offset, version); - break; + uint32_t atom_size; + + atom_size = ATOM_HEADER_SIZE + sizeof(trun_atom_t) + sizeof(trun_audio_frame_t); + write_atom_header(p, atom_size, 't', 'r', 'u', 'n'); + write_be32(p, TRUN_AUDIO_FLAGS); // flags = data offset, duration, size + write_be32(p, 1); // sample count = 1 + write_be32(p, first_frame_offset); + + write_be32(p, duration); + *sample_size = p; + write_be32(p, 0); - case MEDIA_TYPE_AUDIO: - p = mp4_fragment_write_audio_trun_atom(p, sequence, first_frame_offset); - break; - } return p; } diff --git a/vod/mp4/mp4_fragment.h b/vod/mp4/mp4_fragment.h index cc5bfae2..16cfb344 100644 --- a/vod/mp4/mp4_fragment.h +++ b/vod/mp4/mp4_fragment.h @@ -62,12 +62,24 @@ u_char* mp4_fragment_write_tfdt64_atom(u_char* p, uint64_t earliest_pres_time); size_t mp4_fragment_get_trun_atom_size(uint32_t media_type, uint32_t frame_count); -u_char* mp4_fragment_write_trun_atom( - u_char* p, - media_sequence_t* sequence, +u_char* mp4_fragment_write_video_trun_atom( + u_char* p, + media_sequence_t* sequence, uint32_t first_frame_offset, uint32_t version); +u_char* mp4_fragment_write_audio_trun_atom( + u_char* p, + media_sequence_t* sequence, + uint32_t first_frame_offset); + +u_char* mp4_fragment_write_subtitle_trun_atom( + u_char* p, + uint32_t first_frame_offset, + uint32_t duration, + u_char** sample_size); + + vod_status_t mp4_fragment_frame_writer_init( request_context_t* request_context, media_sequence_t* sequence, diff --git a/vod/mp4/mp4_init_segment.c b/vod/mp4/mp4_init_segment.c index e9fe34eb..d3319f28 100644 --- a/vod/mp4/mp4_init_segment.c +++ b/vod/mp4/mp4_init_segment.c @@ -105,6 +105,19 @@ static const u_char hdlr_audio_atom[] = { 0x00 }; +static const u_char hdlr_subtitle_atom[] = { + 0x00, 0x00, 0x00, 0x25, // size + 0x68, 0x64, 0x6c, 0x72, // hdlr + 0x00, 0x00, 0x00, 0x00, // version + flags + 0x00, 0x00, 0x00, 0x00, // pre defined + 0x73, 0x75, 0x62, 0x74, // handler type = subt + 0x00, 0x00, 0x00, 0x00, // reserved1 + 0x00, 0x00, 0x00, 0x00, // reserved2 + 0x00, 0x00, 0x00, 0x00, // reserved3 + 0x73, 0x75, 0x62, 0x74, // name = subt\0 + 0x00 +}; + static const u_char dinf_atom[] = { 0x00, 0x00, 0x00, 0x24, // atom size 0x64, 0x69, 0x6e, 0x66, // dinf @@ -132,6 +145,79 @@ static const u_char smhd_atom[] = { 0x00, 0x00, 0x00, 0x00, // reserved }; +static const u_char sthd_atom[] = { + 0x00, 0x00, 0x00, 0x0C, // atom size + 0x73, 0x74, 0x68, 0x64, // sthd + 0x00, 0x00, 0x00, 0x00, // version & flags +}; + +static const u_char smpte_tt_stsd_atom[] = { + 0x00, 0x00, 0x00, 0xff, // size + 0x73, 0x74, 0x73, 0x64, // stsd + 0x00, 0x00, 0x00, 0x00, // version & flags + 0x00, 0x00, 0x00, 0x01, // entries + 0x00, 0x00, 0x00, 0xef, // size + 0x73, 0x74, 0x70, 0x70, // stpp + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x01, // reserved + data_reference_index + 0x68, 0x74, 0x74, 0x70, // namespace: + 0x3a, 0x2f, 0x2f, 0x77, // http://www.smpte-ra.org/schemas/2052-1/2013/smpte-tt + 0x77, 0x77, 0x2e, 0x73, // http://www.w3.org/ns/ttml + 0x6d, 0x70, 0x74, 0x65, // http://www.w3.org/ns/ttml#metadata + 0x2d, 0x72, 0x61, 0x2e, // http://www.w3.org/ns/ttml#parameter + 0x6f, 0x72, 0x67, 0x2f, // http://www.w3.org/ns/ttml#styling + 0x73, 0x63, 0x68, 0x65, // urn:ebu:tt:metadata + 0x6d, 0x61, 0x73, 0x2f, // urn:ebu:tt:style + 0x32, 0x30, 0x35, 0x32, + 0x2d, 0x31, 0x2f, 0x32, + 0x30, 0x31, 0x33, 0x2f, + 0x73, 0x6d, 0x70, 0x74, + 0x65, 0x2d, 0x74, 0x74, + 0x20, 0x68, 0x74, 0x74, + 0x70, 0x3a, 0x2f, 0x2f, + 0x77, 0x77, 0x77, 0x2e, + 0x77, 0x33, 0x2e, 0x6f, + 0x72, 0x67, 0x2f, 0x6e, + 0x73, 0x2f, 0x74, 0x74, + 0x6d, 0x6c, 0x20, 0x68, + 0x74, 0x74, 0x70, 0x3a, + 0x2f, 0x2f, 0x77, 0x77, + 0x77, 0x2e, 0x77, 0x33, + 0x2e, 0x6f, 0x72, 0x67, + 0x2f, 0x6e, 0x73, 0x2f, + 0x74, 0x74, 0x6d, 0x6c, + 0x23, 0x6d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, + 0x61, 0x20, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, + 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x77, 0x33, 0x2e, + 0x6f, 0x72, 0x67, 0x2f, + 0x6e, 0x73, 0x2f, 0x74, + 0x74, 0x6d, 0x6c, 0x23, + 0x70, 0x61, 0x72, 0x61, + 0x6d, 0x65, 0x74, 0x65, + 0x72, 0x20, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, + 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x77, 0x33, 0x2e, + 0x6f, 0x72, 0x67, 0x2f, + 0x6e, 0x73, 0x2f, 0x74, + 0x74, 0x6d, 0x6c, 0x23, + 0x73, 0x74, 0x79, 0x6c, + 0x69, 0x6e, 0x67, 0x20, + 0x75, 0x72, 0x6e, 0x3a, + 0x65, 0x62, 0x75, 0x3a, + 0x74, 0x74, 0x3a, 0x6d, + 0x65, 0x74, 0x61, 0x64, + 0x61, 0x74, 0x61, 0x20, + 0x75, 0x72, 0x6e, 0x3a, + 0x65, 0x62, 0x75, 0x3a, + 0x74, 0x74, 0x3a, 0x73, + 0x74, 0x79, 0x6c, 0x65, + 0x00, 0x00, 0x00, // schema_location + auxiliary_mime_types (null) +}; + static const u_char fixed_stbl_atoms[] = { 0x00, 0x00, 0x00, 0x10, // atom size 0x73, 0x74, 0x74, 0x73, // stts @@ -165,7 +251,11 @@ mp4_init_segment_get_track_sizes( size_t mdhd_atom_size; size_t hdlr_atom_size = 0; - if (stsd_atom_writer != NULL) + if (cur_track->media_info.media_type == MEDIA_TYPE_SUBTITLE) + { + result->stsd_size = sizeof(smpte_tt_stsd_atom); + } + else if (stsd_atom_writer != NULL) { result->stsd_size = stsd_atom_writer->atom_size; } @@ -198,6 +288,10 @@ mp4_init_segment_get_track_sizes( result->minf_size += sizeof(smhd_atom); hdlr_atom_size = sizeof(hdlr_audio_atom); break; + case MEDIA_TYPE_SUBTITLE: + result->minf_size += sizeof(sthd_atom); + hdlr_atom_size = sizeof(hdlr_subtitle_atom); + break; } result->mdia_size = ATOM_HEADER_SIZE + mdhd_atom_size + hdlr_atom_size + result->minf_size; result->trak_size = ATOM_HEADER_SIZE + tkhd_atom_size + result->mdia_size; @@ -691,6 +785,9 @@ mp4_init_segment_write( case MEDIA_TYPE_AUDIO: p = vod_copy(p, hdlr_audio_atom, sizeof(hdlr_audio_atom)); break; + case MEDIA_TYPE_SUBTITLE: + p = vod_copy(p, hdlr_subtitle_atom, sizeof(hdlr_subtitle_atom)); + break; } // moov.trak.mdia.minf @@ -703,12 +800,19 @@ mp4_init_segment_write( case MEDIA_TYPE_AUDIO: p = vod_copy(p, smhd_atom, sizeof(smhd_atom)); break; + case MEDIA_TYPE_SUBTITLE: + p = vod_copy(p, sthd_atom, sizeof(sthd_atom)); + break; } p = vod_copy(p, dinf_atom, sizeof(dinf_atom)); // moov.trak.mdia.minf.stbl write_atom_header(p, track_sizes->stbl_size, 's', 't', 'b', 'l'); - if (stsd_atom_writers != NULL) + if (cur_track->media_info.media_type == MEDIA_TYPE_SUBTITLE) + { + p = vod_copy(p, smpte_tt_stsd_atom, sizeof(smpte_tt_stsd_atom)); + } + else if (stsd_atom_writers != NULL) { p = stsd_atom_writers[i].write(stsd_atom_writers[i].context, p); } diff --git a/vod/mss/mss_packager.c b/vod/mss/mss_packager.c index 3cdfe557..7a206ef3 100644 --- a/vod/mss/mss_packager.c +++ b/vod/mss/mss_packager.c @@ -773,11 +773,16 @@ mss_packager_build_fragment_header( } // moof.traf.trun - p = mp4_fragment_write_trun_atom( - p, - sequence, - moof_atom_size + ATOM_HEADER_SIZE, - 0); + switch (sequence->media_type) + { + case MEDIA_TYPE_VIDEO: + p = mp4_fragment_write_video_trun_atom(p, sequence, moof_atom_size + ATOM_HEADER_SIZE, 0); + break; + + case MEDIA_TYPE_AUDIO: + p = mp4_fragment_write_audio_trun_atom(p, sequence, moof_atom_size + ATOM_HEADER_SIZE); + break; + } // moof.traf.tfxd mss_get_segment_timing_info(sequence, &timing_info); diff --git a/vod/subtitle/ttml_builder.c b/vod/subtitle/ttml_builder.c index b7ef6d59..01414973 100644 --- a/vod/subtitle/ttml_builder.c +++ b/vod/subtitle/ttml_builder.c @@ -9,6 +9,7 @@ #define TTML_HEADER \ "\n" \ "\n" \ + " \n" \ " \n" \ "
\n" @@ -127,10 +128,25 @@ ttml_copy_payload_without_styles( return p; } -static u_char* -ttml_builder_write( - media_set_t* media_set, - u_char* p) +size_t +ttml_builder_get_max_size(media_set_t* media_set) +{ + media_track_t* cur_track; + size_t result; + + result = + sizeof(TTML_HEADER) - 1 + + sizeof(TTML_FOOTER) - 1; + for (cur_track = media_set->filtered_tracks; cur_track < media_set->filtered_tracks_end; cur_track++) + { + result += cur_track->total_frames_size + TTML_P_MAX_SIZE * cur_track->frame_count; + } + + return result; +} + +u_char* +ttml_builder_write(media_set_t* media_set, u_char* p) { frame_list_part_t* part; media_track_t* cur_track; @@ -191,7 +207,6 @@ ttml_build_mp4( uint32_t timescale, vod_str_t* result) { - media_track_t* cur_track; size_t traf_atom_size; size_t moof_atom_size; size_t mdat_atom_size; @@ -202,13 +217,7 @@ ttml_build_mp4( u_char* p; // get the result size - ttml_size = - sizeof(TTML_HEADER) - 1 + - sizeof(TTML_FOOTER) - 1; - for (cur_track = media_set->filtered_tracks; cur_track < media_set->filtered_tracks_end; cur_track++) - { - ttml_size += cur_track->total_frames_size + TTML_P_MAX_SIZE * cur_track->frame_count; - } + ttml_size = ttml_builder_get_max_size(media_set); traf_atom_size = ATOM_HEADER_SIZE + ATOM_HEADER_SIZE + sizeof(ttml_tfhd_atom_t) + diff --git a/vod/subtitle/ttml_builder.h b/vod/subtitle/ttml_builder.h index a41d3fde..61e601e7 100644 --- a/vod/subtitle/ttml_builder.h +++ b/vod/subtitle/ttml_builder.h @@ -7,7 +7,11 @@ // constants #define TTML_TIMESCALE (1000) -// globals +// functions +size_t ttml_builder_get_max_size(media_set_t* media_set); + +u_char* ttml_builder_write(media_set_t* media_set, u_char* p); + vod_status_t ttml_build_mp4( request_context_t* request_context, media_set_t* media_set,