X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fhlsenc.c;h=96b780fe37776a5021a70ded1841b85af29d7d0a;hb=70c77fdfc1076fd7f6cd20079237ddc97e1a10bc;hp=aa38d0546208d8dfb81e471c86f247a6b8bf64f5;hpb=34e2ce5dde073244ccb2b62f930e96fe612690f7;p=ffmpeg diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index aa38d054620..96b780fe377 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -33,7 +33,7 @@ typedef struct ListEntry { char name[1024]; - int duration; + int64_t duration; // segment duration in AV_TIME_BASE units struct ListEntry *next; } ListEntry; @@ -47,17 +47,19 @@ typedef struct HLSContext { float time; // Set by a private option. int size; // Set by a private option. int wrap; // Set by a private option. + int version; // Set by a private option. + int allowcache; int64_t recording_time; int has_video; + // The following timestamps are in AV_TIME_BASE units. int64_t start_pts; int64_t end_pts; - int64_t duration; // last segment duration computed so far, in seconds + int64_t duration; // last segment duration computed so far. int nb_entries; ListEntry *list; ListEntry *end_list; char *basename; char *baseurl; - AVIOContext *pb; } HLSContext; static int hls_mux_init(AVFormatContext *s) @@ -72,19 +74,23 @@ static int hls_mux_init(AVFormatContext *s) oc->oformat = hls->oformat; oc->interrupt_callback = s->interrupt_callback; + oc->opaque = s->opaque; + oc->io_open = s->io_open; + oc->io_close = s->io_close; for (i = 0; i < s->nb_streams; i++) { AVStream *st; if (!(st = avformat_new_stream(oc, NULL))) return AVERROR(ENOMEM); - avcodec_copy_context(st->codec, s->streams[i]->codec); + avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar); st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; + st->time_base = s->streams[i]->time_base; } return 0; } -static int append_entry(HLSContext *hls, uint64_t duration) +static int append_entry(HLSContext *hls, int64_t duration) { ListEntry *en = av_malloc(sizeof(*en)); @@ -130,12 +136,14 @@ static int hls_window(AVFormatContext *s, int last) { HLSContext *hls = s->priv_data; ListEntry *en; - int target_duration = 0; + int64_t target_duration = 0; int ret = 0; + AVIOContext *out = NULL; + char temp_filename[1024]; int64_t sequence = FFMAX(hls->start_sequence, hls->sequence - hls->size); - if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", s->filename); + if ((ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL)) < 0) goto fail; for (en = hls->list; en; en = en->next) { @@ -143,26 +151,38 @@ static int hls_window(AVFormatContext *s, int last) target_duration = en->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); + avio_printf(out, "#EXTM3U\n"); + avio_printf(out, "#EXT-X-VERSION:%d\n", hls->version); + if (hls->allowcache == 0 || hls->allowcache == 1) { + avio_printf(out, "#EXT-X-ALLOW-CACHE:%s\n", hls->allowcache == 0 ? "NO" : "YES"); + } + avio_printf(out, "#EXT-X-TARGETDURATION:%"PRId64"\n", + av_rescale_rnd(target_duration, 1, AV_TIME_BASE, + AV_ROUND_UP)); + avio_printf(out, "#EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); av_log(s, AV_LOG_VERBOSE, "EXT-X-MEDIA-SEQUENCE:%"PRId64"\n", sequence); for (en = hls->list; en; en = en->next) { - avio_printf(hls->pb, "#EXTINF:%d,\n", en->duration); + if (hls->version > 2) + avio_printf(out, "#EXTINF:%f\n", + (double)en->duration / AV_TIME_BASE); + else + avio_printf(out, "#EXTINF:%"PRId64",\n", + av_rescale(en->duration, 1, AV_TIME_BASE)); if (hls->baseurl) - avio_printf(hls->pb, "%s", hls->baseurl); - avio_printf(hls->pb, "%s\n", en->name); + avio_printf(out, "%s", hls->baseurl); + avio_printf(out, "%s\n", en->name); } if (last) - avio_printf(hls->pb, "#EXT-X-ENDLIST\n"); + avio_printf(out, "#EXT-X-ENDLIST\n"); fail: - avio_closep(&hls->pb); + ff_format_io_close(s, &out); + if (ret >= 0) + ff_rename(temp_filename, s->filename); return ret; } @@ -177,8 +197,7 @@ static int hls_start(AVFormatContext *s) return AVERROR(EINVAL); c->number++; - if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, - &s->interrupt_callback, NULL)) < 0) + if ((err = s->io_open(s, &oc->pb, oc->filename, AVIO_FLAG_WRITE, NULL)) < 0) return err; if (oc->oformat->priv_class && oc->priv_data) @@ -201,7 +220,7 @@ static int hls_write_header(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) hls->has_video += - s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO; + s->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO; if (hls->has_video > 1) av_log(s, AV_LOG_WARNING, @@ -256,34 +275,33 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) AVFormatContext *oc = hls->avf; AVStream *st = s->streams[pkt->stream_index]; int64_t end_pts = hls->recording_time * hls->number; + int64_t pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); int ret, can_split = 1; if (hls->start_pts == AV_NOPTS_VALUE) { - hls->start_pts = pkt->pts; - hls->end_pts = pkt->pts; + hls->start_pts = pts; + hls->end_pts = pts; } if (hls->has_video) { - can_split = st->codec->codec_type == AVMEDIA_TYPE_VIDEO && + can_split = st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && pkt->flags & AV_PKT_FLAG_KEY; } if (pkt->pts == AV_NOPTS_VALUE) can_split = 0; else - hls->duration = av_rescale(pkt->pts - hls->end_pts, - st->time_base.num, st->time_base.den); + hls->duration = pts - hls->end_pts; - if (can_split && av_compare_ts(pkt->pts - hls->start_pts, st->time_base, - end_pts, AV_TIME_BASE_Q) >= 0) { + if (can_split && pts - hls->start_pts >= end_pts) { ret = append_entry(hls, hls->duration); if (ret) return ret; - hls->end_pts = pkt->pts; + hls->end_pts = pts; hls->duration = 0; av_write_frame(oc, NULL); /* Flush any buffered data */ - avio_close(oc->pb); + ff_format_io_close(s, &oc->pb); ret = hls_start(s); @@ -307,14 +325,13 @@ static int hls_write_trailer(struct AVFormatContext *s) AVFormatContext *oc = hls->avf; av_write_trailer(oc); - avio_closep(&oc->pb); + ff_format_io_close(s, &oc->pb); avformat_free_context(oc); av_free(hls->basename); append_entry(hls, hls->duration); hls_window(s, 1); free_entries(hls); - avio_close(hls->pb); return 0; } @@ -325,7 +342,9 @@ static const AVOption options[] = { {"hls_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E}, {"hls_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E}, {"hls_wrap", "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_version", "protocol version", OFFSET(version), AV_OPT_TYPE_INT, {.i64 = 3}, 2, 3, E}, { NULL }, };