From cd04bab19aa5732baa603f4f238443f9790a2051 Mon Sep 17 00:00:00 2001 From: sr55 Date: Mon, 8 Apr 2024 07:37:14 +0200 Subject: [PATCH] libhb: enable FFV1 encoder. Co-authored-by: Damiano Galassi --- contrib/ffmpeg/module.defs | 1 + libhb/common.c | 24 ++++--- libhb/encavcodec.c | 131 +++++++++++++++++++++++++++++++++++-- libhb/handbrake/common.h | 2 + libhb/muxavformat.c | 5 ++ libhb/work.c | 17 ++++- 6 files changed, 165 insertions(+), 15 deletions(-) diff --git a/contrib/ffmpeg/module.defs b/contrib/ffmpeg/module.defs index 0a0932df6a81..93c7e366b9b1 100644 --- a/contrib/ffmpeg/module.defs +++ b/contrib/ffmpeg/module.defs @@ -56,6 +56,7 @@ FFMPEG.CONFIGURE.extra = \ --disable-decoder=libvpx_* \ --enable-encoder=libvpx_vp8 \ --enable-encoder=libvpx_vp9 \ + --enable-encoder=ffv1 \ --disable-decoder=*_crystalhd \ --enable-demuxer=av1 \ --enable-demuxer=obu \ diff --git a/libhb/common.c b/libhb/common.c index 7bd262c8c70e..cbe61c862425 100644 --- a/libhb/common.c +++ b/libhb/common.c @@ -79,6 +79,7 @@ enum HB_GID_VCODEC_AV1_QSV, HB_GID_VCODEC_AV1_NVENC, HB_GID_VCODEC_AV1_VCE, + HB_GID_VCODEC_FFV1, HB_GID_ACODEC_AAC, HB_GID_ACODEC_AAC_HE, HB_GID_ACODEC_AAC_PASS, @@ -282,6 +283,7 @@ hb_encoder_internal_t hb_video_encoders[] = { { "AV1 (NVEnc)", "nvenc_av1", "AV1 (NVEnc)", HB_VCODEC_FFMPEG_NVENC_AV1, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_NVENC, }, { { "AV1 10-bit (NVEnc)", "nvenc_av1_10bit", "AV1 10-bit (NVEnc)", HB_VCODEC_FFMPEG_NVENC_AV1_10BIT, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_NVENC, }, { { "AV1 (AMD VCE)", "vce_av1", "AV1 (AMD VCE)", HB_VCODEC_FFMPEG_VCE_AV1, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_AV1_VCE, }, + { { "FFV1", "ffv1", "FFV1 (libavcodec)", HB_VCODEC_FFMPEG_FFV1, HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_FFV1, }, { { "H.264 (x264)", "x264", "H.264 (libx264)", HB_VCODEC_X264_8BIT, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_X264, }, { { "H.264 10-bit (x264)", "x264_10bit", "H.264 10-bit (libx264)", HB_VCODEC_X264_10BIT, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_X264, }, { { "H.264 (Intel QSV)", "qsv_h264", "H.264 (Intel Media SDK)", HB_VCODEC_QSV_H264, HB_MUX_MASK_MP4|HB_MUX_MASK_MKV, }, NULL, 0, 1, HB_GID_VCODEC_H264_QSV, }, @@ -375,6 +377,7 @@ static int hb_video_encoder_is_enabled(int encoder, int disable_hardware) case HB_VCODEC_FFMPEG_VP9_10BIT: case HB_VCODEC_SVT_AV1: case HB_VCODEC_SVT_AV1_10BIT: + case HB_VCODEC_FFMPEG_FFV1: return 1; #if HB_PROJECT_FEATURE_X265 @@ -1532,6 +1535,13 @@ void hb_video_quality_get_limits(uint32_t codec, float *low, float *high, *high = 100; break; + case HB_VCODEC_FFMPEG_FFV1: + *direction = 0; + *granularity = 1; + *low = 0; + *high = 0; + break; + case HB_VCODEC_FFMPEG_MPEG2: case HB_VCODEC_FFMPEG_MPEG4: default: @@ -1637,6 +1647,7 @@ int hb_video_multipass_is_supported(uint32_t codec, int constant_quality) case HB_VCODEC_FFMPEG_VP9: case HB_VCODEC_FFMPEG_VP9_10BIT: + case HB_VCODEC_FFMPEG_FFV1: return 1; default: @@ -1847,12 +1858,15 @@ const char* const* hb_video_encoder_get_levels(int encoder) } #endif + if (encoder & HB_VCODEC_FFMPEG_MASK) + { + return hb_av_level_get_names(encoder); + } + switch (encoder) { case HB_VCODEC_X264_8BIT: case HB_VCODEC_X264_10BIT: - case HB_VCODEC_FFMPEG_NVENC_H264: - case HB_VCODEC_FFMPEG_MF_H264: return hb_h264_level_names; #if HB_PROJECT_FEATURE_VCE @@ -1864,11 +1878,6 @@ const char* const* hb_video_encoder_get_levels(int encoder) case HB_VCODEC_X265_10BIT: case HB_VCODEC_X265_12BIT: case HB_VCODEC_X265_16BIT: - case HB_VCODEC_FFMPEG_NVENC_H265: - case HB_VCODEC_FFMPEG_NVENC_H265_10BIT: - case HB_VCODEC_FFMPEG_VCE_H265: - case HB_VCODEC_FFMPEG_VCE_H265_10BIT: - case HB_VCODEC_FFMPEG_MF_H265: return hb_h265_level_names; #ifdef __APPLE__ @@ -1880,7 +1889,6 @@ const char* const* hb_video_encoder_get_levels(int encoder) case HB_VCODEC_SVT_AV1: case HB_VCODEC_SVT_AV1_10BIT: - case HB_VCODEC_FFMPEG_VCE_AV1: return hb_av1_level_names; default: diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c index d7579eb689fd..d998c9be5431 100644 --- a/libhb/encavcodec.c +++ b/libhb/encavcodec.c @@ -17,6 +17,7 @@ #include "handbrake/av1_common.h" #include "handbrake/nal_units.h" #include "handbrake/nvenc_common.h" +#include "handbrake/vce_common.h" #include "handbrake/extradata.h" /* @@ -57,10 +58,12 @@ int encavcodecInit( hb_work_object_t *, hb_job_t * ); int encavcodecWork( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** ); void encavcodecClose( hb_work_object_t * ); -static int apply_encoder_preset(int vcodec, AVDictionary ** av_opts, - const char * preset); +static int apply_encoder_preset(int vcodec, AVCodecContext *context, + AVDictionary **av_opts, + const char *preset); static int apply_encoder_tune(int vcodec, AVDictionary ** av_opts, const char * tune); + static int apply_encoder_options(hb_job_t *job, AVCodecContext *context, AVDictionary **av_opts); @@ -88,6 +91,11 @@ static const char * const h26x_nvenc_preset_names[] = "fastest", "faster", "fast", "medium", "slow", "slower", "slowest", NULL }; +static const char * const ffv1_preset_names[] = +{ + "default", "preservation", NULL +}; + static const char * const h264_nvenc_profile_names[] = { "auto", "baseline", "main", "high", NULL // "high444p" not supported. @@ -118,6 +126,21 @@ static const char * const h265_mf_profile_name[] = "auto", "main", NULL }; +static const char * const ffv1_profile_names[] = +{ + "auto", NULL +}; + +static const char * const hb_ffv1_level_names[] = +{ + "auto", "1", "3", NULL +}; + +static const int hb_ffv1_level_values[] = +{ + -1, 1, 3, 0 +}; + static const enum AVPixelFormat standard_pix_fmts[] = { AV_PIX_FMT_YUV420P, AV_PIX_FMT_NONE @@ -148,6 +171,13 @@ static const enum AVPixelFormat vce_pix_formats_10bit[] = AV_PIX_FMT_P010, AV_PIX_FMT_NONE }; +static const enum AVPixelFormat ffv1_pix_formats[] = +{ + AV_PIX_FMT_YUV444P16, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV444P10, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV420P, + AV_PIX_FMT_NONE +}; int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) { @@ -157,6 +187,7 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) const AVCodec * codec = NULL; AVCodecContext * context; AVRational fps; + AVDictionary *av_opts = NULL; hb_work_private_t * pv = calloc( 1, sizeof( hb_work_private_t ) ); w->private_data = pv; @@ -248,6 +279,15 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) break; } }break; + case AV_CODEC_ID_FFV1: + { + switch (job->vcodec) { + case HB_VCODEC_FFMPEG_FFV1: + hb_log("encavcodecInit: FFV1 (libavcodec)"); + codec_name = "ffv1"; + break; + } + }break; } if (codec_name == NULL) @@ -347,8 +387,7 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) } } - AVDictionary * av_opts = NULL; - if (apply_encoder_preset(job->vcodec, &av_opts, job->encoder_preset)) + if (apply_encoder_preset(job->vcodec, context, &av_opts, job->encoder_preset)) { av_free( context ); ret = 1; @@ -726,6 +765,32 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job ) av_dict_set(&av_opts, "a53cc", "0", 0); av_dict_set(&av_opts, "s12m_tc", "0", 0); } + if (job->vcodec == HB_VCODEC_FFMPEG_FFV1) + { + if (job->encoder_level != NULL && *job->encoder_level) + { + int i = 1; + while (hb_ffv1_level_names[i] != NULL) + { + if (!strcasecmp(job->encoder_level, hb_ffv1_level_names[i])) + context->level = hb_ffv1_level_values[i]; + ++i; + } + } + + int slices[] = {4, 6, 9, 12, 16, 24, 30}; + context->slices = hb_get_cpu_count(); + + int slice_index = 0; + for (int i = 0; i < sizeof(slices) / sizeof(int); i++) + { + if (context->slices >= slices[i]) + { + slice_index = i; + } + } + context->slices = slices[slice_index]; + } // Make VCE h.265 encoder emit an IDR for every GOP if (job->vcodec == HB_VCODEC_FFMPEG_VCE_H265 || job->vcodec == HB_VCODEC_FFMPEG_VCE_H265_10BIT) @@ -1289,8 +1354,22 @@ static int apply_vp9_10bit_preset(AVDictionary ** av_opts, const char * preset) return apply_vpx_preset(av_opts, preset); } -static int apply_encoder_preset(int vcodec, AVDictionary ** av_opts, - const char * preset) +static int apply_ffv1_preset(AVCodecContext *context, AVDictionary **av_opts, const char *preset) +{ + if (!strcasecmp(preset, "preservation")) + { + context->gop_size = 1; + context->level = 3; + av_dict_set(av_opts, "coder", "1", 0); + av_dict_set(av_opts, "context", "1", 0); + av_dict_set(av_opts, "slicecrc", "1", 0); + } + return 0; +} + +static int apply_encoder_preset(int vcodec, AVCodecContext *context, + AVDictionary **av_opts, + const char *preset) { switch (vcodec) { @@ -1311,6 +1390,9 @@ static int apply_encoder_preset(int vcodec, AVDictionary ** av_opts, av_dict_set( av_opts, "preset", preset, 0); break; #endif + + case HB_VCODEC_FFMPEG_FFV1: + return apply_ffv1_preset(context, av_opts, preset); default: break; } @@ -1365,6 +1447,9 @@ const char* const* hb_av_preset_get_names(int encoder) case HB_VCODEC_FFMPEG_MF_H265: return h26x_mf_preset_name; + case HB_VCODEC_FFMPEG_FFV1: + return ffv1_preset_names; + default: return NULL; } @@ -1396,6 +1481,37 @@ const char* const* hb_av_profile_get_names(int encoder) return h264_mf_profile_name; case HB_VCODEC_FFMPEG_MF_H265: return h265_mf_profile_name; + case HB_VCODEC_FFMPEG_FFV1: + return ffv1_profile_names; + + default: + return NULL; + } +} + +const char* const* hb_av_level_get_names(int encoder) +{ + switch (encoder) + { + case HB_VCODEC_FFMPEG_NVENC_H264: + case HB_VCODEC_FFMPEG_MF_H264: + return hb_h264_level_names; + + case HB_VCODEC_FFMPEG_VCE_H264: + return hb_vce_h264_level_names; // Not quite the same as x264 + + case HB_VCODEC_FFMPEG_NVENC_H265: + case HB_VCODEC_FFMPEG_NVENC_H265_10BIT: + case HB_VCODEC_FFMPEG_VCE_H265: + case HB_VCODEC_FFMPEG_VCE_H265_10BIT: + case HB_VCODEC_FFMPEG_MF_H265: + return hb_h265_level_names; + + case HB_VCODEC_FFMPEG_VCE_AV1: + return hb_av1_level_names; + + case HB_VCODEC_FFMPEG_FFV1: + return hb_ffv1_level_names; default: return NULL; @@ -1425,6 +1541,9 @@ const int* hb_av_get_pix_fmts(int encoder) case HB_VCODEC_FFMPEG_VP9_10BIT: return standard_10bit_pix_fmts; + case HB_VCODEC_FFMPEG_FFV1: + return ffv1_pix_formats; + default: return standard_pix_fmts; } diff --git a/libhb/handbrake/common.h b/libhb/handbrake/common.h index 00429bd51d9b..29697207c137 100644 --- a/libhb/handbrake/common.h +++ b/libhb/handbrake/common.h @@ -629,6 +629,8 @@ struct hb_job_s #define HB_VCODEC_FFMPEG_NVENC_AV1 (0x00000033 | HB_VCODEC_FFMPEG_MASK | HB_VCODEC_AV1_MASK) #define HB_VCODEC_FFMPEG_NVENC_AV1_10BIT (0x00000034 | HB_VCODEC_FFMPEG_MASK | HB_VCODEC_AV1_MASK) +#define HB_VCODEC_FFMPEG_FFV1 (0x00000040 | HB_VCODEC_FFMPEG_MASK) + #define HB_VCODEC_SVT_AV1_8BIT (0x00000041 | HB_VCODEC_SVT_AV1_MASK | HB_VCODEC_AV1_MASK) #define HB_VCODEC_SVT_AV1 HB_VCODEC_SVT_AV1_8BIT #define HB_VCODEC_SVT_AV1_10BIT (0x00000042 | HB_VCODEC_SVT_AV1_MASK | HB_VCODEC_AV1_MASK) diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c index 6390256ac77b..5da7e96ca732 100644 --- a/libhb/muxavformat.c +++ b/libhb/muxavformat.c @@ -355,6 +355,7 @@ static int avformatInit( hb_mux_object_t * m ) } } } break; + case HB_VCODEC_THEORA: track->st->codecpar->codec_id = AV_CODEC_ID_THEORA; break; @@ -383,6 +384,10 @@ static int avformatInit( hb_mux_object_t * m ) } break; + case HB_VCODEC_FFMPEG_FFV1: + track->st->codecpar->codec_id = AV_CODEC_ID_FFV1; + break; + default: hb_error("muxavformat: Unknown video codec: %x", job->vcodec); goto error; diff --git a/libhb/work.c b/libhb/work.c index f58c12eb0ef8..a33662fbcdb1 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -100,6 +100,16 @@ static void SetWorkStateInfo(hb_job_t *job) hb_set_state( job->h, &state ); } +static void sanitize_multipass(hb_job_t *job) +{ + int bit_depth = hb_get_bit_depth(job->title->pix_fmt); + if (job->vcodec == HB_VCODEC_FFMPEG_FFV1 && bit_depth > 8) + { + hb_log("FFV1 2-pass is not supported for bit depth higher than 8, disabling"); + job->multipass = 0; + } +} + /** * Iterates through job list and calls do_job for each job. * @param _work Handle work object. @@ -155,6 +165,8 @@ static void work_func( void * _work ) } #endif + + sanitize_multipass(job); hb_job_setup_passes(job->h, job, passes); hb_job_close(&job); @@ -337,7 +349,10 @@ hb_work_object_t* hb_video_encoder(hb_handle_t *h, int vcodec) case HB_VCODEC_SVT_AV1_10BIT: w = hb_get_work(h, WORK_ENCSVTAV1); break; - + case HB_VCODEC_FFMPEG_FFV1: + w = hb_get_work(h, WORK_ENCAVCODEC); + w->codec_param = AV_CODEC_ID_FFV1; + break; default: hb_error("Unknown video codec (0x%x)", vcodec ); }