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]
