mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Arun Iyengar via B4 Relay <devnull+aiyengar1990.gmail.com@kernel.org>
To: Mauro Carvalho Chehab <mchehab@kernel.org>,
	 Paul Kocialkowski <paulk@sys-base.io>
Cc: linux-media@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Nicolas Dufresne <nicolas.dufresne@collabora.com>,
	 Hans Verkuil <hverkuil@kernel.org>,
	Marco Felsch <m.felsch@pengutronix.de>,
	 Michael Tretter <m.tretter@pengutronix.de>,
	 Arun Iyengar <aiyengar1990@gmail.com>
Subject: [PATCH RFC 06/11] media: v4l2-core: Bound the I picture size in the encode CBR rate control
Date: Tue, 10 Nov 2026 10:05:20 +0000	[thread overview]
Message-ID: <20261110-h264-encode-fixes-v1-6-7a64486df499@gmail.com> (raw)
In-Reply-To: <20261110-h264-encode-fixes-v1-0-7a64486df499@gmail.com>

From: Arun Iyengar <aiyengar1990@gmail.com>

The constant bitrate rate control codes I pictures a fixed QP delta below
the current QP state of the P pictures, whatever their size target. On
content whose P pictures get very cheap (static scenes), the state drifts
down over a GOP and the next IDR picture comes out far above its plan:
up to 33 times the nominal picture size at 1080p60 and 2 Mbit/s with
HEVC, 2.6 times its target and a third over its share of the GOP at the
largest I/P size ratio the plan allows. The P pictures of the GOP pay for
it in the end, but the bitrate over one second rises to 1.5 times the
target, and IDR sizes swing by up to 3 times from one GOP to the next.

Do what the Rockchip MPP library does for both codecs:

- code an I picture at the mean of the QP state now and at the previous
  I picture (weighing the current state 70% when it rose from 33 or below
  to 35 or above), so that a drift of the state over a GOP moves the next
  I picture by half as much;
- after an I picture larger than its share of the GOP at the largest I/P
  ratio (30), raise the QP of the following I pictures by up to 4 QP per
  picture, in all at most a quarter QP per QP step below 45, and take the
  increase back as I pictures come out below that share.

All-intra streams are unchanged: their QP state follows the I pictures
directly.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-enc-cbr.c | 98 +++++++++++++++++++++++++++++-----
 include/media/v4l2-enc-cbr.h           |  8 +++
 2 files changed, 93 insertions(+), 13 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-enc-cbr.c b/drivers/media/v4l2-core/v4l2-enc-cbr.c
index 5bd10d494054..32f98052e981 100644
--- a/drivers/media/v4l2-core/v4l2-enc-cbr.c
+++ b/drivers/media/v4l2-core/v4l2-enc-cbr.c
@@ -4,7 +4,12 @@
  *
  * One QP state is kept for the stream: it is the QP of the P (and B)
  * pictures, and I pictures are coded a fixed number of steps below it, so
- * that the quality of the reference pictures stays even across GOPs. Each
+ * that the quality of the reference pictures stays even across GOPs. The
+ * I pictures take the mean of the state now and at the previous I picture,
+ * so that a drift of the state over a GOP (static content, where P pictures
+ * are cheap) moves the size of the next I picture by half as much, and an
+ * I picture that exceeded its share of the GOP at the largest I/P size
+ * ratio raises the QP of the following I pictures until they fit. Each
  * picture gets a size target from a GOP plan, in which an I picture is
  * "ratio" times the size of a P picture, with the ratio and the IDR period
  * measured on the stream. The difference between the coded size so far and
@@ -39,6 +44,15 @@
 #define CBR_RATIO_INIT		160
 #define CBR_RATIO_MIN		24
 #define CBR_RATIO_MAX		480
+/*
+ * The I QP increase after oversized I pictures: at most 4 QP (1/256 units)
+ * per I picture for an I picture twice its largest share, and in all at most
+ * a quarter QP per QP step below 45, up to 6 QP (more where the QP is low,
+ * where a QP step changes the size by fewer bits).
+ */
+#define CBR_INTRA_ADD_STEP	(4 * 256)
+#define CBR_INTRA_ADD_QP_END	45
+#define CBR_INTRA_ADD_MAX	(6 * 256)
 /* P pictures after an I picture when the I/P size ratio is measured */
 #define CBR_RATIO_PICS		4
 /* QP errors up to this are corrected by 1/8 per picture, 1/256 units */
@@ -85,9 +99,9 @@ static u64 cbr_nominal(u32 bitrate, const struct v4l2_fract *timeperframe)
 				  timeperframe->denominator), 1);
 }
 
