Fix T88623, T87044: Make encoded videos play correctly in VLC

The issue was two fold. We didn't properly:

1. Initialize the codec default values which would lead to VLC
   complaining because of garbage/wrong codec settings.

2.Calculate the time base for the video. FFmpeg would happily accept
  this but VLC seems to assume the time base value is at least somewhat
  correct and couldn't properly display the frames as the internal time
  base was huge. We are talking about 90k ticks (tbn) for one second of
  video!

This patch initializes all codecs to use their default values and fixes
the time base calculation so it follows the guidelines from ffmpeg.

Reviewed By: Sergey, Richard Antalik

Differential Revision: http://developer.blender.org/D11426
This commit is contained in:
Sebastian Parborg 2021-05-31 11:25:12 +02:00 committed by Jeroen Bakker
parent 14308b0a5e
commit 19c0666d40
Notes: blender-bot 2023-02-13 22:38:46 +01:00
Referenced by issue #88449, Blender LTS: Maintenance Task 2.93
Referenced by issue #88623, Crash when rendering animation with mpeg-1
Referenced by issue #87044, rendered video from VSE could not be played with VLC
2 changed files with 103 additions and 86 deletions

View File

@ -518,6 +518,48 @@ static void set_ffmpeg_properties(RenderData *rd,
}
}
static AVRational calc_time_base(uint den, double num, int codec_id)
{
/* Convert the input 'num' to an integer. Simply shift the decimal places until we get an integer
* (within a floating point error range).
* For example if we have den = 3 and num = 0.1 then the fps is: den/num = 30 fps.
* When converthing this to a ffmpeg time base, we want num to be an integer.
* So we simply move the decimal places of both numbers. IE den = 30, num = 1.*/
float eps = FLT_EPSILON;
const uint DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1;
/* Calculate the precision of the initial floating point number. */
if (num > 1.0) {
const uint num_integer_bits = log2_floor_u((unsigned int)num);
/* Formula for calculating the epsilon value: (power of two range) / (pow mantissa bits)
* For example, a float has 23 manitissa bits and the float value 3.5f as a pow2 range of
* (4-2=2):
* (2) / pow2(23) = floating point precision for 3.5f
*/
eps = (float)(1 << num_integer_bits) * FLT_EPSILON;
}
/* Calculate how many decimal shifts we can do until we run out of precision. */
const int max_num_shift = fabsf(log10f(eps));
/* Calculate how many times we can shift the denominator. */
const int max_den_shift = log10f(DENUM_MAX) - log10f(den);
const int max_iter = min_ii(max_num_shift, max_den_shift);
for (int i = 0; i < max_iter && fabs(num - round(num)) > eps; i++) {
/* Increase the number and denominator until both are integers. */
num *= 10;
den *= 10;
eps *= 10;
}
AVRational time_base;
time_base.den = den;
time_base.num = (int)num;
return time_base;
}
/* prepare a video stream for the output file */
static AVStream *alloc_video_stream(FFMpegContext *context,
@ -548,13 +590,24 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->codec_id = codec_id;
c->codec_type = AVMEDIA_TYPE_VIDEO;
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
fprintf(stderr, "Couldn't find valid video codec\n");
avcodec_free_context(&c);
context->video_codec = NULL;
return NULL;
}
/* Load codec defaults into 'c'. */
avcodec_get_context_defaults3(c, codec);
/* Get some values from the current render settings */
c->width = rectx;
c->height = recty;
/* FIXME: Really bad hack (tm) for NTSC support */
if (context->ffmpeg_type == FFMPEG_DV && rd->frs_sec != 25) {
/* FIXME: Really bad hack (tm) for NTSC support */
c->time_base.den = 2997;
c->time_base.num = 100;
}
@ -562,21 +615,23 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->time_base.den = rd->frs_sec;
c->time_base.num = (int)rd->frs_sec_base;
}
else if (compare_ff(rd->frs_sec_base, 1.001f, 0.000001f)) {
/* This converts xx/1.001 (which is used in presets) to xx000/1001 (which is used in the rest
* of the world, including FFmpeg). */
c->time_base.den = (int)(rd->frs_sec * 1000);
c->time_base.num = (int)(rd->frs_sec_base * 1000);
}
else {
/* This calculates a fraction (DENUM_MAX / num) which approximates the scene frame rate
* (frs_sec / frs_sec_base). It uses the maximum denominator allowed by FFmpeg.
*/
const double DENUM_MAX = (codec_id == AV_CODEC_ID_MPEG4) ? (1UL << 16) - 1 : (1UL << 31) - 1;
const double num = (DENUM_MAX / (double)rd->frs_sec) * rd->frs_sec_base;
c->time_base = calc_time_base(rd->frs_sec, rd->frs_sec_base, codec_id);
}
c->time_base.den = (int)DENUM_MAX;
c->time_base.num = (int)num;
/* As per the timebase documentation here:
* https://www.ffmpeg.org/ffmpeg-codecs.html#Codec-Options
* We want to set the time base to (1 / fps) for fixed frame rate video.
* If it is not possible, we want to set the timebase numbers to something as
* small as possible.
*/
if (c->time_base.num != 1) {
AVRational new_time_base;
if (av_reduce(
&new_time_base.num, &new_time_base.den, c->time_base.num, c->time_base.den, INT_MAX)) {
/* Exact reduction was possible. Use the new value. */
c->time_base = new_time_base;
}
}
st->time_base = c->time_base;
@ -588,6 +643,11 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
ffmpeg_dict_set_int(&opts, "lossless", 1);
}
else if (context->ffmpeg_crf >= 0) {
/* As per https://trac.ffmpeg.org/wiki/Encode/VP9 we must set the bit rate to zero when
* encoding with vp9 in crf mode.
* Set this to always be zero for other codecs as well.
* We don't care about bit rate in crf mode. */
c->bit_rate = 0;
ffmpeg_dict_set_int(&opts, "crf", context->ffmpeg_crf);
}
else {
@ -627,12 +687,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
}
}
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
avcodec_free_context(&c);
return NULL;
}
/* Be sure to use the correct pixel format(e.g. RGB, YUV) */
if (codec->pix_fmts) {
@ -649,12 +703,6 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->codec_tag = (('D' << 24) + ('I' << 16) + ('V' << 8) + 'X');
}
if (codec_id == AV_CODEC_ID_H264) {
/* correct wrong default ffmpeg param which crash x264 */
c->qmin = 10;
c->qmax = 51;
}
/* Keep lossless encodes in the RGB domain. */
if (codec_id == AV_CODEC_ID_HUFFYUV) {
if (rd->im_format.planes == R_IMF_PLANES_RGBA) {
@ -714,10 +762,14 @@ static AVStream *alloc_video_stream(FFMpegContext *context,
c->thread_type = FF_THREAD_SLICE;
}
if (avcodec_open2(c, codec, &opts) < 0) {
int ret = avcodec_open2(c, codec, &opts);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize video codec: %s\n", av_err2str(ret));
BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size);
av_dict_free(&opts);
avcodec_free_context(&c);
context->video_codec = NULL;
return NULL;
}
av_dict_free(&opts);
@ -777,6 +829,17 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
c->codec_id = codec_id;
c->codec_type = AVMEDIA_TYPE_AUDIO;
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
fprintf(stderr, "Couldn't find valid audio codec\n");
avcodec_free_context(&c);
context->audio_codec = NULL;
return NULL;
}
/* Load codec defaults into 'c'. */
avcodec_get_context_defaults3(c, codec);
c->sample_rate = rd->ffcodecdata.audio_mixrate;
c->bit_rate = context->ffmpeg_audio_bitrate * 1000;
c->sample_fmt = AV_SAMPLE_FMT_S16;
@ -806,13 +869,6 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
c->sample_fmt = AV_SAMPLE_FMT_FLT;
}
codec = avcodec_find_encoder(c->codec_id);
if (!codec) {
// XXX error("Couldn't find a valid audio codec");
avcodec_free_context(&c);
return NULL;
}
if (codec->sample_fmts) {
/* Check if the preferred sample format for this codec is supported.
* this is because, depending on the version of libav,
@ -852,11 +908,14 @@ static AVStream *alloc_audio_stream(FFMpegContext *context,
set_ffmpeg_properties(rd, c, "audio", &opts);
if (avcodec_open2(c, codec, &opts) < 0) {
// XXX error("Couldn't initialize audio codec");
int ret = avcodec_open2(c, codec, &opts);
if (ret < 0) {
fprintf(stderr, "Couldn't initialize audio codec: %s\n", av_err2str(ret));
BLI_strncpy(error, IMB_ffmpeg_last_error(), error_size);
av_dict_free(&opts);
avcodec_free_context(&c);
context->audio_codec = NULL;
return NULL;
}
av_dict_free(&opts);
@ -1634,49 +1693,7 @@ static void ffmpeg_set_expert_options(RenderData *rd)
IDP_FreePropertyContent(rd->ffcodecdata.properties);
}
if (codec_id == AV_CODEC_ID_H264) {
/*
* All options here are for x264, but must be set via ffmpeg.
* The names are therefore different - Search for "x264 to FFmpeg option mapping"
* to get a list.
*/
/*
* Use CABAC coder. Using "coder:1", which should be equivalent,
* crashes Blender for some reason. Either way - this is no big deal.
*/
BKE_ffmpeg_property_add_string(rd, "video", "coder:vlc");
/*
* The other options were taken from the libx264-default.preset
* included in the ffmpeg distribution.
*/
/* This breaks compatibility for QT. */
// BKE_ffmpeg_property_add_string(rd, "video", "flags:loop");
BKE_ffmpeg_property_add_string(rd, "video", "cmp:chroma");
BKE_ffmpeg_property_add_string(rd, "video", "partitions:parti4x4"); /* Deprecated. */
BKE_ffmpeg_property_add_string(rd, "video", "partitions:partp8x8"); /* Deprecated. */
BKE_ffmpeg_property_add_string(rd, "video", "partitions:partb8x8"); /* Deprecated. */
BKE_ffmpeg_property_add_string(rd, "video", "me:hex");
BKE_ffmpeg_property_add_string(rd, "video", "subq:6");
BKE_ffmpeg_property_add_string(rd, "video", "me_range:16");
BKE_ffmpeg_property_add_string(rd, "video", "qdiff:4");
BKE_ffmpeg_property_add_string(rd, "video", "keyint_min:25");
BKE_ffmpeg_property_add_string(rd, "video", "sc_threshold:40");
BKE_ffmpeg_property_add_string(rd, "video", "i_qfactor:0.71");
BKE_ffmpeg_property_add_string(rd, "video", "b_strategy:1");
BKE_ffmpeg_property_add_string(rd, "video", "bf:3");
BKE_ffmpeg_property_add_string(rd, "video", "refs:2");
BKE_ffmpeg_property_add_string(rd, "video", "qcomp:0.6");
BKE_ffmpeg_property_add_string(rd, "video", "trellis:0");
BKE_ffmpeg_property_add_string(rd, "video", "weightb:1");
BKE_ffmpeg_property_add_string(rd, "video", "8x8dct:1");
BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1");
BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2");
}
else if (codec_id == AV_CODEC_ID_DNXHD) {
if (codec_id == AV_CODEC_ID_DNXHD) {
if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) {
BKE_ffmpeg_property_add_string(rd, "video", "mbd:rd");
}

View File

@ -492,13 +492,6 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg(
rv->c = avcodec_alloc_context3(NULL);
rv->c->codec_type = AVMEDIA_TYPE_VIDEO;
rv->c->codec_id = AV_CODEC_ID_H264;
rv->c->width = width;
rv->c->height = height;
rv->c->gop_size = 10;
rv->c->max_b_frames = 0;
/* Correct wrong default ffmpeg param which crash x264. */
rv->c->qmin = 10;
rv->c->qmax = 51;
rv->of->oformat->video_codec = rv->c->codec_id;
rv->codec = avcodec_find_encoder(rv->c->codec_id);
@ -513,6 +506,13 @@ static struct proxy_output_ctx *alloc_proxy_output_ffmpeg(
return NULL;
}
avcodec_get_context_defaults3(rv->c, rv->codec);
rv->c->width = width;
rv->c->height = height;
rv->c->gop_size = 10;
rv->c->max_b_frames = 0;
if (rv->codec->pix_fmts) {
rv->c->pix_fmt = rv->codec->pix_fmts[0];
}