[FFmpeg-devel] [PATCH 2/2] libavcodec/ffv1: Support storing LSB raw

Michael Niedermayer michael at niedermayer.cc
Wed Oct 16 02:17:35 EEST 2024


This makes a 16bit RGB raw sample 25% faster at a 2% loss of compression with rawlsb=4

Please test and comment

This stores the LSB through non binary range coding, this is simpler than using a
separate coder
For cases where range coding is not wanted its probably best to use golomb rice
for everything.

We also pass the LSB through the decorrelation and context stages (which is basically free)
this leads to slightly better compression than separating them earlier.

Signed-off-by: Michael Niedermayer <michael at niedermayer.cc>
---
 libavcodec/ffv1.h             |  2 ++
 libavcodec/ffv1_template.c    | 19 ++++++++++---------
 libavcodec/ffv1dec.c          |  2 ++
 libavcodec/ffv1dec_template.c | 16 +++++++++++++---
 libavcodec/ffv1enc.c          | 15 ++++++++++++++-
 libavcodec/ffv1enc_template.c | 17 +++++++++++++++--
 libavcodec/rangecoder.h       | 20 ++++++++++++++++++++
 libavcodec/tests/rangecoder.c |  9 +++++++++
 8 files changed, 85 insertions(+), 15 deletions(-)

