PR #22666 opened by Renjianguang-mi
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22666
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22666.patch
Add ARM MVE SIMD optimization for resample_common in libswresample,
targeting Cortex-M processors with ARMv8.1-M Helium extension.
MVE and NEON are mutually exclusive (Cortex-M vs Cortex-A), so
compile-time selection is used. The assembly uses MVE tail-predicated
loops (wlstp/letp) which handle arbitrary filter lengths without
remainder loops. S32P support is added as MVE-only since vmlaldava.s32
provides native 32x32->64 bit accumulation not available in NEON.
Also rename resample.S to resample_neon.S for clarity.
Benchmarked on Cortex-M55, 200ms 2ch stereo, speedup vs C fallback:
44.1->48k 48->44.1k 8->48k 48->8k 16->48k 48->16k
S16P: 1.70x 1.38x 1.77x 2.45x 1.75x 2.04x
FLTP: 1.32x 1.19x 1.32x 2.14x 1.34x 1.76x
S32P: 1.38x 1.17x 1.37x 1.80x 1.36x 1.59x
Signed-off-by: Jianguang Ren <[email protected]>
>From bb95ebe46e55bddbda90c98522828d2bab0a201f Mon Sep 17 00:00:00 2001
From: Jianguang Ren <[email protected]>
Date: Tue, 31 Mar 2026 17:51:36 +0800
Subject: [PATCH] libswresample/arm: add MVE (Helium) optimized resample
functions
Add ARM MVE SIMD optimization for resample_common in libswresample,
targeting Cortex-M processors with ARMv8.1-M Helium extension.
MVE and NEON are mutually exclusive (Cortex-M vs Cortex-A), so
compile-time selection is used. The assembly uses MVE tail-predicated
loops (wlstp/letp) which handle arbitrary filter lengths without
remainder loops. S32P support is added as MVE-only since vmlaldava.s32
provides native 32x32->64 bit accumulation not available in NEON.
Also rename resample.S to resample_neon.S for clarity.
Benchmarked on Cortex-M55, 200ms 2ch stereo, speedup vs C fallback:
44.1->48k 48->44.1k 8->48k 48->8k 16->48k 48->16k
S16P: 1.70x 1.38x 1.77x 2.45x 1.75x 2.04x
FLTP: 1.32x 1.19x 1.32x 2.14x 1.34x 1.76x
S32P: 1.38x 1.17x 1.37x 1.80x 1.36x 1.59x
Signed-off-by: Jianguang Ren <[email protected]>
---
configure | 5 ++
ffbuild/arch.mak | 1 +
libavutil/arm/asm.S | 15 +++-
libavutil/arm/cpu.c | 9 ++-
libavutil/arm/cpu.h | 1 +
libavutil/cpu.h | 1 +
libswresample/arm/Makefile | 4 +-
libswresample/arm/resample_init.c | 61 +++++++++++----
libswresample/arm/resample_mve.S | 76 +++++++++++++++++++
.../arm/{resample.S => resample_neon.S} | 0
10 files changed, 152 insertions(+), 21 deletions(-)
create mode 100644 libswresample/arm/resample_mve.S
rename libswresample/arm/{resample.S => resample_neon.S} (100%)
diff --git a/configure b/configure
index fb1baf2eac..6873ed6201 100755
--- a/configure
+++ b/configure
@@ -479,6 +479,7 @@ Optimization options (experts only):
--disable-armv6t2 disable armv6t2 optimizations
--disable-vfp disable VFP optimizations
--disable-neon disable NEON optimizations
+ --disable-mve disable MVE (Helium) optimizations
--disable-arm-crc disable ARM/AArch64 CRC optimizations
--disable-dotprod disable DOTPROD optimizations
--disable-i8mm disable I8MM optimizations
@@ -2303,6 +2304,7 @@ ARCH_EXT_LIST_ARM="
i8mm
pmull
eor3
+ mve
neon
vfp
vfpv3
@@ -2915,6 +2917,7 @@ armv5te_deps="arm"
armv6_deps="arm"
armv6t2_deps="arm"
armv8_deps="aarch64"
+mve_deps="arm"
neon_deps_any="aarch64 arm"
intrinsics_neon_deps="neon"
intrinsics_sse2_deps="sse2"
@@ -6627,6 +6630,7 @@ EOF
enabled vfp && check_insn vfp 'fadds s0, s0, s0'
enabled vfpv3 && check_insn vfpv3 'vmov.f32 s0, #1.0'
enabled setend && check_insn setend 'setend be'
+ enabled mve && check_insn mve 'dlstp.16 lr, r0'
# If neither inline nor external assembly can use the feature by default,
# disable the main unsuffixed feature (e.g. HAVE_NEON).
@@ -8417,6 +8421,7 @@ if enabled arm; then
echo "ARMv6T2 enabled ${armv6t2-no}"
echo "VFP enabled ${vfp-no}"
echo "NEON enabled ${neon-no}"
+ echo "MVE enabled ${mve-no}"
echo "THUMB enabled ${thumb-no}"
fi
if enabled mips; then
diff --git a/ffbuild/arch.mak b/ffbuild/arch.mak
index 13e1eb33bc..985fa0a291 100644
--- a/ffbuild/arch.mak
+++ b/ffbuild/arch.mak
@@ -3,6 +3,7 @@ OBJS-$(HAVE_ARMV6) += $(ARMV6-OBJS) $(ARMV6-OBJS-yes)
OBJS-$(HAVE_ARMV8) += $(ARMV8-OBJS) $(ARMV8-OBJS-yes)
OBJS-$(HAVE_VFP) += $(VFP-OBJS) $(VFP-OBJS-yes)
OBJS-$(HAVE_NEON) += $(NEON-OBJS) $(NEON-OBJS-yes)
+OBJS-$(HAVE_MVE) += $(MVE-OBJS) $(MVE-OBJS-yes)
OBJS-$(HAVE_SVE) += $(SVE-OBJS) $(SVE-OBJS-yes)
OBJS-$(HAVE_SVE2) += $(SVE2-OBJS) $(SVE2-OBJS-yes)
OBJS-$(HAVE_SME) += $(SME-OBJS) $(SME-OBJS-yes)
diff --git a/libavutil/arm/asm.S b/libavutil/arm/asm.S
index e3a8c7f065..f62845d7aa 100644
--- a/libavutil/arm/asm.S
+++ b/libavutil/arm/asm.S
@@ -53,7 +53,12 @@
#endif
#if HAVE_AS_ARCH_DIRECTIVE
-#if HAVE_NEON
+#if HAVE_MVE
+ .arch armv8.1-m.main
+ .arch_extension dsp
+ .arch_extension mve.fp
+ .arch_extension fp.dp
+#elif HAVE_NEON
.arch armv7-a
#elif HAVE_ARMV6T2
.arch armv6t2
@@ -64,10 +69,16 @@
#endif
#endif
#if HAVE_AS_OBJECT_ARCH
+#if HAVE_MVE
+ELF .object_arch armv8-m.main
+#else
ELF .object_arch armv4
#endif
+#endif
-#if HAVE_NEON
+#if HAVE_MVE
+ /* MVE: .fpu not needed, .arch_extension mve.fp already set above */
+#elif HAVE_NEON
FPU .fpu neon
ELF .eabi_attribute 10, 0 @ suppress Tag_FP_arch
ELF .eabi_attribute 12, 0 @ suppress Tag_Advanced_SIMD_arch
diff --git a/libavutil/arm/cpu.c b/libavutil/arm/cpu.c
index 2e2977efc9..efe392b777 100644
--- a/libavutil/arm/cpu.c
+++ b/libavutil/arm/cpu.c
@@ -29,7 +29,8 @@
CORE_FLAG(ARMV6T2) | \
CORE_FLAG(VFP) | \
CORE_FLAG(VFPV3) | \
- CORE_FLAG(NEON))
+ CORE_FLAG(NEON) | \
+ CORE_FLAG(MVE))
#if defined __linux__ || defined __ANDROID__ || HAVE_ELF_AUX_INFO
@@ -112,6 +113,8 @@ static int get_cpuinfo(uint32_t *hwcap)
*hwcap |= HWCAP_ARM_VFPv3;
if (strstr(buf, " neon ") || strstr(buf, " asimd "))
*hwcap |= HWCAP_ARM_NEON;
+ if (strstr(buf, " mve "))
+ *hwcap |= HWCAP_MVE;
if (strstr(buf, " fp ")) // Listed on 64 bit ARMv8 kernels
*hwcap |= HWCAP_ARM_VFP | HWCAP_ARM_VFPv3;
break;
@@ -147,6 +150,7 @@ int ff_get_cpu_flags_arm(void)
check_cap(VFP, VFP);
check_cap(VFPv3, VFPV3);
check_cap(NEON, NEON);
+ check_cap(MVE, MVE);
/* The v6 checks above are not reliable so let higher flags
trickle down. */
@@ -178,6 +182,7 @@ int ff_get_cpu_flags_arm(void)
AV_CPU_FLAG_VFP * HAVE_VFP |
AV_CPU_FLAG_VFPV3 * HAVE_VFPV3 |
AV_CPU_FLAG_NEON * HAVE_NEON |
+ AV_CPU_FLAG_MVE * HAVE_MVE |
AV_CPU_FLAG_SETEND * !(HAVE_NEON | HAVE_VFPV3);
}
@@ -187,7 +192,7 @@ size_t ff_get_cpu_max_align_arm(void)
{
int flags = av_get_cpu_flags();
- if (flags & AV_CPU_FLAG_NEON)
+ if (flags & (AV_CPU_FLAG_NEON | AV_CPU_FLAG_MVE))
return 16;
return 8;
diff --git a/libavutil/arm/cpu.h b/libavutil/arm/cpu.h
index 1d6cc65dc4..31b5bfcb4e 100644
--- a/libavutil/arm/cpu.h
+++ b/libavutil/arm/cpu.h
@@ -28,6 +28,7 @@
#define have_vfp(flags) CPUEXT(flags, VFP)
#define have_vfpv3(flags) CPUEXT(flags, VFPV3)
#define have_neon(flags) CPUEXT(flags, NEON)
+#define have_mve(flags) CPUEXT(flags, MVE)
#define have_setend(flags) CPUEXT(flags, SETEND)
/* some functions use the VFPv2 vector mode which is deprecated in ARMv7-A
diff --git a/libavutil/cpu.h b/libavutil/cpu.h
index 07076dafb8..8bef99b705 100644
--- a/libavutil/cpu.h
+++ b/libavutil/cpu.h
@@ -84,6 +84,7 @@
#define AV_CPU_FLAG_SETEND (1 <<16)
#define AV_CPU_FLAG_PMULL (1 <<17)
#define AV_CPU_FLAG_EOR3 (1 <<18)
+#define AV_CPU_FLAG_MVE (1 <<19)
#define AV_CPU_FLAG_MMI (1 << 0)
#define AV_CPU_FLAG_MSA (1 << 1)
diff --git a/libswresample/arm/Makefile b/libswresample/arm/Makefile
index 53ab4626f4..37568c16b9 100644
--- a/libswresample/arm/Makefile
+++ b/libswresample/arm/Makefile
@@ -5,4 +5,6 @@ OBJS += arm/audio_convert_init.o \
OBJS-$(CONFIG_NEON_CLOBBER_TEST) += arm/neontest.o
NEON-OBJS += arm/audio_convert_neon.o \
- arm/resample.o
+ arm/resample_neon.o
+
+MVE-OBJS += arm/resample_mve.o
diff --git a/libswresample/arm/resample_init.c
b/libswresample/arm/resample_init.c
index b119943a7c..2e5b12f08a 100644
--- a/libswresample/arm/resample_init.c
+++ b/libswresample/arm/resample_init.c
@@ -24,21 +24,35 @@
#include "libavutil/attributes.h"
#include "libavutil/cpu.h"
-#include "libavutil/avassert.h"
#include "libavutil/arm/cpu.h"
#include "libswresample/resample.h"
+#if HAVE_MVE
+#define SIMD mve
+#define have_simd have_mve
+#elif HAVE_NEON
+#define SIMD neon
+#define have_simd have_neon
+#endif
+
+#ifdef SIMD
+
+#define CONCAT3_(a, b, c) a ## _ ## b ## _ ## c
+#define CONCAT3(a, b, c) CONCAT3_(a, b, c)
+#define RESAMPLE_FUNC(type) CONCAT3(ff_resample_common, type, SIMD)
+#define APPLY_FILTER(n, type) CONCAT3(ff_resample_common_apply_filter_x ## n,
type, SIMD)
+
#define DECLARE_RESAMPLE_COMMON_TEMPLATE(TYPE, DELEM, FELEM, FELEM2, OUT)
\
\
-void ff_resample_common_apply_filter_x4_##TYPE##_neon(FELEM2 *acc, const DELEM
*src, \
- const FELEM *filter, int
length); \
+void APPLY_FILTER(4, TYPE)(FELEM2 *acc, const DELEM *src,
\
+ const FELEM *filter, int length);
\
\
-void ff_resample_common_apply_filter_x8_##TYPE##_neon(FELEM2 *acc, const DELEM
*src, \
- const FELEM *filter, int
length); \
+void APPLY_FILTER(8, TYPE)(FELEM2 *acc, const DELEM *src,
\
+ const FELEM *filter, int length);
\
\
-static int ff_resample_common_##TYPE##_neon(ResampleContext *c, void *dest,
const void *source, \
- int n, int update_ctx)
\
+static int RESAMPLE_FUNC(TYPE)(ResampleContext *c, void *dest, const void
*source, \
+ int n, int update_ctx)
\
{
\
DELEM *dst = dest;
\
const DELEM *src = source;
\
@@ -57,16 +71,16 @@ static int ff_resample_common_##TYPE##_neon(ResampleContext
*c, void *dest, cons
for (dst_index = 0; dst_index < n; dst_index++) {
\
FELEM *filter = ((FELEM *) c->filter_bank) + c->filter_alloc * index;
\
\
- FELEM2 val = 0;
\
+ FELEM2 val = 0;
\
int i = 0;
\
if (x8_aligned_filter_length >= 8) {
\
- ff_resample_common_apply_filter_x8_##TYPE##_neon(&val,
&src[sample_index], \
- filter,
x8_aligned_filter_length); \
+ APPLY_FILTER(8, TYPE)(&val, &src[sample_index],
\
+ filter, x8_aligned_filter_length);
\
i += x8_aligned_filter_length;
\
\
} else if (x4_aligned_filter_length >= 4) {
\
- ff_resample_common_apply_filter_x4_##TYPE##_neon(&val,
&src[sample_index], \
- filter,
x4_aligned_filter_length); \
+ APPLY_FILTER(4, TYPE)(&val, &src[sample_index],
\
+ filter, x4_aligned_filter_length);
\
i += x4_aligned_filter_length;
\
}
\
for (; i < c->filter_length; i++) {
\
@@ -103,19 +117,34 @@ DECLARE_RESAMPLE_COMMON_TEMPLATE(float, float, float,
float, OUT)
DECLARE_RESAMPLE_COMMON_TEMPLATE(s16, int16_t, int16_t, int32_t, OUT)
#undef OUT
+#if HAVE_MVE
+#define OUT(d, v) (v) = ((v) + (1<<(29)))>>30; (d) = av_clipl_int32(v)
+DECLARE_RESAMPLE_COMMON_TEMPLATE(s32, int32_t, int32_t, int64_t, OUT)
+#undef OUT
+#endif
+
+#endif /* SIMD */
+
av_cold void swri_resample_dsp_arm_init(ResampleContext *c)
{
+#ifdef SIMD
int cpu_flags = av_get_cpu_flags();
- if (!have_neon(cpu_flags))
+ if (!have_simd(cpu_flags))
return;
- switch(c->format) {
+ switch (c->format) {
case AV_SAMPLE_FMT_FLTP:
- c->dsp.resample_common = ff_resample_common_float_neon;
+ c->dsp.resample_common = RESAMPLE_FUNC(float);
break;
case AV_SAMPLE_FMT_S16P:
- c->dsp.resample_common = ff_resample_common_s16_neon;
+ c->dsp.resample_common = RESAMPLE_FUNC(s16);
break;
+#if HAVE_MVE
+ case AV_SAMPLE_FMT_S32P:
+ c->dsp.resample_common = RESAMPLE_FUNC(s32);
+ break;
+#endif
}
+#endif
}
diff --git a/libswresample/arm/resample_mve.S b/libswresample/arm/resample_mve.S
new file mode 100644
index 0000000000..3b44eec92e
--- /dev/null
+++ b/libswresample/arm/resample_mve.S
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2025 Jianguang Ren <[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 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 "libavutil/arm/asm.S"
+
+@ MVE low-overhead loops with tail predication (wlstp/letp) handle
+@ arbitrary lengths automatically, so x4 and x8 share one implementation.
+
+function ff_resample_common_apply_filter_x4_float_mve, export=1
+ b ff_resample_common_apply_filter_x8_float_mve
+endfunc
+
+function ff_resample_common_apply_filter_x8_float_mve, export=1
+ push {r4, lr} @ r4
for 8-byte stack alignment
+ vmov.i32 q0, #0 @
accumulator
+ wlstp.32 lr, r3, 1f
+0: vldrw.32 q2, [r1], #16 @
src[0..3]
+ vldrw.32 q3, [r2], #16 @
filter[0..3]
+ vfma.f32 q0, q2, q3 @ acc
+= src * filter
+ letp lr, 0b
+1: vadd.f32 s0, s0, s1 @
horizontal sum
+ vadd.f32 s0, s0, s2
+ vadd.f32 s0, s0, s3
+ vstr.32 s0, [r0] @ *acc
= sum
+ pop {r4, pc}
+endfunc
+
+function ff_resample_common_apply_filter_x4_s16_mve, export=1
+ b ff_resample_common_apply_filter_x8_s16_mve
+endfunc
+
+function ff_resample_common_apply_filter_x8_s16_mve, export=1
+ push {r4, lr} @ r4
for 8-byte stack alignment
+ mov r12, #0 @
accumulator
+ wlstp.16 lr, r3, 1f
+0: vldrh.s16 q1, [r1], #16 @
src[0..7]
+ vldrh.s16 q2, [r2], #16 @
filter[0..7]
+ vmladava.s16 r12, q1, q2 @ acc
+= dot(src, filter)
+ letp lr, 0b
+1: str r12, [r0] @ *acc
= sum
+ pop {r4, pc}
+endfunc
+
+function ff_resample_common_apply_filter_x4_s32_mve, export=1
+ b ff_resample_common_apply_filter_x8_s32_mve
+endfunc
+
+function ff_resample_common_apply_filter_x8_s32_mve, export=1
+ push {r4, r5, lr}
+ mov r4, #0 @
accumulator low
+ mov r5, #0 @
accumulator high
+ wlstp.32 lr, r3, 1f
+0: vldrw.32 q1, [r1], #16 @
src[0..3]
+ vldrw.32 q2, [r2], #16 @
filter[0..3]
+ vmlaldava.s32 r4, r5, q1, q2 @ acc
+= dot(src, filter)
+ letp lr, 0b
+1: strd r4, r5, [r0] @ *acc
= sum (64-bit)
+ pop {r4, r5, pc}
+endfunc
diff --git a/libswresample/arm/resample.S b/libswresample/arm/resample_neon.S
similarity index 100%
rename from libswresample/arm/resample.S
rename to libswresample/arm/resample_neon.S
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]