Skip to content

Commit 06ac7bc

Browse files
lrgirdwoclaude
andcommitted
audio: ffmpeg_dec: add FFmpeg (libavcodec/libavfilter) module
Add a loadable (LLEXT) audio module that wraps FFmpeg. Two build modes: - decoder (default): a compressed elementary stream (FLAC/AAC/Opus, selected via Kconfig) is parsed and decoded to PCM (process_raw_data); - filter (CONFIG_FFMPEG_DEC_FILTER_MODE): a PCM source/sink effect that runs an FFmpeg audio filter graph (afftdn noise reduction). FFmpeg is a west-pinned source (see west.yml) cross-built by ffmpeg.cmake as a CMake ExternalProject, enabling only the Kconfig-selected decoders and filters. The module supplies the libc surface FFmpeg needs that the SOF core does not export to LLEXT (ffmpeg_dec-shims.c), a SOF-heap-backed malloc (ffmpeg_dec-alloc.c), fast single-precision float math (fastmathf.c), and routes av_log() into the Zephyr log. The avfilter graph backend and the PCM effect ops are in ffmpeg_dec-filter.c. Also adds the rimage module manifest (ffmpeg_dec.toml + per-platform includes), the topology widget (ffmpeg_dec.conf), host test tooling (ffmpeg_dec_prepare.sh) and docs (README/TESTING). Verified building and load-ready (all symbols resolved, signed) for ace30 (ptl) with the Zephyr SDK: FLAC and FLAC+AAC decoders, and the afftdn filter effect. Runtime decode/denoise verification is pending hardware. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6f1170c commit 06ac7bc

31 files changed

Lines changed: 2666 additions & 0 deletions

src/audio/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ if(NOT CONFIG_COMP_MODULE_SHARED_LIBRARY_BUILD)
4141
if(CONFIG_COMP_DRC)
4242
add_subdirectory(drc)
4343
endif()
44+
if(CONFIG_COMP_FFMPEG_DEC)
45+
add_subdirectory(ffmpeg_dec)
46+
endif()
4447
if(CONFIG_COMP_FIR)
4548
add_subdirectory(eq_fir)
4649
endif()

src/audio/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ rsource "dcblock/Kconfig"
139139
rsource "drc/Kconfig"
140140
rsource "eq_fir/Kconfig"
141141
rsource "eq_iir/Kconfig"
142+
rsource "ffmpeg_dec/Kconfig"
142143
rsource "google/Kconfig"
143144
rsource "igo_nr/Kconfig"
144145
rsource "mfcc/Kconfig"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
if(CONFIG_COMP_FFMPEG_DEC STREQUAL "m" AND DEFINED CONFIG_LLEXT)
4+
add_subdirectory(llext ${PROJECT_BINARY_DIR}/ffmpeg_dec_llext)
5+
add_dependencies(app ffmpeg_dec)
6+
else()
7+
add_local_sources(sof ffmpeg_dec.c)
8+
9+
if(CONFIG_COMP_FFMPEG_DEC_STUB)
10+
add_local_sources(sof ffmpeg_dec-stub.c)
11+
else()
12+
add_local_sources(sof ffmpeg_dec-ffmpeg.c)
13+
# NOTE: ffmpeg_dec-shims.c / ffmpeg_dec-alloc.c are LLEXT-only (see
14+
# llext/CMakeLists.txt). They provide libc/libm symbols the isolated LLEXT
15+
# cannot import; in a built-in (=y) or native/testbench build the real libc
16+
# provides those, so including them here would multiply-define malloc etc.
17+
endif()
18+
endif()

