X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavformat%2Fhlsenc.c;h=7e3a7f2f677e8530429b8d9c63c751ff33ccbde7;hb=2f7cc21b61220d205e3c57384f354187417971fb;hp=e16fb0caa9cb881664ea8b97f48c1eb7d5dd186b;hpb=4a485daa7fc50fe5eac7a3fc7ce7d83081fa00e1;p=ffmpeg diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index e16fb0caa9c..7e3a7f2f677 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -39,13 +39,21 @@ #include "internal.h" #include "os_support.h" +typedef enum { + HLS_START_SEQUENCE_AS_START_NUMBER = 0, + HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH = 1, + HLS_START_SEQUENCE_AS_FORMATTED_DATETIME = 2, // YYYYMMDDhhmmss +} StartSequenceSourceType; + #define KEYSIZE 16 #define LINE_BUFFER_SIZE 1024 +#define HLS_MICROSECOND_UNIT 1000000 typedef struct HLSSegment { char filename[1024]; char sub_filename[1024]; double duration; /* in seconds */ + int discont; int64_t pos; int64_t size; @@ -65,6 +73,9 @@ typedef enum HLSFlags { HLS_SPLIT_BY_TIME = (1 << 5), HLS_APPEND_LIST = (1 << 6), HLS_PROGRAM_DATE_TIME = (1 << 7), + HLS_SECOND_LEVEL_SEGMENT_INDEX = (1 << 8), // include segment index in segment filenames when use_localtime e.g.: %%03d + HLS_SECOND_LEVEL_SEGMENT_DURATION = (1 << 9), // include segment duration (microsec) in segment filenames when use_localtime e.g.: %%09t + HLS_SECOND_LEVEL_SEGMENT_SIZE = (1 << 10), // include segment size (bytes) in segment filenames when use_localtime e.g.: %%014s } HLSFlags; typedef enum { @@ -79,6 +90,7 @@ typedef struct HLSContext { unsigned number; int64_t sequence; int64_t start_sequence; + uint32_t start_sequence_source_type; // enum StartSequenceSourceType AVOutputFormat *oformat; AVOutputFormat *vtt_oformat; @@ -99,6 +111,8 @@ typedef struct HLSContext { int64_t recording_time; int has_video; int has_subtitle; + int new_start; + double dpp; // duration per packet int64_t start_pts; int64_t end_pts; double duration; // last segment duration computed so far, in seconds @@ -107,6 +121,7 @@ typedef struct HLSContext { int64_t max_seg_size; // every segment file max size int nb_entries; int discontinuity_set; + int discontinuity; HLSSegment *segments; HLSSegment *last_segment; @@ -131,8 +146,14 @@ typedef struct HLSContext { char *method; double initial_prog_date_time; + char current_segment_final_filename_fmt[1024]; // when renaming segments } HLSContext; +static int get_int_from_double(double val) +{ + return (int)((val - (int)val) >= 0.001) ? (int)(val + 1) : (int)val; +} + static int mkdir_p(const char *path) { int ret = 0; char *temp = av_strdup(path); @@ -166,6 +187,58 @@ static int mkdir_p(const char *path) { return ret; } +static int replace_int_data_in_filename(char *buf, int buf_size, const char *filename, char placeholder, int64_t number) +{ + const char *p; + char *q, buf1[20], c; + int nd, len, addchar_count; + int found_count = 0; + + q = buf; + p = filename; + for (;;) { + c = *p; + if (c == '\0') + break; + if (c == '%' && *(p+1) == '%') // %% + addchar_count = 2; + else if (c == '%' && (av_isdigit(*(p+1)) || *(p+1) == placeholder)) { + nd = 0; + addchar_count = 1; + while (av_isdigit(*(p + addchar_count))) { + nd = nd * 10 + *(p + addchar_count) - '0'; + addchar_count++; + } + + if (*(p + addchar_count) == placeholder) { + len = snprintf(buf1, sizeof(buf1), "%0*"PRId64, (number < 0) ? nd : nd++, number); + if (len < 1) // returned error or empty buf1 + goto fail; + if ((q - buf + len) > buf_size - 1) + goto fail; + memcpy(q, buf1, len); + q += len; + p += (addchar_count + 1); + addchar_count = 0; + found_count++; + } + + } else + addchar_count = 1; + + while (addchar_count--) + if ((q - buf) < buf_size - 1) + *q++ = *p++; + else + goto fail; + } + *q = '\0'; + return found_count; +fail: + *q = '\0'; + return -1; +} + static int hls_delete_old_segments(HLSContext *hls) { HLSSegment *segment, *previous_segment = NULL; @@ -173,6 +246,8 @@ static int hls_delete_old_segments(HLSContext *hls) { int ret = 0, path_size, sub_path_size; char *dirname = NULL, *p, *sub_path; char *path = NULL; + AVDictionary *options = NULL; + AVIOContext *out = NULL; segment = hls->segments; while (segment) { @@ -183,7 +258,6 @@ static int hls_delete_old_segments(HLSContext *hls) { segment = hls->old_segments; while (segment) { playlist_duration -= segment->duration; - hls->initial_prog_date_time += segment->duration; previous_segment = segment; segment = previous_segment->next; if (playlist_duration <= -previous_segment->duration) { @@ -192,7 +266,7 @@ static int hls_delete_old_segments(HLSContext *hls) { } } - if (segment) { + if (segment && !hls->use_localtime_mkdir) { if (hls->segment_filename) { dirname = av_strdup(hls->segment_filename); } else { @@ -209,22 +283,32 @@ static int hls_delete_old_segments(HLSContext *hls) { while (segment) { av_log(hls, AV_LOG_DEBUG, "deleting old segment %s\n", segment->filename); - path_size = strlen(dirname) + strlen(segment->filename) + 1; + path_size = (hls->use_localtime_mkdir ? 0 : strlen(dirname)) + strlen(segment->filename) + 1; path = av_malloc(path_size); if (!path) { ret = AVERROR(ENOMEM); goto fail; } - av_strlcpy(path, dirname, path_size); - av_strlcat(path, segment->filename, path_size); - if (unlink(path) < 0) { + if (hls->use_localtime_mkdir) + av_strlcpy(path, segment->filename, path_size); + else { // segment->filename contains basename only + av_strlcpy(path, dirname, path_size); + av_strlcat(path, segment->filename, path_size); + } + + if (hls->method) { + av_dict_set(&options, "method", "DELETE", 0); + if ((ret = hls->avf->io_open(hls->avf, &out, path, AVIO_FLAG_WRITE, &options)) < 0) + goto fail; + ff_format_io_close(hls->avf, &out); + } else if (unlink(path) < 0) { av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", path, strerror(errno)); } - if (segment->sub_filename[0] != '\0') { - sub_path_size = strlen(dirname) + strlen(segment->sub_filename) + 1; + if ((segment->sub_filename[0] != '\0')) { + sub_path_size = strlen(segment->sub_filename) + 1 + (dirname ? strlen(dirname) : 0); sub_path = av_malloc(sub_path_size); if (!sub_path) { ret = AVERROR(ENOMEM); @@ -233,7 +317,15 @@ static int hls_delete_old_segments(HLSContext *hls) { av_strlcpy(sub_path, dirname, sub_path_size); av_strlcat(sub_path, segment->sub_filename, sub_path_size); - if (unlink(sub_path) < 0) { + + if (hls->method) { + av_dict_set(&options, "method", "DELETE", 0); + if ((ret = hls->avf->io_open(hls->avf, &out, sub_path, AVIO_FLAG_WRITE, &options)) < 0) { + av_free(sub_path); + goto fail; + } + ff_format_io_close(hls->avf, &out); + } else if (unlink(sub_path) < 0) { av_log(hls, AV_LOG_ERROR, "failed to delete old segment %s: %s\n", sub_path, strerror(errno)); } @@ -356,10 +448,21 @@ static int hls_mux_init(AVFormatContext *s) st->time_base = s->streams[i]->time_base; } hls->start_pos = 0; + hls->new_start = 1; return 0; } +static HLSSegment *find_segment_by_filename(HLSSegment *segment, const char *filename) +{ + while (segment) { + if (!av_strcasecmp(segment->filename,filename)) + return segment; + segment = segment->next; + } + return (HLSSegment *) NULL; +} + /* Create a new segment and append it to the segment list */ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double duration, int64_t pos, int64_t size) @@ -371,11 +474,57 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double if (!en) return AVERROR(ENOMEM); + if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && + strlen(hls->current_segment_final_filename_fmt)) { + av_strlcpy(hls->avf->filename, hls->current_segment_final_filename_fmt, sizeof(hls->avf->filename)); + if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) { + char * filename = av_strdup(hls->avf->filename); // %%s will be %s after strftime + if (!filename) { + av_free(en); + return AVERROR(ENOMEM); + } + if (replace_int_data_in_filename(hls->avf->filename, sizeof(hls->avf->filename), + filename, 's', pos + size) < 1) { + av_log(hls, AV_LOG_ERROR, + "Invalid second level segment filename template '%s', " + "you can try to remove second_level_segment_size flag\n", + filename); + av_free(filename); + av_free(en); + return AVERROR(EINVAL); + } + av_free(filename); + } + if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) { + char * filename = av_strdup(hls->avf->filename); // %%t will be %t after strftime + if (!filename) { + av_free(en); + return AVERROR(ENOMEM); + } + if (replace_int_data_in_filename(hls->avf->filename, sizeof(hls->avf->filename), + filename, 't', (int64_t)round(duration * HLS_MICROSECOND_UNIT)) < 1) { + av_log(hls, AV_LOG_ERROR, + "Invalid second level segment filename template '%s', " + "you can try to remove second_level_segment_time flag\n", + filename); + av_free(filename); + av_free(en); + return AVERROR(EINVAL); + } + av_free(filename); + } + } + + filename = av_basename(hls->avf->filename); if (hls->use_localtime_mkdir) { filename = hls->avf->filename; } + if (find_segment_by_filename(hls->segments, filename) + || find_segment_by_filename(hls->old_segments, filename)) { + av_log(hls, AV_LOG_WARNING, "Duplicated segment filename detected: %s\n", filename); + } av_strlcpy(en->filename, filename, sizeof(en->filename)); if(hls->has_subtitle) @@ -387,6 +536,12 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double en->pos = pos; en->size = size; en->next = NULL; + en->discont = 0; + + if (hls->discontinuity) { + en->discont = 1; + hls->discontinuity = 0; + } if (hls->key_info_file) { av_strlcpy(en->key_uri, hls->key_uri, sizeof(en->key_uri)); @@ -406,6 +561,7 @@ static int hls_append_segment(struct AVFormatContext *s, HLSContext *hls, double if (hls->max_nb_segments && hls->nb_entries >= hls->max_nb_segments) { en = hls->segments; + hls->initial_prog_date_time += en->duration; hls->segments = en->next; if (en && hls->flags & HLS_DELETE_SEGMENTS && !(hls->flags & HLS_SINGLE_FILE || hls->wrap)) { @@ -446,10 +602,23 @@ static int parse_playlist(AVFormatContext *s, const char *url) goto fail; } + hls->discontinuity = 0; while (!avio_feof(in)) { read_chomp_line(in, line, sizeof(line)); if (av_strstart(line, "#EXT-X-MEDIA-SEQUENCE:", &ptr)) { - hls->sequence = atoi(ptr); + int64_t tmp_sequence = strtoll(ptr, NULL, 10); + if (tmp_sequence < hls->sequence) + av_log(hls, AV_LOG_VERBOSE, + "Found playlist sequence number was smaller """ + "than specified start sequence number: %"PRId64" < %"PRId64", " + "omitting\n", tmp_sequence, hls->start_sequence); + else { + av_log(hls, AV_LOG_DEBUG, "Found playlist sequence number: %"PRId64"\n", tmp_sequence); + hls->sequence = tmp_sequence; + } + } else if (av_strstart(line, "#EXT-X-DISCONTINUITY", &ptr)) { + is_segment = 1; + hls->discontinuity = 1; } else if (av_strstart(line, "#EXTINF:", &ptr)) { is_segment = 1; hls->duration = atof(ptr); @@ -525,8 +694,8 @@ static int hls_window(AVFormatContext *s, int last) goto fail; for (en = hls->segments; en; en = en->next) { - if (target_duration < en->duration) - target_duration = ceil(en->duration); + if (target_duration <= en->duration) + target_duration = get_int_from_double(en->duration); } hls->discontinuity_set = 0; @@ -560,6 +729,10 @@ static int hls_window(AVFormatContext *s, int last) iv_string = en->iv_string; } + if (en->discont) { + avio_printf(out, "#EXT-X-DISCONTINUITY\n"); + } + if (hls->flags & HLS_ROUND_DURATIONS) avio_printf(out, "#EXTINF:%ld,\n", lrint(en->duration)); else @@ -653,9 +826,8 @@ static int hls_start(AVFormatContext *s) av_strlcpy(vtt_oc->filename, c->vtt_basename, sizeof(vtt_oc->filename)); } else if (c->max_seg_size > 0) { - if (av_get_frame_filename2(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), + c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s', you can try to use -use_localtime 1 with it\n", c->basename); return AVERROR(EINVAL); } @@ -669,7 +841,53 @@ static int hls_start(AVFormatContext *s) av_log(oc, AV_LOG_ERROR, "Could not get segment filename with use_localtime\n"); return AVERROR(EINVAL); } - + if (c->flags & HLS_SECOND_LEVEL_SEGMENT_INDEX) { + char * filename = av_strdup(oc->filename); // %%d will be %d after strftime + if (!filename) + return AVERROR(ENOMEM); + if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), + filename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { + av_log(c, AV_LOG_ERROR, + "Invalid second level segment filename template '%s', " + "you can try to remove second_level_segment_index flag\n", + filename); + av_free(filename); + return AVERROR(EINVAL); + } + av_free(filename); + } + if (c->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) { + av_strlcpy(c->current_segment_final_filename_fmt, oc->filename, + sizeof(c->current_segment_final_filename_fmt)); + if (c->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) { + char * filename = av_strdup(oc->filename); // %%s will be %s after strftime + if (!filename) + return AVERROR(ENOMEM); + if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), filename, 's', 0) < 1) { + av_log(c, AV_LOG_ERROR, + "Invalid second level segment filename template '%s', " + "you can try to remove second_level_segment_size flag\n", + filename); + av_free(filename); + return AVERROR(EINVAL); + } + av_free(filename); + } + if (c->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) { + char * filename = av_strdup(oc->filename); // %%t will be %t after strftime + if (!filename) + return AVERROR(ENOMEM); + if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), filename, 't', 0) < 1) { + av_log(c, AV_LOG_ERROR, + "Invalid second level segment filename template '%s', " + "you can try to remove second_level_segment_time flag\n", + filename); + av_free(filename); + return AVERROR(EINVAL); + } + av_free(filename); + } + } if (c->use_localtime_mkdir) { const char *dir; char *fn_copy = av_strdup(oc->filename); @@ -684,16 +902,14 @@ static int hls_start(AVFormatContext *s) } av_free(fn_copy); } - } else if (av_get_frame_filename2(oc->filename, sizeof(oc->filename), - c->basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + } else if (replace_int_data_in_filename(oc->filename, sizeof(oc->filename), + c->basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s' you can try to use -use_localtime 1 with it\n", c->basename); return AVERROR(EINVAL); } if( c->vtt_basename) { - if (av_get_frame_filename2(vtt_oc->filename, sizeof(vtt_oc->filename), - c->vtt_basename, c->wrap ? c->sequence % c->wrap : c->sequence, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + if (replace_int_data_in_filename(vtt_oc->filename, sizeof(vtt_oc->filename), + c->vtt_basename, 'd', c->wrap ? c->sequence % c->wrap : c->sequence) < 1) { av_log(vtt_oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", c->vtt_basename); return AVERROR(EINVAL); } @@ -759,21 +975,48 @@ fail: return err; } +static const char * get_default_pattern_localtime_fmt(void) +{ + char b[21]; + time_t t = time(NULL); + struct tm *p, tmbuf; + p = localtime_r(&t, &tmbuf); + // no %s support when strftime returned error or left format string unchanged + return (!strftime(b, sizeof(b), "%s", p) || !strcmp(b, "%s")) ? "-%Y%m%d%H%M%S.ts" : "-%s.ts"; +} + static int hls_write_header(AVFormatContext *s) { HLSContext *hls = s->priv_data; int ret, i; char *p; const char *pattern = "%d.ts"; - const char *pattern_localtime_fmt = "-%s.ts"; + const char *pattern_localtime_fmt = get_default_pattern_localtime_fmt(); const char *vtt_pattern = "%d.vtt"; AVDictionary *options = NULL; int basename_size; int vtt_basename_size; + if (hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH || hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_FORMATTED_DATETIME) { + time_t t = time(NULL); // we will need it in either case + if (hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH) { + hls->start_sequence = (int64_t)t; + } else if (hls->start_sequence_source_type == HLS_START_SEQUENCE_AS_FORMATTED_DATETIME) { + char b[15]; + struct tm *p, tmbuf; + if (!(p = localtime_r(&t, &tmbuf))) + return AVERROR(ENOMEM); + if (!strftime(b, sizeof(b), "%Y%m%d%H%M%S", p)) + return AVERROR(ENOMEM); + hls->start_sequence = strtoll(b, NULL, 10); + } + av_log(hls, AV_LOG_DEBUG, "start_number evaluated to %"PRId64"\n", hls->start_sequence); + } + hls->sequence = hls->start_sequence; hls->recording_time = (hls->init_time ? hls->init_time : hls->time) * AV_TIME_BASE; hls->start_pts = AV_NOPTS_VALUE; + hls->current_segment_final_filename_fmt[0] = '\0'; if (hls->flags & HLS_PROGRAM_DATE_TIME) { time_t now0; @@ -848,7 +1091,42 @@ static int hls_write_header(AVFormatContext *s) av_strlcat(hls->basename, pattern, basename_size); } } - + if (!hls->use_localtime) { + if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) { + av_log(hls, AV_LOG_ERROR, + "second_level_segment_duration hls_flag requires use_localtime to be true\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) { + av_log(hls, AV_LOG_ERROR, + "second_level_segment_size hls_flag requires use_localtime to be true\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if (hls->flags & HLS_SECOND_LEVEL_SEGMENT_INDEX) { + av_log(hls, AV_LOG_ERROR, + "second_level_segment_index hls_flag requires use_localtime to be true\n"); + ret = AVERROR(EINVAL); + goto fail; + } + } else { + const char *proto = avio_find_protocol_name(hls->basename); + int segment_renaming_ok = proto && !strcmp(proto, "file"); + + if ((hls->flags & HLS_SECOND_LEVEL_SEGMENT_DURATION) && !segment_renaming_ok) { + av_log(hls, AV_LOG_ERROR, + "second_level_segment_duration hls_flag works only with file protocol segment names\n"); + ret = AVERROR(EINVAL); + goto fail; + } + if ((hls->flags & HLS_SECOND_LEVEL_SEGMENT_SIZE) && !segment_renaming_ok) { + av_log(hls, AV_LOG_ERROR, + "second_level_segment_size hls_flag works only with file protocol segment names\n"); + ret = AVERROR(EINVAL); + goto fail; + } + } if(hls->has_subtitle) { if (hls->flags & HLS_SINGLE_FILE) @@ -883,6 +1161,7 @@ static int hls_write_header(AVFormatContext *s) if (hls->flags & HLS_APPEND_LIST) { parse_playlist(s, s->filename); + hls->discontinuity = 1; if (hls->init_time > 0) { av_log(s, AV_LOG_WARNING, "append_list mode does not support hls_init_time," " hls_init_time value will have no effect\n"); @@ -979,21 +1258,36 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) 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) { + if (hls->new_start) { + hls->new_start = 0; + hls->duration = (double)(pkt->pts - hls->end_pts) + * st->time_base.num / st->time_base.den; + hls->dpp = (double)(pkt->duration) * st->time_base.num / st->time_base.den; + } else { + hls->duration += (double)(pkt->duration) * 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; + char *old_filename = av_strdup(hls->avf->filename); + + if (!old_filename) { + return AVERROR(ENOMEM); + } + av_write_frame(oc, NULL); /* Flush any buffered data */ new_start_pos = avio_tell(hls->avf->pb); hls->size = new_start_pos - hls->start_pos; ret = hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); hls->start_pos = new_start_pos; - if (ret < 0) + if (ret < 0) { + av_free(old_filename); return ret; + } hls->end_pts = pkt->pts; hls->duration = 0; @@ -1008,6 +1302,10 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) if (hls->start_pos >= hls->max_seg_size) { hls->sequence++; ff_format_io_close(s, &oc->pb); + if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && + strlen(hls->current_segment_final_filename_fmt)) { + ff_rename(old_filename, hls->avf->filename, hls); + } if (hls->vtt_avf) ff_format_io_close(s, &hls->vtt_avf->pb); ret = hls_start(s); @@ -1019,22 +1317,25 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) hls->number++; } else { ff_format_io_close(s, &oc->pb); + if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && + strlen(hls->current_segment_final_filename_fmt)) { + ff_rename(old_filename, hls->avf->filename, hls); + } if (hls->vtt_avf) ff_format_io_close(s, &hls->vtt_avf->pb); ret = hls_start(s); } - if (ret < 0) + if (ret < 0) { + av_free(old_filename); return ret; + } - if( st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE ) - oc = hls->vtt_avf; - else - oc = hls->avf; - - if ((ret = hls_window(s, 0)) < 0) + if ((ret = hls_window(s, 0)) < 0) { + av_free(old_filename); return ret; + } } ret = ff_write_chained(oc, stream_index, pkt, s, 0); @@ -1047,12 +1348,24 @@ static int hls_write_trailer(struct AVFormatContext *s) HLSContext *hls = s->priv_data; AVFormatContext *oc = hls->avf; AVFormatContext *vtt_oc = hls->vtt_avf; + char *old_filename = av_strdup(hls->avf->filename); + + if (!old_filename) { + return AVERROR(ENOMEM); + } + av_write_trailer(oc); if (oc->pb) { hls->size = avio_tell(hls->avf->pb) - hls->start_pos; ff_format_io_close(s, &oc->pb); - hls_append_segment(s, hls, hls->duration, hls->start_pos, hls->size); + /* after av_write_trailer, then duration + 1 duration per packet */ + hls_append_segment(s, hls, hls->duration + hls->dpp, hls->start_pos, hls->size); + } + + if ((hls->flags & (HLS_SECOND_LEVEL_SEGMENT_SIZE | HLS_SECOND_LEVEL_SEGMENT_DURATION)) && + strlen(hls->current_segment_final_filename_fmt)) { + ff_rename(old_filename, hls->avf->filename, hls); } if (vtt_oc) { @@ -1075,6 +1388,7 @@ static int hls_write_trailer(struct AVFormatContext *s) hls_free_segments(hls->segments); hls_free_segments(hls->old_segments); + av_free(old_filename); return 0; } @@ -1103,13 +1417,19 @@ static const AVOption options[] = { {"split_by_time", "split the hls segment by time which user set by hls_time", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SPLIT_BY_TIME }, 0, UINT_MAX, E, "flags"}, {"append_list", "append the new segments into old hls segment list", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_APPEND_LIST }, 0, UINT_MAX, E, "flags"}, {"program_date_time", "add EXT-X-PROGRAM-DATE-TIME", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_PROGRAM_DATE_TIME }, 0, UINT_MAX, E, "flags"}, + {"second_level_segment_index", "include segment index in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_INDEX }, 0, UINT_MAX, E, "flags"}, + {"second_level_segment_duration", "include segment duration in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_DURATION }, 0, UINT_MAX, E, "flags"}, + {"second_level_segment_size", "include segment size in segment filenames when use_localtime", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_SECOND_LEVEL_SEGMENT_SIZE }, 0, UINT_MAX, E, "flags"}, {"use_localtime", "set filename expansion with strftime at segment creation", OFFSET(use_localtime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, {"use_localtime_mkdir", "create last directory component in strftime-generated filename", OFFSET(use_localtime_mkdir), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, {"hls_playlist_type", "set the HLS playlist type", OFFSET(pl_type), AV_OPT_TYPE_INT, {.i64 = PLAYLIST_TYPE_NONE }, 0, PLAYLIST_TYPE_NB-1, E, "pl_type" }, {"event", "EVENT playlist", 0, AV_OPT_TYPE_CONST, {.i64 = PLAYLIST_TYPE_EVENT }, INT_MIN, INT_MAX, E, "pl_type" }, {"vod", "VOD playlist", 0, AV_OPT_TYPE_CONST, {.i64 = PLAYLIST_TYPE_VOD }, INT_MIN, INT_MAX, E, "pl_type" }, {"method", "set the HTTP method", OFFSET(method), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, - + {"hls_start_number_source", "set source of first number in sequence", OFFSET(start_sequence_source_type), AV_OPT_TYPE_INT, {.i64 = HLS_START_SEQUENCE_AS_START_NUMBER }, 0, HLS_START_SEQUENCE_AS_FORMATTED_DATETIME, E, "start_sequence_source_type" }, + {"generic", "start_number value (default)", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_START_NUMBER }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, + {"epoch", "seconds since epoch", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_SECONDS_SINCE_EPOCH }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, + {"datetime", "current datetime as YYYYMMDDhhmmss", 0, AV_OPT_TYPE_CONST, {.i64 = HLS_START_SEQUENCE_AS_FORMATTED_DATETIME }, INT_MIN, INT_MAX, E, "start_sequence_source_type" }, { NULL }, };