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]

Reply via email to