-/* Planned size of an I or P picture of the current GOP */
-static u64 cbr_plan(const struct v4l2_enc_cbr *cbr, unsigned int fps,
-		    bool intra)
+/* Planned size of an I or P picture of the current GOP at an I/P ratio */
+static u64 cbr_plan_ratio(const struct v4l2_enc_cbr *cbr, unsigned int fps,
+			  bool intra, unsigned int ratio)
 {
 	unsigned int gop = cbr->gop;
 	u64 p;
@@ -98,13 +112,43 @@ static u64 cbr_plan(const struct v4l2_enc_cbr *cbr, unsigned int fps,
 	if (gop <= 1)
 		return cbr->nominal;
 
-	p = div_u64(cbr->nominal * gop * 16, cbr->ratio + 16 * (gop - 1));
+	p = div_u64(cbr->nominal * gop * 16, ratio + 16 * (gop - 1));
 	if (intra)
-		p = div_u64(p * cbr->ratio, 16);
+		p = div_u64(p * ratio, 16);
 
 	return max_t(u64, p, 1);
 }
 
+static u64 cbr_plan(const struct v4l2_enc_cbr *cbr, unsigned int fps,
+		    bool intra)
+{
+	return cbr_plan_ratio(cbr, fps, intra, cbr->ratio);
+}
+
+/*
+ * QP of an I picture: the mean of the QP state now and at the previous I
+ * picture (the current state weighs 70% when it rose from 33 or below to 35
+ * or above: content that got much harder), plus the increase after
+ * oversized I pictures, less the I QP delta. All-intra streams use the
+ * state alone, which then follows the I pictures fully.
+ */
+static int cbr_qp_intra(const struct v4l2_enc_cbr *cbr)
+{
+	int cur = cbr->qp_q8, ref = cbr->qp_intra_ref_q8, q8;
+
+	if (cbr->gop == 1 || ref < 0)
+		q8 = cur;
+	else if (cur >= 35 * 256 && ref <= 33 * 256)
+		q8 = (3 * ref + 7 * cur) / 10;
+	else
+		q8 = (ref + cur) / 2;
+
+	if (cbr->gop != 1)
+		q8 += cbr->qp_intra_add_q8;
+
+	return DIV_ROUND_CLOSEST(q8, 256) - cbr->qp_delta_intra;
+}
+
 /*
  * Start QP of an I picture from its size in bits per luma sample:
  * 20.3 - 6 * log2(bits per sample), clipped to 15..45. This is the relation
@@ -165,6 +209,7 @@ int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
 	if (!cbr->bitrate) {
 		cbr->ratio = CBR_RATIO_INIT;
 		cbr->qp_q8 = -1;
+		cbr->qp_intra_ref_q8 = -1;
 	}
 
 	if (cbr->bitrate != bitrate || cbr->nominal != nominal) {
@@ -190,6 +235,9 @@ int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
 		cbr->qp_q8 = clamp_t(int, qp, qp_min, qp_max) * 256;
 	}
 
+	cbr->qp_pic = intra ? cbr_qp_intra(cbr) :
+			      DIV_ROUND_CLOSEST(cbr->qp_q8, 256);
+
 	/*
 	 * The buffer holds the coded minus the nominal sizes. Within a GOP the
 	 * plan expects it to go up by the excess of the I picture and back
@@ -214,6 +262,9 @@ int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
 		 cbr->nominal, cbr->plan_bits, cbr->target_bits);
 	pr_debug("  buffer: %lld bits, expected: %lld bits, gop: %u, ratio: %u",
 		 cbr->buffer_bits, expected, cbr->gop, cbr->ratio);
+	pr_debug("  intra: %d, picture QP %d, I state ref %d/256, I add %d/256",
+		 intra, cbr->qp_pic, cbr->qp_intra_ref_q8,
+		 cbr->qp_intra_add_q8);
 
 	return 0;
 }
@@ -227,12 +278,7 @@ EXPORT_SYMBOL_GPL(v4l2_enc_cbr_begin);
  */
 int v4l2_enc_cbr_qp(const struct v4l2_enc_cbr *cbr)
 {
-	int qp = DIV_ROUND_CLOSEST(cbr->qp_q8, 256);
-
-	if (cbr->intra)
-		qp -= cbr->qp_delta_intra;
-
-	return qp;
+	return cbr->qp_pic;
 }
 EXPORT_SYMBOL_GPL(v4l2_enc_cbr_qp);
 
@@ -248,9 +294,11 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 		      const struct v4l2_fract *timeperframe, int qp_min,
 		      int qp_max)
 {
-	s64 limit = cbr->nominal * CBR_BUFFER_SECONDS * cbr_fps(timeperframe);
+	unsigned int fps = cbr_fps(timeperframe);
+	s64 limit = cbr->nominal * CBR_BUFFER_SECONDS * fps;
 	unsigned int ratio;
 	int err, step;
+	s64 cap, add;
 
 	if (!cbr->bitrate || !cbr->target_bits)
 		return;
@@ -285,6 +333,30 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 		 * estimate: only all-intra streams steer the QP fully with it.
 		 */
 		cbr->i_bits = bits;
+
+		/*
+		 * Above its share of the GOP at the largest I/P ratio, an I
+		 * picture is more than the P pictures of the plan can make
+		 * up for: raise the QP of the next I pictures by up to 4 QP
+		 * per picture (at twice the share), and lower the increase
+		 * again as I pictures come out below the share.
+		 */
+		cap = cbr_plan_ratio(cbr, fps, true, CBR_RATIO_MAX);
+		if (cbr->gop != 1 && (bits > cap || cbr->qp_intra_add_q8)) {
+			add = div64_s64(((s64)bits - cap) * CBR_INTRA_ADD_STEP,
+					cap);
+			add = clamp_t(s64, add, -CBR_INTRA_ADD_STEP,
+				      CBR_INTRA_ADD_STEP) +
+			      cbr->qp_intra_add_q8;
+			cbr->qp_intra_add_q8 =
+				clamp_t(s64, add, 0,
+					clamp((CBR_INTRA_ADD_QP_END -
+					       cbr->qp_pic) * 64, 0,
+					      CBR_INTRA_ADD_MAX));
+		}
+
+		/* The state the next I picture takes the mean with */
+		cbr->qp_intra_ref_q8 = cbr->qp_q8;
 		cbr->qp_q8 += cbr->gop == 1 ? step : step / 4;
 	}
 
