All of lore.kernel.org
 help / color / mirror / Atom feed
From: Arun Iyengar <aiyengar1990@gmail.com>
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 09/11] media: v4l2-core: Fix all-intra streams in the encode CBR rate control
Date: Tue, 10 Nov 2026 10:05:23 +0000	[thread overview]
Message-ID: <20261110-h264-encode-fixes-v1-9-7a64486df499@gmail.com> (raw)
In-Reply-To: <20261110-h264-encode-fixes-v1-0-7a64486df499@gmail.com>

All-intra streams (an IDR period of 1) have two problems in the constant
bitrate rate control:

- the QP state, which is the QP of the P pictures, is limited to the QP
  range, and the I pictures are coded the I QP delta below it, so they
  never reach the highest QP: at targets that need it the bitrate stays
  far above the target (2.3 times at 1080p30 and 1 Mbit/s with QP 49
  instead of 51);
- the start QP is estimated for an I picture of a GOP of two seconds,
  which gets about nine times the nominal size, so the first pictures of
  an all-intra stream come out several times their size until the state
  has climbed: the largest one second window of a 1080p30 stream at
  4 Mbit/s is 1.55 times the bitrate.

Shift the range of the state by the I QP delta in all-intra streams, and
when the second picture is an I picture again, set the state from the
size of the first one at its QP. In a host simulation against a model
encoder (sizes halving every 6 QP, 720p and 1080p, 256 kbit/s to
20 Mbit/s), the all-intra bitrate is then within 0.1% of the target
wherever QP 51 can reach it, and the largest one second window drops to
1.02-1.11 times the bitrate (was up to 1.88). Streams with P pictures are
not changed.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-enc-cbr.c | 27 ++++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-enc-cbr.c b/drivers/media/v4l2-core/v4l2-enc-cbr.c
index 28826e32c418..87db60469182 100644
--- a/drivers/media/v4l2-core/v4l2-enc-cbr.c
+++ b/drivers/media/v4l2-core/v4l2-enc-cbr.c
@@ -239,8 +239,22 @@ int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
 
 	if (intra) {
 		/* An I picture closes the GOP: its length is the IDR period. */
-		if (cbr->seen_i)
+		if (cbr->seen_i) {
+			/*
+			 * The second picture of an all-intra stream: the size
+			 * of the first one at its QP tells the QP of the
+			 * nominal size better than the start estimate, which
+			 * assumed a GOP with P pictures.
+			 */
+			if (!cbr->gop && !cbr->since_i)
+				cbr->qp_q8 = clamp_t(int, (cbr->qp_pic +
+						 cbr->qp_delta_intra) * 256 +
+						 6 * (cbr_log2_q8(cbr->i_bits) -
+						      cbr_log2_q8(nominal)),
+						 (qp_min + cbr->qp_delta_intra) * 256,
+						 (qp_max + cbr->qp_delta_intra) * 256);
 			cbr->gop = cbr->since_i + 1;
+		}
 		cbr->since_i = 0;
 		cbr->seen_i = true;
 	}
@@ -381,7 +395,7 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 	unsigned int fps = cbr_fps(timeperframe);
 	s64 limit = cbr->nominal * CBR_BUFFER_SECONDS * fps;
 	unsigned int ratio;
-	int err, step;
+	int err, step, shift;
 	s64 cap, add;
 
 	if (!cbr->bitrate || !cbr->target_bits)
@@ -452,7 +466,14 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 		cbr->qp_q8 += cbr->gop == 1 ? step : step / 4;
 	}
 
-	cbr->qp_q8 = clamp_t(int, cbr->qp_q8, qp_min * 256, qp_max * 256);
+	/*
+	 * The state is the QP of the P pictures. All-intra streams code their
+	 * pictures at the state less the I QP delta, so they need the QP range
+	 * shifted up by it to reach the highest QP.
+	 */
+	shift = cbr->gop == 1 ? cbr->qp_delta_intra : 0;
+	cbr->qp_q8 = clamp_t(int, cbr->qp_q8, (qp_min + shift) * 256,
+			     (qp_max + shift) * 256);
 
 	pr_debug("+ v4l2-enc-cbr: end");
 	pr_debug("  size: %llu bits, target: %llu bits, qp error: %d/256, qp: %d/256",

-- 
2.55.0


WARNING: multiple messages have this Message-ID (diff)
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 09/11] media: v4l2-core: Fix all-intra streams in the encode CBR rate control
Date: Tue, 10 Nov 2026 10:05:23 +0000	[thread overview]
Message-ID: <20261110-h264-encode-fixes-v1-9-7a64486df499@gmail.com> (raw)
In-Reply-To: <20261110-h264-encode-fixes-v1-0-7a64486df499@gmail.com>

From: Arun Iyengar <aiyengar1990@gmail.com>

All-intra streams (an IDR period of 1) have two problems in the constant
bitrate rate control:

- the QP state, which is the QP of the P pictures, is limited to the QP
  range, and the I pictures are coded the I QP delta below it, so they
  never reach the highest QP: at targets that need it the bitrate stays
  far above the target (2.3 times at 1080p30 and 1 Mbit/s with QP 49
  instead of 51);
