diff --git a/lib/ffmpeg/qsv/encoder.py b/lib/ffmpeg/qsv/encoder.py index 7af0bea0..2891f83d 100644 --- a/lib/ffmpeg/qsv/encoder.py +++ b/lib/ffmpeg/qsv/encoder.py @@ -15,6 +15,7 @@ from ....lib.common import mapRangeInt, get_media, call, exe2os from ....lib.codecs import Codec from ....lib.formats import PixelFormat +from ....lib.mfx.api import StringAPI class Encoder(FFEncoder): hwaccel = property(lambda s: "qsv") @@ -65,6 +66,13 @@ def encparams(self): f"{self.iqoffset}{self.bqoffset}" ) +# NOTE: Inherit from the StringAPI class first so that it overrides the +# Encoder class +class StringAPIEncoder(StringAPI, Encoder): + @property + def encparams(self): + return f" -qsv_params '{super().encparams}'" + @slash.requires(*have_ffmpeg_hwaccel("qsv")) @slash.requires(using_compatible_driver) class EncoderTest(BaseEncoderTest): diff --git a/lib/ffmpeg/util.py b/lib/ffmpeg/util.py index 71a0c650..c8d50d4e 100644 --- a/lib/ffmpeg/util.py +++ b/lib/ffmpeg/util.py @@ -85,6 +85,17 @@ def have_ffmpeg_encoder(encoder): result = try_call(f"{exe2os('ffmpeg')} -hide_banner -encoders | awk '{{print $2}}' | grep -w {encoder}") return result, encoder +@memoize +def have_ffmpeg_encoder_options(name, *args): + failmsg = "{0}.{1}".format(name, '.'.join(args)) + result = try_call(f"{exe2os('ffmpeg')} -hide_banner -encoders | awk '{{print $2}}' | grep -w {name}") + if result is True: + for key in args: + result = try_call(f"{exe2os('ffmpeg')} -hide_banner -h encoder={name} | grep -e {key}") + if result is False: + break + return result, failmsg + @memoize def have_ffmpeg_decoder(decoder): result = try_call(f"{exe2os('ffmpeg')} -hide_banner -decoders | awk '{{print $2}}' | grep -w {decoder}") diff --git a/lib/mfx/api.py b/lib/mfx/api.py new file mode 100644 index 00000000..8374b8a8 --- /dev/null +++ b/lib/mfx/api.py @@ -0,0 +1,197 @@ +from ...lib.codecs import Codec +from ...lib.common import memoize +from ...lib.properties import PropertyHandler +from enum import IntEnum, unique + +#define come from _install/include/vpl/mfxstructures.h +class CodecProfile(IntEnum): + MFX_PROFILE_UNKNOWN =0 #/*!< Unspecified profile. */ + + #/* Combined with H.264 profile these flags impose additional constrains. See H.264 specification for the list of constrains. */ + MFX_PROFILE_AVC_CONSTRAINT_SET0 = (0x100 << 0) + MFX_PROFILE_AVC_CONSTRAINT_SET1 = (0x100 << 1) + MFX_PROFILE_AVC_CONSTRAINT_SET2 = (0x100 << 2) + MFX_PROFILE_AVC_CONSTRAINT_SET3 = (0x100 << 3) + MFX_PROFILE_AVC_CONSTRAINT_SET4 = (0x100 << 4) + MFX_PROFILE_AVC_CONSTRAINT_SET5 = (0x100 << 5) + + #/* H.264 Profiles. */ + MFX_PROFILE_AVC_BASELINE =66 + MFX_PROFILE_AVC_MAIN =77 + MFX_PROFILE_AVC_EXTENDED =88 + MFX_PROFILE_AVC_HIGH =100 + MFX_PROFILE_AVC_HIGH10 =110 + MFX_PROFILE_AVC_HIGH_422 =122 + MFX_PROFILE_AVC_CONSTRAINED_BASELINE =MFX_PROFILE_AVC_BASELINE + MFX_PROFILE_AVC_CONSTRAINT_SET1 + MFX_PROFILE_AVC_CONSTRAINED_HIGH =MFX_PROFILE_AVC_HIGH + MFX_PROFILE_AVC_CONSTRAINT_SET4 + MFX_PROFILE_AVC_CONSTRAINT_SET5 + MFX_PROFILE_AVC_PROGRESSIVE_HIGH =MFX_PROFILE_AVC_HIGH + MFX_PROFILE_AVC_CONSTRAINT_SET4 + + #/* HEVC profiles */ + MFX_PROFILE_HEVC_MAIN =1 + MFX_PROFILE_HEVC_MAIN10 =2 + MFX_PROFILE_HEVC_MAINSP =3 + MFX_PROFILE_HEVC_REXT =4 + MFX_PROFILE_HEVC_SCC =9 + + #/* AV1 Profiles */ + MFX_PROFILE_AV1_MAIN = 1 + MFX_PROFILE_AV1_HIGH = 2 + MFX_PROFILE_AV1_PRO = 3 + + #/* VP9 Profiles */ + MFX_PROFILE_VP9_0 = 1 + MFX_PROFILE_VP9_1 = 2 + MFX_PROFILE_VP9_2 = 3 + MFX_PROFILE_VP9_3 = 4 + +class RateControlMethod(IntEnum): +#/*! The RateControlMethod enumerator itemizes bitrate control methods. */ + MFX_RATECONTROL_CBR =1 #/*!< Use the constant bitrate control algorithm. */ + MFX_RATECONTROL_VBR =2 #/*!< Use the variable bitrate control algorithm. */ + MFX_RATECONTROL_CQP =3 #/*!< Use the constant quantization parameter algorithm. */ + MFX_RATECONTROL_AVBR =4 #/*!< Use the average variable bitrate control algorithm. */ + MFX_RATECONTROL_RESERVED1 =5 + MFX_RATECONTROL_RESERVED2 =6 + MFX_RATECONTROL_RESERVED3 =100 + MFX_RATECONTROL_RESERVED4 =7 + #/*! + # Use the VBR algorithm with look ahead. It is a special bitrate control mode in the AVC encoder that has been designed + # to improve encoding quality. It works by performing extensive analysis of several dozen frames before the actual encoding and as a side + # effect significantly increases encoding delay and memory consumption. + + # The only available rate control parameter in this mode is mfxInfoMFX::TargetKbps. Two other parameters, MaxKbps and InitialDelayInKB, + # are ignored. To control LA depth the application can use mfxExtCodingOption2::LookAheadDepth parameter. + + # This method is not HRD compliant. + #*/ + MFX_RATECONTROL_LA =8 + #/*! + # Use the Intelligent Constant Quality algorithm. This algorithm improves subjective video quality of encoded stream. Depending on content, + # it may or may not decrease objective video quality. Only one control parameter is used - quality factor, specified by mfxInfoMFX::ICQQuality. + #*/ + MFX_RATECONTROL_ICQ =9 + #/*! + # Use the Video Conferencing Mode algorithm. This algorithm is similar to the VBR and uses the same set of parameters mfxInfoMFX::InitialDelayInKB, + # TargetKbpsandMaxKbps. It is tuned for IPPP GOP pattern and streams with strong temporal correlation between frames. + # It produces better objective and subjective video quality in these conditions than other bitrate control algorithms. + # It does not support interlaced content, B-frames and produced stream is not HRD compliant. + #*/ + MFX_RATECONTROL_VCM =10 + #/*! + # Use Intelligent Constant Quality algorithm with look ahead. Quality factor is specified by mfxInfoMFX::ICQQuality. + # To control LA depth the application can use mfxExtCodingOption2::LookAheadDepth parameter. + # + # This method is not HRD compliant. + #*/ + MFX_RATECONTROL_LA_ICQ =11 + #/*! + # MFX_RATECONTROL_LA_EXT has been removed + #*/ + + #/*! Use HRD compliant look ahead rate control algorithm. */ + MFX_RATECONTROL_LA_HRD =13 + #/*! + # Use the variable bitrate control algorithm with constant quality. This algorithm trying to achieve the target subjective quality with + # the minimum number of bits, while the bitrate constraint and HRD compliance are satisfied. It uses the same set of parameters + # as VBR and quality factor specified by mfxExtCodingOption3::QVBRQuality. + #*/ + MFX_RATECONTROL_QVBR =14 + +class StringAPI(PropertyHandler): + level = property(lambda s: s.ifprop("level", "CodecLevel={level}")) + gop = property(lambda s: s.ifprop("gop", "GopPicSize={gop}")) + bframes = property(lambda s: s.ifprop("bframes", "GopRefDist={bframes}")) + slices = property(lambda s: s.ifprop("slices", "NumSlice={slices}")) + minrate = property(lambda s: s.ifprop("minrate", "TargetKbps={minrate}")) + maxrate = property(lambda s: s.ifprop("maxrate", "MaxKbps={maxrate}")) + refs = property(lambda s: s.ifprop("refs", "NumRefFrame={refs}")) + ladepth = property(lambda s: s.ifprop("ladepth", "LookAheadDepth={ladepth}")) + extbrc = property(lambda s: s.ifprop("extbrc", "ExtBRC={extbrc}")) + ldb = property(lambda s: s.ifprop("ldb", "mfxExtCodingOption3.LowDelayBRC={ldb}")) + strict = property(lambda s: s.ifprop("strict", "GopOptFlag={strict}")) + pict = property(lambda s: s.ifprop("vpict", "mfxExtCodingOption.PicTimingSEI=0")) + quality = property(lambda s: s.ifprop("quality", "TargetUsage={quality}")) + lowpower = property(lambda s: s.ifprop("lowpower", "LowPower={lowpower}")) + tilecols = property(lambda s: s.ifprop("tilecols", "mfxExtAV1TileParam.NumTileColumns={tilecols}")) + tilerows = property(lambda s: s.ifprop("tilerows", "mfxExtAV1TileParam.NumTileRows={tilerows}")) + maxframesize = property(lambda s: s.ifprop("maxframesize", "MaxSliceSize={maxframesize}")) + + @property + def profile(self): + def inner(profile): + return f"CodecProfile={self.map_profile(self.codec, self.props['profile'])}" + return self.ifprop("profile", inner) + + @property + def rcmode(self): + return f"RateControlMethod={self.map_rcmode(self.props['rcmode'])}" + + @property + def qp(self): + def inner(qp): + rcmode = self.props["rcmode"].upper() + mqp = qp + if self.codec in [Codec.MPEG2]: + mqp = mapRangeInt(qp, [0, 100], [1, 51]) + elif self.codec in [Codec.AV1] and "ICQ" == rcmode: + mqp = mapRangeInt(qp, [0, 255], [1, 51]) + elif self.codec in [Codec.HEVC] and "QVBR" == rcmode: + mqp = mapRangeInt(qp, [0, 255], [1, 51]) + return f"QPI={mqp}:QPP={mqp}:QPB={mqp}" + return self.ifprop("qp", inner) + + @property + def encparams(self): + params = [ + self.profile, self.rcmode, self.qp, self.level, self.gop, self.bframes, + self.slices, self.minrate, self.maxrate, self.refs, self.ladepth, + self.extbrc, self.ldb, self.strict, self.pict, self.quality, self.lowpower, + self.tilecols, self.tilerows, self.maxframesize, + ] + return ':'.join(v for v in params if len(v) != 0) + + @classmethod + @memoize + def map_profile(cls, codec, profile): + # TODO: we could move this method to the enum class as "lookup" + # then call MfxCodecProfile.lookup(codec, profile) + return { + Codec.AVC : { + "high" : CodecProfile.MFX_PROFILE_AVC_HIGH, + "main" : CodecProfile.MFX_PROFILE_AVC_MAIN, + "baseline" : CodecProfile.MFX_PROFILE_AVC_BASELINE, + "unknown" : CodecProfile.MFX_PROFILE_UNKNOWN + }, + Codec.HEVC : { + "main" : CodecProfile.MFX_PROFILE_HEVC_MAIN, + "main444" : CodecProfile.MFX_PROFILE_HEVC_REXT, + "scc" : CodecProfile.MFX_PROFILE_HEVC_SCC, + "scc-444" : CodecProfile.MFX_PROFILE_HEVC_SCC, + "mainsp" : CodecProfile.MFX_PROFILE_HEVC_MAINSP, + "main10" : CodecProfile.MFX_PROFILE_HEVC_MAIN10, + "main10sp" : CodecProfile.MFX_PROFILE_HEVC_MAINSP, + "main444-10" : CodecProfile.MFX_PROFILE_HEVC_REXT, + "unknown" : CodecProfile.MFX_PROFILE_UNKNOWN + }, + Codec.AV1 : { + "main" : CodecProfile.MFX_PROFILE_AV1_MAIN, + }, + Codec.VP9 : { + "profile0" : CodecProfile.MFX_PROFILE_VP9_0, + "profile1" : CodecProfile.MFX_PROFILE_VP9_1, + "profile2" : CodecProfile.MFX_PROFILE_VP9_2, + "profile3" : CodecProfile.MFX_PROFILE_VP9_3 + }, + }[codec][profile] + + @classmethod + @memoize + def map_rcmode(cls, rcmode): + # TODO: we could move this method to the enum class as "lookup" + # then call MfxRateControlMethod.lookup(rcmode) + return { + "CQP" : RateControlMethod.MFX_RATECONTROL_CQP, + "CBR" : RateControlMethod.MFX_RATECONTROL_CBR, + "VBR" : RateControlMethod.MFX_RATECONTROL_VBR, + "ICQ" : RateControlMethod.MFX_RATECONTROL_ICQ, + }[rcmode.upper()] diff --git a/lib/parameters.py b/lib/parameters.py index bc6d85b7..eb5144a7 100755 --- a/lib/parameters.py +++ b/lib/parameters.py @@ -22,15 +22,19 @@ def format_value(value, **params): return value.format(**augmented) -def gen_avc_cqp_variants(spec, profiles): +def gen_avc_cqp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - variants = copy.deepcopy(params.get("variants", dict()).get("cqp", None)) - if variants is None: - keys = ["gop", "slices", "bframes", "qp", "quality", "profile"] - product = list(itertools.product([1], [1], [0], [14, 28], [1, 4, 7], profiles)) # I, single-slice - product += list(itertools.product([30], [1], [0], [14, 28], [1, 4, 7], profiles)) # IP, single-slice - product += list(itertools.product([30], [4], [2], [14, 28], [1, 4, 7], profiles)) # IPB, multi-slice - variants = [dict(zip(keys, vals)) for vals in product] + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp", None)) + + if variants is None: + keys = ["gop", "slices", "bframes", "qp", "quality", "profile"] + product = list(itertools.product([1], [1], [0], [14, 28], [1, 4, 7], profiles)) # I, single-slice + product += list(itertools.product([30], [1], [0], [14, 28], [1, 4, 7], profiles)) # IP, single-slice + product += list(itertools.product([30], [4], [2], [14, 28], [1, 4, 7], profiles)) # IPB, multi-slice + variants = [dict(zip(keys, vals)) for vals in product] for variant in variants: uprofile = variant.get("profile", None) @@ -53,14 +57,19 @@ def gen_avc_cqp_variants(spec, profiles): variant["qp"], variant["quality"], profile ] -def gen_avc_cqp_parameters(spec, profiles): +def gen_avc_cqp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "qp", "quality", "profile") - params = gen_avc_cqp_variants(spec, profiles) + params = gen_avc_cqp_variants(spec, profiles, strapi) return keys, params -def gen_avc_cbr_variants(spec, profiles): +def gen_avc_cbr_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("cbr", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles @@ -81,12 +90,12 @@ def gen_avc_cbr_variants(spec, profiles): variant["bitrate"], variant.get("fps", 30), profile ] -def gen_avc_cbr_parameters(spec, profiles): +def gen_avc_cbr_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "bitrate", "fps", "profile") - params = gen_avc_cbr_variants(spec, profiles) + params = gen_avc_cbr_variants(spec, profiles, strapi) return keys, params -def gen_hevc_cbr_level_variants(spec, profiles): +def gen_hevc_cbr_level_variants(spec, profiles, strapi=False): for case, params in spec.items(): for variant in copy.deepcopy(params.get("variants", dict()).get("cbr_level", [])): uprofile = variant.get("profile", None) @@ -102,9 +111,14 @@ def gen_hevc_cbr_level_parameters( spec, profiles): params = gen_hevc_cbr_level_variants(spec, profiles) return keys, params -def gen_avc_vbr_variants(spec, profiles): +def gen_avc_vbr_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("vbr", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -114,14 +128,19 @@ def gen_avc_vbr_variants(spec, profiles): variant.get("refs", 1), profile ] -def gen_avc_vbr_parameters(spec, profiles): +def gen_avc_vbr_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "bitrate", "fps", "quality", "refs", "profile") - params = gen_avc_vbr_variants(spec, profiles) + params = gen_avc_vbr_variants(spec, profiles, strapi) return keys, params -def gen_avc_cqp_lp_variants(spec, profiles): +def gen_avc_cqp_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("cqp_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -130,14 +149,19 @@ def gen_avc_cqp_lp_variants(spec, profiles): variant["quality"], profile ] -def gen_avc_cqp_lp_parameters(spec, profiles): +def gen_avc_cqp_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "qp", "quality", "profile") - params = gen_avc_cqp_lp_variants(spec, profiles) + params = gen_avc_cqp_lp_variants(spec, profiles, strapi) return keys, params -def gen_avc_cbr_lp_variants(spec, profiles): +def gen_avc_cbr_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("cbr_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -146,14 +170,19 @@ def gen_avc_cbr_lp_variants(spec, profiles): variant["bitrate"], variant.get("fps", 30), profile ] -def gen_avc_cbr_lp_parameters(spec, profiles): +def gen_avc_cbr_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "bitrate", "fps", "profile") - params = gen_avc_cbr_lp_variants(spec, profiles) + params = gen_avc_cbr_lp_variants(spec, profiles, strapi) return keys, params -def gen_avc_vbr_lp_variants(spec, profiles): +def gen_avc_vbr_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("vbr_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -163,9 +192,9 @@ def gen_avc_vbr_lp_variants(spec, profiles): variant.get("refs", 1), profile ] -def gen_avc_vbr_lp_parameters(spec, profiles): +def gen_avc_vbr_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bframes", "bitrate", "fps", "quality", "refs", "profile") - params = gen_avc_vbr_lp_variants(spec, profiles) + params = gen_avc_vbr_lp_variants(spec, profiles, strapi) return keys, params def gen_avc_tcbrc_variants(spec, profiles): @@ -390,9 +419,14 @@ def gen_avc_rqp_parameters(spec, profiles): params = gen_avc_rqp_variants(spec, profiles) return keys, params -def gen_hevc_cqp_lp_variants(spec, profiles): +def gen_hevc_cqp_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("cqp_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cqp_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -401,14 +435,19 @@ def gen_hevc_cqp_lp_variants(spec, profiles): variant["quality"], profile ] -def gen_hevc_cqp_lp_parameters(spec, profiles): +def gen_hevc_cqp_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "qp", "quality", "profile") - params = gen_hevc_cqp_lp_variants(spec, profiles) + params = gen_hevc_cqp_lp_variants(spec, profiles, strapi) return keys, params -def gen_hevc_cbr_lp_variants(spec, profiles): +def gen_hevc_cbr_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("cbr_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("cbr_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -417,14 +456,19 @@ def gen_hevc_cbr_lp_variants(spec, profiles): variant["bitrate"], variant.get("fps", 30), profile ] -def gen_hevc_cbr_lp_parameters(spec, profiles): +def gen_hevc_cbr_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bitrate", "fps", "profile") - params = gen_hevc_cbr_lp_variants(spec, profiles) + params = gen_hevc_cbr_lp_variants(spec, profiles, strapi) return keys, params -def gen_hevc_vbr_lp_variants(spec, profiles): +def gen_hevc_vbr_lp_variants(spec, profiles, strapi=False): for case, params in spec.items(): - for variant in copy.deepcopy(params.get("variants", dict()).get("vbr_lp", [])): + if strapi: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr_lp_strapi", [])) + else: + variants = copy.deepcopy(params.get("variants", dict()).get("vbr_lp", [])) + + for variant in variants: uprofile = variant.get("profile", None) cprofiles = [uprofile] if uprofile else profiles for profile in cprofiles: @@ -434,9 +478,9 @@ def gen_hevc_vbr_lp_variants(spec, profiles): variant.get("refs", 1), profile ] -def gen_hevc_vbr_lp_parameters(spec, profiles): +def gen_hevc_vbr_lp_parameters(spec, profiles, strapi=False): keys = ("case", "gop", "slices", "bitrate", "fps", "quality", "refs", "profile") - params = gen_hevc_vbr_lp_variants(spec, profiles) + params = gen_hevc_vbr_lp_variants(spec, profiles, strapi) return keys, params def gen_hevc_qvbr_variants(spec, profiles): @@ -802,17 +846,21 @@ def gen_vp9_vbr_lp_parameters(spec): params = gen_vp9_vbr_lp_variants(spec) return keys, params -def gen_av1_cqp_variants(spec): +def gen_av1_cqp_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("cqp", []) + if strapi: + variants = params.get("variants", dict()).get("cqp_strapi", []) + else: + variants = params.get("variants", dict()).get("cqp", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["qp"], variant["quality"], variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("profile", "profile0")] -def gen_av1_cqp_parameters(spec): +def gen_av1_cqp_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "qp", "quality", "tilecols", "tilerows", "profile") - params = gen_av1_cqp_variants(spec) + params = gen_av1_cqp_variants(spec, strapi) return keys, params def gen_av1_icq_variants(spec): @@ -841,73 +889,93 @@ def gen_av1_icq_lp_parameters(spec): params = gen_av1_icq_lp_variants(spec) return keys, params -def gen_av1_cbr_variants(spec): +def gen_av1_cbr_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("cbr", []) + if strapi: + variants = params.get("variants", dict()).get("cbr_strapi", []) + else: + variants = params.get("variants", dict()).get("cbr", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["bitrate"], variant.get("quality", 4), variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("fps", 30), variant.get("profile", "profile0")] -def gen_av1_cbr_parameters(spec): +def gen_av1_cbr_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "bitrate", "quality", "tilecols", "tilerows", "fps", "profile") - params = gen_av1_cbr_variants(spec) + params = gen_av1_cbr_variants(spec, strapi) return keys, params -def gen_av1_vbr_variants(spec): +def gen_av1_vbr_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("vbr", []) + if strapi: + variants = params.get("variants", dict()).get("vbr_strapi", []) + else: + variants = params.get("variants", dict()).get("vbr", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["bitrate"], variant.get("quality", 4), variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("fps", 30), variant.get("profile", "profile0")] -def gen_av1_vbr_parameters(spec): +def gen_av1_vbr_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "bitrate", "quality", "tilecols", "tilerows", "fps", "profile") - params = gen_av1_vbr_variants(spec) + params = gen_av1_vbr_variants(spec, strapi) return keys, params -def gen_av1_cqp_lp_variants(spec): +def gen_av1_cqp_lp_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("cqp_lp", []) + if strapi: + variants = params.get("variants", dict()).get("cqp_lp_strapi", []) + else: + variants = params.get("variants", dict()).get("cqp_lp", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["qp"], variant["quality"], variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("profile", "profile0")] -def gen_av1_cqp_lp_parameters(spec): +def gen_av1_cqp_lp_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "qp", "quality", "tilecols", "tilerows", "profile") - params = gen_av1_cqp_lp_variants(spec) + params = gen_av1_cqp_lp_variants(spec, strapi) return keys, params -def gen_av1_cbr_lp_variants(spec): +def gen_av1_cbr_lp_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("cbr_lp", []) + if strapi: + variants = params.get("variants", dict()).get("cbr_lp_strapi", []) + else: + variants = params.get("variants", dict()).get("cbr_lp", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["bitrate"], variant.get("quality", 4), variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("fps", 30), variant.get("profile", "profile0")] -def gen_av1_cbr_lp_parameters(spec): +def gen_av1_cbr_lp_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "bitrate", "quality", "tilecols", "tilerows", "fps", "profile") - params = gen_av1_cbr_lp_variants(spec) + params = gen_av1_cbr_lp_variants(spec, strapi) return keys, params -def gen_av1_vbr_lp_variants(spec): +def gen_av1_vbr_lp_variants(spec, strapi=False): for case, params in spec.items(): - variants = params.get("variants", dict()).get("vbr_lp", []) + if strapi: + variants = params.get("variants", dict()).get("vbr_lp_strapi", []) + else: + variants = params.get("variants", dict()).get("vbr_lp", []) + for variant in variants: yield [ case, variant["gop"], variant["bframes"], variant["bitrate"], variant.get("quality", 4), variant.get("tilecols", 0), variant.get("tilerows", 0), variant.get("fps", 30), variant.get("profile", "profile0")] -def gen_av1_vbr_lp_parameters(spec): +def gen_av1_vbr_lp_parameters(spec, strapi=False): keys = ("case", "gop", "bframes", "bitrate", "quality", "tilecols", "tilerows", "fps", "profile") - params = gen_av1_vbr_lp_variants(spec) + params = gen_av1_vbr_lp_variants(spec, strapi) return keys, params def gen_vpp_sharpen_variants(spec): diff --git a/test/ffmpeg-qsv/encode/av1.py b/test/ffmpeg-qsv/encode/av1.py index 3fd5cf15..7dfc0b6b 100644 --- a/test/ffmpeg-qsv/encode/av1.py +++ b/test/ffmpeg-qsv/encode/av1.py @@ -7,6 +7,7 @@ from ....lib import * from ....lib.ffmpeg.qsv.util import * from ....lib.ffmpeg.qsv.encoder import AV1EncoderLPTest, AV1EncoderTest +from ....lib.ffmpeg.qsv.encoder import StringAPIEncoder spec = load_test_spec("av1", "encode", "8bit") spec_r2r = load_test_spec("av1", "encode", "8bit", "r2r") @@ -37,6 +38,13 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, qp, quality, profile) vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_cqp_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, qp, quality, profile) + self.encode() + class icq(AV1EncoderTest): def init(self, tspec, case, gop, bframes, tilecols, tilerows,qp, quality, profile): vars(self).update(tspec[case].copy()) @@ -92,6 +100,13 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, bitrate, quality, fps vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_cbr_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, bitrate, quality, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile) + self.encode() + class vbr(AV1EncoderTest): def init(self, tspec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile): vars(self).update(tspec[case].copy()) @@ -122,6 +137,13 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_vbr_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile) + self.encode() + class cqp_lp(AV1EncoderLPTest): def init(self, tspec, case, gop, bframes, tilecols, tilerows,qp, quality, profile): vars(self).update(tspec[case].copy()) @@ -148,6 +170,13 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, qp, quality, profile) vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_cqp_lp_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, qp, quality, profile) + self.encode() + class icq_lp(AV1EncoderLPTest): def init(self, tspec, case, gop, bframes, tilecols, tilerows,qp, quality, profile): vars(self).update(tspec[case].copy()) @@ -203,6 +232,13 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, bitrate, quality, fps vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_cbr_lp_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, bitrate, quality, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile) + self.encode() + class vbr_lp(AV1EncoderLPTest): def init(self, tspec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile): vars(self).update(tspec[case].copy()) @@ -232,3 +268,11 @@ def test_r2r(self, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality self.init(spec_r2r, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile) vars(self).setdefault("r2r", 5) self.encode() + + @slash.requires(*have_ffmpeg_encoder_options("av1_qsv", "qsv_params")) + @slash.parametrize(*gen_av1_vbr_lp_parameters(spec, strapi=True)) + def test_strapi(self, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, bframes, tilecols, tilerows, bitrate, fps, quality, profile) + self.encode() + diff --git a/test/ffmpeg-qsv/encode/avc.py b/test/ffmpeg-qsv/encode/avc.py index e612a45d..a38303f3 100755 --- a/test/ffmpeg-qsv/encode/avc.py +++ b/test/ffmpeg-qsv/encode/avc.py @@ -7,6 +7,7 @@ from ....lib import * from ....lib.ffmpeg.qsv.util import * from ....lib.ffmpeg.qsv.encoder import AVCEncoderTest, AVCEncoderLPTest +from ....lib.ffmpeg.qsv.encoder import StringAPIEncoder spec = load_test_spec("avc", "encode") spec_r2r = load_test_spec("avc", "encode", "r2r") @@ -36,6 +37,13 @@ def test_r2r(self, case, gop, slices, bframes, qp, quality, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_cqp_parameters(spec, ['high', 'main', 'baseline'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, qp, quality, profile) + self.encode() + class cqp_lp(AVCEncoderLPTest): def init(self, tspec, case, gop, slices, bframes, qp, quality, profile): vars(self).update(tspec[case].copy()) @@ -61,6 +69,13 @@ def test_r2r(self, case, gop, slices, bframes, qp, quality, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_cqp_lp_parameters(spec, ['high', 'main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, qp, quality, profile) + self.encode() + class cbr(AVCEncoderTest): def init(self, tspec, case, gop, slices, bframes, bitrate, fps, profile): vars(self).update(tspec[case].copy()) @@ -88,6 +103,13 @@ def test_r2r(self, case, gop, slices, bframes, bitrate, fps, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_cbr_parameters(spec, ['high', 'main', 'baseline'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, profile) + self.encode() + class cbr_lp(AVCEncoderLPTest): def init(self, tspec, case, gop, slices, bframes, bitrate, fps, profile): vars(self).update(tspec[case].copy()) @@ -115,6 +137,13 @@ def test_r2r(self, case, gop, slices, bframes, bitrate, fps, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_cbr_lp_parameters(spec, ['high', 'main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, profile) + self.encode() + class vbr(AVCEncoderTest): def init(self, tspec, case, gop, slices, bframes, bitrate, fps, quality, refs, profile): vars(self).update(tspec[case].copy()) @@ -144,6 +173,13 @@ def test_r2r(self, case, gop, slices, bframes, bitrate, fps, quality, refs, prof vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_vbr_parameters(spec, ['high', 'main', 'baseline'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, quality, refs, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, quality, refs, profile) + self.encode() + class vbr_lp(AVCEncoderLPTest): def before(self): super().before() @@ -191,6 +227,13 @@ def test_tcbrc(self, case, bitrate, fps, profile): ) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("h264_qsv", "qsv_params")) + @slash.parametrize(*gen_avc_vbr_lp_parameters(spec, ['high', 'main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, quality, refs, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, quality, refs, profile) + self.encode() + # TODO: This can be moved into the vbr test class in a test_la method class vbr_la(AVCEncoderTest): @slash.parametrize(*gen_avc_vbr_la_parameters(spec, ['high', 'main', 'baseline'])) diff --git a/test/ffmpeg-qsv/encode/hevc.py b/test/ffmpeg-qsv/encode/hevc.py index c87fa4e8..90861ec1 100755 --- a/test/ffmpeg-qsv/encode/hevc.py +++ b/test/ffmpeg-qsv/encode/hevc.py @@ -7,6 +7,7 @@ from ....lib import * from ....lib.ffmpeg.qsv.util import * from ....lib.ffmpeg.qsv.encoder import HEVC8EncoderTest, HEVC8EncoderLPTest +from ....lib.ffmpeg.qsv.encoder import StringAPIEncoder spec = load_test_spec("hevc", "encode", "8bit") spec_r2r = load_test_spec("hevc", "encode", "8bit", "r2r") @@ -36,6 +37,13 @@ def test_r2r(self, case, gop, slices, bframes, qp, quality, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_cqp_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, qp, quality, profile) + self.encode() + class cqp_lp(HEVC8EncoderLPTest): def init(self, tspec, case, gop, slices, qp, quality, profile): vars(self).update(tspec[case].copy()) @@ -60,6 +68,13 @@ def test_r2r(self, case, gop, slices, qp, quality, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_cqp_lp_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, qp, quality, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, qp, quality, profile) + self.encode() + class cbr(HEVC8EncoderTest): def init(self, tspec, case, gop, slices, bframes, bitrate, fps, profile): vars(self).update(tspec[case].copy()) @@ -87,6 +102,13 @@ def test_r2r(self, case, gop, slices, bframes, bitrate, fps, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_cbr_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, profile) + self.encode() + class cbr_lp(HEVC8EncoderLPTest): def init(self, tspec, case, gop, slices, bitrate, fps, profile): vars(self).update(tspec[case].copy()) @@ -113,6 +135,13 @@ def test_r2r(self, case, gop, slices, bitrate, fps, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_cbr_lp_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, bitrate, fps, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bitrate, fps, profile) + self.encode() + class vbr(HEVC8EncoderTest): def init(self, tspec, case, gop, slices, bframes, bitrate, fps, quality, refs, profile): vars(self).update(tspec[case].copy()) @@ -142,6 +171,13 @@ def test_r2r(self, case, gop, slices, bframes, bitrate, fps, quality, refs, prof vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_vbr_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, bframes, bitrate, fps, quality, refs, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bframes, bitrate, fps, quality, refs, profile) + self.encode() + class vbr_lp(HEVC8EncoderLPTest): def before(self): super().before() @@ -173,6 +209,13 @@ def test_r2r(self, case, gop, slices, bitrate, fps, quality, refs, profile): vars(self).setdefault("r2r", 5) self.encode() + @slash.requires(*have_ffmpeg_encoder_options("hevc_qsv", "qsv_params")) + @slash.parametrize(*gen_hevc_vbr_lp_parameters(spec, ['main'], strapi=True)) + def test_strapi(self, case, gop, slices, bitrate, fps, quality, refs, profile): + self.EncoderClass = StringAPIEncoder + self.init(spec, case, gop, slices, bitrate, fps, quality, refs, profile) + self.encode() + # TCBRC is VBR LP + LDB + Strict(-1) @slash.parametrize(*gen_hevc_tcbrc_parameters(spec, ['main'])) def test_tcbrc(self, case, bitrate, fps, profile):