From patchwork Tue Feb 23 00:07:12 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tak Li X-Patchwork-Id: 25911 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id C071944A971 for ; Tue, 23 Feb 2021 04:29:53 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8FCD368A936; Tue, 23 Feb 2021 04:29:53 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-pg1-f173.google.com (mail-pg1-f173.google.com [209.85.215.173]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id EB68B68A315 for ; Tue, 23 Feb 2021 04:29:46 +0200 (EET) Received: by mail-pg1-f173.google.com with SMTP id a4so11437941pgc.11 for ; Mon, 22 Feb 2021 18:29:46 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=thematroid-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=yTRrx3jftTc5SQcORiYHcyzIfvTVjXD3J0NmkIjnAog=; b=OLnF0JS/EG+WhH1u3yjN8dGaCQKONZVnegUiyPg4/JNSAEGiCO4w2t39WCezeXmgxX uhA9MUCKlXAqZv5FqD0qqpAe1CC7PawUAHp4sRHkaL4oSaeDHCW+sdNbBB1JOsXf+XWB W3Y/BgULpRNVbiuL7ICRIMEZZ/pl0CyuDnOa47MO3dXxXP496+7jT4INNja6w+ceDjfr xWp8kjsVRdVCYSwqMHIBflpqHfhLPR4lJSTnmHKJWkfRylTWczNxvKaw1vXEyCGH8O7j MtSVgSWHEahfzdt2B5HCa5MWco+XzhrbY16MFB/7wxoUP3OoIwlyiKm7jVPCbyTt6dBP Gb8Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:mime-version :content-transfer-encoding; bh=yTRrx3jftTc5SQcORiYHcyzIfvTVjXD3J0NmkIjnAog=; b=BYyOGRi3J3Y5VrlPCwR8DvDe8nsxmhrhajz3ChadifhRLNUrjdZsRU1q3nEDr66aS4 makPAC5I+O2VbNaFbLuTs1XQ7W5esNMMNfEIhSg5Nw/iF6wLgKU9hlJPORQ0VxAZedIE lAqnsk8fBdvJEDUlRjbvjs9EtxPHXq7Ex3Csw8tHHm2enZ79zgk7BRDaCKqk0r5U6DCO yFzLeF/ORN+XCqss/03XJTQF03U1gHgiH+ys2Me36Nmt0ImhlC6guAgTf+dp9+7N+CYc gefSCJM8cBWQp3TnCvHNaPsHsHCJuYV4wdQ1XpkFq9amxZq6E1jaSXHkhYG8uDojxHxA 4tiw== X-Gm-Message-State: AOAM532Qugiix1MdP4RY+OzOXNkbeL9i9RgGT/9d747WppLef8xJmkWv QW8M0FYtj8g6pnEd2uR5W75QV6si5awOPg== X-Google-Smtp-Source: ABdhPJzrDsenAyfNXspA9RXk2qgAdFF5vbrKWvqBKEJvo50ii4KjqFum2qogTim2GsY0sNnXyvt4EQ== X-Received: by 2002:a17:902:b941:b029:e3:1628:97b7 with SMTP id h1-20020a170902b941b02900e3162897b7mr24628402pls.60.1614038835836; Mon, 22 Feb 2021 16:07:15 -0800 (PST) Received: from localhost.localdomain ([2601:647:5a00:74f0:a947:a711:d239:9c36]) by smtp.gmail.com with ESMTPSA id v4sm19709913pff.156.2021.02.22.16.07.14 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 22 Feb 2021 16:07:15 -0800 (PST) From: tak@thematroid.com X-Google-Original-From: tak@matroid.com To: ffmpeg-devel@ffmpeg.org Date: Mon, 22 Feb 2021 16:07:12 -0800 Message-Id: <20210223000712.26949-1-tak@matroid.com> X-Mailer: git-send-email 2.24.3 (Apple Git-128) MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] Matroid patch: add support for writing additional metadata to filename X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: tak-matroid Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: tak-matroid * Support micro seconds for image2 strftime filename * Support micro seconds for segment strftime filename * Support duration for segment output * Allow split on keyframe with NOPTS * Add EXT-X-PROGRAM-DATE-TIME parsing to hls.c * Add global timestamp (gts) metatdata to AVPacket and AVFrame * Support gts for image2 filename * Support gts for segment filename Signed-off-by: tak-matroid --- fftools/ffmpeg.c | 1 + libavcodec/avpacket.c | 2 + libavcodec/decode.c | 1 + libavcodec/encode.c | 9 +++ libavcodec/packet.h | 2 + libavformat/avformat.h | 5 +- libavformat/hls.c | 58 ++++++++++++++++++- libavformat/hlsenc.c | 15 +++-- libavformat/img2enc.c | 49 ++++++++++++---- libavformat/segment.c | 119 +++++++++++++++++++++++++++++--------- libavformat/utils.c | 49 +++++++++++++++- libavutil/Makefile | 1 - libavutil/frame.c | 2 + libavutil/frame.h | 2 + libavutil/time.c | 17 ++++++ libavutil/time.h | 11 ++++ libavutil/time_internal.h | 23 ++++++++ 17 files changed, 315 insertions(+), 51 deletions(-) diff --git a/fftools/ffmpeg.c b/fftools/ffmpeg.c index add5a3e505..9f3ea8a691 100644 --- a/fftools/ffmpeg.c +++ b/fftools/ffmpeg.c @@ -2117,6 +2117,7 @@ static void do_streamcopy(InputStream *ist, OutputStream *ost, const AVPacket *p } else opkt.dts = av_rescale_q(pkt->dts, ist->st->time_base, ost->mux_timebase); opkt.dts -= ost_tb_start_time; + opkt.gts = pkt->gts; opkt.duration = av_rescale_q(pkt->duration, ist->st->time_base, ost->mux_timebase); diff --git a/libavcodec/avpacket.c b/libavcodec/avpacket.c index e4ba403cf6..08eb0dc5cb 100644 --- a/libavcodec/avpacket.c +++ b/libavcodec/avpacket.c @@ -37,6 +37,7 @@ void av_init_packet(AVPacket *pkt) pkt->pts = AV_NOPTS_VALUE; pkt->dts = AV_NOPTS_VALUE; pkt->pos = -1; + pkt->gts = -1; pkt->duration = 0; #if FF_API_CONVERGENCE_DURATION FF_DISABLE_DEPRECATION_WARNINGS @@ -575,6 +576,7 @@ int av_packet_copy_props(AVPacket *dst, const AVPacket *src) dst->pts = src->pts; dst->dts = src->dts; + dst->gts = src->gts; dst->pos = src->pos; dst->duration = src->duration; #if FF_API_CONVERGENCE_DURATION diff --git a/libavcodec/decode.c b/libavcodec/decode.c index 8086362eb2..499ec4bd85 100644 --- a/libavcodec/decode.c +++ b/libavcodec/decode.c @@ -1741,6 +1741,7 @@ FF_ENABLE_DEPRECATION_WARNINGS frame->pkt_pos = pkt->pos; frame->pkt_duration = pkt->duration; frame->pkt_size = pkt->size; + frame->gts = pkt->gts; for (i = 0; i < FF_ARRAY_ELEMS(sd); i++) { int size; diff --git a/libavcodec/encode.c b/libavcodec/encode.c index 282337e453..b5a6dde879 100644 --- a/libavcodec/encode.c +++ b/libavcodec/encode.c @@ -126,6 +126,7 @@ static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt) AVFrame *frame = es->in_frame; int got_packet; int ret; + double gts = -1; if (avci->draining_done) return AVERROR_EOF; @@ -137,6 +138,10 @@ static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt) return ret; } + // record frame global timestamp + if (frame) + gts = frame->gts; + if (!frame->buf[0]) { if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY || (avci->frame_thread_encoder && avctx->active_thread_type & FF_THREAD_FRAME))) @@ -164,6 +169,10 @@ static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt) avpkt->pts = avpkt->dts = frame->pts; } + // assign global timestamp if available + if (gts > 0 && avpkt->gts <= 0) + avpkt->gts = gts; + av_assert0(ret <= 0); emms_c(); diff --git a/libavcodec/packet.h b/libavcodec/packet.h index b9d4c9c2c8..8b4fee759b 100644 --- a/libavcodec/packet.h +++ b/libavcodec/packet.h @@ -391,6 +391,8 @@ typedef struct AVPacket { attribute_deprecated int64_t convergence_duration; #endif + + double gts; } AVPacket; typedef struct AVPacketList { diff --git a/libavformat/avformat.h b/libavformat/avformat.h index 41482328f6..38c90cba26 100644 --- a/libavformat/avformat.h +++ b/libavformat/avformat.h @@ -2803,8 +2803,11 @@ void av_dump_format(AVFormatContext *ic, * @param flags AV_FRAME_FILENAME_FLAGS_* * @return 0 if OK, -1 on format error */ +int av_get_frame_filename3(char *buf, int buf_size, + const char *path, int number, int flags, int64_t ts, double duration, double global_timestamp); + int av_get_frame_filename2(char *buf, int buf_size, - const char *path, int number, int flags); + const char *path, int number, int flags, int64_t ts); int av_get_frame_filename(char *buf, int buf_size, const char *path, int number); diff --git a/libavformat/hls.c b/libavformat/hls.c index af2468ad9b..e9f222d922 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -34,6 +34,7 @@ #include "libavutil/opt.h" #include "libavutil/dict.h" #include "libavutil/time.h" +#include "libavutil/time_internal.h" #include "avformat.h" #include "internal.h" #include "avio_internal.h" @@ -75,6 +76,8 @@ struct segment { uint8_t iv[16]; /* associated Media Initialization Section, treated as a segment */ struct segment *init_section; + double program_date_time; + int64_t initial_dts; }; struct rendition; @@ -723,6 +726,7 @@ static int parse_playlist(HLSContext *c, const char *url, struct segment **prev_segments = NULL; int prev_n_segments = 0; int64_t prev_start_seq_no = -1; + double program_date_time = -1; if (is_http && !in && c->http_persistent && c->playlist_pb) { in = c->playlist_pb; @@ -872,6 +876,28 @@ static int parse_playlist(HLSContext *c, const char *url, ptr = strchr(ptr, '@'); if (ptr) seg_offset = strtoll(ptr+1, NULL, 10); + } else if (av_strstart(line, "#EXT-X-PROGRAM-DATE-TIME:", &ptr)) { + struct tm pdt; + int y,M,d,h,m; + double s; + + // TODO: take timezone into consideration + if (sscanf(ptr, "%d-%d-%dT%d:%d:%lf", &y, &M, &d, &h, &m, &s) != 6) { + ret = AVERROR_INVALIDDATA; + goto fail; + } + + pdt.tm_year = y - 1900; + pdt.tm_mon = M - 1; + pdt.tm_mday = d; + pdt.tm_hour = h; + pdt.tm_min = m; + pdt.tm_sec = 0; + pdt.tm_isdst = -1; + + program_date_time = ff_timegm(&pdt); + program_date_time += s; + // TODO: avoid rounding errors by tracking ms separately } else if (av_strstart(line, "#", NULL)) { av_log(c->ctx, AV_LOG_INFO, "Skip ('%s')\n", line); continue; @@ -941,6 +967,13 @@ static int parse_playlist(HLSContext *c, const char *url, } seg->duration = duration; seg->key_type = key_type; + + seg->program_date_time = program_date_time; + if (program_date_time > 0) { + program_date_time += (double) duration / AV_TIME_BASE; + } + seg->initial_dts = -1; + dynarray_add(&pls->segments, &pls->n_segments, seg); is_segment = 0; @@ -992,8 +1025,20 @@ fail: return ret; } +static struct segment *closest_segment(struct playlist *pls) { + int n = pls->cur_seq_no - pls->start_seq_no; + if (n < 0) + return pls->segments[0]; + if (n >= pls->n_segments) + return pls->segments[pls->n_segments - 1]; + return pls->segments[pls->cur_seq_no - pls->start_seq_no]; +} + static struct segment *current_segment(struct playlist *pls) { + int n = pls->cur_seq_no - pls->start_seq_no; + if (n >= pls->n_segments) + return NULL; return pls->segments[pls->cur_seq_no - pls->start_seq_no]; } @@ -2127,7 +2172,7 @@ static void fill_timing_for_id3_timestamped_stream(struct playlist *pls) static AVRational get_timebase(struct playlist *pls) { if (pls->is_id3_timestamped) - return MPEG_TIME_BASE_Q; + return MPEG_TIME_BASE_Q; return pls->ctx->streams[pls->pkt.stream_index]->time_base; } @@ -2163,6 +2208,17 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt) return ret; break; } else { + struct segment *seg = closest_segment(pls); + if (seg->initial_dts < 0 && pls->pkt.dts != AV_NOPTS_VALUE) { + seg->initial_dts = pls->pkt.dts; + } + + if (pls->pkt.pts != AV_NOPTS_VALUE && seg->program_date_time > 0 && seg->initial_dts > 0) { + pls->pkt.gts = seg->program_date_time + (pls->pkt.pts - seg->initial_dts) * av_q2d(get_timebase(pls)); + } else { + pls->pkt.gts = -1; + } + /* stream_index check prevents matching picture attachments etc. */ if (pls->is_id3_timestamped && pls->pkt.stream_index == 0) { /* audio elementary streams are id3 timestamped */ diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 7d97ce1789..2e583293dc 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -1265,9 +1265,11 @@ static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs } } else if (av_strstart(line, "#EXT-X-PROGRAM-DATE-TIME:", &ptr)) { struct tm program_date_time; - int y,M,d,h,m,s; - double ms; - if (sscanf(ptr, "%d-%d-%dT%d:%d:%d.%lf", &y, &M, &d, &h, &m, &s, &ms) != 7) { + int y,M,d,h,m; + double s; + + // TODO: take timezone into consideration + if (sscanf(ptr, "%d-%d-%dT%d:%d:%lf", &y, &M, &d, &h, &m, &s) != 6) { ret = AVERROR_INVALIDDATA; goto fail; } @@ -1277,11 +1279,12 @@ static int parse_playlist(AVFormatContext *s, const char *url, VariantStream *vs program_date_time.tm_mday = d; program_date_time.tm_hour = h; program_date_time.tm_min = m; - program_date_time.tm_sec = s; + program_date_time.tm_sec = 0; program_date_time.tm_isdst = -1; - discont_program_date_time = mktime(&program_date_time); - discont_program_date_time += (double)(ms / 1000); + discont_program_date_time = ff_timegm(&program_date_time); + discont_program_date_time += s; + // TODO: avoid rounding errors by tracking ms separately } else if (av_strstart(line, "#", NULL)) { continue; } else if (line[0]) { diff --git a/libavformat/img2enc.c b/libavformat/img2enc.c index 0f7a21ffa0..a1f0616229 100644 --- a/libavformat/img2enc.c +++ b/libavformat/img2enc.c @@ -27,7 +27,9 @@ #include "libavutil/log.h" #include "libavutil/opt.h" #include "libavutil/pixdesc.h" +#include "libavutil/time.h" #include "libavutil/time_internal.h" +#include #include "avformat.h" #include "avio_internal.h" #include "internal.h" @@ -42,6 +44,7 @@ typedef struct VideoMuxData { char target[4][1024]; int update; int use_strftime; + int use_global_timestamp; int frame_pts; const char *muxer; int use_rename; @@ -130,31 +133,54 @@ static int write_packet(AVFormatContext *s, AVPacket *pkt) VideoMuxData *img = s->priv_data; AVIOContext *pb[4] = {0}; char filename[1024]; - AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar; + AVStream *stream = s->streams[pkt->stream_index]; + AVCodecParameters *par = stream->codecpar; const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(par->format); int ret, i; int nb_renames = 0; + int64_t ts = av_rescale_q(pkt->pts, stream->time_base, AV_TIME_BASE_Q); AVDictionary *options = NULL; if (img->update) { av_strlcpy(filename, img->path, sizeof(filename)); - } else if (img->use_strftime) { - time_t now0; - struct tm *tm, tmpbuf; - time(&now0); - tm = localtime_r(&now0, &tmpbuf); - if (!strftime(filename, sizeof(filename), img->path, tm)) { - av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n"); - return AVERROR(EINVAL); + } else if (img->use_strftime || img->frame_pts || img->use_global_timestamp) { + char temp_name[sizeof(filename)]; + if (img->use_strftime) { + struct timeval tv; + gettimeofday(&tv, NULL); + if (!av_strftime_micro(temp_name, sizeof(temp_name), img->path, &tv)) { + av_log(s, AV_LOG_ERROR, "Could not get frame filename with strftime\n"); + return AVERROR(EINVAL); + } + } else { + av_strlcpy(temp_name, img->path, sizeof(temp_name)); + } + + if (img->frame_pts || img->use_global_timestamp) { + double global_timestamp = pkt->gts; + if (av_get_frame_filename3(filename, sizeof(filename), temp_name, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts, 0, global_timestamp) < 0) { + av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames."); + return AVERROR(EINVAL); + } + } else { + av_strlcpy(filename, temp_name, sizeof(filename)); } + } else if (av_get_frame_filename2(filename, sizeof(filename), img->path, + img->img_number, + AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0 && + img->img_number > 1) { + av_log(s, AV_LOG_ERROR, + "Could not get frame filename number %d from pattern '%s' (either set update or use a pattern like %%03d within the filename pattern)\n", + img->img_number, img->path); + return AVERROR(EINVAL); } else if (img->frame_pts) { - if (av_get_frame_filename2(filename, sizeof(filename), img->path, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0) { + if (av_get_frame_filename2(filename, sizeof(filename), img->path, pkt->pts, AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0) { av_log(s, AV_LOG_ERROR, "Cannot write filename by pts of the frames."); return AVERROR(EINVAL); } } else if (av_get_frame_filename2(filename, sizeof(filename), img->path, img->img_number, - AV_FRAME_FILENAME_FLAGS_MULTIPLE) < 0 && + AV_FRAME_FILENAME_FLAGS_MULTIPLE, ts) < 0 && img->img_number > 1) { av_log(s, AV_LOG_ERROR, "Could not get frame filename number %d from pattern '%s'. " @@ -244,6 +270,7 @@ static const AVOption muxoptions[] = { { "start_number", "set first number in the sequence", OFFSET(img_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, INT_MAX, ENC }, { "strftime", "use strftime for filename", OFFSET(use_strftime), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC }, { "frame_pts", "use current frame pts for filename", OFFSET(frame_pts), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC }, + { "global_timestamp", "use global timestamp for filename", OFFSET(use_global_timestamp), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC }, { "atomic_writing", "write files atomically (using temporary files and renames)", OFFSET(use_rename), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, ENC }, { "protocol_opts", "specify protocol options for the opened files", OFFSET(protocol_opts), AV_OPT_TYPE_DICT, {0}, 0, 0, ENC }, { NULL }, diff --git a/libavformat/segment.c b/libavformat/segment.c index dff3d0ed48..685e6ab4d3 100644 --- a/libavformat/segment.c +++ b/libavformat/segment.c @@ -41,7 +41,9 @@ #include "libavutil/time.h" #include "libavutil/timecode.h" #include "libavutil/time_internal.h" +#include #include "libavutil/timestamp.h" +#include "libavutil/avstring.h" typedef struct SegmentListEntry { int index; @@ -118,6 +120,10 @@ typedef struct SegmentContext { int write_empty; int use_rename; + int use_pts; + int use_global_timestamp; + int64_t segment_ts; + double global_timestamp; char temp_list_filename[1024]; SegmentListEntry cur_entry; @@ -204,19 +210,23 @@ static int set_segment_filename(AVFormatContext *s) if (seg->segment_idx_wrap) seg->segment_idx %= seg->segment_idx_wrap; if (seg->use_strftime) { - time_t now0; - struct tm *tm, tmpbuf; - time(&now0); - tm = localtime_r(&now0, &tmpbuf); - if (!strftime(buf, sizeof(buf), s->url, tm)) { - av_log(oc, AV_LOG_ERROR, "Could not get segment filename with strftime\n"); + struct timeval tv; + gettimeofday(&tv, NULL); + if (!av_strftime_micro(buf, sizeof(buf), s->url, &tv)) { + av_log(s, AV_LOG_ERROR, "Could not get segment filename with strftime\n"); return AVERROR(EINVAL); } + } else if (seg->use_pts || seg->use_global_timestamp) { + av_strlcpy(buf, s->url, sizeof(buf)); } else if (av_get_frame_filename(buf, sizeof(buf), s->url, seg->segment_idx) < 0) { av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->url); return AVERROR(EINVAL); } + + if (seg->use_pts || seg->use_global_timestamp) + av_strlcat(buf, ".tmp", sizeof(buf)); + new_name = av_strdup(buf); if (!new_name) return AVERROR(ENOMEM); @@ -251,6 +261,8 @@ static int segment_start(AVFormatContext *s, int write_header) } seg->segment_idx++; + seg->segment_ts = -1; + seg->global_timestamp = -1; if ((seg->segment_idx_wrap) && (seg->segment_idx % seg->segment_idx_wrap == 0)) seg->segment_idx_wrap_nb++; @@ -374,6 +386,34 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n", oc->url); + if (seg->use_pts || seg->use_global_timestamp) { + char final_name[1024]; + if (av_get_frame_filename3(final_name, sizeof(final_name), seg->cur_entry.filename, + 0, AV_FRAME_FILENAME_FLAGS_MULTIPLE, seg->segment_ts, + seg->cur_entry.end_time - seg->cur_entry.start_time, seg->global_timestamp) < 0) { + av_log(s, AV_LOG_ERROR, "Cannot rename filename by pts of the frames."); + return AVERROR(EINVAL); + } + // remove .tmp + final_name[strlen(final_name) - 4] = '\0'; + + char *url, *src, *dst; + if (!(url = av_strdup(oc->url))) + return AVERROR(ENOMEM); + const char *dir = av_dirname(url); + if (!(src = av_append_path_component(dir, seg->cur_entry.filename)) || + !(dst = av_append_path_component(dir, final_name))) + return AVERROR(ENOMEM); + if (ff_rename(src, dst, s)) + return AVERROR(EINVAL); + + av_free(url); av_free(src); av_free(dst); + size_t len = strlen(final_name) + 1; + if ((ret = av_reallocp(&seg->cur_entry.filename, len)) < 0) + return ret; + av_strlcpy(seg->cur_entry.filename, final_name, len); + } + if (seg->list) { if (seg->list_size || seg->list_type == LIST_TYPE_M3U8) { SegmentListEntry *entry = av_mallocz(sizeof(*entry)); @@ -685,6 +725,8 @@ static int seg_init(AVFormatContext *s) int i; seg->segment_count = 0; + seg->segment_ts = -1; + seg->global_timestamp = -1; if (!seg->write_header_trailer) seg->individual_header_trailer = 0; @@ -872,6 +914,18 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) } calc_times: + if (pkt->stream_index == seg->reference_stream_index) { + if (seg->use_pts) { + if (pkt->pts != AV_NOPTS_VALUE && (seg->segment_ts < 0 || + av_compare_ts(pkt->pts, st->time_base, seg->segment_ts, AV_TIME_BASE_Q) < 0)) { + seg->segment_ts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); + } + } + if (seg->use_global_timestamp && seg->global_timestamp < 0 && pkt->gts > 0) { + seg->global_timestamp = pkt->gts; + } + } + if (seg->times) { end_pts = seg->segment_count < seg->nb_times ? seg->times[seg->segment_count] : INT64_MAX; @@ -902,28 +956,35 @@ calc_times: if (pkt->stream_index == seg->reference_stream_index && (pkt->flags & AV_PKT_FLAG_KEY || seg->break_non_keyframes) && (seg->segment_frame_count > 0 || seg->write_empty) && - (seg->cut_pending || seg->frame_count >= start_frame || - (pkt->pts != AV_NOPTS_VALUE && - av_compare_ts(pkt->pts, st->time_base, - end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0))) { - /* sanitize end time in case last packet didn't have a defined duration */ - if (seg->cur_entry.last_duration == 0) - seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base); - - if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0) - goto fail; - - if ((ret = segment_start(s, seg->individual_header_trailer)) < 0) - goto fail; - - seg->cut_pending = 0; - seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap * seg->segment_idx_wrap_nb; - seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base); - seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); - seg->cur_entry.end_time = seg->cur_entry.start_time; - - if (seg->times || (!seg->frames && !seg->use_clocktime) && seg->write_empty) - goto calc_times; + (pkt->pts != AV_NOPTS_VALUE || pkt->dts != AV_NOPTS_VALUE)) { + + if (pkt->pts == AV_NOPTS_VALUE) { + av_log(s, AV_LOG_WARNING, "dangerously set keyframe NOPTS pts to dts %" PRId64 "\n", pkt->dts); + pkt->pts = pkt->dts; + } + + if (seg->cut_pending || seg->frame_count >= start_frame || + av_compare_ts(pkt->pts, st->time_base, + end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0) { + /* sanitize end time in case last packet didn't have a defined duration */ + if (!seg->cur_entry.last_duration) + seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base); + + if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0) + goto fail; + + if ((ret = segment_start(s, seg->individual_header_trailer)) < 0) + goto fail; + + seg->cut_pending = 0; + seg->cur_entry.index = seg->segment_idx + seg->segment_idx_wrap * seg->segment_idx_wrap_nb; + seg->cur_entry.start_time = (double)pkt->pts * av_q2d(st->time_base); + seg->cur_entry.start_pts = av_rescale_q(pkt->pts, st->time_base, AV_TIME_BASE_Q); + seg->cur_entry.end_time = seg->cur_entry.start_time; + + if (seg->times || (!seg->frames && !seg->use_clocktime) && seg->write_empty) + goto calc_times; + } } if (pkt->stream_index == seg->reference_stream_index) { @@ -1045,6 +1106,8 @@ static const AVOption options[] = { { "strftime", "set filename expansion with strftime at segment creation", OFFSET(use_strftime), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, { "increment_tc", "increment timecode between each segment", OFFSET(increment_tc), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, { "break_non_keyframes", "allow breaking segments on non-keyframes", OFFSET(break_non_keyframes), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E }, + { "frame_pts", "use current frame pts for filename", OFFSET(use_pts), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E }, + { "global_timestamp", "use global timestamp for filename", OFFSET(use_global_timestamp), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E}, { "individual_header_trailer", "write header/trailer to each segment", OFFSET(individual_header_trailer), AV_OPT_TYPE_BOOL, {.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_BOOL, {.i64 = 1}, 0, 1, E }, diff --git a/libavformat/utils.c b/libavformat/utils.c index 652758e98e..3de777baa4 100644 --- a/libavformat/utils.c +++ b/libavformat/utils.c @@ -20,6 +20,7 @@ */ #include +#include #include "config.h" @@ -4695,10 +4696,17 @@ uint64_t ff_get_formatted_ntp_time(uint64_t ntp_time_us) return ntp_ts; } -int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number, int flags) +int av_get_frame_filename2(char *buf, int buf_size, const char *path, + int number, int flags, int64_t ts) +{ + return av_get_frame_filename3(buf, buf_size, path, number, flags, ts, 0, -1); +} + +int av_get_frame_filename3(char *buf, int buf_size, const char *path, + int number, int flags, int64_t ts, double duration, double global_timestamp) { const char *p; - char *q, buf1[20], c; + char *q, buf1[32], c; int nd, len, percentd_found; q = buf; @@ -4735,6 +4743,41 @@ int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number memcpy(q, buf1, len); q += len; break; + case 't': + if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found) + goto fail; + percentd_found = 1; + int64_t seconds = ts / AV_TIME_BASE; + int64_t microsecs = ts % AV_TIME_BASE; + snprintf(buf1, sizeof(buf1), "%010" PRId64 ".%06" PRId64, seconds, microsecs); + len = strlen(buf1); + if ((q - buf + len) > buf_size - 1) + goto fail; + memcpy(q, buf1, len); + q += len; + break; + case 'l': + if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found) + goto fail; + percentd_found = 1; + snprintf(buf1, sizeof(buf1), "%017.6f", duration); + len = strlen(buf1); + if ((q - buf + len) > buf_size - 1) + goto fail; + memcpy(q, buf1, len); + q += len; + break; + case 'g': + if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found) + goto fail; + percentd_found = 1; + snprintf(buf1, sizeof(buf1), "%017.6f", global_timestamp); + len = strlen(buf1); + if ((q - buf + len) > buf_size - 1) + goto fail; + memcpy(q, buf1, len); + q += len; + break; default: goto fail; } @@ -4755,7 +4798,7 @@ fail: int av_get_frame_filename(char *buf, int buf_size, const char *path, int number) { - return av_get_frame_filename2(buf, buf_size, path, number, 0); + return av_get_frame_filename2(buf, buf_size, path, number, 0, 0); } void av_url_split(char *proto, int proto_size, diff --git a/libavutil/Makefile b/libavutil/Makefile index 27bafe9e12..3936978b26 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -173,7 +173,6 @@ OBJS = adler32.o \ video_enc_params.o \ film_grain_params.o \ - OBJS-$(CONFIG_CUDA) += hwcontext_cuda.o OBJS-$(CONFIG_D3D11VA) += hwcontext_d3d11va.o OBJS-$(CONFIG_DXVA2) += hwcontext_dxva2.o diff --git a/libavutil/frame.c b/libavutil/frame.c index eab51b6a32..2021239a75 100644 --- a/libavutil/frame.c +++ b/libavutil/frame.c @@ -164,6 +164,7 @@ FF_ENABLE_DEPRECATION_WARNINGS frame->color_range = AVCOL_RANGE_UNSPECIFIED; frame->chroma_location = AVCHROMA_LOC_UNSPECIFIED; frame->flags = 0; + frame->gts = -1; } static void free_side_data(AVFrameSideData **ptr_sd) @@ -386,6 +387,7 @@ FF_ENABLE_DEPRECATION_WARNINGS dst->colorspace = src->colorspace; dst->color_range = src->color_range; dst->chroma_location = src->chroma_location; + dst->gts = src->gts; av_dict_copy(&dst->metadata, src->metadata, 0); diff --git a/libavutil/frame.h b/libavutil/frame.h index 1aeafef6de..fa69c6a424 100644 --- a/libavutil/frame.h +++ b/libavutil/frame.h @@ -691,6 +691,8 @@ typedef struct AVFrame { * for the target frame's private_ref field. */ AVBufferRef *private_ref; + + double gts; } AVFrame; #if FF_API_FRAME_GET_SET diff --git a/libavutil/time.c b/libavutil/time.c index 740afc4785..54152173a6 100644 --- a/libavutil/time.c +++ b/libavutil/time.c @@ -96,3 +96,20 @@ int av_usleep(unsigned usec) return AVERROR(ENOSYS); #endif } + +size_t av_strftime_micro(char *buf, size_t size, const char *format, const struct timeval *tv) +{ + struct tm *tm; + char *temp_name = av_malloc(size); + if (!temp_name) + return 0; + + if (!(tm = localtime(&(tv->tv_sec)))) + return 0; + + strftime(temp_name, size, format, tm); + size_t retval = snprintf(buf, size, temp_name, tv->tv_usec); + + av_free(temp_name); + return retval; +} diff --git a/libavutil/time.h b/libavutil/time.h index dc169b064a..baa9aacc73 100644 --- a/libavutil/time.h +++ b/libavutil/time.h @@ -21,6 +21,7 @@ #ifndef AVUTIL_TIME_H #define AVUTIL_TIME_H +#include "mem.h" #include /** @@ -53,4 +54,14 @@ int av_gettime_relative_is_monotonic(void); */ int av_usleep(unsigned usec); +/** + * Linux strftime function with micro second accuracy + * @param buf char buffer to write output + * @param size max number of bytes + * @param format format string, identical to the one for strftime + * @param tv timeval struct containing current time + * @return number of bytes written + */ +size_t av_strftime_micro(char *buf, size_t size, const char *format, const struct timeval *tv); + #endif /* AVUTIL_TIME_H */ diff --git a/libavutil/time_internal.h b/libavutil/time_internal.h index d0f007ab1c..a6fdc10ad1 100644 --- a/libavutil/time_internal.h +++ b/libavutil/time_internal.h @@ -20,6 +20,9 @@ #define AVUTIL_TIME_INTERNAL_H #include +#include +#include +#include #include "config.h" #if !HAVE_GMTIME_R && !defined(gmtime_r) @@ -46,4 +49,24 @@ static inline struct tm *ff_localtime_r(const time_t* clock, struct tm *result) #define localtime_r ff_localtime_r #endif +static inline time_t ff_timegm(struct tm *tm) +{ + time_t ret; + char *tz; + + tz = getenv("TZ"); + if (tz) + tz = strdup(tz); + setenv("TZ", "", 1); + tzset(); + ret = mktime(tm); + if (tz) { + setenv("TZ", tz, 1); + free(tz); + } else + unsetenv("TZ"); + tzset(); + return ret; +} + #endif /* AVUTIL_TIME_INTERNAL_H */