Skip to content

Commit 44aca99

Browse files
committed
Generic PFA C implementation based on Neon asm
Used AI agent to write a C implementation of the PFA code, including forward/backward MDCT and FFT
1 parent e343df3 commit 44aca99

21 files changed

Lines changed: 5322 additions & 5422 deletions

‎CMakeLists.txt‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ set(OPUS_OSCE_HELP_STR "enable OSCE.")
104104
option(OPUS_OSCE ${OPUS_OSCE_HELP_STR} OFF)
105105
add_feature_info(OPUS_OSCE OPUS_OSCE ${OPUS_OSCE_HELP_STR})
106106

107+
set(OPUS_PFA_HELP_STR "enable PFA FFT/MDCT (experimental).")
108+
option(OPUS_PFA ${OPUS_PFA_HELP_STR} OFF)
109+
add_feature_info(OPUS_PFA OPUS_PFA ${OPUS_PFA_HELP_STR})
110+
107111
if(APPLE)
108112
set(OPUS_BUILD_FRAMEWORK_HELP_STR "build Framework bundle for Apple systems.")
109113
option(OPUS_BUILD_FRAMEWORK ${OPUS_BUILD_FRAMEWORK_HELP_STR} OFF)
@@ -431,6 +435,10 @@ if (OPUS_OSCE)
431435
target_compile_definitions(opus PRIVATE ENABLE_OSCE ENABLE_OSCE_BWE)
432436
endif()
433437

