From 58a2dc3c2ea31da1c73b81ea8162abdb0a00e628 Mon Sep 17 00:00:00 2001 From: Shreesh Adiga <16567adigashreesh@gmail.com> 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 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. Refactored the C implementation into a header file so that it can be used from checkasm test without having to duplicate the code there. 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 | 1 + libavutil/aarch64/base64.S | 162 ++++++++++++++++++++++++++++++++++++ libavutil/aarch64/base64.h | 26 ++++++ libavutil/base64.c | 39 +++------ libavutil/base64_internal.h | 66 +++++++++++++++ tests/checkasm/Makefile | 1 + tests/checkasm/base64.c | 75 +++++++++++++++++ tests/checkasm/checkasm.c | 1 + tests/checkasm/checkasm.h | 1 + tests/fate/checkasm.mak | 1 + 10 files changed, 345 insertions(+), 28 deletions(-) create mode 100644 libavutil/aarch64/base64.S create mode 100644 libavutil/aarch64/base64.h create mode 100644 libavutil/base64_internal.h create mode 100644 tests/checkasm/base64.c diff --git a/libavutil/aarch64/Makefile b/libavutil/aarch64/Makefile index ba5e0a4a6a..b286cfadb0 100644 --- a/libavutil/aarch64/Makefile +++ b/libavutil/aarch64/Makefile @@ -6,6 +6,7 @@ OBJS += aarch64/aes_init.o \ ARMV8-OBJS += aarch64/crc.o NEON-OBJS += aarch64/aes.o \ + aarch64/base64.o \ aarch64/float_dsp_neon.o \ aarch64/tx_float_neon.o \ diff --git a/libavutil/aarch64/base64.S b/libavutil/aarch64/base64.S new file mode 100644 index 0000000000..74cb75e21d --- /dev/null +++ b/libavutil/aarch64/base64.S @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2026 Shreesh Adiga <16567adigashreesh@gmail.com> + * + * 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 + */ + +#include "asm.S" + +function ff_base64_encode_neon, export=1 + // x0 - char *out + // x1 - const char *in + // w2 - int in_size + // x3 - const char *tbl + uxtw x2, w2 + 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.gt 2f + + // process 24 bytes using half width vector + 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.gt 4f // <4 bytes tail + +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: // <4 bytes tail + 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..ed91669177 --- /dev/null +++ b/libavutil/aarch64/base64.h @@ -0,0 +1,26 @@ +/* + * 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 + +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..a6c21b05e7 100644 --- a/libavutil/base64.c +++ b/libavutil/base64.c @@ -28,8 +28,13 @@ #include #include "base64.h" +#include "base64_internal.h" #include "error.h" -#include "intreadwrite.h" + +#if ARCH_AARCH64 +#include "libavutil/aarch64/cpu.h" +#include "libavutil/aarch64/base64.h" +#endif /* ---------------- private code */ static const uint8_t map2[256] = @@ -148,36 +153,14 @@ char *av_base64_encode(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]; +#if ARCH_AARCH64 + if (in_size >= 48 && have_neon(av_get_cpu_flags())) { + return ff_base64_encode_neon(out, in, in_size, b64); } - 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; +#endif + return ff_base64_encode_c(out, in, in_size, b64); } diff --git a/libavutil/base64_internal.h b/libavutil/base64_internal.h new file mode 100644 index 0000000000..754720f9da --- /dev/null +++ b/libavutil/base64_internal.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2006 Ryan Martell. (rdm4@martellventures.com) + * + * 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_BASE64_INTERNAL_H +#define AVUTIL_BASE64_INTERNAL_H + +#include +#include "intreadwrite.h" + +/***************************************************************************** +* b64_encode: Stolen from VLC's http.c. +* Simplified by Michael. +* Fixed edge cases and made it work from data (vs. strings) by Ryan. +*****************************************************************************/ + +static inline char *ff_base64_encode_c(char *out, const uint8_t *in, int in_size, const char *b64) +{ + char *ret, *dst; + unsigned i_bits = 0; + int i_shift = 0; + int bytes_remaining = in_size; + + 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; +} + +#endif /* AVUTIL_BASE64_INTERNAL_H */ diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile index c5b25e753a..6b78f0d35a 100644 --- a/tests/checkasm/Makefile +++ b/tests/checkasm/Makefile @@ -101,6 +101,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..dd75aed9e6 --- /dev/null +++ b/tests/checkasm/base64.c @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026 Shreesh Adiga <16567adigashreesh@gmail.com> + * + * 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/base64_internal.h" +#include "libavutil/mem_internal.h" +#if ARCH_AARCH64 +#include "libavutil/aarch64/cpu.h" +#include "libavutil/aarch64/base64.h" +#endif +#include + +#define BUF_SIZE (1024) + +static char *(*base64_encode_get_fn(void))(char *, const uint8_t *, int, const char *) { +#if ARCH_AARCH64 + int cpu_flags = av_get_cpu_flags(); + if (have_neon(cpu_flags)) + return ff_base64_encode_neon; + else +#endif + return ff_base64_encode_c; +} + +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)]); + static const char b64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + for (int i = 0; i < BUF_SIZE; i++) + buf[i] = rnd(); + + declare_func(char *, char *out, const uint8_t *in, int in_size, const char *b64); + + func_type *fn = base64_encode_get_fn(); + + if (check_func(fn, "av_base64_encode")) { + for (int i = 0; i < 48 * 5; i++) { + size_t offset = rnd() % (BUF_SIZE - i); + call_new(out1, i ? buf + offset : NULL, i, b64); + call_ref(out2, i ? buf + offset : NULL, i, b64); + if (memcmp(out1, out2, AV_BASE64_SIZE(i))) + fail(); + } + + bench_new(out1, buf, BUF_SIZE, b64); + } +} + +void checkasm_check_base64(void) +{ + check_base64_encode(); + report("base64"); +} diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c index f0f6f097de..5127b35729 100644 --- a/tests/checkasm/checkasm.c +++ b/tests/checkasm/checkasm.c @@ -303,6 +303,7 @@ static const CheckasmTest tests[] = { #endif #if CONFIG_AVUTIL { "aes", checkasm_check_aes }, + { "base64", checkasm_check_base64 }, { "crc", checkasm_check_crc, .uninit = checkasm_uninit_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 84fc0b0c08..3e975e54a7 100644 --- a/tests/checkasm/checkasm.h +++ b/tests/checkasm/checkasm.h @@ -39,6 +39,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); diff --git a/tests/fate/checkasm.mak b/tests/fate/checkasm.mak index fad24e5861..df05224e76 100644 --- a/tests/fate/checkasm.mak +++ b/tests/fate/checkasm.mak @@ -8,6 +8,7 @@ FATE_CHECKASM = fate-checkasm-aacencdsp \ fate-checkasm-apv_dsp \ fate-checkasm-audiodsp \ fate-checkasm-av_tx \ + fate-checkasm-base64 \ fate-checkasm-blockdsp \ fate-checkasm-bswapdsp \ fate-checkasm-cavsdsp \