Skip to content

Commit fb9fd55

Browse files
lrgirdwoclaude
andcommitted
audio: ffmpeg_dec: document HiFi intrinsics opportunities
Add HIFI.md: analysis of the hot audio-processing paths in the module (decode/ filter/encode) and the linked FFmpeg DSP, and where Xtensa HiFi intrinsics would help. FFmpeg is built --disable-asm (scalar C on Xtensa) and has no _xtensa DSP init, so the generic C kernels run. Documents: the module's own PCM conversion loops (easy HiFi wins we own), FFmpeg's DSP dispatch contexts (float_dsp, flacdsp, mpegaudiodsp, tx) as fork-patch targets mirroring the other arch inits, fastmathf vectorisation, and a cost/benefit priority order. References SOF's existing HiFi kernels (src/math/*_hifi*) as the template. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 0712481 commit fb9fd55

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

src/audio/ffmpeg_dec/HIFI.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
# HiFi intrinsics opportunities in ffmpeg_dec
2+
3+
Analysis of the hot audio-processing paths in the FFmpeg SOF module (decode /
4+
filter / encode) and where Xtensa **HiFi** SIMD intrinsics would give the biggest
5+
wins. It covers both the code this module owns and the FFmpeg code it links.
6+
7+
## Background: why this matters on Xtensa
8+
9+
- The FFmpeg archive is cross-built with **`--disable-asm`** (see `ffmpeg.cmake`),
10+
because FFmpeg has no Xtensa assembly. Every DSP kernel therefore runs as
11+
**scalar C** on the DSP — no SIMD, no fused multiply-add vectorisation.
12+
- FFmpeg dispatches its DSP through function-pointer contexts with per-architecture
13+
init hooks (`ff_*_init_aarch64/arm/x86/riscv`) — but **no `_xtensa`** variant. The
14+
generic `_c` kernels are what execute on our target.
15+
- SOF already ships HiFi-optimised DSP and the toolchain for it. Use it as the
16+
template:
17+
- ISA select + intrinsics header (`src/math/exp_fcn_hifi.c`):
18+
```c
19+
#if XCHAL_HAVE_HIFI5
20+
#include <xtensa/tie/xt_hifi5.h>
21+
#elif XCHAL_HAVE_HIFI4
22+
#include <xtensa/tie/xt_hifi4.h>
23+
#else
24+
#include <xtensa/tie/xt_hifi3.h>
25+
#endif
26+
```
27+
- Existing HiFi kernels to mirror: `src/math/fir_hifi{2ep,3,5}.c`,
28+
`src/math/iir_df1_hifi{3,4,5}.c`, `src/math/fft/fft_{16,32}_hifi3.c`,
29+
`src/math/exp_fcn_hifi.c`. `ace30` (ptl) is HiFi4-class.
30+
31+
There are two distinct bodies of code:
32+
33+
- **(A) Module glue we own** — the PCM format-conversion loops in
34+
`ffmpeg_dec-*.c`. Small, per-sample, easy to hand-vectorise with HiFi, and we
35+
can change them freely.
36+
- **(B) FFmpeg DSP** — the actual codec compute (MDCT/FFT, LPC, windowing). Much
37+
higher cycle cost, but upstream; adding HiFi means either FFmpeg fork patches
38+
(an `_xtensa` DSP init, mirroring the other arch dirs) or routing to SOF's own
39+
HiFi kernels.
40+
41+
## Hot-path summary
42+
43+
| # | Path | File | Kind | Now | HiFi win | Effort |
44+
|---|------|------|------|-----|----------|--------|
45+
| 1 | MDCT/FFT (AAC/MP3/Opus/afftdn) | FFmpeg `libavutil/tx*` | float/fixed transform | scalar C | **very high** | high |
46+
| 2 | float vector kernels (fmul/window/overlap-add) | FFmpeg `libavutil/float_dsp` | float SIMD | scalar C | **high** | low–med |
47+
| 3 | FLAC LPC / residual | FFmpeg `libavcodec/flacdsp` | int32/64 MAC | scalar C | high | med |
48+
| 4 | MP3 synthesis window / imdct36 | FFmpeg `libavcodec/mpegaudiodsp` | float/fixed | scalar C | high | med |
49+
| 5 | AAC PS / SBR DSP | FFmpeg `aacpsdsp`/`sbrdsp` | float SIMD | scalar C | med (HE-AAC only) | med |
50+
| 6 | S32↔float PCM convert (filter) | `ffmpeg_dec-filter.c:230,255` | int↔float + scale | scalar C | med | **low** |
51+
| 7 | planar→interleaved PCM (decode) | `ffmpeg_dec-ffmpeg.c:231` | copy/interleave | scalar C | low–med | **low** |
52+
| 8 | S32→S16 pack (encode) | `ffmpeg_dec-encode.c:147` | narrow + interleave | scalar C | med | **low** |
53+
| 9 | fast float libm (powf/exp2f/...) | `fastmathf.c` | scalar poly/Newton | scalar C | med (per-band) | med |
54+
55+
Priority order (cost/benefit): **2 → 6/7/8 → 3 → 1 → 4 → 9 → 5**. Start with the
56+
easy, high-use float vector kernels (2) and the conversion loops we own (6–8);
57+
tackle the transform (1) last because it is the most work despite the highest
58+
raw cost.
59+
60+
## (A) Module conversion loops — we own these, do them first
61+
62+
These run **per sample over a whole frame** (typically 1024–4608 samples ×
63+
channels per call), so they are genuinely hot and are trivial, self-contained HiFi
64+
wins. Add HiFi versions guarded by `#if XCHAL_HAVE_HIFI*` with the current scalar
65+
loop as the `#else` fallback.
66+
67+
1. **Filter S32→float deinterleave + normalise**`ffmpeg_dec-filter.c:230`
68+
```c
69+
((float *)in->data[c])[i] = (float)src[i * ch + c] / FFMPEG_AF_S32_SCALE;
70+
```
71+
HiFi: load 32-bit lanes, convert int32→float (`AE_FLOAT32`-family), multiply by
72+
the reciprocal scale, store to the per-channel plane. Deinterleave with
73+
strided/`AE_SEL` moves.
74+
75+
2. **Filter float→S32 interleave + denormalise + clip**`ffmpeg_dec-filter.c:255`
76+
The scalar path multiplies, branches to clamp, then casts. HiFi does the
77+
float→int32 convert **with saturation** in one op (no per-sample branch), which
78+
is both faster and removes the mispredicted clamp branch.
79+
80+
3. **Encode S32→S16 narrow + deinterleave**`ffmpeg_dec-encode.c:147`
81+
```c
82+
int16_t s = (int16_t)(in[i * ch + c] >> 16);
83+
```
84+
HiFi: pack 32→16 with rounding (`AE_ROUND16X4F32`-style) and store; SIMD handles
85+
4–8 samples per iteration.
86+
87+
4. **Decode planar→interleaved**`ffmpeg_dec-ffmpeg.c:231`
88+
Per-element `memcpy`-equivalent; a HiFi interleave (vector load per plane,
89+
`AE_SEL` interleave, vector store) removes the per-sample loop overhead. Lower
90+
priority as many decoders already output the sink format.
91+
92+
A single small `ffmpeg_dec-convert.c` with HiFi + scalar variants (like SOF's
93+
`*_generic.c` / `*_hifi3.c` split) would hold all four and be reusable across the
94+
three modes.
95+
96+
## (B) FFmpeg DSP — the real compute
97+
98+
FFmpeg's DSP contexts are function-pointer tables initialised per arch. The
99+
Xtensa-idiomatic fix is a **fork patch** adding `ff_<dsp>_init_xtensa()` with HiFi
100+
intrinsics, mirroring `libavcodec/aarch64/`, `x86/` etc. (our FFmpeg is already a
101+
SOF fork — see `west.yml` — so such patches have a home).
102+
103+
- **`AVFloatDSPContext`** (`libavutil/float_dsp.h`) — `vector_fmul`,
104+
`vector_fmul_window`, `vector_fmul_add`, `vector_fmul_reverse`,
105+
`scalarproduct_float`. Used for windowing / overlap-add by **AAC, MP3 and Opus**.
106+
These are plain multiply/MAC over contiguous float arrays — the easiest and
107+
highest-leverage HiFi target (one small `float_dsp_init_xtensa` benefits three
108+
codecs). **Do this first on the FFmpeg side.**
109+
- **`FLACDSPContext`** (`libavcodec/flacdsp.h`) — `lpc16`/`lpc32`/`lpc33` and
110+
decorrelate. FLAC decode is dominated by the LPC prediction MAC loop over the
111+
residual: a natural fit for HiFi multiply-accumulate. `ff_flacdsp_init_xtensa`.
112+
- **`MPADSPContext`** (`libavcodec/mpegaudiodsp.h`) — `apply_window_{float,fixed}`,
113+
`imdct36_blocks_*`, `synth_filter`. The MP3 subband synthesis window is the MP3
114+
decode hot loop.
115+
- **`libavutil/tx`** (MDCT/FFT) — the single largest cost for AAC/MP3/Opus decode
116+
and for the `afftdn` filter. It is a "codelet" system rather than one function
117+
pointer, so it is the most work. Two routes:
118+
1. Add HiFi FFT/MDCT codelets to the fork (largest effort, cleanest for FFmpeg).
119+
2. Bridge to **SOF's existing HiFi3 FFT** (`src/math/fft/fft_*_hifi3.c`) by
120+
replacing FFmpeg's transform behind `av_tx_init` for the sizes SOF supports.
121+
Less code, but a semantic bridge (twiddle/scaling/layout must match) and only
122+
covers power-of-two sizes.
123+
- **`PSDSPContext` / SBR DSP** — only relevant if HE-AAC (SBR/PS) is enabled; skip
124+
unless needed.
125+
126+
## (C) fastmathf — the module's float libm
127+
128+
`fastmathf.c` (`powf`/`exp2f`/`log2f`/`sinf`/`cosf`/`sqrtf`/`cbrtf`) is scalar
129+
polynomial/Newton code. These are called per-band / per-frame (not the innermost
130+
per-sample loop), so they are a secondary target. HiFi wins come from:
131+
- `sqrtf`/`cbrtf`: HiFi `RSQRT`/reciprocal seed instructions instead of the
132+
bit-trick + Newton.
133+
- `exp2f`/`log2f`/`sinf`/`cosf`: process 2/4 lanes at once when a decoder needs a
134+
vector of them (e.g. AAC scalefactor gains), and use FMA for the polynomials.
135+
Mirror `src/math/exp_fcn_hifi.c`.
136+
137+
## Recommendations
138+
139+
1. **Quick wins we own** — hand-vectorise the four conversion loops (§A) with
140+
`#if XCHAL_HAVE_HIFI*` + scalar fallback. Small, self-contained, no fork needed.
141+
2. **Biggest FFmpeg leverage-per-effort** — add `ff_float_dsp_init_xtensa` (HiFi
142+
`vector_fmul*`/`scalarproduct`) to the FFmpeg fork; benefits AAC + MP3 + Opus.
143+
3. **Per-codec kernels**`flacdsp` (FLAC), `mpegaudiodsp` (MP3) HiFi inits as
144+
fork patches, gated on the enabled decoders.
145+
4. **Transform** — evaluate SOF-HiFi-FFT bridge vs. HiFi tx codelets for
146+
`libavutil/tx`; highest payoff, most work, do once the cheaper items land.
147+
5. Always keep the scalar C path as the fallback (`#else`) and behind the same ISA
148+
guards SOF uses, so non-HiFi / host/testbench builds still work.
149+
150+
Measurement: profile on hardware per codec before and after; the ranking above is
151+
by expected cost, but the actual hot spot depends on the codec and content (FLAC
152+
is LPC-bound, AAC/MP3/Opus are transform+window-bound).

0 commit comments

Comments
 (0)