diff options
author | Sybren A. Stüvel <sybren@stuvel.eu> | 2016-09-21 16:01:51 +0300 |
---|---|---|
committer | Sybren A. Stüvel <sybren@stuvel.eu> | 2016-09-21 16:03:11 +0300 |
commit | a7e74791221e2ef9b44ee1b3eb9ece37785aa62a (patch) | |
tree | bdef7bcac914ea1330046baac05c52719c34cd7e /source | |
parent | 2476faebd751fe7a250d7a496a1f56338b83d4e9 (diff) |
FFmpeg interface improvements
This patch changes a couple of things in the video output encoding.
{F362527}
- Clearer separation between container and codec. No more "format", as this is
too ambiguous. As a result, codecs were removed from the container list.
- Added FFmpeg speed presets, so the user can choosen from the range "Very
slow" to "Ultra fast". By default no preset is used.
- Added Constant Rate Factor (CRF) mode, which allows changing the bit-rate
depending on the desired quality and the input. This generally produces the
best quality videos, at the expense of not knowing the exact bit-rate and
file size.
- Added optional maximum of non-B-frames between B-frames (`max_b_frames`).
- Presets were adjusted for these changes, and new presets added. One of the
new presets is [recommended](https://trac.ffmpeg.org/wiki/Encode/VFX#H.264)
for reviewing videos, as it allows players to scrub through it easily. Might
be nice in weeklies. This preset also requires control over the
`max_b_frames` setting.
GUI-only changes:
- Renamed "MPEG" in the output file format menu with "FFmpeg", as this is more
accurate. After all, FFmpeg is used when this option is chosen, which can
also output non-MPEG files.
- Certain parts of the GUI are disabled when not in use:
- bit rate options are not used when a constant rate factor is given.
- audio bitrate & volume are not used when no audio is exported.
Note that I did not touch `BKE_ffmpeg_preset_set()`. There are currently two
preset systems for FFmpeg (`BKE_ffmpeg_preset_set()` and the Python preset
system). Before we do more work on `BKE_ffmpeg_preset_set()`, I think it's a
good idea to determine whether we want to keep it at all.
After this patch has been accepted, I'd be happy to go through the code and
remove any then-obsolete bits, such as the handling of "XVID" as a container
format.
Reviewers: sergey, mont29, brecht
Subscribers: mpan3, Blendify, brecht, fsiddi
Tags: #bf_blender
Differential Revision: https://developer.blender.org/D2242
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/blenkernel/intern/image.c | 2 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/writeffmpeg.c | 76 | ||||
-rw-r--r-- | source/blender/blenloader/intern/versioning_270.c | 14 | ||||
-rw-r--r-- | source/blender/makesdna/DNA_scene_types.h | 37 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_scene.c | 81 |
5 files changed, 177 insertions, 33 deletions
diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index 8a9cb73c8c9..a4eef2f9230 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -1325,7 +1325,7 @@ char BKE_imtype_from_arg(const char *imtype_arg) else if (STREQ(imtype_arg, "EXR")) return R_IMF_IMTYPE_OPENEXR; else if (STREQ(imtype_arg, "MULTILAYER")) return R_IMF_IMTYPE_MULTILAYER; #endif - else if (STREQ(imtype_arg, "MPEG")) return R_IMF_IMTYPE_FFMPEG; + else if (STREQ(imtype_arg, "FFMPEG")) return R_IMF_IMTYPE_FFMPEG; else if (STREQ(imtype_arg, "FRAMESERVER")) return R_IMF_IMTYPE_FRAMESERVER; #ifdef WITH_CINEON else if (STREQ(imtype_arg, "CINEON")) return R_IMF_IMTYPE_CINEON; diff --git a/source/blender/blenkernel/intern/writeffmpeg.c b/source/blender/blenkernel/intern/writeffmpeg.c index de55a1977bf..b0ab6f707fa 100644 --- a/source/blender/blenkernel/intern/writeffmpeg.c +++ b/source/blender/blenkernel/intern/writeffmpeg.c @@ -69,10 +69,14 @@ typedef struct FFMpegContext { int ffmpeg_video_bitrate; int ffmpeg_audio_bitrate; int ffmpeg_gop_size; + int ffmpeg_max_b_frames; int ffmpeg_autosplit; int ffmpeg_autosplit_count; bool ffmpeg_preview; + int ffmpeg_crf; /* set to 0 to not use CRF mode; we have another flag for lossless anyway. */ + int ffmpeg_preset; /* see FFMpegPreset */ + AVFormatContext *outfile; AVStream *video_stream; AVStream *audio_stream; @@ -560,10 +564,37 @@ static AVStream *alloc_video_stream(FFMpegContext *context, RenderData *rd, int } c->gop_size = context->ffmpeg_gop_size; - c->bit_rate = context->ffmpeg_video_bitrate * 1000; - c->rc_max_rate = rd->ffcodecdata.rc_max_rate * 1000; - c->rc_min_rate = rd->ffcodecdata.rc_min_rate * 1000; - c->rc_buffer_size = rd->ffcodecdata.rc_buffer_size * 1024; + c->max_b_frames = context->ffmpeg_max_b_frames; + + if (context->ffmpeg_crf >= 0) { + ffmpeg_dict_set_int(&opts, "crf", context->ffmpeg_crf); + } else { + c->bit_rate = context->ffmpeg_video_bitrate * 1000; + c->rc_max_rate = rd->ffcodecdata.rc_max_rate * 1000; + c->rc_min_rate = rd->ffcodecdata.rc_min_rate * 1000; + c->rc_buffer_size = rd->ffcodecdata.rc_buffer_size * 1024; + } + + if (context->ffmpeg_preset) { + char const * preset_name; + switch(context->ffmpeg_preset) { + case FFM_PRESET_ULTRAFAST: preset_name = "ultrafast"; break; + case FFM_PRESET_SUPERFAST: preset_name = "superfast"; break; + case FFM_PRESET_VERYFAST: preset_name = "veryfast"; break; + case FFM_PRESET_FASTER: preset_name = "faster"; break; + case FFM_PRESET_FAST: preset_name = "fast"; break; + case FFM_PRESET_MEDIUM: preset_name = "medium"; break; + case FFM_PRESET_SLOW: preset_name = "slow"; break; + case FFM_PRESET_SLOWER: preset_name = "slower"; break; + case FFM_PRESET_VERYSLOW: preset_name = "veryslow"; break; + default: + printf("Unknown preset number %i, ignoring.\n", context->ffmpeg_preset); + preset_name = NULL; + } + if (preset_name != NULL) { + av_dict_set(&opts, "preset", preset_name, 0); + } + } #if 0 /* this options are not set in ffmpeg.c and leads to artifacts with MPEG-4 @@ -819,6 +850,12 @@ static int start_ffmpeg_impl(FFMpegContext *context, struct RenderData *rd, int context->ffmpeg_audio_bitrate = rd->ffcodecdata.audio_bitrate; context->ffmpeg_gop_size = rd->ffcodecdata.gop_size; context->ffmpeg_autosplit = rd->ffcodecdata.flags & FFMPEG_AUTOSPLIT_OUTPUT; + context->ffmpeg_crf = rd->ffcodecdata.constant_rate_factor; + context->ffmpeg_preset = rd->ffcodecdata.ffmpeg_preset; + + if ((rd->ffcodecdata.flags & FFMPEG_USE_MAX_B_FRAMES) != 0) { + context->ffmpeg_max_b_frames = rd->ffcodecdata.max_b_frames; + } /* Determine the correct filename */ ffmpeg_filepath_get(context, name, rd, context->ffmpeg_preview, suffix); @@ -852,12 +889,16 @@ static int start_ffmpeg_impl(FFMpegContext *context, struct RenderData *rd, int /* Returns after this must 'goto fail;' */ of->oformat = fmt; - of->packet_size = rd->ffcodecdata.mux_packet_size; - if (context->ffmpeg_audio_codec != AV_CODEC_ID_NONE) { - ffmpeg_dict_set_int(&opts, "muxrate", rd->ffcodecdata.mux_rate); - } - else { - av_dict_set(&opts, "muxrate", "0", 0); + + /* Only bother with setting packet size & mux rate when CRF is not used. */ + if (context->ffmpeg_crf == 0) { + of->packet_size = rd->ffcodecdata.mux_packet_size; + if (context->ffmpeg_audio_codec != AV_CODEC_ID_NONE) { + ffmpeg_dict_set_int(&opts, "muxrate", rd->ffcodecdata.mux_rate); + } + else { + av_dict_set(&opts, "muxrate", "0", 0); + } } ffmpeg_dict_set_int(&opts, "preload", (int)(0.5 * AV_TIME_BASE)); @@ -1503,14 +1544,6 @@ static void ffmpeg_set_expert_options(RenderData *rd) BKE_ffmpeg_property_add_string(rd, "video", "fast-pskip:1"); BKE_ffmpeg_property_add_string(rd, "video", "wpredp:2"); #endif - - if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) { -#ifdef FFMPEG_HAVE_DEPRECATED_FLAGS2 - BKE_ffmpeg_property_add_string(rd, "video", "cqp:0"); -#else - BKE_ffmpeg_property_add_string(rd, "video", "qp:0"); -#endif - } } else if (codec_id == AV_CODEC_ID_DNXHD) { if (rd->ffcodecdata.flags & FFMPEG_LOSSLESS_OUTPUT) @@ -1622,9 +1655,10 @@ void BKE_ffmpeg_image_type_verify(RenderData *rd, ImageFormatData *imf) rd->ffcodecdata.audio_codec <= 0 || rd->ffcodecdata.video_bitrate <= 1) { - rd->ffcodecdata.codec = AV_CODEC_ID_MPEG2VIDEO; - - BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_DVD); + BKE_ffmpeg_preset_set(rd, FFMPEG_PRESET_H264); + rd->ffcodecdata.constant_rate_factor = FFM_CRF_MEDIUM; + rd->ffcodecdata.ffmpeg_preset = FFM_PRESET_MEDIUM; + rd->ffcodecdata.type = FFMPEG_MKV; } if (rd->ffcodecdata.type == FFMPEG_OGG) { rd->ffcodecdata.type = FFMPEG_MPEG2; diff --git a/source/blender/blenloader/intern/versioning_270.c b/source/blender/blenloader/intern/versioning_270.c index dfaa59c4e3f..aa5ef1c98cf 100644 --- a/source/blender/blenloader/intern/versioning_270.c +++ b/source/blender/blenloader/intern/versioning_270.c @@ -1392,4 +1392,18 @@ void blo_do_versions_270(FileData *fd, Library *UNUSED(lib), Main *main) } } } + if (!MAIN_VERSION_ATLEAST(main, 279, 0)) { + if (!DNA_struct_elem_find(fd->filesdna, "FFMpegCodecData", "int", "ffmpeg_preset")) { + for (Scene *scene = main->scene.first; scene; scene = scene->id.next) { + /* "medium" is the preset FFmpeg uses when no presets are given. */ + scene->r.ffcodecdata.ffmpeg_preset = FFM_PRESET_MEDIUM; + } + } + if (!DNA_struct_elem_find(fd->filesdna, "FFMpegCodecData", "int", "constant_rate_factor")) { + for (Scene *scene = main->scene.first; scene; scene = scene->id.next) { + /* fall back to behaviour from before we introduced CRF for old files */ + scene->r.ffcodecdata.constant_rate_factor = FFM_CRF_NONE; + } + } + } } diff --git a/source/blender/makesdna/DNA_scene_types.h b/source/blender/makesdna/DNA_scene_types.h index 5c5264afcba..94f23197293 100644 --- a/source/blender/makesdna/DNA_scene_types.h +++ b/source/blender/makesdna/DNA_scene_types.h @@ -135,6 +135,37 @@ typedef struct QuicktimeCodecSettings { int pad1; } QuicktimeCodecSettings; +typedef enum FFMpegPreset { + FFM_PRESET_NONE, + FFM_PRESET_ULTRAFAST, + FFM_PRESET_SUPERFAST, + FFM_PRESET_VERYFAST, + FFM_PRESET_FASTER, + FFM_PRESET_FAST, + FFM_PRESET_MEDIUM, + FFM_PRESET_SLOW, + FFM_PRESET_SLOWER, + FFM_PRESET_VERYSLOW, +} FFMpegPreset; + + +/* Mapping from easily-understandable descriptions to CRF values. + * Assumes we output 8-bit video. Needs to be remapped if 10-bit + * is output. + * We use a slightly wider than "subjectively sane range" according + * to https://trac.ffmpeg.org/wiki/Encode/H.264#a1.ChooseaCRFvalue + */ +typedef enum FFMpegCrf { + FFM_CRF_NONE = -1, + FFM_CRF_LOSSLESS = 0, + FFM_CRF_PERC_LOSSLESS = 17, + FFM_CRF_HIGH = 20, + FFM_CRF_MEDIUM = 23, + FFM_CRF_LOW = 26, + FFM_CRF_VERYLOW = 29, + FFM_CRF_LOWEST = 32, +} FFMpegCrf; + typedef struct FFMpegCodecData { int type; int codec; @@ -146,13 +177,18 @@ typedef struct FFMpegCodecData { int audio_pad; float audio_volume; int gop_size; + int max_b_frames; /* only used if FFMPEG_USE_MAX_B_FRAMES flag is set. */ int flags; + int constant_rate_factor; + int ffmpeg_preset; /* see FFMpegPreset */ int rc_min_rate; int rc_max_rate; int rc_buffer_size; int mux_packet_size; int mux_rate; + int pad1; + IDProperty *properties; } FFMpegCodecData; @@ -1982,6 +2018,7 @@ enum { #endif FFMPEG_AUTOSPLIT_OUTPUT = 2, FFMPEG_LOSSLESS_OUTPUT = 4, + FFMPEG_USE_MAX_B_FRAMES = (1 << 3), }; /* Paint.flags */ diff --git a/source/blender/makesrna/intern/rna_scene.c b/source/blender/makesrna/intern/rna_scene.c index bd6674f899c..c63d4e775f8 100644 --- a/source/blender/makesrna/intern/rna_scene.c +++ b/source/blender/makesrna/intern/rna_scene.c @@ -282,16 +282,11 @@ EnumPropertyItem rna_enum_image_type_items[] = { {R_IMF_IMTYPE_FRAMESERVER, "FRAMESERVER", ICON_FILE_SCRIPT, "Frame Server", "Output image to a frameserver"}, #endif #ifdef WITH_FFMPEG - {R_IMF_IMTYPE_H264, "H264", ICON_FILE_MOVIE, "H.264", "Output video in H.264 format"}, - {R_IMF_IMTYPE_FFMPEG, "FFMPEG", ICON_FILE_MOVIE, "MPEG", "Output video in MPEG format"}, - {R_IMF_IMTYPE_THEORA, "THEORA", ICON_FILE_MOVIE, "Ogg Theora", "Output video in Ogg format"}, + {R_IMF_IMTYPE_FFMPEG, "FFMPEG", ICON_FILE_MOVIE, "FFmpeg video", "The most versatile way to output video files"}, #endif #ifdef WITH_QUICKTIME {R_IMF_IMTYPE_QUICKTIME, "QUICKTIME", ICON_FILE_MOVIE, "QuickTime", "Output video in Quicktime format"}, #endif -#ifdef WITH_FFMPEG - {R_IMF_IMTYPE_XVID, "XVID", ICON_FILE_MOVIE, "Xvid", "Output video in Xvid format"}, -#endif {0, NULL, 0, NULL, NULL} }; @@ -5320,6 +5315,7 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) PropertyRNA *prop; #ifdef WITH_FFMPEG + /* Container types */ static EnumPropertyItem ffmpeg_format_items[] = { {FFMPEG_MPEG1, "MPEG1", 0, "MPEG-1", ""}, {FFMPEG_MPEG2, "MPEG2", 0, "MPEG-2", ""}, @@ -5327,8 +5323,8 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) {FFMPEG_AVI, "AVI", 0, "AVI", ""}, {FFMPEG_MOV, "QUICKTIME", 0, "Quicktime", ""}, {FFMPEG_DV, "DV", 0, "DV", ""}, - {FFMPEG_H264, "H264", 0, "H.264", ""}, - {FFMPEG_XVID, "XVID", 0, "Xvid", ""}, +// {FFMPEG_H264, "H264", 0, "H.264", ""}, not a container +// {FFMPEG_XVID, "XVID", 0, "Xvid", ""}, not a container {FFMPEG_OGG, "OGG", 0, "Ogg", ""}, {FFMPEG_MKV, "MKV", 0, "Matroska", ""}, {FFMPEG_FLV, "FLASH", 0, "Flash", ""}, @@ -5352,6 +5348,32 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) {0, NULL, 0, NULL, NULL} }; + static EnumPropertyItem ffmpeg_preset_items[] = { + {FFM_PRESET_ULTRAFAST, "ULTRAFAST", 0, "Ultra fast; biggest file", ""}, + {FFM_PRESET_SUPERFAST, "SUPERFAST", 0, "Super fast", ""}, + {FFM_PRESET_VERYFAST, "VERYFAST", 0, "Very fast", ""}, + {FFM_PRESET_FASTER, "FASTER", 0, "Faster", ""}, + {FFM_PRESET_FAST, "FAST", 0, "Fast", ""}, + {FFM_PRESET_MEDIUM, "MEDIUM", 0, "Medium speed", ""}, + {FFM_PRESET_SLOW, "SLOW", 0, "Slow", ""}, + {FFM_PRESET_SLOWER, "SLOWER", 0, "Slower", ""}, + {FFM_PRESET_VERYSLOW, "VERYSLOW", 0, "Very slow; smallest file", ""}, + {0, NULL, 0, NULL, NULL} + }; + + static EnumPropertyItem ffmpeg_crf_items[] = { + {FFM_CRF_NONE, "NONE", 0, "None; use constant bit-rate", + "Use constant bit rate, rather than constant output quality"}, + {FFM_CRF_LOSSLESS, "LOSSLESS", 0, "Lossless", ""}, + {FFM_CRF_PERC_LOSSLESS, "PERC_LOSSLESS", 0, "Perceptually lossless", ""}, + {FFM_CRF_HIGH, "HIGH", 0, "High quality", ""}, + {FFM_CRF_MEDIUM, "MEDIUM", 0, "Medium quality", ""}, + {FFM_CRF_LOW, "LOW", 0, "Low quality", ""}, + {FFM_CRF_VERYLOW, "VERYLOW", 0, "Very low quality", ""}, + {FFM_CRF_LOWEST, "LOWEST", 0, "Lowest quality", ""}, + {0, NULL, 0, NULL, NULL} + }; + static EnumPropertyItem ffmpeg_audio_codec_items[] = { {AV_CODEC_ID_NONE, "NONE", 0, "None", ""}, {AV_CODEC_ID_MP2, "MP2", 0, "MP2", ""}, @@ -5383,13 +5405,15 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) RNA_def_property_enum_bitflag_sdna(prop, NULL, "type"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_enum_items(prop, ffmpeg_format_items); - RNA_def_property_ui_text(prop, "Format", "Output file format"); + RNA_def_property_enum_default(prop, FFMPEG_MKV); + RNA_def_property_ui_text(prop, "Container", "Output file container"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_FFmpegSettings_codec_settings_update"); prop = RNA_def_property(srna, "codec", PROP_ENUM, PROP_NONE); RNA_def_property_enum_bitflag_sdna(prop, NULL, "codec"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); RNA_def_property_enum_items(prop, ffmpeg_codec_items); + RNA_def_property_enum_default(prop, AV_CODEC_ID_H264); RNA_def_property_ui_text(prop, "Codec", "FFmpeg codec to use"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, "rna_FFmpegSettings_codec_settings_update"); @@ -5421,8 +5445,25 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) prop = RNA_def_property(srna, "gopsize", PROP_INT, PROP_NONE); RNA_def_property_int_sdna(prop, NULL, "gop_size"); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); - RNA_def_property_range(prop, 0, 100); - RNA_def_property_ui_text(prop, "GOP Size", "Distance between key frames"); + RNA_def_property_range(prop, 0, 500); + RNA_def_property_int_default(prop, 25); + RNA_def_property_ui_text(prop, "Keyframe interval", + "Distance between key frames, also known as GOP size; " + "influences file size and seekability"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + + prop = RNA_def_property(srna, "max_b_frames", PROP_INT, PROP_NONE); + RNA_def_property_int_sdna(prop, NULL, "max_b_frames"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_range(prop, 0, 16); + RNA_def_property_ui_text(prop, "Max B-frames", + "Maximum number of B-frames between non-B-frames; influences file size and seekability"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + + prop = RNA_def_property(srna, "use_max_b_frames", PROP_BOOLEAN, PROP_NONE); + RNA_def_property_boolean_sdna(prop, NULL, "flags", FFMPEG_USE_MAX_B_FRAMES); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_ui_text(prop, "Use max B-frames", "Set a maximum number of B-frames"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); prop = RNA_def_property(srna, "buffersize", PROP_INT, PROP_NONE); @@ -5439,6 +5480,24 @@ static void rna_def_scene_ffmpeg_settings(BlenderRNA *brna) RNA_def_property_ui_text(prop, "Mux Packet Size", "Mux packet size (byte)"); RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "constant_rate_factor", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_sdna(prop, NULL, "constant_rate_factor"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_enum_items(prop, ffmpeg_crf_items); + RNA_def_property_enum_default(prop, FFM_CRF_MEDIUM); + RNA_def_property_ui_text(prop, "Output quality", + "Constant Rate Factor (CRF); tradeoff between video quality and file size"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + + prop = RNA_def_property(srna, "ffmpeg_preset", PROP_ENUM, PROP_NONE); + RNA_def_property_enum_bitflag_sdna(prop, NULL, "ffmpeg_preset"); + RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); + RNA_def_property_enum_items(prop, ffmpeg_preset_items); + RNA_def_property_enum_default(prop, FFM_PRESET_MEDIUM); + RNA_def_property_ui_text(prop, "Encoding speed", + "Tradeoff between encoding speed and compression ratio"); + RNA_def_property_update(prop, NC_SCENE | ND_RENDER_OPTIONS, NULL); + prop = RNA_def_property(srna, "use_autosplit", PROP_BOOLEAN, PROP_NONE); RNA_def_property_boolean_sdna(prop, NULL, "flags", FFMPEG_AUTOSPLIT_OUTPUT); RNA_def_property_clear_flag(prop, PROP_ANIMATABLE); |