diff --git a/libavcodec/ffv1.h b/libavcodec/ffv1.h
index 4f5a8ab2be7..02bfc33f680 100644
--- a/libavcodec/ffv1.h
+++ b/libavcodec/ffv1.h
@@ -83,6 +83,7 @@ typedef struct FFV1SliceContext {
     int slice_coding_mode;
     int slice_rct_by_coef;
     int slice_rct_ry_coef;
+    int rawlsb;
 
     // RefStruct reference, array of MAX_PLANES elements
     PlaneContext *plane;
@@ -139,6 +140,7 @@ typedef struct FFV1Context {
     int key_frame_ok;
     int context_model;
     int qtable;
+    int rawlsb;
 
     int bits_per_raw_sample;
     int packed_at_lsb;
diff --git a/libavcodec/ffv1_template.c b/libavcodec/ffv1_template.c
index abb90a12e49..10206702ee8 100644
--- a/libavcodec/ffv1_template.c
+++ b/libavcodec/ffv1_template.c
@@ -30,24 +30,25 @@ static inline int RENAME(predict)(TYPE *src, TYPE *last)
 }
 
 static inline int RENAME(get_context)(const int16_t quant_table[MAX_CONTEXT_INPUTS][MAX_QUANT_TABLE_SIZE],
-                                      TYPE *src, TYPE *last, TYPE *last2)
+                                      TYPE *src, TYPE *last, TYPE *last2, int rawlsb)
 {
     const int LT = last[-1];
     const int T  = last[0];
     const int RT = last[1];
     const int L  = src[-1];
+    const int rawoff = (1<<rawlsb) >> 1;
 
     if (quant_table[3][127] || quant_table[4][127]) {
         const int TT = last2[0];
         const int LL = src[-2];
-        return quant_table[0][(L - LT) & MAX_QUANT_TABLE_MASK] +
-               quant_table[1][(LT - T) & MAX_QUANT_TABLE_MASK] +
-               quant_table[2][(T - RT) & MAX_QUANT_TABLE_MASK] +
-               quant_table[3][(LL - L) & MAX_QUANT_TABLE_MASK] +
-               quant_table[4][(TT - T) & MAX_QUANT_TABLE_MASK];
+        return quant_table[0][(L - LT + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[1][(LT - T + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[2][(T - RT + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[3][(LL - L + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[4][(TT - T + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK];
     } else
-        return quant_table[0][(L - LT) & MAX_QUANT_TABLE_MASK] +
-               quant_table[1][(LT - T) & MAX_QUANT_TABLE_MASK] +
-               quant_table[2][(T - RT) & MAX_QUANT_TABLE_MASK];
+        return quant_table[0][(L - LT + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[1][(LT - T + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK] +
+               quant_table[2][(T - RT + rawoff >> rawlsb) & MAX_QUANT_TABLE_MASK];
 }
 
diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index 5c099e49ad4..fc96bfb4cea 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -249,6 +249,8 @@ static int decode_slice_header(const FFV1Context *f,
                 return AVERROR_INVALIDDATA;
             }
         }
+        if (f->micro_version > 2)
+            sc->rawlsb = get_symbol(c, state, 0);
     }
 
     return 0;
diff --git a/libavcodec/ffv1dec_template.c b/libavcodec/ffv1dec_template.c
index 2da6bd935dc..dbdcad7768e 100644
--- a/libavcodec/ffv1dec_template.c
+++ b/libavcodec/ffv1dec_template.c
@@ -60,8 +60,13 @@ RENAME(decode_line)(FFV1Context *f, FFV1SliceContext *sc,
                 return AVERROR_INVALIDDATA;
         }
 
-        context = RENAME(get_context)(quant_table,
-                                      sample[1] + x, sample[0] + x, sample[1] + x);
+        if (sc->rawlsb) {
+            context = RENAME(get_context)(quant_table,
+                                          sample[1] + x, sample[0] + x, sample[1] + x, sc->rawlsb);
+        } else {
+            context = RENAME(get_context)(quant_table,
+                                          sample[1] + x, sample[0] + x, sample[1] + x, 0);
+        }
         if (context < 0) {
             context = -context;
             sign    = 1;
@@ -71,7 +76,12 @@ RENAME(decode_line)(FFV1Context *f, FFV1SliceContext *sc,
         av_assert2(context < p->context_count);
 
         if (ac != AC_GOLOMB_RICE) {
-            diff = get_symbol_inline(c, p->state[context], 1);
+            if (sc->rawlsb) {
+                const int rawoff = (1<<sc->rawlsb) >> 1;
+                diff = get_rac_raw(c, sc->rawlsb);
+                diff += (get_symbol_inline(c, p->state[context], 1) << sc->rawlsb) - rawoff;
+            } else
+                diff = get_symbol_inline(c, p->state[context], 1);
         } else {
             if (context == 0 && run_mode == 0)
                 run_mode = 1;
diff --git a/libavcodec/ffv1enc.c b/libavcodec/ffv1enc.c
index 0dbfebc1a1a..c574c739380 100644
--- a/libavcodec/ffv1enc.c
+++ b/libavcodec/ffv1enc.c
@@ -416,7 +416,7 @@ static int write_extradata(FFV1Context *f)
         if (f->version == 3) {
             f->micro_version = 4;
         } else if (f->version == 4)
-            f->micro_version = 2;
+            f->micro_version = 3;
         put_symbol(&c, state, f->micro_version, 0);
     }
 
@@ -564,6 +564,9 @@ static av_cold int encode_init(AVCodecContext *avctx)
     if (s->ec == 2)
         s->version = FFMAX(s->version, 4);
 
+    if (s->rawlsb)
+        s->version = FFMAX(s->version, 4);
+
     if ((s->version == 2 || s->version>3) && avctx->strict_std_compliance > FF_COMPLIANCE_EXPERIMENTAL) {
         av_log(avctx, AV_LOG_ERROR, "Version 2 or 4 needed for requested features but version 2 or 4 is experimental and not enabled\n");
         return AVERROR_INVALIDDATA;
@@ -716,6 +719,11 @@ static av_cold int encode_init(AVCodecContext *avctx)
         }
     }
 
+    if (s->rawlsb > s->bits_per_raw_sample) {
+        av_log(avctx, AV_LOG_ERROR, "too many raw lsb\n");
+        return AVERROR(EINVAL);
+    }
+
     if (s->ac == AC_RANGE_CUSTOM_TAB) {
         for (i = 1; i < 256; i++)
             s->state_transition[i] = ver2_state[i];
@@ -958,6 +966,7 @@ static void encode_slice_header(FFV1Context *f, FFV1SliceContext *sc)
             put_symbol(c, state, sc->slice_rct_by_coef, 0);
             put_symbol(c, state, sc->slice_rct_ry_coef, 0);
         }
+        put_symbol(c, state, sc->rawlsb, 0);
     }
 }
 
@@ -1077,6 +1086,8 @@ static int encode_slice(AVCodecContext *c, void *arg)
         sc->slice_rct_ry_coef = 1;
     }
 
+    sc->rawlsb = f->rawlsb; // we do not optimize this per slice, but other encoders could
+
 retry:
     if (f->key_frame)
         ff_ffv1_clear_slice_state(f, sc);
@@ -1291,6 +1302,8 @@ static const AVOption options[] = {
             { .i64 = 0 }, 0, 1, VE },
     { "qtable", "Quantization table", OFFSET(qtable), AV_OPT_TYPE_INT,
             { .i64 = -1 }, -1, 2, VE },
+    { "rawlsb", "number of LSBs stored RAW", OFFSET(rawlsb), AV_OPT_TYPE_INT,
+            { .i64 = 0 }, 0, 16, VE },
 
     { NULL }
 };
diff --git a/libavcodec/ffv1enc_template.c b/libavcodec/ffv1enc_template.c
index bc14926ab95..848328c70af 100644
--- a/libavcodec/ffv1enc_template.c
+++ b/libavcodec/ffv1enc_template.c
@@ -62,8 +62,14 @@ RENAME(encode_line)(FFV1Context *f, FFV1SliceContext *sc,
     for (x = 0; x < w; x++) {
         int diff, context;
 
-        context = RENAME(get_context)(f->quant_tables[p->quant_table_index],
-                                      sample[0] + x, sample[1] + x, sample[2] + x);
+        if (f->rawlsb) {
+            context = RENAME(get_context)(f->quant_tables[p->quant_table_index],
+                                        sample[0] + x, sample[1] + x, sample[2] + x, f->rawlsb);
+        } else {
+            //try to force a version with rawlsb optimized out
+            context = RENAME(get_context)(f->quant_tables[p->quant_table_index],
+                                        sample[0] + x, sample[1] + x, sample[2] + x, 0);
+        }
         diff    = sample[0][x] - RENAME(predict)(sample[0] + x, sample[1] + x);
 
         if (context < 0) {
@@ -74,6 +80,13 @@ RENAME(encode_line)(FFV1Context *f, FFV1SliceContext *sc,
         diff = fold(diff, bits);
 
         if (ac != AC_GOLOMB_RICE) {
+            if (f->rawlsb) {
+                const int rawoff = (1<<f->rawlsb) >> 1;
+                const unsigned mask = (1<<f->rawlsb) - 1;
+                diff += rawoff;
+                put_rac_raw(c, (diff & mask), f->rawlsb);
+                diff = diff >> f->rawlsb; // Note, this will be biased on small rawlsb
+            }
             if (pass1) {
                 put_symbol_inline(c, p->state[context], diff, 1, sc->rc_stat,
                                   sc->rc_stat2[p->quant_table_index][context]);
diff --git a/libavcodec/rangecoder.h b/libavcodec/rangecoder.h
index 89d178ac314..d02a65fa7da 100644
--- a/libavcodec/rangecoder.h
+++ b/libavcodec/rangecoder.h
@@ -111,6 +111,16 @@ static inline void put_rac(RangeCoder *c, uint8_t *const state, int bit)
     renorm_encoder(c);
 }
 
+static inline void put_rac_raw(RangeCoder *c, int bits, int len)
+{
+    int r = c->range >> len;
+
+    c->low += r * bits;
+    c->range = r;
+
+    renorm_encoder(c);
+}
+
 static inline void refill(RangeCoder *c)
 {
     if (c->range < 0x100) {
@@ -142,4 +152,14 @@ static inline int get_rac(RangeCoder *c, uint8_t *const state)
     }
 }
 
+static inline int get_rac_raw(RangeCoder *c, int len)
+{
+    int r = c->range >> len;
+    int bits = c->low / r;
+    c->low -= r * bits;
+    c->range = r;
+    refill(c);
+    return bits;
+}
+
 #endif /* AVCODEC_RANGECODER_H */
diff --git a/libavcodec/tests/rangecoder.c b/libavcodec/tests/rangecoder.c
index fd858535a5b..9205be2bf3f 100644
--- a/libavcodec/tests/rangecoder.c
+++ b/libavcodec/tests/rangecoder.c
@@ -76,6 +76,10 @@ int main(void)
             for (i = 0; i < SIZE; i++)
                 put_rac(&c, state, r[i] & 1);
 
+            for (i = 0; i < 30; i++) {
+                put_rac_raw(&c, r[i]&7, 3);
+            }
+
             actual_length = ff_rac_terminate(&c, version);
 
             ff_init_range_decoder(&c, b, version ? SIZE : actual_length);
@@ -87,6 +91,11 @@ int main(void)
                     av_log(NULL, AV_LOG_ERROR, "rac failure at %d pass %d version %d\n", i, p, version);
                     return 1;
                 }
+            for (i = 0; i < 30; i++)
+                if ((r[i] & 7) != get_rac_raw(&c, 3)) {
+                    av_log(NULL, AV_LOG_ERROR, "rac raw failure at %d pass %d version %d\n", i, p, version);
+                    return 1;
+                }
 
             if (rac_check_termination(&c, version) < 0) {
                 av_log(NULL, AV_LOG_ERROR, "rac failure at termination pass %d version %d\n", p, version);
-- 
2.47.0



More information about the ffmpeg-devel mailing list