diff mbox series

[FFmpeg-devel,3/3] avformat/movenc: Add an automatic timescale computation

Message ID 1587405459-29001-4-git-send-email-kevin.j.wheatley@gmail.com
State New
Headers show
Series avformat/movenc: Support for variable timescale in mov containers | expand

Checks

Context Check Description
andriy/default pending
andriy/make success Make finished
andriy/make_fate success Make fate finished

Commit Message

Kevin Wheatley April 20, 2020, 5:57 p.m. UTC
Activated when the -mov_timescale command line/MOVMuxContext
parameter is set to 0 or less. If the user passes any value
greater than 0, then it will be used as-is. The default
value is kept unchanged at 1000.

When active, it uses the base assumption from the QuickTime
specification of 600 and combines the video stream time
bases to compute an accurate answer if possible.

In cases when the first stream result falls outside
MOV_MAX_AUTO_TIMESCALE or if a non-integer video stream is
encounted, then the first stream's time_base will be used as the
base.

Signed-off-by: Kevin Wheatley <kevin.j.wheatley@gmail.com>
---
 libavformat/movenc.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 libavformat/movenc.h |  1 +
 2 files changed, 72 insertions(+)

Comments

Marton Balint May 21, 2020, 9:43 p.m. UTC | #1
On Mon, 20 Apr 2020, Kevin Wheatley wrote:

> Activated when the -mov_timescale command line/MOVMuxContext
> parameter is set to 0 or less. If the user passes any value
> greater than 0, then it will be used as-is. The default
> value is kept unchanged at 1000.
>
> When active, it uses the base assumption from the QuickTime
> specification of 600 and combines the video stream time
> bases to compute an accurate answer if possible.

Maybe I am wrong, but I would think that you should determine the MOV 
timescale based on the track timescales, and not inspect time bases 
directly.

I think an attempt should be made to calculate the least common multiple 
of track timescales, but if it becomes too big, then do some other 
heuristic, like select biggest track timescale?

Regards,
Marton