- the start QP is estimated for an I picture of a GOP of two seconds,
  which gets about nine times the nominal size, so the first pictures of
  an all-intra stream come out several times their size until the state
  has climbed: the largest one second window of a 1080p30 stream at
  4 Mbit/s is 1.55 times the bitrate.

Shift the range of the state by the I QP delta in all-intra streams, and
when the second picture is an I picture again, set the state from the
size of the first one at its QP. In a host simulation against a model
encoder (sizes halving every 6 QP, 720p and 1080p, 256 kbit/s to
20 Mbit/s), the all-intra bitrate is then within 0.1% of the target
wherever QP 51 can reach it, and the largest one second window drops to
1.02-1.11 times the bitrate (was up to 1.88). Streams with P pictures are
not changed.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-enc-cbr.c | 27 ++++++++++++++++++++++++---
 1 file changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-enc-cbr.c b/drivers/media/v4l2-core/v4l2-enc-cbr.c
index 28826e32c418..87db60469182 100644
--- a/drivers/media/v4l2-core/v4l2-enc-cbr.c
+++ b/drivers/media/v4l2-core/v4l2-enc-cbr.c
@@ -239,8 +239,22 @@ int v4l2_enc_cbr_begin(struct v4l2_enc_cbr *cbr, u32 bitrate,
 
 	if (intra) {
 		/* An I picture closes the GOP: its length is the IDR period. */
-		if (cbr->seen_i)
+		if (cbr->seen_i) {
+			/*
+			 * The second picture of an all-intra stream: the size
+			 * of the first one at its QP tells the QP of the
+			 * nominal size better than the start estimate, which
+			 * assumed a GOP with P pictures.
+			 */
+			if (!cbr->gop && !cbr->since_i)
+				cbr->qp_q8 = clamp_t(int, (cbr->qp_pic +
+						 cbr->qp_delta_intra) * 256 +
+						 6 * (cbr_log2_q8(cbr->i_bits) -
+						      cbr_log2_q8(nominal)),
+						 (qp_min + cbr->qp_delta_intra) * 256,
+						 (qp_max + cbr->qp_delta_intra) * 256);
 			cbr->gop = cbr->since_i + 1;
+		}
 		cbr->since_i = 0;
 		cbr->seen_i = true;
 	}
@@ -381,7 +395,7 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 	unsigned int fps = cbr_fps(timeperframe);
 	s64 limit = cbr->nominal * CBR_BUFFER_SECONDS * fps;
 	unsigned int ratio;
-	int err, step;
+	int err, step, shift;
 	s64 cap, add;
 
 	if (!cbr->bitrate || !cbr->target_bits)
@@ -452,7 +466,14 @@ void v4l2_enc_cbr_end(struct v4l2_enc_cbr *cbr, u64 bits,
 		cbr->qp_q8 += cbr->gop == 1 ? step : step / 4;
 	}
 
-	cbr->qp_q8 = clamp_t(int, cbr->qp_q8, qp_min * 256, qp_max * 256);
+	/*
+	 * The state is the QP of the P pictures. All-intra streams code their
+	 * pictures at the state less the I QP delta, so they need the QP range
+	 * shifted up by it to reach the highest QP.
+	 */
+	shift = cbr->gop == 1 ? cbr->qp_delta_intra : 0;
+	cbr->qp_q8 = clamp_t(int, cbr->qp_q8, (qp_min + shift) * 256,
+			     (qp_max + shift) * 256);
 
 	pr_debug("+ v4l2-enc-cbr: end");
 	pr_debug("  size: %llu bits, target: %llu bits, qp error: %d/256, qp: %d/256",

-- 
2.55.0



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

Thread overview: 26+ 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
2026-11-10 10:05 ` 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
2026-11-10 10:05   ` 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
2026-11-10 10:05   ` 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
2026-11-10 10:05   ` 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
2026-11-10 10:05   ` 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
2026-11-10 10:05   ` Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 06/11] media: v4l2-core: Bound the I picture size in the encode CBR rate control Arun Iyengar
2026-11-10 10:05   ` Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 07/11] media: v4l2-core: Halve the I picture QP increase of the encode CBR Arun Iyengar
2026-11-10 10:05   ` Arun Iyengar via B4 Relay
2026-11-10 10:05 ` [PATCH RFC 08/11] media: v4l2-core: Re-encode oversized pictures in " Arun Iyengar
2026-11-10 10:05   ` Arun Iyengar via B4 Relay
2026-11-10 10:05 ` Arun Iyengar [this message]
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
2026-11-10 10:05   ` 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
2026-11-10 10:05   ` Arun Iyengar via B4 Relay
2026-11-11  1:20 ` [PATCH RFC 00/11] media: h264: Stateless encode core follow-ups: parameter sets, POC type 2, CBR and VBR Nicolas Dufresne
2026-11-12 16:20   ` Paul Kocialkowski

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-9-7a64486df499@gmail.com \
    --to=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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.