Fix T53857: Incorrect framerate for videos imported from OBS

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
This commit is contained in:
Sergey Sharybin 2018-02-26 16:39:18 +01:00
parent ecab7bef69
commit 370a2d6917
Notes: blender-bot 2023-02-14 11:01:33 +01:00
Referenced by issue #53857, Incorrect Framerate for videos imported from OBS (2.79+)
Referenced by issue #53687, VSE Detecting and setting incorrect frame rate
4 changed files with 52 additions and 5 deletions

View File

@ -48,6 +48,16 @@
#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;

View 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);

View 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) {

View 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;