Commit 098dd9f46a for ffmpeg

commit 098dd9f46a4962bfe6c50bbb7bc0d05db1a39843
Author: Devin Heitmueller <dheitmueller@ltnglobal.com>
Date:   Wed Sep 16 14:50:28 2026 -0400

    avcodec/bitpacked: add AArch64 NEON unpacking

    Unpack packed 10-bit YUV 4:2:2 in 16-pixel NEON blocks and unroll the
    main loop to 32 pixels. Keep exact-width source loads and use the scalar
    implementation for decoder tails smaller than eight pixels.

    Enable the optimized implementation on little-endian AArch64. Retain the
    C fallback on big-endian AArch64, where the byte layout expected by this
    implementation does not apply.

    The implementation passed 300 consecutive checkasm repetitions and
    produced bit-exact output in a 100-frame UHD decode.

    Thanks to Martin Storsjö for feedback and suggestions for improvement.

    Correctness command:
    ./tests/checkasm/checkasm --test=bitpackeddec --repeat=300 66000

    Benchmark command:
    ./tests/checkasm/checkasm --bench --test=bitpackeddec \
        --function=bitpacked_unpack_yuv422p10 --duration=1000000 95000

    Apple M4 Mac mini, Apple clang 17.0.0:
    bitpacked_unpack_yuv422p10_c:       876.8 ns
    bitpacked_unpack_yuv422p10_neon:    227.2 ns (3.86x)

    Clang auto-vectorizes the C reference with NEON, so the result measures
    the hand-written implementation against compiler-generated SIMD rather than
    against purely scalar machine code.

diff --git a/libavcodec/aarch64/Makefile b/libavcodec/aarch64/Makefile
index e0762e3fa1..b1ceb36668 100644
--- a/libavcodec/aarch64/Makefile
+++ b/libavcodec/aarch64/Makefile
@@ -21,6 +21,7 @@ OBJS-$(CONFIG_VP8DSP)                   += aarch64/vp8dsp_init_aarch64.o
 OBJS-$(CONFIG_AAC_DECODER)              += aarch64/aacpsdsp_init_aarch64.o \
                                            aarch64/sbrdsp_init_aarch64.o
 OBJS-$(CONFIG_AAC_ENCODER)              += aarch64/aacencdsp_init.o
+OBJS-$(CONFIG_BITPACKED_DECODER)        += aarch64/bitpacked_init_aarch64.o
 OBJS-$(CONFIG_DCA_DECODER)              += aarch64/dcadsp_init_aarch64.o \
                                            aarch64/synth_filter_init.o
 OBJS-$(CONFIG_OPUS_DECODER)             += aarch64/opusdsp_init.o
@@ -64,6 +65,7 @@ NEON-OBJS-$(CONFIG_VP8DSP)              += aarch64/vp8dsp_neon.o

 # decoders/encoders
 NEON-OBJS-$(CONFIG_AAC_DECODER)         += aarch64/aacpsdsp_neon.o
+NEON-OBJS-$(CONFIG_BITPACKED_DECODER)   += aarch64/bitpacked_neon.o
 NEON-OBJS-$(CONFIG_DCA_DECODER)         += aarch64/dcadsp_neon.o             \
                                            aarch64/synth_filter_neon.o
 NEON-OBJS-$(CONFIG_OPUS_DECODER)        += aarch64/opusdsp_neon.o
