From 3927133d027aa9af0b6ca5e02269e461725e7b5b Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Tue, 19 May 2026 23:16:24 +0200 Subject: [PATCH 01/13] frame: add duration field. --- source/common/frame.cpp | 4 ++++ source/common/frame.h | 8 ++++++++ source/encoder/encoder.cpp | 17 +++++++++++------ source/encoder/encoder.h | 20 ++++++++++++++++++-- source/encoder/slicetype.cpp | 5 +++++ source/encoder/slicetype.h | 1 + source/x265.h | 4 ++-- 7 files changed, 49 insertions(+), 10 deletions(-) diff --git a/source/common/frame.cpp b/source/common/frame.cpp index ca25e25bd..65827733d 100644 --- a/source/common/frame.cpp +++ b/source/common/frame.cpp @@ -85,6 +85,10 @@ Frame::Frame() m_targetBitrate = 0; m_targetCrf = 0; m_targetQp = 0; + + // HRD management + m_duration = 0; + m_displayDurSecs = 0.0; } bool Frame::create(x265_param *param, float* quantOffsets) diff --git a/source/common/frame.h b/source/common/frame.h index 9916ba92e..91907b868 100644 --- a/source/common/frame.h +++ b/source/common/frame.h @@ -182,6 +182,14 @@ class Frame /* target QP for this picture.*/ int m_targetQp; + /*HRD delays for this frame*/ + int64_t m_duration; // In VUI timebase + float m_displayDurSecs; // display time of picture in seconds + int64_t m_plannedCpbDuration; // lifetime in the CPB + int64_t m_cpbDelay; // In VUI timebase + int64_t m_dpbOutputDelay; // In VUI timebase + + Frame(); bool create(x265_param *param, float* quantOffsets); diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 25a776250..475f64d35 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -50,6 +50,7 @@ namespace X265_NS { const char g_sliceTypeToChar[] = {'B', 'P', 'I'}; +const uint8_t g_deltaToDivisor[PIC_STRUCT_COUNT] = {1, 1, 1, 2, 2, 3, 3, 2, 3, 1, 1, 1, 1}; /* Dolby Vision profile specific settings */ typedef struct @@ -1567,13 +1568,13 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) { if (m_dupBuffer[0]->bDup) { - m_dupBuffer[0]->dupPic->picStruct = tripling; + m_dupBuffer[0]->dupPic->picStruct = PIC_STRUCT_TRIPLING; m_dupBuffer[0]->bDup = false; read++; } else { - m_dupBuffer[0]->dupPic->picStruct = doubling; + m_dupBuffer[0]->dupPic->picStruct = PIC_STRUCT_DOUBLING; m_dupBuffer[0]->bDup = true; m_dupBuffer[1]->bOccupied = false; read++; @@ -1759,7 +1760,11 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_forceqp = inputPic[0]->forceqp; inFrame[layer]->m_param = (m_reconfigure || m_reconfigureRc || m_param->bConfigRCFrame) ? m_latestParam : m_param; - inFrame[layer]->m_picStruct = inputPic[0]->picStruct; + inFrame[layer]->m_picStruct = inputPic[0]->picStruct < PIC_STRUCT_COUNT ? inputPic[0]->picStruct : 0; + + //Determine duration for slicetype and ratecontrol + inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; + inFrame[layer]->m_displayDurSecs = inFrame[layer]->m_duration * (m_sps.vuiParameters.timingInfo.numUnitsInTick / m_sps.vuiParameters.timingInfo.timeScale); /*Copy reconfigured RC parameters to frame*/ if (m_param->rc.rateControlMode == X265_RC_ABR) @@ -1894,9 +1899,9 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) m_param->bUseRcStats = 0; } - if (m_param->bEnableFrameDuplication && ((read < written) || (m_dupBuffer[0]->dupPic->picStruct == tripling && (read <= written)))) + if (m_param->bEnableFrameDuplication && ((read < written) || (m_dupBuffer[0]->dupPic->picStruct == PIC_STRUCT_TRIPLING && (read <= written)))) { - if (m_dupBuffer[0]->dupPic->picStruct == tripling) + if (m_dupBuffer[0]->dupPic->picStruct == PIC_STRUCT_TRIPLING) m_dupBuffer[0]->bOccupied = m_dupBuffer[1]->bOccupied = false; else { @@ -1971,7 +1976,7 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) //TODO: Add subsampling here if required inFrame[0]->m_mcstffencPic->copyFromFrame(inFrame[0]->m_fencPic); - m_lookahead->m_origPicBuf->addPicture(inFrame[0]);; + m_lookahead->m_origPicBuf->addPicture(inFrame[0]); } m_lookahead->addPicture(*inFrame[0], sliceType); diff --git a/source/encoder/encoder.h b/source/encoder/encoder.h index 9101b79b1..30359ce67 100644 --- a/source/encoder/encoder.h +++ b/source/encoder/encoder.h @@ -94,8 +94,24 @@ struct EncStats #define MAX_NUM_REF_IDX 64 #define DUP_BUFFER 2 -#define doubling 7 -#define tripling 8 + +enum PicStruct +{ + PIC_STRUCT_PROGRESSIVE_FRAME = 0, + PIC_STRUCT_FIELD_TOP = 1, + PIC_STRUCT_FIELD_BOTTOM = 2, + PIC_STRUCT_TOP_BOTTOM = 3, + PIC_STRUCT_BOTTOM_TOP = 4, + PIC_STRUCT_TOP_BOTTOM_TOP = 5, + PIC_STRUCT_BOTTOM_TOP_BOTTOM = 6, + PIC_STRUCT_DOUBLING = 7, + PIC_STRUCT_TRIPLING = 8, + PIC_STRUCT_TOP_PREVBOTTOM = 9, + PIC_STRUCT_BOTTOM_PREVTOP = 10, + PIC_STRUCT_TOP_NEXTBOTTOM = 11, + PIC_STRUCT_BOTTOM_NEXTTOP = 12, + PIC_STRUCT_COUNT +}; struct RefIdxLastGOP { diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 9e2beb0b3..5bad1da6a 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2750,6 +2750,11 @@ void Lookahead::slicetypeDecide() } } +void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t *cpbDelay, int64_t *dispCount) +{ + //TODO +} + void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) { int prevNonB = 0, curNonB = 1, idx = 0; diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 36fbeb457..59c2caacb 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -258,6 +258,7 @@ class Lookahead : public JobProvider void slicetypePath(Lowres **frames, int length, char(*best_paths)[X265_LOOKAHEAD_MAX + 1]); int64_t slicetypePathCost(Lowres **frames, char *path, int64_t threshold); int64_t vbvFrameCost(Lowres **frames, int p0, int p1, int b); + void calculateDurations(Frame *frame, Frame *prevFrame, int64_t *cpbDelay, int64_t *dispCount); void vbvLookahead(Lowres **frames, int numFrames, int keyframes); void aqMotion(Lowres **frames, bool bintra); void calcMotionAdaptiveQuantFrame(Lowres **frames, int p0, int p1, int b); diff --git a/source/x265.h b/source/x265.h index 990cf9a8a..7ce38666f 100644 --- a/source/x265.h +++ b/source/x265.h @@ -2198,8 +2198,8 @@ typedef struct x265_param /* * Signals picture structure SEI timing message for every frame - * picture structure 7 is signalled for frame doubling - * picture structure 8 is signalled for frame tripling + * picture structure 7 is signalled for frame doubling (PIC_STRUCT_DOUBLING) + * picture structure 8 is signalled for frame tripling (PIC_STRUCT_TRIPLING) * */ int bEnableFrameDuplication; From 64bc7fe433dd9eca6c33fb9c836c07e52c633c40 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 23 May 2026 13:24:32 +0200 Subject: [PATCH 02/13] hrd: plan cpb and dpb delays in slicetype. --- source/common/frame.cpp | 5 +++- source/common/frame.h | 13 ++++----- source/encoder/encoder.cpp | 48 ++++++++++++++++++++++++++++++--- source/encoder/encoder.h | 1 + source/encoder/frameencoder.cpp | 4 +-- source/encoder/slicetype.cpp | 46 ++++++++++++++++++++++++++++--- source/encoder/slicetype.h | 8 ++++-- 7 files changed, 106 insertions(+), 19 deletions(-) diff --git a/source/common/frame.cpp b/source/common/frame.cpp index 65827733d..f5369864c 100644 --- a/source/common/frame.cpp +++ b/source/common/frame.cpp @@ -88,7 +88,10 @@ Frame::Frame() // HRD management m_duration = 0; - m_displayDurSecs = 0.0; + m_timebase = 0.0; + m_cpbDelay = 0; + m_dpbOutputDelay = 0; + m_plannedCpbDuration = 0; } bool Frame::create(x265_param *param, float* quantOffsets) diff --git a/source/common/frame.h b/source/common/frame.h index 91907b868..214700b4d 100644 --- a/source/common/frame.h +++ b/source/common/frame.h @@ -183,12 +183,13 @@ class Frame int m_targetQp; /*HRD delays for this frame*/ - int64_t m_duration; // In VUI timebase - float m_displayDurSecs; // display time of picture in seconds - int64_t m_plannedCpbDuration; // lifetime in the CPB - int64_t m_cpbDelay; // In VUI timebase - int64_t m_dpbOutputDelay; // In VUI timebase - + int64_t m_duration; // display duration of picture in ticks (VUI timebase) + float m_timebase; // the timebase for this picture [seconds] + int64_t m_plannedCpbDuration; // lifetime in the CPB (VUI timebase) + int64_t m_cpbDelay; // removal delay of the picture (VUI timebase) + uint64_t m_displayPicCount; // display picture count + uint64_t m_codedPicCount; // remove me, for debugging + int64_t m_dpbOutputDelay; // output delay of the picture (VUI timebase) Frame(); diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 475f64d35..57fc49e15 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -179,6 +179,8 @@ Encoder::Encoder() m_startPoint = 0; m_saveCTUSize = 0; m_zoneIndex = 0; + + m_dispPicCount = 0; } inline char *strcatFilename(const char *input, const char *suffix) @@ -374,7 +376,7 @@ void Encoder::create() } else lookAheadThreadPool = m_threadPool ? &m_threadPool[m_numTmePools] : NULL; - m_lookahead = new Lookahead(m_param, lookAheadThreadPool); + m_lookahead = new Lookahead(m_param, lookAheadThreadPool, &m_sps); m_lookahead->m_numPools = lookaheadPools; if (lookaheadPools) { @@ -1760,11 +1762,24 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_forceqp = inputPic[0]->forceqp; inFrame[layer]->m_param = (m_reconfigure || m_reconfigureRc || m_param->bConfigRCFrame) ? m_latestParam : m_param; - inFrame[layer]->m_picStruct = inputPic[0]->picStruct < PIC_STRUCT_COUNT ? inputPic[0]->picStruct : 0; - //Determine duration for slicetype and ratecontrol + /* todo: pic struct defined per frame should have priority over param one. */ + if (inFrame[layer]->m_param->pictureStructure > -1) + inFrame[layer]->m_picStruct = inFrame[layer]->m_param->pictureStructure; + else + inFrame[layer]->m_picStruct = inputPic[0]->picStruct; + inFrame[layer]->m_picStruct = inFrame[layer]->m_picStruct < PIC_STRUCT_COUNT ? inFrame[layer]->m_picStruct : 0; + + /* Set up frame timing info for slicetype and ratecontrol and the frame timebase (could change across cvs) */ inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; - inFrame[layer]->m_displayDurSecs = inFrame[layer]->m_duration * (m_sps.vuiParameters.timingInfo.numUnitsInTick / m_sps.vuiParameters.timingInfo.timeScale); + inFrame[layer]->m_displayPicCount = m_dispPicCount; + if (inFrame[layer]->m_param->bEmitVUITimingInfo) + inFrame[layer]->m_timebase = (m_sps.vuiParameters.timingInfo.numUnitsInTick / m_sps.vuiParameters.timingInfo.timeScale); + else + inFrame[layer]->m_timebase = (inFrame[layer]->m_param->fpsDenom / inFrame[layer]->m_param->fpsNum); + + /* update presentation counts (decoder ticks count) */ + m_dispPicCount += inFrame[layer]->m_duration; /*Copy reconfigured RC parameters to frame*/ if (m_param->rc.rateControlMode == X265_RC_ABR) @@ -4522,6 +4537,31 @@ void Encoder::configure(x265_param *p) x265_log(p, X265_LOG_WARNING, "Dynamic-rd disabled, requires RD <= 4, VBV and aq-mode enabled\n"); } + // Cannot use temporal layers with a picture structure whose DeltaToDivisor is not 1: we would have to drop access units in higher layers + // and enforce that structures in higher layers do not hide a frame in the lower layers: it's easier to just forbid it. + if (p->bEnableTemporalSubLayers) + { + // PF, TB and BT each have DeltaToDivisor = 1 and convey convey a full frame (or a field pair) + if (p->pictureStructure > PIC_STRUCT_PROGRESSIVE_FRAME && p->pictureStructure != PIC_STRUCT_TOP_BOTTOM && p->pictureStructure != PIC_STRUCT_BOTTOM_TOP) + { + x265_log(p, X265_LOG_WARNING, "Picture structure is not compatible with temporal sub layers. Not using the user-provided pic-struct.\n"); + p->pictureStructure = -1; + } + if (p->bEnableFrameDuplication) + { + x265_log(p, X265_LOG_WARNING, "Frame-duplication is not compatible with temporal sub layers. Disabling Frame Duplication.\n"); + p->bEnableFrameDuplication = 0; + p->dupThreshold = 0; // prevent it from being enabled below + } + } + + // reject any configuration that leads to orphaned fields (1, 2, 5, 6, 9, 10, 11, 12) + if (p->pictureStructure >= PIC_STRUCT_COUNT || ((1 << p->pictureStructure) & 0b1111001100110)) + { + x265_log(p, X265_LOG_WARNING, "Invalid or illegal picture structure, not using the user-provided value.\n"); + p->pictureStructure = -1; + } + if (!p->bEnableFrameDuplication && p->dupThreshold && p->dupThreshold != 70) { x265_log(p, X265_LOG_WARNING, "Frame-duplication threshold works only with frame-duplication enabled. Enabling frame-duplication.\n"); diff --git a/source/encoder/encoder.h b/source/encoder/encoder.h index 30359ce67..90bda5f26 100644 --- a/source/encoder/encoder.h +++ b/source/encoder/encoder.h @@ -210,6 +210,7 @@ class Encoder : public x265_encoder int m_numPools; int m_numTmePools; int m_curEncoder; + uint64_t m_dispPicCount; /* displayed picture count (implied by explicit or implicit picture structure) */ // weighted prediction int m_numLumaWPFrames; // number of P frames with weighted luma reference diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index 5b64950b4..edf60b5d8 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -837,8 +837,8 @@ void FrameEncoder::compressFrame(int layer) // access unit associated with the picture timing SEI message has to // wait after removal of the access unit with the most recent // buffering period SEI message - sei->m_auCpbRemovalDelay = X265_MIN(X265_MAX(1, m_rce.encodeOrder - prevBPSEI), (1 << hrd->cpbRemovalDelayLength)); - sei->m_picDpbOutputDelay = slice->m_sps->numReorderPics[m_frame[layer]->m_tempLayer] + poc - m_rce.encodeOrder; + sei->m_auCpbRemovalDelay = X265_MIN(X265_MAX(1, m_frame[layer]->m_cpbDelay), (1 << hrd->cpbRemovalDelayLength));//X265_MIN(X265_MAX(1, m_rce.encodeOrder - prevBPSEI), (1 << hrd->cpbRemovalDelayLength)); + sei->m_picDpbOutputDelay = m_frame[layer]->m_dpbOutputDelay;//slice->m_sps->numReorderPics[m_frame[layer]->m_tempLayer] + poc - m_rce.encodeOrder; } sei->writeSEImessages(m_bs, *slice->m_sps, NAL_UNIT_PREFIX_SEI, m_nalList, m_param->bSingleSeiNal, layer); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 5bad1da6a..038f12d04 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1041,10 +1041,11 @@ int32_t Lookahead::estimateNoise(Frame* curFrame) return (int32_t)((sum * 82137) / (6 * num * (1 << (X265_DEPTH - 8)))); } -Lookahead::Lookahead(x265_param *param, ThreadPool* pool) +Lookahead::Lookahead(x265_param *param, ThreadPool* pool, SPS* sps) { m_param = param; m_pool = pool; + m_sps = sps; m_lastNonB = NULL; m_isSceneTransition = false; @@ -1070,6 +1071,10 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool) m_origPicBuf = 0; m_metld = NULL; + /* HRD counters */ + m_codedPicCount = 0; + m_cpbDelay = 0; + /* Allow the strength to be adjusted via qcompress, since the two concepts * are very similar. */ m_cuTreeStrength = (m_param->rc.hevcAq ? 6.0 : 5.0) * (1.0 - m_param->rc.qCompress); @@ -2674,6 +2679,7 @@ void Lookahead::slicetypeDecide() * in the output queue. The order is important because Frame can * only be in one list at a time */ int64_t pts[X265_BFRAME_MAX + 1]; + uint8_t codedFrameOrderedIndex[X265_BFRAME_MAX + 1]; for (int i = 0; i <= bframes; i++) { Frame *curFrame; @@ -2685,8 +2691,9 @@ void Lookahead::slicetypeDecide() m_outputLock.acquire(); - /* add non-B to output queue */ int idx = 0; + /* add non-B to output queue */ + codedFrameOrderedIndex[idx] = bframes; list[bframes]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[bframes]); @@ -2697,6 +2704,7 @@ void Lookahead::slicetypeDecide() { if (list[i]->m_lowres.sliceType == X265_TYPE_BREF) { + codedFrameOrderedIndex[idx] = i; list[i]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[i]); } @@ -2709,11 +2717,20 @@ void Lookahead::slicetypeDecide() /* push all the B frames into output queue except B-ref, which already pushed into output queue */ if (list[i]->m_lowres.sliceType != X265_TYPE_BREF) { + codedFrameOrderedIndex[idx] = i; list[i]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[i]); } } + // Compute HRD CpbDpb delays + for (int i = 0; i <= bframes; ++i) + { + /* to keep cpb and dpb operations in sync, use the display duration of the current picture to specify the pic + lifetime in the cpb. Given that pulldown sequences are alternating between single-doubling or doubling-tripling, + the cpb lifetime "error" is limited to 1 time clock and does not accumulate on long re-ordered sequences */ + calculateDurations(list[codedFrameOrderedIndex[i]], list[i]->m_duration); + } bool isKeyFrameAnalyse = (m_param->rc.cuTree || (m_param->rc.vbvBufferSize && m_param->lookaheadDepth)); if (isKeyFrameAnalyse && IS_X265_TYPE_I(m_lastNonB->sliceType)) @@ -2750,9 +2767,30 @@ void Lookahead::slicetypeDecide() } } -void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t *cpbDelay, int64_t *dispCount) +void Lookahead::calculateDurations(Frame *frame, int64_t concurrentDisplayDuration) { - //TODO + frame->m_cpbDelay = m_cpbDelay; + frame->m_dpbOutputDelay = frame->m_displayPicCount - m_codedPicCount; + frame->m_plannedCpbDuration = concurrentDisplayDuration; + frame->m_codedPicCount = m_codedPicCount; + + /* largest re-ordering at highest temporal layer */ + frame->m_dpbOutputDelay += 1 + m_sps->numReorderPics[X265_MAX(0, (m_param->bEnableTemporalSubLayers - 1))]; + + if (frame->m_dpbOutputDelay < 0) + { + frame->m_cpbDelay += frame->m_dpbOutputDelay; + frame->m_dpbOutputDelay = 0; + } + + /* HRD counters are reset after a Buffering Period SEI (attached with the keyframe) */ + if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) + { + m_cpbDelay = 0; + } + + m_cpbDelay += concurrentDisplayDuration; + m_codedPicCount += frame->m_duration; } void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 59c2caacb..000baf0be 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -161,6 +161,7 @@ class Lookahead : public JobProvider Event m_outputSignal; LookaheadTLD* m_tld; x265_param* m_param; + SPS* m_sps; // needed for pic timing used in ratecontrol Lowres* m_lastNonB; int* m_scratch; // temp buffer for cutree propagate pixel* m_noiseBlurBuf; // persistent blur buffer for estimateNoise() fallback path @@ -211,7 +212,7 @@ class Lookahead : public JobProvider OrigPicBuffer* m_origPicBuf; MotionEstimatorTLD* m_metld; - Lookahead(x265_param *param, ThreadPool *pool); + Lookahead(x265_param *param, ThreadPool *pool, SPS* sps); #if DETAILED_CU_STATS int64_t m_slicetypeDecideElapsedTime; int64_t m_preLookaheadElapsedTime; @@ -224,6 +225,9 @@ class Lookahead : public JobProvider void getWorkerStats(int64_t& framecostBatchElapsedTime, int64_t& coopSliceElapsedTime, int64_t& mcstfBatchElapsedTime); #endif + int64_t m_cpbDelay; /* current cpb delay in clock ticks */ + uint64_t m_codedPicCount; /* coded picture count in clock ticks */ + bool create(); void destroy(); void stopJobs(); @@ -258,7 +262,7 @@ class Lookahead : public JobProvider void slicetypePath(Lowres **frames, int length, char(*best_paths)[X265_LOOKAHEAD_MAX + 1]); int64_t slicetypePathCost(Lowres **frames, char *path, int64_t threshold); int64_t vbvFrameCost(Lowres **frames, int p0, int p1, int b); - void calculateDurations(Frame *frame, Frame *prevFrame, int64_t *cpbDelay, int64_t *dispCount); + void calculateDurations(Frame *frame, int64_t concurrentDisplayDuration); void vbvLookahead(Lowres **frames, int numFrames, int keyframes); void aqMotion(Lowres **frames, bool bintra); void calcMotionAdaptiveQuantFrame(Lowres **frames, int p0, int p1, int b); From e82cb8e9424999028bf5162090a5f5db3c90019c Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 23 May 2026 17:00:30 +0200 Subject: [PATCH 03/13] HRD: offset dbp delay based on bframes presence. --- source/encoder/encoder.cpp | 8 +++----- source/encoder/encoder.h | 1 - source/encoder/frameencoder.cpp | 14 +++++--------- source/encoder/slicetype.cpp | 27 +++++++++++++++++++-------- source/encoder/slicetype.h | 2 +- 5 files changed, 28 insertions(+), 24 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 57fc49e15..a5d63bc84 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -1763,11 +1763,9 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_forceqp = inputPic[0]->forceqp; inFrame[layer]->m_param = (m_reconfigure || m_reconfigureRc || m_param->bConfigRCFrame) ? m_latestParam : m_param; - /* todo: pic struct defined per frame should have priority over param one. */ + inFrame[layer]->m_picStruct = inputPic[0]->picStruct; if (inFrame[layer]->m_param->pictureStructure > -1) inFrame[layer]->m_picStruct = inFrame[layer]->m_param->pictureStructure; - else - inFrame[layer]->m_picStruct = inputPic[0]->picStruct; inFrame[layer]->m_picStruct = inFrame[layer]->m_picStruct < PIC_STRUCT_COUNT ? inFrame[layer]->m_picStruct : 0; /* Set up frame timing info for slicetype and ratecontrol and the frame timebase (could change across cvs) */ @@ -4544,12 +4542,12 @@ void Encoder::configure(x265_param *p) // PF, TB and BT each have DeltaToDivisor = 1 and convey convey a full frame (or a field pair) if (p->pictureStructure > PIC_STRUCT_PROGRESSIVE_FRAME && p->pictureStructure != PIC_STRUCT_TOP_BOTTOM && p->pictureStructure != PIC_STRUCT_BOTTOM_TOP) { - x265_log(p, X265_LOG_WARNING, "Picture structure is not compatible with temporal sub layers. Not using the user-provided pic-struct.\n"); + x265_log(p, X265_LOG_WARNING, "Picture structure is not compatible with temporal sub layers. Not using the user-provided pic-struct.\n"); p->pictureStructure = -1; } if (p->bEnableFrameDuplication) { - x265_log(p, X265_LOG_WARNING, "Frame-duplication is not compatible with temporal sub layers. Disabling Frame Duplication.\n"); + x265_log(p, X265_LOG_WARNING, "Frame-duplication is not compatible with temporal sub layers. Disabling Frame Duplication.\n"); p->bEnableFrameDuplication = 0; p->dupThreshold = 0; // prevent it from being enabled below } diff --git a/source/encoder/encoder.h b/source/encoder/encoder.h index 90bda5f26..be436774e 100644 --- a/source/encoder/encoder.h +++ b/source/encoder/encoder.h @@ -218,7 +218,6 @@ class Encoder : public x265_encoder int m_numLumaWPBiFrames; // number of B frames with weighted luma reference int m_numChromaWPBiFrames; // number of B frames with weighted chroma reference int m_conformanceMode; - int m_lastBPSEI; uint32_t m_numDelayedPic; ThreadPool* m_threadPool; diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index edf60b5d8..dee453bcc 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -761,7 +761,6 @@ void FrameEncoder::compressFrame(int layer) } m_rce.encodeOrder = m_frame[layer]->m_encodeOrder; - int prevBPSEI = m_rce.encodeOrder ? m_top->m_lastBPSEI : 0; if (m_frame[layer]->m_lowres.bKeyframe) { @@ -778,8 +777,6 @@ void FrameEncoder::compressFrame(int layer) // hrdFullness() calculates the initial CPB removal delay and offset m_top->m_rateControl->hrdFullness(bpSei); bpSei->writeSEImessages(m_bs, *slice->m_sps, NAL_UNIT_PREFIX_SEI, m_nalList, m_param->bSingleSeiNal, layer); - - m_top->m_lastBPSEI = m_rce.encodeOrder; } if (m_frame[layer]->m_lowres.sliceType == X265_TYPE_IDR && m_param->bEmitIDRRecoverySEI) @@ -833,12 +830,11 @@ void FrameEncoder::compressFrame(int layer) if (vui->hrdParametersPresentFlag) { - // The m_aucpbremoval delay specifies how many clock ticks the - // access unit associated with the picture timing SEI message has to - // wait after removal of the access unit with the most recent - // buffering period SEI message - sei->m_auCpbRemovalDelay = X265_MIN(X265_MAX(1, m_frame[layer]->m_cpbDelay), (1 << hrd->cpbRemovalDelayLength));//X265_MIN(X265_MAX(1, m_rce.encodeOrder - prevBPSEI), (1 << hrd->cpbRemovalDelayLength)); - sei->m_picDpbOutputDelay = m_frame[layer]->m_dpbOutputDelay;//slice->m_sps->numReorderPics[m_frame[layer]->m_tempLayer] + poc - m_rce.encodeOrder; + // The m_aucpbremoval delay specifies how many clock ticks the access unit + // with the picture timing SEI message has to wait after removal of the + // access unit with the most recent buffering period SEI message + sei->m_auCpbRemovalDelay = X265_MIN(X265_MAX(1, m_frame[layer]->m_cpbDelay), (1 << hrd->cpbRemovalDelayLength)); + sei->m_picDpbOutputDelay = m_frame[layer]->m_dpbOutputDelay; } sei->writeSEImessages(m_bs, *slice->m_sps, NAL_UNIT_PREFIX_SEI, m_nalList, m_param->bSingleSeiNal, layer); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 038f12d04..5562c2dac 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2724,12 +2724,16 @@ void Lookahead::slicetypeDecide() } // Compute HRD CpbDpb delays - for (int i = 0; i <= bframes; ++i) { - /* to keep cpb and dpb operations in sync, use the display duration of the current picture to specify the pic - lifetime in the cpb. Given that pulldown sequences are alternating between single-doubling or doubling-tripling, - the cpb lifetime "error" is limited to 1 time clock and does not accumulate on long re-ordered sequences */ - calculateDurations(list[codedFrameOrderedIndex[i]], list[i]->m_duration); + Frame *prevFrame = NULL; + for (int i = 0; i <= bframes; ++i) + { + /* to keep cpb and dpb operations in sync, use the display duration of the current picture to specify the pic + lifetime in the cpb. Given that pulldown sequences are alternating between single-doubling or doubling-tripling, + the cpb lifetime "error" is limited to 1 time clock and does not accumulate on long re-ordered sequences */ + calculateDurations(list[codedFrameOrderedIndex[i]], prevFrame, list[i]->m_duration); + prevFrame = list[codedFrameOrderedIndex[i]]; + } } bool isKeyFrameAnalyse = (m_param->rc.cuTree || (m_param->rc.vbvBufferSize && m_param->lookaheadDepth)); @@ -2767,7 +2771,7 @@ void Lookahead::slicetypeDecide() } } -void Lookahead::calculateDurations(Frame *frame, int64_t concurrentDisplayDuration) +void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t concurrentDisplayDuration) { frame->m_cpbDelay = m_cpbDelay; frame->m_dpbOutputDelay = frame->m_displayPicCount - m_codedPicCount; @@ -2775,12 +2779,19 @@ void Lookahead::calculateDurations(Frame *frame, int64_t concurrentDisplayDurati frame->m_codedPicCount = m_codedPicCount; /* largest re-ordering at highest temporal layer */ - frame->m_dpbOutputDelay += 1 + m_sps->numReorderPics[X265_MAX(0, (m_param->bEnableTemporalSubLayers - 1))]; + frame->m_dpbOutputDelay += ((m_param->bframes > 0) ? 1 : 0) + m_sps->numReorderPics[X265_MAX(0, (m_param->bEnableTemporalSubLayers - 1))]; if (frame->m_dpbOutputDelay < 0) { frame->m_cpbDelay += frame->m_dpbOutputDelay; frame->m_dpbOutputDelay = 0; + + // fix collisions with long sequences of doubling or tripling and reordering of bref + if (prevFrame && (prevFrame->m_cpbDelay == frame->m_cpbDelay)) + { + prevFrame->m_cpbDelay -= 1; + prevFrame->m_dpbOutputDelay += 1; + } } /* HRD counters are reset after a Buffering Period SEI (attached with the keyframe) */ @@ -2789,7 +2800,7 @@ void Lookahead::calculateDurations(Frame *frame, int64_t concurrentDisplayDurati m_cpbDelay = 0; } - m_cpbDelay += concurrentDisplayDuration; + m_cpbDelay += frame->m_plannedCpbDuration; m_codedPicCount += frame->m_duration; } diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 000baf0be..a85e48c25 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -262,7 +262,7 @@ class Lookahead : public JobProvider void slicetypePath(Lowres **frames, int length, char(*best_paths)[X265_LOOKAHEAD_MAX + 1]); int64_t slicetypePathCost(Lowres **frames, char *path, int64_t threshold); int64_t vbvFrameCost(Lowres **frames, int p0, int p1, int b); - void calculateDurations(Frame *frame, int64_t concurrentDisplayDuration); + void calculateDurations(Frame *frame, Frame *prevFrame, int64_t concurrentDisplayDuration); void vbvLookahead(Lowres **frames, int numFrames, int keyframes); void aqMotion(Lowres **frames, bool bintra); void calcMotionAdaptiveQuantFrame(Lowres **frames, int p0, int p1, int b); From a6ecf44615c39a7f7c8c0a5933af1e7fab415c42 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Mon, 25 May 2026 16:53:01 +0200 Subject: [PATCH 04/13] slicetype: Support HRD timings with temporal layers. --- source/encoder/encoder.cpp | 2 +- source/encoder/slicetype.cpp | 36 +++++++++++++++++++++++++++--------- source/encoder/slicetype.h | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index a5d63bc84..d7f880b1e 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -4542,7 +4542,7 @@ void Encoder::configure(x265_param *p) // PF, TB and BT each have DeltaToDivisor = 1 and convey convey a full frame (or a field pair) if (p->pictureStructure > PIC_STRUCT_PROGRESSIVE_FRAME && p->pictureStructure != PIC_STRUCT_TOP_BOTTOM && p->pictureStructure != PIC_STRUCT_BOTTOM_TOP) { - x265_log(p, X265_LOG_WARNING, "Picture structure is not compatible with temporal sub layers. Not using the user-provided pic-struct.\n"); + x265_log(p, X265_LOG_WARNING, "Specified picture structure is not compatible with temporal sub layers. Not using the user-provided pic-struct.\n"); p->pictureStructure = -1; } if (p->bEnableFrameDuplication) diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 5562c2dac..194a0d4e3 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2308,6 +2308,8 @@ void Lookahead::slicetypeDecide() if (m_param->bEnableTemporalSubLayers > 2) { + uint8_t codedFrameOrderedIndex[X265_BFRAME_MAX+1]; + //Split the partial mini GOP into sub mini GOPs when temporal sub layers are enabled if (bframes < m_param->bframes) { @@ -2379,6 +2381,7 @@ void Lookahead::slicetypeDecide() int idx = 0; /* add non-B to output queue */ + codedFrameOrderedIndex[idx] = newbFrames; list[newbFrames]->m_reorderedPts = pts[idx++]; list[newbFrames]->m_gopOffset = 0; list[newbFrames]->m_gopId = gopId; @@ -2398,6 +2401,7 @@ void Lookahead::slicetypeDecide() list[bframes]->m_gopId = gopId; list[offset]->m_tempLayer = x265_gop_ra[gopId][j++].layer; + codedFrameOrderedIndex[idx] = offset; list[offset]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[offset]); i++; @@ -2462,6 +2466,7 @@ void Lookahead::slicetypeDecide() m_inputLock.release(); m_lastNonB = &list[newbFrames]->m_lowres; + codedFrameOrderedIndex[idx] = newbFrames; list[newbFrames]->m_reorderedPts = pts[idx++]; list[newbFrames]->m_gopOffset = 0; list[newbFrames]->m_gopId = -1; @@ -2473,6 +2478,7 @@ void Lookahead::slicetypeDecide() { if (list[i]->m_lowres.sliceType == X265_TYPE_BREF) { + codedFrameOrderedIndex[idx] = i; list[i]->m_reorderedPts = pts[idx++]; list[i]->m_gopOffset = 0; list[i]->m_gopId = -1; @@ -2488,6 +2494,7 @@ void Lookahead::slicetypeDecide() /* push all the B frames into output queue except B-ref, which already pushed into output queue */ if (list[i]->m_lowres.sliceType != X265_TYPE_BREF) { + codedFrameOrderedIndex[idx] = i; list[i]->m_reorderedPts = pts[idx++]; list[i]->m_gopOffset = 0; list[i]->m_gopId = -1; @@ -2550,6 +2557,7 @@ void Lookahead::slicetypeDecide() int idx = 0; /* add non-B to output queue */ + codedFrameOrderedIndex[idx] = bframes; list[bframes]->m_reorderedPts = pts[idx++]; list[bframes]->m_gopOffset = 0; list[bframes]->m_gopId = m_gopId; @@ -2569,12 +2577,24 @@ void Lookahead::slicetypeDecide() list[offset]->m_tempLayer = x265_gop_ra[m_gopId][j++].layer; /* add B frames to output queue */ + codedFrameOrderedIndex[idx] = offset; list[offset]->m_reorderedPts = pts[idx++]; m_outputQueue.pushBack(*list[offset]); i++; } } + /* Compute HRD CpbDpb delays */ + { + Frame *prevFrame = NULL; + for (int i = 0; i <= bframes; ++i) + { + calculateDurations(list[codedFrameOrderedIndex[i]], prevFrame); + prevFrame = list[codedFrameOrderedIndex[i]]; + } + } + + bool isKeyFrameAnalyse = (m_param->rc.cuTree || (m_param->rc.vbvBufferSize && m_param->lookaheadDepth)); if (isKeyFrameAnalyse && IS_X265_TYPE_I(m_lastNonB->sliceType)) { @@ -2723,15 +2743,12 @@ void Lookahead::slicetypeDecide() } } - // Compute HRD CpbDpb delays + /* Compute HRD CpbDpb delays */ { Frame *prevFrame = NULL; for (int i = 0; i <= bframes; ++i) { - /* to keep cpb and dpb operations in sync, use the display duration of the current picture to specify the pic - lifetime in the cpb. Given that pulldown sequences are alternating between single-doubling or doubling-tripling, - the cpb lifetime "error" is limited to 1 time clock and does not accumulate on long re-ordered sequences */ - calculateDurations(list[codedFrameOrderedIndex[i]], prevFrame, list[i]->m_duration); + calculateDurations(list[codedFrameOrderedIndex[i]], prevFrame); prevFrame = list[codedFrameOrderedIndex[i]]; } } @@ -2771,11 +2788,11 @@ void Lookahead::slicetypeDecide() } } -void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t concurrentDisplayDuration) +void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) { frame->m_cpbDelay = m_cpbDelay; frame->m_dpbOutputDelay = frame->m_displayPicCount - m_codedPicCount; - frame->m_plannedCpbDuration = concurrentDisplayDuration; + frame->m_plannedCpbDuration = frame->m_duration; frame->m_codedPicCount = m_codedPicCount; /* largest re-ordering at highest temporal layer */ @@ -2786,7 +2803,8 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t concu frame->m_cpbDelay += frame->m_dpbOutputDelay; frame->m_dpbOutputDelay = 0; - // fix collisions with long sequences of doubling or tripling and reordering of bref + /* Bref and next B-frame cpbRemovalDelay can be equal on long sequence of doubling or tripling. + larger m_dpbOutputDelay margin would fix this, but it creates buffering delay and breaks UHD BD compliance */ if (prevFrame && (prevFrame->m_cpbDelay == frame->m_cpbDelay)) { prevFrame->m_cpbDelay -= 1; @@ -2794,7 +2812,7 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame, int64_t concu } } - /* HRD counters are reset after a Buffering Period SEI (attached with the keyframe) */ + /* Buffering Period SEI (attached with the keyframe) */ if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) { m_cpbDelay = 0; diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index a85e48c25..8bd053abf 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -262,7 +262,7 @@ class Lookahead : public JobProvider void slicetypePath(Lowres **frames, int length, char(*best_paths)[X265_LOOKAHEAD_MAX + 1]); int64_t slicetypePathCost(Lowres **frames, char *path, int64_t threshold); int64_t vbvFrameCost(Lowres **frames, int p0, int p1, int b); - void calculateDurations(Frame *frame, Frame *prevFrame, int64_t concurrentDisplayDuration); + void calculateDurations(Frame *frame, Frame *prevFrame); void vbvLookahead(Lowres **frames, int numFrames, int keyframes); void aqMotion(Lowres **frames, bool bintra); void calcMotionAdaptiveQuantFrame(Lowres **frames, int p0, int p1, int b); From 4d429cd6412b4b3e64179c3b8460149e85812134 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 5 Sep 2026 00:09:53 +0200 Subject: [PATCH 05/13] ratecontrol: preliminary support for VFR. --- source/common/lowres.cpp | 3 +- source/common/lowres.h | 4 +- source/encoder/encoder.cpp | 9 +- source/encoder/frameencoder.cpp | 31 ++-- source/encoder/frameencoder.h | 2 +- source/encoder/ratecontrol.cpp | 242 +++++++++++++++++--------------- source/encoder/ratecontrol.h | 22 +-- source/encoder/slicetype.cpp | 15 +- 8 files changed, 169 insertions(+), 159 deletions(-) diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index 233f63665..e8331c91a 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -351,6 +351,7 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) filterThisGOP = false; noiseScore = -1; frameNum = poc; + cpbDurationSecs = 0.; leadingBframes = 0; indB = 0; memset(costEst, -1, sizeof(costEst)); @@ -386,7 +387,7 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) extendPicBorder(lowresPlane[1], lumaStride, width, lines, origPic->m_lumaMarginX, origPic->m_lumaMarginY); extendPicBorder(lowresPlane[2], lumaStride, width, lines, origPic->m_lumaMarginX, origPic->m_lumaMarginY); extendPicBorder(lowresPlane[3], lumaStride, width, lines, origPic->m_lumaMarginX, origPic->m_lumaMarginY); - + if (origPic->m_param->bEnableHME || origPic->m_param->bEnableTemporalFilter) { if (bEnableTemporalFilter) diff --git a/source/common/lowres.h b/source/common/lowres.h index fa64ea4f5..4aad46f91 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -181,7 +181,8 @@ struct Lowres : public ReferencePlanes int32_t noiseScore; // Raw noise score from estimateNoise() at GOP start; -1 for non-GOP-start frames double ipCostRatio; - + double cpbDurationSecs; + double dispDurationSecs; /* lookahead output data */ int64_t costEst[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; int64_t costEstAq[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; @@ -207,6 +208,7 @@ struct Lowres : public ReferencePlanes /* used for vbvLookahead */ int plannedType[X265_LOOKAHEAD_MAX + 1]; int64_t plannedSatd[X265_LOOKAHEAD_MAX + 1]; + double plannedCpbDuration[X265_LOOKAHEAD_MAX + 1]; int indB; int bframes; diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index d7f880b1e..12cc23a4b 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -430,7 +430,7 @@ void Encoder::create() initVPS(&m_vps); initSPS(&m_sps); initPPS(&m_pps); - + if (m_param->rc.vbvBufferSize) { m_offsetEmergency = (uint16_t(*)[MAX_NUM_TR_CATEGORIES][MAX_NUM_TR_COEFFS])X265_MALLOC(uint16_t, MAX_NUM_TR_CATEGORIES * MAX_NUM_TR_COEFFS * (QP_MAX_MAX - QP_MAX_SPEC)); @@ -1759,7 +1759,6 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) } } } - inFrame[layer]->m_forceqp = inputPic[0]->forceqp; inFrame[layer]->m_param = (m_reconfigure || m_reconfigureRc || m_param->bConfigRCFrame) ? m_latestParam : m_param; @@ -1769,13 +1768,15 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_picStruct = inFrame[layer]->m_picStruct < PIC_STRUCT_COUNT ? inFrame[layer]->m_picStruct : 0; /* Set up frame timing info for slicetype and ratecontrol and the frame timebase (could change across cvs) */ - inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; - inFrame[layer]->m_displayPicCount = m_dispPicCount; if (inFrame[layer]->m_param->bEmitVUITimingInfo) inFrame[layer]->m_timebase = (m_sps.vuiParameters.timingInfo.numUnitsInTick / m_sps.vuiParameters.timingInfo.timeScale); else inFrame[layer]->m_timebase = (inFrame[layer]->m_param->fpsDenom / inFrame[layer]->m_param->fpsNum); + inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; + inFrame[layer]->m_displayPicCount = m_dispPicCount; + inFrame[layer]->m_lowres.dispDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; + /* update presentation counts (decoder ticks count) */ m_dispPicCount += inFrame[layer]->m_duration; diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index dee453bcc..e3da3cb2a 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -106,7 +106,6 @@ void FrameEncoder::destroy() if (m_param->bEmitHRDSEI || !!m_param->interlaceMode) { delete m_rce.picTimingSEI; - delete m_rce.hrdTiming; } } @@ -129,7 +128,7 @@ bool FrameEncoder::init(Encoder *top, int numRows, int numCols) m_vbvResetTriggerRow = X265_MALLOC(int, m_param->maxSlices); ok &= !!m_sliceBaseRow; m_sliceGroupSize = (uint16_t)(m_numRows + m_param->maxSlices - 1) / m_param->maxSlices; - uint32_t sliceGroupSizeAccu = (m_numRows << 8) / m_param->maxSlices; + uint32_t sliceGroupSizeAccu = (m_numRows << 8) / m_param->maxSlices; uint32_t rowSum = sliceGroupSizeAccu; uint32_t sidx = 0; for (uint32_t i = 0; i < m_numRows; i++) @@ -183,9 +182,7 @@ bool FrameEncoder::init(Encoder *top, int numRows, int numCols) if (m_param->bEmitHRDSEI || !!m_param->interlaceMode) { m_rce.picTimingSEI = new SEIPictureTiming; - m_rce.hrdTiming = new HRDTiming; - - ok &= m_rce.picTimingSEI && m_rce.hrdTiming; + ok &= !!m_rce.picTimingSEI; } if (m_param->noiseReductionIntra || m_param->noiseReductionInter) @@ -543,7 +540,7 @@ void FrameEncoder::compressFrame(int layer) #endif if (strlen(m_param->analysisLoad)) { - for (int list = 0; list < slice->isInterB() + 1; list++) + for (int list = 0; list < slice->isInterB() + 1; list++) { for (int plane = 0; plane < (m_param->internalCsp != X265_CSP_I400 ? 3 : 1); plane++) { @@ -727,9 +724,9 @@ void FrameEncoder::compressFrame(int layer) WaveFront::setLayerId(layer); /* reset entropy coders and compute slice id */ m_entropyCoder.load(m_initSliceContext); - for (uint32_t sliceId = 0; sliceId < m_param->maxSlices; sliceId++) + for (uint32_t sliceId = 0; sliceId < m_param->maxSlices; sliceId++) for (uint32_t row = m_sliceBaseRow[sliceId]; row < m_sliceBaseRow[sliceId + 1]; row++) - m_rows[row].init(m_initSliceContext, sliceId); + m_rows[row].init(m_initSliceContext, sliceId); // reset slice counter for rate control update m_sliceCnt = 0; @@ -802,14 +799,14 @@ void FrameEncoder::compressFrame(int layer) if (m_param->interlaceMode > 0) { if( m_param->interlaceMode == 2 ) - { + { // m_picStruct should be set to 3 or 4 when field feature is enabled if (m_param->bField) // 3: Top field, bottom field, in that order; 4: Bottom field, top field, in that order sei->m_picStruct = (slice->m_fieldNum == 1) ? 4 : 3; else sei->m_picStruct = (poc & 1) ? 1 /* top */ : 2 /* bottom */; - } + } else if (m_param->interlaceMode == 1) { if (m_param->bField) @@ -830,9 +827,9 @@ void FrameEncoder::compressFrame(int layer) if (vui->hrdParametersPresentFlag) { - // The m_aucpbremoval delay specifies how many clock ticks the access unit - // with the picture timing SEI message has to wait after removal of the - // access unit with the most recent buffering period SEI message + /* The m_aucpbremoval delay specifies how many clock ticks the access unit + * with the picture timing SEI message has to wait after removal of the + * access unit with the most recent buffering period SEI message */ sei->m_auCpbRemovalDelay = X265_MIN(X265_MAX(1, m_frame[layer]->m_cpbDelay), (1 << hrd->cpbRemovalDelayLength)); sei->m_picDpbOutputDelay = m_frame[layer]->m_dpbOutputDelay; } @@ -919,7 +916,7 @@ void FrameEncoder::compressFrame(int layer) for (uint32_t sliceId = 0; sliceId < m_param->maxSlices; sliceId++) m_rows[m_sliceBaseRow[sliceId]].active = true; - + if (m_param->bEnableWavefront) { int i = 0; @@ -977,7 +974,7 @@ void FrameEncoder::compressFrame(int layer) m_mref[l][ref].applyWeight(rowIdx, m_numRows, sliceEndRow, sliceId); } } - + enableRowEncoder(m_row_to_idx[row]); /* clear external dependency for this row */ if (m_top->m_threadedME && !slice->isIntra()) @@ -1065,7 +1062,7 @@ void FrameEncoder::compressFrame(int layer) PicYuv *reconPic = m_frame[layer]->m_reconPic[0]; uint32_t height = reconPic->m_picHeight; initDecodedPictureHashSEI(0, 0, height, layer); - } + } if (m_param->bDynamicRefine && m_top->m_startPoint <= m_frame[layer]->m_encodeOrder) //Avoid collecting data that will not be used by future frames. collectDynDataFrame(layer); @@ -2040,7 +2037,7 @@ void FrameEncoder::processRowEncoder(int intRow, ThreadLocalData& tld, int layer { uint32_t startAddr = m_sliceBaseRow[sliceId] * numCols; uint32_t finishAddr = startAddr + rowCount * numCols; - + for (uint32_t cuAddr = startAddr; cuAddr < finishAddr; cuAddr++) m_rowSliceTotalBits[sliceId] += curEncData.m_cuStat[cuAddr].totalBits; } diff --git a/source/encoder/frameencoder.h b/source/encoder/frameencoder.h index d06c22f12..37216775e 100644 --- a/source/encoder/frameencoder.h +++ b/source/encoder/frameencoder.h @@ -217,7 +217,7 @@ class FrameEncoder : public WaveFront, public Thread CTURow* m_rows; uint16_t m_sliceAddrBits; uint32_t m_sliceGroupSize; - uint32_t* m_sliceBaseRow; + uint32_t* m_sliceBaseRow; uint32_t* m_sliceMaxBlockRow; int64_t m_rowSliceTotalBits[2]; RateControlEntry m_rce; diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp index 21fb9e086..794872446 100644 --- a/source/encoder/ratecontrol.cpp +++ b/source/encoder/ratecontrol.cpp @@ -199,7 +199,7 @@ RateControl::RateControl(x265_param& p, Encoder *top) m_partialResidualCost = 0; m_rateFactorMaxIncrement = 0; m_rateFactorMaxDecrement = 0; - m_fps = (double)m_param->fpsNum / m_param->fpsDenom; + m_timebase = (double)m_param->fpsDenom / m_param->fpsNum; m_startEndOrder.set(0); m_bTerminated = false; m_finalFrameCount.set(0); @@ -235,7 +235,6 @@ RateControl::RateControl(x265_param& p, Encoder *top) m_isAbr = m_param->rc.rateControlMode != X265_RC_CQP && !m_param->rc.bStatRead; m_2pass = m_param->rc.rateControlMode != X265_RC_CQP && m_param->rc.bStatRead; m_bitrate = m_param->rc.bitrate * 1000; - m_frameDuration = (double)m_param->fpsDenom / m_param->fpsNum; m_qp = m_param->rc.qp; m_lastRceq = 1; /* handles the cmplxrsum when the previous frame cost is zero */ m_shortTermCplxSum = 0; @@ -385,7 +384,7 @@ bool RateControl::initCUTreeSharedMem() itemSize += sizeof(uint16_t) * m_ncu; } - int32_t itemCnt = X265_MIN(m_param->keyframeMax, (int)(m_fps + 0.5)); + int32_t itemCnt = X265_MIN(m_param->keyframeMax, (int)(1./m_timebase + 0.5)); itemCnt *= GOP_CNT_CU_TREE; char shrname[MAX_SHR_NAME_LEN] = { 0 }; @@ -404,10 +403,10 @@ bool RateControl::initCUTreeSharedMem() void RateControl::initVBV(const SPS& sps) { /* We don't support changing the ABR bitrate right now, - * so if the stream starts as CBR, keep it CBR. */ - if (m_param->rc.vbvBufferSize < (int)(m_param->rc.vbvMaxBitrate / m_fps)) + * so if the stream starts as CBR, keep it CBR. */ + if (m_param->rc.vbvBufferSize < (int)(m_param->rc.vbvMaxBitrate * m_timebase)) { - m_param->rc.vbvBufferSize = (int)(m_param->rc.vbvMaxBitrate / m_fps); + m_param->rc.vbvBufferSize = (int)(m_param->rc.vbvMaxBitrate * m_timebase); x265_log(m_param, X265_LOG_WARNING, "VBV buffer size cannot be smaller than one frame, using %d kbit\n", m_param->rc.vbvBufferSize); } @@ -420,7 +419,7 @@ void RateControl::initVBV(const SPS& sps) vbvBufferSize = hrd->cpbSizeValue << (hrd->cpbSizeScale + CPB_SHIFT); vbvMaxBitrate = hrd->bitRateValue << (hrd->bitRateScale + BR_SHIFT); } - m_bufferRate = static_cast(vbvMaxBitrate) / m_fps; + m_bufferRate = static_cast(vbvMaxBitrate) * m_timebase; m_vbvMaxRate = static_cast(vbvMaxBitrate); m_bufferSize = static_cast(vbvBufferSize); m_singleFrameVbv = m_bufferRate * 1.1 > m_bufferSize; @@ -459,13 +458,16 @@ bool RateControl::init(const SPS& sps) m_encodedBits = 0; m_encodedSegmentBits = 0; m_framesDone = 0; + m_durationDone = 0; m_segDur = 0; m_residualCost = 0; m_partialResidualCost = 0; m_amortizeFraction = 0.85; m_amortizeFrames = 75; m_bRcReConfig = false; - if (m_param->totalFrames && m_param->totalFrames <= 2 * m_fps && m_param->rc.bStrictCbr) /* Strict CBR segment encode */ + + /* subpar if the frames' duration does not match the timebase */ + if (m_param->totalFrames && m_param->totalFrames <= 2. / m_timebase && m_param->rc.bStrictCbr) /* Strict CBR segment encode */ { m_amortizeFraction = 0.85; m_amortizeFrames = m_param->totalFrames / 2; @@ -492,7 +494,8 @@ bool RateControl::init(const SPS& sps) /* estimated ratio that produces a reasonable QP for the first I-frame */ m_cplxrSum = .01 * pow(7.0e5, m_qCompress) * pow(m_ncu, 0.5) * tuneCplxFactor; - m_wantedBitsWindow = m_bitrate * m_frameDuration; + /* possibly a bad start if the first frame is very long */ + m_wantedBitsWindow = m_bitrate * m_timebase; m_accumPNorm = .01; m_accumPQp = (m_param->rc.rateControlMode == X265_RC_CRF ? CRF_INIT_QP : ABR_INIT_QP_MIN) * m_accumPNorm; @@ -672,6 +675,7 @@ bool RateControl::init(const SPS& sps) } /* read stats */ p = statsIn; + uint64_t totalDuration = 0; for (int i = 0; i < m_numEntries; i++) { RateControlEntry *rce, *rcePocOrder; @@ -696,10 +700,10 @@ bool RateControl::init(const SPS& sps) if (!m_param->bMultiPassOptRPS) { int scenecut = 0; - e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf sc:%d", + e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf sc:%d cpbd:%u, dspd:%u", &picType, &qpRc, &qpAq, &qNoVbv, &qRceq, &rce->coeffBits, &rce->mvBits, &rce->miscBits, &rce->iCuCount, &rce->pCuCount, - &rce->skipCuCount, &scenecut); + &rce->skipCuCount, &scenecut, &rce->cpbDuration, &rce->frameDuration); rcePocOrder->scenecut = scenecut != 0; } else @@ -708,14 +712,15 @@ bool RateControl::init(const SPS& sps) char bUsed[40]; memset(deltaPOC, 0, sizeof(deltaPOC)); memset(bUsed, 0, sizeof(bUsed)); - e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf nump:%d numnegp:%d numposp:%d deltapoc:%127s bused:%39s", + e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf nump:%d numnegp:%d numposp:%d deltapoc:%127s bused:%39s cpbd:%u, dspd:%u", &picType, &qpRc, &qpAq, &qNoVbv, &qRceq, &rce->coeffBits, &rce->mvBits, &rce->miscBits, &rce->iCuCount, &rce->pCuCount, - &rce->skipCuCount, &rce->rpsData.numberOfPictures, &rce->rpsData.numberOfNegativePictures, &rce->rpsData.numberOfPositivePictures, deltaPOC, bUsed); + &rce->skipCuCount, &rce->rpsData.numberOfPictures, &rce->rpsData.numberOfNegativePictures, &rce->rpsData.numberOfPositivePictures, deltaPOC, bUsed, &rce->cpbDuration, &rce->frameDuration); splitdeltaPOC(deltaPOC, rce); splitbUsed(bUsed, rce); rce->rpsIdx = -1; } + totalDuration += rce->frameDuration; rce->keptAsRef = true; rce->isIdr = false; if (picType == 'b' || picType == 'p') @@ -742,6 +747,8 @@ bool RateControl::init(const SPS& sps) rce->qRceq = qRceq; p = next; } + m_durationDone = (double)totalDuration * m_timebase; + X265_FREE(statsBuf); if (m_param->rc.rateControlMode != X265_RC_CQP) { @@ -837,7 +844,7 @@ void RateControl::reconfigureRC() m_param->rc.vbvBufferSize = x265_clip3(0, 8000000, m_param->rc.vbvBufferSize); m_param->rc.vbvMaxBitrate = x265_clip3(0, 8000000, m_param->rc.vbvMaxBitrate); if (m_param->reconfigWindowSize) - m_param->rc.vbvMaxBitrate = (int)(m_param->rc.vbvMaxBitrate * (double)(m_fps / m_param->reconfigWindowSize)); + m_param->rc.vbvMaxBitrate = (int)(m_param->rc.vbvMaxBitrate * (double)(1. / (m_timebase * m_param->reconfigWindowSize))); if (m_param->rc.vbvMaxBitrate < m_param->rc.bitrate && m_param->rc.rateControlMode == X265_RC_ABR) { @@ -845,15 +852,15 @@ void RateControl::reconfigureRC() m_param->rc.bitrate = m_param->rc.vbvMaxBitrate; } - if (m_param->rc.vbvBufferSize < (int)(m_param->rc.vbvMaxBitrate / m_fps)) + if (m_param->rc.vbvBufferSize < (int)(m_param->rc.vbvMaxBitrate * m_timebase)) { - m_param->rc.vbvBufferSize = (int)(m_param->rc.vbvMaxBitrate / m_fps); + m_param->rc.vbvBufferSize = (int)(m_param->rc.vbvMaxBitrate * m_timebase); x265_log(m_param, X265_LOG_WARNING, "VBV buffer size cannot be smaller than one frame, using %d kbit\n", m_param->rc.vbvBufferSize); } uint64_t vbvBufferSize = m_param->rc.vbvBufferSize * 1000ULL; uint64_t vbvMaxBitrate = m_param->rc.vbvMaxBitrate * 1000ULL; - m_bufferRate = static_cast(vbvMaxBitrate) / m_fps; + m_bufferRate = static_cast(vbvMaxBitrate) * m_timebase; m_vbvMaxRate = static_cast(vbvMaxBitrate); m_bufferSize = static_cast(vbvBufferSize); m_singleFrameVbv = m_bufferRate * 1.1 > m_bufferSize; @@ -920,9 +927,8 @@ void RateControl::initHRD(SPS& sps) // arbitrary #define MAX_DURATION 0.5 - TimingInfo *time = &sps.vuiParameters.timingInfo; - int maxCpbOutputDelay = (int)(X265_MIN(m_param->keyframeMax * MAX_DURATION * time->timeScale / time->numUnitsInTick, INT_MAX)); - int maxDpbOutputDelay = (int)(sps.maxDecPicBuffering[sps.maxTempSubLayers - 1] * MAX_DURATION * time->timeScale / time->numUnitsInTick); + int maxCpbOutputDelay = (int)(X265_MIN(m_param->keyframeMax * MAX_DURATION / m_timebase, INT_MAX)); + int maxDpbOutputDelay = (int)(sps.maxDecPicBuffering[sps.maxTempSubLayers - 1] * MAX_DURATION / m_timebase); int maxDelay = (int)(90000.0 * cpbSizeUnscale / bitRateUnscale + 0.5); hrd->initialCpbRemovalDelayLength = 2 + x265_clip3(4, 22, 32 - calcLength(maxDelay)); @@ -941,7 +947,6 @@ bool RateControl::analyseABR2Pass(uint64_t allAvailableBits) double expectedBits; double *qScale, *blurredQscale; double baseCplx = m_ncu * (m_param->bframes ? 120 : 80); - double clippedDuration = CLIP_DURATION(m_frameDuration) / BASE_FRAME_DURATION; /* Blur complexities, to reduce local fluctuation of QP. * We don't blur the QPs directly, because then one very simple frame * could drag down the QP of a nearby complex frame and give it more @@ -957,6 +962,8 @@ bool RateControl::analyseABR2Pass(uint64_t allAvailableBits) { int index = i+j; RateControlEntry *rcj = &m_rce2Pass[index]; + double clippedDuration = CLIP_DURATION(rcj->frameDuration * m_timebase) / BASE_FRAME_DURATION; + weight *= 1 - pow(rcj->iCuCount / m_ncu, 2); if (weight < 0.0001) break; @@ -970,6 +977,8 @@ bool RateControl::analyseABR2Pass(uint64_t allAvailableBits) { int index = i-j; RateControlEntry *rcj = &m_rce2Pass[index]; + double clippedDuration = CLIP_DURATION(rcj->frameDuration * m_timebase) / BASE_FRAME_DURATION; + gaussianWeight = weight * exp(-j * j / 200.0); weightSum += gaussianWeight; cplxSum += gaussianWeight * (qScale2bits(rcj, 1) - rcj->miscBits) / clippedDuration; @@ -1085,7 +1094,7 @@ bool RateControl::analyseABR2Pass(uint64_t allAvailableBits) x265_log(m_param, X265_LOG_WARNING, "Error: 2pass curve failed to converge\n"); x265_log(m_param, X265_LOG_WARNING, "target: %.2f kbit/s, expected: %.2f kbit/s, avg QP: %.4f\n", (double)m_param->rc.bitrate, - expectedBits * m_fps / (m_numEntries * 1000.), + expectedBits / (m_durationDone * 1000.), avgq); if (expectedBits < allAvailableBits && avgq < m_param->rc.qpMin + 2) { @@ -1115,9 +1124,9 @@ bool RateControl::analyseABR2Pass(uint64_t allAvailableBits) bool RateControl::initPass2() { uint64_t allConstBits = 0; - uint64_t allAvailableBits = uint64_t(m_param->rc.bitrate * 1000. * m_numEntries * m_frameDuration); + uint64_t allAvailableBits = uint64_t(m_param->rc.bitrate * 1000. * m_durationDone); int startIndex, endIndex; - int fps = X265_MIN(m_param->keyframeMax, (int)(m_fps + 0.5)); + int fps = X265_MIN(m_param->keyframeMax, (int)(1./m_timebase + 0.5)); int distance = fps << 1; distance = distance > m_param->keyframeMax ? (m_param->keyframeMax << 1) : m_param->keyframeMax; startIndex = endIndex = 0; @@ -1138,7 +1147,7 @@ bool RateControl::initPass2() if (allAvailableBits < allConstBits) { x265_log(m_param, X265_LOG_ERROR, "requested bitrate is too low. estimated minimum is %d kbps\n", - (int)(allConstBits * m_fps / (m_numEntries - m_start) * 1000.)); + (int)(1000. * allConstBits / m_durationDone)); return false; } if (!analyseABR2Pass(allAvailableBits)) @@ -1184,9 +1193,11 @@ bool RateControl::initPass2() for (startIndex = m_start; startIndex < m_numEntries; startIndex++) { + RateControlEntry *rce = &m_rce2Pass[startIndex]; + m_shortTermCplxSum *= 0.5; m_shortTermCplxCount *= 0.5; - m_shortTermCplxSum += m_rce2Pass[startIndex].currentSatd / (CLIP_DURATION(m_frameDuration) / BASE_FRAME_DURATION); + m_shortTermCplxSum += rce->currentSatd / (CLIP_DURATION(rce->frameDuration * m_timebase) / BASE_FRAME_DURATION); m_shortTermCplxCount++; } @@ -1366,6 +1377,8 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode rce->keptAsRef = IS_REFERENCED(curFrame); m_predType = getPredictorType(curFrame->m_lowres.sliceType, m_sliceType); rce->poc = m_curSlice->m_poc; + rce->cpbDuration = curFrame->m_plannedCpbDuration; + rce->frameDuration = curFrame->m_duration; if (m_param->bEnableSBRC) { @@ -1407,8 +1420,8 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode } } } - - + + if (m_param->bResetZoneConfig) { /* change ratecontrol stats for next zone if specified */ @@ -1504,7 +1517,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode else { /* 1.5 * MaxLumaSr * (AuCpbRemovalTime[ n ] - AuCpbRemovalTime[ n - 1 ]) / MinCr */ - rce->frameSizeMaximum = 8 * 1.5 * enc->m_vps.ptl.maxLumaSrForLevel * m_frameDuration / mincr; + rce->frameSizeMaximum = 8 * 1.5 * enc->m_vps.ptl.maxLumaSrForLevel * rce->cpbDuration * m_timebase / mincr; } rce->frameSizeMaximum *= m_param->maxAUSizeFactor; } @@ -1531,13 +1544,14 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode m_qp = int(rce->qpaRc + 0.5); rce->frameSizePlanned = qScale2bits(rce, rce->qScale); m_framesDone++; + m_durationDone += rce->frameDuration * m_timebase; return m_qp; } else - { + { int index = m_encOrder[rce->poc]; + double totalDuration = m_rce2Pass[index].frameDuration * m_timebase; index++; - double totalDuration = m_frameDuration; for (int j = 0; totalDuration < 1.0 && index < m_numEntries; j++) { switch (m_rce2Pass[index].sliceType) @@ -1556,7 +1570,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode } curFrame->m_lowres.plannedSatd[j] = m_rce2Pass[index].currentSatd; - totalDuration += m_frameDuration; + totalDuration += m_rce2Pass[index].frameDuration * m_timebase; index++; } } @@ -1594,7 +1608,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode m_isPatternPresent = true; } /* For a scenecut that occurs within the mini-gop, enable scene transition - * switch until the next mini-gop to ensure a min qp for all the frames within + * switch until the next mini-gop to ensure a min qp for all the frames within * the scene-transition mini-gop */ double q = x265_qScale2qp(rateEstimateQscale(curFrame, rce)); @@ -1619,7 +1633,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode else m_qp = m_qpConstant[m_sliceType]; curEncData.m_avgQpAq = curEncData.m_avgQpRc = m_qp; - + x265_zone* zone = getZone(); if (zone) { @@ -1655,6 +1669,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode } } m_framesDone++; + m_durationDone += rce->frameDuration * m_timebase; return m_qp; } @@ -1764,8 +1779,9 @@ bool RateControl::findUnderflow(double *fills, int *t0, int *t1, int over, int e int start = -1, end = -1; for (int i = *t0; i <= endPos; i++) { - fill += (m_frameDuration * m_vbvMaxRate - - qScale2bits(&m_rce2Pass[i], m_rce2Pass[i].newQScale)) * parity; + RateControlEntry *rce = &m_rce2Pass[i]; + fill += (rce->frameDuration * m_timebase * m_vbvMaxRate - + qScale2bits(rce, rce->newQScale)) * parity; fill = x265_clip3(0.0, m_bufferSize, fill); fills[i] = fill; if (fill <= bufferMin || i == 0) @@ -1865,16 +1881,18 @@ double RateControl::tuneAbrQScaleFromFeedback(double qScale) double abrBuffer = 2 * m_rateTolerance * m_bitrate; /* use framesDone instead of POC as poc count is not serial with bframes enabled */ double overflow = 1.0; - double timeDone = (double)(m_framesDone - m_param->frameNumThreads + 1) * m_frameDuration; + /* - m_param->frameNumThreads + 1 ? */ + double timeDone = m_durationDone; double wantedBits = timeDone * m_bitrate; int64_t encodedBits = m_totalBits; - if (m_param->totalFrames && m_param->totalFrames <= 2 * m_fps) + /* */ + if (m_param->totalFrames && m_param->totalFrames <= 2. / m_timebase) { - abrBuffer = m_param->totalFrames * (m_bitrate / m_fps); + abrBuffer = m_param->totalFrames * (m_bitrate * m_timebase); encodedBits = m_encodedBits; } - if (wantedBits > 0 && encodedBits > 0 && (!m_partialResidualFrames || + if (wantedBits > 0 && encodedBits > 0 && (!m_partialResidualFrames || m_param->rc.bStrictCbr || m_isGrainEnabled)) { abrBuffer *= X265_MAX(1, sqrt(timeDone)); @@ -1909,15 +1927,16 @@ double RateControl::tuneQScaleForZone(RateControlEntry *rce, double qScale) return qScale; } -double RateControl::tuneQScaleForGrain(double rcOverflow) +double RateControl::tuneQScaleForGrain(RateControlEntry *rce, double rcOverflow) { double qpstep = rcOverflow > 1.1 ? rcOverflow : m_lstep; double qScaleAvg = x265_qp2qScale(m_avgPFrameQp); double q = m_lastQScaleFor[P_SLICE]; int curQp = int (x265_qScale2qp(m_lastQScaleFor[P_SLICE]) + 0.5); - double curBitrate = m_qpToEncodedBits[curQp] * int(m_fps + 0.5); + /* replace 1/timebase with the running average of the framerate */ + double curBitrate = m_qpToEncodedBits[curQp] * int(1./m_timebase + 0.5); int newQp = rcOverflow > 1.1 ? curQp + 2 : rcOverflow > 1 ? curQp + 1 : curQp - 1 ; - double projectedBitrate = int(m_fps + 0.5) * m_qpToEncodedBits[newQp]; + double projectedBitrate = int(1./m_timebase + 0.5) * m_qpToEncodedBits[newQp]; if (curBitrate > 0 && projectedBitrate > 0) q = std::abs(projectedBitrate - m_bitrate) < std::abs (curBitrate - m_bitrate) ? x265_qp2qScale(newQp) : m_lastQScaleFor[P_SLICE]; else @@ -2024,7 +2043,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) if (m_isSceneTransition) { q = X265_MAX(ABR_SCENECUT_INIT_QP_MIN, q); - double minScenecutQscale =x265_qp2qScale(ABR_SCENECUT_INIT_QP_MIN); + double minScenecutQscale =x265_qp2qScale(ABR_SCENECUT_INIT_QP_MIN); m_lastQScaleFor[P_SLICE] = X265_MAX(minScenecutQscale, m_lastQScaleFor[P_SLICE]); } @@ -2054,7 +2073,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) m_avgPFrameQp = m_avgPFrameQp == 0 ? rce->qpNoVbv : m_avgPFrameQp; if (overflow != 1) { - qScale = tuneQScaleForGrain(overflow); + qScale = tuneQScaleForGrain(rce, overflow); q = x265_qScale2qp(qScale); } rce->qpNoVbv = q; @@ -2096,7 +2115,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) if (!m_2pass || m_param->bliveVBV2pass || (m_2pass && m_param->rc.rateControlMode == X265_RC_CRF && m_param->rc.bEncFocusedFramesOnly)) { - /* clip qp to permissible range after vbv-lookahead estimation to avoid possible + /* clip qp to permissible range after vbv-lookahead estimation to avoid possible * mispredictions by initial frame size predictors */ qScale = clipQscale(curFrame, rce, qScale); @@ -2140,9 +2159,9 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) { m_predictedBits = m_totalBits; if (rce->encodeOrder < m_param->frameNumThreads) - m_predictedBits += (int64_t)(rce->encodeOrder * m_bitrate / m_fps); + m_predictedBits += (int64_t)(rce->encodeOrder * m_bitrate * rce->cpbDuration * m_timebase); else - m_predictedBits += (int64_t)(m_param->frameNumThreads * m_bitrate / m_fps); + m_predictedBits += (int64_t)(m_param->frameNumThreads * m_bitrate * rce->cpbDuration * m_timebase); } /* Adjust ABR buffer based on distance to the end of the video. */ if (m_numEntries > rce->encodeOrder) @@ -2206,7 +2225,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) { /* Do not overflow vbv */ double expectedSize = qScale2bits(rce, q); - double expectedVbv = m_bufferFill + m_bufferRate - expectedSize; + double expectedVbv = m_bufferFill + m_bufferRate * rce->cpbDuration - expectedSize; double expectedFullness = rce->expectedVbv / m_bufferSize; double qmax = q * (2 - expectedFullness); double sizeConstraint = 1 + expectedFullness; @@ -2219,7 +2238,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) { q *= 1.05; expectedSize = qScale2bits(rce, q); - expectedVbv = m_bufferFill + m_bufferRate - expectedSize; + expectedVbv = m_bufferFill + m_bufferRate * rce->cpbDuration - expectedSize; } } else @@ -2251,7 +2270,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) double lqmax = m_lmax[m_sliceType]; m_shortTermCplxSum *= 0.5; m_shortTermCplxCount *= 0.5; - m_shortTermCplxSum += m_currentSatd / (CLIP_DURATION(m_frameDuration) / BASE_FRAME_DURATION); + m_shortTermCplxSum += m_currentSatd / (CLIP_DURATION(rce->frameDuration * m_timebase) / BASE_FRAME_DURATION); m_shortTermCplxCount++; /* coeffBits to be used in 2-pass */ rce->coeffBits = (int)m_currentSatd; @@ -2312,13 +2331,14 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) q = (!m_partialResidualFrames) ? tunedQScale : initialQScale; bool isEncodeEnd = (m_param->totalFrames && m_framesDone > 0.75 * m_param->totalFrames) ? 1 : 0; - bool isEncodeBeg = m_framesDone < (int)(m_fps + 0.5); + /* beginning: less than one gop encoded */ + bool isEncodeBeg = m_durationDone < m_param->keyframeMax * m_timebase; if (m_isGrainEnabled) { if(m_sliceType!= I_SLICE && m_framesDone && !isEncodeEnd && ((overflow < 1.05 && overflow > 0.95) || isEncodeBeg)) { - q = tuneQScaleForGrain(overflow); + q = tuneQScaleForGrain(rce, overflow); } } } @@ -2417,7 +2437,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) /* Always use up the whole VBV in this case. */ if (m_singleFrameVbv) - rce->frameSizePlanned = m_bufferRate; + rce->frameSizePlanned = m_bufferRate * rce->cpbDuration; /* Limit planned size by MinCR */ if (m_isVbv) rce->frameSizePlanned = X265_MIN(rce->frameSizePlanned, rce->frameSizeMaximum); @@ -2487,11 +2507,12 @@ void RateControl::checkAndResetABR(RateControlEntry* rce, bool isFrameDone) // Check if current Slice is a scene cut that follows low detailed/blank frames if (rce->lastSatd > 4 * rce->movingAvgSum || rce->scenecut || rce->isFadeEnd) { + double frameDurSec = rce->frameDuration * m_timebase; if (!m_isAbrReset && rce->movingAvgSum > 0 && (m_isPatternPresent || !m_param->bframes)) { int pos = X265_MAX(m_sliderPos - m_param->frameNumThreads, 0); - int64_t shrtTermWantedBits = (int64_t) (X265_MIN(pos, s_slidingWindowFrames) * m_bitrate * m_frameDuration); + int64_t shrtTermWantedBits = (int64_t) (X265_MIN(pos, s_slidingWindowFrames) * m_bitrate * frameDurSec); int64_t shrtTermTotalBitsSum = 0; // Reset ABR if prev frames are blank to prevent further sudden overflows/ high bit rate spikes. for (int i = 0; i < s_slidingWindowFrames ; i++) @@ -2504,7 +2525,7 @@ void RateControl::checkAndResetABR(RateControlEntry* rce, bool isFrameDone) // Reduce tune complexity factor for scenes that follow blank frames double tuneCplxFactor = (m_ncu > 3600 && m_param->rc.cuTree && !m_param->rc.hevcAq) ? 2.5 : m_param->rc.hevcAq ? 1.5 : m_isGrainEnabled ? 1.9 : 1.0; m_cplxrSum /= tuneCplxFactor; - m_shortTermCplxSum = rce->lastSatd / (CLIP_DURATION(m_frameDuration) / BASE_FRAME_DURATION); + m_shortTermCplxSum = rce->lastSatd / (CLIP_DURATION(frameDurSec) / BASE_FRAME_DURATION); m_shortTermCplxCount = 1; m_isAbrReset = true; m_lastAbrResetPoc = rce->poc; @@ -2573,11 +2594,13 @@ double RateControl::tuneQscaleForSBRC(Frame* curFrame, double q) double curBits = predictSize(&m_pred[predType], q, (double)satd); frameBitsTotal += curBits; lookaheadBits += curBits; - lookaheadDur += m_frameDuration; - totalDuration += m_frameDuration; + lookaheadDur += curFrame->m_lowres.plannedCpbDuration[i]; + totalDuration += curFrame->m_lowres.plannedCpbDuration[i]; } //Check for segment buffer overflow and adjust QP accordingly - double segDur = m_param->keyframeMax / m_fps; + + /* should this use lookaheadDur rather? */ + double segDur = m_param->keyframeMax * m_timebase; double allowedSize = m_vbvMaxRate * segDur; double remDur = segDur - totalDuration; double remainingBits = frameBitsTotal; @@ -2635,7 +2658,8 @@ double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q) curBits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd); double bufferFillCur = m_bufferFill - curBits; double targetFill; - double totalDuration = m_frameDuration; + double lastDuration = curFrame->m_plannedCpbDuration * curFrame->m_timebase; + double totalDuration = 0; frameQ[P_SLICE] = m_sliceType == I_SLICE ? q * m_param->rc.ipFactor : (m_sliceType == B_SLICE ? q / m_param->rc.pbFactor : q); frameQ[B_SLICE] = frameQ[P_SLICE] * m_param->rc.pbFactor; frameQ[I_SLICE] = frameQ[P_SLICE] / m_param->rc.ipFactor; @@ -2643,21 +2667,27 @@ double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q) bool iter = true; for (int j = 0; bufferFillCur >= 0 && iter ; j++) { + totalDuration += lastDuration; + double thisCpbFill = bufferFillCur + m_vbvMaxRate * lastDuration; + bufferFillCur += X265_MIN(thisCpbFill, X265_MAX(0, m_bufferSize - thisCpbFill)); + int type = curFrame->m_lowres.plannedType[j]; if (type == X265_TYPE_AUTO || totalDuration >= 1.0) break; - totalDuration += m_frameDuration; - double wantedFrameSize = m_vbvMaxRate * m_frameDuration; - if (bufferFillCur + wantedFrameSize <= m_bufferSize) - bufferFillCur += wantedFrameSize; + int64_t satd = curFrame->m_lowres.plannedSatd[j] >> (X265_DEPTH - 8); type = IS_X265_TYPE_I(type) ? I_SLICE : IS_X265_TYPE_B(type) ? B_SLICE : P_SLICE; int predType = getPredictorType(curFrame->m_lowres.plannedType[j], type); + curBits = predictSize(&m_pred[predType], frameQ[type], (double)satd); bufferFillCur -= curBits; + if (!m_param->bResetZoneConfig && ((uint64_t)j == (m_param->reconfigWindowSize - 1))) iter = false; + lastDuration = curFrame->m_lowres.plannedCpbDuration[j]; } + totalDuration = X265_MAX(totalDuration, m_timebase); /* if bufferFillCurr had a underflow already */ + if (rce->vbvEndAdj) { bool loopBreak = false; @@ -2749,8 +2779,8 @@ double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q) double pbits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd); if (pbits > rce->frameSizeMaximum) q *= pbits / rce->frameSizeMaximum; - /* To detect frames that are more complex in SATD costs compared to prev window, yet - * lookahead vbv reduces its qscale by half its value. Be on safer side and avoid drastic + /* To detect frames that are more complex in SATD costs compared to prev window, yet + * lookahead vbv reduces its qscale by half its value. Be on safer side and avoid drastic * qscale reductions for frames high in complexity */ bool mispredCheck = rce->movingAvgSum && m_currentSatd >= rce->movingAvgSum && q <= q0 / 2; if (!m_isCbr || ((m_isAbr || m_2pass) && mispredCheck)) @@ -2825,8 +2855,8 @@ double RateControl::predictRowsSizeSum(Frame* curFrame, RateControlEntry* rce, d if (picType == I_SLICE || qScale >= refQScale) { - if (picType == P_SLICE - && refFrame + if (picType == P_SLICE + && refFrame && refFrame->m_encData->m_slice->m_sliceType == picType && refQScale > 0 && refRowBits > 0 @@ -2923,7 +2953,7 @@ int RateControl::rowVbvRateControl(Frame* curFrame, uint32_t row, RateControlEnt totalBits = (double)(int64_t)m_totalBits; double totalBitsNeeded = wantedBits; if (m_param->totalFrames) - totalBitsNeeded = (m_param->totalFrames * m_bitrate) / m_fps; + totalBitsNeeded = (m_param->totalFrames * m_bitrate) * m_timebase; double abrOvershoot = (accFrameBits + totalBits - wantedBits) / totalBitsNeeded; while (qpVbv < qpMax @@ -2942,7 +2972,7 @@ int RateControl::rowVbvRateControl(Frame* curFrame, uint32_t row, RateControlEnt while (qpVbv > qpMin && (qpVbv > row0Qp || m_singleFrameVbv) && (((accFrameBits < rce->frameSizePlanned * 0.8f && qpVbv <= prevRowQp) - || accFrameBits < (rce->bufferFill - m_bufferSize + m_bufferRate) * 1.1 + || accFrameBits < (rce->bufferFill - m_bufferSize + m_bufferRate * rce->cpbDuration) * 1.1 || (rce->vbvEndAdj && ((rce->bufferFill - accFrameBits) > (rce->targetFill * vbvEndBias)))) && (!m_param->rc.bStrictCbr ? 1 : abrOvershoot < 0))) { @@ -2971,7 +3001,7 @@ int RateControl::rowVbvRateControl(Frame* curFrame, uint32_t row, RateControlEnt /* avoid VBV underflow or MinCr violation */ while ((qpVbv < qpAbsoluteMax) - && ((rce->bufferFill - accFrameBits < m_bufferRate * maxFrameError) || + && ((rce->bufferFill - accFrameBits < m_bufferRate * rce->cpbDuration * maxFrameError) || (rce->frameSizeMaximum - accFrameBits < rce->frameSizeMaximum * maxFrameError))) { qpVbv += stepSize; @@ -3003,7 +3033,7 @@ int RateControl::rowVbvRateControl(Frame* curFrame, uint32_t row, RateControlEnt /* Last-ditch attempt: if the last row of the frame underflowed the VBV, * try again. */ - if ((rce->frameSizeEstimated > (rce->bufferFill - m_bufferRate * maxFrameError) && + if ((rce->frameSizeEstimated > (rce->bufferFill - m_bufferRate * rce->cpbDuration * maxFrameError) && qpVbv < qpMax && canReencodeRow)) { qpVbv = qpMax; @@ -3021,8 +3051,8 @@ double RateControl::getQScale(RateControlEntry *rce, double rateFactor) if (m_param->rc.cuTree && !m_param->rc.hevcAq) { // Scale and units are obtained from rateNum and rateDenom for videos with fixed frame rates. - double timescale = (double)m_param->fpsDenom / (2 * m_param->fpsNum); - q = pow(BASE_FRAME_DURATION / CLIP_DURATION(2 * timescale), 1 - m_param->rc.qCompress); + double timescale = m_timebase * (double)rce->cpbDuration; + q = pow(BASE_FRAME_DURATION / CLIP_DURATION(timescale), 1 - m_param->rc.qCompress); } else q = pow(rce->blurredComplexity, 1 - m_param->rc.qCompress); @@ -3077,8 +3107,10 @@ int RateControl::updateVbv(int64_t bits, RateControlEntry* rce) if (m_bufferFillFinal < 0) x265_log(m_param, X265_LOG_WARNING, "poc:%d, VBV underflow (%.0f bits)\n", rce->poc, (double)m_bufferFillFinal); + double bufferFillThisAU = rce->bufferRate * rce->cpbDuration; m_bufferFillFinal = X265_MAX(double(m_bufferFillFinal), 0.0); - m_bufferFillFinal = double(m_bufferFillFinal) + rce->bufferRate; + m_bufferFillFinal = double(m_bufferFillFinal) + bufferFillThisAU; + if (m_param->csvLogLevel >= 2) m_unclippedBufferFillFinal = m_bufferFillFinal; @@ -3089,15 +3121,17 @@ int RateControl::updateVbv(int64_t bits, RateControlEntry* rce) filler = (int)(m_bufferFillFinal - m_bufferSize); filler += FILLER_OVERHEAD * 8; } + m_bufferFillFinal = double(m_bufferFillFinal) - filler; - bufferBits = X265_MIN(bits + filler + m_bufferExcess, rce->bufferRate); + bufferBits = X265_MIN(bits + filler + m_bufferExcess, bufferFillThisAU); m_bufferExcess = X265_MAX(m_bufferExcess - bufferBits + bits + filler, 0); m_bufferFillActual = double(m_bufferFillActual) + bufferBits - bits - filler; } else { + m_bufferFillFinal = X265_MIN(double(m_bufferFillFinal), m_bufferSize); - bufferBits = X265_MIN(bits + m_bufferExcess, rce->bufferRate); + bufferBits = X265_MIN(bits + m_bufferExcess, bufferFillThisAU); m_bufferExcess = X265_MAX(m_bufferExcess - bufferBits + bits, 0); m_bufferFillActual = double(m_bufferFillActual) + bufferBits - bits; m_bufferFillActual = X265_MIN(double(m_bufferFillActual), m_bufferSize); @@ -3225,11 +3259,13 @@ int RateControl::rateControlEnd(Frame* curFrame, int64_t bits, RateControlEntry* * Not perfectly accurate with B-refs, but good enough. */ m_cplxrSum += (bits * x265_qp2qScale(rce->qpaRc) / (rce->qRceq * fabs(m_param->rc.pbFactor))) - (rce->rowCplxrSum); } - m_wantedBitsWindow = double(m_wantedBitsWindow) + m_frameDuration * (m_bRcReConfig ? (curFrame->m_targetBitrate * 1000) : m_bitrate); + + double frameDurSecs = rce->frameDuration * m_timebase; + m_wantedBitsWindow = double(m_wantedBitsWindow) + frameDurSecs * (m_bRcReConfig ? (curFrame->m_targetBitrate * 1000) : m_bitrate); m_totalBits.add(bits - rce->rowTotalBits); m_encodedBits += actualBits; m_encodedSegmentBits += actualBits; - m_segDur += m_frameDuration; + m_segDur += frameDurSecs; int pos = m_sliderPos - m_param->frameNumThreads; if (pos >= 0) m_encodedBitsWindow[pos % s_slidingWindowFrames] = actualBits; @@ -3261,31 +3297,6 @@ int RateControl::rateControlEnd(Frame* curFrame, int64_t bits, RateControlEntry* curFrame->m_rcData->count[i] = m_pred[i].count; curFrame->m_rcData->offset[i] = m_pred[i].offset; } - if (m_param->bEmitHRDSEI) - { - const VUI *vui = &curEncData.m_slice->m_sps->vuiParameters; - const HRDInfo *hrd = &vui->hrdParameters; - const TimingInfo *time = &vui->timingInfo; - if (!curFrame->m_poc) - { - // first access unit initializes the HRD - rce->hrdTiming->cpbInitialAT = 0; - rce->hrdTiming->cpbRemovalTime = m_nominalRemovalTime = (double)m_bufPeriodSEI.m_initialCpbRemovalDelay / 90000; - } - else - { - rce->hrdTiming->cpbRemovalTime = m_nominalRemovalTime + (double)rce->picTimingSEI->m_auCpbRemovalDelay * time->numUnitsInTick / time->timeScale; - double cpbEarliestAT = rce->hrdTiming->cpbRemovalTime - (double)m_bufPeriodSEI.m_initialCpbRemovalDelay / 90000; - if (!curFrame->m_lowres.bKeyframe) - cpbEarliestAT -= (double)m_bufPeriodSEI.m_initialCpbRemovalDelayOffset / 90000; - - rce->hrdTiming->cpbInitialAT = hrd->cbrFlag ? m_prevCpbFinalAT : X265_MAX(m_prevCpbFinalAT, cpbEarliestAT); - } - int filler_bits = *filler ? (*filler - START_CODE_OVERHEAD * 8) : 0; - uint32_t cpbsizeUnscale = hrd->cpbSizeValue << (hrd->cpbSizeScale + CPB_SHIFT); - rce->hrdTiming->cpbFinalAT = m_prevCpbFinalAT = rce->hrdTiming->cpbInitialAT + (actualBits + filler_bits)/ cpbsizeUnscale; - rce->hrdTiming->dpbOutputTime = (double)rce->picTimingSEI->m_picDpbOutputDelay * time->numUnitsInTick / time->timeScale + rce->hrdTiming->cpbRemovalTime; - } } if (rce->sliceType == I_SLICE) { @@ -3300,16 +3311,16 @@ int RateControl::rateControlEnd(Frame* curFrame, int64_t bits, RateControlEntry* /* called to write out the rate control frame stats info in multipass encodes */ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* rce) { - FrameData& curEncData = *curFrame->m_encData; + FrameData& curEncData = *curFrame->m_encData; int ncu = (m_param->rc.qgSize == 8) ? m_ncu * 4 : m_ncu; char cType = rce->sliceType == I_SLICE ? (curFrame->m_lowres.sliceType == X265_TYPE_IDR ? 'I' : 'i') : rce->sliceType == P_SLICE ? 'P' : IS_REFERENCED(curFrame) ? 'B' : 'b'; - + if (!curEncData.m_param->bMultiPassOptRPS) { if (fprintf(m_statFileOut, - "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f sc:%d ;\n", + "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f sc:%d cpbd:%u, dspd:%u ;\n", rce->poc, rce->encodeOrder, cType, curEncData.m_avgQpRc, curEncData.m_avgQpAq, rce->qpNoVbv, rce->qRceq, @@ -3319,7 +3330,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r curFrame->m_encData->m_frameStats.percent8x8Intra * m_ncu, curFrame->m_encData->m_frameStats.percent8x8Inter * m_ncu, curFrame->m_encData->m_frameStats.percent8x8Skip * m_ncu, - curFrame->m_lowres.bScenecut) < 0) + curFrame->m_lowres.bScenecut, rce->cpbDuration, rce->frameDuration) < 0) goto writeFailure; } else @@ -3340,7 +3351,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r } if (fprintf(m_statFileOut, - "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f nump:%d numnegp:%d numposp:%d %s %s ;\n", + "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f nump:%d numnegp:%d numposp:%d %s %s cpbd:%u, dspd:%u ;\n", rce->poc, rce->encodeOrder, cType, curEncData.m_avgQpRc, curEncData.m_avgQpAq, rce->qpNoVbv, rce->qRceq, @@ -3353,7 +3364,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r rpsWriter->numberOfPictures, rpsWriter->numberOfNegativePictures, rpsWriter->numberOfPositivePictures, - deltaPOC, bUsed) < 0) + deltaPOC, bUsed, rce->cpbDuration, rce->frameDuration) < 0) goto writeFailure; } /* Don't re-write the data in multi-pass mode. */ @@ -3380,7 +3391,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r shrItem.type = &sliceType; shrItem.stats = m_cuTreeStats.qpBuffer[0]; m_cutreeShrMem->writeData(&shrItem, WriteSharedCUTreeData); - } + } } return 0; @@ -3467,7 +3478,7 @@ void RateControl::destroy() X265_FREE(m_encOrder); for (int i = 0; i < 2; i++) X265_FREE(m_cuTreeStats.qpBuffer[i]); - + if (m_relativeComplexity) X265_FREE(m_relativeComplexity); @@ -3522,14 +3533,17 @@ void RateControl::splitbUsed(char bused[], RateControlEntry *rce) double RateControl::forwardMasking(Frame* curFrame, double q) { double qp = x265_qScale2qp(q); - uint32_t maxWindowSize = uint32_t((m_param->fwdMaxScenecutWindow / 1000.0) * (m_param->fpsNum / m_param->fpsDenom) + 0.5); + double fps = 1. / m_timebase; + /* todo: consider exponential moving average for the window sizes, looking at each frame's duration */ + fps = m_framesDone < fps ? 1/(curFrame->m_duration * curFrame->m_timebase) : m_durationDone / m_framesDone; + uint32_t maxWindowSize = uint32_t((m_param->fwdMaxScenecutWindow / 1000.0) * fps + 0.5); uint32_t windowSize[6], prevWindow = 0; int lastScenecut = m_top->m_rateControl->m_lastScenecut; double fwdRefQpDelta[6], fwdNonRefQpDelta[6], sliceTypeDelta[6]; for (int i = 0; i < 6; i++) { - windowSize[i] = prevWindow + (uint32_t((m_param->fwdScenecutWindow[i] / 1000.0) * (m_param->fpsNum / m_param->fpsDenom) + 0.5)); + windowSize[i] = prevWindow + (uint32_t((m_param->fwdScenecutWindow[i] / 1000.0) * (fps) + 0.5)); fwdRefQpDelta[i] = double(m_param->fwdRefQpDelta[i]); fwdNonRefQpDelta[i] = double(m_param->fwdNonRefQpDelta[i]); sliceTypeDelta[i] = SLICE_TYPE_DELTA * fwdRefQpDelta[i]; diff --git a/source/encoder/ratecontrol.h b/source/encoder/ratecontrol.h index a8fe14064..39a70238c 100644 --- a/source/encoder/ratecontrol.h +++ b/source/encoder/ratecontrol.h @@ -57,14 +57,6 @@ struct Predictor double offset; }; -struct HRDTiming -{ - double cpbInitialAT; - double cpbFinalAT; - double dpbOutputTime; - double cpbRemovalTime; -}; - struct RateControlEntry { Predictor rowPreds[3][2]; @@ -89,8 +81,6 @@ struct RateControlEntry double bufferFillActual; double targetFill; bool vbvEndAdj; - double frameDuration; - double clippedDuration; AtomicDouble frameSizeEstimated; /* hold frameSize, updated from cu level vbv rc */ double frameSizeMaximum; /* max frame Size according to minCR restrictions and level of the video */ int sliceType; @@ -118,7 +108,8 @@ struct RateControlEntry bool scenecut; bool isIdr; SEIPictureTiming *picTimingSEI; - HRDTiming *hrdTiming; + unsigned int cpbDuration; /* in clock ticks */ + unsigned int frameDuration; /* in clock ticks */ int rpsIdx; RPS rpsData; bool isFadeEnd; @@ -153,14 +144,13 @@ class RateControl int m_lastScenecut; int m_lastScenecutAwareIFrame; double m_rateTolerance; - double m_frameDuration; /* current frame duration in seconds */ double m_bitrate; double m_rateFactorConstant; double m_bufferSize; AtomicDouble m_bufferFillFinal; /* real buffer as of the last finished frame */ double m_unclippedBufferFillFinal; /* real unclipped buffer as of the last finished frame used to log in CSV*/ double m_bufferFill; /* planned buffer, if all in-progress frames hit their bit budget */ - double m_bufferRate; /* # of bits added to buffer_fill after each frame */ + double m_bufferRate; /* # of bits added to buffer_fill after each tick */ double m_vbvMaxRate; /* in kbps */ double m_rateFactorMaxIncrement; /* Don't allow RF above (CRF + this value). */ double m_rateFactorMaxDecrement; /* don't allow RF below (this value). */ @@ -180,6 +170,7 @@ class RateControl int m_qpConstant[3]; int m_lastNonBPictType; int m_framesDone; /* # of frames passed through RateCotrol already */ + double m_durationDone; /* total duration pass through RateControl already, in seconds */ int64_t m_iBits; double m_cplxrSum; /* sum of bits*qscale/rceq */ AtomicDouble m_wantedBitsWindow; /* target bitrate * window */ @@ -199,7 +190,7 @@ class RateControl double m_movingSumComplexitySeg[3]; int m_frameCountSeg[3]; double m_segDur; - double m_fps; + double m_timebase; /* bitstream timebase (num_units_in_tick / time_scale) */ int64_t m_satdCostWindow[50]; int64_t m_encodedBitsWindow[50]; int m_sliderPos; @@ -234,7 +225,6 @@ class RateControl /* hrd stuff */ SEIBufferingPeriod m_bufPeriodSEI; double m_nominalRemovalTime; - double m_prevCpbFinalAT; /* 2 pass */ bool m_2pass; @@ -326,7 +316,7 @@ class RateControl bool vbv2Pass(uint64_t allAvailableBits, int frameCount, int startPos); bool findUnderflow(double *fills, int *t0, int *t1, int over, int framesCount); bool fixUnderflow(int t0, int t1, double adjustment, double qscaleMin, double qscaleMax); - double tuneQScaleForGrain(double rcOverflow); + double tuneQScaleForGrain(RateControlEntry *rce, double rcOverflow); void splitdeltaPOC(char deltapoc[], RateControlEntry *rce); void splitbUsed(char deltapoc[], RateControlEntry *rce); void checkAndResetCRF(RateControlEntry* rce); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 194a0d4e3..503d26a82 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2631,7 +2631,6 @@ void Lookahead::slicetypeDecide() } else { - if (bframes) list[bframes - 1]->m_lowres.bLastMiniGopBFrame = true; list[bframes]->m_lowres.leadingBframes = bframes; @@ -2812,6 +2811,8 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) } } + frame->m_lowres.cpbDurationSecs = frame->m_plannedCpbDuration * frame->m_timebase; + /* Buffering Period SEI (attached with the keyframe) */ if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) { @@ -2841,6 +2842,7 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) int p0 = IS_X265_TYPE_I(frames[curNonB]->sliceType) ? curNonB : prevNonB; frames[nextNonB]->plannedSatd[idx] = vbvFrameCost(frames, p0, curNonB, curNonB); frames[nextNonB]->plannedType[idx] = frames[curNonB]->sliceType; + frames[nextNonB]->plannedCpbDuration[idx] = frames[curNonB]->cpbDurationSecs; /* Save the nextNonB Cost in each B frame of the current miniGop */ if (curNonB > miniGopEnd) @@ -2848,7 +2850,8 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) for (int j = nextB; j < miniGopEnd; j++) { frames[j]->plannedSatd[frames[j]->indB] = frames[nextNonB]->plannedSatd[idx]; - frames[j]->plannedType[frames[j]->indB++] = frames[nextNonB]->plannedType[idx]; + frames[j]->plannedType[frames[j]->indB] = frames[nextNonB]->plannedType[idx]; + frames[j]->plannedCpbDuration[frames[j]->indB++] = frames[nextNonB]->plannedCpbDuration[idx]; } } idx++; @@ -2878,6 +2881,7 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) satdCost = vbvFrameCost(frames, prevNonB, curNonB, i); frames[nextNonB]->plannedSatd[idx] = satdCost; frames[nextNonB]->plannedType[idx] = type; + frames[nextNonB]->plannedCpbDuration[idx] = frames[i]->cpbDurationSecs; /* Save the nextB Cost in each B frame of the current miniGop */ for (int j = nextB; j < miniGopEnd; j++) @@ -2887,7 +2891,8 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) if (j >= i && j !=nextBRef) continue; frames[j]->plannedSatd[frames[j]->indB] = satdCost; - frames[j]->plannedType[frames[j]->indB++] = type; + frames[j]->plannedType[frames[j]->indB] = type; + frames[j]->plannedCpbDuration[frames[j]->indB++] = frames[j]->cpbDurationSecs; } } prevNonB = curNonB; @@ -3741,9 +3746,9 @@ void Lookahead::cuTree(Lowres **frames, int numframes, bool bIntra) int bframes = 0; x265_emms(); - double totalDuration = 0.0; + double totalDuration = 0.; for (int j = 0; j <= numframes; j++) - totalDuration += (double)m_param->fpsDenom / m_param->fpsNum; + totalDuration += frames[j]->dispDurationSecs; double averageDuration = totalDuration / (numframes + 1); From 94138b2ca7173a0447b23e76f9fc9ca2b2ad1a4a Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 5 Sep 2026 22:18:59 +0200 Subject: [PATCH 06/13] encoder: fix duration handling in VBV lookahead. --- source/common/frame.h | 14 +++++++------- source/common/lowres.h | 1 + source/encoder/encoder.cpp | 7 +++++-- source/encoder/frameencoder.cpp | 10 +++++----- source/encoder/ratecontrol.cpp | 8 ++++---- source/encoder/slicetype.cpp | 13 +++++++------ 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/source/common/frame.h b/source/common/frame.h index 214700b4d..a8c028f2b 100644 --- a/source/common/frame.h +++ b/source/common/frame.h @@ -183,13 +183,13 @@ class Frame int m_targetQp; /*HRD delays for this frame*/ - int64_t m_duration; // display duration of picture in ticks (VUI timebase) - float m_timebase; // the timebase for this picture [seconds] - int64_t m_plannedCpbDuration; // lifetime in the CPB (VUI timebase) - int64_t m_cpbDelay; // removal delay of the picture (VUI timebase) - uint64_t m_displayPicCount; // display picture count - uint64_t m_codedPicCount; // remove me, for debugging - int64_t m_dpbOutputDelay; // output delay of the picture (VUI timebase) + unsigned int m_duration; // display duration of picture in ticks (VUI timebase) + double m_timebase; // the timebase for this picture [seconds] + unsigned int m_plannedCpbDuration; // lifetime in the CPB (VUI timebase) + unsigned int m_cpbDelay; // removal delay of the picture (VUI timebase) + uint64_t m_displayPicCount; // display picture count (counted in decoder ticks) + uint64_t m_codedPicCount; // coded picture count (counted in decoder ticks) + unsigned int m_dpbOutputDelay; // output delay of the picture (VUI timebase) Frame(); diff --git a/source/common/lowres.h b/source/common/lowres.h index 4aad46f91..510dd2b2e 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -183,6 +183,7 @@ struct Lowres : public ReferencePlanes double ipCostRatio; double cpbDurationSecs; double dispDurationSecs; + /* lookahead output data */ int64_t costEst[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; int64_t costEstAq[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 12cc23a4b..0064d13d9 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -1769,14 +1769,17 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) /* Set up frame timing info for slicetype and ratecontrol and the frame timebase (could change across cvs) */ if (inFrame[layer]->m_param->bEmitVUITimingInfo) - inFrame[layer]->m_timebase = (m_sps.vuiParameters.timingInfo.numUnitsInTick / m_sps.vuiParameters.timingInfo.timeScale); + inFrame[layer]->m_timebase = ((double)m_sps.vuiParameters.timingInfo.numUnitsInTick / (double)m_sps.vuiParameters.timingInfo.timeScale); else - inFrame[layer]->m_timebase = (inFrame[layer]->m_param->fpsDenom / inFrame[layer]->m_param->fpsNum); + inFrame[layer]->m_timebase = ((double)inFrame[layer]->m_param->fpsDenom / (double)inFrame[layer]->m_param->fpsNum); inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; inFrame[layer]->m_displayPicCount = m_dispPicCount; inFrame[layer]->m_lowres.dispDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; + /* start off by assuming the cpb duration is equal to the display duration */ + inFrame[layer]->m_lowres.cpbDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; + /* update presentation counts (decoder ticks count) */ m_dispPicCount += inFrame[layer]->m_duration; diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index e3da3cb2a..43c8f3d91 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -1748,7 +1748,7 @@ void FrameEncoder::processRowEncoder(int intRow, ThreadLocalData& tld, int layer { // Delay one row to avoid intra prediction conflict if (m_pool && !bFirstRowInSlice) - { + { int allowCol = col; // avoid race condition on last column @@ -1830,9 +1830,9 @@ void FrameEncoder::processRowEncoder(int intRow, ThreadLocalData& tld, int layer x265_emms(); if (!layer && bIsVbv) - { + { // Update encoded bits, satdCost, baseQP for each CU if tune grain is disabled - FrameData::RCStatCU& cuStat = curEncData.m_cuStat[cuAddr]; + FrameData::RCStatCU& cuStat = curEncData.m_cuStat[cuAddr]; if ((m_param->bEnableWavefront && ((cuAddr == m_sliceBaseRow[sliceId] * numCols) || !m_param->rc.bEnableConstVbv)) || !m_param->bEnableWavefront) { curEncData.m_rowStat[row].rowSatd.fetchAdd(cuStat.vbvCost); @@ -1841,7 +1841,7 @@ void FrameEncoder::processRowEncoder(int intRow, ThreadLocalData& tld, int layer curEncData.m_rowStat[row].sumQpRc += cuStat.baseQp; curEncData.m_rowStat[row].numEncodedCUs = cuAddr; } - + // If current block is at row end checkpoint, call vbv ratecontrol. if (!m_param->bEnableWavefront && col == numCols - 1) { @@ -2019,7 +2019,7 @@ void FrameEncoder::processRowEncoder(int intRow, ThreadLocalData& tld, int layer uint32_t maxRows = m_sliceBaseRow[sliceId + 1] - m_sliceBaseRow[sliceId]; if (!m_rce.encodeOrder) - rowCount = maxRows - 1; + rowCount = maxRows - 1; else if ((uint32_t)m_rce.encodeOrder <= 2 * (m_param->fpsNum / m_param->fpsDenom)) rowCount = X265_MIN((maxRows + 1) / 2, maxRows - 1); else diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp index 794872446..2325629be 100644 --- a/source/encoder/ratecontrol.cpp +++ b/source/encoder/ratecontrol.cpp @@ -700,7 +700,7 @@ bool RateControl::init(const SPS& sps) if (!m_param->bMultiPassOptRPS) { int scenecut = 0; - e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf sc:%d cpbd:%u, dspd:%u", + e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf sc:%d cpbd:%u dspd:%u", &picType, &qpRc, &qpAq, &qNoVbv, &qRceq, &rce->coeffBits, &rce->mvBits, &rce->miscBits, &rce->iCuCount, &rce->pCuCount, &rce->skipCuCount, &scenecut, &rce->cpbDuration, &rce->frameDuration); @@ -712,7 +712,7 @@ bool RateControl::init(const SPS& sps) char bUsed[40]; memset(deltaPOC, 0, sizeof(deltaPOC)); memset(bUsed, 0, sizeof(bUsed)); - e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf nump:%d numnegp:%d numposp:%d deltapoc:%127s bused:%39s cpbd:%u, dspd:%u", + e += sscanf(p, " in:%*d out:%*d type:%c q:%lf q-aq:%lf q-noVbv:%lf q-Rceq:%lf tex:%d mv:%d misc:%d icu:%lf pcu:%lf scu:%lf nump:%d numnegp:%d numposp:%d deltapoc:%127s bused:%39s cpbd:%u dspd:%u", &picType, &qpRc, &qpAq, &qNoVbv, &qRceq, &rce->coeffBits, &rce->mvBits, &rce->miscBits, &rce->iCuCount, &rce->pCuCount, &rce->skipCuCount, &rce->rpsData.numberOfPictures, &rce->rpsData.numberOfNegativePictures, &rce->rpsData.numberOfPositivePictures, deltaPOC, bUsed, &rce->cpbDuration, &rce->frameDuration); @@ -1341,7 +1341,7 @@ void RateControl::initFramePredictors() } m_pred[0].coeff = m_pred[3].coeff = 0.75; m_pred[0].coeffMin = m_pred[3].coeffMin = 0.75 / 4; - if (m_isGrainEnabled) // when tuned for grain + if (m_isGrainEnabled) // when tuned for grain { m_pred[1].coeffMin = 0.75 / 4; m_pred[1].coeff = 0.75; @@ -1568,7 +1568,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode default: break; } - + curFrame->m_lowres.plannedCpbDuration[j] = m_rce2Pass[index].cpbDuration * m_timebase; curFrame->m_lowres.plannedSatd[j] = m_rce2Pass[index].currentSatd; totalDuration += m_rce2Pass[index].frameDuration * m_timebase; index++; diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 503d26a82..9e993ce44 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1471,14 +1471,14 @@ void Lookahead::getEstimatedPictureCost(Frame *curFrame) frames[b] = &curFrame->m_lowres; frames[p1] = &slice->m_refFrameList[1][0]->m_lowres; } - else + else { p0 = b = 0; p1 = b + l1poc - poc; frames[p0] = frames[b] = &curFrame->m_lowres; frames[p1] = &slice->m_refFrameList[1][0]->m_lowres; } - + break; default: @@ -2803,7 +2803,8 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) frame->m_dpbOutputDelay = 0; /* Bref and next B-frame cpbRemovalDelay can be equal on long sequence of doubling or tripling. - larger m_dpbOutputDelay margin would fix this, but it creates buffering delay and breaks UHD BD compliance */ + * larger m_dpbOutputDelay margin would fix this, but it creates buffering delay and breaks UHD BD compliance + * to avoid two access units with the same cpb removal time, shift prior frame back by a tick. */ if (prevFrame && (prevFrame->m_cpbDelay == frame->m_cpbDelay)) { prevFrame->m_cpbDelay -= 1; @@ -2811,8 +2812,6 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) } } - frame->m_lowres.cpbDurationSecs = frame->m_plannedCpbDuration * frame->m_timebase; - /* Buffering Period SEI (attached with the keyframe) */ if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) { @@ -2882,6 +2881,7 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) frames[nextNonB]->plannedSatd[idx] = satdCost; frames[nextNonB]->plannedType[idx] = type; frames[nextNonB]->plannedCpbDuration[idx] = frames[i]->cpbDurationSecs; + /* Save the nextB Cost in each B frame of the current miniGop */ for (int j = nextB; j < miniGopEnd; j++) @@ -2892,7 +2892,8 @@ void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) continue; frames[j]->plannedSatd[frames[j]->indB] = satdCost; frames[j]->plannedType[frames[j]->indB] = type; - frames[j]->plannedCpbDuration[frames[j]->indB++] = frames[j]->cpbDurationSecs; + frames[j]->plannedCpbDuration[frames[j]->indB++] = frames[nextB]->cpbDurationSecs; + } } prevNonB = curNonB; From 00655be98c907b09b1d81726e219567ec5a76ec3 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 5 Sep 2026 22:20:00 +0200 Subject: [PATCH 07/13] cutree: change fps factor to use the frame display duration. --- source/encoder/encoder.cpp | 3 ++- source/encoder/slicetype.cpp | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 0064d13d9..ebdebfc4c 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -1777,7 +1777,8 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_displayPicCount = m_dispPicCount; inFrame[layer]->m_lowres.dispDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; - /* start off by assuming the cpb duration is equal to the display duration */ + /* start off by assuming the cpb duration is equal to the display duration + * this is a fair assumption, except for highly VFR streams */ inFrame[layer]->m_lowres.cpbDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; /* update presentation counts (decoder ticks count) */ diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 9e993ce44..0edfea941 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -3856,7 +3856,7 @@ void Lookahead::estimateCUPropagate(Lowres **frames, double averageDuration, int uint16_t *propagateCost = frames[b]->propagateCost; x265_emms(); - double fpsFactor = CLIP_DURATION((double)m_param->fpsDenom / m_param->fpsNum) / CLIP_DURATION(averageDuration); + double fpsFactor = CLIP_DURATION(frames[b]->dispDurationSecs) / CLIP_DURATION(averageDuration); /* For non-referred frames the source costs are always zero, so just memset one row and re-use it. */ if (!referenced) @@ -3953,7 +3953,7 @@ void Lookahead::estimateCUPropagate(Lowres **frames, double averageDuration, int void Lookahead::computeCUTreeQpOffset(Lowres *frame, double averageDuration, int ref0Distance) { - int fpsFactor = (int)(CLIP_DURATION(averageDuration) / CLIP_DURATION((double)m_param->fpsDenom / m_param->fpsNum) * 256); + int fpsFactor = (int)(CLIP_DURATION(averageDuration) / CLIP_DURATION(frame->dispDurationSecs) * 256); uint32_t loopIncr = (m_param->rc.qgSize == 8) ? 8 : 16; double weightdelta = 0.0; From d7da156f999343d8dbeb4d390080839c54f99ad5 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sat, 5 Sep 2026 23:00:12 +0200 Subject: [PATCH 08/13] encoder: prevent negative shift with picstruct check, fix 2nd pass. --- source/encoder/encoder.cpp | 11 +++++++---- source/encoder/ratecontrol.cpp | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index ebdebfc4c..07a88a154 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -4558,11 +4558,14 @@ void Encoder::configure(x265_param *p) } } - // reject any configuration that leads to orphaned fields (1, 2, 5, 6, 9, 10, 11, 12) - if (p->pictureStructure >= PIC_STRUCT_COUNT || ((1 << p->pictureStructure) & 0b1111001100110)) + if (p->pictureStructure >= 0) { - x265_log(p, X265_LOG_WARNING, "Invalid or illegal picture structure, not using the user-provided value.\n"); - p->pictureStructure = -1; + // reject any configuration that leads to orphaned fields (1, 2, 5, 6, 9, 10, 11, 12) + if (p->pictureStructure >= PIC_STRUCT_COUNT || ((1 << p->pictureStructure) & 0b1111001100110)) + { + x265_log(p, X265_LOG_WARNING, "Invalid or illegal picture structure, not using the user-provided value.\n"); + p->pictureStructure = -1; + } } if (!p->bEnableFrameDuplication && p->dupThreshold && p->dupThreshold != 70) diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp index 2325629be..f2058a263 100644 --- a/source/encoder/ratecontrol.cpp +++ b/source/encoder/ratecontrol.cpp @@ -3320,7 +3320,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r if (!curEncData.m_param->bMultiPassOptRPS) { if (fprintf(m_statFileOut, - "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f sc:%d cpbd:%u, dspd:%u ;\n", + "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f sc:%d cpbd:%u dspd:%u ;\n", rce->poc, rce->encodeOrder, cType, curEncData.m_avgQpRc, curEncData.m_avgQpAq, rce->qpNoVbv, rce->qRceq, @@ -3351,7 +3351,7 @@ int RateControl::writeRateControlFrameStats(Frame* curFrame, RateControlEntry* r } if (fprintf(m_statFileOut, - "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f nump:%d numnegp:%d numposp:%d %s %s cpbd:%u, dspd:%u ;\n", + "in:%d out:%d type:%c q:%.2f q-aq:%.2f q-noVbv:%.2f q-Rceq:%.2f tex:%d mv:%d misc:%d icu:%.2f pcu:%.2f scu:%.2f nump:%d numnegp:%d numposp:%d %s %s cpbd:%u dspd:%u ;\n", rce->poc, rce->encodeOrder, cType, curEncData.m_avgQpRc, curEncData.m_avgQpAq, rce->qpNoVbv, rce->qRceq, From 2984812d5ee6fc8691112559dc53abf50dc2e9af Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Sun, 6 Sep 2026 21:54:32 +0200 Subject: [PATCH 09/13] encoder: fix stats. slicetype: fix dpbDelay overflow. --- source/encoder/encoder.cpp | 19 +++++++++++++++---- source/encoder/encoder.h | 4 ++++ source/encoder/ratecontrol.cpp | 13 ++++++++----- source/encoder/ratecontrol.h | 2 +- source/encoder/slicetype.cpp | 15 ++++++++++----- 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 07a88a154..24d6516f6 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -2840,10 +2840,15 @@ void EncStats::addQP(double aveQp) m_totalQp += aveQp; } +void EncStats::addDuration(unsigned int durationInVuiTB) +{ + m_totDuration += durationInVuiTB; +} + char* Encoder::statsString(EncStats& stat, char* buffer, size_t bufferSize) { double fps = (double)m_param->fpsNum / m_param->fpsDenom; - double scale = fps / 1000 / (double)stat.m_numPics; + double scale = (fps / 1000.) / double(stat.m_totDuration); int len = snprintf(buffer, bufferSize, "%6u, ", stat.m_numPics); @@ -2913,7 +2918,8 @@ void Encoder::printSummary() { int p = 0; double elapsedEncodeTime = (double)(x265_mdate() - m_encodeStartTime) / 1000000; - double elapsedVideoTime = (double)m_analyzeAll[layer].m_numPics * m_param->fpsDenom / m_param->fpsNum; + /* with temporal layering totDuration and numPics of each layers are equal. Else they can differ (VFR or pulldown) */ + double elapsedVideoTime = double(m_analyzeAll[layer].m_totDuration) * double(m_param->fpsDenom) / double(m_param->fpsNum); double bitrate = (0.001f * m_analyzeAll[layer].m_accBits) / elapsedVideoTime; p += snprintf(buffer + p, sizeof(buffer) - p,"\nencoded %d frames in %.2fs (%.2f fps), %.2f kb/s, Avg QP:%2.2lf", m_analyzeAll[layer].m_numPics, @@ -3200,6 +3206,8 @@ void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, x265_f m_analyzeAll[layer].addBits(bits); m_analyzeAll[layer].addQP(curEncData.m_avgQpAq); + m_analyzeAll[layer].addDuration(curFrame->m_duration); + if (m_param->bEnablePsnr) m_analyzeAll[layer].addPsnr(psnrY, psnrU, psnrV); @@ -3213,6 +3221,7 @@ void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, x265_f { m_analyzeI[layer].addBits(bits); m_analyzeI[layer].addQP(curEncData.m_avgQpAq); + m_analyzeI[layer].addDuration(curFrame->m_duration); if (m_param->bEnablePsnr) m_analyzeI[layer].addPsnr(psnrY, psnrU, psnrV); if (m_param->bEnableSsim) @@ -3222,6 +3231,7 @@ void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, x265_f { m_analyzeP[layer].addBits(bits); m_analyzeP[layer].addQP(curEncData.m_avgQpAq); + m_analyzeP[layer].addDuration(curFrame->m_duration); if (m_param->bEnablePsnr) m_analyzeP[layer].addPsnr(psnrY, psnrU, psnrV); if (m_param->bEnableSsim) @@ -3231,6 +3241,7 @@ void Encoder::finishFrameStats(Frame* curFrame, FrameEncoder *curEncoder, x265_f { m_analyzeB[layer].addBits(bits); m_analyzeB[layer].addQP(curEncData.m_avgQpAq); + m_analyzeB[layer].addDuration(curFrame->m_duration); if (m_param->bEnablePsnr) m_analyzeB[layer].addPsnr(psnrY, psnrU, psnrV); if (m_param->bEnableSsim) @@ -3454,7 +3465,7 @@ void Encoder::getStreamHeaders(NALList& list, Entropy& sbacCoder, Bitstream& bs) bs.write(0x10, 8); list.serialize(NAL_UNIT_ACCESS_UNIT_DELIMITER, bs); } - + /* headers for start of bitstream */ bs.resetBits(); #if ENABLE_ALPHA || ENABLE_MULTIVIEW @@ -5265,7 +5276,7 @@ void Encoder::readAnalysisFile(x265_analysis_data* analysis, int curPoc, const x X265_FREAD(&analysis->satdCost, sizeof(int64_t), 1, m_analysisFileIn, &(picData->satdCost)); X265_FREAD(&analysis->numCUsInFrame, sizeof(int), 1, m_analysisFileIn, &(picData->numCUsInFrame)); X265_FREAD(&analysis->numPartitions, sizeof(int), 1, m_analysisFileIn, &(picData->numPartitions)); - + if (m_param->bDisableLookahead) { X265_FREAD(&analysis->numCuInHeight, sizeof(uint32_t), 1, m_analysisFileIn, &(picData->numCuInHeight)); diff --git a/source/encoder/encoder.h b/source/encoder/encoder.h index be436774e..c2cc4d3b3 100644 --- a/source/encoder/encoder.h +++ b/source/encoder/encoder.h @@ -70,6 +70,7 @@ struct EncStats double m_totalQp; double m_maxFALL; uint64_t m_accBits; + uint64_t m_totDuration; uint32_t m_numPics; uint16_t m_maxCLL; @@ -81,6 +82,7 @@ struct EncStats m_totalQp = 0; m_maxCLL = 0; m_maxFALL = 0; + m_totDuration = 0; } void addQP(double aveQp); @@ -90,6 +92,8 @@ struct EncStats void addBits(uint64_t bits); void addSsim(double ssim); + + void addDuration(unsigned int durationInVuiTB); }; #define MAX_NUM_REF_IDX 64 diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp index f2058a263..5fa46b14d 100644 --- a/source/encoder/ratecontrol.cpp +++ b/source/encoder/ratecontrol.cpp @@ -1927,16 +1927,19 @@ double RateControl::tuneQScaleForZone(RateControlEntry *rce, double qScale) return qScale; } -double RateControl::tuneQScaleForGrain(RateControlEntry *rce, double rcOverflow) +double RateControl::tuneQScaleForGrain(double rcOverflow) { + /* change to moving average framerate */ + double fpsFactor = 1. / m_timebase; + double qpstep = rcOverflow > 1.1 ? rcOverflow : m_lstep; double qScaleAvg = x265_qp2qScale(m_avgPFrameQp); double q = m_lastQScaleFor[P_SLICE]; int curQp = int (x265_qScale2qp(m_lastQScaleFor[P_SLICE]) + 0.5); /* replace 1/timebase with the running average of the framerate */ - double curBitrate = m_qpToEncodedBits[curQp] * int(1./m_timebase + 0.5); + double curBitrate = m_qpToEncodedBits[curQp] * int(fpsFactor + 0.5); int newQp = rcOverflow > 1.1 ? curQp + 2 : rcOverflow > 1 ? curQp + 1 : curQp - 1 ; - double projectedBitrate = int(1./m_timebase + 0.5) * m_qpToEncodedBits[newQp]; + double projectedBitrate = int(fpsFactor + 0.5) * m_qpToEncodedBits[newQp]; if (curBitrate > 0 && projectedBitrate > 0) q = std::abs(projectedBitrate - m_bitrate) < std::abs (curBitrate - m_bitrate) ? x265_qp2qScale(newQp) : m_lastQScaleFor[P_SLICE]; else @@ -2073,7 +2076,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) m_avgPFrameQp = m_avgPFrameQp == 0 ? rce->qpNoVbv : m_avgPFrameQp; if (overflow != 1) { - qScale = tuneQScaleForGrain(rce, overflow); + qScale = tuneQScaleForGrain(overflow); q = x265_qScale2qp(qScale); } rce->qpNoVbv = q; @@ -2338,7 +2341,7 @@ double RateControl::rateEstimateQscale(Frame* curFrame, RateControlEntry *rce) if(m_sliceType!= I_SLICE && m_framesDone && !isEncodeEnd && ((overflow < 1.05 && overflow > 0.95) || isEncodeBeg)) { - q = tuneQScaleForGrain(rce, overflow); + q = tuneQScaleForGrain(overflow); } } } diff --git a/source/encoder/ratecontrol.h b/source/encoder/ratecontrol.h index 39a70238c..d50f7b1d8 100644 --- a/source/encoder/ratecontrol.h +++ b/source/encoder/ratecontrol.h @@ -316,7 +316,7 @@ class RateControl bool vbv2Pass(uint64_t allAvailableBits, int frameCount, int startPos); bool findUnderflow(double *fills, int *t0, int *t1, int over, int framesCount); bool fixUnderflow(int t0, int t1, double adjustment, double qscaleMin, double qscaleMax); - double tuneQScaleForGrain(RateControlEntry *rce, double rcOverflow); + double tuneQScaleForGrain(double rcOverflow); void splitdeltaPOC(char deltapoc[], RateControlEntry *rce); void splitbUsed(char deltapoc[], RateControlEntry *rce); void checkAndResetCRF(RateControlEntry* rce); diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 0edfea941..1e131e144 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2794,23 +2794,28 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) frame->m_plannedCpbDuration = frame->m_duration; frame->m_codedPicCount = m_codedPicCount; + int dpbDelay = (int64_t)frame->m_displayPicCount - (int64_t)m_codedPicCount; /* largest re-ordering at highest temporal layer */ - frame->m_dpbOutputDelay += ((m_param->bframes > 0) ? 1 : 0) + m_sps->numReorderPics[X265_MAX(0, (m_param->bEnableTemporalSubLayers - 1))]; + dpbDelay += ((m_param->bframes > 0) ? 1 : 0) + m_sps->numReorderPics[X265_MAX(0, (m_param->bEnableTemporalSubLayers - 1))]; - if (frame->m_dpbOutputDelay < 0) + if (dpbDelay < 0) { - frame->m_cpbDelay += frame->m_dpbOutputDelay; + frame->m_cpbDelay += dpbDelay; frame->m_dpbOutputDelay = 0; /* Bref and next B-frame cpbRemovalDelay can be equal on long sequence of doubling or tripling. - * larger m_dpbOutputDelay margin would fix this, but it creates buffering delay and breaks UHD BD compliance - * to avoid two access units with the same cpb removal time, shift prior frame back by a tick. */ + * larger m_dpbOutputDelay offset could solve this, but numReorder+1 is the max allowed on UHD BD. + * To avoid two access units with the same cpb removal time, shift prior frame back by a tick. */ if (prevFrame && (prevFrame->m_cpbDelay == frame->m_cpbDelay)) { prevFrame->m_cpbDelay -= 1; prevFrame->m_dpbOutputDelay += 1; } } + else + { + frame->m_dpbOutputDelay = (unsigned int)dpbDelay; + } /* Buffering Period SEI (attached with the keyframe) */ if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) From 17abeb4fbb5c5cfd99789185757400257c938d39 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Mon, 7 Sep 2026 13:29:07 +0200 Subject: [PATCH 10/13] cli: add --psfile for dynamic pulldown. --- doc/reST/cli.rst | 17 ++++++++++-- source/abrEncApp.cpp | 13 ++++++++- source/abrEncApp.h | 3 +- source/encoder/api.cpp | 2 +- source/encoder/encoder.cpp | 23 +++++++++------- source/encoder/encoder.h | 18 ------------ source/encoder/frameencoder.cpp | 40 ++++++++++++++------------- source/x265.h | 25 +++++++++++++++-- source/x265cli.cpp | 49 ++++++++++++++++++++++++++++++++- source/x265cli.h | 4 +++ 10 files changed, 137 insertions(+), 57 deletions(-) diff --git a/doc/reST/cli.rst b/doc/reST/cli.rst index 82040d1e6..4bb90d012 100755 --- a/doc/reST/cli.rst +++ b/doc/reST/cli.rst @@ -2523,7 +2523,7 @@ VUI fields must be manually specified. .. option:: --nalu-file Text file containing userSEI in POC order : / - Parse the input file specified and inserts SEI messages into the bitstream. + Parse the input file specified and inserts SEI messages into the bitstream. Currently, we support only PREFIX SEI messages. This is an "application-only" feature. .. option:: --atc-sei @@ -2535,8 +2535,19 @@ VUI fields must be manually specified. .. option:: --pic-struct Set the picture structure and emits it in the picture timing SEI message. - Values in the range 0..12. See D.3.3 of the HEVC spec. for a detailed explanation. - Required for HLG (Hybrid Log Gamma) signaling. Not signaled by default. + Values in the range 0..12. See Table D.2 and D.3.3 of the HEVC spec. for a detailed explanation. + +.. option:: --psfile + + Specify a text file which contains the picture structure for some or all frames. + Allows for dynamic pulldown (VFR in CFR containers). The format of each line is + + framenumber framefieldcoding picstruct + + Framefieldcoding shall be 0, 1, 2 (progressive, bottom-first or top-first, resp.), + It shall not change for an encoded sequence and match the encoder configuration. + + Picstruct is the picture structure to use in the framenumber's Picture Timing SEI. .. option:: --video-signal-type-preset diff --git a/source/abrEncApp.cpp b/source/abrEncApp.cpp index d9b2cedec..8c491cdfa 100644 --- a/source/abrEncApp.cpp +++ b/source/abrEncApp.cpp @@ -535,7 +535,7 @@ namespace X265_NS { int analysisWrite = m_parent->m_analysisWriteCnt[analysisQId].get(); int written = analysisWrite * m_parent->m_passEnc[analysisQId]->m_cliopt.numRefs; int analysisRead = m_parent->m_analysisReadCnt[analysisQId].get(); - + while (m_threadActive && written == analysisRead) { analysisWrite = m_parent->m_analysisWriteCnt[analysisQId].waitForChange(analysisWrite); @@ -763,6 +763,17 @@ namespace X265_NS { } } + if (m_cliopt.psfile) + { + if (!m_cliopt.parsePSFile(pic_orig[view], m_param->interlaceMode, m_param->bField)) + { + x265_log(NULL, X265_LOG_ERROR, "can't parse psfile for frame %d in %s\n", + pic_in[view]->poc, profileName); + fclose(m_cliopt.psfile); + m_cliopt.psfile = NULL; + } + } + if (m_cliopt.framesToBeEncoded && inFrameCount >= m_cliopt.framesToBeEncoded) pic_in[view] = NULL; else if (readPicture(pic_in[view], view)){ diff --git a/source/abrEncApp.h b/source/abrEncApp.h index 4c3ededc0..7fbc51e64 100644 --- a/source/abrEncApp.h +++ b/source/abrEncApp.h @@ -3,7 +3,7 @@ * * Authors: Pooja Venkatesan * Aruna Matheswaran -* +* * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -91,6 +91,7 @@ namespace X265_NS { CLIOptions m_cliopt; InputFile* m_input[MAX_VIEWS]; const char* m_reconPlayCmd; + FILE* m_psfile; FILE* m_qpfile; FILE* m_zoneFile; FILE* m_dolbyVisionRpu;/* File containing Dolby Vision BL RPU metadata */ diff --git a/source/encoder/api.cpp b/source/encoder/api.cpp index efcc6bdb6..e5c2d4183 100644 --- a/source/encoder/api.cpp +++ b/source/encoder/api.cpp @@ -1011,7 +1011,7 @@ void x265_picture_init(x265_param *param, x265_picture *pic) pic->userSEI.numPayloads = 0; pic->rpu.payloadSize = 0; pic->rpu.payload = NULL; - pic->picStruct = 0; + pic->picStruct = PIC_STRUCT_AUTO; pic->vbvEndFlag = 0; if ((strlen(param->analysisSave) || strlen(param->analysisLoad)) || (param->bAnalysisType == AVC_INFO)) diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index 24d6516f6..dc1f25d84 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -220,8 +220,8 @@ void Encoder::create() framesize += (stride * (p->sourceHeight >> x265_cli_csps[p->internalCsp].height[i])); } - //Sets the picture structure and emits it in the picture timing SEI message - m_param->pictureStructure = 0; + // Override user setting, let the dup logic decide. + m_param->pictureStructure = -1; for (uint32_t i = 0; i < DUP_BUFFER; i++) { @@ -1268,7 +1268,7 @@ uint64_t Encoder::computeSSD(pixel *fenc, pixel *rec, intptr_t stride, uint32_t } } - /* Handle last few rows of frames for videos + /* Handle last few rows of frames for videos with height not divisble by 4 */ uint32_t h = height % y; if (param->bEnableFrameDuplication && h) @@ -1762,10 +1762,13 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_forceqp = inputPic[0]->forceqp; inFrame[layer]->m_param = (m_reconfigure || m_reconfigureRc || m_param->bConfigRCFrame) ? m_latestParam : m_param; - inFrame[layer]->m_picStruct = inputPic[0]->picStruct; - if (inFrame[layer]->m_param->pictureStructure > -1) + if (inFrame[layer]->m_param->pictureStructure >= 0) inFrame[layer]->m_picStruct = inFrame[layer]->m_param->pictureStructure; - inFrame[layer]->m_picStruct = inFrame[layer]->m_picStruct < PIC_STRUCT_COUNT ? inFrame[layer]->m_picStruct : 0; + else + inFrame[layer]->m_picStruct = inputPic[0]->picStruct; + + if (inFrame[layer]->m_picStruct >= PIC_STRUCT_COUNT) + inFrame[layer]->m_picStruct = PIC_STRUCT_PROGRESSIVE_FRAME; /* Set up frame timing info for slicetype and ratecontrol and the frame timebase (could change across cvs) */ if (inFrame[layer]->m_param->bEmitVUITimingInfo) @@ -3771,7 +3774,7 @@ void Encoder::initSPS(SPS *sps) vui.defaultDisplayWindow.bottomOffset = m_param->vui.defDispWinBottomOffset; vui.defaultDisplayWindow.leftOffset = m_param->vui.defDispWinLeftOffset; - vui.frameFieldInfoPresentFlag = !!m_param->interlaceMode || (m_param->pictureStructure >= 0); + vui.frameFieldInfoPresentFlag = !!m_param->interlaceMode || (m_param->bEmitHRDSEI); vui.fieldSeqFlag = !!m_param->interlaceMode; vui.hrdParametersPresentFlag = m_param->bEmitHRDSEI; @@ -4591,10 +4594,10 @@ void Encoder::configure(x265_param *p) p->bEnableFrameDuplication = 0; } - if (p->bEnableFrameDuplication && p->pictureStructure != 0 && p->pictureStructure != -1) + if (p->bEnableFrameDuplication && p->pictureStructure > PIC_STRUCT_AUTO) { - x265_log(p, X265_LOG_WARNING, "Frame-duplication works only with pic_struct = 0. Setting pic-struct = 0.\n"); - p->pictureStructure = 0; + x265_log(p, X265_LOG_WARNING, "Frame-duplication works only without forced pic-struct value. Ignoring the picture structure.\n"); + p->pictureStructure = PIC_STRUCT_AUTO; } if (m_param->bEnableFrameDuplication && (!bIsVbv || !m_param->bEmitHRDSEI)) diff --git a/source/encoder/encoder.h b/source/encoder/encoder.h index c2cc4d3b3..9f38f3eab 100644 --- a/source/encoder/encoder.h +++ b/source/encoder/encoder.h @@ -99,24 +99,6 @@ struct EncStats #define MAX_NUM_REF_IDX 64 #define DUP_BUFFER 2 -enum PicStruct -{ - PIC_STRUCT_PROGRESSIVE_FRAME = 0, - PIC_STRUCT_FIELD_TOP = 1, - PIC_STRUCT_FIELD_BOTTOM = 2, - PIC_STRUCT_TOP_BOTTOM = 3, - PIC_STRUCT_BOTTOM_TOP = 4, - PIC_STRUCT_TOP_BOTTOM_TOP = 5, - PIC_STRUCT_BOTTOM_TOP_BOTTOM = 6, - PIC_STRUCT_DOUBLING = 7, - PIC_STRUCT_TRIPLING = 8, - PIC_STRUCT_TOP_PREVBOTTOM = 9, - PIC_STRUCT_BOTTOM_PREVTOP = 10, - PIC_STRUCT_TOP_NEXTBOTTOM = 11, - PIC_STRUCT_BOTTOM_NEXTTOP = 12, - PIC_STRUCT_COUNT -}; - struct RefIdxLastGOP { int numRefIdxDefault[2]; diff --git a/source/encoder/frameencoder.cpp b/source/encoder/frameencoder.cpp index 43c8f3d91..db0e91e08 100644 --- a/source/encoder/frameencoder.cpp +++ b/source/encoder/frameencoder.cpp @@ -796,32 +796,34 @@ void FrameEncoder::compressFrame(int layer) if (vui->frameFieldInfoPresentFlag) { - if (m_param->interlaceMode > 0) + if (m_frame[layer]->m_picStruct == PIC_STRUCT_AUTO) { - if( m_param->interlaceMode == 2 ) + if (m_param->interlaceMode > 0) { - // m_picStruct should be set to 3 or 4 when field feature is enabled - if (m_param->bField) - // 3: Top field, bottom field, in that order; 4: Bottom field, top field, in that order - sei->m_picStruct = (slice->m_fieldNum == 1) ? 4 : 3; - else - sei->m_picStruct = (poc & 1) ? 1 /* top */ : 2 /* bottom */; - } - else if (m_param->interlaceMode == 1) - { - if (m_param->bField) - sei->m_picStruct = (slice->m_fieldNum == 1) ? 3: 4; - else - sei->m_picStruct = (poc & 1) ? 2 /* bottom */ : 1 /* top */; + if( m_param->interlaceMode == 2 ) + { + // m_picStruct should be set to 3 or 4 when field feature is enabled + if (m_param->bField) + // 3: Top field, bottom field, in that order; 4: Bottom field, top field, in that order + sei->m_picStruct = (slice->m_fieldNum == 1) ? PIC_STRUCT_BOTTOM_TOP : PIC_STRUCT_TOP_BOTTOM; + else + sei->m_picStruct = (poc & 1) ? PIC_STRUCT_FIELD_TOP : PIC_STRUCT_FIELD_BOTTOM; + } + else if (m_param->interlaceMode == 1) + { + if (m_param->bField) + sei->m_picStruct = (slice->m_fieldNum == 1) ? PIC_STRUCT_TOP_BOTTOM: PIC_STRUCT_BOTTOM_TOP; + else + sei->m_picStruct = (poc & 1) ? PIC_STRUCT_FIELD_BOTTOM : PIC_STRUCT_FIELD_TOP; + } } + else + sei->m_picStruct = PIC_STRUCT_PROGRESSIVE_FRAME; } - else if (m_param->bEnableFrameDuplication) - sei->m_picStruct = m_frame[layer]->m_picStruct; else - sei->m_picStruct = m_param->pictureStructure; + sei->m_picStruct = m_frame[layer]->m_picStruct; sei->m_sourceScanType = m_param->interlaceMode ? 0 : 1; - sei->m_duplicateFlag = false; } diff --git a/source/x265.h b/source/x265.h index 7ce38666f..e17f72f15 100644 --- a/source/x265.h +++ b/source/x265.h @@ -780,7 +780,7 @@ typedef struct x265_zone struct x265_param* zoneParam; double* relativeComplexity; } x265_zone; - + /* data to calculate aggregate VMAF score */ typedef struct x265_vmaf_data { @@ -797,8 +797,8 @@ typedef struct x265_vmaf_framedata { int width; int height; - int frame_set; - int internalBitDepth; + int frame_set; + int internalBitDepth; void *reference_frame; /* points to fenc of particular frame */ void *distorted_frame; /* points to recon of particular frame */ }x265_vmaf_framedata; @@ -2812,6 +2812,25 @@ static const char * const x265_api_query_errnames[] = { "libx265 has an invalid bitdepth" }; +enum PicStruct +{ + PIC_STRUCT_PROGRESSIVE_FRAME = 0, + PIC_STRUCT_FIELD_TOP = 1, + PIC_STRUCT_FIELD_BOTTOM = 2, + PIC_STRUCT_TOP_BOTTOM = 3, + PIC_STRUCT_BOTTOM_TOP = 4, + PIC_STRUCT_TOP_BOTTOM_TOP = 5, + PIC_STRUCT_BOTTOM_TOP_BOTTOM = 6, + PIC_STRUCT_DOUBLING = 7, + PIC_STRUCT_TRIPLING = 8, + PIC_STRUCT_TOP_PREVBOTTOM = 9, + PIC_STRUCT_BOTTOM_PREVTOP = 10, + PIC_STRUCT_TOP_NEXTBOTTOM = 11, + PIC_STRUCT_BOTTOM_NEXTTOP = 12, + PIC_STRUCT_COUNT, + PIC_STRUCT_AUTO = PIC_STRUCT_PROGRESSIVE_FRAME, +}; + #ifdef __cplusplus } #endif diff --git a/source/x265cli.cpp b/source/x265cli.cpp index b8d314332..86091a153 100755 --- a/source/x265cli.cpp +++ b/source/x265cli.cpp @@ -379,7 +379,8 @@ namespace X265_NS { H0(" --[no-]eos Emit end of sequence nal unit at the end of every coded video sequence. Default %s\n", OPT(param->bEnableEndOfSequence)); H1(" --hash Decoded Picture Hash SEI 0: disabled, 1: MD5, 2: CRC, 3: Checksum. Default %d\n", param->decodedPictureHashSEI); H0(" --atc-sei Emit the alternative transfer characteristics SEI message where the integer is the preferred transfer characteristic. Default disabled\n"); - H0(" --pic-struct Set the picture structure and emit it in the picture timing SEI message. Values in the range 0..12. See D.3.3 of the HEVC spec. for a detailed explanation.\n"); + H0(" --pic-struct Specify a unique picture structure to emit in every frames' picture timing SEI message. Values in the range 0..12. See D.3.3 of the HEVC spec. for a detailed explanation.\n"); + H0(" --psfile PicStruct file specifying the picture structure for some or all frames.\n"); H0(" --log2-max-poc-lsb Maximum of the picture order count\n"); H0(" --[no-]vui-timing-info Emit VUI timing information in the bitstream. Default %s\n", OPT(param->bEmitVUITimingInfo)); H0(" --[no-]vui-hrd-info Emit VUI HRD information in the bitstream. Default %s\n", OPT(param->bEmitVUIHRDInfo)); @@ -475,6 +476,9 @@ namespace X265_NS { if (qpfile) fclose(qpfile); qpfile = NULL; + if (psfile) + fclose(psfile); + psfile = NULL; if (zoneFile) fclose(zoneFile); zoneFile = NULL; @@ -803,6 +807,12 @@ namespace X265_NS { OPT("output-depth") /* handled above */; OPT("recon-y4m-exec") reconPlayCmd = optarg; OPT("svt") /* handled above */; + OPT("psfile") + { + this->psfile = x265_fopen(optarg, "rb"); + if (!this->qpfile) + x265_log_file(param, X265_LOG_ERROR, "%s psfile not found or error in opening ps file\n", optarg); + } OPT("qpfile") { this->qpfile = x265_fopen(optarg, "rb"); @@ -1115,6 +1125,43 @@ namespace X265_NS { return false; } + bool CLIOptions::parsePSFile(x265_picture &pic_org, int fieldOrder, bool frameFields) + { + int32_t num = -1, ret; + uint32_t filePos, frameFieldCoding, pictureStructure; + + uint32_t validPicStructMask = 0x181; //progressive, doubling, tripling + if (fieldOrder > 0) + validPicStructMask = frameFields ? 0x78 : 0x1e06; /* D.2 field_seq_flag = 0 or 1 */ + + while (num < pic_org.poc) + { + filePos = ftell(psfile); + ret = fscanf(psfile, "%u %u%*[ \t]%u\n", &num, &frameFieldCoding, &pictureStructure); + + if (num > pic_org.poc || ret == EOF) + { + fseek(psfile, filePos, SEEK_SET); + break; + } + if (num < pic_org.poc && ret >= 1) + continue; + if (ret == 3 && pictureStructure < PIC_STRUCT_COUNT) + { + /* don't allow to change frame_field coding */ + if ((frameFieldCoding > 0) ^ (fieldOrder > 0)) + return 0; + if ((1 << pictureStructure) & validPicStructMask) + pic_org.picStruct = pictureStructure; + return 1; + } + if (ret < 3) + return 0; + } + /* not changed, use default from constructor */ + return 1; + } + bool CLIOptions::parseQPFile(x265_picture &pic_org) { int32_t num = -1, qp, ret; diff --git a/source/x265cli.h b/source/x265cli.h index 6ac7d2ab1..1420d9da0 100644 --- a/source/x265cli.h +++ b/source/x265cli.h @@ -273,6 +273,7 @@ static const struct option long_options[] = { "info", no_argument, NULL, 0 }, { "no-info", no_argument, NULL, 0 }, { "zones", required_argument, NULL, 0 }, + { "psfile", required_argument, NULL, 0 }, { "qpfile", required_argument, NULL, 0 }, { "zonefile", required_argument, NULL, 0 }, { "no-zonefile-rc-init", no_argument, NULL, 0 }, @@ -416,6 +417,7 @@ static const struct option long_options[] = InputFile* input[MAX_VIEWS]; ReconFile* recon[MAX_LAYERS]; OutputFile* output; + FILE* psfile; FILE* qpfile; FILE* zoneFile; FILE* dolbyVisionRpu; /* File containing Dolby Vision BL RPU metadata */ @@ -464,6 +466,7 @@ static const struct option long_options[] = for (int i = 0; i < MAX_VIEWS; i++) inputfn[i] = NULL; output = NULL; + psfile = NULL; qpfile = NULL; zoneFile = NULL; dolbyVisionRpu = NULL; @@ -502,6 +505,7 @@ static const struct option long_options[] = bool parse(int argc, char **argv); bool parseZoneParam(int argc, char **argv, x265_param* globalParam, int zonefileCount); bool parseQPFile(x265_picture &pic_org); + bool parsePSFile(x265_picture &pic_org, int fieldOrder, bool frameFields); bool parseZoneFile(); int rpuParser(x265_picture * pic); bool parseScenecutAwareQpConfig(); From 8cc17eef3fd18fc0f208bcf30f156bfee57b431c Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:28:53 +0200 Subject: [PATCH 11/13] slicetype: clean-up HRD timing variables. --- source/common/frame.cpp | 3 +-- source/common/frame.h | 6 +++--- source/common/lowres.cpp | 2 ++ source/common/lowres.h | 6 +++++- source/encoder/encoder.cpp | 7 +------ source/encoder/ratecontrol.cpp | 6 +++--- source/encoder/slicetype.cpp | 24 +++++++++++++++++++----- source/encoder/slicetype.h | 1 + 8 files changed, 35 insertions(+), 20 deletions(-) diff --git a/source/common/frame.cpp b/source/common/frame.cpp index f5369864c..8a4a01c55 100644 --- a/source/common/frame.cpp +++ b/source/common/frame.cpp @@ -88,10 +88,9 @@ Frame::Frame() // HRD management m_duration = 0; - m_timebase = 0.0; m_cpbDelay = 0; m_dpbOutputDelay = 0; - m_plannedCpbDuration = 0; + m_cpbDuration = 0; } bool Frame::create(x265_param *param, float* quantOffsets) diff --git a/source/common/frame.h b/source/common/frame.h index a8c028f2b..76a824420 100644 --- a/source/common/frame.h +++ b/source/common/frame.h @@ -184,12 +184,12 @@ class Frame /*HRD delays for this frame*/ unsigned int m_duration; // display duration of picture in ticks (VUI timebase) - double m_timebase; // the timebase for this picture [seconds] - unsigned int m_plannedCpbDuration; // lifetime in the CPB (VUI timebase) + unsigned int m_cpbDuration; // lifetime in the CPB (VUI timebase) unsigned int m_cpbDelay; // removal delay of the picture (VUI timebase) + unsigned int m_dpbOutputDelay; // output delay of the picture (VUI timebase) uint64_t m_displayPicCount; // display picture count (counted in decoder ticks) uint64_t m_codedPicCount; // coded picture count (counted in decoder ticks) - unsigned int m_dpbOutputDelay; // output delay of the picture (VUI timebase) + double m_timebase; // the timebase for this picture [seconds] Frame(); diff --git a/source/common/lowres.cpp b/source/common/lowres.cpp index e8331c91a..3903c470b 100644 --- a/source/common/lowres.cpp +++ b/source/common/lowres.cpp @@ -352,6 +352,8 @@ void Lowres::init(PicYuv* origPic, int poc, bool bEnableTemporalFilter) noiseScore = -1; frameNum = poc; cpbDurationSecs = 0.; + dispDurationSecs = 0.; + dispPicCount = durationPicCount = 0; leadingBframes = 0; indB = 0; memset(costEst, -1, sizeof(costEst)); diff --git a/source/common/lowres.h b/source/common/lowres.h index 510dd2b2e..6387756d1 100644 --- a/source/common/lowres.h +++ b/source/common/lowres.h @@ -181,8 +181,12 @@ struct Lowres : public ReferencePlanes int32_t noiseScore; // Raw noise score from estimateNoise() at GOP start; -1 for non-GOP-start frames double ipCostRatio; + + /* Vbv Lookahead & cuTree durations */ double cpbDurationSecs; double dispDurationSecs; + int64_t dispPicCount; + int64_t durationPicCount; /* lookahead output data */ int64_t costEst[X265_BFRAME_MAX + 2][X265_BFRAME_MAX + 2]; @@ -209,7 +213,7 @@ struct Lowres : public ReferencePlanes /* used for vbvLookahead */ int plannedType[X265_LOOKAHEAD_MAX + 1]; int64_t plannedSatd[X265_LOOKAHEAD_MAX + 1]; - double plannedCpbDuration[X265_LOOKAHEAD_MAX + 1]; + double plannedCpbDuration[X265_LOOKAHEAD_MAX + 1]; /* durations of frames ahead for VBV */ int indB; int bframes; diff --git a/source/encoder/encoder.cpp b/source/encoder/encoder.cpp index dc1f25d84..801551905 100644 --- a/source/encoder/encoder.cpp +++ b/source/encoder/encoder.cpp @@ -1778,13 +1778,8 @@ int Encoder::encode(const x265_picture* pic_in, x265_picture* pic_out) inFrame[layer]->m_duration = g_deltaToDivisor[inFrame[layer]->m_picStruct]; inFrame[layer]->m_displayPicCount = m_dispPicCount; - inFrame[layer]->m_lowres.dispDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; - /* start off by assuming the cpb duration is equal to the display duration - * this is a fair assumption, except for highly VFR streams */ - inFrame[layer]->m_lowres.cpbDurationSecs = inFrame[layer]->m_duration * inFrame[layer]->m_timebase; - - /* update presentation counts (decoder ticks count) */ + /* update presentation count (in-display decoder ticks count) */ m_dispPicCount += inFrame[layer]->m_duration; /*Copy reconfigured RC parameters to frame*/ diff --git a/source/encoder/ratecontrol.cpp b/source/encoder/ratecontrol.cpp index 5fa46b14d..30779b9ee 100644 --- a/source/encoder/ratecontrol.cpp +++ b/source/encoder/ratecontrol.cpp @@ -1377,7 +1377,7 @@ int RateControl::rateControlStart(Frame* curFrame, RateControlEntry* rce, Encode rce->keptAsRef = IS_REFERENCED(curFrame); m_predType = getPredictorType(curFrame->m_lowres.sliceType, m_sliceType); rce->poc = m_curSlice->m_poc; - rce->cpbDuration = curFrame->m_plannedCpbDuration; + rce->cpbDuration = curFrame->m_cpbDuration; rce->frameDuration = curFrame->m_duration; if (m_param->bEnableSBRC) @@ -2661,7 +2661,7 @@ double RateControl::clipQscale(Frame* curFrame, RateControlEntry* rce, double q) curBits = predictSize(&m_pred[m_predType], q, (double)m_currentSatd); double bufferFillCur = m_bufferFill - curBits; double targetFill; - double lastDuration = curFrame->m_plannedCpbDuration * curFrame->m_timebase; + double lastDuration = curFrame->m_cpbDuration * m_timebase; double totalDuration = 0; frameQ[P_SLICE] = m_sliceType == I_SLICE ? q * m_param->rc.ipFactor : (m_sliceType == B_SLICE ? q / m_param->rc.pbFactor : q); frameQ[B_SLICE] = frameQ[P_SLICE] * m_param->rc.pbFactor; @@ -3538,7 +3538,7 @@ double RateControl::forwardMasking(Frame* curFrame, double q) double qp = x265_qScale2qp(q); double fps = 1. / m_timebase; /* todo: consider exponential moving average for the window sizes, looking at each frame's duration */ - fps = m_framesDone < fps ? 1/(curFrame->m_duration * curFrame->m_timebase) : m_durationDone / m_framesDone; + fps = m_framesDone < fps ? 1/(curFrame->m_duration * m_timebase) : m_durationDone / m_framesDone; uint32_t maxWindowSize = uint32_t((m_param->fwdMaxScenecutWindow / 1000.0) * fps + 0.5); uint32_t windowSize[6], prevWindow = 0; int lastScenecut = m_top->m_rateControl->m_lastScenecut; diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 1e131e144..2429db8de 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -2129,7 +2129,6 @@ void Lookahead::slicetypeDecide() x265_log(m_param, X265_LOG_WARNING, "B-ref at frame %d incompatible with B-pyramid\n", frm.frameNum); } - /* pyramid with multiple B-refs needs a big enough dpb that the preceding P-frame stays available. * smaller dpb could be supported by smart enough use of mmco, but it's easier just to forbid it. */ else if (frm.sliceType == X265_TYPE_BREF && m_param->bBPyramid && brefs && @@ -2761,8 +2760,10 @@ void Lookahead::slicetypeDecide() int j; for (j = 0; j < maxSearch; j++) { + setDurationsToLowres(curFrame); frames[j + 1] = &curFrame->m_lowres; curFrame = curFrame->m_next; + } m_inputLock.release(); @@ -2791,7 +2792,7 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) { frame->m_cpbDelay = m_cpbDelay; frame->m_dpbOutputDelay = frame->m_displayPicCount - m_codedPicCount; - frame->m_plannedCpbDuration = frame->m_duration; + frame->m_cpbDuration = frame->m_duration; frame->m_codedPicCount = m_codedPicCount; int dpbDelay = (int64_t)frame->m_displayPicCount - (int64_t)m_codedPicCount; @@ -2817,16 +2818,30 @@ void Lookahead::calculateDurations(Frame *frame, Frame *prevFrame) frame->m_dpbOutputDelay = (unsigned int)dpbDelay; } + setDurationsToLowres(frame); + /* Buffering Period SEI (attached with the keyframe) */ if (!m_param->bIntraRefresh && frame->m_lowres.bKeyframe) { m_cpbDelay = 0; } - m_cpbDelay += frame->m_plannedCpbDuration; + m_cpbDelay += frame->m_cpbDuration; m_codedPicCount += frame->m_duration; } +void Lookahead::setDurationsToLowres(Frame *frame) +{ + frame->m_lowres.dispPicCount = frame->m_displayPicCount; + frame->m_lowres.durationPicCount = frame->m_duration; + + /* fair assumption: assume the cpb duration is equal to the display duration. + * It's only an issue with extreme VFR or low delay applications */ + double durationSecs = frame->m_duration * frame->m_timebase; + frame->m_lowres.dispDurationSecs = durationSecs; + frame->m_lowres.cpbDurationSecs = durationSecs; +} + void Lookahead::vbvLookahead(Lowres **frames, int numFrames, int keyframe) { int prevNonB = 0, curNonB = 1, idx = 0; @@ -2951,7 +2966,6 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) if (framecnt) { - frames[framecnt + 1] = NULL; if (m_param->bResetZoneConfig) @@ -4210,7 +4224,7 @@ int64_t Lookahead::frameCostRecalculate(Lowres** frames, int p0, int p1, int b) qp_offset[cux * 2 + cuy * m_8x8Width * 4 + 1] + qp_offset[cux * 2 + cuy * m_8x8Width * 4 + frames[b]->maxBlocksInRowFullRes] + qp_offset[cux * 2 + cuy * m_8x8Width * 4 + frames[b]->maxBlocksInRowFullRes + 1]) / 4; - else + else qp_adj = qp_offset[cuxy]; cuCost = (cuCost * x265_exp2fix8(qp_adj) + 128) >> 8; rowSatd[cuy] += cuCost; diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index 8bd053abf..fb1516beb 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -263,6 +263,7 @@ class Lookahead : public JobProvider int64_t slicetypePathCost(Lowres **frames, char *path, int64_t threshold); int64_t vbvFrameCost(Lowres **frames, int p0, int p1, int b); void calculateDurations(Frame *frame, Frame *prevFrame); + void setDurationsToLowres(Frame *frame); void vbvLookahead(Lowres **frames, int numFrames, int keyframes); void aqMotion(Lowres **frames, bool bintra); void calcMotionAdaptiveQuantFrame(Lowres **frames, int p0, int p1, int b); From c8d0efd7c9a22c69c4ee92e91d2b7c9f22b9a1d1 Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Wed, 9 Sep 2026 15:29:21 +0200 Subject: [PATCH 12/13] WIP: tick-based keyframe interval. --- source/encoder/slicetype.cpp | 126 +++++++++++++++++++++++------------ source/encoder/slicetype.h | 3 +- 2 files changed, 85 insertions(+), 44 deletions(-) diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 2429db8de..6137e0b5b 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1080,6 +1080,7 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool, SPS* sps) m_cuTreeStrength = (m_param->rc.hevcAq ? 6.0 : 5.0) * (1.0 - m_param->rc.qCompress); m_lastKeyframe = -m_param->keyframeMax; + m_lastKeyframeNum = -m_param->keyframeMax; m_sliceTypeBusy = false; m_fullQueueSize = X265_MAX(1, m_param->lookaheadDepth); m_bAdaptiveQuant = m_param->rc.aqMode || @@ -2055,7 +2056,7 @@ void Lookahead::slicetypeDecide() { if (m_frameVariance[k] == -1) break; - if((k > 0 && m_frameVariance[k] >= m_frameVariance[k - 1]) || + if((k > 0 && m_frameVariance[k] >= m_frameVariance[k - 1]) || (k == 0 && m_frameVariance[k] >= m_frameVariance[length - 1])) { m_isFadeIn = true; @@ -2120,6 +2121,11 @@ void Lookahead::slicetypeDecide() for (bframes = 0, brefs = 0;; bframes++) { Lowres& frm = list[bframes]->m_lowres; + /* frame timing. duration must be accounted for else a keyframe limit could be overshoot + * during the display of the said frame. */ + int64_t displayPicCount = (int64_t)list[bframes]->m_displayPicCount; + int64_t durationPicCount = (int64_t)list[bframes]->m_duration; + int64_t keyintTicksDelta = displayPicCount - m_lastKeyframe + durationPicCount; if (frm.sliceTypeReq != X265_TYPE_AUTO && frm.sliceTypeReq != frm.sliceType) frm.sliceType = frm.sliceTypeReq; @@ -2138,8 +2144,9 @@ void Lookahead::slicetypeDecide() x265_log(m_param, X265_LOG_WARNING, "B-ref at frame %d incompatible with B-pyramid and %d reference frames\n", frm.sliceType, m_param->maxNumReferences); } - if (((!m_param->bIntraRefresh || frm.frameNum == 0) && frm.frameNum - m_lastKeyframe >= m_param->keyframeMax && - (!m_extendGopBoundary || frm.frameNum - m_lastKeyframe >= m_param->keyframeMax + m_param->gopLookahead)) || + + if (((!m_param->bIntraRefresh || frm.frameNum == 0) && keyintTicksDelta > m_param->keyframeMax && + (!m_extendGopBoundary || keyintTicksDelta > m_param->keyframeMax + m_param->gopLookahead)) || (frm.frameNum == (m_param->chunkStart - 1)) || (frm.frameNum == m_param->chunkEnd)) { if (frm.sliceType == X265_TYPE_AUTO || frm.sliceType == X265_TYPE_I) @@ -2167,11 +2174,12 @@ void Lookahead::slicetypeDecide() frm.sliceType = X265_TYPE_IDR; } } - if ((frm.sliceType == X265_TYPE_I && frm.frameNum - m_lastKeyframe >= m_param->keyframeMin) || (frm.frameNum == (m_param->chunkStart - 1)) || (frm.frameNum == m_param->chunkEnd)) + if ((frm.sliceType == X265_TYPE_I && keyintTicksDelta > m_param->keyframeMin) || (frm.frameNum == (m_param->chunkStart - 1)) || (frm.frameNum == m_param->chunkEnd)) { if (m_param->bOpenGOP) { - m_lastKeyframe = frm.frameNum; + m_lastKeyframe = displayPicCount; + m_lastKeyframeNum = frm.frameNum; frm.bKeyframe = true; } else @@ -2186,7 +2194,8 @@ void Lookahead::slicetypeDecide() if (frm.sliceType == X265_TYPE_IDR) { /* Closed GOP */ - m_lastKeyframe = frm.frameNum; + m_lastKeyframe = displayPicCount; + m_lastKeyframeNum = frm.frameNum; frm.bKeyframe = true; int zoneRadl = 0; if (m_param->bResetZoneConfig) @@ -2230,12 +2239,15 @@ void Lookahead::slicetypeDecide() for (bframes = 0, brefs = 0;; bframes++) { Lowres& frm = list[bframes]->m_lowres; + int64_t displayPicCount = (int64_t)list[bframes]->m_displayPicCount; + int64_t durationPicCount = (int64_t)list[bframes]->m_duration; if (frm.sliceType == X265_TYPE_BREF) brefs++; - if ((IS_X265_TYPE_I(frm.sliceType) && frm.frameNum - m_lastKeyframe >= m_param->keyframeMin) + if ((IS_X265_TYPE_I(frm.sliceType) && displayPicCount - m_lastKeyframe + durationPicCount > m_param->keyframeMin) || (frm.frameNum == (m_param->chunkStart - 1)) || (frm.frameNum == m_param->chunkEnd)) { - m_lastKeyframe = frm.frameNum; + m_lastKeyframe = displayPicCount; + m_lastKeyframeNum = frm.frameNum; frm.bKeyframe = true; } if (!IS_X265_TYPE_B(frm.sliceType)) @@ -2943,20 +2955,29 @@ int64_t Lookahead::vbvFrameCost(Lowres **frames, int p0, int p1, int b) void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) { - int numFrames, origNumFrames, keyintLimit, framecnt; + int numFrames, origNumFrames, framecnt; int maxSearch = X265_MIN(m_param->lookaheadDepth, X265_LOOKAHEAD_MAX); int cuCount = m_8x8Blocks; int resetStart; bool bIsVbvLookahead = m_param->rc.vbvBufferSize && m_param->lookaheadDepth; + int keyintTimeLimit = m_param->keyframeMax + (int)(m_lastKeyframe - (frames[0]->dispPicCount + frames[0]->durationPicCount)); + int keyIntFrameCnt = 0, durationAhead = 0; + /* count undecided frames */ for (framecnt = 0; framecnt < maxSearch; framecnt++) { Lowres *fenc = frames[framecnt + 1]; if (!fenc || fenc->sliceType != X265_TYPE_AUTO) break; + durationAhead += fenc->durationPicCount; + if (durationAhead <= keyintTimeLimit) + ++keyIntFrameCnt; } + /* protect against runs where lowres durations aren't ready */ + keyIntFrameCnt = X265_MIN(keyIntFrameCnt, m_param->keyframeMax); + if (!framecnt && m_param->analysisLoadReuseLevel != 1) { if (m_param->rc.cuTree) @@ -2976,31 +2997,29 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) curZoneStart += curZoneStart ? m_param->rc.zones[i].zoneParam->radl : 0; nextZoneStart += (i + 1 < m_param->rc.zonefileCount) ? m_param->rc.zones[i + 1].startFrame + m_param->rc.zones[i + 1].zoneParam->radl : m_param->totalFrames; if (curZoneStart <= frames[0]->frameNum && nextZoneStart > frames[0]->frameNum) - m_param->keyframeMax = nextZoneStart - curZoneStart; + keyIntFrameCnt = X265_MIN(keyIntFrameCnt, nextZoneStart - curZoneStart); if (m_param->rc.zones[m_param->rc.zonefileCount - 1].startFrame <= frames[0]->frameNum && nextZoneStart == 0) - m_param->keyframeMax = m_param->rc.zones[0].keyframeMax; + keyIntFrameCnt = X265_MIN(keyIntFrameCnt, m_param->rc.zones[0].keyframeMax); } } } - int keylimit = m_param->keyframeMax; if (frames[0]->frameNum < m_param->chunkEnd) { - int chunkStart = (m_param->chunkStart - m_lastKeyframe - 1); - int chunkEnd = (m_param->chunkEnd - m_lastKeyframe); - if ((chunkStart > 0) && (chunkStart < m_param->keyframeMax)) - keylimit = chunkStart; - else if ((chunkEnd > 0) && (chunkEnd < m_param->keyframeMax)) - keylimit = chunkEnd; + int chunkStart = (m_param->chunkStart - m_lastKeyframeNum - 1); + int chunkEnd = (m_param->chunkEnd - m_lastKeyframeNum); + if ((chunkStart > 0) && (chunkStart < keyIntFrameCnt)) + keyIntFrameCnt = chunkStart; + else if ((chunkEnd > 0) && (chunkEnd < keyIntFrameCnt)) { + keyIntFrameCnt = chunkEnd; + } } - int keyFrameLimit = keylimit + m_lastKeyframe - frames[0]->frameNum - 1; - if (m_param->gopLookahead && keyFrameLimit <= m_param->bframes + 1) - keyintLimit = keyFrameLimit + m_param->gopLookahead; - else - keyintLimit = keyFrameLimit; + int keyIntFrameCntExtended = keyIntFrameCnt; + if (m_param->gopLookahead && keyIntFrameCnt <= m_param->bframes + 1) + keyIntFrameCntExtended += m_param->gopLookahead; - origNumFrames = numFrames = m_param->bIntraRefresh ? framecnt : X265_MIN(framecnt, keyintLimit); + origNumFrames = numFrames = m_param->bIntraRefresh ? framecnt : X265_MIN(framecnt, keyIntFrameCntExtended); if (framecnt) { @@ -3100,7 +3119,7 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) frames[1]->sliceType = X265_TYPE_I; return; } - if (m_param->gopLookahead && (keyFrameLimit >= 0) && (keyFrameLimit <= m_param->bframes + 1)) + if (m_param->gopLookahead && (keyIntFrameCnt >= 0) && (keyIntFrameCnt <= m_param->bframes + 1)) { bool sceneTransition = m_isSceneTransition; m_extendGopBoundary = false; @@ -3206,7 +3225,20 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) bool bForceRADL = zoneRadl || (m_param->radl && (m_param->keyframeMax == m_param->keyframeMin)); bool bLastMiniGop = (framecnt >= m_param->bframes + 1) ? false : true; int radl = m_param->radl ? m_param->radl : zoneRadl; - int preRADL = m_lastKeyframe + m_param->keyframeMax - radl - 1; /*Frame preceeding RADL in POC order*/ + + int preRADL = frames[0]->frameNum; + for (int j = numBFrames; j > 0; --j) + { + /*Frame preceeding RADL in POC order*/ + if (frames[j]->dispPicCount + frames[j]->durationPicCount - m_lastKeyframe < m_param->keyframeMax) + { + preRADL = frames[j]->frameNum; + break; + } + } + /* radl in frame counts */ + preRADL -= radl; + if (bForceRADL && (frames[0]->frameNum == preRADL) && !bLastMiniGop) { int j = 1; @@ -3241,17 +3273,25 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) aqMotion(frames, bKeyframe); if (m_param->rc.cuTree) - cuTree(frames, X265_MIN(numFrames, m_param->keyframeMax), bKeyframe); + cuTree(frames, X265_MIN(numFrames, keyIntFrameCnt), bKeyframe); - if (m_param->gopLookahead && (keyFrameLimit >= 0) && (keyFrameLimit <= m_param->bframes + 1) && !m_extendGopBoundary) - keyintLimit = keyFrameLimit; + /* why? */ + if (m_param->gopLookahead && (keyIntFrameCnt >= 0) && (keyIntFrameCnt <= m_param->bframes + 1) && !m_extendGopBoundary) + keyIntFrameCntExtended = keyIntFrameCnt; if (!m_param->bIntraRefresh) - for (int j = keyintLimit + 1; j <= numFrames; j += m_param->keyframeMax) + { + unsigned int duration = 0; + for (int j = keyIntFrameCntExtended + 1; j <= numFrames; ++j) { - frames[j]->sliceType = X265_TYPE_I; - resetStart = X265_MIN(resetStart, j + 1); + if (duration % m_param->keyframeMax == 0) + { + frames[j]->sliceType = X265_TYPE_I; + resetStart = X265_MIN(resetStart, j + 1); + } + duration += frames[j]->durationPicCount; } + } if (bIsVbvLookahead) vbvLookahead(frames, numFrames, bKeyframe); @@ -3276,7 +3316,7 @@ void Lookahead::slicetypeAnalyse(Lowres **frames, bool bKeyframe) if (!shhh) break; } - cuTree(frames, X265_MIN(framecnt, m_param->keyframeMax), bKeyframe); + cuTree(frames, X265_MIN(framecnt, keyIntFrameCnt), bKeyframe); } } @@ -3337,7 +3377,7 @@ bool Lookahead::scenecut(Lowres **frames, int p0, int p1, bool bRealScenecut, in { int64_t curCost = frames[i]->costEst[i - p0][0]; int64_t prevCost = frames[i - 1]->costEst[i - 1 - p0][0]; - if (fabs((double)(curCost - avgSatdCost)) > 0.1 * avgSatdCost || + if (fabs((double)(curCost - avgSatdCost)) > 0.1 * avgSatdCost || fabs((double)(curCost - prevCost)) > 0.1 * prevCost) { fluctuate = true; @@ -3365,8 +3405,8 @@ bool Lookahead::scenecut(Lowres **frames, int p0, int p1, bool bRealScenecut, in } /* A frame is always analysed with bRealScenecut = true first, and then bRealScenecut = false, - the former for I decisions and the latter for P/B decisions. It's possible that the first - analysis detected scenecuts which were later nulled due to scene transitioning, in which + the former for I decisions and the latter for P/B decisions. It's possible that the first + analysis detected scenecuts which were later nulled due to scene transitioning, in which case do not return a true scenecut for this frame */ if (!frames[p1]->bScenecut && m_param->bframes) @@ -3383,7 +3423,7 @@ bool Lookahead::scenecutInternal(Lowres **frames, int p0, int p1, bool bRealScen estGroup.singleCost(p0, p1, p1); int64_t icost = frame->costEst[0][0]; int64_t pcost = frame->costEst[p1 - p0][0]; - int gopSize = (frame->frameNum - m_lastKeyframe) % m_param->keyframeMax; + int gopDuration = (frame->dispPicCount - m_lastKeyframe) % m_param->keyframeMax; float threshMax = (float)(m_param->scenecutThreshold / 100.0); /* magic numbers pulled out of thin air */ float threshMin = (float)(threshMax * 0.25); @@ -3393,15 +3433,15 @@ bool Lookahead::scenecutInternal(Lowres **frames, int p0, int p1, bool bRealScen { if (m_param->keyframeMin == m_param->keyframeMax) threshMin = threshMax; - if (gopSize <= m_param->keyframeMin / 4 || m_param->bIntraRefresh) + if (gopDuration <= m_param->keyframeMin / 4 || m_param->bIntraRefresh) bias = threshMin / 4; - else if (gopSize <= m_param->keyframeMin) - bias = threshMin * gopSize / m_param->keyframeMin; + else if (gopDuration <= m_param->keyframeMin) + bias = threshMin * gopDuration / m_param->keyframeMin; else { bias = threshMin + (threshMax - threshMin) - * (gopSize - m_param->keyframeMin) + * (gopDuration - m_param->keyframeMin) / (m_param->keyframeMax - m_param->keyframeMin); } } @@ -3412,7 +3452,7 @@ bool Lookahead::scenecutInternal(Lowres **frames, int p0, int p1, bool bRealScen int pmb = m_8x8Blocks - imb; frame->bScenecut = res; // for csv log x265_log(m_param, X265_LOG_DEBUG, "scene cut at %d Icost:%d Pcost:%d ratio:%.4f bias:%.4f gop:%d (imb:%d pmb:%d)\n", - frame->frameNum, icost, pcost, 1. - (double)pcost / icost, bias, gopSize, imb, pmb); + frame->frameNum, icost, pcost, 1. - (double)pcost / icost, bias, gopDuration, imb, pmb); } return res; } @@ -4043,7 +4083,7 @@ void Lookahead::computeCUTreeQpOffset(Lowres *frame, double averageDuration, int uint32_t idx = ((block_yy / loopIncr) * (maxCols)) + (block_xx / loopIncr); log2_ratio += *(pcCuTree8x8 + idx); - + blockXY++; } } diff --git a/source/encoder/slicetype.h b/source/encoder/slicetype.h index fb1516beb..838320a50 100644 --- a/source/encoder/slicetype.h +++ b/source/encoder/slicetype.h @@ -170,7 +170,8 @@ class Lookahead : public JobProvider /* pre-lookahead */ int m_fullQueueSize; - int m_lastKeyframe; + int64_t m_lastKeyframe; /* in decoder ticks */ + int m_lastKeyframeNum; int m_8x8Width; int m_8x8Height; int m_8x8Blocks; From 18035fa6b1e08dde6c17dea403a11fbe5996e23e Mon Sep 17 00:00:00 2001 From: cubicibo <55701024+cubicibo@users.noreply.github.com> Date: Wed, 9 Sep 2026 23:27:06 +0200 Subject: [PATCH 13/13] Fix pipeline warnings. --- source/encoder/slicetype.cpp | 3 +++ source/x265cli.cpp | 9 ++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/encoder/slicetype.cpp b/source/encoder/slicetype.cpp index 6137e0b5b..12d5e4c5f 100644 --- a/source/encoder/slicetype.cpp +++ b/source/encoder/slicetype.cpp @@ -1047,6 +1047,7 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool, SPS* sps) m_pool = pool; m_sps = sps; + m_numPools = 0; m_lastNonB = NULL; m_isSceneTransition = false; m_scratch = NULL; @@ -1075,6 +1076,8 @@ Lookahead::Lookahead(x265_param *param, ThreadPool* pool, SPS* sps) m_codedPicCount = 0; m_cpbDelay = 0; + memset(m_frameVariance, 0, sizeof(m_frameVariance)); + /* Allow the strength to be adjusted via qcompress, since the two concepts * are very similar. */ m_cuTreeStrength = (m_param->rc.hevcAq ? 6.0 : 5.0) * (1.0 - m_param->rc.qCompress); diff --git a/source/x265cli.cpp b/source/x265cli.cpp index 86091a153..b289b50a4 100755 --- a/source/x265cli.cpp +++ b/source/x265cli.cpp @@ -1127,8 +1127,8 @@ namespace X265_NS { bool CLIOptions::parsePSFile(x265_picture &pic_org, int fieldOrder, bool frameFields) { - int32_t num = -1, ret; - uint32_t filePos, frameFieldCoding, pictureStructure; + int32_t num = -1; + uint32_t frameFieldCoding, pictureStructure; uint32_t validPicStructMask = 0x181; //progressive, doubling, tripling if (fieldOrder > 0) @@ -1136,8 +1136,8 @@ namespace X265_NS { while (num < pic_org.poc) { - filePos = ftell(psfile); - ret = fscanf(psfile, "%u %u%*[ \t]%u\n", &num, &frameFieldCoding, &pictureStructure); + uint32_t filePos = ftell(psfile); + int ret = fscanf(psfile, "%d %u%*[ \t]%u\n", &num, &frameFieldCoding, &pictureStructure); if (num > pic_org.poc || ret == EOF) { @@ -1158,7 +1158,6 @@ namespace X265_NS { if (ret < 3) return 0; } - /* not changed, use default from constructor */ return 1; }