diff --git a/include/media/v4l2-enc-cbr.h b/include/media/v4l2-enc-cbr.h
index 6b66d121b0cc..c47e459a1d1e 100644
--- a/include/media/v4l2-enc-cbr.h
+++ b/include/media/v4l2-enc-cbr.h
@@ -18,6 +18,11 @@
  * @bitrate: bitrate the state was set up for, 0 before the first picture
  * @nominal: nominal picture size in bits (bitrate times frame interval)
  * @qp_q8: QP of the P pictures in 1/256 units, -1 before the first picture
+ * @qp_intra_ref_q8: @qp_q8 when the previous I picture was coded, -1 before
+ *		     the first I picture
+ * @qp_intra_add_q8: QP increase of the I pictures after I pictures larger
+ *		     than their share of the GOP at the largest I/P ratio
+ * @qp_pic: QP of the current picture
  * @ratio: I/P picture size ratio in 1/16 units
  * @gop: measured IDR period in pictures, 0 until two I pictures were seen
  * @since_i: pictures coded since the last I picture
@@ -39,6 +44,9 @@ struct v4l2_enc_cbr {
 	u32 bitrate;
 	u64 nominal;
 	int qp_q8;
+	int qp_intra_ref_q8;
+	int qp_intra_add_q8;
+	int qp_pic;
 	unsigned int ratio;
 	unsigned int gop;
 	unsigned int since_i;

-- 
2.55.0



  parent reply	other threads:[~2026-11-10 10:05 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-10 10:05 [PATCH RFC 00/11] media: h264: Stateless encode core follow-ups: parameter sets, POC type 2, CBR and VBR Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 01/11] media: h264: Only write the SPS and PPS of a stateless encode when they change Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 02/11] media: h264: Support picture order count type 2 in stateless encode Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 03/11] media: h264: Write the stateless encode SPS and PPS with nal_ref_idc 3 Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 04/11] media: v4l2-h264-enc-rc: Rework the constant bitrate mode Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 05/11] media: v4l2-core: Move the H.264 encode CBR rate control to a helper Arun Iyengar via B4 Relay
2026-11-10 10:05 ` Arun Iyengar via B4 Relay [this message]
2026-11-10 10:05 ` [PATCH RFC 07/11] media: v4l2-core: Halve the I picture QP increase of the encode CBR Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 08/11] media: v4l2-core: Re-encode oversized pictures in " Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 09/11] media: v4l2-core: Fix all-intra streams in the encode CBR rate control Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 10/11] media: v4l2-core: Add a peak bitrate to the encode " Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 11/11] media: h264: Add a VBR mode to the stateless " Arun Iyengar via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261110-h264-encode-fixes-v1-6-7a64486df499@gmail.com \
    --to=devnull+aiyengar1990.gmail.com@kernel.org \
    --cc=aiyengar1990@gmail.com \
    --cc=hverkuil@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-media@vger.kernel.org \
    --cc=m.felsch@pengutronix.de \
    --cc=m.tretter@pengutronix.de \
    --cc=mchehab@kernel.org \
    --cc=nicolas.dufresne@collabora.com \
    --cc=paulk@sys-base.io \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®