|
| 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