On 01/13/2015 04:19 PM, Anshul wrote: > Hi > > I have attached the patch. > > Thanks > Anshul Maheshwari Another patch freeing memory leakage introduced by this patch.
-Anshul
>From 6829d7013819418011dfba2b40657d3f921bcaee Mon Sep 17 00:00:00 2001 From: Anshul Maheshwari <anshul.ffm...@gmail.com> Date: Fri, 2 Jan 2015 13:18:11 +0530 Subject: [PATCH 1/2] Added webvtt in hls Signed-off-by: Anshul Maheshwari <anshul.ffm...@gmail.com> --- libavformat/Makefile | 2 +- libavformat/hlsenc_vaibhav.c | 600 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 libavformat/hlsenc_vaibhav.c diff --git a/libavformat/Makefile b/libavformat/Makefile index 7e4beac..431031d 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -177,7 +177,7 @@ OBJS-$(CONFIG_HDS_MUXER) += hdsenc.o OBJS-$(CONFIG_HEVC_DEMUXER) += hevcdec.o rawdec.o OBJS-$(CONFIG_HEVC_MUXER) += rawenc.o OBJS-$(CONFIG_HLS_DEMUXER) += hls.o -OBJS-$(CONFIG_HLS_MUXER) += hlsenc.o +OBJS-$(CONFIG_HLS_MUXER) += hlsenc_vaibhav.o OBJS-$(CONFIG_HNM_DEMUXER) += hnm.o OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o OBJS-$(CONFIG_ICO_MUXER) += icoenc.o diff --git a/libavformat/hlsenc_vaibhav.c b/libavformat/hlsenc_vaibhav.c new file mode 100644 index 0000000..f80aab0 --- /dev/null +++ b/libavformat/hlsenc_vaibhav.c @@ -0,0 +1,600 @@ +/* + * Apple HTTP Live Streaming segmenter + * Copyright (c) 2012, Luca Barbato + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <float.h> +#include <stdint.h> + +#include "libavutil/avassert.h" +#include "libavutil/mathematics.h" +#include "libavutil/parseutils.h" +#include "libavutil/avstring.h" +#include "libavutil/opt.h" +#include "libavutil/log.h" + +#include "avformat.h" +#include "internal.h" + +typedef struct HLSSegment { + char filename[1024]; + char sub_filename[1024]; + double duration; /* in seconds */ + int64_t pos; + int64_t size; + + struct HLSSegment *next; +} HLSSegment; + +typedef enum HLSFlags { + // Generate a single media file and use byte ranges in the playlist. + HLS_SINGLE_FILE = (1 << 0), +} HLSFlags; + +typedef struct HLSContext { + const AVClass *class; // Class for private options. + unsigned number; + int64_t sequence; + int64_t start_sequence; + AVOutputFormat *ts_oformat; + AVOutputFormat *vtt_oformat; + + AVFormatContext *ts_avf; + AVFormatContext *vtt_avf; + + float time; // Set by a private option. + int max_nb_segments; // Set by a private option. + int wrap; // Set by a private option. + uint32_t flags; // enum HLSFlags + + 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 + int64_t start_pos; // last segment starting position + int64_t size; // last segment size + int nb_entries; + + HLSSegment *segments; + HLSSegment *last_segment; + + char *ts_basename; + char *vtt_basename; + char *vtt_m3u8_name; + char *baseurl; + char *ts_format_options_str; + char *vtt_format_options_str; + char *subtitle_filename; + AVDictionary *ts_format_options; + AVDictionary *vtt_format_options; + + AVIOContext *pb; + AVIOContext *sub_pb; +} HLSContext; + +static int hls_mux_init(AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + AVFormatContext *ts_oc; + AVFormatContext *vtt_oc; + int i, ret; + + ret = avformat_alloc_output_context2(&hls->ts_avf, hls->ts_oformat, NULL, NULL); + if (ret < 0) + return ret; + + ts_oc = hls->ts_avf; + ts_oc->oformat = hls->ts_oformat; + ts_oc->interrupt_callback = s->interrupt_callback; + ts_oc->max_delay = s->max_delay; + av_dict_copy(&ts_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 (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(ts_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; + } + hls->start_pos = 0; + + return 0; +} + +/* Create a new segment and append it to the segment list */ +static int hls_append_segment(HLSContext *hls, double duration, int64_t pos, + int64_t size) +{ + HLSSegment *en = av_malloc(sizeof(*en)); + + if (!en) + return AVERROR(ENOMEM); + + av_strlcpy(en->filename, av_basename(hls->ts_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; + en->next = NULL; + + if (!hls->segments) + hls->segments = en; + else + hls->last_segment->next = en; + + hls->last_segment = en; + + if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { + en = hls->segments; + hls->segments = en->next; + av_free(en); + } else + hls->nb_entries++; + + hls->sequence++; + + return 0; +} + +static void hls_free_segments(HLSContext *hls) +{ + HLSSegment *p = hls->segments, *en; + + while(p) { + en = p; + p = p->next; + av_free(en); + } +} + +static int hls_window(AVFormatContext *s, int last) +{ + HLSContext *hls = s->priv_data; + HLSSegment *en; + int target_duration = 0; + int ret = 0; + int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); + int version = hls->flags & HLS_SINGLE_FILE ? 4 : 3; + + if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + goto fail; + + for (en = hls->segments; en; en = en->next) { + if (target_duration < en->duration) + target_duration = ceil(en->duration); + } + + avio_printf(hls->pb, "#EXTM3U\n"); + avio_printf(hls->pb, "#EXT-X-VERSION:%d\n", version); + if (hls->allowcache == 0 || hls->allowcache == 1) { + avio_printf(hls->pb, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); + } + avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", target_duration); + avio_printf(hls->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->pb, "#EXTINF:%f,\n", en->duration); + if (hls->flags & HLS_SINGLE_FILE) + avio_printf(hls->pb, "#EXT-X-BYTERANGE:%"PRIi64"@%"PRIi64"\n", + en->size, en->pos); + if (hls->baseurl) + avio_printf(hls->pb, "%s", hls->baseurl); + avio_printf(hls->pb, "%s\n", en->filename); + } + + 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; +} + +static int hls_start(AVFormatContext *s) +{ + HLSContext *c = s->priv_data; + AVFormatContext *ts_oc = c->ts_avf; + AVFormatContext *vtt_oc = c->vtt_avf; + int err = 0; + + if (c->flags & HLS_SINGLE_FILE) { + av_strlcpy(ts_oc->filename, c->ts_basename, + sizeof(ts_oc->filename)); + av_strlcpy(vtt_oc->filename, c->vtt_basename, + sizeof(vtt_oc->filename)); + } + else { + if (av_get_frame_filename(ts_oc->filename, sizeof(ts_oc->filename), + c->ts_basename, c->wrap ? c->sequence % c->wrap : c->sequence) < 0) { + av_log(ts_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->ts_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(&ts_oc->pb, ts_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 (ts_oc->oformat->priv_class && ts_oc->priv_data) + av_opt_set(ts_oc->priv_data, "mpegts_flags", "resend_headers", 0); + + if (c->vtt_basename) + avformat_write_header(vtt_oc,NULL); + + return 0; +} + +static int hls_write_header(AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + int ret, i; + char *p; + const char *ts_pattern = NULL; + const char *vtt_pattern = NULL; + AVDictionary *options = NULL; + int ts_basename_size; + int vtt_basename_size; + + 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) + { + if (hls->flags & HLS_SINGLE_FILE) + ts_pattern = ".ts"; + else + ts_pattern = "%d.ts"; + ts_basename_size = strlen(s->filename) + strlen(ts_pattern) + 5; + hls->ts_oformat = av_guess_format("mpegts", NULL, NULL); + if (!hls->ts_oformat) { + ret = AVERROR_MUXER_NOT_FOUND; + goto fail; + } + hls->ts_basename = av_malloc(ts_basename_size); + if (!hls->ts_basename ) { + ret = AVERROR(ENOMEM); + goto fail; + } + strcpy(hls->ts_basename, s->filename); + p = strrchr(hls->ts_basename, '.'); + + if (p) + *p = '\0'; + av_strlcat(hls->ts_basename, ts_pattern, ts_basename_size); + + } + if(hls->has_subtitle) + { + if (hls->flags & HLS_SINGLE_FILE) + vtt_pattern = ".vtt"; + else + vtt_pattern = "%d.vtt"; + vtt_basename_size = strlen(s->filename) + strlen(vtt_pattern) + 128; + hls->vtt_oformat = av_guess_format("webvtt", NULL, NULL); + if (!hls->vtt_oformat) { + ret = AVERROR_MUXER_NOT_FOUND; + goto fail; + } + hls->vtt_basename = av_malloc(vtt_basename_size); + if (!hls->vtt_basename ) { + ret = AVERROR(ENOMEM); + goto fail; + } + + 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; + } + strcpy(hls->vtt_basename, s->filename); + 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); + } + + hls->sequence = hls->start_sequence; + hls->recording_time = hls->time * AV_TIME_BASE; + hls->start_pts = AV_NOPTS_VALUE; + + if (hls->ts_format_options_str) { + ret = av_dict_parse_string(&hls->ts_format_options, hls->ts_format_options_str, "=", ":", 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", hls->ts_format_options_str); + goto fail; + } + } + if (hls->vtt_format_options_str) { + ret = av_dict_parse_string(&hls->ts_format_options, hls->vtt_format_options_str, "=", ":", 0); + if (ret < 0) { + av_log(s, AV_LOG_ERROR, "Could not parse format options list '%s'\n", hls->vtt_format_options_str); + goto fail; + } + } + + if (hls->has_video > 1) + av_log(s, AV_LOG_WARNING, + "More than a single video stream present, " + "expect issues decoding it.\n"); + + + + + if ((ret = hls_mux_init(s)) < 0) + goto fail; + + if ((ret = hls_start(s)) < 0) + goto fail; + + av_dict_copy(&options, hls->ts_format_options, 0); + ret = avformat_write_header(hls->ts_avf, &options); + if (av_dict_count(options)) { + av_log(s, AV_LOG_ERROR, "Some of provided format options in '%s' are not recognized\n", hls->ts_format_options_str); + ret = AVERROR(EINVAL); + goto fail; + } + + + for (i = 0; i < s->nb_streams; i++) { + AVStream *inner_st; + AVStream *outer_st = s->streams[i]; + if (outer_st->codec->codec_type != AVMEDIA_TYPE_SUBTITLE) + inner_st = hls->ts_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: + + av_dict_free(&options); + if (ret) { + av_free(hls->ts_basename); + av_free(hls->vtt_basename); + if (hls->ts_avf) + avformat_free_context(hls->ts_avf); + if (hls->vtt_avf) + avformat_free_context(hls->vtt_avf); + } + return ret; +} + +static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + HLSContext *hls = s->priv_data; + 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->ts_avf; + stream_index = pkt->stream_index; + } + + if (hls->start_pts == AV_NOPTS_VALUE) { + hls->start_pts = pkt->pts; + hls->end_pts = pkt->pts; + } + + if (hls->has_video) { + can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + pkt->flags & AV_PKT_FLAG_KEY; + is_ref_pkt = st->codec->codec_type == AVMEDIA_TYPE_VIDEO; + } + if (pkt->pts == AV_NOPTS_VALUE) + is_ref_pkt = can_split = 0; + + if (is_ref_pkt) + hls->duration = (double)(pkt->pts - hls->end_pts) + * st->time_base.num / st->time_base.den; + + if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, + end_pts, AV_TIME_BASE_Q) >= 0) { + int64_t new_start_pos; + av_write_frame(oc, NULL); /* Flush any buffered data */ + + new_start_pos = avio_tell(hls->ts_avf->pb); + hls->size = new_start_pos - hls->start_pos; + ret = hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + hls->start_pos = new_start_pos; + if (ret) + return ret; + + hls->end_pts = pkt->pts; + hls->duration = 0; + + if (hls->flags & HLS_SINGLE_FILE) { + if (hls->ts_avf->oformat->priv_class && hls->ts_avf->priv_data) + av_opt_set(hls->ts_avf->priv_data, "mpegts_flags", "resend_headers", 0); + hls->number++; + } else { + avio_close(oc->pb); + if (hls->vtt_avf) + avio_close(hls->vtt_avf->pb); + + ret = hls_start(s); + } + + if (ret) + return ret; + + if( st->codec->codec_type == AVMEDIA_TYPE_SUBTITLE ) + oc = hls->vtt_avf; + else + oc = hls->ts_avf; + + if ((ret = hls_window(s, 0)) < 0) + return ret; + } + + ret = ff_write_chained(oc, stream_index, pkt, s, 0); + + return ret; +} + +static int hls_write_trailer(struct AVFormatContext *s) +{ + HLSContext *hls = s->priv_data; + AVFormatContext *oc = hls->ts_avf; + + av_write_trailer(oc); + hls->size = avio_tell(hls->ts_avf->pb) - hls->start_pos; + avio_closep(&oc->pb); + avformat_free_context(oc); + av_free(hls->ts_basename); + av_free(hls->vtt_basename); + hls_append_segment(hls, hls->duration, hls->start_pos, hls->size); + hls_window(s, 1); + + hls_free_segments(hls); + avio_close(hls->pb); + return 0; +} + +#define OFFSET(x) offsetof(HLSContext, x) +#define E AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + {"start_number", "set first number in the sequence", OFFSET(start_sequence),AV_OPT_TYPE_INT64, {.i64 = 0}, 0, INT64_MAX, E}, + {"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(ts_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_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"}, + + { NULL }, +}; + +static const AVClass hls_class = { + .class_name = "hls muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + + +AVOutputFormat ff_hls_muxer = { + .name = "hls", + .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"), + .extensions = "m3u8", + .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, + .write_trailer = hls_write_trailer, + .priv_class = &hls_class, +}; -- 1.8.1.4
_______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel