Skip to content

Commit 06e1cfc

Browse files
committed
audio: ffmpeg_dec: add root Kconfig and standalone cmake helper
Add the root Kconfig that exposes the ffmpeg_dec module configuration tree (codec selection, filter mode, cold-split experimental option) and the standalone ffmpeg.cmake cross-build helper for out-of-tree builds. These files were omitted from the earlier ffmpeg_dec commits and are grouped here to keep all ffmpeg_dec build system pieces together. Signed-off-by: Liam Girdwood <liam.r.girdwood@linux.intel.com>
1 parent 46e6b20 commit 06e1cfc

2 files changed

Lines changed: 307 additions & 3 deletions

File tree

Kconfig

Lines changed: 108 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,112 @@
11
# SPDX-License-Identifier: BSD-3-Clause
22

3-
mainmenu "SOF $(PROJECTVERSION) Configuration"
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.
414

5-
comment "Compiler: $(CC_VERSION_TEXT)"
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.
623

7-
source "Kconfig.sof"
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+
config FFMPEG_DEC_MP3
49+
bool "MP3 decoder"
50+
help
51+
MPEG-1/2 Layer III decoder. Enables --enable-decoder=mp3 and the
52+
mpegaudio parser in the libavcodec build. Uses the float math layer.
53+
54+
config FFMPEG_ENC_MP3
55+
bool "MP3 encoder (libshine, fixed-point)"
56+
help
57+
MPEG-1 Layer III encoder via libshine, a small fixed-point encoder
58+
(west-pinned; FFmpeg has no native MP3 encoder). Cross-builds libshine
59+
and enables --enable-libshine --enable-encoder=libshine. Fixed-point, so
60+
no float math dependency. The encoder is built into the archive; a module
61+
encode path (PCM->MP3) is a separate build mode.
62+
63+
comment "FFmpeg audio filters (libavfilter; need the avfilter-graph backend to run)"
64+
65+
config FFMPEG_FILTER_AFFTDN
66+
bool "FFT noise reduction (afftdn)"
67+
help
68+
Build FFmpeg's FFT-based denoiser (afftdn) into libavfilter. Pure DSP,
69+
no external model file (unlike arnndn). Enabling any filter turns on
70+
libavfilter in the cross-build. NOTE: a filter is only usable at runtime
71+
once the module gains an avfilter-graph backend; selecting it here just
72+
builds the filter into the archive.
73+
74+
config FFMPEG_DEC_FILTER_MODE
75+
bool "Build as a PCM filter effect instead of a decoder [EXPERIMENTAL]"
76+
select FFMPEG_FILTER_AFFTDN
77+
help
78+
Build the module as a PCM source->sink effect that runs an FFmpeg
79+
audio filter graph (default afftdn noise reduction) via the modern
80+
.process interface, instead of the compressed-stream decoder. Turns on
81+
libavfilter and the fast float math. Structurally complete and
82+
load-ready; real-time latency/format tuning needs on-hardware bring-up.
83+
84+
# Turns on libavfilter in the FFmpeg cross-build when any filter is selected.
85+
config FFMPEG_BUILD_AVFILTER
86+
bool
87+
default y if FFMPEG_FILTER_AFFTDN
88+
89+
# Selected automatically when a decoder or filter that needs IEEE float math is
90+
# built. Gates compilation of the fast single-precision libm (fastmathf.c).
91+
config FFMPEG_DEC_FLOAT_MATH
92+
bool
93+
default y if FFMPEG_DEC_AAC || FFMPEG_DEC_OPUS || FFMPEG_DEC_MP3 || FFMPEG_FILTER_AFFTDN
94+
95+
config FFMPEG_DEC_COLD_SPLIT
96+
bool "Place cold FFmpeg code (av_cold init/setup) in DRAM [EXPERIMENTAL]"
97+
depends on COLD_STORE_EXECUTE_DRAM
98+
default n
99+
help
100+
EXPERIMENTAL. Build libavcodec with -mtext-section-literals and rename
101+
FFmpeg's cold functions (its av_cold init/table-generation code, which GCC
102+
emits into .text.unlikely) into SOF's .cold section so the LLEXT loader
103+
places them in DRAM, keeping the hot per-frame decode in fast SRAM.
104+
105+
Measured cold code is small (~1 KB for FLAC, ~26 KB for AAC) because
106+
FFmpeg only marks init functions av_cold. Placing that much cold code in a
107+
-shared LLEXT currently hits Xtensa linker limits (cold->hot call8 range
108+
needs -mlongcalls; the orphan .cold lands at a bad LMA and overlaps .hash),
109+
so this is off by default pending proper cold-region placement in the LLEXT
110+
memory map. See git history / TESTING notes.
111+
112+
endif # COMP_FFMPEG_DEC && !COMP_FFMPEG_DEC_STUB

ffmpeg.cmake

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# SPDX-License-Identifier: BSD-3-Clause
2+
#
3+
# Cross-build decoder-only FFmpeg static libraries for the ffmpeg_dec module.
4+
#
5+
# Invokes FFmpeg's autotools build as an ExternalProject, cross-compiled with the
6+
# Zephyr target toolchain, enabling only the decoders selected in Kconfig. The
7+
# source comes from the west-pinned FFmpeg project (see west.yml). Produces
8+
# libavcodec/libavutil/libswresample .a under ${FFMPEG_INSTALL_DIR} for the LLEXT
9+
# to link, and defines the 'ffmpeg_ext' target it must depend on.
10+
11+
include(ExternalProject)
12+
13+
# --- 1. Locate the FFmpeg source (west module), overridable for local trees ---
14+
if(NOT DEFINED SOF_FFMPEG_SRC_DIR)
15+
set(SOF_FFMPEG_SRC_DIR "${sof_top_dir}/../modules/audio/ffmpeg"
16+
CACHE PATH "FFmpeg source tree (west-pinned)")
17+
endif()
18+
cmake_path(NORMAL_PATH SOF_FFMPEG_SRC_DIR)
19+
if(NOT EXISTS "${SOF_FFMPEG_SRC_DIR}/configure")
20+
message(FATAL_ERROR
21+
"ffmpeg_dec: FFmpeg source not found at '${SOF_FFMPEG_SRC_DIR}'.\n"
22+
"Run 'west update' (FFmpeg is pinned in west.yml) or pass "
23+
"-DSOF_FFMPEG_SRC_DIR=<path-to-clean-ffmpeg-checkout>.")
24+
endif()
25+
26+
# --- 2. Kconfig -> --enable-decoder / --enable-parser lists ---
27+
set(_ff_decoders "")
28+
set(_ff_parsers "")
29+
if(CONFIG_FFMPEG_DEC_FLAC)
30+
list(APPEND _ff_decoders flac)
31+
list(APPEND _ff_parsers flac)
32+
endif()
33+
if(CONFIG_FFMPEG_DEC_AAC)
34+
list(APPEND _ff_decoders aac aac_latm)
35+
list(APPEND _ff_parsers aac aac_latm)
36+
endif()
37+
if(CONFIG_FFMPEG_DEC_OPUS)
38+
list(APPEND _ff_decoders opus)
39+
list(APPEND _ff_parsers opus)
40+
endif()
41+
if(CONFIG_FFMPEG_DEC_MP3)
42+
list(APPEND _ff_decoders mp3)
43+
list(APPEND _ff_parsers mpegaudio)
44+
endif()
45+
if(NOT _ff_decoders)
46+
message(FATAL_ERROR "ffmpeg_dec: no decoder selected (Kconfig FFMPEG_DEC_*)")
47+
endif()
48+
list(JOIN _ff_decoders "," _ff_dec_csv)
49+
list(JOIN _ff_parsers "," _ff_par_csv)
50+
51+
# --- 2b. Kconfig -> libavfilter audio filters (off unless a filter is chosen) ---
52+
set(_ff_avfilter_cfg --disable-avfilter)
53+
if(CONFIG_FFMPEG_BUILD_AVFILTER)
54+
set(_ff_filters "")
55+
if(CONFIG_FFMPEG_FILTER_AFFTDN)
56+
list(APPEND _ff_filters afftdn)
57+
endif()
58+
list(JOIN _ff_filters "," _ff_filt_csv)
59+
# abuffer/abuffersink feed/drain a filter graph; aformat negotiates format.
60+
set(_ff_avfilter_cfg
61+
--enable-avfilter
62+
--enable-filter=abuffer,abuffersink,aformat,${_ff_filt_csv})
63+
message(STATUS "ffmpeg_dec: enabling avfilter, filters [${_ff_filt_csv}]")
64+
endif()
65+
66+
# --- 3. Derive the cross toolchain prefix from the Zephyr target compiler ---
67+
# e.g. .../bin/xtensa-intel_ace30_ptl_zephyr-elf-gcc
68+
# -> prefix .../bin/xtensa-intel_ace30_ptl_zephyr-elf-
69+
get_filename_component(_tc_dir "${CMAKE_C_COMPILER}" DIRECTORY)
70+
get_filename_component(_tc_name "${CMAKE_C_COMPILER}" NAME)
71+
string(REGEX REPLACE "gcc$" "" _tc_prefix_name "${_tc_name}")
72+
set(_ff_cross_prefix "${_tc_dir}/${_tc_prefix_name}")
73+
74+
# GCC 14 (Zephyr SDK) promotes these to errors; FFmpeg 7.x trips them.
75+
set(_ff_extra_cflags "-fPIC -Wno-error=incompatible-pointer-types -Wno-error=implicit-function-declaration")
76+
77+
set(FFMPEG_INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/ffmpeg-install" CACHE INTERNAL "ffmpeg_dec libs")
78+
79+
# Hot/cold split: GCC emits av_cold (init/setup) functions into .text.unlikely.
80+
# We rename that to SOF's .cold (post-install, below) so the LLEXT loader places
81+
# it in DRAM. -mtext-section-literals interleaves each function's Xtensa L32R
82+
# literals into its text section (as SOF does for LLEXT module code) so the
83+
# renamed section carries its literals and L32R stays in range. We deliberately
84+
# do NOT use -ffunction-sections: keeping one .text.unlikely per object lets a
85+
# single objcopy --rename-section catch it, and lets .cold be placed by SOF's
86+
# existing orphan .cold handling (a hand-written linker script that forces .cold
87+
# ahead of .text overruns the SRAM budget and overlaps .rodata for big codecs).
88+
if(CONFIG_FFMPEG_DEC_COLD_SPLIT)
89+
# -mlongcalls: cold code lives in DRAM (.cold, relocated by the loader) far
90+
# from the hot SRAM .text, so calls between them exceed call8 range; longcalls
91+
# emit an indirect L32R+CALLX with unlimited range. -mtext-section-literals
92+
# keeps each function's literals in its own text section so the rename carries
93+
# them. Coarse split: rename the whole FFmpeg .text (not just av_cold) into
94+
# .cold so essentially all of libav* executes from DRAM, freeing SRAM.
95+
set(_ff_extra_cflags "${_ff_extra_cflags} -mtext-section-literals -mlongcalls")
96+
set(_ff_objcopy "${_ff_cross_prefix}objcopy")
97+
set(_ff_cold_rename
98+
COMMAND ${_ff_objcopy} --rename-section .text=.cold
99+
${FFMPEG_INSTALL_DIR}/lib/libavcodec.a
100+
COMMAND ${_ff_objcopy} --rename-section .text=.cold
101+
${FFMPEG_INSTALL_DIR}/lib/libavutil.a
102+
COMMAND ${_ff_objcopy} --rename-section .text=.cold
103+
${FFMPEG_INSTALL_DIR}/lib/libswresample.a)
104+
endif()
105+
106+
# --- 3b. MP3 encoder via libshine (fixed-point; FFmpeg has no native one) ---
107+
set(_ff_cfg_env "PATH=${_tc_dir}:$ENV{PATH}")
108+
set(_ff_shine_cfg "")
109+
set(_ff_shine_dep "")
110+
if(CONFIG_FFMPEG_ENC_MP3)
111+
set(SHINE_SRC "${sof_top_dir}/../modules/audio/shine")
112+
if(NOT EXISTS "${SHINE_SRC}/src/lib/layer3.h")
113+
message(FATAL_ERROR "ffmpeg_dec: libshine source not at '${SHINE_SRC}'; run 'west update'")
114+
endif()
115+
set(SHINE_INSTALL "${CMAKE_CURRENT_BINARY_DIR}/shine-install")
116+
file(MAKE_DIRECTORY "${SHINE_INSTALL}/lib/pkgconfig" "${SHINE_INSTALL}/include/shine")
117+
# pkg-config file for FFmpeg's require_pkg_config libshine.
118+
file(WRITE "${SHINE_INSTALL}/lib/pkgconfig/shine.pc"
119+
"prefix=${SHINE_INSTALL}
120+
libdir=\${prefix}/lib
121+
includedir=\${prefix}/include
122+
Name: shine
123+
Description: Shine fixed-point MP3 encoder
124+
Version: 3.1.1
125+
Libs: -L\${libdir} -lshine
126+
Cflags: -I\${includedir}
127+
")
128+
# FFmpeg's -lshine link *test* pulls newlib malloc -> Zephyr runtime syms
129+
# (z_errno_wrap, ...) that only exist at module load. Provide dummies so the
130+
# configure test links; --extra-ldflags only affects tests, not the .a build.
131+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/shine_cfgstub.c"
132+
"int z_errno_wrap(void){return 0;}
133+
void *stdout;
134+
int open(const char *p, int f, ...){return -1;}
135+
void *sbrk(int i){return (void *)-1;}
136+
")
137+
# libshine's lib needs no config.h; build the objects directly and archive
138+
# (its autotools CLI/shared link cannot work bare-metal).
139+
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/shine-build.sh"
140+
"#!/bin/sh
141+
set -e
142+
export PATH=${_tc_dir}:\$PATH
143+
mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/shine-obj
144+
for f in ${SHINE_SRC}/src/lib/*.c; do
145+
${CMAKE_C_COMPILER} -O2 -fPIC -mtext-section-literals -DSHINE_HAVE_BSWAP_H \\
146+
-I${SHINE_SRC}/src/lib -c \"\$f\" \\
147+
-o ${CMAKE_CURRENT_BINARY_DIR}/shine-obj/\$(basename \"\$f\").o
148+
done
149+
${_ff_cross_prefix}ar rcs ${SHINE_INSTALL}/lib/libshine.a ${CMAKE_CURRENT_BINARY_DIR}/shine-obj/*.o
150+
cp ${SHINE_SRC}/src/lib/layer3.h ${SHINE_INSTALL}/include/shine/layer3.h
151+
${CMAKE_C_COMPILER} -fPIC -c ${CMAKE_CURRENT_BINARY_DIR}/shine_cfgstub.c \\
152+
-o ${CMAKE_CURRENT_BINARY_DIR}/shine_cfgstub.o
153+
")
154+
add_custom_command(
155+
OUTPUT "${SHINE_INSTALL}/lib/libshine.a" "${CMAKE_CURRENT_BINARY_DIR}/shine_cfgstub.o"
156+
COMMAND sh "${CMAKE_CURRENT_BINARY_DIR}/shine-build.sh"
157+
VERBATIM)
158+
add_custom_target(shine_ext DEPENDS "${SHINE_INSTALL}/lib/libshine.a")
159+
160+
set(_ff_cfg_env "PATH=${_tc_dir}:$ENV{PATH}" "PKG_CONFIG_PATH=${SHINE_INSTALL}/lib/pkgconfig")
161+
set(_ff_shine_cfg --enable-libshine --enable-encoder=libshine --pkg-config=pkg-config
162+
"--extra-ldflags=${CMAKE_CURRENT_BINARY_DIR}/shine_cfgstub.o")
163+
set(_ff_shine_dep shine_ext)
164+
message(STATUS "ffmpeg_dec: enabling MP3 encoder via libshine (${SHINE_SRC})")
165+
endif()
166+
167+
# --- 4. Configure + make + install (out-of-tree; source must be clean) ---
168+
ExternalProject_Add(ffmpeg_ext
169+
DEPENDS ${_ff_shine_dep}
170+
SOURCE_DIR "${SOF_FFMPEG_SRC_DIR}"
171+
BUILD_IN_SOURCE 0
172+
CONFIGURE_COMMAND
173+
${CMAKE_COMMAND} -E env ${_ff_cfg_env}
174+
<SOURCE_DIR>/configure
175+
--prefix=${FFMPEG_INSTALL_DIR}
176+
--enable-cross-compile --target-os=none --arch=xtensa
177+
--cross-prefix=${_ff_cross_prefix} --cc=${CMAKE_C_COMPILER}
178+
--disable-asm --disable-all --disable-everything --disable-autodetect
179+
--disable-programs --disable-doc --disable-network
180+
--disable-avformat --disable-avdevice ${_ff_avfilter_cfg}
181+
--disable-swscale --disable-postproc
182+
--disable-pthreads --disable-w32threads --disable-os2threads
183+
--disable-runtime-cpudetect --disable-debug
184+
--enable-avcodec --enable-avutil --enable-swresample
185+
--enable-decoder=${_ff_dec_csv} --enable-parser=${_ff_par_csv}
186+
${_ff_shine_cfg}
187+
--enable-small --enable-pic
188+
"--extra-cflags=${_ff_extra_cflags}"
189+
BUILD_COMMAND
190+
${CMAKE_COMMAND} -E env "PATH=${_tc_dir}:$ENV{PATH}" make -j 8
191+
INSTALL_COMMAND make install
192+
${_ff_cold_rename}
193+
BUILD_BYPRODUCTS
194+
${FFMPEG_INSTALL_DIR}/lib/libavcodec.a
195+
${FFMPEG_INSTALL_DIR}/lib/libavutil.a
196+
${FFMPEG_INSTALL_DIR}/lib/libswresample.a
197+
)
198+
199+
message(STATUS "ffmpeg_dec: cross-building FFmpeg decoders [${_ff_dec_csv}] from ${SOF_FFMPEG_SRC_DIR}")

0 commit comments

Comments
 (0)