Fix T53857: Incorrect framerate for videos imported from OBS
authorSergey Sharybin <sergey.vfx@gmail.com>
Mon, 26 Feb 2018 15:39:18 +0000 (16:39 +0100)
committerSergey Sharybin <sergey.vfx@gmail.com>
Fri, 9 Mar 2018 09:32:17 +0000 (10:32 +0100)
This is an issue with which value to trust: fps vs. tbr. They both cam be
somewhat broken. Currently the idea is:

- If file was saved with FFmpeg AND we are decoding with FFmpeg we trust tbr.
- If we are decoding with Libav we use fps (there does not seem to be tbr in
  Libav, unless i'm missing something).
- All other cases we use fps.

Seems to work all good for files from T53857, T54148 and T51153. Ideally we
would need to collect some amount of regression files to make further tweaks
more scientific.

Reviewers: mont29

Reviewed By: mont29

Differential Revision: https://developer.blender.org/D3083

intern/ffmpeg/ffmpeg_compat.h
source/blender/imbuf/intern/anim_movie.c
source/blender/imbuf/intern/indexer.c
source/gameengine/VideoTexture/VideoFFmpeg.cpp

index 9c06c8a6d67ccd44119880f91240b1a781a18ef4..1eb6c3ba2dca1aff0660eae270c7575334850686 100644 (file)
 
 #include <libswscale/swscale.h>
 
+/* Stupid way to distinguish FFmpeg from Libav:
+ * - FFmpeg's MICRO version starts from 100 and goes up, while
+ * - Libav's micro is always below 100.
+ */
+#if LIBAVCODEC_VERSION_MICRO >= 100
+#  define AV_USING_FFMPEG
+#else
+#  define AV_USING_LIBAV
+#endif
+
 #if (LIBAVFORMAT_VERSION_MAJOR > 52) || ((LIBAVFORMAT_VERSION_MAJOR >= 52) && (LIBAVFORMAT_VERSION_MINOR >= 105))
 #  define FFMPEG_HAVE_AVIO 1
 #endif
@@ -428,8 +438,45 @@ void av_frame_free(AVFrame **frame)
 #endif
 
 FFMPEG_INLINE
-AVRational av_get_r_frame_rate_compat(const AVStream *stream)
+const char* av_get_metadata_key_value(AVDictionary *metadata, const char *key)
 {
+       if (metadata == NULL) {
+               return NULL;
+       }
+       AVDictionaryEntry *tag = NULL;
+       while ((tag = av_dict_get(metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) {
+               if (!strcmp(tag->key, key)) {
+                       return tag->value;
+               }
+       }
+       return NULL;
+}
+
+FFMPEG_INLINE
+bool av_check_encoded_with_ffmpeg(AVFormatContext *ctx)
+{
+       const char* encoder = av_get_metadata_key_value(ctx->metadata, "ENCODER");
+       if (encoder != NULL && !strncmp(encoder, "Lavf", 4)) {
+               return true;
+       }
+       return false;
+}
+
+FFMPEG_INLINE
+AVRational av_get_r_frame_rate_compat(AVFormatContext *ctx,
+                                      const AVStream *stream)
+{
+       /* If the video is encoded with FFmpeg and we are decoding with FFmpeg
+        * as well it seems to be more reliable to use r_frame_rate (tbr).
+        *
+        * For other cases we fall back to avg_frame_rate (fps) when possible.
+        */
+#ifdef AV_USING_FFMPEG
+       if (av_check_encoded_with_ffmpeg(ctx)) {
+               return stream->r_frame_rate;
+       }
+#endif
+
 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 23, 1)
        /* For until r_frame_rate was deprecated use it. */
        return stream->r_frame_rate;
index 5472cae3ef22832adc8fd8372c4e1421dd1c3705..a770b34ecc6ae4d0cb37acd10d54919cd8db3663 100644 (file)
@@ -511,7 +511,7 @@ static int startffmpeg(struct anim *anim)
                return -1;
        }
 
-       frame_rate = av_get_r_frame_rate_compat(pFormatCtx->streams[videoStream]);
+       frame_rate = av_get_r_frame_rate_compat(pFormatCtx, pFormatCtx->streams[videoStream]);
        if (pFormatCtx->streams[videoStream]->nb_frames != 0) {
                anim->duration = pFormatCtx->streams[videoStream]->nb_frames;
        }
@@ -989,7 +989,7 @@ static ImBuf *ffmpeg_fetchibuf(struct anim *anim, int position,
 
        v_st = anim->pFormatCtx->streams[anim->videoStream];
 
-       frame_rate = av_q2d(av_get_r_frame_rate_compat(v_st));
+       frame_rate = av_q2d(av_get_r_frame_rate_compat(anim->pFormatCtx, v_st));
 
        st_time = anim->pFormatCtx->start_time;
        pts_time_base = av_q2d(v_st->time_base);
index 009258079ee7aa3a9f0ff57b580a43eb21e00a0f..eaf4dfd84b4ca58cc6ff8df9e5ece26e0f0b9942 100644 (file)
@@ -909,7 +909,7 @@ static int index_rebuild_ffmpeg(FFmpegIndexBuilderContext *context,
 
        stream_size = avio_size(context->iFormatCtx->pb);
 
-       context->frame_rate = av_q2d(av_get_r_frame_rate_compat(context->iStream));
+       context->frame_rate = av_q2d(av_get_r_frame_rate_compat(context->iFormatCtx, context->iStream));
        context->pts_time_base = av_q2d(context->iStream->time_base);
 
        while (av_read_frame(context->iFormatCtx, &next_packet) >= 0) {
index 10196804656c4cae3e7434b7113aa009df2ba391..11ec97ca5f8e59831132869f0be6f1403433520c 100644 (file)
@@ -228,7 +228,7 @@ int VideoFFmpeg::openStream(const char *filename, AVInputFormat *inputFormat, AV
                codecCtx->frame_rate_base=1000;
        m_baseFrameRate = (double)codecCtx->frame_rate / (double)codecCtx->frame_rate_base;
 #else
-       m_baseFrameRate = av_q2d(av_get_r_frame_rate_compat(formatCtx->streams[videoStream]));
+       m_baseFrameRate = av_q2d(av_get_r_frame_rate_compat(formatCtx, formatCtx->streams[videoStream]));
 #endif
        if (m_baseFrameRate <= 0.0) 
                m_baseFrameRate = defFrameRate;