src/audio/ffmpeg_dec/Kconfig

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
3+
config COMP_FFMPEG_DEC
4+
tristate "FFmpeg (libavcodec) audio decoder"
5+
select COMP_FFMPEG_DEC_STUB if COMP_STUBS
6+
help
7+
Select to include the FFmpeg audio decoder module. It wraps
8+
libavcodec decoders behind the SOF module interface, decoding a
9+
compressed elementary stream to PCM. The real backend cross-builds
10+
libavcodec/libavutil/libswresample from the FFmpeg source pulled in
11+
by west (see west.yml), enabling only the decoders selected below.
12+
Without the source (or for CI) select COMP_FFMPEG_DEC_STUB to build
13+
the dependency-free passthrough stub.
14+
15+
config COMP_FFMPEG_DEC_STUB
16+
bool "FFmpeg decoder stub backend"
17+
depends on COMP_FFMPEG_DEC
18+
help
19+
Build the ffmpeg_dec module against a dependency-free passthrough
20+
backend instead of libavcodec. Intended for testing and CI so the
21+
SOF glue and LLEXT packaging can be validated without the FFmpeg
22+
source or a cross-built archive.
23+
24+
if COMP_FFMPEG_DEC && !COMP_FFMPEG_DEC_STUB
25+
26+
comment "FFmpeg decoders to build (footprint scales with selection)"
27+
28+
config FFMPEG_DEC_FLAC
29+
bool "FLAC decoder"
30+
default y
31+
help
32+
Lossless integer decoder. Smallest footprint, no floating-point
33+
math. Enables --enable-decoder=flac in the libavcodec build.
34+
35+
config FFMPEG_DEC_AAC
36+
bool "AAC-LC decoder"
37+
help
38+
AAC Low Complexity decoder. Pulls in the single-precision float
39+
math layer (see FFMPEG_DEC_FLOAT_MATH). Enables
40+
--enable-decoder=aac in the libavcodec build.
41+
42+
config FFMPEG_DEC_OPUS
43+
bool "Opus decoder"
44+
help
45+
Opus (SILK+CELT) decoder. Pulls in the single-precision float math
46+
layer. Enables --enable-decoder=opus in the libavcodec build.
47+
48+
comment "FFmpeg audio filters (libavfilter; need the avfilter-graph backend to run)"
49+
50+
config FFMPEG_FILTER_AFFTDN
51+
bool "FFT noise reduction (afftdn)"
52+
help
53+
Build FFmpeg's FFT-based denoiser (afftdn) into libavfilter. Pure DSP,
54+
no external model file (unlike arnndn). Enabling any filter turns on
55+
libavfilter in the cross-build. NOTE: a filter is only usable at runtime
56+
once the module gains an avfilter-graph backend; selecting it here just
57+
builds the filter into the archive.
58+
59+
config FFMPEG_DEC_FILTER_MODE
60+
bool "Build as a PCM filter effect instead of a decoder [EXPERIMENTAL]"
61+
select FFMPEG_FILTER_AFFTDN
62+
help
63+
Build the module as a PCM source->sink effect that runs an FFmpeg
64+
audio filter graph (default afftdn noise reduction) via the modern
65+
.process interface, instead of the compressed-stream decoder. Turns on
66+
libavfilter and the fast float math. Structurally complete and
67+
load-ready; real-time latency/format tuning needs on-hardware bring-up.
68+
69+
# Turns on libavfilter in the FFmpeg cross-build when any filter is selected.
70+
config FFMPEG_BUILD_AVFILTER
71+
bool
72+
default y if FFMPEG_FILTER_AFFTDN
73+
74+
# Selected automatically when a decoder or filter that needs IEEE float math is
75+
# built. Gates compilation of the fast single-precision libm (fastmathf.c).
76+
config FFMPEG_DEC_FLOAT_MATH
77+
bool
78+
default y if FFMPEG_DEC_AAC || FFMPEG_DEC_OPUS || FFMPEG_FILTER_AFFTDN
79+
80+
config FFMPEG_DEC_COLD_SPLIT
81+
bool "Place cold FFmpeg code (av_cold init/setup) in DRAM [EXPERIMENTAL]"
82+
depends on COLD_STORE_EXECUTE_DRAM
83+
default n
84+
help
85+
EXPERIMENTAL. Build libavcodec with -mtext-section-literals and rename
86+
FFmpeg's cold functions (its av_cold init/table-generation code, which GCC
87+
emits into .text.unlikely) into SOF's .cold section so the LLEXT loader
88+
places them in DRAM, keeping the hot per-frame decode in fast SRAM.
89+
90+
Measured cold code is small (~1 KB for FLAC, ~26 KB for AAC) because
91+
FFmpeg only marks init functions av_cold. Placing that much cold code in a
92+
-shared LLEXT currently hits Xtensa linker limits (cold->hot call8 range
93+
needs -mlongcalls; the orphan .cold lands at a bad LMA and overlaps .hash),
94+
so this is off by default pending proper cold-region placement in the LLEXT
95+
memory map. See git history / TESTING notes.
96+
97+
endif # COMP_FFMPEG_DEC && !COMP_FFMPEG_DEC_STUB

