Before adding new features, I read the code and cleaned it. The main issue was abstruse identifier names.
The behaviour of the muxer is *not* modified, by this patch, this is only cosmetic. If this is not the case, it is a mistake. --- libavformat/hlsenc.c | 360 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 213 insertions(+), 147 deletions(-) diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 388a23a..30b320f 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -31,236 +31,294 @@ #include "avformat.h" #include "internal.h" -typedef struct ListEntry { - char name[1024]; - double duration; - struct ListEntry *next; -} ListEntry; +typedef struct HLSSegment { + char filename[1024]; + double duration; /* in seconds */ + + struct HLSSegment *next; +} HLSSegment; typedef struct HLSContext { const AVClass *class; // Class for private options. + unsigned number; int64_t sequence; int64_t start_sequence; + AVOutputFormat *oformat; - AVFormatContext *avf; - float time; // Set by a private option. - int size; // Set by a private option. - int wrap; // Set by a private option. + AVFormatContext *ctx; + + float target_duration; + int max_nb_segments; + int wrap; + int64_t recording_time; int has_video; int64_t start_pts; int64_t end_pts; double duration; // last segment duration computed so far, in seconds int nb_entries; - ListEntry *list; - ListEntry *end_list; - char *basename; + + HLSSegment *segments; + HLSSegment *last_segment; + + char *media_filename; char *baseurl; + AVIOContext *pb; } HLSContext; -static int hls_mux_init(AVFormatContext *s) +static int hls_mux_init(AVFormatContext *ctx) { - HLSContext *hls = s->priv_data; - AVFormatContext *oc; + HLSContext *hls; int i; - hls->avf = oc = avformat_alloc_context(); - if (!oc) + hls = ctx->priv_data; + + hls->ctx = avformat_alloc_context(); + if (!hls->ctx) return AVERROR(ENOMEM); - oc->oformat = hls->oformat; - oc->interrupt_callback = s->interrupt_callback; - av_dict_copy(&oc->metadata, s->metadata, 0); + hls->ctx->oformat = hls->oformat; + hls->ctx->interrupt_callback = ctx->interrupt_callback; + av_dict_copy(&hls->ctx->metadata, ctx->metadata, 0); - for (i = 0; i < s->nb_streams; i++) { - AVStream *st; - if (!(st = avformat_new_stream(oc, NULL))) + for (i = 0; i < ctx->nb_streams; i++) { + AVStream *stream; + + stream = avformat_new_stream(hls->ctx, NULL); + if (!stream) return AVERROR(ENOMEM); - avcodec_copy_context(st->codec, s->streams[i]->codec); - st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; + + avcodec_copy_context(stream->codec, ctx->streams[i]->codec); + stream->sample_aspect_ratio = ctx->streams[i]->sample_aspect_ratio; } return 0; } -static int append_entry(HLSContext *hls, double duration) +static int hls_append_segment(HLSContext *hls, double duration) { - ListEntry *en = av_malloc(sizeof(*en)); + const char *basename; + HLSSegment *segment; - if (!en) + /* Create a new segment and append it to the segment list */ + + segment = av_malloc(sizeof(*segment)); + if (!segment) return AVERROR(ENOMEM); - av_strlcpy(en->name, av_basename(hls->avf->filename), sizeof(en->name)); + basename = av_basename(hls->ctx->filename); + av_strlcpy(segment->filename, basename, sizeof(segment->filename)); - en->duration = duration; - en->next = NULL; + segment->duration = duration; + segment->next = NULL; - if (!hls->list) - hls->list = en; - else - hls->end_list->next = en; + if (!hls->segments) { + hls->segments = segment; + } else { + hls->last_segment->next = segment; + } - hls->end_list = en; + hls->last_segment = segment; - if (hls->size && hls->nb_entries >= hls->size) { - en = hls->list; - hls->list = en->next; - av_free(en); - } else + if (hls->max_nb_segments > 0 && hls->nb_entries >= hls->max_nb_segments) { + segment = hls->segments; + hls->segments = segment->next; + av_free(segment); + } else { hls->nb_entries++; + } hls->sequence++; return 0; } -static void free_entries(HLSContext *hls) +static void hls_free_segments(HLSContext *hls) { - ListEntry *p = hls->list, *en; + HLSSegment *segment; - while(p) { - en = p; - p = p->next; - av_free(en); + segment = hls->segments; + + while (segment) { + HLSSegment *next; + + next = segment->next; + av_free(segment); + segment = next; } } -static int hls_window(AVFormatContext *s, int last) +static int hls_generate_playlist(AVFormatContext *ctx, int last) { - HLSContext *hls = s->priv_data; - ListEntry *en; - int target_duration = 0; - int ret = 0; - int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); - - if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - goto fail; + HLSContext *hls; + HLSSegment *segment; + int target_duration; + int64_t sequence; + int ret; + + hls = ctx->priv_data; + target_duration = 0; + ret = 0; + + ret = avio_open2(&hls->pb, ctx->filename, AVIO_FLAG_WRITE, + &ctx->interrupt_callback, NULL); + if (ret < 0) + return ret; - for (en = hls->list; en; en = en->next) { - if (target_duration < en->duration) - target_duration = ceil(en->duration); + for (segment = hls->segments; segment; segment = segment->next) { + if (segment->duration > target_duration) + target_duration = ceil(segment->duration); } avio_printf(hls->pb, "#EXTM3U\n"); avio_printf(hls->pb, "#EXT-X-VERSION:3\n"); 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); + sequence = FFMAX(hls->start_sequence, hls->sequence - hls->nb_entries); + avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); - for (en = hls->list; en; en = en->next) { - avio_printf(hls->pb, "#EXTINF:%f,\n", en->duration); - if (hls->baseurl) - avio_printf(hls->pb, "%s", hls->baseurl); - avio_printf(hls->pb, "%s\n", en->name); + for (segment = hls->segments; segment; segment = segment->next) { + avio_printf(hls->pb, "#EXTINF:%f,\n", segment->duration); + avio_printf(hls->pb, "%s%s\n", + (hls->baseurl ? hls->baseurl : ""), segment->filename); } if (last) avio_printf(hls->pb, "#EXT-X-ENDLIST\n"); -fail: avio_closep(&hls->pb); - return ret; + return 0; } -static int hls_start(AVFormatContext *s) +static int hls_create_file(AVFormatContext *ctx) { - HLSContext *c = s->priv_data; - AVFormatContext *oc = c->avf; - int err = 0; + HLSContext *hls; + int file_idx; + int ret; + + hls = ctx->priv_data; - 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); + ret = 0; + + if (hls->wrap) { + file_idx = hls->sequence % hls->wrap; + } else { + file_idx = hls->sequence; + } + + if (av_get_frame_filename(hls->ctx->filename, sizeof(hls->ctx->filename), + hls->media_filename, file_idx) < 0) { + av_log(hls->ctx, AV_LOG_ERROR, + "Invalid segment filename template '%s'\n", hls->media_filename); return AVERROR(EINVAL); } - c->number++; + hls->number++; - if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) - return err; + ret = avio_open2(&hls->ctx->pb, hls->ctx->filename, AVIO_FLAG_WRITE, + &ctx->interrupt_callback, NULL); + if (ret < 0) + return ret; - if (oc->oformat->priv_class && oc->priv_data) - av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); + if (hls->ctx->oformat->priv_class && hls->ctx->priv_data) + av_opt_set(hls->ctx->priv_data, "mpegts_flags", "resend_headers", 0); return 0; } -static int hls_write_header(AVFormatContext *s) +static int hls_write_header(AVFormatContext *ctx) { - HLSContext *hls = s->priv_data; + HLSContext *hls; int ret, i; - char *p; - const char *pattern = "%d.ts"; - int basename_size = strlen(s->filename) + strlen(pattern) + 1; + char *dot; + size_t basename_size; + char filename[1024]; + + hls = ctx->priv_data; hls->sequence = hls->start_sequence; - hls->recording_time = hls->time * AV_TIME_BASE; + hls->recording_time = hls->target_duration * AV_TIME_BASE; hls->start_pts = AV_NOPTS_VALUE; - for (i = 0; i < s->nb_streams; i++) - hls->has_video += - s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; + /* Findout if the input file contains a video stream */ + for (i = 0; i < ctx->nb_streams; i++) { + AVStream *stream; + + stream = ctx->streams[i]; + hls->has_video += stream->codec->codec_type == AVMEDIA_TYPE_VIDEO; + } - if (hls->has_video > 1) - av_log(s, AV_LOG_WARNING, + if (hls->has_video > 1) { + av_log(ctx, AV_LOG_WARNING, "More than a single video stream present, " "expect issues decoding it.\n"); + } + /* HLS demands that media files use the MPEG-TS container */ hls->oformat = av_guess_format("mpegts", NULL, NULL); - if (!hls->oformat) { ret = AVERROR_MUXER_NOT_FOUND; goto fail; } - hls->basename = av_malloc(basename_size); + /* Generate the basename of all generated media files */ + av_strlcpy(filename, ctx->filename, sizeof(filename)); + dot = strrchr(filename, '.'); + if (dot) + *dot = '\0'; - if (!hls->basename) { + basename_size = sizeof(filename); + hls->media_filename = av_malloc(basename_size); + if (!hls->media_filename) { ret = AVERROR(ENOMEM); goto fail; } - strcpy(hls->basename, s->filename); - - p = strrchr(hls->basename, '.'); - - if (p) - *p = '\0'; + av_strlcpy(hls->media_filename, filename, basename_size); + av_strlcat(hls->media_filename, "%d.ts", basename_size); - av_strlcat(hls->basename, pattern, basename_size); - - if ((ret = hls_mux_init(s)) < 0) + /* Initialize the muxer and create the first file */ + ret = hls_mux_init(ctx); + if (ret < 0) goto fail; - if ((ret = hls_start(s)) < 0) + ret = hls_create_file(ctx); + if (ret < 0) goto fail; - if ((ret = avformat_write_header(hls->avf, NULL)) < 0) + ret = avformat_write_header(hls->ctx, NULL); + if (ret < 0) return ret; + return 0; fail: if (ret) { - av_free(hls->basename); - if (hls->avf) - avformat_free_context(hls->avf); + av_free(hls->media_filename); + if (hls->ctx) + avformat_free_context(hls->ctx); } + return ret; } -static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) +static int hls_write_packet(AVFormatContext *ctx, AVPacket *pkt) { - HLSContext *hls = s->priv_data; - AVFormatContext *oc = hls->avf; - 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; + HLSContext *hls; + AVStream *stream; + int64_t end_pts; + int64_t pkt_ts; + int is_ref_pkt; + int ret, can_split; + + hls = ctx->priv_data; + stream = ctx->streams[pkt->stream_index]; + + is_ref_pkt = 1; + can_split = 1; + + end_pts = hls->recording_time * hls->number; if (hls->start_pts == AV_NOPTS_VALUE) { hls->start_pts = pkt->pts; @@ -268,58 +326,66 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) } 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; + can_split = (pkt->flags & AV_PKT_FLAG_KEY) + && (stream->codec->codec_type == AVMEDIA_TYPE_VIDEO); + is_ref_pkt = (stream->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 (is_ref_pkt) { + double pts_diff; + + pts_diff = pkt->pts - hls->end_pts; - if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, + hls->duration = pts_diff * stream->time_base.num + / stream->time_base.den; + } + + pkt_ts = pkt->pts - hls->start_pts; + + if (can_split && av_compare_ts(pkt_ts, stream->time_base, end_pts, AV_TIME_BASE_Q) >= 0) { - ret = append_entry(hls, hls->duration); + ret = hls_append_segment(hls, hls->duration); if (ret) return ret; hls->end_pts = pkt->pts; hls->duration = 0; - av_write_frame(oc, NULL); /* Flush any buffered data */ - avio_close(oc->pb); - - ret = hls_start(s); + /* Flush any buffered data and close the current file */ + av_write_frame(hls->ctx, NULL); + avio_close(hls->ctx->pb); + /* Open the next file */ + ret = hls_create_file(ctx); if (ret) return ret; - oc = hls->avf; - - if ((ret = hls_window(s, 0)) < 0) + ret = hls_generate_playlist(ctx, 0); + if (ret < 0) return ret; } - ret = ff_write_chained(oc, pkt->stream_index, pkt, s); - + ret = ff_write_chained(hls->ctx, pkt->stream_index, pkt, ctx); return ret; } -static int hls_write_trailer(struct AVFormatContext *s) +static int hls_write_trailer(struct AVFormatContext *ctx) { - HLSContext *hls = s->priv_data; - AVFormatContext *oc = hls->avf; + HLSContext *hls; + + hls = ctx->priv_data; - av_write_trailer(oc); - avio_closep(&oc->pb); - avformat_free_context(oc); - av_free(hls->basename); - append_entry(hls, hls->duration); - hls_window(s, 1); + av_write_trailer(hls->ctx); + avio_closep(&hls->ctx->pb); + avformat_free_context(hls->ctx); + av_free(hls->media_filename); + hls_append_segment(hls, hls->duration); + hls_generate_playlist(ctx, 1); - free_entries(hls); + hls_free_segments(hls); avio_close(hls->pb); return 0; } @@ -328,8 +394,8 @@ static int hls_write_trailer(struct AVFormatContext *s) #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(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, + {"hls_time", "set segment length in seconds", OFFSET(target_duration), 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_wrap", "set number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E}, {"hls_base_url", "url to prepend to each playlist entry", OFFSET(baseurl), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, { NULL }, -- 1.8.5.5 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel