diff --git a/cfg/encoder_lowdelay_P_vtm.cfg b/cfg/encoder_lowdelay_P_vtm.cfg index 2fb32e0a1957bb8f96e3caf6c556769cdf7e5239..678fb2a77685c8064f7a7007912029df3701be71 100644 --- a/cfg/encoder_lowdelay_P_vtm.cfg +++ b/cfg/encoder_lowdelay_P_vtm.cfg @@ -64,8 +64,9 @@ TransformSkipLog2MaxSize : 5 SAOLcuBoundary : 0 # SAOLcuBoundary using non-deblocked pixels (0: OFF, 1: ON) #=========== TemporalFilter ================= -TemporalFilter : 1 # Enable/disable GOP Based Temporal Filter -TemporalFilterFutureReference : 0 # Enable/disable reading future frames +TemporalFilter : 1 +TemporalFilterPastRefs : 4 # Number of past references for temporal prefilter +TemporalFilterFutureRefs : 0 # Number of future references for temporal prefilter TemporalFilterStrengthFrame8 : 0.2 # Enable filter at every 8th frame with strength #============ Rate Control ====================== diff --git a/cfg/encoder_lowdelay_vtm.cfg b/cfg/encoder_lowdelay_vtm.cfg index c8198e0898d43f824b66615279768a8101e6fbcc..62d1ef02a0619addb65b1a1f831cedefc4da42a5 100644 --- a/cfg/encoder_lowdelay_vtm.cfg +++ b/cfg/encoder_lowdelay_vtm.cfg @@ -64,8 +64,9 @@ TransformSkipLog2MaxSize : 5 SAOLcuBoundary : 0 # SAOLcuBoundary using non-deblocked pixels (0: OFF, 1: ON) #=========== TemporalFilter ================= -TemporalFilter : 1 # Enable/disable GOP Based Temporal Filter -TemporalFilterFutureReference : 0 # Enable/disable reading future frames +TemporalFilter : 1 +TemporalFilterPastRefs : 4 # Number of past references for temporal prefilter +TemporalFilterFutureRefs : 0 # Number of future references for temporal prefilter TemporalFilterStrengthFrame8 : 0.2 # Enable filter at every 8th frame with strength #============ Rate Control ====================== diff --git a/cfg/encoder_randomaccess_vtm.cfg b/cfg/encoder_randomaccess_vtm.cfg index 82064508dcf8fcd4b219b0523f77ea4fe82ca825..0b64102c56875b4d25865c3997fddbfad8d41a6c 100644 --- a/cfg/encoder_randomaccess_vtm.cfg +++ b/cfg/encoder_randomaccess_vtm.cfg @@ -170,8 +170,10 @@ ALFAllowPredefinedFilters : 1 ALFStrengthTargetLuma : 1.0 ALFStrengthTargetChroma : 1.0 CCALFStrengthTarget : 1.0 -TemporalFilter : 1 # Enable/disable GOP Based Temporal Filter -TemporalFilterFutureReference : 1 # Enable/disable reading future frames + +TemporalFilter : 1 +TemporalFilterPastRefs : 4 # Number of past references for temporal prefilter +TemporalFilterFutureRefs : 4 # Number of future references for temporal prefilter TemporalFilterStrengthFrame8 : 0.95 # Enable filter at every 8th frame with given strength TemporalFilterStrengthFrame16 : 1.5 # Enable filter at every 16th frame with given strength, longer intervals has higher priority ### DO NOT ADD ANYTHING BELOW THIS LINE ### diff --git a/cfg/encoder_randomaccess_vtm_gop16.cfg b/cfg/encoder_randomaccess_vtm_gop16.cfg index 48f3b2d0032a8e361dc13ee8f4dd15592dead4c7..cfb25425841a3f2a39f95dab4fc192bd114b5019 100644 --- a/cfg/encoder_randomaccess_vtm_gop16.cfg +++ b/cfg/encoder_randomaccess_vtm_gop16.cfg @@ -154,8 +154,8 @@ ALFAllowPredefinedFilters : 1 ALFStrengthTargetLuma : 1.0 ALFStrengthTargetChroma : 1.0 CCALFStrengthTarget : 1.0 -TemporalFilter : 1 # Enable/disable GOP Based Temporal Filter -TemporalFilterFutureReference : 1 # Enable/disable reading future frames +TemporalFilterPastRefs : 4 # Number of past references for temporal prefilter +TemporalFilterFutureRefs : 4 # Number of future references for temporal prefilter TemporalFilterStrengthFrame8 : 0.95 # Enable filter at every 8th frame with given strength TemporalFilterStrengthFrame16 : 1.5 # Enable filter at every 16th frame with given strength, longer intervals has higher priority ### DO NOT ADD ANYTHING BELOW THIS LINE ### diff --git a/cfg/per-sequence/BQMall.cfg b/cfg/per-sequence/BQMall.cfg index 03317919d7cb7ea5e42f566ae3677c8a64148e7f..0d019abca858e273f4ed21f68c351dc41cc31720 100644 --- a/cfg/per-sequence/BQMall.cfg +++ b/cfg/per-sequence/BQMall.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 832 # Input frame width SourceHeight : 480 # Input frame height FramesToBeEncoded : 600 # Number of frames to be coded +LastValidFrame : 599 Level : 3.1 diff --git a/cfg/per-sequence/BQSquare.cfg b/cfg/per-sequence/BQSquare.cfg index eebd2d4e91ee6e3183f0aadf990e11593d88192c..a769a7971b04f8e07d90c62e05bf49b8c57010e8 100644 --- a/cfg/per-sequence/BQSquare.cfg +++ b/cfg/per-sequence/BQSquare.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 416 # Input frame width SourceHeight : 240 # Input frame height FramesToBeEncoded : 600 # Number of frames to be coded +LastValidFrame : 599 Level : 2.1 diff --git a/cfg/per-sequence/BQTerrace.cfg b/cfg/per-sequence/BQTerrace.cfg index b5d4c76d44754dc3ac88452e264086bb70a51da6..e7cace5fa7c2f57ba791a16c45f780785923a123 100644 --- a/cfg/per-sequence/BQTerrace.cfg +++ b/cfg/per-sequence/BQTerrace.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 1920 # Input frame width SourceHeight : 1080 # Input frame height FramesToBeEncoded : 600 # Number of frames to be coded +LastValidFrame : 599 Level : 4.1 diff --git a/cfg/per-sequence/BasketballDrill.cfg b/cfg/per-sequence/BasketballDrill.cfg index 6bfce02e7ee88ee2eb59132b352e39df5e41509e..87b55b88e33926b93171f060c471fa7e92304e5f 100644 --- a/cfg/per-sequence/BasketballDrill.cfg +++ b/cfg/per-sequence/BasketballDrill.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 832 # Input frame width SourceHeight : 480 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 3.1 diff --git a/cfg/per-sequence/BasketballDrillText.cfg b/cfg/per-sequence/BasketballDrillText.cfg index 04e614ffd391711f5bc3ca487bddd90b3a71b044..be30cfa58698996f5c17509dcfbd24f9dad0b436 100644 --- a/cfg/per-sequence/BasketballDrillText.cfg +++ b/cfg/per-sequence/BasketballDrillText.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 832 # Input frame width SourceHeight : 480 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 3.1 diff --git a/cfg/per-sequence/BasketballDrive.cfg b/cfg/per-sequence/BasketballDrive.cfg index ec2eb7631e36643e0d205d529281e305c2ef20cb..4354a67bd1a250cde4a5e73d06c05c62f4e024a1 100644 --- a/cfg/per-sequence/BasketballDrive.cfg +++ b/cfg/per-sequence/BasketballDrive.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 1920 # Input frame width SourceHeight : 1080 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 4.1 diff --git a/cfg/per-sequence/BasketballPass.cfg b/cfg/per-sequence/BasketballPass.cfg index c6b756c9423418e82deebceaf0a87ec08e33cf24..99fb95505360cb10a6d7658028bc7804d0f48067 100644 --- a/cfg/per-sequence/BasketballPass.cfg +++ b/cfg/per-sequence/BasketballPass.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 416 # Input frame width SourceHeight : 240 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 2.1 diff --git a/cfg/per-sequence/BlowingBubbles.cfg b/cfg/per-sequence/BlowingBubbles.cfg index 61a08aa469fe4b933089f10ff3f6342c862118ec..da6d9a60ce81a28f05d8cbe51e683bcd928eb04c 100644 --- a/cfg/per-sequence/BlowingBubbles.cfg +++ b/cfg/per-sequence/BlowingBubbles.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 416 # Input frame width SourceHeight : 240 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 2.1 diff --git a/cfg/per-sequence/NebutaFestival_10bit.cfg b/cfg/per-sequence/NebutaFestival_10bit.cfg index 3daf33595fe788662107f7ce790e83e9b4f17f6e..5e18b3a11119c56561b8c622a5548451083b90ac 100644 --- a/cfg/per-sequence/NebutaFestival_10bit.cfg +++ b/cfg/per-sequence/NebutaFestival_10bit.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 2560 # Input frame width SourceHeight : 1600 # Input frame height FramesToBeEncoded : 300 # Number of frames to be coded +LastValidFrame : 299 Level : 5 diff --git a/cfg/per-sequence/PartyScene.cfg b/cfg/per-sequence/PartyScene.cfg index caff00737fc1c697123ad2ce5ab0dc8946388563..4bd7066e27a27eb6204dd68e1c79d0d69f09847c 100644 --- a/cfg/per-sequence/PartyScene.cfg +++ b/cfg/per-sequence/PartyScene.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 832 # Input frame width SourceHeight : 480 # Input frame height FramesToBeEncoded : 500 # Number of frames to be coded +LastValidFrame : 499 Level : 3.1 diff --git a/cfg/per-sequence/SocialNetworkMap_444.cfg b/cfg/per-sequence/SocialNetworkMap_444.cfg index 8f0916ec317ffd69effac0f6ccc9291da3ea2af1..b2da2f194b4649663e848b0e279f2a05664c3480 100644 --- a/cfg/per-sequence/SocialNetworkMap_444.cfg +++ b/cfg/per-sequence/SocialNetworkMap_444.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 1920 # Input frame width SourceHeight : 1080 # Input frame height FramesToBeEncoded : 600 # Number of frames to be coded +LastValidFrame : 599 Level : 6.2 diff --git a/cfg/per-sequence/SocialNetworkMap_RGB.cfg b/cfg/per-sequence/SocialNetworkMap_RGB.cfg index 02dde0d074535ea587d63db07c4bf96302a979d8..db2beb6becc44dafcea1ef0bdadc5147271b6373 100644 --- a/cfg/per-sequence/SocialNetworkMap_RGB.cfg +++ b/cfg/per-sequence/SocialNetworkMap_RGB.cfg @@ -7,6 +7,7 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 1920 # Input frame width SourceHeight : 1080 # Input frame height FramesToBeEncoded : 600 # Number of frames to be coded +LastValidFrame : 599 InputColourSpaceConvert : RGBtoGBR # Non-normative colour space conversion to apply to input video SNRInternalColourSpace : 1 # Evaluate SNRs in GBR order OutputInternalColourSpace : 0 # Convert recon output back to RGB order. Use --OutputColourSpaceConvert GBRtoRGB on decoder to produce a matching output file. diff --git a/cfg/per-sequence/SteamLocomotiveTrain_10bit.cfg b/cfg/per-sequence/SteamLocomotiveTrain_10bit.cfg index 712ff44f54c2755ca6e10b6d50793ab2df69414d..d668cfb7a4506a15c9357c542b402a3edcb862ae 100644 --- a/cfg/per-sequence/SteamLocomotiveTrain_10bit.cfg +++ b/cfg/per-sequence/SteamLocomotiveTrain_10bit.cfg @@ -7,5 +7,6 @@ FrameSkip : 0 # Number of frames to be skipped in SourceWidth : 2560 # Input frame width SourceHeight : 1600 # Input frame height FramesToBeEncoded : 300 # Number of frames to be coded +LastValidFrame : 299 Level : 5 diff --git a/doc/software-manual.tex b/doc/software-manual.tex index 9597cda7c3d35bbf4bd20c8bbb05863d121647b3..55aff111b44917cc4c7bcaf82d25c3c202586c24 100644 --- a/doc/software-manual.tex +++ b/doc/software-manual.tex @@ -935,18 +935,35 @@ Picture output options: output upscaled (2), decoded but in full resolution buff %% GOP based temporal filter parameters %% -\begin{OptionTableNoShorthand}{GOP based temporal filter paramters}{tab:gop-based-temporal-filter} +\begin{OptionTableNoShorthand}{GOP based temporal filter parameters}{tab:gop-based-temporal-filter} \Option{TemporalFilter} & %\ShortOption{\None} & -\Default{false} & -Enables or disables GOP based temporal filter. +\Default{0} & +Enable motion-compensated temporal pre-filter. When enabled, at least one of TemporalFilterPastRefs and TemporalFilterFutureRefs +must be larger than 0. \\ -\Option{TemporalFilterFutureReference} & +\Option{TemporalFilterPastRefs} & %\ShortOption{\None} & -\Default{true} & -Enables or disable referencing future frames in the GOP based temporal filter. Can be used to disable future referencing for -low delay configurations. +\Default{4} & +Number of past frames used by the temporal filter. +\\ +\Option{TemporalFilterFutureRefs} & +%\ShortOption{\None} & +\Default{4} & +Number of future frames used by the temporal filter. This may be set to 0 to avoid using future frames. +\\ +\Option{FirstValidFrame} & +%\ShortOption{\None} & +\Default{0} & +Index of first frame in video sequence that may be used by the temporal filter. If a negative value is given, the index defaults to the value +of FrameSkip. +\\ +\Option{LastValidFrame} & +%\ShortOption{\None} & +\Default{MAX_INT} & +Index of last frame in video sequence that may be used by the temporal filter. If a negative value is given, the index defaults to the value +of FrameSkip + FramesToBeEncoded - 1. \\ \Option{TemporalFilterStrengthFrame*} & %\ShortOption{\None} & diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp index cb9ed720aecbf80ababd2575b0dd764c230e9853..723137c66af656e478ab44f241853bb7c6430b2d 100644 --- a/source/App/EncoderApp/EncApp.cpp +++ b/source/App/EncoderApp/EncApp.cpp @@ -267,7 +267,7 @@ void EncApp::xInitLibCfg() m_cEncLib.setSwitchPocPeriod ( m_switchPocPeriod ); m_cEncLib.setUpscaledOutput ( m_upscaledOutput ); m_cEncLib.setFramesToBeEncoded ( m_framesToBeEncoded ); - + m_cEncLib.setValidFrames(m_firstValidFrame, m_lastValidFrame); m_cEncLib.setAvoidIntraInDepLayer ( m_avoidIntraInDepLayer ); //====== SPS constraint flags ======= @@ -1234,6 +1234,7 @@ void EncApp::xInitLibCfg() m_cEncLib.setTargetOlsIdx (m_targetOlsIdx); } } + m_cEncLib.setGopBasedTemporalFilterEnabled(m_gopBasedTemporalFilterEnabled); m_cEncLib.setNumRefLayers ( m_numRefLayers ); @@ -1317,7 +1318,7 @@ void EncApp::createLib( const int layerIdx ) m_trueOrgPic = new PelStorage; m_orgPic->create( unitArea ); m_trueOrgPic->create( unitArea ); - if(m_gopBasedTemporalFilterEnabled) + if (m_gopBasedTemporalFilterEnabled) { m_filteredOrgPic = new PelStorage; m_filteredOrgPic->create( unitArea ); @@ -1349,16 +1350,16 @@ void EncApp::createLib( const int layerIdx ) m_ext360 = new TExt360AppEncTop( *this, m_cEncLib.getGOPEncoder()->getExt360Data(), *( m_cEncLib.getGOPEncoder() ), *m_orgPic ); #endif - if( m_gopBasedTemporalFilterEnabled ) + if (m_gopBasedTemporalFilterEnabled) { - m_temporalFilter.init( m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_sourceWidth, sourceHeight, - m_sourcePadding, m_bClipInputVideoToRec709Range, m_inputFileName, m_chromaFormatIDC, - m_inputColourSpaceConvert, m_iQP, m_gopBasedTemporalFilterStrengths, - m_gopBasedTemporalFilterFutureReference ); + m_temporalFilter.init(m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_sourceWidth, + sourceHeight, m_sourcePadding, m_bClipInputVideoToRec709Range, m_inputFileName, + m_chromaFormatIDC, m_inputColourSpaceConvert, m_iQP, m_gopBasedTemporalFilterStrengths, + m_gopBasedTemporalFilterPastRefs, m_gopBasedTemporalFilterFutureRefs, m_firstValidFrame, + m_lastValidFrame); } if ( m_fgcSEIAnalysisEnabled ) { - bool temporalFilterFutureReference = 1; int filteredFrame = 0; if ( m_iIntraPeriod < 1 ) @@ -1368,10 +1369,11 @@ void EncApp::createLib( const int layerIdx ) map<int, double> filteredFramesAndStrengths = { { filteredFrame, 1.5 } }; // TODO: adjust MCTF and MCTF strenght - m_temporalFilterForFG.init( m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_sourceWidth, - sourceHeight, m_sourcePadding, m_bClipInputVideoToRec709Range, m_inputFileName, - m_chromaFormatIDC, m_inputColourSpaceConvert, m_iQP, filteredFramesAndStrengths, - temporalFilterFutureReference ); + m_temporalFilterForFG.init(m_FrameSkip, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth, m_sourceWidth, + sourceHeight, m_sourcePadding, m_bClipInputVideoToRec709Range, m_inputFileName, + m_chromaFormatIDC, m_inputColourSpaceConvert, m_iQP, filteredFramesAndStrengths, + m_gopBasedTemporalFilterPastRefs, m_gopBasedTemporalFilterFutureRefs, m_firstValidFrame, + m_lastValidFrame); } } @@ -1401,7 +1403,7 @@ void EncApp::destroyLib() m_trueOrgPic->destroy(); delete m_trueOrgPic; delete m_orgPic; - if(m_gopBasedTemporalFilterEnabled) + if (m_gopBasedTemporalFilterEnabled) { m_filteredOrgPic->destroy(); delete m_filteredOrgPic; diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp index af7176059f5fbda61712b4e02c0a679f2e3f1d47..1a9347596571d1fe7b6301a96538205617ad0a51 100644 --- a/source/App/EncoderApp/EncAppCfg.cpp +++ b/source/App/EncoderApp/EncAppCfg.cpp @@ -81,6 +81,8 @@ enum ExtendedProfileName // this is used for determining profile strings, wher AUTO = -1 }; +constexpr int TF_DEFAULT_REFS = 4; + //! \ingroup EncoderApp //! \{ @@ -1608,9 +1610,12 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) ; opts.addOptions() - ("TemporalFilter", m_gopBasedTemporalFilterEnabled, false, "Enable GOP based temporal filter. Disabled per default") - ("TemporalFilterFutureReference", m_gopBasedTemporalFilterFutureReference, true, "Enable referencing of future frames in the GOP based temporal filter. This is typically disabled for Low Delay configurations.") - ("TemporalFilterStrengthFrame*", m_gopBasedTemporalFilterStrengths, std::map<int, double>(), "Strength for every * frame in GOP based temporal filter, where * is an integer." + ("TemporalFilter", m_gopBasedTemporalFilterEnabled, false, "Enable GOP based temporal filter. Disabled per default") + ("TemporalFilterPastRefs", m_gopBasedTemporalFilterPastRefs, TF_DEFAULT_REFS, "Number of past references for temporal prefilter") + ("TemporalFilterFutureRefs", m_gopBasedTemporalFilterFutureRefs, TF_DEFAULT_REFS, "Number of future references for temporal prefilter") + ("FirstValidFrame", m_firstValidFrame, 0, "First valid frame") + ("LastValidFrame", m_lastValidFrame, MAX_INT, "Last valid frame") + ("TemporalFilterStrengthFrame*", m_gopBasedTemporalFilterStrengths, std::map<int, double>(), "Strength for every * frame in GOP based temporal filter, where * is an integer." " E.g. --TemporalFilterStrengthFrame8 0.95 will enable GOP based temporal filter at every 8th frame with strength 0.95"); // clang-format on @@ -1864,6 +1869,15 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) } m_inputFileName = inputPathPrefix + m_inputFileName; + if (m_firstValidFrame < 0) + { + m_firstValidFrame = m_FrameSkip; + } + if (m_lastValidFrame < 0) + { + m_lastValidFrame = m_firstValidFrame + m_framesToBeEncoded - 1; + } + if( m_temporalSubsampleRatio < 1) { EXIT ( "Error: TemporalSubsampleRatio must be greater than 0" ); @@ -4358,6 +4372,15 @@ bool EncAppCfg::xCheckParameter() if (m_gopBasedTemporalFilterEnabled) { xConfirmPara(m_temporalSubsampleRatio != 1, "GOP Based Temporal Filter only support Temporal sub-sample ratio 1"); + xConfirmPara( + m_gopBasedTemporalFilterPastRefs <= 0 && m_gopBasedTemporalFilterFutureRefs <= 0, + "Either TemporalFilterPastRefs or TemporalFilterFutureRefs must be larger than 0 when TemporalFilter is enabled"); + + if ((m_gopBasedTemporalFilterPastRefs != 0 && m_gopBasedTemporalFilterPastRefs != TF_DEFAULT_REFS) + || (m_gopBasedTemporalFilterFutureRefs != 0 && m_gopBasedTemporalFilterFutureRefs != TF_DEFAULT_REFS)) + { + msg(WARNING, "Number of frames used for temporal prefilter is different from default.\n"); + } } #if EXTENSION_360_VIDEO check_failed |= m_ext360.verifyParameters(); @@ -4718,7 +4741,7 @@ void EncAppCfg::xPrintParameter() { msg( VERBOSE, "RPR:%d ", 0 ); } - msg(VERBOSE, "TemporalFilter:%d ", m_gopBasedTemporalFilterEnabled); + msg(VERBOSE, "TemporalFilter:%d/%d ", m_gopBasedTemporalFilterPastRefs, m_gopBasedTemporalFilterFutureRefs); msg(VERBOSE, "SEI CTI:%d ", m_ctiSEIEnabled); #if EXTENSION_360_VIDEO m_ext360.outputConfigurationSummary(); diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h index 843d162823b24cb4dc3f80492a3df4cde88346cb..a21bc0c37d3695d0e572db0782fe34d9c66c962b 100644 --- a/source/App/EncoderApp/EncAppCfg.h +++ b/source/App/EncoderApp/EncAppCfg.h @@ -114,9 +114,12 @@ protected: int m_confWinTop; int m_confWinBottom; int m_sourcePadding[2]; ///< number of padded pixels for width and height + int m_firstValidFrame; + int m_lastValidFrame; int m_framesToBeEncoded; ///< number of encoded frames bool m_AccessUnitDelimiter; ///< add Access Unit Delimiter NAL units bool m_enablePictureHeaderInSliceHeader; ///< Enable Picture Header in Slice Header + InputColourSpaceConversion m_inputColourSpaceConvert; ///< colour space conversion to apply to input video bool m_snrInternalColourSpace; ///< if true, then no colour space conversion is applied for snr calculation, otherwise inverse of input is applied. bool m_outputInternalColourSpace; ///< if true, then no colour space conversion is applied for reconstructed video, otherwise inverse of input is applied. @@ -837,8 +840,9 @@ protected: bool m_rprRASLtoolSwitch; bool m_avoidIntraInDepLayer; - bool m_gopBasedTemporalFilterEnabled; ///< GOP-based Temporal Filter enable/disable - bool m_gopBasedTemporalFilterFutureReference; ///< Enable/disable future frame references in the GOP-based Temporal Filter + bool m_gopBasedTemporalFilterEnabled; + int m_gopBasedTemporalFilterPastRefs; + int m_gopBasedTemporalFilterFutureRefs; std::map<int, double> m_gopBasedTemporalFilterStrengths; ///< Filter strength per frame for the GOP-based Temporal Filter int m_maxLayers; diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h index 1909bc6695fe7e8b59d0df8ae87c4d21d6ca881c..f4e96cabefbbd165f8f40bedd7e14e138a61dd2f 100644 --- a/source/Lib/EncoderLib/EncCfg.h +++ b/source/Lib/EncoderLib/EncCfg.h @@ -160,12 +160,15 @@ protected: //==== File I/O ======== int m_iFrameRate; int m_FrameSkip; - uint32_t m_temporalSubsampleRatio; + uint32_t m_temporalSubsampleRatio; int m_sourceWidth; int m_sourceHeight; Window m_conformanceWindow; int m_sourcePadding[2]; int m_framesToBeEncoded; + int m_firstValidFrame; + int m_lastValidFrame; + double m_adLambdaModifier[ MAX_TLAYER ]; std::vector<double> m_adIntraLambdaModifier; double m_dIntraQpFactor; ///< Intra Q Factor. If negative, use a default equation: 0.57*(1.0 - Clip3( 0.0, 0.5, 0.05*(double)(isField ? (GopSize-1)/2 : GopSize-1) )) @@ -527,6 +530,7 @@ protected: bool m_gopBasedTemporalFilterEnabled; bool m_noPicPartitionFlag; ///< no picture partitioning flag (single tile, single slice) bool m_mixedLossyLossless; ///< enable mixed lossy/lossless coding + std::vector<uint16_t> m_sliceLosslessArray; ///< Slice lossless array std::vector<uint32_t> m_tileColumnWidth; ///< tile column widths in units of CTUs (last column width will be repeated uniformly to cover any remaining picture width) std::vector<uint32_t> m_tileRowHeight; ///< tile row heights in units of CTUs (last row height will be repeated uniformly to cover any remaining picture height) @@ -1049,6 +1053,12 @@ public: void setFramesToBeEncoded ( int i ) { m_framesToBeEncoded = i; } + void setValidFrames(const int first, const int last) + { + m_firstValidFrame = first; + m_lastValidFrame = last; + } + bool getPrintMSEBasedSequencePSNR () const { return m_printMSEBasedSequencePSNR; } void setPrintMSEBasedSequencePSNR (bool value) { m_printMSEBasedSequencePSNR = value; } @@ -1575,8 +1585,9 @@ public: bool getFastUDIUseMPMEnabled () { return m_bFastUDIUseMPMEnabled; } bool getFastMEForGenBLowDelayEnabled () { return m_bFastMEForGenBLowDelayEnabled; } bool getUseBLambdaForNonKeyLowDelayPictures () { return m_bUseBLambdaForNonKeyLowDelayPictures; } - void setGopBasedTemporalFilterEnabled(bool flag) { m_gopBasedTemporalFilterEnabled = flag; } - bool getGopBasedTemporalFilterEnabled() { return m_gopBasedTemporalFilterEnabled; } + + void setGopBasedTemporalFilterEnabled(const bool b) { m_gopBasedTemporalFilterEnabled = b; } + bool getGopBasedTemporalFilterEnabled() const { return m_gopBasedTemporalFilterEnabled; } bool getUseReconBasedCrossCPredictionEstimate () const { return m_reconBasedCrossCPredictionEstimate; } void setUseReconBasedCrossCPredictionEstimate (const bool value) { m_reconBasedCrossCPredictionEstimate = value; } diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp index e887671efd3e8c219618bb9e357b24a4309d640e..34115ba9c18aabbd3420afbd3e5921cf55ccfc73 100644 --- a/source/Lib/EncoderLib/EncLib.cpp +++ b/source/Lib/EncoderLib/EncLib.cpp @@ -364,7 +364,9 @@ void EncLib::init(AUWriterIf *auWriterIf) if (getUseCompositeRef()) { Picture *picBg = new Picture; - picBg->create( sps0.getChromaFormatIdc(), Size( pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples() ), sps0.getMaxCUWidth(), sps0.getMaxCUWidth() + 16, false, m_layerId, m_gopBasedTemporalFilterEnabled ); + picBg->create(sps0.getChromaFormatIdc(), Size(pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples()), + sps0.getMaxCUWidth(), sps0.getMaxCUWidth() + 16, false, m_layerId, + getGopBasedTemporalFilterEnabled()); picBg->getRecoBuf().fill(0); #if GDR_ENABLED PicHeader *picHeader = new PicHeader(); @@ -377,7 +379,9 @@ void EncLib::init(AUWriterIf *auWriterIf) picBg->createSpliceIdx(pps0.pcv->sizeInCtus); m_cGOPEncoder.setPicBg(picBg); Picture *picOrig = new Picture; - picOrig->create( sps0.getChromaFormatIdc(), Size( pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples() ), sps0.getMaxCUWidth(), sps0.getMaxCUWidth() + 16, false, m_layerId, m_gopBasedTemporalFilterEnabled ); + picOrig->create(sps0.getChromaFormatIdc(), Size(pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples()), + sps0.getMaxCUWidth(), sps0.getMaxCUWidth() + 16, false, m_layerId, + getGopBasedTemporalFilterEnabled()); picOrig->getOrigBuf().fill(0); m_cGOPEncoder.setPicOrig(picOrig); } @@ -567,7 +571,7 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYu pcPicCurr->M_BUFS( 0, PIC_TRUE_ORIGINAL_INPUT ).getBuf( COMPONENT_Cb ).copyFrom( cPicYuvTrueOrg->getBuf( COMPONENT_Cb ) ); pcPicCurr->M_BUFS( 0, PIC_TRUE_ORIGINAL_INPUT ).getBuf( COMPONENT_Cr ).copyFrom( cPicYuvTrueOrg->getBuf( COMPONENT_Cr ) ); - if(m_gopBasedTemporalFilterEnabled) + if (getGopBasedTemporalFilterEnabled()) { pcPicCurr->M_BUFS( 0, PIC_FILTERED_ORIGINAL_INPUT ).getBuf( COMPONENT_Y ).copyFrom( pcPicYuvFilteredOrg->getBuf( COMPONENT_Y ) ); pcPicCurr->M_BUFS( 0, PIC_FILTERED_ORIGINAL_INPUT ).getBuf( COMPONENT_Cb ).copyFrom( pcPicYuvFilteredOrg->getBuf( COMPONENT_Cb ) ); @@ -593,7 +597,7 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYu pSPS->getHorCollocatedChromaFlag(), pSPS->getVerCollocatedChromaFlag() ); Picture::rescalePicture( scalingRatio, *cPicYuvTrueOrg, refPPS->getScalingWindow(), pcPicCurr->getTrueOrigBuf(), pPPS->getScalingWindow(), chromaFormatIDC, pSPS->getBitDepths(), true, true, pSPS->getHorCollocatedChromaFlag(), pSPS->getVerCollocatedChromaFlag() ); - if(m_gopBasedTemporalFilterEnabled) + if (getGopBasedTemporalFilterEnabled()) { Picture::rescalePicture( scalingRatio, *pcPicYuvFilteredOrg, refPPS->getScalingWindow(), pcPicCurr->getFilteredOrigBuf(), pPPS->getScalingWindow(), chromaFormatIDC, pSPS->getBitDepths(), true, true, pSPS->getHorCollocatedChromaFlag(), pSPS->getVerCollocatedChromaFlag() ); @@ -603,7 +607,7 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYu { pcPicCurr->M_BUFS( 0, PIC_ORIGINAL ).swap( *pcPicYuvOrg ); pcPicCurr->M_BUFS( 0, PIC_TRUE_ORIGINAL ).swap( *cPicYuvTrueOrg ); - if(m_gopBasedTemporalFilterEnabled) + if (getGopBasedTemporalFilterEnabled()) { pcPicCurr->M_BUFS( 0, PIC_FILTERED_ORIGINAL ).swap( *pcPicYuvFilteredOrg ); } @@ -746,7 +750,7 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* pcPicY compBuf.width, compBuf.height, isTopField); - if(m_gopBasedTemporalFilterEnabled) + if (getGopBasedTemporalFilterEnabled()) { compBuf = pcPicYuvFilteredOrg->get( compID ); separateFields( compBuf.buf, @@ -892,15 +896,14 @@ void EncLib::xGetNewPicBuffer ( std::list<PelUnitBuf*>& rcListPicYuvRecOut, Pict if (rpcPic==0) { rpcPic = new Picture; - rpcPic->create( sps.getChromaFormatIdc(), Size( pps.getPicWidthInLumaSamples(), pps.getPicHeightInLumaSamples() ), sps.getMaxCUWidth(), sps.getMaxCUWidth() + 16, false, m_layerId, m_gopBasedTemporalFilterEnabled - , m_fgcSEIAnalysisEnabled - ); + rpcPic->create(sps.getChromaFormatIdc(), Size(pps.getPicWidthInLumaSamples(), pps.getPicHeightInLumaSamples()), + sps.getMaxCUWidth(), sps.getMaxCUWidth() + 16, false, m_layerId, getGopBasedTemporalFilterEnabled(), m_fgcSEIAnalysisEnabled); if (m_resChangeInClvsEnabled) { const PPS &pps0 = *m_ppsMap.getPS(0); rpcPic->M_BUFS(0, PIC_ORIGINAL_INPUT).create(sps.getChromaFormatIdc(), Area(Position(), Size(pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples()))); rpcPic->M_BUFS(0, PIC_TRUE_ORIGINAL_INPUT).create(sps.getChromaFormatIdc(), Area(Position(), Size(pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples()))); - if(m_gopBasedTemporalFilterEnabled) + if (getGopBasedTemporalFilterEnabled()) { rpcPic->M_BUFS(0, PIC_FILTERED_ORIGINAL_INPUT).create(sps.getChromaFormatIdc(), Area(Position(), Size(pps0.getPicWidthInLumaSamples(), pps0.getPicHeightInLumaSamples()))); } diff --git a/source/Lib/EncoderLib/EncTemporalFilter.cpp b/source/Lib/EncoderLib/EncTemporalFilter.cpp index 867effa41fa62b2f32141cc9cd0fc34ef7788f4b..073a6230c0662a0194932a547894f09498e785f1 100644 --- a/source/Lib/EncoderLib/EncTemporalFilter.cpp +++ b/source/Lib/EncoderLib/EncTemporalFilter.cpp @@ -43,7 +43,6 @@ // Constructor / destructor / initialization / destroy // ==================================================================================================================== -const int EncTemporalFilter::m_range = 4; const double EncTemporalFilter::m_chromaFactor = 0.55; const double EncTemporalFilter::m_sigmaMultiplier = 9.0; const double EncTemporalFilter::m_sigmaZeroPoint = 10.0; @@ -69,12 +68,11 @@ const int EncTemporalFilter::m_interpolationFilter[16][8] = { 0, 0, -2, 4, 64, -3, 1, 0 } //15-->--> }; -const double EncTemporalFilter::m_refStrengths[3][4] = -{ // abs(POC offset) +const double EncTemporalFilter::m_refStrengths[2][4] = { + // abs(POC offset) // 1, 2 3 4 - {0.85, 0.57, 0.41, 0.33}, // m_range * 2 - {1.13, 0.97, 0.81, 0.57}, // m_range - {0.30, 0.30, 0.30, 0.30} // otherwise + { 0.85, 0.57, 0.41, 0.33 }, // random access + { 1.13, 0.97, 0.81, 0.57 }, // low delay }; EncTemporalFilter::EncTemporalFilter() : @@ -87,20 +85,13 @@ EncTemporalFilter::EncTemporalFilter() : m_inputColourSpaceConvert(NUMBER_INPUT_COLOUR_SPACE_CONVERSIONS) {} -void EncTemporalFilter::init(const int frameSkip, - const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], - const int msbExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], - const int internalBitDepth[MAX_NUM_CHANNEL_TYPE], - const int width, - const int height, - const int *pad, - const bool rec709, - const std::string &filename, - const ChromaFormat inputChromaFormatIDC, - const InputColourSpaceConversion colorSpaceConv, - const int qp, - const std::map<int, double> &temporalFilterStrengths, - const bool gopBasedTemporalFilterFutureReference) +void EncTemporalFilter::init(const int frameSkip, const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], + const int msbExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], + const int internalBitDepth[MAX_NUM_CHANNEL_TYPE], const int width, const int height, + const int *pad, const bool rec709, const std::string &filename, + const ChromaFormat inputChromaFormatIDC, const InputColourSpaceConversion colorSpaceConv, + const int qp, const std::map<int, double> &temporalFilterStrengths, const int pastRefs, + const int futureRefs, const int firstValidFrame, const int lastValidFrame) { m_FrameSkip = frameSkip; for (int i = 0; i < MAX_NUM_CHANNEL_TYPE; i++) @@ -123,7 +114,11 @@ void EncTemporalFilter::init(const int frameSkip, m_area = Area(0, 0, width, height); m_QP = qp; m_temporalFilterStrengths = temporalFilterStrengths; - m_gopBasedTemporalFilterFutureReference = gopBasedTemporalFilterFutureReference; + + m_pastRefs = pastRefs; + m_futureRefs = futureRefs; + m_firstValidFrame = firstValidFrame; + m_lastValidFrame = lastValidFrame; } // ==================================================================================================================== @@ -148,21 +143,15 @@ bool EncTemporalFilter::filter(PelStorage *orgPic, int receivedPoc) if (isFilterThisFrame) { - int offset = m_FrameSkip; + const int currentFilePoc = receivedPoc + m_FrameSkip; + const int firstFrame = std::max(currentFilePoc - m_pastRefs, m_firstValidFrame); + const int lastFrame = std::min(currentFilePoc + m_futureRefs, m_lastValidFrame); VideoIOYuv yuvFrames; yuvFrames.open(m_inputFileName, false, m_inputBitDepth, m_MSBExtendedBitDepth, m_internalBitDepth); - yuvFrames.skipFrames(std::max(offset + receivedPoc - m_range, 0), m_sourceWidth - m_pad[0], m_sourceHeight - m_pad[1], m_chromaFormatIDC); + yuvFrames.skipFrames(firstFrame, m_sourceWidth - m_pad[0], m_sourceHeight - m_pad[1], m_chromaFormatIDC); std::deque<TemporalFilterSourcePicInfo> srcFrameInfo; - int firstFrame = receivedPoc + offset - m_range; - int lastFrame = receivedPoc + offset + m_range; - if (!m_gopBasedTemporalFilterFutureReference) - { - lastFrame = receivedPoc + offset - 1; - } - int origOffset = -m_range; - // subsample original picture so it only needs to be done once PelStorage origPadded; @@ -179,15 +168,9 @@ bool EncTemporalFilter::filter(PelStorage *orgPic, int receivedPoc) // determine motion vectors for (int poc = firstFrame; poc <= lastFrame; poc++) { - if (poc < 0) - { - origOffset++; - continue; // frame not available - } - else if (poc == offset + receivedPoc) + if (poc == currentFilePoc) { // hop over frame that will be filtered yuvFrames.skipFrames(1, m_sourceWidth - m_pad[0], m_sourceHeight - m_pad[1], m_chromaFormatIDC); - origOffset++; continue; } srcFrameInfo.push_back(TemporalFilterSourcePicInfo()); @@ -198,14 +181,16 @@ bool EncTemporalFilter::filter(PelStorage *orgPic, int receivedPoc) dummyPicBufferTO.create(m_chromaFormatIDC, m_area, 0, m_padding); if (!yuvFrames.read(srcPic.picBuffer, dummyPicBufferTO, m_inputColourSpaceConvert, m_pad, m_chromaFormatIDC, m_clipInputVideoToRec709Range)) { - return false; // eof or read fail + // eof or read fail + srcPic.picBuffer.destroy(); + srcFrameInfo.pop_back(); + break; } srcPic.picBuffer.extendBorderPel(m_padding, m_padding); srcPic.mvs.allocate(m_sourceWidth / 4, m_sourceHeight / 4); motionEstimation(srcPic.mvs, origPadded, srcPic.picBuffer, origSubsampled2, origSubsampled4); - srcPic.origOffset = origOffset; - origOffset++; + srcPic.origOffset = poc - currentFilePoc; } // filter @@ -608,15 +593,7 @@ void EncTemporalFilter::bilateralFilter(const PelStorage &orgPic, applyMotion(srcFrameInfo[i].mvs, srcFrameInfo[i].picBuffer, correctedPics[i]); } - int refStrengthRow = 2; - if (numRefs == m_range * 2) - { - refStrengthRow = 0; - } - else if (numRefs == m_range) - { - refStrengthRow = 1; - } + const int refStrengthRow = m_futureRefs > 0 ? 0 : 1; const double lumaSigmaSq = (m_QP - m_sigmaZeroPoint) * (m_QP - m_sigmaZeroPoint) * m_sigmaMultiplier; const double chromaSigmaSq = 30 * 30; @@ -654,27 +631,36 @@ void EncTemporalFilter::bilateralFilter(const PelStorage &orgPic, for (int i = 0; i < numRefs; i++) { double variance = 0, diffsum = 0; - for (int y1 = 0; y1 < blockSizeY - 1; y1++) + const ptrdiff_t refStride = correctedPics[i].bufs[c].stride; + const Pel * refPel = correctedPics[i].bufs[c].buf + y * refStride + x; + for (int y1 = 0; y1 < blockSizeY; y1++) { - for (int x1 = 0; x1 < blockSizeX - 1; x1++) + for (int x1 = 0; x1 < blockSizeX; x1++) { - int pix = *(srcPel + x1); - int pixR = *(srcPel + x1 + 1); - int pixD = *(srcPel + x1 + srcStride); - int ref = *(correctedPics[i].bufs[c].buf + ((y + y1) * correctedPics[i].bufs[c].stride + x + x1)); - int refR = *(correctedPics[i].bufs[c].buf + ((y + y1) * correctedPics[i].bufs[c].stride + x + x1 + 1)); - int refD = *(correctedPics[i].bufs[c].buf + ((y + y1 + 1) * correctedPics[i].bufs[c].stride + x + x1)); - - int diff = pix - ref; - int diffR = pixR - refR; - int diffD = pixD - refD; - + const Pel pix = *(srcPel + srcStride * y1 + x1); + const Pel ref = *(refPel + refStride * y1 + x1); + const int diff = pix - ref; variance += diff * diff; - diffsum += (diffR - diff) * (diffR - diff); - diffsum += (diffD - diff) * (diffD - diff); + if (x1 != blockSizeX - 1) + { + const Pel pixR = *(srcPel + srcStride * y1 + x1 + 1); + const Pel refR = *(refPel + refStride * y1 + x1 + 1); + const int diffR = pixR - refR; + diffsum += (diffR - diff) * (diffR - diff); + } + if (y1 != blockSizeY - 1) + { + const Pel pixD = *(srcPel + srcStride * y1 + x1 + srcStride); + const Pel refD = *(refPel + refStride * y1 + x1 + refStride); + const int diffD = pixD - refD; + diffsum += (diffD - diff) * (diffD - diff); + } } } - srcFrameInfo[i].mvs.get(x / blockSizeX, y / blockSizeY).noise = (int) round((300 * variance + 50) / (10 * diffsum + 50)); + const int cntV = blockSizeX * blockSizeY; + const int cntD = 2 * cntV - blockSizeX - blockSizeY; + srcFrameInfo[i].mvs.get(x / blockSizeX, y / blockSizeY).noise = + (int) round((15.0 * cntD / cntV * variance + 5.0) / (diffsum + 5.0)); } } double minError = 9999999; diff --git a/source/Lib/EncoderLib/EncTemporalFilter.h b/source/Lib/EncoderLib/EncTemporalFilter.h index 42f2f88c8663d2038a9fe87a1c3a05f7d8ed6b1f..fe095e6048658538605f69a12243fd7864236862 100644 --- a/source/Lib/EncoderLib/EncTemporalFilter.h +++ b/source/Lib/EncoderLib/EncTemporalFilter.h @@ -104,33 +104,24 @@ public: EncTemporalFilter(); ~EncTemporalFilter() {} - void init(const int frameSkip, - const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], - const int msbExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], - const int internalBitDepth[MAX_NUM_CHANNEL_TYPE], - const int width, - const int height, - const int *pad, - const bool rec709, - const std::string &filename, - const ChromaFormat inputChroma, - const InputColourSpaceConversion colorSpaceConv, - const int qp, - const std::map<int, double> &temporalFilterStrengths, - const bool gopBasedTemporalFilterFutureReference); + void init(const int frameSkip, const int inputBitDepth[MAX_NUM_CHANNEL_TYPE], + const int msbExtendedBitDepth[MAX_NUM_CHANNEL_TYPE], const int internalBitDepth[MAX_NUM_CHANNEL_TYPE], + const int width, const int height, const int *pad, const bool rec709, const std::string &filename, + const ChromaFormat inputChroma, const InputColourSpaceConversion colorSpaceConv, const int qp, + const std::map<int, double> &temporalFilterStrengths, const int pastRefs, const int futureRefs, + const int firstValidFrame, const int lastValidFrame); bool filter(PelStorage *orgPic, int frame); private: // Private static member variables - static const int m_range; static const double m_chromaFactor; static const double m_sigmaMultiplier; static const double m_sigmaZeroPoint; static const int m_motionVectorFactor; static const int m_padding; static const int m_interpolationFilter[16][8]; - static const double m_refStrengths[3][4]; + static const double m_refStrengths[2][4]; // Private member variables int m_FrameSkip; @@ -147,7 +138,11 @@ private: bool m_clipInputVideoToRec709Range; InputColourSpaceConversion m_inputColourSpaceConvert; Area m_area; - bool m_gopBasedTemporalFilterFutureReference; + + int m_pastRefs; + int m_futureRefs; + int m_firstValidFrame; + int m_lastValidFrame; // Private functions void subsampleLuma(const PelStorage &input, PelStorage &output, const int factor = 2) const;