#!/usr/bin/env bash set -Eeuo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" # shellcheck source=lib.sh . "${SCRIPT_DIR}/lib.sh" LOCK_FILE="${RKMEDIA_ROOT}/sources.lock.json" WORK_DIR="${RKMEDIA_ROOT}/out/work" SOURCE_DIR="${RKMEDIA_ROOT}/out/sources" OUTPUT_DIR="${RKMEDIA_ROOT}/out" JOBS="$(nproc)" FETCH=1 PACKAGE_VERSION="$(tr -d '[:space:]' < "${RKMEDIA_ROOT}/VERSION")" RUNTIME_PREFIX=/opt/rkmedia usage() { cat <<'EOF' Usage: build-media.sh [options] --source-dir DIR Locked source checkouts (default: out/sources) --work-dir DIR Build work directory (default: out/work) --output-dir DIR Package output directory (default: out) --jobs N Parallel jobs (default: nproc) --no-fetch Require sources to exist; never access the network --version VERSION Debian package version (default: VERSION file) EOF } while (($#)); do case "$1" in --source-dir) [[ $# -ge 2 ]] || die "--source-dir requires a directory"; SOURCE_DIR="$2"; shift 2 ;; --work-dir) [[ $# -ge 2 ]] || die "--work-dir requires a directory"; WORK_DIR="$2"; shift 2 ;; --output-dir) [[ $# -ge 2 ]] || die "--output-dir requires a directory"; OUTPUT_DIR="$2"; shift 2 ;; --jobs) [[ $# -ge 2 ]] || die "--jobs requires a number"; JOBS="$2"; shift 2 ;; --no-fetch) FETCH=0; shift ;; --version) [[ $# -ge 2 ]] || die "--version requires a value"; PACKAGE_VERSION="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) die "unknown argument: $1" ;; esac done [[ "${JOBS}" =~ ^[1-9][0-9]*$ ]] || die "--jobs must be a positive integer" [[ "${PACKAGE_VERSION}" =~ ^[0-9A-Za-z.+:~-]+$ ]] || die "invalid Debian package version: ${PACKAGE_VERSION}" require_arm64_trixie for command_name in cmake git jq meson ninja pkg-config make patch readelf strip; do need_command "${command_name}" done SOURCE_DIR="$(absolute_path "${SOURCE_DIR}")" WORK_DIR="$(absolute_path "${WORK_DIR}")" OUTPUT_DIR="$(absolute_path "${OUTPUT_DIR}")" mkdir -p -- "${SOURCE_DIR}" "${WORK_DIR}" "${OUTPUT_DIR}" if ((FETCH)); then "${SCRIPT_DIR}/fetch-sources.sh" --lock "${LOCK_FILE}" --dest "${SOURCE_DIR}" fi while IFS=$'\t' read -r name commit; do dir="${SOURCE_DIR}/${name}" [[ -d "${dir}/.git" ]] || die "missing locked source: ${dir}" [[ "$(git -C "${dir}" rev-parse HEAD)" == "${commit}" ]] || die "source commit mismatch: ${name}" done < <(jq -r '.sources[] | select(.build == true) | [.name, .commit] | @tsv' "${LOCK_FILE}") BUILD_ROOT="${WORK_DIR}/build" BUILD_PREFIX="${WORK_DIR}/prefix" STAGE_DIR="${WORK_DIR}/stage" reset_child_dir "${WORK_DIR}" "${BUILD_ROOT}" reset_child_dir "${WORK_DIR}" "${BUILD_PREFIX}" reset_child_dir "${WORK_DIR}" "${STAGE_DIR}" export SOURCE_DATE_EPOCH SOURCE_DATE_EPOCH="$(jq -er '.source_date_epoch' "${LOCK_FILE}")" export CFLAGS="${CFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}" export CXXFLAGS="${CXXFLAGS:--O2 -pipe -ffile-prefix-map=${WORK_DIR}=. -fdebug-prefix-map=${WORK_DIR}=.}" export LDFLAGS="${LDFLAGS:-}" log "building MPP" cmake -S "${SOURCE_DIR}/mpp" -B "${BUILD_ROOT}/mpp" -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="${BUILD_PREFIX}" \ -DCMAKE_INSTALL_LIBDIR=lib \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TEST=OFF cmake --build "${BUILD_ROOT}/mpp" --parallel "${JOBS}" cmake --install "${BUILD_ROOT}/mpp" log "building RGA" meson setup "${BUILD_ROOT}/rga" "${SOURCE_DIR}/rga" \ --prefix="${BUILD_PREFIX}" \ --libdir=lib \ --buildtype=release \ --default-library=shared \ -Dcpp_args=-fpermissive \ -Dlibdrm=false \ -Dlibrga_demo=false meson compile -C "${BUILD_ROOT}/rga" -j "${JOBS}" meson install -C "${BUILD_ROOT}/rga" log "building dav1d" meson setup "${BUILD_ROOT}/dav1d" "${SOURCE_DIR}/dav1d" \ --prefix="${BUILD_PREFIX}" \ --libdir=lib \ --buildtype=release \ --default-library=shared \ -Denable_tools=false \ -Denable_tests=false \ -Denable_examples=false \ -Denable_docs=false meson compile -C "${BUILD_ROOT}/dav1d" -j "${JOBS}" meson install -C "${BUILD_ROOT}/dav1d" export PKG_CONFIG_PATH="${BUILD_PREFIX}/lib/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}" export LD_LIBRARY_PATH="${BUILD_PREFIX}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}" log "building FFmpeg-Rockchip" mkdir -p -- "${BUILD_ROOT}/ffmpeg" pushd "${BUILD_ROOT}/ffmpeg" >/dev/null "${SOURCE_DIR}/ffmpeg-rockchip/configure" \ --prefix="${BUILD_PREFIX}" \ --libdir="${BUILD_PREFIX}/lib" \ --enable-shared \ --disable-static \ --disable-debug \ --disable-doc \ --enable-pic \ --enable-gpl \ --enable-version3 \ --enable-libdrm \ --enable-rkmpp \ --enable-rkrga \ --enable-libdav1d \ --extra-cflags="-I${BUILD_PREFIX}/include" \ --extra-ldflags="-L${BUILD_PREFIX}/lib -Wl,-rpath,${RUNTIME_PREFIX}/lib" make -j "${JOBS}" make install popd >/dev/null FFMPEG_BUILD="${BUILD_PREFIX}/bin/ffmpeg" for decoder in h264_rkmpp hevc_rkmpp vp9_rkmpp av1_rkmpp mjpeg_rkmpp; do "${FFMPEG_BUILD}" -hide_banner -decoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${decoder}" \ || die "FFmpeg is missing decoder ${decoder}" done for encoder in h264_rkmpp hevc_rkmpp mjpeg_rkmpp; do "${FFMPEG_BUILD}" -hide_banner -encoders 2>/dev/null | awk '{print $2}' | grep -Fxq "${encoder}" \ || die "FFmpeg is missing encoder ${encoder}" done for filter in scale_rkrga vpp_rkrga overlay_rkrga; do "${FFMPEG_BUILD}" -hide_banner -filters 2>/dev/null | awk '{print $2}' | grep -Fxq "${filter}" \ || die "FFmpeg is missing filter ${filter}" done log "building libdisplay-info" meson setup "${BUILD_ROOT}/libdisplay-info" "${SOURCE_DIR}/libdisplay-info" \ --prefix="${BUILD_PREFIX}" \ --libdir=lib \ --buildtype=release \ --default-library=shared meson compile -C "${BUILD_ROOT}/libdisplay-info" -j "${JOBS}" meson install -C "${BUILD_ROOT}/libdisplay-info" log "building Kodi for GBM/GLES with external FFmpeg-Rockchip" cmake -S "${SOURCE_DIR}/kodi" -B "${BUILD_ROOT}/kodi" -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX="${RUNTIME_PREFIX}" \ -DCMAKE_INSTALL_RPATH="${RUNTIME_PREFIX}/lib" \ -DCMAKE_BUILD_RPATH="${BUILD_PREFIX}/lib" \ -DCMAKE_PREFIX_PATH="${BUILD_PREFIX}" \ -DCORE_PLATFORM_NAME=gbm \ -DAPP_RENDER_SYSTEM=gles \ -DENABLE_INTERNAL_FFMPEG=OFF \ -DFFMPEG_PATH="${BUILD_PREFIX}" \ -DENABLE_INTERNAL_FMT=ON \ -DENABLE_INTERNAL_FLATBUFFERS=ON cmake --build "${BUILD_ROOT}/kodi" --parallel "${JOBS}" mkdir -p -- "${STAGE_DIR}${RUNTIME_PREFIX}" cp -a -- "${BUILD_PREFIX}/." "${STAGE_DIR}${RUNTIME_PREFIX}/" DESTDIR="${STAGE_DIR}" cmake --install "${BUILD_ROOT}/kodi" log "adding runtime integration and source provenance" cp -a -- "${RKMEDIA_ROOT}/packaging/rootfs/." "${STAGE_DIR}/" mkdir -p -- "${STAGE_DIR}/usr/bin" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses" ln -sfn /opt/rkmedia/bin/ffmpeg-rk "${STAGE_DIR}/usr/bin/ffmpeg-rk" ln -sfn /opt/rkmedia/bin/ffprobe-rk "${STAGE_DIR}/usr/bin/ffprobe-rk" ln -sfn /opt/rkmedia/bin/kodi-rk "${STAGE_DIR}/usr/bin/kodi-rk" install -m 0644 "${LOCK_FILE}" "${STAGE_DIR}/usr/share/doc/rk1-media-stack/sources.lock.json" while IFS=$'\t' read -r name license_path; do source_license="${SOURCE_DIR}/${name}/${license_path}" destination="${STAGE_DIR}/usr/share/doc/rk1-media-stack/licenses/${name}" [[ -e "${source_license}" ]] || die "locked license path is missing: ${name}/${license_path}" mkdir -p -- "${destination}" cp -a -- "${source_license}" "${destination}/" done < <(jq -r '.sources[] | select(.build == true) as $source | $source.license_files[] | [$source.name, .] | @tsv' "${LOCK_FILE}") rm -rf -- \ "${STAGE_DIR}${RUNTIME_PREFIX}/include" \ "${STAGE_DIR}${RUNTIME_PREFIX}/lib/pkgconfig" \ "${STAGE_DIR}${RUNTIME_PREFIX}/share/pkgconfig" find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f \( -name '*.a' -o -name '*.la' \) -delete while IFS= read -r -d '' candidate; do if file --brief "${candidate}" | grep -q '^ELF '; then strip --strip-unneeded "${candidate}" || die "failed to strip ${candidate}" fi done < <(find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -print0) for required in \ "${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffmpeg" \ "${STAGE_DIR}${RUNTIME_PREFIX}/bin/ffprobe" \ "${STAGE_DIR}${RUNTIME_PREFIX}/bin/kodi" \ "${STAGE_DIR}/usr/lib/systemd/system/kodi-rk.service"; do [[ -e "${required}" ]] || die "staged runtime file is missing: ${required}" done if find "${STAGE_DIR}${RUNTIME_PREFIX}" -type f -exec grep -IlF "${WORK_DIR}" {} + | grep -q .; then die "build path leaked into a staged text file" fi "${SCRIPT_DIR}/package-media.sh" \ --stage "${STAGE_DIR}" \ --output-dir "${OUTPUT_DIR}" \ --version "${PACKAGE_VERSION}" log "media build complete"