src/audio/ffmpeg_dec/README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# FFmpeg audio decoder module (ffmpeg_dec)
2+
3+
Wraps FFmpeg's `libavcodec` audio decoders behind the SOF `module_interface`,
4+
decoding a compressed elementary stream to PCM inside the DSP. First target
5+
codec is **FLAC**.
6+
7+
## Design
8+
9+
The SOF glue and the decoder are separated so the integration can be validated
10+
without libavcodec:
11+
12+
- `ffmpeg_dec.c` — SOF module core: `init` / `prepare` / `process_raw_data` /
13+
`set_configuration` / `reset` / `free`, plus the LLEXT manifest. Input is the
14+
compressed byte stream (raw data), output is interleaved PCM. Decoding is
15+
delegated to a `struct ffmpeg_dec_backend`.
16+
- `ffmpeg_dec-stub.c` — dependency-free passthrough backend. Lets CI build and
17+
exercise the module (pipeline, IPC config, LLEXT packaging) with **no** FFmpeg
18+
libraries. Selected by `CONFIG_COMP_FFMPEG_DEC_STUB`.
19+
- `ffmpeg_dec-ffmpeg.c` — real backend driving the standalone `libavcodec`
20+
send-packet / receive-frame API. A raw stream is framed on-DSP with an
21+
`AVCodecParser` (`av_parser_parse2`); decode is forced single-threaded
22+
(`thread_count = 1`). Requires pre-compiled static libraries and headers under
23+
`third_party/` (see below).
24+
- `ffmpeg_dec.h` — private data (`struct ffmpeg_dec_comp_data`) and the backend
25+
interface.
26+
27+
The codec setup header (e.g. FLAC STREAMINFO) is delivered as a binary control
28+
via `set_configuration` and stored as `extradata`, which the libavcodec backend
29+
installs before `avcodec_open2()`.
30+
31+
## Build
32+
33+
- **Stub (default for testing/CI):** `CONFIG_COMP_FFMPEG_DEC=y` (or `=m` for
34+
LLEXT) with `CONFIG_COMP_FFMPEG_DEC_STUB=y`. No external dependencies.
35+
- **Real decoder:** `CONFIG_COMP_FFMPEG_DEC=m`, `CONFIG_COMP_FFMPEG_DEC_STUB=n`.
36+
Requires cross-compiled decoder-only FFmpeg static libraries installed as:
37+
- `third_party/lib/libavcodec.a`, `libavutil.a`, `libswresample.a`
38+
- `third_party/include/libavcodec/…`, `libavutil/…`, `libswresample/…`
39+
Configure FFmpeg with `--disable-everything --disable-avformat
40+
--disable-pthreads --enable-decoder=flac --enable-parser=flac` (plus the
41+
target cross-compile flags).
42+
43+
## Files
44+
45+
- `Kconfig``COMP_FFMPEG_DEC` (tristate) and `COMP_FFMPEG_DEC_STUB`.
46+
- `CMakeLists.txt` / `llext/CMakeLists.txt` — static and LLEXT builds; the LLEXT
47+
target name is `ffmpeg_dec` and links the FFmpeg libraries in the non-stub
48+
branch.
49+
- `ffmpeg_dec.toml` / `llext/llext.toml.h` — rimage module manifest
50+
(`UUIDREG_STR_FFMPEG_DEC`); OBS is sized above IBS since PCM out ≫ compressed
51+
in.
52+
53+
## Status / TODO
54+
55+
- Codec id is currently hard-coded to FLAC in `init`; wire it from topology/IPC
56+
init config for other codecs.
57+
- Output is assumed to already match the sink sample format; add
58+
`libswresample` (or a fixed-point path) for format/rate conversion.
59+
- Topology and host test tooling for end-to-end (bit-exact) FLAC decode.

