Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Michael Niedermayer <michael@niedermayer.cc>
To: FFmpeg development discussions and patches <ffmpeg-devel@ffmpeg.org>
Subject: [FFmpeg-devel] [PATCH] avcodec/ffv1: Implement 2D RLE for remap
Date: Sat, 22 Mar 2025 19:01:39 +0100
Message-ID: <20250322180139.3462267-1-michael@niedermayer.cc> (raw)

ATM this performs as well or better as any other algorithm tried.
Its simple for the decoder.
On the encoder side complexity depends on how parameters are
choosen. But with a fixed mul_count of 1 and basic heuristic
it performs as well as any more complex choice i tried so far.

The encoder code here is flexible and allows mul_count > 1
and also can easily be used to exactly test various parameters.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 libavcodec/ffv1dec.c |  77 +++++++++++-----
 libavcodec/ffv1enc.c | 205 ++++++++++++++++++++++++++++++++++---------
 2 files changed, 218 insertions(+), 64 deletions(-)

diff --git a/libavcodec/ffv1dec.c b/libavcodec/ffv1dec.c
index f254c875a3f..19eb546d3f1 100644
--- a/libavcodec/ffv1dec.c
+++ b/libavcodec/ffv1dec.c
@@ -274,6 +274,17 @@ static void slice_set_damaged(FFV1Context *f, FFV1SliceContext *sc)
         f->frame_damaged = 1;
 }
 
+static int decode_current_mul(RangeCoder *rc, uint8_t state[32], int *mul, int mul_count, int64_t i)
+{
+    int ndx = (i * mul_count) >> 32;
+    av_assert2(ndx <= 4096U);
+
+    if (mul[ndx] < 0)
+        mul[ndx] = ff_ffv1_get_symbol(rc, state, 0) & 0x3FFFFFFF;
+
+    return mul[ndx];
+}
+
 static int decode_remap(FFV1Context *f, FFV1SliceContext *sc)
 {
     unsigned int end = f->avctx->bits_per_raw_sample == 32 ? 0xFFFFFFFF : 0xFFFF;
@@ -282,33 +293,53 @@ static int decode_remap(FFV1Context *f, FFV1SliceContext *sc)
     for (int p= 0; p < 1 + 2*f->chroma_planes + f->transparency; p++) {
         int j = 0;
         int lu = 0;
-        uint8_t state[2][32];
+        uint8_t state[2][3][32];
         int64_t i;
+        int mul[4096+1];
+        int mul_count;
+
         memset(state, 128, sizeof(state));
-        for (i=0; i <= end ; i++) {
-            unsigned run = get_symbol_inline(&sc->c, state[lu], 0);
-            if (run > end - i + 1)
-                return AVERROR_INVALIDDATA;
-            if (lu) {
-                lu ^= !run;
-                while (run--) {
-                    if (end == 0xFFFF) {
-                        sc->fltmap  [p][j++] = i ^ ((i&    0x8000) ? 0 : flip);
-                    } else
-                        sc->fltmap32[p][j++] = i ^ ((i&0x80000000) ? 0 : flip);
-                    i++;
-                }
-            } else {
-                i += run;
-                if (i <= end) {
-                    if (end == 0xFFFF) {
-                        sc->fltmap  [p][j++] = i ^ ((i&    0x8000) ? 0 : flip);
-                    } else {
-                        sc->fltmap32[p][j++] = i ^ ((i&0x80000000) ? 0 : flip);
-                    }
+        mul_count = ff_ffv1_get_symbol(&sc->c, state[0][0], 0);
+
+        if (mul_count > 4096U)
+            return AVERROR_INVALIDDATA;
+        for (int i = 0; i<mul_count; i++) {
+            mul[i] = -1;
+
+        }
+        mul[mul_count] = 1;
+
+        memset(state, 128, sizeof(state));
+        int current_mul = 1;
+        for (i=0; i <= end ;) {
+            unsigned run = get_symbol_inline(&sc->c, state[lu][0], 0);
+            unsigned run0 = lu ? 0   : run;
+            unsigned run1 = lu ? run : 1;
+
+            i += run0 * current_mul;
+
+            while (run1--) {
+                if (current_mul > 1) {
+                    int delta = get_symbol_inline(&sc->c, state[lu][1], 1);
+                    if (delta <= -current_mul || delta > current_mul/2)
+                        return AVERROR_INVALIDDATA; //not sure we should check this
+                    i += current_mul - 1 + delta;
                 }
-                lu ^= !run;
+                if (i == end)
+                    break;
+                if (i - 1 > end || j > 65535)
+                    return AVERROR_INVALIDDATA;
+                if (end == 0xFFFF) {
+                    sc->fltmap  [p][j++] = i ^ ((i&    0x8000) ? 0 : flip);
+                } else
+                    sc->fltmap32[p][j++] = i ^ ((i&0x80000000) ? 0 : flip);
+                i++;
+                current_mul = decode_current_mul(&sc->c, state[0][2], mul, mul_count, i);
+            }
+            if (lu) {
+                i += current_mul;
             }
+            lu ^= !run;
         }
     }
     return 0;
diff --git a/libavcodec/ffv1enc.c b/libavcodec/ffv1enc.c
index e557e7fcdfe..5b251ac2e80 100644
--- a/libavcodec/ffv1enc.c
+++ b/libavcodec/ffv1enc.c
@@ -433,7 +433,7 @@ static void set_micro_version(FFV1Context *f)
         if (f->version == 3) {
             f->micro_version = 4;
         } else if (f->version == 4) {
-            f->micro_version = 6;
+            f->micro_version = 7;
         } else
             av_assert0(0);
 
@@ -1179,6 +1179,9 @@ static void encode_histogram_remap(FFV1Context *f, FFV1SliceContext *sc)
         int lu = 0;
         uint8_t state[2][32];
         int run = 0;
+
+        memset(state, 128, sizeof(state));
+        put_symbol(&sc->c, state[0], 0, 0);
         memset(state, 128, sizeof(state));
         for (int i= 0; i<65536; i++) {
             int ri = i ^ ((i&0x8000) ? 0 : flip);
@@ -1258,59 +1261,179 @@ static void load_rgb_float32_frame(FFV1Context *f, FFV1SliceContext *sc,
         AV_QSORT(unit[3], i, Unit, CMP);
 }
 
+typedef struct RemapEncoderState {
+    int delta_stack[65536];     //We need to encode the run value before the adjustments, this stores the adjustments until we know the length of the run
+    int16_t index_stack[65537]; //only needed with multiple segments
+    uint8_t state[2][3][32];
+    int mul[4096+1];
+    RangeCoder rc;
+    int lu;
+    int run;
+    int64_t last_val;
+    int compact_index;
+    int mul_count;
+    int i;
+    int pixel_num;
+    int p;
+} RemapEncoderState;
+
+static inline void copy_state(RemapEncoderState *dst, const RemapEncoderState *src)
+{
+    dst->rc = src->rc;
+    memcpy(dst->mul, src->mul, (src->mul_count + 1) * sizeof(src->mul[0]));
+    memcpy(dst->delta_stack, src->delta_stack, src->run * sizeof(src->delta_stack[0]));
+    memcpy(dst->index_stack, src->index_stack, (src->run + 1) * sizeof(src->index_stack[0]));
+    memcpy(dst->state, src->state, sizeof(dst->state));
+    dst->lu             = src->lu;
+    dst->run            = src->run;
+    dst->last_val       = src->last_val;
+    dst->compact_index  = src->compact_index;
+    dst->mul_count      = src->mul_count;
+    dst->i              = src->i;
+    dst->pixel_num      = src->pixel_num;
+    dst->p              = src->p;
+}
+
+static inline void encode_mul(RemapEncoderState *s, int mul_index)
+{
+    av_assert2(s->mul[ mul_index ]);
+    if (s->mul[ mul_index ] < 0) {
+        s->mul[ mul_index ] *= -1;
+        put_symbol_inline(&s->rc, s->state[0][2], s->mul[ mul_index ], 0, NULL, NULL);
+    }
+}
+
+static int encode_float32_remap_segment(FFV1SliceContext *sc, Unit unit[4][65536],
+                                        RemapEncoderState *state_arg, int update, int final)
+{
+    RemapEncoderState s;
+
+    copy_state(&s, state_arg);
+
+    if (s.i == 0) {
+        memset(s.state, 128, sizeof(s.state));
+        put_symbol(&s.rc, s.state[0][0], s.mul_count, 0);
+        memset(s.state, 128, sizeof(s.state));
+        s.last_val = -1;
+        s.compact_index = -1;
+        s.lu = 0;
+        s.run = 0;
+    }
+
+    for (; s.i < s.pixel_num+1; s.i++) {
+        int64_t val;
+        if (s.i == s.pixel_num) {
+            if (s.last_val == 0xFFFFFFFF) {
+                break;
+            } else {
+                val = 1LL<<32;
+            }
+        } else
+            val = unit[s.p][s.i].val;
+
+        if (s.last_val != val) {
+            int current_mul_index = ((s.last_val + 1) * s.mul_count) >> 32;
+            int current_mul = s.i ? FFABS(s.mul[current_mul_index]) : 1;
+            int64_t delta = 0;
+            av_assert2(s.last_val < val);
+            av_assert2(current_mul > 0);
+
+            if (current_mul > 1) {
+                delta = val - s.last_val;
+                val = FFMAX(1, (delta + current_mul/2) / current_mul);
+
+                delta -= val*current_mul;
+                av_assert2(delta <= current_mul/2);
+                av_assert2(delta > -current_mul);
+                val += s.last_val;
+            }
+            av_assert2(s.last_val < val);
+            if (s.lu) {
+                s.index_stack[s.run] = current_mul_index;
+                av_assert2(s.run < FF_ARRAY_ELEMS(s.delta_stack));
+                if (val - s.last_val == 1) {
+                    s.delta_stack[s.run] = delta;
+                    s.run ++;
+                    s.last_val += current_mul + delta;
+                } else {
+                    put_symbol_inline(&s.rc, s.state[s.lu][0], s.run, 0, NULL, NULL);
+                    for(int k=0; k<s.run; k++) {
+                        int stack_mul = s.mul[ s.index_stack[k] ];
+                        if (stack_mul>1)
+                            put_symbol_inline(&s.rc, s.state[s.lu][1], s.delta_stack[k], 1, NULL, NULL);
+                        encode_mul(&s, s.index_stack[k+1]);
+                    }
+                    if (s.run == 0)
+                        s.lu ^= 1;
+                    s.run = 0;
+                    s.i--; // we did not encode val so we need to backstep
+                    s.last_val += current_mul;
+                    continue;
+                }
+            } else {
+                av_assert2(s.run == 0);
+                put_symbol_inline(&s.rc, s.state[s.lu][0], val - s.last_val - 1, 0, NULL, NULL);
+                if (current_mul > 1)
+                    put_symbol_inline(&s.rc, s.state[s.lu][1], delta, 1, NULL, NULL);
+                if (val - s.last_val == 1)
+                    s.lu ^= 1;
+                s.last_val += (val - s.last_val) * current_mul + delta;
+
+                encode_mul(&s, ((s.last_val + 1) * s.mul_count) >> 32);
+            }
+            s.compact_index ++;
+        }
+        if (final && s.i < s.pixel_num)
+            sc->bitmap[s.p][unit[s.p][s.i].ndx] = s.compact_index;
+    }
+
+    if (update) {
+        copy_state(state_arg, &s);
+    }
+    return get_rac_count(&s.rc);
+}
+
 static void encode_float32_remap(FFV1Context *f, FFV1SliceContext *sc,
                                  const uint8_t *src[4], Unit unit[4][65536])
 {
-    int pixel_num = sc->slice_width * sc->slice_height;
+    RemapEncoderState s;
+    s.pixel_num = sc->slice_width * sc->slice_height;
 
-    av_assert0 (pixel_num <= 65536);
+    av_assert0 (s.pixel_num <= 65536);
 
     for (int p= 0; p < 1 + 2*f->chroma_planes + f->transparency; p++) {
-        int lu = 0;
-        uint8_t state[2][32];
-        int run = 0;
+        float score_tab[16] = {0};
         int64_t last_val = -1;
-        int compact_index = -1;
+        s.rc = sc->c;
+        s.i = 0;
+        s.p = p;
 
-        memset(state, 128, sizeof(state));
-        for (int i= 0; i<pixel_num+1; i++) {
-            int64_t val;
-            if (i == pixel_num) {
-                if (last_val == 0xFFFFFFFF) {
-                    break;
-                } else {
-                    val = 1LL<<32;
-                }
-            } else
-                val = unit[p][i].val;
+        s.mul_count = 1;
 
-            if (last_val != val) {
+        for (int i= 0; i<s.pixel_num; i++) {
+            int64_t val = unit[p][i].val;
+            if (val != last_val) {
                 av_assert2(last_val < val);
-                if (lu) {
-                    if (val - last_val == 1) {
-                        run ++;
-                        last_val = val;
-                    } else {
-                        put_symbol_inline(&sc->c, state[lu], run, 0, NULL, NULL);
-                        if (run == 0)
-                            lu ^= 1;
-                        run = 0;
-                        i--; // we did not encode val so we need to backstep
-                        last_val ++;
-                        continue;
-                    }
-                } else {
-                    av_assert2(run == 0);
-                    put_symbol_inline(&sc->c, state[lu], val - last_val - 1, 0, NULL, NULL);
-                    if (val - last_val == 1)
-                        lu ^= 1;
-                    last_val = val;
+                for(int si= 0; si < FF_ARRAY_ELEMS(score_tab); si++) {
+                    int64_t delta = val - last_val;
+                    int mul = last_val < 0 ? 1 : (1<<si);
+                    int64_t cost = FFMAX((delta + mul/2)  / mul, 1);
+                    score_tab[si] += log2(cost) + fabs(delta - cost*mul);
                 }
-                compact_index ++;
+                last_val = val;
             }
-            if (i < pixel_num)
-                sc->bitmap[p][unit[p][i].ndx] = compact_index;
         }
+        int best_index = 0;
+        for(int si= 1; si < FF_ARRAY_ELEMS(score_tab); si++) {
+            if (score_tab[si] < score_tab[ best_index ])
+                best_index = si;
+        }
+        s.mul[0] = -1 << best_index;
+        s.mul[s.mul_count] = 1;
+
+        encode_float32_remap_segment(sc, unit, &s, 1, 1);
+
+        sc->c = s.rc;
     }
 }
 
-- 
2.48.1

_______________________________________________
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:[~2025-03-22 18:01 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-03-22 18:01 Michael Niedermayer [this message]
  -- strict thread matches above, loose matches on Subject: below --
2025-03-20 22:30 Michael Niedermayer
2025-03-20 23:07 ` Lynne
2025-03-21 20:13   ` Michael Niedermayer
2025-03-21 21:12     ` Michael Niedermayer
2025-03-21 21:36       ` Michael Niedermayer
2025-03-21 22:22         ` Michael Niedermayer
2025-03-22 17:45           ` Michael Niedermayer
2025-03-22 17:54             ` Jerome Martinez
2025-03-24  1:53               ` Michael Niedermayer
2025-03-24 12:31                 ` Michael Niedermayer

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=20250322180139.3462267-1-michael@niedermayer.cc \
    --to=michael@niedermayer.cc \
    --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