438+
if (OPUS_PFA)
439+
target_compile_definitions(opus PRIVATE ENABLE_PFA)
440+
endif()
441+
434442
if(NOT OPUS_DISABLE_INTRINSICS)
435443
if(((OPUS_X86_MAY_HAVE_SSE AND NOT OPUS_X86_PRESUME_SSE) OR
436444
(OPUS_X86_MAY_HAVE_SSE2 AND NOT OPUS_X86_PRESUME_SSE2) OR

‎Makefile.am‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ tests_test_opus_custom_SOURCES = tests/test_opus_custom.c tests/test_opus_common
226226
tests_test_opus_custom_LDADD = libopus.la $(LIBM)
227227
endif
228228

229-
CELT_OBJ = $(CELT_SOURCES:.c=.lo)
229+
CELT_OBJ_TEMP = $(CELT_SOURCES:.c=.lo)
230+
CELT_OBJ = $(CELT_OBJ_TEMP:.S=.lo)
230231
SILK_OBJ = $(SILK_SOURCES:.c=.lo)
231232
LPCNET_OBJ = $(LPCNET_SOURCES:.c=.lo)
232233
OPUS_OBJ = $(OPUS_SOURCES:.c=.lo)

‎celt/arm/celt_mdct_tx.c‎

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ void celt_tx_mdct_inv_float_neon(const OpusTXContext *s, void *out, void *in, pt
108108
/* M-point power-of-two sub-FFTs. The PFA kernel reads len and map (the
109109
split-radix parity scatter map); the preshuffled kernels themselves read
110110
nothing (fft_sr additionally reads len). */
111-
static const OpusTXContext celt_tx_p2_4 = { 4, 1, celt_tx_p2_map_4, NULL, NULL, NULL, NULL };
112-
static const OpusTXContext celt_tx_p2_8 = { 8, 1, celt_tx_p2_map_8, NULL, NULL, NULL, NULL };
113-
static const OpusTXContext celt_tx_p2_16 = { 16, 1, celt_tx_p2_map_16, NULL, NULL, NULL, NULL };
114-
static const OpusTXContext celt_tx_p2_32 = { 32, 1, celt_tx_p2_map_32, NULL, NULL, NULL, NULL };
111+
static const OpusTXContext celt_tx_p2_4 = { 4, 1, celt_tx_p2_map_4, NULL, NULL, NULL, celt_tx_fft4_fwd_float_neon };
112+
static const OpusTXContext celt_tx_p2_8 = { 8, 1, celt_tx_p2_map_8, NULL, NULL, NULL, celt_tx_fft8_ns_float_neon };
113+
static const OpusTXContext celt_tx_p2_16 = { 16, 1, celt_tx_p2_map_16, NULL, NULL, NULL, celt_tx_fft16_ns_float_neon };
114+
static const OpusTXContext celt_tx_p2_32 = { 32, 1, celt_tx_p2_map_32, NULL, NULL, NULL, celt_tx_fft32_ns_float_neon };
115115
#if defined(ENABLE_QEXT)
116-
static const OpusTXContext celt_tx_p2_64 = { 64, 1, celt_tx_p2_map_64, NULL, NULL, NULL, NULL };
116+
static const OpusTXContext celt_tx_p2_64 = { 64, 1, celt_tx_p2_map_64, NULL, NULL, NULL, celt_tx_fft_sr_ns_float_neon };
117117
#endif
118118

119119
/* 15xM PFA sub-FFTs; .tmp is filled in per call with stack scratch. */
@@ -139,23 +139,23 @@ static const OpusTXContext celt_tx_sr_512 = { 512, 1, NULL, NULL, NULL, NULL, NU
139139

140140
/* Inverse MDCT roots; for the 15*M sizes .sub is relinked per call to the
141141
stack copy of the PFA context, the power-of-two ones run fully const. */
142-
static const OpusTXContext celt_tx_mdct_120 = { 120, 1, celt_tx_mdct_map_120, (const void *)celt_tx_mdct_exp_120, NULL, &celt_tx_pfa_60, celt_tx_fft_pfa_15xM_ns_float_neon };
143-
static const OpusTXContext celt_tx_mdct_240 = { 240, 1, celt_tx_mdct_map_240, (const void *)celt_tx_mdct_exp_240, NULL, &celt_tx_pfa_120, celt_tx_fft_pfa_15xM_ns_float_neon };
144-
static const OpusTXContext celt_tx_mdct_480 = { 480, 1, celt_tx_mdct_map_480, (const void *)celt_tx_mdct_exp_480, NULL, &celt_tx_pfa_240, celt_tx_fft_pfa_15xM_ns_float_neon };
145-
static const OpusTXContext celt_tx_mdct_960 = { 960, 1, celt_tx_mdct_map_960, (const void *)celt_tx_mdct_exp_960, NULL, &celt_tx_pfa_480, celt_tx_fft_pfa_15xM_ns_float_neon };
142+
static const OpusTXContext celt_tx_mdct_120 = { 120, 1, celt_tx_mdct_map_120, NULL, NULL, &celt_tx_pfa_60, celt_tx_fft_pfa_15xM_ns_float_neon };
143+
static const OpusTXContext celt_tx_mdct_240 = { 240, 1, celt_tx_mdct_map_240, NULL, NULL, &celt_tx_pfa_120, celt_tx_fft_pfa_15xM_ns_float_neon };
144+
static const OpusTXContext celt_tx_mdct_480 = { 480, 1, celt_tx_mdct_map_480, NULL, NULL, &celt_tx_pfa_240, celt_tx_fft_pfa_15xM_ns_float_neon };
145+
static const OpusTXContext celt_tx_mdct_960 = { 960, 1, celt_tx_mdct_map_960, NULL, NULL, &celt_tx_pfa_480, celt_tx_fft_pfa_15xM_ns_float_neon };
146146
#if defined(ENABLE_QEXT)
147-
static const OpusTXContext celt_tx_mdct_1920 = { 1920, 1, celt_tx_mdct_map_1920, (const void *)celt_tx_mdct_exp_1920, NULL, &celt_tx_pfa_960, celt_tx_fft_pfa_15xM_ns_float_neon };
147+
static const OpusTXContext celt_tx_mdct_1920 = { 1920, 1, celt_tx_mdct_map_1920, NULL, NULL, &celt_tx_pfa_960, celt_tx_fft_pfa_15xM_ns_float_neon };
148148
#endif
149149

150150
#if defined(CUSTOM_MODES)
151-
static const OpusTXContext celt_tx_mdct_64 = { 64, 1, celt_tx_mdct_map_64, (const void *)celt_tx_mdct_exp_64, NULL, &celt_tx_sr_32, celt_tx_fft32_ns_float_neon };
152-
static const OpusTXContext celt_tx_mdct_128 = { 128, 1, celt_tx_mdct_map_128, (const void *)celt_tx_mdct_exp_128, NULL, &celt_tx_sr_64, celt_tx_fft_sr_ns_float_neon };
153-
static const OpusTXContext celt_tx_mdct_256 = { 256, 1, celt_tx_mdct_map_256, (const void *)celt_tx_mdct_exp_256, NULL, &celt_tx_sr_128, celt_tx_fft_sr_ns_float_neon };
154-
static const OpusTXContext celt_tx_mdct_512 = { 512, 1, celt_tx_mdct_map_512, (const void *)celt_tx_mdct_exp_512, NULL, &celt_tx_sr_256, celt_tx_fft_sr_ns_float_neon };
155-
static const OpusTXContext celt_tx_mdct_1024 = { 1024, 1, celt_tx_mdct_map_1024, (const void *)celt_tx_mdct_exp_1024, NULL, &celt_tx_sr_512, celt_tx_fft_sr_ns_float_neon };
151+
static const OpusTXContext celt_tx_mdct_64 = { 64, 1, celt_tx_mdct_map_64, NULL, NULL, &celt_tx_sr_32, celt_tx_fft32_ns_float_neon };
152+
static const OpusTXContext celt_tx_mdct_128 = { 128, 1, celt_tx_mdct_map_128, NULL, NULL, &celt_tx_sr_64, celt_tx_fft_sr_ns_float_neon };
153+
static const OpusTXContext celt_tx_mdct_256 = { 256, 1, celt_tx_mdct_map_256, NULL, NULL, &celt_tx_sr_128, celt_tx_fft_sr_ns_float_neon };
154+
static const OpusTXContext celt_tx_mdct_512 = { 512, 1, celt_tx_mdct_map_512, NULL, NULL, &celt_tx_sr_256, celt_tx_fft_sr_ns_float_neon };
155+
static const OpusTXContext celt_tx_mdct_1024 = { 1024, 1, celt_tx_mdct_map_1024, NULL, NULL, &celt_tx_sr_512, celt_tx_fft_sr_ns_float_neon };
156156
#endif
157157

158-
static const OpusTXContext *celt_tx_mdct_kernel(int len)
158+
const OpusTXContext *celt_tx_mdct_kernel(int len)
159159
{
160160
switch (len) {
161161
case 120: return &celt_tx_mdct_120;
@@ -185,6 +185,10 @@ void clt_mdct_backward_tx(const mdct_lookup *l, kiss_fft_scalar *in,
185185
int N = l->n >> shift;
186186
int N2 = N >> 1;
187187
const OpusTXContext *tpl = celt_tx_mdct_kernel(N2);
188+
const kiss_twiddle_scalar *trig;
189+
int cur_n;
190+
VARDECL(kiss_fft_scalar, tmp);
191+
SAVE_STACK;
188192

189193
if (tpl == NULL)
190194
{
@@ -194,28 +198,37 @@ void clt_mdct_backward_tx(const mdct_lookup *l, kiss_fft_scalar *in,
194198
}
195199
(void)arch;
196200

201+
trig = l->trig;
202+
cur_n = l->n;
203+
for (i = 0; i < shift; i++) {
204+
cur_n >>= 1;
205+
trig += cur_n;
206+
}
207+
197208
/* Pre-rotate, N/4 complex FFT and post-rotate, writing the raw N/2
198209
samples to out[overlap/2 ..] like the C code does. For the 15*M sizes,
199210
only the PFA scratch buffer is written through the context tree, so
200211
the two levels that reference it are copied to the stack; the mode
201212
data stays shared and read-only across threads. The power-of-two
202213
sizes run their sub-FFT in place on the output and need no fixup. */
214+
ALLOC(tmp, N2, kiss_fft_scalar); /* N/4 complex values */
215+
203216
if (tpl->fn == celt_tx_fft_pfa_15xM_ns_float_neon)
204217
{
205218
OpusTXContext mdct, pfa;
206-
VARDECL(kiss_fft_scalar, tmp);
207-
SAVE_STACK;
208-
ALLOC(tmp, N2, kiss_fft_scalar); /* N/4 complex values */
209-
210219
mdct = *tpl;
220+
mdct.exp = trig;
221+
mdct.tmp = tmp;
211222
pfa = *tpl->sub;
212223
pfa.tmp = tmp;
213224
mdct.sub = &pfa;
214225
celt_tx_mdct_inv_float_neon(&mdct, out+(overlap>>1), in,
215226
(ptrdiff_t)stride*sizeof(kiss_fft_scalar));
216-
RESTORE_STACK;
217227
} else {
218-
celt_tx_mdct_inv_float_neon(tpl, out+(overlap>>1), in,
228+
OpusTXContext mdct = *tpl;
229+
mdct.exp = trig;
230+
mdct.tmp = tmp;
231+
celt_tx_mdct_inv_float_neon(&mdct, out+(overlap>>1), in,
219232
(ptrdiff_t)stride*sizeof(kiss_fft_scalar));
220233
}
221234

@@ -237,6 +250,7 @@ void clt_mdct_backward_tx(const mdct_lookup *l, kiss_fft_scalar *in,
237250
wp2--;
238251
}
239252
}
253+
RESTORE_STACK;
240254
}
241255

242256
#endif /* OPUS_ARM_TX_MDCT */

‎celt/arm/celt_neon_aarch64.S‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
/* Copyright (c) 2026 Lynne */
22
/* Ported and relicensed from FFmpeg, libavcodec/aarch64/opusdsp_neon.S */
3+
#ifdef HAVE_CONFIG_H
4+
#include "config.h"
5+
#endif
36
/*
47
Redistribution and use in source and binary forms, with or without
58
modification, are permitted provided that the following conditions

0 commit comments

Comments
 (0)