Hi I have attached the patch.
Thanks Anshul Maheshwari
>From eacd2356796ae927c500afdca15c040a2318415b Mon Sep 17 00:00:00 2001 From: Anshul Maheshwari <anshul.ffm...@gmail.com> Date: Tue, 13 Jan 2015 16:18:16 +0530 Subject: [PATCH] webvtt in hls muxer Signed-off-by: Anshul Maheshwari <anshul.ffm...@gmail.com> --- libavformat/hlsenc.c | 174 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 10 deletions(-) diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 29bf30e..342efc9 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -39,6 +39,7 @@ typedef struct HLSSegment { char filename[1024]; + char sub_filename[1024]; double duration; /* in seconds */ int64_t pos; int64_t size; @@ -58,8 +59,10 @@ typedef struct HLSContext { int64_t sequence; int64_t start_sequence; AVOutputFormat *oformat; + AVOutputFormat *vtt_oformat; AVFormatContext *avf; + AVFormatContext *vtt_avf; float time; // Set by a private option. int max_nb_segments; // Set by a private option. @@ -70,6 +73,7 @@ typedef struct HLSContext { int allowcache; int64_t recording_time; int has_video; + int has_subtitle; int64_t start_pts; int64_t end_pts; double duration; // last segment duration computed so far, in seconds @@ -82,11 +86,17 @@ typedef struct HLSContext { HLSSegment *old_segments; char *basename; + char *vtt_basename; + char *vtt_m3u8_name; char *baseurl; char *format_options_str; + char *vtt_format_options_str; + char *subtitle_filename; AVDictionary *format_options; + AVDictionary *vtt_format_options; AVIOContext *pb; + AVIOContext *sub_pb; } HLSContext; static int hls_delete_old_segments(HLSContext *hls) { @@ -158,6 +168,7 @@ static int hls_mux_init(AVFormatContext *s) { HLSContext *hls = s->priv_data; AVFormatContext *oc; + AVFormatContext *vtt_oc; int i, ret; ret = avformat_alloc_output_context2(&hls->avf, hls->oformat, NULL, NULL); @@ -170,10 +181,25 @@ static int hls_mux_init(AVFormatContext *s) oc->max_delay = s->max_delay; av_dict_copy(&oc->metadata, s->metadata, 0); + + if(hls->vtt_oformat) { + ret = avformat_alloc_output_context2(&hls->vtt_avf, hls->vtt_oformat, NULL, NULL); + if (ret < 0) + return ret; + vtt_oc = hls->vtt_avf; + vtt_oc->oformat = hls->vtt_oformat; + av_dict_copy(&vtt_oc->metadata, s->metadata, 0); + } + for (i = 0; i < s->nb_streams; i++) { AVStream *st; - if (!(st = avformat_new_stream(oc, NULL))) - return AVERROR(ENOMEM); + if (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { + if (!(st = avformat_new_stream(vtt_oc, NULL))) + return AVERROR(ENOMEM); + } else { + if (!(st = avformat_new_stream(oc, NULL))) + return AVERROR(ENOMEM); + } avcodec_copy_context(st->codec, s->streams[i]->codec); st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; st->time_base = s->streams[i]->time_base; @@ -195,6 +221,9 @@ static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, av_strlcpy(en->filename, av_basename(hls->avf->filename), sizeof(en->filename)); + if(hls->has_subtitle) + av_strlcpy(en->sub_filename, av_basename(hls->vtt_avf->filename), sizeof(en->sub_filename)); + en->duration = duration; en->pos = pos; en->size = size; @@ -279,8 +308,37 @@ static int hls_window(AVFormatContext *s, int last) if (last) avio_printf(hls->pb, "#EXT-X-ENDLIST\n"); + if( hls->vtt_m3u8_name ) { + if ((ret = avio_open2(&hls->sub_pb, hls->vtt_m3u8_name, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + goto fail; + avio_printf(hls->sub_pb, "#EXTM3U\n"); + avio_printf(hls->sub_pb, "#EXT-X-VERSION:%d\n", version); + if (hls->allowcache == 0 || hls->allowcache == 1) { + avio_printf(hls->sub_pb, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); + } + avio_printf(hls->sub_pb, "#EXT-X-TARGETDURATION:%d\n", target_duration); + avio_printf(hls->sub_pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); + + av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", + sequence); + + for (en = hls->segments; en; en = en->next) { + avio_printf(hls->sub_pb, "#EXTINF:%f,\n", en->duration); + if (hls->flags & HLS_SINGLE_FILE) + avio_printf(hls->sub_pb, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", + en->size, en->pos); + if (hls->baseurl) + avio_printf(hls->sub_pb, "%s", hls->baseurl); + avio_printf(hls->sub_pb, "%s\n", en->sub_filename); + } + + if (last) + avio_printf(hls->sub_pb, "#EXT-X-ENDLIST\n"); + } fail: avio_closep(&hls->pb); + avio_closep(&hls->sub_pb); return ret; } @@ -288,26 +346,45 @@ static int hls_start(AVFormatContext *s) { HLSContext *c = s->priv_data; AVFormatContext *oc = c->avf; + AVFormatContext *vtt_oc = c->vtt_avf; int err = 0; - if (c->flags & HLS_SINGLE_FILE) + if (c->flags & HLS_SINGLE_FILE) { av_strlcpy(oc->filename, c->basename, sizeof(oc->filename)); - else + av_strlcpy(vtt_oc->filename, c->vtt_basename, + sizeof(vtt_oc->filename)); + } + else { if (av_get_frame_filename(oc->filename, sizeof(oc->filename), c->basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->basename); return AVERROR(EINVAL); } + if( c->vtt_basename) { + if (av_get_frame_filename(vtt_oc->filename, sizeof(vtt_oc->filename), + c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { + av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename); + return AVERROR(EINVAL); + } + } + } c->number++; if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) return err; + if (c->vtt_basename) { + if ((err = avio_open2(&vtt_oc->pb, vtt_oc->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + return err; + } if (oc->oformat->priv_class && oc->priv_data) av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); + if (c->vtt_basename) + avformat_write_header(vtt_oc,NULL); return 0; } @@ -317,8 +394,10 @@ static int hls_write_header(AVFormatContext *s) int ret, i; char *p; const char *pattern = "%d.ts"; + const char *vtt_pattern = "%d.vtt"; AVDictionary *options = NULL; int basename_size; + int vtt_basename_size; hls->sequence = hls->start_sequence; hls->recording_time = hls->time * AV_TIME_BASE; @@ -332,9 +411,12 @@ static int hls_write_header(AVFormatContext *s) } } - for (i = 0; i < s->nb_streams; i++) + for (i = 0; i < s->nb_streams; i++) { hls->has_video += s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; + hls->has_subtitle += + s->streams[i]->codec->codec_type == AVMEDIA_TYPE_SUBTITLE; + } if (hls->has_video > 1) av_log(s, AV_LOG_WARNING, @@ -371,6 +453,41 @@ static int hls_write_header(AVFormatContext *s) if (p) *p = '\0'; av_strlcat(hls->basename, pattern, basename_size); + if(hls->has_subtitle) { + + hls->vtt_oformat = av_guess_format("webvtt", NULL, NULL); + if (!hls->oformat) { + ret = AVERROR_MUXER_NOT_FOUND; + goto fail; + } + if (hls->flags & HLS_SINGLE_FILE) + vtt_pattern = ".vtt"; + vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 1; + hls->vtt_basename = av_malloc(vtt_basename_size); + if (!hls->vtt_basename) { + ret = AVERROR(ENOMEM); + goto fail; + } + hls->vtt_m3u8_name = av_malloc(vtt_basename_size); + if (!hls->vtt_m3u8_name ) { + ret = AVERROR(ENOMEM); + goto fail; + } + av_strlcpy(hls->vtt_basename, s->filename, vtt_basename_size); + p = strrchr(hls->vtt_basename, '.'); + if (p) + *p = '\0'; + + if( hls->subtitle_filename ) { + strcpy(hls->vtt_m3u8_name, hls->subtitle_filename); + } else { + strcpy(hls->vtt_m3u8_name, hls->vtt_basename); + av_strlcat(hls->vtt_m3u8_name, "_vtt.m3u8", vtt_basename_size); + } + av_strlcat(hls->vtt_basename, vtt_pattern, vtt_basename_size); + + + } } if ((ret = hls_mux_init(s)) < 0) @@ -386,10 +503,14 @@ static int hls_write_header(AVFormatContext *s) ret = AVERROR(EINVAL); goto fail; } - av_assert0(s->nb_streams == hls->avf->nb_streams); + //av_assert0(s->nb_streams == hls->avf->nb_streams); for (i = 0; i < s->nb_streams; i++) { - AVStream *inner_st = hls->avf->streams[i]; + AVStream *inner_st; AVStream *outer_st = s->streams[i]; + if (outer_st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + inner_st = hls->avf->streams[i]; + else + inner_st = hls->vtt_avf->streams[0]; avpriv_set_pts_info(outer_st, inner_st->pts_wrap_bits, inner_st->time_base.num, inner_st->time_base.den); } fail: @@ -397,8 +518,11 @@ fail: av_dict_free(&options); if (ret < 0) { av_freep(&hls->basename); + av_freep(&hls->vtt_basename); if (hls->avf) avformat_free_context(hls->avf); + if (hls->vtt_avf) + avformat_free_context(hls->vtt_avf); } return ret; } @@ -406,11 +530,22 @@ fail: static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) { HLSContext *hls = s->priv_data; - AVFormatContext *oc = hls->avf; + AVFormatContext *oc = NULL; AVStream *st = s->streams[pkt->stream_index]; int64_t end_pts = hls->recording_time * hls->number; int is_ref_pkt = 1; int ret, can_split = 1; + int stream_index = 0; + + if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE ) + { + oc = hls->vtt_avf; + stream_index = 0; + } + else { + oc = hls->avf; + stream_index = pkt->stream_index; + } if (hls->start_pts == AV_NOPTS_VALUE) { hls->start_pts = pkt->pts; @@ -450,6 +585,8 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) hls->number++; } else { avio_closep(&oc->pb); + if (hls->vtt_avf) + avio_close(hls->vtt_avf->pb); ret = hls_start(s); } @@ -457,13 +594,16 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) if (ret < 0) return ret; - oc = hls->avf; + if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE ) + oc = hls->vtt_avf; + else + oc = hls->avf; if ((ret = hls_window(s, 0)) < 0) return ret; } - ret = ff_write_chained(oc, pkt->stream_index, pkt, s, 0); + ret = ff_write_chained(oc, stream_index, pkt, s, 0); return ret; } @@ -472,6 +612,7 @@ static int hls_write_trailer(struct AVFormatContext *s) { HLSContext *hls = s->priv_data; AVFormatContext *oc = hls->avf; + AVFormatContext *vtt_oc = hls->vtt_avf; av_write_trailer(oc); if (oc->pb) { @@ -479,8 +620,18 @@ static int hls_write_trailer(struct AVFormatContext *s) avio_closep(&oc->pb); hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); } + + av_write_trailer(vtt_oc); + if (vtt_oc->pb) { + hls->size = avio_tell(hls->vtt_avf->pb) - hls->start_pos; + avio_closep(&vtt_oc->pb); + hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + } av_freep(&hls->basename); + av_freep(&hls->vtt_basename); + av_freep(&hls->vtt_m3u8_name); avformat_free_context(oc); + avformat_free_context(vtt_oc); hls->avf = NULL; hls_window(s, 1); @@ -497,11 +648,13 @@ static const AVOption options[] = { {"hls_time", "set segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, {"hls_list_size", "set maximum number of playlist entries", OFFSET(max_nb_segments), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, {"hls_ts_options","set hls mpegts list of options for the container format used for hls", OFFSET(format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, + {"hls_vtt_options","set hls vtt list of options for the container format used for hls", OFFSET(vtt_format_options_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, {"hls_allow_cache", "explicitly set whether the client MAY (1) or MUST NOT (0) cache media segments", OFFSET(allowcache), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, E}, {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_segment_filename", "filename template for segment files", OFFSET(segment_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, "flags"}, + {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"single_file", "generate a single media file indexed with byte ranges", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SINGLE_FILE }, 0, UINT_MAX, E, "flags"}, {"delete_segments", "delete segment files that are no longer part of the playlist", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_DELETE_SEGMENTS }, 0, UINT_MAX, E, "flags"}, @@ -523,6 +676,7 @@ AVOutputFormat ff_hls_muxer = { .priv_data_size = sizeof(HLSContext), .audio_codec = AV_CODEC_ID_AAC, .video_codec = AV_CODEC_ID_H264, + .subtitle_codec = AV_CODEC_ID_WEBVTT, .flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH, .write_header = hls_write_header, .write_packet = hls_write_packet, -- 1.8.1.4
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel