PR #23682 opened by Shreesh Adiga (tantei3)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23682
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23682.patch

Add NEON implementation using LD3 to load 48 bytes with de-interleaving
using which 6 bits are extracted using USHR and SLI to get 4 registers, which
is used as a index into lookup table using TBL with 4 registers to translate
into the 64-byte output, which is written using ST4 to de-interleave.
Add a checkasm test to validate the input lengths from 0 to 239 bytes to cover
all the code paths introduced in the NEON implementation.

Observed about 4x speedup by checkasm on A720 core on 1kB input:
```
C:
 - base64.base64 [OK]
NEON:
 - base64.base64 [OK]
checkasm: all 1 tests passed
Benchmark results:
  name                      nsecs (vs ref)
  av_base64_encode_c:      1286.9
  av_base64_encode_neon:    315.5 ( 4.08x)
```


>From 7aa79a908ecfcb09790983b8470be33685c69b50 Mon Sep 17 00:00:00 2001
From: Shreesh Adiga <[email protected]>
Date: Thu, 2 Jul 2026 21:54:16 +0530
Subject: [PATCH] avutil/base64: add aarch64 NEON implementation for base64
 encode

Add NEON implementation using LD3 to load 48 bytes with de-interleaving
using which 6 bits are extracted using USHR and SLI to get 4 registers, which
is used as a index into lookup table using TBL with 4 registers to translate
into the 64-byte output, which is written using ST4 to de-interleave.
Add a checkasm test to validate the input lengths from 0 to 239 bytes to cover
all the code paths introduced in the NEON implementation.

Observed about 4x speedup by checkasm on A720 core on 1kB input:
C:
 - base64.base64 [OK]
NEON:
 - base64.base64 [OK]
checkasm: all 1 tests passed
Benchmark results:
  name                      nsecs (vs ref)
  av_base64_encode_c:      1286.9
  av_base64_encode_neon:    315.5 ( 4.08x)
---
 libavutil/aarch64/Makefile |   3 +-
 libavutil/aarch64/base64.S | 160 +++++++++++++++++++++++++++++++++++++
 libavutil/aarch64/base64.h |  31 +++++++
 libavutil/base64.c         |   9 +++
 tests/checkasm/Makefile    |   1 +
 tests/checkasm/base64.c    | 130 ++++++++++++++++++++++++++++++
 tests/checkasm/checkasm.c  |   1 +
 tests/checkasm/checkasm.h  |   1 +
 8 files changed, 335 insertions(+), 1 deletion(-)
 create mode 100644 libavutil/aarch64/base64.S
 create mode 100644 libavutil/aarch64/base64.h
 create mode 100644 tests/checkasm/base64.c

diff --git a/libavutil/aarch64/Makefile b/libavutil/aarch64/Makefile
index 8a7e7ca057..fffeca96ee 100644
--- a/libavutil/aarch64/Makefile
+++ b/libavutil/aarch64/Makefile
@@ -4,7 +4,8 @@ OBJS += aarch64/cpu.o                                           
      \
 
 ARMV8-OBJS += aarch64/crc.o
 
-NEON-OBJS += aarch64/float_dsp_neon.o                                 \
+NEON-OBJS += aarch64/base64.o                                         \
+             aarch64/float_dsp_neon.o                                 \
              aarch64/tx_float_neon.o                                  \
 
 NEON-OBJS-$(CONFIG_PIXELUTILS) += aarch64/pixelutils_neon.o
diff --git a/libavutil/aarch64/base64.S b/libavutil/aarch64/base64.S
new file mode 100644
index 0000000000..776930b3b3
--- /dev/null
+++ b/libavutil/aarch64/base64.S
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2026 Shreesh Adiga <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "asm.S"
+
+function ff_base64_encode_neon, export=1
+          // x0 - char *out
+          // x1 - const char *in
+          // x2 - int in_size
+          // x3 - const char *tbl
+        cmp             x2, #48
+        add             x4, x2, x1 // end = src + in_size
+        mov             x5, x0 // dst = out
+        b.lt            2f // skip NEON loop
+        ld1             { v0.16b - v3.16b }, [x3]
+        movi            v4.16b, #63
+        sub             x6, x4, #48
+
+1: // NEON 48bytes loop
+        ld3             { v5.16b - v7.16b }, [x1], #48
+        ushr            v18.16b, v5.16b, #2
+        ushr            v16.16b, v6.16b, #4
+        tbl             v18.16b, { v0.16b, v1.16b, v2.16b, v3.16b }, v18.16b
+        ushr            v17.16b, v7.16b, #6
+        sli             v16.16b, v5.16b, #4
+        and             v5.16b, v7.16b, v4.16b
+        sli             v17.16b, v6.16b, #2
+        tbl             v21.16b, { v0.16b, v1.16b, v2.16b, v3.16b }, v5.16b
+        and             v16.16b, v16.16b, v4.16b
+        cmp             x1, x6
+        tbl             v19.16b, { v0.16b, v1.16b, v2.16b, v3.16b }, v16.16b
+        and             v17.16b, v17.16b, v4.16b
+        tbl             v20.16b, { v0.16b, v1.16b, v2.16b, v3.16b }, v17.16b
+        st4             { v18.16b - v21.16b }, [x5], #64
+        b.ls            1b // NEON 48bytes loop
+        sub             x6, x4, #24
+        cmp             x1, x6
+        b.hi            2f
+
+        ld3             { v5.8b - v7.8b }, [x1], #24
+        ushr            v18.8b, v5.8b, #2
+        ushr            v16.8b, v6.8b, #4
+        tbl             v18.8b, { v0.16b, v1.16b, v2.16b, v3.16b }, v18.8b
+        ushr            v17.8b, v7.8b, #6
+        sli             v16.8b, v5.8b, #4
+        and             v5.8b, v7.8b, v4.8b
+        sli             v17.8b, v6.8b, #2
+        tbl             v21.8b, { v0.16b, v1.16b, v2.16b, v3.16b }, v5.8b
+        and             v16.8b, v16.8b, v4.8b
+        tbl             v19.8b, { v0.16b, v1.16b, v2.16b, v3.16b }, v16.8b
+        and             v17.8b, v17.8b, v4.8b
+        tbl             v20.8b, { v0.16b, v1.16b, v2.16b, v3.16b }, v17.8b
+        st4             { v18.8b - v21.8b }, [x5], #32
+2: // skip NEON loop
+        sub             x6, x4, #4
+        cmp             x1, x6
+        b.hi            4f
+
+3: // 4bytes available loop
+        ldr             w7, [x1], #3
+        rev             w7, w7
+        lsr             x8, x7, #26
+        ubfx            x9, x7, #20, #6
+        ldrb            w12, [x3, x8]
+        ubfx            x10, x7, #14, #6
+        ldrb            w13, [x3, x9]
+        ubfx            x11, x7, #8, #6
+        ldrb            w14, [x3, x10]
+        orr             w12, w12, w13, lsl #8
+        cmp             x1, x6
+        ldrb            w15, [x3, x11]
+        orr             w12, w12, w14, lsl #16
+        orr             w12, w12, w15, lsl #24
+        str             w12, [x5], #4
+        b.ls            3b // 4bytes available loop
+
+4:
+        sub             x6, x4, x1
+        cmp             x6, #3
+        b.eq            7f
+        cmp             x6, #2
+        b.eq            6f
+        cmp             x6, #1
+        b.eq            5f
+        strb            wzr, [x5]
+        ret
+
+5:
+          // 1 byte input remaining, 2 bytes padding needed
+        ldrb            w7, [x1]
+        lsr             x8, x7, #2
+        ubfiz           x9, x7, #4, #2
+        ldrb            w8, [x3, x8]
+        movz            w10, #0x3d3d, lsl #16
+        ldrb            w9, [x3, x9]
+        orr             w10, w10, w8
+        orr             w10, w10, w9, lsl #8
+        str             w10, [x5]
+        strb            wzr, [x5, #4]
+        ret
+
+6:
+          // 2 bytes input remaining, 1 byte padding needed
+        ldrb            w7, [x1]
+        ldrb            w8, [x1, #1]
+        lsr             w9, w7, #2
+        lsr             w10, w8, #4
+        ubfiz           w11, w8, #2, #4
+        ldrb            w12, [x3, x9]
+        bfi             w10, w7, #4, #2
+        ldrb            w14, [x3, x11]
+        movz            w15, #0x3d00, lsl #16
+        ldrb            w13, [x3, x10]
+        orr             w15, w15, w12
+        orr             w15, w15, w14, lsl #16
+        orr             w15, w15, w13, lsl #8
+        str             w15, [x5]
+        strb            wzr, [x5, #4]
+        ret
+
+7:
+          // 3 bytes input remaining, no padding needed
+        ldrb            w7, [x1]
+        ldrb            w8, [x1, #1]
+        lsr             w10, w7, #2
+        ldrb            w9, [x1, #2]
+        lsr             w11, w8, #4
+        ldrb            w10, [x3, x10]
+        lsr             w12, w9, #6
+        bfi             w11, w7, #4, #2
+        bfi             w12, w8, #2, #4
+        ldrb            w11, [x3, x11]
+        and             w13, w9, #0x3f
+        ldrb            w12, [x3, x12]
+        orr             w10, w10, w11, lsl #8
+        ldrb            w13, [x3, x13]
+        orr             w10, w10, w12, lsl #16
+        orr             w10, w10, w13, lsl #24
+        str             w10, [x5]
+        strb            wzr, [x5, #4]
+        ret
+
+endfunc
diff --git a/libavutil/aarch64/base64.h b/libavutil/aarch64/base64.h
new file mode 100644
index 0000000000..7201b76ce1
--- /dev/null
+++ b/libavutil/aarch64/base64.h
@@ -0,0 +1,31 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+#ifndef AVUTIL_AARCH64_BASE64_H
+#define AVUTIL_AARCH64_BASE64_H
+
+#include "libavutil/cpu.h"
+#include "libavutil/aarch64/cpu.h"
+
+static inline int ff_is_neon_supported(void) {
+    int cpu_flags = av_get_cpu_flags();
+
+    return have_neon(cpu_flags);
+}
+
+char *ff_base64_encode_neon(char *out, const uint8_t *in, int in_size, const 
char *tbl);
+#endif /* AVUTIL_AARCH64_BASE64_H */
diff --git a/libavutil/base64.c b/libavutil/base64.c
index 69e11e6f5e..b16993a723 100644
--- a/libavutil/base64.c
+++ b/libavutil/base64.c
@@ -31,6 +31,10 @@
 #include "error.h"
 #include "intreadwrite.h"
 
+#if ARCH_AARCH64
+#include "libavutil/aarch64/base64.h"
+#endif
+
 /* ---------------- private code */
 static const uint8_t map2[256] =
 {
@@ -156,6 +160,11 @@ char *av_base64_encode(char *out, int out_size, const 
uint8_t *in, int in_size)
     if (in_size >= UINT_MAX / 4 ||
         out_size < AV_BASE64_SIZE(in_size))
         return NULL;
+#if ARCH_AARCH64
+    if (ff_is_neon_supported()) {
+        return ff_base64_encode_neon(out, in, in_size, b64);
+    }
+#endif
     ret = dst = out;
     while (bytes_remaining > 3) {
         i_bits = AV_RB32(in);
diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index c154d19ed4..d79a6b8b55 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -100,6 +100,7 @@ CHECKASMOBJS-$(CONFIG_SWSCALE)  += $(SWSCALEOBJS)
 # libavutil tests
 AVUTILOBJS                              += aes.o
 AVUTILOBJS                              += av_tx.o
+AVUTILOBJS                              += base64.o
 AVUTILOBJS                              += crc.o
 AVUTILOBJS                              += fixed_dsp.o
 AVUTILOBJS                              += float_dsp.o
diff --git a/tests/checkasm/base64.c b/tests/checkasm/base64.c
new file mode 100644
index 0000000000..28846d0fa6
--- /dev/null
+++ b/tests/checkasm/base64.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (c) 2026 Shreesh Adiga <[email protected]>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "checkasm.h"
+#include "libavutil/base64.h"
+#include "libavutil/mem_internal.h"
+#include "libavutil/error.h"
+#include "libavutil/intreadwrite.h"
+#if ARCH_AARCH64
+#include "libavutil/aarch64/base64.h"
+#endif
+
+
+#define BUF_SIZE (1024)
+
+static char *base64_encode_ref(char *out, int out_size, const uint8_t *in, int 
in_size)
+{
+    static const char b64[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+    char *ret, *dst;
+    unsigned i_bits = 0;
+    int i_shift = 0;
+    int bytes_remaining = in_size;
+
+    if (in_size >= UINT_MAX / 4 ||
+        out_size < AV_BASE64_SIZE(in_size))
+        return NULL;
+
+    ret = dst = out;
+    while (bytes_remaining > 3) {
+        i_bits = AV_RB32(in);
+        in += 3; bytes_remaining -= 3;
+        *dst++ = b64[ i_bits>>26        ];
+        *dst++ = b64[(i_bits>>20) & 0x3F];
+        *dst++ = b64[(i_bits>>14) & 0x3F];
+        *dst++ = b64[(i_bits>>8 ) & 0x3F];
+    }
+    i_bits = 0;
+    while (bytes_remaining) {
+        i_bits = (i_bits << 8) + *in++;
+        bytes_remaining--;
+        i_shift += 8;
+    }
+    while (i_shift > 0) {
+        *dst++ = b64[(i_bits << 6 >> i_shift) & 0x3f];
+        i_shift -= 6;
+    }
+    while ((dst - ret) & 3)
+        *dst++ = '=';
+    *dst = '\0';
+
+    return ret;
+}
+
+#if ARCH_AARCH64
+static char *base64_encode_neon(char *out, int out_size, const uint8_t *in, 
int in_size)
+{
+    static const char b64[] =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+    if (in_size >= UINT_MAX / 4 ||
+        out_size < AV_BASE64_SIZE(in_size))
+        return NULL;
+
+    return ff_base64_encode_neon(out, in, in_size, b64);
+}
+#endif
+
+char *(*base64_func)(char *, int, const uint8_t *, int);
+
+static void base64_encode_init_fn(void) {
+#if ARCH_AARCH64
+    int cpu_flags = av_get_cpu_flags();
+    if (have_neon(cpu_flags))
+        base64_func = base64_encode_neon;
+    else
+#endif
+        base64_func = base64_encode_ref;
+}
+
+static void check_base64_encode(void)
+{
+    LOCAL_ALIGNED_32(uint8_t, buf, [BUF_SIZE]);
+    LOCAL_ALIGNED_32(uint8_t, out1, [AV_BASE64_SIZE(BUF_SIZE)]);
+    LOCAL_ALIGNED_32(uint8_t, out2, [AV_BASE64_SIZE(BUF_SIZE)]);
+
+    for (int i = 0; i < BUF_SIZE; i++)
+        buf[i] = rnd();
+
+    base64_encode_init_fn();
+    declare_func(char *, char *out, int out_size, const uint8_t *in, int 
in_size);
+
+    func_type *fn = base64_func;
+
+    if (check_func(fn, "av_base64_encode")) {
+        for (int i = 0; i < 48 * 5 - 1; i++) {
+            size_t offset = rnd() % (BUF_SIZE - i);
+            call_new(out1, AV_BASE64_SIZE(BUF_SIZE), buf + offset, i);
+            call_ref(out2, AV_BASE64_SIZE(BUF_SIZE), buf + offset, i);
+            if (memcmp(out1, out2, AV_BASE64_SIZE(i)))
+                fail();
+        }
+
+        bench_new(out1, AV_BASE64_SIZE(BUF_SIZE), buf, BUF_SIZE);
+    }
+}
+
+
+void checkasm_check_base64(void)
+{
+    check_base64_encode();
+    report("base64");
+}
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 4f54db8e1d..5858c8c03a 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -304,6 +304,7 @@ static const CheckasmTest tests[] = {
 #endif
 #if CONFIG_AVUTIL
         { "aes",       checkasm_check_aes },
+        { "base64",    checkasm_check_base64 },
         { "crc",       checkasm_check_crc },
         { "fixed_dsp", checkasm_check_fixed_dsp },
         { "float_dsp", checkasm_check_float_dsp },
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 612ad0f9b9..2fad8f197e 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -44,6 +44,7 @@ void checkasm_check_alacdsp(void);
 void checkasm_check_apv_dsp(void);
 void checkasm_check_audiodsp(void);
 void checkasm_check_av_tx(void);
+void checkasm_check_base64(void);
 void checkasm_check_blackdetect(void);
 void checkasm_check_blend(void);
 void checkasm_check_blockdsp(void);
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to