src/audio/ffmpeg_dec/TESTING.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Testing the ffmpeg_dec module
2+
3+
This describes how to verify the FFmpeg FLAC decoder module end to end. FLAC is
4+
lossless, so a correct decode is **bit-exact** against a reference decode.
5+
6+
## Test vectors
7+
8+
Generate vectors with the helper (needs `ffmpeg` on the host):
9+
10+
```
11+
tools/test/audio/ffmpeg_dec_prepare.sh -i song.wav -o vectors/
12+
```
13+
14+
Produces in `vectors/`:
15+
- `streaminfo.bin` — 34-byte FLAC STREAMINFO = the codec **extradata** blob to
16+
load into the module's bytes control (`set_configuration`).
17+
- `stream.raw` — the raw FLAC elementary stream fed to the decoder input.
18+
- `ref_s32le.raw` — reference interleaved S32_LE PCM; the decoder output must
19+
match this byte-for-byte.
20+
21+
## Topology
22+
23+
The component widget is `tools/topology/topology2/include/components/ffmpeg_dec.conf`
24+
(models the DTS codec: `type "effect"`, one input/one output pin, a `bytes`
25+
control for STREAMINFO). Its `uuid` must match the firmware's registry entry for
26+
`ffmpeg_dec` (see `uuid-registry.txt`).
27+
28+
A runnable pipeline must deliver **compressed bytes** to the module and take PCM
29+
out. Two paths:
30+
31+
1. **ALSA compress-offload** (the production path for a decoder): the host writes
32+
the FLAC elementary stream to a compress DMA gateway; the module decodes; PCM
33+
goes to a DAI or host capture. This is the correct model (a decoder is not a
34+
fixed-rate PCM effect) and is the remaining integration to wire on real ptl
35+
hardware.
36+
2. **Direct raw-data injection** (bring-up shortcut): a minimal pipeline where a
37+
host copier delivers `stream.raw` as an opaque byte buffer into the module's
38+
`process_raw_data`, capturing PCM on the other side.
39+
40+
## Verification paths
41+
42+
### A. On target (ptl hardware) — full verification
43+
1. Build + flash firmware with the real backend:
44+
`CONFIG_COMP_FFMPEG_DEC=m`, `CONFIG_COMP_FFMPEG_DEC_STUB=n` (requires the
45+
FFmpeg static libs in `third_party/`; see the module README).
46+
2. Load the topology; set the STREAMINFO bytes control:
47+
`amixer -c0 cset name='FFMPGDEC...' -- $(od -An -tx1 vectors/streaminfo.bin)`
48+
(or via `tinymix`).
49+
3. Play `stream.raw` through the pipeline, capture the PCM output.
50+
4. `cmp captured.raw vectors/ref_s32le.raw` — must be identical.
51+
52+
### B. Native testbench (no hardware) — decode-only check
53+
The module builds native (built-in, real libc — the LLEXT-only shims/alloc are
54+
excluded automatically, see `CMakeLists.txt`). Linking the **host** libavcodec
55+
and driving `process_raw_data` over `stream.raw` with `ref_s32le.raw` as the
56+
oracle gives a bit-exact check without a DSP. This harness is not yet wired (the
57+
testbench file component is PCM-oriented; feeding compressed bytes needs a small
58+
adapter) and is the recommended next step for CI-friendly verification.
59+
60+
## Status
61+
62+
- Component widget conf: **done**.
63+
- Host vector/reference tooling: **done** (`ffmpeg_dec_prepare.sh`).
64+
- Runnable pipeline (compress-offload) + on-target bit-exact run: **pending
65+
hardware**.
66+
- Native testbench harness: **pending** (small compressed-input adapter).

0 commit comments

Comments
 (0)