diff --git a/libavcodec/aarch64/bitpacked_init_aarch64.c b/libavcodec/aarch64/bitpacked_init_aarch64.c
new file mode 100644
index 0000000000..a9c875a5b0
--- /dev/null
+++ b/libavcodec/aarch64/bitpacked_init_aarch64.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2026 Devin Heitmueller <dheitmueller@ltnglobal.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 <stdint.h>
+
+#include "config.h"
+#include "libavutil/attributes.h"
+#include "libavutil/aarch64/cpu.h"
+#include "libavcodec/bitpacked_dec.h"
+
+void ff_bitpacked_unpack_yuv422p10_neon(const uint8_t *src,
+                                        uint16_t *y, uint16_t *u, uint16_t *v,
+                                        int width);
+
+av_cold void ff_bitpacked_init_aarch64(struct BitpackedContext *s)
+{
+    int cpu_flags = av_get_cpu_flags();
+
+    if (!HAVE_BIGENDIAN && have_neon(cpu_flags))
+        s->unpack_yuv422p10 = ff_bitpacked_unpack_yuv422p10_neon;
+}
diff --git a/libavcodec/aarch64/bitpacked_neon.S b/libavcodec/aarch64/bitpacked_neon.S
new file mode 100644
index 0000000000..f7bbd51b85
--- /dev/null
+++ b/libavcodec/aarch64/bitpacked_neon.S
@@ -0,0 +1,102 @@
+/*
+ * Bitpacked 10-bit YUV 4:2:2 unpacking
+ * Copyright (c) 2026 Devin Heitmueller <dheitmueller@ltnglobal.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 "libavutil/aarch64/asm.S"
+
+const   bitpacked_tbl, align=4
+        // Byte-pair indices for Y samples 0-7 and 8-15.
+        .byte           2, 1, 4, 3, 7, 6, 9, 8, 12, 11, 14, 13, 17, 16, 19, 18
+        .byte           22, 21, 24, 23, 27, 26, 29, 28
+        .byte           32, 31, 34, 33, 37, 36, 39, 38
+        // Byte-pair indices for the U and V samples.
+        .byte           1, 0, 6, 5, 11, 10, 16, 15, 21, 20, 26, 25, 31, 30, 36, 35
+        .byte           3, 2, 8, 7, 13, 12, 18, 17, 23, 22, 28, 27, 33, 32, 38, 37
+        // Per-lane multipliers align the alternating Y fields.
+        .hword          4, 64, 4, 64, 4, 64, 4, 64
+endconst
+
+// Consume 40 bytes and produce 16 Y, 8 U and 8 V samples. TBL reverses
+// each big-endian byte pair; 16-bit multiply/shift overflow discards unwanted
+// high bits before the common right shift. The main loop unrolls to 80 bytes.
+.macro  unpack_16
+        ld1             {v0.16b, v1.16b}, [x0]
+        ldr             d2, [x0, #32]
+        add             x0, x0, #40
+
+        tbl             v3.16b, {v0.16b-v2.16b}, v16.16b
+        tbl             v4.16b, {v0.16b-v2.16b}, v17.16b
+        tbl             v5.16b, {v0.16b-v2.16b}, v18.16b
+        tbl             v6.16b, {v0.16b-v2.16b}, v19.16b
+
+        mul             v3.8h, v3.8h, v20.8h
+        mul             v4.8h, v4.8h, v20.8h
+        shl             v6.8h, v6.8h, #4
+        ushr            v3.8h, v3.8h, #6
+        ushr            v4.8h, v4.8h, #6
+        ushr            v5.8h, v5.8h, #6
+        ushr            v6.8h, v6.8h, #6
+
+        st1             {v3.8h, v4.8h}, [x1], #32
+        str             q5, [x2], #16
+        str             q6, [x3], #16
+.endm
+
+// ff_bitpacked_unpack_yuv422p10_neon(const uint8_t *src,
+//                                    uint16_t *y, uint16_t *u, uint16_t *v,
+//                                    int width)
+function ff_bitpacked_unpack_yuv422p10_neon, export=1
+        movrel          x5, bitpacked_tbl
+        ld1             {v16.16b-v19.16b}, [x5], #64
+        ldr             q20, [x5]
+
+        subs            w4, w4, #32
+        b.lt            2f
+
+1:      unpack_16
+        unpack_16
+        subs            w4, w4, #32
+        b.ge            1b
+
+2:      add             w4, w4, #32
+        tbz             w4, #4, 3f
+        unpack_16
+        sub             w4, w4, #16
+
+3:      cbz             w4, 4f
+        ldr             q0, [x0], #16
+        ldr             s1, [x0]
+
+        tbl             v3.16b, {v0.16b-v1.16b}, v16.16b
+        tbl             v5.16b, {v0.16b-v1.16b}, v18.16b
+        tbl             v6.16b, {v0.16b-v1.16b}, v19.16b
+
+        mul             v3.8h, v3.8h, v20.8h
+        shl             v6.8h, v6.8h, #4
+        ushr            v3.8h, v3.8h, #6
+        ushr            v5.8h, v5.8h, #6
+        ushr            v6.8h, v6.8h, #6
+
+        str             q3, [x1]
+        str             d5, [x2]
+        str             d6, [x3]
+
+4:      ret
+endfunc
diff --git a/libavcodec/bitpacked_dec.h b/libavcodec/bitpacked_dec.h
index a92c94ab29..637f3789ef 100644
--- a/libavcodec/bitpacked_dec.h
+++ b/libavcodec/bitpacked_dec.h
@@ -36,4 +36,6 @@ struct BitpackedContext {
                              int width);
 };

+void ff_bitpacked_init_aarch64(struct BitpackedContext *s);
+
 #endif /* AVCODEC_BITPACKED_DEC_H */
diff --git a/libavcodec/bitpacked_dec_init.h b/libavcodec/bitpacked_dec_init.h
index a7a0d257c3..f0b19f04fa 100644
--- a/libavcodec/bitpacked_dec_init.h
+++ b/libavcodec/bitpacked_dec_init.h
@@ -46,6 +46,9 @@ av_unused static av_cold void ff_bitpackeddec_init(struct BitpackedContext *s)
 {
     s->unpack_yuv422p10 = bitpacked_unpack_yuv422p10_c;

+#if ARCH_AARCH64
+    ff_bitpacked_init_aarch64(s);
+#endif
 }

 #endif /* AVCODEC_BITPACKED_DEC_INIT_H */