X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fsegment.c;h=8afb41f93dae9e7fac5ea008106bab28c56b1754;hb=bfe5454cd238b16e7977085f880205229103eccb;hp=1e928a7ebf7e772a4436fc84dfeba199d1f4cef9;hpb=c864c3968acce9cc96631ebbd362cbc207eddfbd;p=ffmpeg diff --git a/libavformat/segment.c b/libavformat/segment.c index 1e928a7ebf7..8afb41f93da 100644 --- a/libavformat/segment.c +++ b/libavformat/segment.c @@ -37,6 +37,7 @@ typedef struct { AVFormatContext *avf; char *format; /**< Set by a private option. */ char *list; /**< Set by a private option. */ + int list_type; /**< Set by a private option. */ float time; /**< Set by a private option. */ int size; /**< Set by a private option. */ int wrap; /**< Set by a private option. */ @@ -48,6 +49,11 @@ typedef struct { AVIOContext *pb; } SegmentContext; +enum { + LIST_FLAT, + LIST_HLS +}; + static int segment_mux_init(AVFormatContext *s) { SegmentContext *seg = s->priv_data; @@ -72,6 +78,36 @@ static int segment_mux_init(AVFormatContext *s) return 0; } +static int segment_hls_window(AVFormatContext *s, int last) +{ + SegmentContext *seg = s->priv_data; + int i, ret = 0; + char buf[1024]; + + if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + goto fail; + + avio_printf(seg->pb, "#EXTM3U\n"); + avio_printf(seg->pb, "#EXT-X-VERSION:3\n"); + avio_printf(seg->pb, "#EXT-X-TARGETDURATION:%d\n", (int)seg->time); + avio_printf(seg->pb, "#EXT-X-MEDIA-SEQUENCE:%d\n", + FFMAX(0, seg->number - seg->size)); + + for (i = FFMAX(0, seg->number - seg->size); + i < seg->number; i++) { + avio_printf(seg->pb, "#EXTINF:%d,\n", (int)seg->time); + av_get_frame_filename(buf, sizeof(buf), s->filename, i); + avio_printf(seg->pb, "%s\n", buf); + } + + if (last) + avio_printf(seg->pb, "#EXT-X-ENDLIST\n"); +fail: + avio_closep(&seg->pb); + return ret; +} + static int segment_start(AVFormatContext *s, int write_header) { SegmentContext *c = s->priv_data; @@ -98,7 +134,7 @@ static int segment_start(AVFormatContext *s, int write_header) return err; if (oc->oformat->priv_class && oc->priv_data) - av_opt_set(oc->priv_data, "resend_headers", "1", 0); + av_opt_set(oc->priv_data, "resend_headers", "1", 0); /* mpegts specific */ if (write_header) { if ((err = avformat_write_header(oc, NULL)) < 0) @@ -112,7 +148,7 @@ static int segment_end(AVFormatContext *oc, int write_trailer) { int ret = 0; - av_write_frame(oc, NULL); /* Flush any buffered data */ + av_write_frame(oc, NULL); /* Flush any buffered data (fragmented mp4) */ if (write_trailer) av_write_trailer(oc); avio_close(oc->pb); @@ -152,7 +188,7 @@ static int seg_write_header(AVFormatContext *s) if (!seg->write_header_trailer) seg->individual_header_trailer = 0; - if (seg->list) + if (seg->list && seg->list_type != LIST_HLS) if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, &s->interrupt_callback, NULL)) < 0) goto fail; @@ -174,7 +210,7 @@ static int seg_write_header(AVFormatContext *s) } if (seg->oformat->flags & AVFMT_NOFILE) { av_log(s, AV_LOG_ERROR, "format %s not supported.\n", - oc->oformat->name); + seg->oformat->name); ret = AVERROR(EINVAL); goto fail; } @@ -211,8 +247,13 @@ static int seg_write_header(AVFormatContext *s) } if (seg->list) { - avio_printf(seg->pb, "%s\n", oc->filename); - avio_flush(seg->pb); + if (seg->list_type == LIST_HLS) { + if ((ret = segment_hls_window(s, 0)) < 0) + goto fail; + } else { + avio_printf(seg->pb, "%s\n", oc->filename); + avio_flush(seg->pb); + } } fail: @@ -252,13 +293,18 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) oc = seg->avf; if (seg->list) { - avio_printf(seg->pb, "%s\n", oc->filename); - avio_flush(seg->pb); - if (seg->size && !(seg->number % seg->size)) { - avio_close(seg->pb); - if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + if (seg->list_type == LIST_HLS) { + if ((ret = segment_hls_window(s, 0)) < 0) goto fail; + } else { + avio_printf(seg->pb, "%s\n", oc->filename); + avio_flush(seg->pb); + if (seg->size && !(seg->number % seg->size)) { + avio_closep(&seg->pb); + if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE, + &s->interrupt_callback, NULL)) < 0) + goto fail; + } } } } @@ -281,15 +327,25 @@ static int seg_write_trailer(struct AVFormatContext *s) AVFormatContext *oc = seg->avf; int ret; if (!seg->write_header_trailer) { - ret = segment_end(oc, 0); + if ((ret = segment_end(oc, 0)) < 0) + goto fail; open_null_ctx(&oc->pb); - av_write_trailer(oc); + ret = av_write_trailer(oc); close_null_ctx(oc->pb); } else { ret = segment_end(oc, 1); } - if (seg->list) - avio_close(seg->pb); + + if (ret < 0) + goto fail; + + if (seg->list && seg->list_type == LIST_HLS) { + if ((ret = segment_hls_window(s, 1) < 0)) + goto fail; + } + +fail: + avio_close(seg->pb); avformat_free_context(oc); return ret; } @@ -301,6 +357,9 @@ static const AVOption options[] = { { "segment_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E }, { "segment_list", "output the segment list", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, { "segment_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E }, + { "segment_list_type", "segment list format", OFFSET(list_type), AV_OPT_TYPE_INT, {.i64 = LIST_FLAT}, 0, 2, E, "list_type" }, + { "flat", "plain list (default)", 0, AV_OPT_TYPE_CONST, {.i64 = LIST_FLAT}, 0, 0, E, "list_type" }, + { "hls", "Apple HTTP Live Streaming compatible", 0, AV_OPT_TYPE_CONST, {.i64 = LIST_HLS}, 0, 0, E, "list_type" }, { "segment_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, { "write_header_trailer", "write a header to the first segment and a trailer to the last one", OFFSET(write_header_trailer), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, E }, @@ -319,7 +378,7 @@ AVOutputFormat ff_segment_muxer = { .name = "segment", .long_name = NULL_IF_CONFIG_SMALL("segment"), .priv_data_size = sizeof(SegmentContext), - .flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE, + .flags = AVFMT_NOFILE, .write_header = seg_write_header, .write_packet = seg_write_packet, .write_trailer = seg_write_trailer,