diff --git a/doc/software-manual.tex b/doc/software-manual.tex index deb0fef18383d24812c20448e0d2680ad415a0e1..1b2fcd8f2895a370e8a08508480009e046a21d42 100644 --- a/doc/software-manual.tex +++ b/doc/software-manual.tex @@ -844,9 +844,10 @@ two for 4:2:0). \Option{FrameRate (-fr)} & %\ShortOption{-fr} & \Default{0} & -Specifies the frame rate of the input video. +Specifies the frame rate of the input video. A frame rate may be specified by two numbers +such as 30000:1001 to define a non-integer value (e.g., 29.97). -Note: This option only affects the reported bit rates. +Note: This option affects the reported bit rates. \\ \Option{FrameSkip (-fs)} & diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp index 753c36bf608cedd935fa9e95203d399b261ccd23..c00f4c8a79dccff841c58d44a324c59cea7ffaa2 100644 --- a/source/App/DecoderApp/DecApp.cpp +++ b/source/App/DecoderApp/DecApp.cpp @@ -36,6 +36,7 @@ */ #include <list> +#include <numeric> #include <vector> #include <stdio.h> #include <fcntl.h> @@ -51,13 +52,6 @@ //! \ingroup DecoderApp //! \{ -static int calcGcd(int a, int b) -{ - // assume that a >= b - return b == 0 ? a : calcGcd(b, a % b); -} - - // ==================================================================================================================== // Constructor / destructor / initialization / destroy // ==================================================================================================================== @@ -461,9 +455,8 @@ uint32_t DecApp::decode() if (isY4mFileExt(reconFileName)) { const auto sps = pcListPic->front()->cs->sps; - int frameRate = 50; - int frameScale = 1; - if(sps->getGeneralHrdParametersPresentFlag()) + Fraction frameRate = { 50, 1 }; + if (sps->getGeneralHrdParametersPresentFlag()) { const auto hrd = sps->getGeneralHrdParameters(); const auto olsHrdParam = sps->getOlsHrdParameters()[sps->getMaxTLayers() - 1]; @@ -474,13 +467,14 @@ uint32_t DecApp::decode() } else { - msg(WARNING, "\nWarning: No fixed picture rate info is found in the bitstream, best guess is used.\n"); + msg(WARNING, + "\nWarning: No fixed picture rate info is found in the bitstream, best guess is used.\n"); } - frameRate = hrd->getTimeScale() * elementDurationInTc; - frameScale = hrd->getNumUnitsInTick(); - int gcd = calcGcd(std::max(frameRate, frameScale), std::min(frameRate, frameScale)); - frameRate /= gcd; - frameScale /= gcd; + frameRate.num = hrd->getTimeScale() * elementDurationInTc; + frameRate.den = hrd->getNumUnitsInTick(); + const int gcd = std::gcd(frameRate.num, frameRate.den); + frameRate.num /= gcd; + frameRate.den /= gcd; } else { @@ -492,9 +486,8 @@ uint32_t DecApp::decode() const auto sy = SPS::getWinUnitY(sps->getChromaFormatIdc()); const int picWidth = pps->getPicWidthInLumaSamples() - (confWindow.getWindowLeftOffset() + confWindow.getWindowRightOffset()) * sx; const int picHeight = pps->getPicHeightInLumaSamples() - (confWindow.getWindowTopOffset() + confWindow.getWindowBottomOffset()) * sy; - m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].setOutputY4mInfo(picWidth, picHeight, frameRate, frameScale, - layerOutputBitDepth[ChannelType::LUMA], - sps->getChromaFormatIdc()); + m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].setOutputY4mInfo( + picWidth, picHeight, frameRate, layerOutputBitDepth[ChannelType::LUMA], sps->getChromaFormatIdc()); } m_cVideoIOYuvReconFile[nalu.m_nuhLayerId].open(reconFileName, true, layerOutputBitDepth, layerOutputBitDepth, bitDepths); // write mode diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp index 17120c27c1526770a2d3c0f8446ecfaab72bbd83..1019f1e77af7fb6e5346be6118adda756c0cca34 100644 --- a/source/App/EncoderApp/EncApp.cpp +++ b/source/App/EncoderApp/EncApp.cpp @@ -1298,15 +1298,18 @@ void EncApp::xInitLibCfg( int layerIdx ) m_cEncLib.setDepQuantEnabledFlag ( m_depQuantEnabledFlag); m_cEncLib.setSignDataHidingEnabledFlag ( m_signDataHidingEnabledFlag); m_cEncLib.setUseRateCtrl ( m_RCEnableRateControl ); - m_cEncLib.setTargetBitrate ( m_RCTargetBitrate ); - m_cEncLib.setKeepHierBit ( m_RCKeepHierarchicalBit ); - m_cEncLib.setLCULevelRC ( m_RCLCULevelRC ); - m_cEncLib.setUseLCUSeparateModel ( m_RCUseLCUSeparateModel ); - m_cEncLib.setInitialQP ( m_RCInitialQP ); - m_cEncLib.setForceIntraQP ( m_RCForceIntraQP ); - m_cEncLib.setCpbSaturationEnabled ( m_RCCpbSaturationEnabled ); - m_cEncLib.setCpbSize ( m_RCCpbSize ); - m_cEncLib.setInitialCpbFullness ( m_RCInitialCpbFullness ); + if (m_RCEnableRateControl) + { + m_cEncLib.setTargetBitrate(m_RCTargetBitrate); + m_cEncLib.setKeepHierBit(m_RCKeepHierarchicalBit); + m_cEncLib.setLCULevelRC(m_RCLCULevelRC); + m_cEncLib.setUseLCUSeparateModel(m_RCUseLCUSeparateModel); + m_cEncLib.setInitialQP(m_RCInitialQP); + m_cEncLib.setForceIntraQP(m_RCForceIntraQP); + m_cEncLib.setCpbSaturationEnabled(m_RCCpbSaturationEnabled); + m_cEncLib.setCpbSize(m_RCCpbSize); + m_cEncLib.setInitialCpbFullness(m_RCInitialCpbFullness); + } m_cEncLib.setCostMode ( m_costMode ); m_cEncLib.setTSRCdisableLL ( m_TSRCdisableLL ); m_cEncLib.setUseRecalculateQPAccordingToLambda ( m_recalculateQPAccordingToLambda ); @@ -1488,7 +1491,7 @@ void EncApp::xCreateLib( std::list<PelUnitBuf*>& recBufList, const int layerId ) const auto sx = SPS::getWinUnitX(m_chromaFormatIdc); const auto sy = SPS::getWinUnitY(m_chromaFormatIdc); m_cVideoIOYuvReconFile.setOutputY4mInfo(m_sourceWidth - (m_confWinLeft + m_confWinRight) * sx, - m_sourceHeight - (m_confWinTop + m_confWinBottom) * sy, m_frameRate, 1, + m_sourceHeight - (m_confWinTop + m_confWinBottom) * sy, m_frameRate, m_internalBitDepth[ChannelType::LUMA], m_chromaFormatIdc); } m_cVideoIOYuvReconFile.open( reconFileName, true, m_outputBitDepth, m_outputBitDepth, m_internalBitDepth ); // write mode @@ -1586,6 +1589,12 @@ void EncApp::createLib( const int layerIdx ) } } + if (isY4mFileExt(m_inputFileName)) + { + // Force signalling of HRD parameters to carry frame rate information + m_hrdParametersPresentFlag = true; + } + // initialize internal class & member variables and VPS xInitLibCfg( layerIdx ); const int layerId = m_cEncLib.getVPS() == nullptr ? 0 : m_cEncLib.getVPS()->getLayerId( layerIdx ); @@ -1980,7 +1989,7 @@ void EncApp::rateStatsAccum(const AccessUnit& au, const std::vector<uint32_t>& a void EncApp::printRateSummary() { - double time = (double) m_frameRcvd / m_frameRate * m_temporalSubsampleRatio; + double time = (double) m_frameRcvd / m_frameRate.getFloatVal() * m_temporalSubsampleRatio; msg( DETAILS,"Bytes written to file: %u (%.3f kbps)\n", m_totalBytes, 0.008 * m_totalBytes / time ); if (m_summaryVerboseness > 0) { diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp index 8b41b7a4a6a6b129b3b1c9e277fd9d2fe14d78b8..fa33f47be055df6f01523ea06c041fd97655efed 100644 --- a/source/App/EncoderApp/EncAppCfg.cpp +++ b/source/App/EncoderApp/EncAppCfg.cpp @@ -768,6 +768,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) #if ENABLE_SIMD_OPT std::string ignore; #endif + std::string frameRate; bool sdr = false; @@ -826,7 +827,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) ("ConfWinBottom", m_confWinBottom, 0, "Bottom offset for window conformance mode 3") ("AccessUnitDelimiter", m_AccessUnitDelimiter, false, "Enable Access Unit Delimiter NALUs") ("EnablePictureHeaderInSliceHeader", m_enablePictureHeaderInSliceHeader, true, "Enable Picture Header in Slice Header") - ("FrameRate,-fr", m_frameRate, 0, "Frame rate") + ("FrameRate,-fr", frameRate, std::to_string(0), "Frame rate") ("FrameSkip,-fs", m_frameSkip, 0u, "Number of frames to skip at start of input YUV") ("TemporalSubsampleRatio,-ts", m_temporalSubsampleRatio, 1u, "Temporal sub-sample ratio when reading input YUV") ("FramesToBeEncoded,f", m_framesToBeEncoded, 0, "Number of frames to be encoded (default=all)") @@ -1961,6 +1962,11 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) m_rprRASLtoolSwitch = false; } + const size_t columnPos = frameRate.find_first_of(':'); + + m_frameRate.num = std::stoi(frameRate.substr(0, columnPos)); + m_frameRate.den = columnPos == frameRate.length() ? 1 : std::stoi(frameRate.substr(columnPos + 1)); + if( m_fractionOfFrames != 1.0 ) { m_framesToBeEncoded = int( m_framesToBeEncoded * m_fractionOfFrames ); @@ -1968,7 +1974,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) if (m_resChangeInClvsEnabled && !m_switchPocPeriod) { - m_switchPocPeriod = m_frameRate / 2 / m_gopSize * m_gopSize; + m_switchPocPeriod = m_frameRate.getIntValRound() / 2 / m_gopSize * m_gopSize; } //Check the given value of intra period and decoding refresh type. If intra period is -1, set decoding refresh type to be equal to 0. And vice versa @@ -2032,12 +2038,12 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) if (m_gdrPeriod < 0) { - m_gdrPeriod = m_frameRate * 2; + m_gdrPeriod = m_frameRate.getIntValRound() * 2; } if (m_gdrInterval < 0) { - m_gdrInterval = m_frameRate; + m_gdrInterval = m_frameRate.getIntValRound(); } if (m_gdrPocStart < 0) @@ -2047,8 +2053,8 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) if (m_intraPeriod == -1) { - m_frameRate = (m_frameRate == 0) ? 30 : m_frameRate; - if (m_gdrPocStart % m_frameRate != 0) + m_frameRate = (m_frameRate.num == 0) ? Fraction{ 30, 1 } : m_frameRate; + if (m_gdrPocStart % m_frameRate.getIntValRound() != 0) { m_intraPeriod = -1; } @@ -2466,7 +2472,10 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) if (isY4mFileExt(m_inputFileName)) { - int width = 0, height = 0, frameRate = 0, inputBitDepth = 0; + int width = 0; + int height = 0; + Fraction frameRate; + int inputBitDepth = 0; ChromaFormat chromaFormat = ChromaFormat::_420; VideoIOYuv inputFile; inputFile.parseY4mFileHeader(m_inputFileName, width, height, frameRate, inputBitDepth, chromaFormat); @@ -2848,7 +2857,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) } if (m_rprSwitchingTime != 0.0) { - int segmentSize = 8 * int(((double)m_frameRate * m_rprSwitchingTime + 4) / 8); + const int segmentSize = 8 * int(m_frameRate.getFloatVal() * m_rprSwitchingTime / 8 + 0.5); m_rprSwitchingSegmentSize = segmentSize; } } @@ -3031,7 +3040,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) if (m_fgcSEIAnalysisEnabled && m_fgcSEITemporalFilterStrengths.empty()) { // By default: in random-acces = filter RAPs, in all-intra = filter every frame, otherwise = filter every 2s - int filteredFrame = m_intraPeriod < 1 ? 2 * m_frameRate : m_intraPeriod; + int filteredFrame = m_intraPeriod < 1 ? 2 * m_frameRate.getIntValRound() : m_intraPeriod; m_fgcSEITemporalFilterStrengths[filteredFrame] = 1.5; } uint32_t numModelCtr; @@ -3317,9 +3326,9 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) } } m_reshapeCW.binCW.resize(3); - m_reshapeCW.rspFps = m_frameRate; + m_reshapeCW.rspFps = m_frameRate.getIntValRound(); m_reshapeCW.rspPicSize = m_sourceWidth*m_sourceHeight; - m_reshapeCW.rspFpsToIp = std::max(16, 16 * (int) (round((double) m_frameRate / 16.0))); + m_reshapeCW.rspFpsToIp = std::max(16, 16 * (int) (round(m_frameRate.getFloatVal() / 16.0))); m_reshapeCW.rspBaseQP = m_iQP; m_reshapeCW.updateCtrl = m_updateCtrl; m_reshapeCW.adpOption = m_adpOption; @@ -3399,7 +3408,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) (blending_ratio * m_siiSEISubLayerNumUnitsInSI[siiMaxSubLayersMinus1])) { m_ShutterFilterEnable = true; - double fpsHFR = (double) m_frameRate; + double fpsHFR = m_frameRate.getFloatVal(); int32_t i; bool checkEqualValuesOfSFR = true; bool checkSubLayerSI = false; @@ -3767,7 +3776,7 @@ bool EncAppCfg::xCheckParameter() std::string sTempIPCSC="InputColourSpaceConvert must be empty, "+getListOfColourSpaceConverts(true); xConfirmPara( m_inputColourSpaceConvert >= NUMBER_INPUT_COLOUR_SPACE_CONVERSIONS, sTempIPCSC.c_str() ); xConfirmPara(m_inputChromaFormatIDC >= ChromaFormat::NUM, "InputChromaFormatIDC must be either 400, 420, 422 or 444"); - xConfirmPara(m_frameRate <= 0, "Frame rate must be more than 1"); + xConfirmPara(m_frameRate.getFloatVal() <= 0, "Frame rate cannot be 0 or less"); xConfirmPara( m_framesToBeEncoded <= 0, "Total Number Of Frames encoded must be more than 0" ); xConfirmPara( m_framesToBeEncoded < m_switchPOC, "debug POC out of range" ); @@ -5090,9 +5099,9 @@ void EncAppCfg::xPrintParameter() } #endif msg(DETAILS, "Real Format : %dx%d %gHz\n", m_sourceWidth - m_confWinLeft - m_confWinRight, - m_sourceHeight - m_confWinTop - m_confWinBottom, (double) m_frameRate / m_temporalSubsampleRatio); + m_sourceHeight - m_confWinTop - m_confWinBottom, m_frameRate.getFloatVal() / m_temporalSubsampleRatio); msg(DETAILS, "Internal Format : %dx%d %gHz\n", m_sourceWidth, m_sourceHeight, - (double) m_frameRate / m_temporalSubsampleRatio); + m_frameRate.getFloatVal() / m_temporalSubsampleRatio); msg( DETAILS, "Sequence PSNR output : %s\n", ( m_printMSEBasedSequencePSNR ? "Linear average, MSE-based" : "Linear average only" ) ); msg( DETAILS, "Hexadecimal PSNR output : %s\n", ( m_printHexPsnr ? "Enabled" : "Disabled" ) ); msg( DETAILS, "Sequence MSE output : %s\n", ( m_printSequenceMSE ? "Enabled" : "Disabled" ) ); diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h index 64029f899451a34311c0add5ad77f075495ba490..4985ac7d41ae67d7b3190d5edf89b6e8557b379b 100644 --- a/source/App/EncoderApp/EncAppCfg.h +++ b/source/App/EncoderApp/EncAppCfg.h @@ -93,7 +93,7 @@ protected: 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) )) // source specification - int m_frameRate; ///< source frame-rates (Hz) + Fraction m_frameRate; uint32_t m_frameSkip; ///< number of skipped frames from the beginning uint32_t m_temporalSubsampleRatio; ///< temporal subsample ratio, 2 means code every two frames int m_sourceWidth; ///< source width in pixel diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h index e547758b1c91699f05c2095ad88bc30c4d639223..3e7cf0d1cdb0b08819a9ae08ecd24add3c37a8b8 100644 --- a/source/Lib/CommonLib/TypeDef.h +++ b/source/Lib/CommonLib/TypeDef.h @@ -777,6 +777,18 @@ enum POST_FILTER_MODE URI = 1 }; +struct Fraction +{ + int num = 0; + int den = 1; + + int getIntValRound() const { return (num + den / 2) / den; } + double getFloatVal() const { return static_cast<double>(num) / den; } +}; + +inline bool operator==(const Fraction& a, const Fraction& b) { return a.num == b.num && a.den == b.den; } +inline bool operator!=(const Fraction& a, const Fraction& b) { return !(a == b); } + #define NUM_SAO_BO_CLASSES_LOG2 5 #define NUM_SAO_BO_CLASSES (1<<NUM_SAO_BO_CLASSES_LOG2) diff --git a/source/Lib/EncoderLib/Analyze.h b/source/Lib/EncoderLib/Analyze.h index bf30f5664df5dcadc10f4cf97e9eda4578b97480..e0c0b7c04bec4261e13d1a2053dc49e2d8d0b64b 100644 --- a/source/Lib/EncoderLib/Analyze.h +++ b/source/Lib/EncoderLib/Analyze.h @@ -63,7 +63,7 @@ private: double m_dPSNRSum[MAX_NUM_COMPONENT]; double m_dAddBits; - double m_frameRate; + Fraction m_frameRate; double m_mseYuvFrame[MAX_NUM_COMPONENT]; // sum of MSEs double m_upscaledPSNR[MAX_NUM_COMPONENT]; double m_msssim[MAX_NUM_COMPONENT]; @@ -124,7 +124,7 @@ public: } #endif - void setFrameRate(double frameRate) { m_frameRate = frameRate; } + void setFrameRate(const Fraction& frameRate) { m_frameRate = frameRate; } void clear() { m_dAddBits = 0; @@ -204,7 +204,7 @@ public: return y; }; - double fps = m_frameRate; + double fps = m_frameRate.getFloatVal(); double scale = fps / 1000 / (double) m_picCount; double mseBasedSNR[MAX_NUM_COMPONENT]; @@ -327,7 +327,7 @@ public: { FILE* pFile = fopen (sFilename.c_str(), "at"); - double dFps = m_frameRate; + double dFps = m_frameRate.getFloatVal(); double dScale = dFps / 1000 / (double) m_picCount; switch (chFmt) { diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h index ece6e4a32adaf845e86c603a995cbfe9d480297b..017751786616a1243d9fd37b3d2b776e7821243a 100644 --- a/source/Lib/EncoderLib/EncCfg.h +++ b/source/Lib/EncoderLib/EncCfg.h @@ -155,7 +155,7 @@ class EncCfg { protected: //==== File I/O ======== - int m_frameRate; + Fraction m_frameRate; int m_frameSkip; uint32_t m_temporalSubsampleRatio; int m_sourceWidth; @@ -1182,7 +1182,7 @@ public: bool getNoReverseLastSigCoeffConstraintFlag() const { return m_noReverseLastSigCoeffConstraintFlag; } void setNoReverseLastSigCoeffConstraintFlag(bool val) { m_noReverseLastSigCoeffConstraintFlag = val; } - void setFrameRate(int i) { m_frameRate = i; } + void setFrameRate(const Fraction& fr) { m_frameRate = fr; } void setFrameSkip(uint32_t i) { m_frameSkip = i; } void setTemporalSubsampleRatio ( uint32_t i ) { m_temporalSubsampleRatio = i; } void setSourceWidth ( int i ) { m_sourceWidth = i; } @@ -1669,7 +1669,7 @@ public: #endif //====== Sequence ======== - int getFrameRate() const { return m_frameRate; } + const Fraction& getFrameRate() const { return m_frameRate; } uint32_t getFrameSkip() const { return m_frameSkip; } uint32_t getTemporalSubsampleRatio () const { return m_temporalSubsampleRatio; } int getSourceWidth () const { return m_sourceWidth; } diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp index 1ca8f4dffbc6267578742022058f167bdd1ffdb2..fc7b8db85942b223effa5a1c2f980a728497ce09 100644 --- a/source/Lib/EncoderLib/EncGOP.cpp +++ b/source/Lib/EncoderLib/EncGOP.cpp @@ -1487,9 +1487,9 @@ validateMinCrRequirements(const ProfileTierLevelFeatures &plt, std::size_t numBy { const uint32_t formatCapabilityFactorx1000 = plt.getProfileFeatures()->formatCapabilityFactorx1000; const uint64_t maxLumaSr = plt.getTierLevelFeatures()->maxLumaSr; - const uint32_t frameRate = pCfg->getFrameRate(); + const double frameRate = pCfg->getFrameRate().getFloatVal(); const double minCr = plt.getMinCr(); - const double denominator = (minCr * frameRate * 1000); + const double denominator = minCr * frameRate * 1000; if (denominator!=0) { const double threshold =(formatCapabilityFactorx1000 * maxLumaSr) / (denominator); @@ -1512,7 +1512,7 @@ validateMinCrRequirements(const ProfileTierLevelFeatures &plt, std::size_t numBy { const uint32_t formatCapabilityFactorx1000 = plt.getProfileFeatures()->formatCapabilityFactorx1000; const uint64_t maxLumaSr = plt.getTierLevelFeatures()->maxLumaSr; - const double denomx1000x256 = (256 * plt.getMinCr() * pCfg->getFrameRate() * 1000 * 256); + const double denomx1000x256 = 256 * plt.getMinCr() * pCfg->getFrameRate().getFloatVal() * 1000 * 256; for (int i = 0; i < seiSubpic.m_numRefLevels; i++) { @@ -2549,7 +2549,8 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l setNewestBgPOC(pocCurr); setLastLTRefPoc(pocCurr); } - else if (m_pcCfg->getUseCompositeRef() && getLastLTRefPoc() >= 0 && getEncodedLTRef()==false && !getPicBg()->getSpliceFull() && (pocCurr - getLastLTRefPoc()) > (m_pcCfg->getFrameRate() * 2)) + else if (m_pcCfg->getUseCompositeRef() && getLastLTRefPoc() >= 0 && getEncodedLTRef() == false + && !getPicBg()->getSpliceFull() && pocCurr - getLastLTRefPoc() > m_pcCfg->getFrameRate().getFloatVal() * 2) { setUseLTRef(false); setPrepareLTRef(false); @@ -3824,7 +3825,9 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l if (m_pcCfg->getFilmGrainAnalysisEnabled()) { - int filteredFrame = m_pcCfg->getIntraPeriod() < 1 ? 2 * m_pcCfg->getFrameRate() : m_pcCfg->getIntraPeriod(); + int filteredFrame = m_pcCfg->getIntraPeriod() < 1 + ? static_cast<int>(2 * m_pcCfg->getFrameRate().getFloatVal() + 0.5) + : m_pcCfg->getIntraPeriod(); bool readyToAnalyze = pcPic->getPOC() % filteredFrame ? false : true; // either it is mctf denoising or external source for film grain analysis. note: @@ -4330,8 +4333,9 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l seiGreenMetadataInfo->m_greenMetadataGranularityType = m_pcCfg->getSEIGreenMetadataGranularityType(); seiGreenMetadataInfo->m_greenMetadataExtendedRepresentation = m_pcCfg->getSEIGreenMetadataExtendedRepresentation(); int64_t codedFrames = m_featureCounter.iSlices + m_featureCounter.bSlices + m_featureCounter.pSlices; - int numberFrames = seiGreenMetadataInfo->m_numSeconds * m_pcCfg->getFrameRate(); - + int numberFrames = + static_cast<int>(seiGreenMetadataInfo->m_numSeconds * m_pcCfg->getFrameRate().getFloatVal() + 0.5); + if (seiGreenMetadataInfo->m_greenMetadataType == 0) { switch (m_pcCfg->getSEIGreenMetadataPeriodType()) // Period type @@ -4356,7 +4360,7 @@ void EncGOP::compressGOP(int pocLast, int numPicRcvd, PicList &rcListPic, std::l seiGreenMetadataInfo->m_numSeconds = m_pcCfg->getSEIGreenMetadataPeriodNumSeconds(); if( ((codedFrames% numberFrames) == 0) || (codedFrames == m_pcCfg->getFramesToBeEncoded())) { - seiGreenMetadataInfo->m_numSeconds = int(floor(double(codedFrames)/double(m_pcCfg->getFrameRate()))); + seiGreenMetadataInfo->m_numSeconds = int(floor(codedFrames / m_pcCfg->getFrameRate().getFloatVal())); xCalculateGreenComplexityMetrics(m_featureCounter, m_featureCounterReference, seiGreenMetadataInfo); m_seiEncoder.initSEIGreenMetadataInfo(seiGreenMetadataInfo, m_featureCounter, m_SEIGreenQualityMetrics,m_SEIGreenComplexityMetrics); leadingSeiMessages.push_back(seiGreenMetadataInfo); @@ -4524,7 +4528,9 @@ void EncGOP::printOutSummary(uint32_t numAllPicCoded, bool isField, const bool p CHECK(!(numAllPicCoded == m_gcAnalyzeAll.getNumPic()), "Unspecified error"); } - const double picRate = m_pcCfg->getFrameRate() * (isField ? 2.0 : 1.0) / m_pcCfg->getTemporalSubsampleRatio(); + Fraction picRate = m_pcCfg->getFrameRate(); + picRate.num *= isField ? 2 : 1; + picRate.den *= m_pcCfg->getTemporalSubsampleRatio(); m_gcAnalyzeAll.setFrameRate(picRate); m_gcAnalyzeI.setFrameRate(picRate); @@ -4607,7 +4613,9 @@ void EncGOP::printOutSummary(uint32_t numAllPicCoded, bool isField, const bool p if(isField) { //-- interlaced summary - m_gcAnalyzeAllField.setFrameRate(m_pcCfg->getFrameRate() / (double) m_pcCfg->getTemporalSubsampleRatio()); + Fraction frameRate = m_pcCfg->getFrameRate(); + frameRate.den *= m_pcCfg->getTemporalSubsampleRatio(); + m_gcAnalyzeAllField.setFrameRate(frameRate); m_gcAnalyzeAllField.setBits(m_gcAnalyzeAll.getBits()); // prior to the above statement, the interlace analyser does not contain the correct total number of bits. id="a"; @@ -5645,7 +5653,8 @@ void EncGOP::xCalculateGreenComplexityMetrics( FeatureCounterStruct featureCount seiGreenMetadataInfo->m_greenComplexityMetrics.portionSaoInstances = int(floor( 255.0 * numSaoFilteredBlocks / totalNum4BlocksInPeriod)); seiGreenMetadataInfo->m_greenComplexityMetrics.portionAlfInstances = int(floor( 255.0 * numAlfFilteredBlocks / totalNum4BlocksInPeriod)); seiGreenMetadataInfo->m_numPictures = int(featureCounterDifference.iSlices + featureCounterDifference.bSlices +featureCounterDifference.pSlices); - seiGreenMetadataInfo->m_numSeconds = int(floor(double(seiGreenMetadataInfo->m_numPictures) / double(m_pcCfg->getFrameRate()))); + seiGreenMetadataInfo->m_numSeconds = + int(floor(seiGreenMetadataInfo->m_numPictures / m_pcCfg->getFrameRateScale().getFloatVal())); } #endif diff --git a/source/Lib/EncoderLib/EncHRD.cpp b/source/Lib/EncoderLib/EncHRD.cpp index d49cd4fc373b4c6fd6e47657e35ec7b350f51e7b..6076b91209464dd3961072a49f5de3f7fe3950a9 100644 --- a/source/Lib/EncoderLib/EncHRD.cpp +++ b/source/Lib/EncoderLib/EncHRD.cpp @@ -31,6 +31,8 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ +#include <numeric> + #include "EncHRD.h" @@ -55,54 +57,40 @@ int EncHRD::xCalcScale(int x) void EncHRD::initHRDParameters(EncCfg* encCfg) { - bool useSubCpbParams = encCfg->getNoPicPartitionFlag() == false; - int bitRate = encCfg->getTargetBitrate(); - int cpbSize = encCfg->getCpbSize(); - CHECK(!(cpbSize != 0), "Unspecified error"); // CPB size may not be equal to zero. ToDo: have a better default and check for level constraints if (!encCfg->getHrdParametersPresentFlag() && !encCfg->getCpbSaturationEnabled()) { return; } - switch (encCfg->getFrameRate()) - { - case 24: - m_generalHrdParams.setNumUnitsInTick(1125000); m_generalHrdParams.setTimeScale(27000000); - break; - case 25: - m_generalHrdParams.setNumUnitsInTick(1080000); m_generalHrdParams.setTimeScale(27000000); - break; - case 30: - m_generalHrdParams.setNumUnitsInTick(900900); m_generalHrdParams.setTimeScale(27000000); - break; - case 50: - m_generalHrdParams.setNumUnitsInTick(540000); m_generalHrdParams.setTimeScale(27000000); - break; - case 60: - m_generalHrdParams.setNumUnitsInTick(450450); m_generalHrdParams.setTimeScale(27000000); - break; - default: - m_generalHrdParams.setNumUnitsInTick(1001); m_generalHrdParams.setTimeScale(60000); - break; - } + int numUnitsInTick = encCfg->getFrameRate().den * encCfg->getTemporalSubsampleRatio(); + int timeScale = encCfg->getFrameRate().num; - if (encCfg->getTemporalSubsampleRatio() > 1) + constexpr int DEFAULT_TICKS = 27000000; // Default 27 Mhz clock + if (numUnitsInTick < timeScale && std::gcd(DEFAULT_TICKS, timeScale) == timeScale) { - uint32_t temporalSubsampleRatio = encCfg->getTemporalSubsampleRatio(); - if (double(m_generalHrdParams.getNumUnitsInTick()) * temporalSubsampleRatio > std::numeric_limits<uint32_t>::max()) - { - m_generalHrdParams.setTimeScale(m_generalHrdParams.getTimeScale() / temporalSubsampleRatio); - } - else - { - m_generalHrdParams.setNumUnitsInTick(m_generalHrdParams.getNumUnitsInTick() * temporalSubsampleRatio); - } + // Use default clock if >1 fps and no loss of precision + numUnitsInTick *= DEFAULT_TICKS / timeScale; + timeScale = DEFAULT_TICKS; } - bool rateCnt = (bitRate > 0); + m_generalHrdParams.setNumUnitsInTick(numUnitsInTick); + m_generalHrdParams.setTimeScale(timeScale); + const int bitRate = encCfg->getTargetBitrate(); + + const bool rateCnt = (bitRate > 0); m_generalHrdParams.setGeneralNalHrdParametersPresentFlag(rateCnt); m_generalHrdParams.setGeneralVclHrdParametersPresentFlag(rateCnt); + if (!rateCnt) + { + return; + } + + int cpbSize = encCfg->getCpbSize(); + CHECK(!(cpbSize != 0), "Unspecified error"); // CPB size may not be equal to zero. ToDo: have a better default and + // check for level constraints + bool useSubCpbParams = encCfg->getNoPicPartitionFlag() == false; + m_generalHrdParams.setGeneralSamePicTimingInAllOlsFlag(encCfg->getSamePicTimingInAllOLS()); useSubCpbParams &= (m_generalHrdParams.getGeneralNalHrdParametersPresentFlag() || m_generalHrdParams.getGeneralVclHrdParametersPresentFlag()); m_generalHrdParams.setGeneralDecodingUnitHrdParamsPresentFlag(useSubCpbParams); @@ -176,4 +164,3 @@ void EncHRD::initHRDParameters(EncCfg* encCfg) } } } - diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp index 6afc28b8e2e1a1eed82619e15d30e5b28677ae2b..346b60f1f84eccc0124afe5f2bfd02d72edea174 100644 --- a/source/Lib/EncoderLib/EncLib.cpp +++ b/source/Lib/EncoderLib/EncLib.cpp @@ -118,9 +118,10 @@ void EncLib::create( const int layerId ) } if ( m_RCEnableRateControl ) { - m_cRateCtrl.init(m_framesToBeEncoded, m_RCTargetBitrate, - (int) ((double) m_frameRate / m_temporalSubsampleRatio + 0.5), m_gopSize, m_intraPeriod, - m_sourceWidth, m_sourceHeight, m_maxCUWidth, m_maxCUHeight, getBitDepth(ChannelType::LUMA), + Fraction frameRate = m_frameRate; + frameRate.den *= m_temporalSubsampleRatio; + m_cRateCtrl.init(m_framesToBeEncoded, m_RCTargetBitrate, frameRate, m_gopSize, m_intraPeriod, m_sourceWidth, + m_sourceHeight, m_maxCUWidth, m_maxCUHeight, getBitDepth(ChannelType::LUMA), m_RCKeepHierarchicalBit, m_RCUseLCUSeparateModel, m_GOPList); } @@ -1635,6 +1636,11 @@ void EncLib::xInitSPS( SPS& sps ) sps.setChromaQpMappingTableFromParams(m_chromaQpMappingTableParams, sps.getQpBDOffset(ChannelType::CHROMA)); sps.deriveChromaQPMappingTables(); + if (m_hrdParametersPresentFlag) + { + sps.setGeneralHrdParametersPresentFlag(true); + xInitHrdParameters(sps); + } if( getPictureTimingSEIEnabled() || getDecodingUnitInfoSEIEnabled() || getCpbSaturationEnabled() ) { xInitHrdParameters(sps); diff --git a/source/Lib/EncoderLib/RateCtrl.cpp b/source/Lib/EncoderLib/RateCtrl.cpp index a1a69679b71d6d801fb6795dd8add68787406cca..010c7840af4497bcdc3d5231ff6bd70b3f25c59b 100644 --- a/source/Lib/EncoderLib/RateCtrl.cpp +++ b/source/Lib/EncoderLib/RateCtrl.cpp @@ -46,7 +46,6 @@ EncRCSeq::EncRCSeq() { m_totalFrames = 0; m_targetRate = 0; - m_frameRate = 0; m_targetBits = 0; m_GOPSize = 0; m_intraPeriod = 0; @@ -75,7 +74,9 @@ EncRCSeq::~EncRCSeq() destroy(); } -void EncRCSeq::create(int totalFrames, int targetBitrate, int frameRate, int GOPSize, int intraPeriod, int picWidth, int picHeight, int LCUWidth, int LCUHeight, int numberOfLevel, bool useLCUSeparateModel, int adaptiveBit) +void EncRCSeq::create(int totalFrames, int targetBitrate, const Fraction& frameRate, int GOPSize, int intraPeriod, + int picWidth, int picHeight, int LCUWidth, int LCUHeight, int numberOfLevel, + bool useLCUSeparateModel, int adaptiveBit) { destroy(); m_totalFrames = totalFrames; @@ -91,8 +92,9 @@ void EncRCSeq::create(int totalFrames, int targetBitrate, int frameRate, int GOP m_useLCUSeparateModel = useLCUSeparateModel; m_numberOfPixel = m_picWidth * m_picHeight; - m_targetBits = (int64_t)m_totalFrames * (int64_t)m_targetRate / (int64_t)m_frameRate; - m_seqTargetBpp = (double)m_targetRate / (double)m_frameRate / (double)m_numberOfPixel; + m_targetBits = + (int64_t) m_totalFrames * (int64_t) m_targetRate * (int64_t) m_frameRate.den / (int64_t) m_frameRate.num; + m_seqTargetBpp = (double) m_targetRate / m_frameRate.getFloatVal() / (double) m_numberOfPixel; if ( m_seqTargetBpp < 0.03 ) { m_alphaUpdate = 0.01; @@ -1451,7 +1453,9 @@ void RateCtrl::destroy() } } -void RateCtrl::init(int totalFrames, int targetBitrate, int frameRate, int GOPSize, int intraPeriod, int picWidth, int picHeight, int LCUWidth, int LCUHeight, int bitDepth, int keepHierBits, bool useLCUSeparateModel, GOPEntry GOPList[MAX_GOP]) +void RateCtrl::init(int totalFrames, int targetBitrate, const Fraction& frameRate, int GOPSize, int intraPeriod, + int picWidth, int picHeight, int LCUWidth, int LCUHeight, int bitDepth, int keepHierBits, + bool useLCUSeparateModel, GOPEntry GOPList[MAX_GOP]) { destroy(); @@ -1488,7 +1492,7 @@ void RateCtrl::init(int totalFrames, int targetBitrate, int frameRate, int GOPSi if ( keepHierBits > 0 ) { - double bpp = (double)( targetBitrate / (double)( frameRate*picWidth*picHeight ) ); + double bpp = (double) (targetBitrate / (frameRate.getFloatVal() * picWidth * picHeight)); if ( GOPSize == 4 && isLowdelay ) { if ( bpp > 0.2 ) @@ -1896,7 +1900,7 @@ void RateCtrl::init(int totalFrames, int targetBitrate, int frameRate, int GOPSi m_CpbSaturationEnabled = false; m_cpbSize = targetBitrate; m_cpbState = (uint32_t)(m_cpbSize*0.5f); - m_bufferingRate = (int)(targetBitrate / frameRate); + m_bufferingRate = (int) (targetBitrate * frameRate.den / frameRate.num); delete[] bitsRatio; delete[] GOPID2Level; @@ -1934,12 +1938,14 @@ int RateCtrl::updateCpbState(int actualBits) return cpbState; } -void RateCtrl::initHrdParam(const GeneralHrdParams* generalHrd, const OlsHrdParams* olsHrd, int iFrameRate, double fInitialCpbFullness) +void RateCtrl::initHrdParam(const GeneralHrdParams* generalHrd, const OlsHrdParams* olsHrd, const Fraction& frameRate, + double fInitialCpbFullness) { m_CpbSaturationEnabled = true; m_cpbSize = (olsHrd->getCpbSizeValueMinus1(0, 0) + 1) << (4 + generalHrd->getCpbSizeScale()); m_cpbState = (uint32_t)(m_cpbSize*fInitialCpbFullness); - m_bufferingRate = (uint32_t)(((olsHrd->getBitRateValueMinus1(0, 0) + 1) << (6 + generalHrd->getBitRateScale())) / iFrameRate); + m_bufferingRate = (uint32_t) (((olsHrd->getBitRateValueMinus1(0, 0) + 1) << (6 + generalHrd->getBitRateScale())) + * frameRate.den / frameRate.num); msg(NOTICE, "\nHRD - [Initial CPB state %6d] [CPB Size %6d] [Buffering Rate %6d]\n", m_cpbState, m_cpbSize, m_bufferingRate); } diff --git a/source/Lib/EncoderLib/RateCtrl.h b/source/Lib/EncoderLib/RateCtrl.h index 0ed7b6c625c528d311a8bb3674482552178c2b27..a47bae2de34a80c7c3e0901cdc60e5be22de9bf6 100644 --- a/source/Lib/EncoderLib/RateCtrl.h +++ b/source/Lib/EncoderLib/RateCtrl.h @@ -99,7 +99,8 @@ public: ~EncRCSeq(); public: - void create(int totalFrames, int targetBitrate, int frameRate, int GOPSize, int intraPeriod, int picWidth, int picHeight, int LCUWidth, int LCUHeight, int numberOfLevel, bool useLCUSeparateModel, int adaptiveBit); + void create(int totalFrames, int targetBitrate, const Fraction& frameRate, int GOPSize, int intraPeriod, int picWidth, + int picHeight, int LCUWidth, int LCUHeight, int numberOfLevel, bool useLCUSeparateModel, int adaptiveBit); void destroy(); void initBitsRatio( int bitsRatio[] ); void initGOPID2Level( int GOPID2Level[] ); @@ -111,7 +112,7 @@ public: public: int getTotalFrames() { return m_totalFrames; } int getTargetRate() { return m_targetRate; } - int getFrameRate() { return m_frameRate; } + const Fraction& getFrameRate() const { return m_frameRate; } int getGOPSize() { return m_GOPSize; } int getIntraPeriod() { return m_intraPeriod; } int getPicWidth() { return m_picWidth; } @@ -154,7 +155,7 @@ public: private: int m_totalFrames; int m_targetRate; - int m_frameRate; + Fraction m_frameRate; int m_GOPSize; int m_intraPeriod; int m_picWidth; @@ -325,7 +326,9 @@ public: ~RateCtrl(); public: - void init(int totalFrames, int targetBitrate, int frameRate, int GOPSize, int intraPeriod, int picWidth, int picHeight, int LCUWidth, int LCUHeight, int bitDepth, int keepHierBits, bool useLCUSeparateModel, GOPEntry GOPList[MAX_GOP]); + void init(int totalFrames, int targetBitrate, const Fraction& frameRate, int GOPSize, int intraPeriod, int picWidth, + int picHeight, int LCUWidth, int LCUHeight, int bitDepth, int keepHierBits, bool useLCUSeparateModel, + GOPEntry GOPList[MAX_GOP]); void destroy(); void initRCPic( int frameLevel ); void initRCGOP( int numberOfPictures ); @@ -355,7 +358,8 @@ public: uint32_t getCpbSize() { return m_cpbSize; } uint32_t getBufferingRate() { return m_bufferingRate; } int updateCpbState(int actualBits); - void initHrdParam(const GeneralHrdParams* generalHrd, const OlsHrdParams* olsHrd, int iFrameRate, double fInitialCpbFullness); + void initHrdParam(const GeneralHrdParams* generalHrd, const OlsHrdParams* olsHrd, const Fraction& frameRate, + double fInitialCpbFullness); private: EncRCSeq* m_encRCSeq; diff --git a/source/Lib/Utilities/VideoIOYuv.cpp b/source/Lib/Utilities/VideoIOYuv.cpp index 6722a627ce8a88202c37572f2225c098d5baa277..98123631d2b52a47e5c0f9a694102d4b8895d5a6 100644 --- a/source/Lib/Utilities/VideoIOYuv.cpp +++ b/source/Lib/Utilities/VideoIOYuv.cpp @@ -171,7 +171,10 @@ void VideoIOYuv::open(const std::string &fileName, bool bWriteMode, const BitDep { if (m_inY4mFileHeaderLength == 0) { - int dummyWidth = 0, dummyHeight = 0, dummyFrameRate = 0, dummyBitDepth = 0; + int dummyWidth = 0; + int dummyHeight = 0; + Fraction dummyFrameRate; + int dummyBitDepth = 0; ChromaFormat dummyChromaFormat = ChromaFormat::_420; parseY4mFileHeader(fileName, dummyWidth, dummyHeight, dummyFrameRate, dummyBitDepth, dummyChromaFormat); } @@ -192,8 +195,8 @@ void VideoIOYuv::open(const std::string &fileName, bool bWriteMode, const BitDep return; } -void VideoIOYuv::parseY4mFileHeader(const std::string &fileName, int &width, int &height, int &frameRate, int &bitDepth, - ChromaFormat &chromaFormat) +void VideoIOYuv::parseY4mFileHeader(const std::string& fileName, int& width, int& height, Fraction& frameRate, + int& bitDepth, ChromaFormat& chromaFormat) { m_cHandle.open(fileName.c_str(), std::ios::binary | std::ios::in); CHECK(m_cHandle.fail(), "File open failed.") @@ -260,7 +263,8 @@ void VideoIOYuv::parseY4mFileHeader(const std::string &fileName, int &width, int { if (denominator != 0) { - frameRate = (int) (1.0 * numerator / denominator + 0.5); + frameRate.num = numerator; + frameRate.den = denominator; } } break; @@ -276,24 +280,24 @@ void VideoIOYuv::parseY4mFileHeader(const std::string &fileName, int &width, int m_cHandle.close(); } -void VideoIOYuv::setOutputY4mInfo(int width, int height, int frameRate, int frameScale, int bitDepth, ChromaFormat chromaFormat) +void VideoIOYuv::setOutputY4mInfo(int width, int height, const Fraction& frameRate, int bitDepth, + ChromaFormat chromaFormat) { m_outPicWidth = width; m_outPicHeight = height; m_outBitDepth = bitDepth; m_outFrameRate = frameRate; - m_outFrameScale = frameScale; m_outChromaFormat = chromaFormat; } void VideoIOYuv::writeY4mFileHeader() { - CHECK(m_outPicWidth == 0 || m_outPicHeight == 0 || m_outBitDepth == 0 || m_outFrameRate == 0, + CHECK(m_outPicWidth == 0 || m_outPicHeight == 0 || m_outBitDepth == 0 || m_outFrameRate.num == 0, "Output Y4M file into has not been set"); std::string header = y4mSignature; header += "W" + std::to_string(m_outPicWidth) + " "; header += "H" + std::to_string(m_outPicHeight) + " "; - header += "F" + std::to_string(m_outFrameRate) + ":" + std::to_string(m_outFrameScale) + " "; + header += "F" + std::to_string(m_outFrameRate.num) + ":" + std::to_string(m_outFrameRate.den) + " "; header += "Ip A0:0 "; switch (m_outChromaFormat) { diff --git a/source/Lib/Utilities/VideoIOYuv.h b/source/Lib/Utilities/VideoIOYuv.h index 3cdf7f852cbc1d1416a28e5205bace9ce6d44ab8..fc006aeb3d078696604cbed0c6b0b29b3259d644 100644 --- a/source/Lib/Utilities/VideoIOYuv.h +++ b/source/Lib/Utilities/VideoIOYuv.h @@ -65,8 +65,7 @@ private: int m_outPicWidth = 0; int m_outPicHeight = 0; int m_outBitDepth = 0; - int m_outFrameRate = 0; - int m_outFrameScale = 1; + Fraction m_outFrameRate; ChromaFormat m_outChromaFormat = ChromaFormat::_420; bool m_outY4m = false; @@ -74,9 +73,9 @@ public: VideoIOYuv() {} virtual ~VideoIOYuv() {} - void parseY4mFileHeader(const std::string &fileName, int &width, int &height, int &frameRate, int &bitDepth, - ChromaFormat &chromaFormat); - void setOutputY4mInfo(int width, int height, int frameRate, int frameScale, int bitDepth, ChromaFormat chromaFormat); + void parseY4mFileHeader(const std::string& fileName, int& width, int& height, Fraction& frameRate, int& bitDepth, + ChromaFormat& chromaFormat); + void setOutputY4mInfo(int width, int height, const Fraction& frameRate, int bitDepth, ChromaFormat chromaFormat); void writeY4mFileHeader(); void open(const std::string &fileName, bool bWriteMode, const BitDepths &fileBitDepth, const BitDepths &MSBExtendedBitDepth,