Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: "Tomas Härdin" <git@haerdin.se>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: Re: [FFmpeg-devel] [PATCH] libavcodec: add bit-rate support to RoQ video encoder
Date: Tue, 23 Jan 2024 18:44:03 +0100
Message-ID: <0e7aec235bb0481be4abb314a615210484914ad9.camel@haerdin.se> (raw)
In-Reply-To: <CAOSHGWPo8vgUB_Zs3-mY+ApPzCxxxD0s=zgQWCYrR-RYF+XmrQ@mail.gmail.com>

[-- Attachment #1: Type: text/plain, Size: 4626 bytes --]

tis 2024-01-23 klockan 11:33 +0300 skrev Victor Luchitz:
> Re-posting the patch as an attachment. Sorry for the inconvenience!

Yep, this one applies and also passes FATE.

I notice ffplay_buffer whines a lot when playing RoQ files:

> [swscaler @ 0x7fcb44043240] deprecated pixel format used, make sure you did set range correctly
> [ffplay_buffer @ 0x7fcb44032fc0] filter context - w: 256 h: 256 fmt: 14 csp: unknown range: unknown, incoming frame - w: 256 h: 256 fmt: 14 csp: unknown range: pc pts_time: 0
> [ffplay_buffer @ 0x7fcb44032fc0] Changing video frame properties on the fly is not supported by all filters.

I don't think this problem relates to this patch however.

Anyway, using -b:v 100k causes the encoder to effectively become stuck
on the first frame, being unable to go below 621 kbps and increasing
qscale very slowly. But you mentioned this already of course. Perhaps
there should be a faster "startup" phase? Subsequent frames being P-
frames may lead to the average hitting the target bitrate.

Even when using 1 Mbps the encoding is very slow:

./ffmpeg -r:v 30 -t 1 -f lavfi -i testsrc2 -s 256x256 -b:v 1000k -y
foo-1000k.roq

> [roqvideo @ 0x3112980] 
> Generated a frame too big for desired bit rate (1489 kbps), now switching to a bigger qscale value (257).
> [roqvideo @ 0x3112980] 
> Generated a frame too big for desired bit rate (1489 kbps), now switching to a bigger qscale value (259).
> [...]
> Generated a frame too big for desired bit rate (1100 kbps), now switching to a bigger qscale value (196863).
> [roqvideo @ 0x3112980] .0 size=       0kB time=N/A bitrate=N/A speed=N/A    
> Generated a frame too big for desired bit rate (1069 kbps), now switching to a bigger qscale value (262399).
> [roqvideo @ 0x3112980] 
> Generated a frame too small for desired bit rate (601 kbps), reverting lambda and using smaller inc on qscale (196863).
> [roqvideo @ 0x3112980] 0.0 size=      17kB time=00:00:00.16 bitrate= 812.7kbits/s speed=0.0476x    
> Generated a frame too small for desired bit rate (915 kbps), qscale value cannot be lowered any further (1).
> [...]
> [roqvideo @ 0x3112980] 
> Generated a frame too big for desired bit rate (1084 kbps), now switching to a bigger qscale value (327680).
> [roqvideo @ 0x3112980] 
> Generated a frame too big for desired bit rate (1068 kbps), now switching to a bigger qscale value (393216).
> [roqvideo @ 0x3112980] 0.0 size=      42kB time=00:00:00.40 bitrate= 866.5kbits/s speed=0.0667x    
> Generated a frame too small for desired bit rate (514 kbps), reverting lambda and using smaller inc on qscale (327680).
> [roqvideo @ 0x3112980] 0.0 size=      62kB time=00:00:00.56 bitrate= 896.2kbits/s speed=0.0708x    
> Generated a frame too small for desired bit rate (901 kbps), qscale value cannot be lowered any further (1).

This suggests an unstable regulation loop. It also makes the encoder
slower than the Cinepak encoder, amazingly. 59.6 vs 21.57 seconds for
encoding 90 frames of dimension 256x256.

-bt should have a default of 0, and similar default logic as -b. The
default of bitrate/20 also seems excessively small? If I change the
default to be the same as the bitrate then the encoding time above
shrinks to 11.7 seconds.

There is an excessive newline visible here:

> +                   "\nGenerated a frame too big for desired bit rate
> (%d kbps), "

And elsewhere.

Using non-integer framerates causes the encoder to go bananas. For
example 24000/1001 fps makes the ratecontrol think it's running at
24000 fps. An easy fix is to reject avctx->time_base.num != 1 on init.
roq_write_header() rejects non-integer framerates.

I'm also curious about this hunk:

> -        if (enc->lambda > 100000) {
> +        if (enc->lambda > 100000000) {
>              av_log(roq->logctx, AV_LOG_ERROR, "Cannot encode video in Quake compatible form\n");
>              return AVERROR(EINVAL);
>          }

Where in the Quake (3?) source code is this limitation? Seems rather
that there should be a retry limit. There's probably no harm in keeping
going so long as the packet doesn't end up above the limit.

A quick logarithmic regression on bitrate vs qscale suggests it scales
with somewhere between qscale^-0.2833 and qscale^-0.2842. Conversely,
to hit a specific bitrate, try scaling qscale with the bitrate ratio
raised to around 3.5. A more conservative exponent like 2 is probably
also fine. See patch attached.

A more systematic approach might be to do a binary search for qscale,
with an initial guess per the formula above.

/Tomas


[-- Attachment #2: 0001-Fast-ratecontrol-could-be-better.patch --]
[-- Type: text/x-patch, Size: 6460 bytes --]

From 5995119843f57ba68dc8d66cb6c53cd8ec386ab9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomas=20H=C3=A4rdin?= <git@haerdin.se>
Date: Tue, 23 Jan 2024 18:40:55 +0100
Subject: [PATCH] Fast ratecontrol, could be better

---
 libavcodec/roqvideoenc.c | 48 +++++++++++++++++++++++++++-------------
 1 file changed, 33 insertions(+), 15 deletions(-)

diff --git a/libavcodec/roqvideoenc.c b/libavcodec/roqvideoenc.c
index 976015d918..0fd70e85c4 100644
--- a/libavcodec/roqvideoenc.c
+++ b/libavcodec/roqvideoenc.c
@@ -894,7 +894,7 @@ static int roq_encode_video(AVCodecContext *avctx)
     RoqEncContext *const enc = avctx->priv_data;
     RoqTempData *const tempData = &enc->tmp_data;
     RoqContext *const roq = &enc->common;
-    int ret;
+    int ret, num_fast_qscale_deltas = 0, num_tries = 0;
 
     memset(tempData, 0, sizeof(*tempData));
 
@@ -908,12 +908,16 @@ static int roq_encode_video(AVCodecContext *avctx)
     }
 
  retry_encode:
+    num_tries++;
     for (int i = 0; i < roq->width * roq->height / 64; i++)
         gather_data_for_cel(enc->cel_evals + i, enc);
 
     /* Quake 3 can't handle chunks bigger than 65535 bytes */
+#define MAX_LAMBDA_DELTA 3000000
+#define MAX_TRIES 30
+#define MAX_FAST_TRIES 5
     if (tempData->mainChunkSize/8 > 65535 && enc->quake3_compat) {
-        if (enc->lambda > 100000000) {
+        if (num_tries > MAX_TRIES) {
             av_log(roq->logctx, AV_LOG_ERROR, "Cannot encode video in Quake compatible form\n");
             return AVERROR(EINVAL);
         }
@@ -941,28 +945,40 @@ static int roq_encode_video(AVCodecContext *avctx)
         int64_t tol = avctx->bit_rate_tolerance;
 
         /* tolerance > bit rate, set to 5% of the bit rate */
-        if (tol > avctx->bit_rate)
+        if (!tol)
             tol = avctx->bit_rate / 20;
 
         av_log(roq->logctx, AV_LOG_VERBOSE,
-               "\nDesired bit rate (%d kbps), "
+               "Desired bit rate (%d kbps), "
+               "Current bit rate (%d kbps), "
                "Bit rate tolerance (%d), "
                "Frame rate (%d)\n",
-               (int)avctx->bit_rate, (int)tol, avctx->time_base.den);
+               (int)avctx->bit_rate / 1000, ftotal / 1000, (int)tol, avctx->time_base.den);
 
         if (ftotal > (avctx->bit_rate + tol)) {
             /* frame is too big - increase qscale */
-            if (enc->lambda > 100000000) {
-                av_log(roq->logctx, AV_LOG_ERROR, "\nCannot encode video at desired bitrate\n");
-                return AVERROR(EINVAL);
+            if (num_tries > MAX_TRIES) {
+                av_log(roq->logctx, AV_LOG_WARNING, "Cannot encode video at desired bitrate (got %d kbps)\n", ftotal / 1000);
+                // don't fail hard
+                goto keepgoing;
+            }
+            enc->lambda_delta = enc->lambda_delta <= 0 ? 1 : enc->lambda_delta < MAX_LAMBDA_DELTA/2 ? enc->lambda_delta*2 : MAX_LAMBDA_DELTA;
+            if (num_fast_qscale_deltas < MAX_FAST_TRIES) {
+                float ratio;
+                int fast_lambda_delta;
+                ratio = powf((float)ftotal / avctx->bit_rate, 2) - 1; // -1 because it's a delta
+                av_log(roq->logctx, AV_LOG_VERBOSE, "ratio = %f\n", ratio);
+                fast_lambda_delta = FFMAX(1, FFMIN(enc->lambda * ratio, MAX_LAMBDA_DELTA));
+                // sometimes ratio ends up quite small, in which case we fall back to the simple doubling logic
+                enc->lambda_delta = FFMAX(enc->lambda_delta, fast_lambda_delta);
+                num_fast_qscale_deltas++;
             }
-            enc->lambda_delta = enc->lambda_delta <= 0 ? 1 : enc->lambda_delta < 65536 ? enc->lambda_delta*2 : 65536;
             enc->last_lambda = enc->lambda;
             enc->lambda += enc->lambda_delta;
             av_log(roq->logctx, AV_LOG_INFO,
-                   "\nGenerated a frame too big for desired bit rate (%d kbps), "
-                   "now switching to a bigger qscale value (%d).\n",
-                   ftotal / 1000, (int)enc->lambda);
+                   "Generated a frame too big for desired bit rate (%d kbps), "
+                   "now switching to a bigger qscale value (%d -> %d).\n",
+                   ftotal / 1000, (int)enc->last_lambda, (int)enc->lambda);
             tempData->mainChunkSize = 0;
             memset(tempData->used_option, 0, sizeof(tempData->used_option));
             memset(tempData->codebooks.usedCB4, 0,
@@ -975,12 +991,12 @@ static int roq_encode_video(AVCodecContext *avctx)
             /* frame is too small - decrease qscale */
             if (enc->lambda <= 1) {
                 av_log(roq->logctx, AV_LOG_WARNING,
-                       "\nGenerated a frame too small for desired bit rate (%d kbps), "
+                       "Generated a frame too small for desired bit rate (%d kbps), "
                        "qscale value cannot be lowered any further (%d).\n",
                        ftotal / 1000, (int)enc->lambda);
             } else if ((enc->lambda - enc->last_lambda) == 1) {
                 av_log(roq->logctx, AV_LOG_WARNING,
-                       "\nCannot find qscale that gives desired bit rate within desired tolerance, "
+                       "Cannot find qscale that gives desired bit rate within desired tolerance, "
                        "using lower bitrate (%d kbps) with higher qscale value (%d).\n",
                        ftotal / 1000, (int)enc->lambda);
             } else {
@@ -992,7 +1008,7 @@ static int roq_encode_video(AVCodecContext *avctx)
                     enc->lambda = enc->last_lambda;
                     //enc->lambda *= (float)(tempData->mainChunkSize * avctx->time_base.den) / avctx->bit_rate;
                     av_log(roq->logctx, AV_LOG_INFO,
-                           "\nGenerated a frame too small for desired bit rate (%d kbps), "
+                           "Generated a frame too small for desired bit rate (%d kbps), "
                            "reverting lambda and using smaller inc on qscale (%d).\n",
                            ftotal / 1000, (int)enc->lambda);
                 }
@@ -1007,6 +1023,7 @@ static int roq_encode_video(AVCodecContext *avctx)
             }
         }
     }
+keepgoing:
 
     write_codebooks(enc);
 
@@ -1200,6 +1217,7 @@ static const AVClass roq_class = {
 
 static const FFCodecDefault roq_defaults[] = {
     { "b",                "0" },
+    { "bt",               "0" },
     { NULL },
 };
 
-- 
2.39.2


[-- Attachment #3: Type: text/plain, Size: 251 bytes --]

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  reply	other threads:[~2024-01-23 17:44 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-01-23  8:33 Victor Luchitz
2024-01-23 17:44 ` Tomas Härdin [this message]
2024-01-24  8:50   ` Victor Luchitz
2024-01-24 21:29     ` Tomas Härdin
2024-01-24 22:09       ` Victor Luchitz
2024-01-24 22:27         ` Tomas Härdin
2024-01-27  8:56           ` Victor Luchitz
2024-01-24 11:06 ` Anton Khirnov
2024-01-24 21:22   ` Victor Luchitz

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=0e7aec235bb0481be4abb314a615210484914ad9.camel@haerdin.se \
    --to=git@haerdin.se \
    --cc=ffmpeg-devel@ffmpeg.org \
    /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

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git