>
> In cases when the first stream result falls outside
> MOV_MAX_AUTO_TIMESCALE or if a non-integer video stream is
> encounted, then the first stream's time_base will be used as the
> base.
>
> Signed-off-by: Kevin Wheatley <kevin.j.wheatley@gmail.com>
> ---
> libavformat/movenc.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> libavformat/movenc.h |  1 +
> 2 files changed, 72 insertions(+)
>
> diff --git a/libavformat/movenc.c b/libavformat/movenc.c
> index 508fa73..8edb848 100644
> --- a/libavformat/movenc.c
> +++ b/libavformat/movenc.c
> @@ -6208,6 +6208,70 @@ static int mov_create_dvd_sub_decoder_specific_info(MOVTrack *track,
>     return 0;
> }
> 
> +static int mov_determine_movie_timescale(AVFormatContext *s, MOVMuxContext *mov)
> +{
> +    // Assume typical integer frame rates:
> +    // 600 is a common multiple of 24, 25, 30, 50, 60, etc.
> +    // see p221, https://developer.apple.com/standards/qtff-2001.pdf
> +    int timescale = 600;
> +    AVRational temp;
> +    int exact;
> +    int first_video_track = 1;
> +
> +    // If the user specified a timescale, just use it as-is
> +    if (mov->mov_timescale > 0) {
> +        return mov->mov_timescale;
> +    }
> +
> +    // Determine the timescale, based on the video stream time_base values
> +    for (int i = 0; i < s->nb_streams; i++) {
> +        AVStream *st = s->streams[i];
> +        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
> +
> +            // If the first video track is not an integer, use its denominator
> +            // as the basis of the remaining calculations
> +            if (first_video_track && st->time_base.num != 1) {
> +                timescale = st->time_base.den;
> +                av_log(s, AV_LOG_VERBOSE, "Using first video stream non-integer time_base: %d/%d\n",
> +                       st->time_base.den, st->time_base.num);
> +            }
> +
> +            // determine if we can make an even multiple of the current timescale and the video track
> +            av_log(s, AV_LOG_VERBOSE, "Current estimated timescale: %d\n", timescale);
> +            av_log(s, AV_LOG_TRACE, "Stream #%d time_base: %d/%d\n", i,
> +                   st->time_base.num, st->time_base.den);
> +
> +            // Try keep the time_scale within a sensible bound
> +            exact = av_reduce(&temp.num, &temp.den,
> +                              (int64_t)timescale * st->time_base.num,
> +                              st->time_base.den,
> +                              MOV_MAX_AUTO_TIMESCALE);
> +
> +            // Use a scaled version of the timescale
> +            if (exact) {
> +                timescale *= temp.den;
> +                av_log(s, AV_LOG_TRACE, "Adjusted timescale: %d/%d %d\n",
> +                       temp.den, temp.num, timescale);
> +            } else {
> +                // We overflowed try the denominator as is
> +                timescale = temp.den;
> +                av_log(s, AV_LOG_WARNING, "Timescale calculation out of bounds approximating %d/%d %d\n",
> +                       temp.den, temp.num, timescale);
> +            }
> +
> +            if (first_video_track && ((timescale > MOV_MAX_AUTO_TIMESCALE) || !exact)) {
> +                timescale = st->time_base.den;
> +                av_log(s, AV_LOG_WARNING, "Potentially large timescale, "
> +                                          "using first video stream time_base: %d/%d\n",
> +                       st->time_base.den, st->time_base.num);
> +            }
> +            first_video_track = 0;
> +        }
> +    }
> +    av_log(s, AV_LOG_VERBOSE, "Final timescale: %d\n", timescale);
> +    return timescale;
> +}
> +
> static int mov_init(AVFormatContext *s)
> {
>     MOVMuxContext *mov = s->priv_data;
> @@ -6266,6 +6330,13 @@ static int mov_init(AVFormatContext *s)
>         mov->reserved_moov_size = -1;
>     }
> 
> +    mov->mov_timescale = mov_determine_movie_timescale(s, mov);
> +    if (mov->mov_timescale > MOV_MAX_AUTO_TIMESCALE) {
> +        av_log(s, AV_LOG_WARNING, "Potentially large timescale %d, use "
> +                                  "-mov_timescale if you have issues\n",
> +                                  mov->mov_timescale);
> +    }
> +
>     if (mov->use_editlist < 0) {
>         mov->use_editlist = 1;
>         if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
> diff --git a/libavformat/movenc.h b/libavformat/movenc.h
> index 322968c..4d6b7b7 100644
> --- a/libavformat/movenc.h
> +++ b/libavformat/movenc.h
> @@ -30,6 +30,7 @@
> #define MOV_FRAG_INFO_ALLOC_INCREMENT 64
> #define MOV_INDEX_CLUSTER_SIZE 1024
> #define MOV_TIMESCALE 1000
> +#define MOV_MAX_AUTO_TIMESCALE 10000
> 
> #define RTP_MAX_PACKET_SIZE 1450
> 
> -- 
> 1.8.5.6
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
diff mbox series

Patch

diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index 508fa73..8edb848 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -6208,6 +6208,70 @@  static int mov_create_dvd_sub_decoder_specific_info(MOVTrack *track,
     return 0;
 }
 
+static int mov_determine_movie_timescale(AVFormatContext *s, MOVMuxContext *mov)
+{
+    // Assume typical integer frame rates:
+    // 600 is a common multiple of 24, 25, 30, 50, 60, etc.
+    // see p221, https://developer.apple.com/standards/qtff-2001.pdf
+    int timescale = 600;
+    AVRational temp;
+    int exact;
+    int first_video_track = 1;
+
+    // If the user specified a timescale, just use it as-is
+    if (mov->mov_timescale > 0) {
+        return mov->mov_timescale;
+    }
+
+    // Determine the timescale, based on the video stream time_base values
+    for (int i = 0; i < s->nb_streams; i++) {
+        AVStream *st = s->streams[i];
+        if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
+
+            // If the first video track is not an integer, use its denominator
+            // as the basis of the remaining calculations
+            if (first_video_track && st->time_base.num != 1) {
+                timescale = st->time_base.den;
+                av_log(s, AV_LOG_VERBOSE, "Using first video stream non-integer time_base: %d/%d\n",
+                       st->time_base.den, st->time_base.num);
+            }
+
+            // determine if we can make an even multiple of the current timescale and the video track
+            av_log(s, AV_LOG_VERBOSE, "Current estimated timescale: %d\n", timescale);
+            av_log(s, AV_LOG_TRACE, "Stream #%d time_base: %d/%d\n", i,
+                   st->time_base.num, st->time_base.den);
+
+            // Try keep the time_scale within a sensible bound
+            exact = av_reduce(&temp.num, &temp.den,
+                              (int64_t)timescale * st->time_base.num,
+                              st->time_base.den,
+                              MOV_MAX_AUTO_TIMESCALE);
+
+            // Use a scaled version of the timescale
+            if (exact) {
+                timescale *= temp.den;
+                av_log(s, AV_LOG_TRACE, "Adjusted timescale: %d/%d %d\n",
+                       temp.den, temp.num, timescale);
+            } else {
+                // We overflowed try the denominator as is
+                timescale = temp.den;
+                av_log(s, AV_LOG_WARNING, "Timescale calculation out of bounds approximating %d/%d %d\n",
+                       temp.den, temp.num, timescale);
+            }
+
+            if (first_video_track && ((timescale > MOV_MAX_AUTO_TIMESCALE) || !exact)) {
+                timescale = st->time_base.den;
+                av_log(s, AV_LOG_WARNING, "Potentially large timescale, "
+                                          "using first video stream time_base: %d/%d\n",
+                       st->time_base.den, st->time_base.num);
+            }
+            first_video_track = 0;
+        }
+    }
+    av_log(s, AV_LOG_VERBOSE, "Final timescale: %d\n", timescale);
+    return timescale;
+}
+
 static int mov_init(AVFormatContext *s)
 {
     MOVMuxContext *mov = s->priv_data;
@@ -6266,6 +6330,13 @@  static int mov_init(AVFormatContext *s)
         mov->reserved_moov_size = -1;
     }
 
+    mov->mov_timescale = mov_determine_movie_timescale(s, mov);
+    if (mov->mov_timescale > MOV_MAX_AUTO_TIMESCALE) {
+        av_log(s, AV_LOG_WARNING, "Potentially large timescale %d, use "
+                                  "-mov_timescale if you have issues\n",
+                                  mov->mov_timescale);
+    }
+
     if (mov->use_editlist < 0) {
         mov->use_editlist = 1;
         if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
diff --git a/libavformat/movenc.h b/libavformat/movenc.h
index 322968c..4d6b7b7 100644
--- a/libavformat/movenc.h
+++ b/libavformat/movenc.h
@@ -30,6 +30,7 @@ 
 #define MOV_FRAG_INFO_ALLOC_INCREMENT 64
 #define MOV_INDEX_CLUSTER_SIZE 1024
 #define MOV_TIMESCALE 1000
+#define MOV_MAX_AUTO_TIMESCALE 10000
 
 #define RTP_MAX_PACKET_SIZE 1450