From fef4bbbfb3ae3a6596990448edcf0686805a88c6 Mon Sep 17 00:00:00 2001 From: "haijun.li" Date: Wed, 15 Jul 2026 21:55:49 +0800 Subject: [PATCH 3/7] chromium: add HW video decode browser build recipe [1/1] SWPL-269930 Add Chromium 132 browser with V4L2 hardware video decoding and DMA-BUF zero-copy for AML A311Y3. - build-chromium.sh: fetch, patch, build, package deb - 17-file minimal patch for V4L2/EGL/format support - gpu_mojo_media_client_amlogic.cc: VideoDecoderPipeline - systemd service for decoder DW mode setup - Controlled by BUILD_UNITS chromium option Change-Id: I555c9394e5e3ec7788a716c3b8eb9ce5fa3d92f2 Signed-off-by: haijun.li --- config/boards/include/amlogic_common.inc | 1 + extensions/amlogic-overlay.sh | 3 + lib/function-img.sh | 12 + scripts/chromium/build-chromium.sh | 446 ++++++++++++ scripts/chromium/config/args.gn | 29 + .../services/gpu_mojo_media_client_amlogic.cc | 77 ++ .../patches/minimal-hwdecode-final.patch | 660 ++++++++++++++++++ 7 files changed, 1228 insertions(+) create mode 100755 scripts/chromium/build-chromium.sh create mode 100644 scripts/chromium/config/args.gn create mode 100644 scripts/chromium/overlay/media/mojo/services/gpu_mojo_media_client_amlogic.cc create mode 100644 scripts/chromium/patches/minimal-hwdecode-final.patch diff --git a/config/boards/include/amlogic_common.inc b/config/boards/include/amlogic_common.inc index 554a39b..5407654 100644 --- a/config/boards/include/amlogic_common.inc +++ b/config/boards/include/amlogic_common.inc @@ -69,6 +69,7 @@ BUILD_UNITS=( screencapture linux-uapi-headers mediactrlsrc + chromium hdmi-test ) else diff --git a/extensions/amlogic-overlay.sh b/extensions/amlogic-overlay.sh index cc4b30f..9c1555e 100644 --- a/extensions/amlogic-overlay.sh +++ b/extensions/amlogic-overlay.sh @@ -252,6 +252,9 @@ function load_local_source() { display_alert "AMLOGIC" ">> Start build logcat " "info" source "${EXTENSION_DIR}/../scripts/devtools/android-tools-logcat/build-logcat.sh" + display_alert "AMLOGIC" ">> Start build chromium " "info" + source "${EXTENSION_DIR}/../scripts/chromium/build-chromium.sh" + if ! is_unit_enabled "rootfs"; then echo "rootfs NOT in BUILD_UNITS, exit immediately" exit 1 diff --git a/lib/function-img.sh b/lib/function-img.sh index 56b7a70..89c9806 100644 --- a/lib/function-img.sh +++ b/lib/function-img.sh @@ -358,6 +358,16 @@ function install_linux_uapi_deb { install_aml_deb $linux_uapi_headers_file } +function install_chromium_amlbian() { + if ! is_unit_enabled "chromium" 2>/dev/null; then + return 0 + fi + local chromium_file=$(find "${BUILD_DEBS}" -name "chromium-browser-aml_*.deb" 2>/dev/null | head -1) + if [ -n "$chromium_file" ]; then + install_aml_deb $chromium_file + fi +} + function install_common() { display_alert "install amlogic debs: GPU/NPU/DEC/ENC/... ... " "IMG" "info" @@ -408,6 +418,8 @@ function install_common() { install_linux_uapi_deb install_vendor_file_deb + install_chromium_amlbian + install_tvserver_deb install_logcat_deb diff --git a/scripts/chromium/build-chromium.sh b/scripts/chromium/build-chromium.sh new file mode 100755 index 0000000..d2a2488 --- /dev/null +++ b/scripts/chromium/build-chromium.sh @@ -0,0 +1,446 @@ +#!/usr/bin/env bash +# +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (c) 2026 AMLOGIC +# +# Chromium browser build script for AMLbian +# Hardware video decoding (V4L2) + DMA-BUF zero-copy +# +# Controlled by BUILD_UNITS: add "chromium" to enable + +# NOTE: set -e is NOT used here. This script modifies PATH/env and runs in a +# subshell (line ~450) to isolate side-effects from the parent build system. +# Error handling is explicit (|| return 1) throughout. + +# ============================================================================== +# Section 1: Default Configuration +# ============================================================================== + +CHROMIUM_VERSION="132.0.6834.83" +DEB_VERSION="${CHROMIUM_VERSION}-aml1" +DEB_NAME="chromium-browser-aml" + +# Get script directory (scripts/chromium/ — two levels below AMLBIAN_DIR) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +AMLBIAN_DIR="$(dirname "$(dirname "${SCRIPT_DIR}")")" +PATCHES_DIR="${SCRIPT_DIR}/patches" +OVERLAY_DIR="${SCRIPT_DIR}/overlay" +CONFIG_DIR="${SCRIPT_DIR}/config" + +# Source common functions if available and not already loaded +if [[ -z "${__FUNCTION_COMMON_LOADED:-}" ]] && [[ -f "${AMLBIAN_DIR}/lib/function-common.sh" ]]; then + source "${AMLBIAN_DIR}/lib/function-common.sh" +fi + +: "${CHROMIUM_BUILD_ROOT:=${AMLBIAN_DIR}/../chromium}" +: "${DEPOT_TOOLS_DIR:=${CHROMIUM_BUILD_ROOT}/depot_tools}" +# CHROMIUM_DL_DIR: optional env var (Jenkins node config or manual). +# Tarball search order: CHROMIUM_DL_DIR → CHROMIUM_MIRROR_DIR → local cache → CDN. +: "${CHROMIUM_MIRROR_DIR:=/mnt/fileroot/Public/mirror}" +CHROMIUM_SRC="${CHROMIUM_BUILD_ROOT}/chromium-${CHROMIUM_VERSION}" +OUT_DIR="${CHROMIUM_SRC}/out/arm64" +# Armbian sets SRC to the build dir. BUILD_DEBS must match ${SRC}/output/ +# which is where install_aml_deb looks for debs in Docker. +if [[ -n "${SRC}" ]] && [[ -d "${SRC}/output" ]]; then + BUILD_DEBS="${SRC}/output" +else + BUILD_DEBS="${AMLBIAN_DIR}/../output" +fi + +: "${NINJA_JOBS:=$(nproc)}" + +# Fallback display_alert for standalone testing +if ! command -v display_alert &>/dev/null; then + display_alert() { echo "[$2] $1 ${3:+($3)}"; } +fi + +# ============================================================================== +# Section 2: Internal Helper +# ============================================================================== + +# Find a file in multiple directories. Sets REPLY to the first hit. +function __chromium_find_in_dirs() { + local filename="$1"; shift + REPLY="" + local dir + for dir in "$@"; do + [[ -z "${dir}" ]] && continue + if [[ -f "${dir}/${filename}" ]]; then + REPLY="${dir}/${filename}" + return 0 + fi + done + return 1 +} + +# ============================================================================== +# Section 3: Build Functions +# ============================================================================== + +function __chromium_check_enabled() { + # When running standalone (no BUILD_UNITS), always build + if ! command -v is_unit_enabled &>/dev/null; then + return 0 + fi + if ! is_unit_enabled "chromium"; then + display_alert "CHROMIUM" "Not in BUILD_UNITS, skipping" "info" + return 1 + fi + return 0 +} + +function __chromium_fetch_source() { + display_alert "CHROMIUM" "Fetching source ${CHROMIUM_VERSION}" "info" + + mkdir -p "${CHROMIUM_BUILD_ROOT}" + + # If source dir exists but is incomplete (any state: empty, only out/, partial), + # remove it so we get a clean extraction. + if [[ -d "${CHROMIUM_SRC}" ]] && [[ ! -f "${CHROMIUM_SRC}/.gn" ]]; then + display_alert "CHROMIUM" "Incomplete source tree at ${CHROMIUM_SRC} — removing" "wrn" + rm -rf "${CHROMIUM_SRC}" + fi + + # Source already complete + if [[ -f "${CHROMIUM_SRC}/.gn" ]]; then + display_alert "CHROMIUM" "Source complete at ${CHROMIUM_SRC}" "info" + return 0 + fi + + # Find tarball: CHROMIUM_DL_DIR (Jenkins) → mirror → local cache + local tarball="chromium-${CHROMIUM_VERSION}.tar.xz" + local tarball_path="" + + if __chromium_find_in_dirs "${tarball}" \ + "${CHROMIUM_DL_DIR}" \ + "${CHROMIUM_MIRROR_DIR}" \ + "${CHROMIUM_BUILD_ROOT}"; then + tarball_path="${REPLY}" + display_alert "CHROMIUM" "Found tarball: ${tarball_path}" "info" + else + # Last resort: download from Google CDN + tarball_path="${CHROMIUM_BUILD_ROOT}/${tarball}" + display_alert "CHROMIUM" "Downloading ${tarball} from Google CDN (6.4 GB) ..." "info" + if ! wget -q --show-progress \ + "https://commondatastorage.googleapis.com/chromium-browser-official/${tarball}" \ + -O "${tarball_path}"; then + display_alert "CHROMIUM" "ERROR: download failed" "err" + rm -f "${tarball_path}" + return 1 + fi + fi + + display_alert "CHROMIUM" "Extracting ${tarball_path} ..." "info" + if ! tar -xf "${tarball_path}" -C "${CHROMIUM_BUILD_ROOT}"; then + display_alert "CHROMIUM" "ERROR: tar extraction failed" "err" + rm -rf "${CHROMIUM_SRC}" + return 1 + fi + + if [[ ! -f "${CHROMIUM_SRC}/.gn" ]]; then + display_alert "CHROMIUM" "ERROR: .gn missing after extraction (CHROMIUM_SRC=${CHROMIUM_SRC})" "err" + return 1 + fi + display_alert "CHROMIUM" "Source ready at ${CHROMIUM_SRC}" "info" +} + +function __chromium_apply_patches() { + display_alert "CHROMIUM" "Applying patches" "info" + cd "${CHROMIUM_SRC}" || { display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err"; return 1; } + + # Apply minimal HW decode patch (use patch -p1, source tree has no git repo) + if [[ -f "${PATCHES_DIR}/minimal-hwdecode-final.patch" ]]; then + if patch -p1 --dry-run -s < "${PATCHES_DIR}/minimal-hwdecode-final.patch" 2>/dev/null; then + # --batch: never ask interactively (CI safe) + patch -p1 --batch < "${PATCHES_DIR}/minimal-hwdecode-final.patch" || \ + { display_alert "CHROMIUM" "ERROR: patch failed" "err"; return 1; } + display_alert "CHROMIUM" "Patch applied" "info" + else + display_alert "CHROMIUM" "Patch already applied or conflict, skipping" "info" + fi + fi + + # Copy overlay files + if [[ -d "${OVERLAY_DIR}" ]]; then + cp -rv "${OVERLAY_DIR}/." "${CHROMIUM_SRC}/" + display_alert "CHROMIUM" "Overlay applied" "info" + fi +} + +function __chromium_configure() { + display_alert "CHROMIUM" "Configuring build" "info" + + # Force system Python — depot_tools bundled Python has wrong prefix (/work/out) + export PATH="/usr/bin:${DEPOT_TOOLS_DIR}:${PATH}" + unset PYTHONHOME PYTHONPATH + export GN_ARGS_USE_SYSTEM_PYTHON=1 + export VPYTHON_BYPASS="manually managed vpython not supported by nickel" + export DEPOT_TOOLS_UPDATE=0 + + cd "${CHROMIUM_SRC}" || { + display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err" + return 1 + } + # Resolve canonical path — CHROMIUM_SRC may contain ".." when sourced by Armbian + # (e.g. amlbian/../chromium/...). gn fails to find .gn with ".." in OUT_DIR. + local src_abs + src_abs="$(pwd)" + local out_abs="${src_abs}/out/arm64" + + if [[ ! -f "${src_abs}/.gn" ]]; then + display_alert "CHROMIUM" "ERROR: .gn not found at ${src_abs} — source tree missing" "err" + return 1 + fi + + # Toolchains: clang, rust, sysroot. + # The source tarball doesn't include these. Strategy: + # 1. Reuse from existing build tree at ${CHROMIUM_BUILD_ROOT}/src/ (same chromium version) + # 2. Download from Google CDN via update scripts + local existing_src="${CHROMIUM_BUILD_ROOT}/src" + local clang_dir="${src_abs}/third_party/llvm-build" + local rust_dir="${src_abs}/third_party/rust-toolchain" + local sysroot_dir="${src_abs}/build/linux/debian_bullseye_arm64-sysroot" + + # Expected clang revision (used to verify correct version) + local expected_clang_rev + expected_clang_rev=$(grep "^CLANG_REVISION = " "${src_abs}/tools/clang/scripts/update.py" | cut -d"'" -f2) + + # --- Clang --- + if ! grep -q "${expected_clang_rev}" "${clang_dir}/Release+Asserts/cr_build_revision" 2>/dev/null; then + if [[ -d "${existing_src}/third_party/llvm-build/Release+Asserts/bin" ]] && \ + grep -q "${expected_clang_rev}" "${existing_src}/third_party/llvm-build/Release+Asserts/cr_build_revision" 2>/dev/null; then + display_alert "CHROMIUM" "Linking clang from existing build" "info" + rm -rf "${clang_dir}" + ln -snf "${existing_src}/third_party/llvm-build" "${clang_dir}" + else + display_alert "CHROMIUM" "Downloading clang via update.py" "info" + python3 "${src_abs}/tools/clang/scripts/update.py" || { + display_alert "CHROMIUM" "ERROR: clang setup failed" "err"; return 1; } + fi + fi + + # --- Rust --- + if [[ ! -x "${rust_dir}/bin/rustc" ]]; then + if [[ -x "${existing_src}/third_party/rust-toolchain/bin/rustc" ]]; then + display_alert "CHROMIUM" "Linking rust from existing build" "info" + rm -rf "${rust_dir}" + ln -snf "${existing_src}/third_party/rust-toolchain" "${rust_dir}" + else + display_alert "CHROMIUM" "Downloading rust via update_rust.py" "info" + python3 "${src_abs}/tools/rust/update_rust.py" || { + display_alert "CHROMIUM" "ERROR: rust setup failed" "err"; return 1; } + fi + fi + + # --- Sysroot (arm64 cross-compilation) --- + if [[ ! -d "${sysroot_dir}" ]]; then + if [[ -d "${existing_src}/build/linux/debian_bullseye_arm64-sysroot" ]]; then + display_alert "CHROMIUM" "Linking arm64 sysroot from existing build" "info" + ln -snf "${existing_src}/build/linux/debian_bullseye_arm64-sysroot" "${sysroot_dir}" + else + display_alert "CHROMIUM" "Installing arm64 sysroot via install-sysroot.py" "info" + python3 "${src_abs}/build/linux/sysroot_scripts/install-sysroot.py" --arch=arm64 || { + display_alert "CHROMIUM" "ERROR: sysroot installation failed" "err"; return 1; } + fi + fi + + mkdir -p "${out_abs}" + cp "${CONFIG_DIR}/args.gn" "${out_abs}/args.gn" + + local gn_bin="${src_abs}/buildtools/linux64/gn" + if [[ ! -x "${gn_bin}" ]]; then + display_alert "CHROMIUM" "ERROR: gn not found at ${gn_bin}" "err" + return 1 + fi + "${gn_bin}" gen "${out_abs}" || { + display_alert "CHROMIUM" "ERROR: gn gen failed" "err" + return 1 + } + display_alert "CHROMIUM" "Configured: ${out_abs}" "info" +} + +function __chromium_compile() { + display_alert "CHROMIUM" "Building (jobs=${NINJA_JOBS})" "info" + + cd "${CHROMIUM_SRC}" || { display_alert "CHROMIUM" "ERROR: Cannot cd to ${CHROMIUM_SRC}" "err"; return 1; } + local src_abs + src_abs="$(pwd)" + "${src_abs}/third_party/ninja/ninja" -C "${src_abs}/out/arm64" -j "${NINJA_JOBS}" chrome || { + display_alert "CHROMIUM" "ERROR: ninja build failed" "err" + return 1 + } + display_alert "CHROMIUM" "Build complete" "info" +} + +function __chromium_package_deb() { + display_alert "CHROMIUM" "Packaging ${DEB_NAME} ${DEB_VERSION}" "info" + + mkdir -p "${BUILD_DEBS}" + local pkg_root="${BUILD_DEBS}/.chromium-pkg-$$" + rm -rf "${pkg_root}" + mkdir -p "${pkg_root}" + local install_dir="${pkg_root}/usr/lib/chromium" + local bin_dir="${pkg_root}/usr/bin" + local app_dir="${pkg_root}/usr/share/applications" + local limits_dir="${pkg_root}/etc/security/limits.d" + local profile_dir="${pkg_root}/etc/profile.d" + + mkdir -p "${install_dir}/locales" "${bin_dir}" "${app_dir}" \ + "${limits_dir}" "${profile_dir}" + + local src + src="$(realpath "${OUT_DIR}")" + display_alert "CHROMIUM" "Packaging from: ${src}" "info" + + # Binary + resources + if [[ ! -f "${src}/chrome" ]]; then + display_alert "CHROMIUM" "ERROR: ${src}/chrome not found!" "err" + return 1 + fi + install -m 755 "${src}/chrome" "${install_dir}/chrome" + for f in "${src}"/icudtl.dat "${src}"/*.pak "${src}"/*.bin; do + [[ -f "${f}" ]] && install -m 644 "${f}" "${install_dir}/" + done + # ANGLE libraries (required for GPU process) + for f in "${src}"/libGLESv2.so "${src}"/libEGL.so; do + [[ -f "${f}" ]] && install -m 644 "${f}" "${install_dir}/" + done + # Crashpad handler + [[ -f "${src}/chrome_crashpad_handler" ]] && \ + install -m 755 "${src}/chrome_crashpad_handler" "${install_dir}/" + [[ -f "${src}/locales/en-US.pak" ]] && \ + install -m 644 "${src}/locales/en-US.pak" "${install_dir}/locales/" + + # Launcher script + cat > "${bin_dir}/chromium-browser" << 'LAUNCHER' +#!/bin/sh +export PULSE_SERVER=unix:/run/user/1000/pulse/native +ulimit -l unlimited 2>/dev/null +exec /usr/lib/chromium/chrome \ + --ozone-platform=wayland --no-sandbox --use-gl=angle --use-angle=gles \ + --ignore-gpu-blocklist --disable-gpu-process-crash-limit \ + --disable-features=WebGPU \ + --enable-features=AcceleratedVideoDecodeLinux,AcceleratedVideoDecodeLinuxGL,ChromeOSHWAV1Decoder \ + --double-buffer-compositing --disable-partial-raster --lang=en-US \ + "$@" 2>/tmp/chrome_stderr.log +LAUNCHER + chmod 755 "${bin_dir}/chromium-browser" + + # Desktop entry + cat > "${app_dir}/chromium-browser.desktop" << 'DESKTOP' +[Desktop Entry] +Version=1.0 +Name=Chromium Browser +Exec=chromium-browser %U +Terminal=false +Type=Application +Icon=chromium-browser +Categories=Network;WebBrowser; +MimeType=text/html;text/xml;application/xhtml+xml; +DESKTOP + + # memlock limit + echo "* - memlock unlimited" > "${limits_dir}/chromium-memlock.conf" + + # HW decode sysfs setup (runs at login) + cat > "${profile_dir}/aml-hwdecode.sh" << 'HWDEC' +#!/bin/sh +for m in amvdec_vp9 amvdec_vp9_v4l amvdec_h265 amvdec_h265_v4l amvdec_av1 amvdec_av1_v4l; do + echo "double_write_mode=16" > /sys/module/$m/parameters/params 2>/dev/null +done +echo performance > /sys/devices/platform/fd000000.avalon/devfreq/fd000000.avalon/governor 2>/dev/null +HWDEC + chmod 644 "${profile_dir}/aml-hwdecode.sh" + + # Systemd service for DW mode + GPU freq (needs root) + local svc_dir="${pkg_root}/etc/systemd/system" + mkdir -p "${svc_dir}" + cat > "${svc_dir}/chromium-hwdecode.service" << 'SVC' +[Unit] +Description=Chromium HW decode setup (DW mode + GPU freq) +After=multi-user.target + +[Service] +Type=oneshot +ExecStart=/bin/sh -c 'for m in amvdec_mh264 amvdec_mh264_v4l amvdec_vp9 amvdec_vp9_v4l amvdec_h265 amvdec_h265_v4l amvdec_av1 amvdec_av1_v4l; do echo "double_write_mode=16" > /sys/module/$m/parameters/params 2>/dev/null; done; echo performance > /sys/devices/platform/fd000000.avalon/devfreq/fd000000.avalon/governor 2>/dev/null' +RemainAfterExit=yes + +[Install] +WantedBy=multi-user.target +SVC + + # Enable service via postinst + mkdir -p "${pkg_root}/DEBIAN" + cat > "${pkg_root}/DEBIAN/postinst" << 'POSTINST' +#!/bin/sh +systemctl daemon-reload +systemctl enable chromium-hwdecode.service +systemctl start chromium-hwdecode.service 2>/dev/null || true +POSTINST + chmod 755 "${pkg_root}/DEBIAN/postinst" + + # DEBIAN control + cat > "${pkg_root}/DEBIAN/control" << EOF +Package: ${DEB_NAME} +Version: ${DEB_VERSION} +Architecture: arm64 +Maintainer: AMLbian +Depends: libc6, libglib2.0-0, libnss3, libatk1.0-0, libatk-bridge2.0-0, libcups2, libdrm2, libgbm1, libpango-1.0-0, libcairo2, libasound2, libxkbcommon0, libwayland-client0, pipewire-pulse | pulseaudio, aml-libgles-eabihf-avalon-wayland-drm +Description: Chromium ${CHROMIUM_VERSION} with AML HW video decode + V4L2 hardware decoding with DMA-BUF zero-copy for Amlogic SoCs. + Supports H.264, VP9, HEVC up to 4K@60fps. +EOF + + # Build deb + mkdir -p "${BUILD_DEBS}" + local deb_file="${BUILD_DEBS}/${DEB_NAME}_${DEB_VERSION}_arm64.deb" + dpkg-deb --build --root-owner-group "${pkg_root}" "${deb_file}" + rm -rf "${pkg_root}" + + display_alert "CHROMIUM" "Package: ${deb_file}" "info" + ls -lh "${deb_file}" +} + +# ============================================================================== +# Section 4: Main Build Function +# ============================================================================== + +function amlbian_build_chromium() { + __chromium_check_enabled || return 0 + + # Check if pre-built deb already exists (skip compilation) + local existing_deb + existing_deb=$(find "${BUILD_DEBS}" -name "${DEB_NAME}_*.deb" 2>/dev/null | head -1) + if [[ -n "$existing_deb" ]] && [[ -s "$existing_deb" ]]; then + local size + size=$(stat -c%s "$existing_deb" 2>/dev/null || echo 0) + if [[ $size -gt 1000000 ]]; then + display_alert "CHROMIUM" "Pre-built deb found: $(basename "$existing_deb") ($(($size/1024/1024))MB), skipping build" "info" + return 0 + fi + fi + + display_alert "CHROMIUM" "No pre-built deb found, compiling from source..." "info" + __chromium_fetch_source || { display_alert "CHROMIUM" "ABORT: fetch failed" "err"; return 1; } + __chromium_apply_patches || { display_alert "CHROMIUM" "ABORT: patch failed" "err"; return 1; } + __chromium_configure || { display_alert "CHROMIUM" "ABORT: configure failed" "err"; return 1; } + __chromium_compile || { display_alert "CHROMIUM" "ABORT: compile failed" "err"; return 1; } + __chromium_package_deb || { display_alert "CHROMIUM" "ABORT: package failed" "err"; return 1; } +} + +# ============================================================================== +# Section 5: Main +# ============================================================================== + +display_alert "AMLbian Chromium Build Script" "Version: ${CHROMIUM_VERSION}" "info" + +# Run in subshell to isolate PATH/env changes from parent build system. +# Other build scripts (kernel, uboot) run directly with set -e, but chromium +# modifies PATH (depot_tools), PYTHONHOME, etc. which would affect subsequent +# scripts if not isolated. +(amlbian_build_chromium) + +# ============================================================================== +# End of build-chromium.sh +# ============================================================================== diff --git a/scripts/chromium/config/args.gn b/scripts/chromium/config/args.gn new file mode 100644 index 0000000..bd5b6ea --- /dev/null +++ b/scripts/chromium/config/args.gn @@ -0,0 +1,29 @@ +target_cpu="arm64" +target_os="linux" +use_ozone=true +ozone_auto_platforms=false +ozone_platform_headless=true +ozone_platform_wayland=true +ozone_platform_x11=false +use_xkbcommon=true +use_gtk=false +use_system_libwayland=false +use_system_freetype=false +use_system_libffi=false +is_amlogic_media=true +mojo_media_services=["renderer","cdm","audio_decoder","audio_encoder","video_decoder"] +mojo_media_host="gpu" +ffmpeg_branding="Chrome" +proprietary_codecs=true +enable_platform_hevc=true +enable_hevc_parser_and_hw_decoder=true +is_official_build=true +is_debug=false +dcheck_always_on=false +is_clang=true +use_sysroot=true +chrome_pgo_phase=0 +treat_warnings_as_errors=false +use_v4l2_codec=true +use_thin_lto=false +widevine_version=0 diff --git a/scripts/chromium/overlay/media/mojo/services/gpu_mojo_media_client_amlogic.cc b/scripts/chromium/overlay/media/mojo/services/gpu_mojo_media_client_amlogic.cc new file mode 100644 index 0000000..75fe4b4 --- /dev/null +++ b/scripts/chromium/overlay/media/mojo/services/gpu_mojo_media_client_amlogic.cc @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +// +// Copyright (c) 2026 Amlogic, Inc. All rights reserved. +// +// AML hardware video decoder - zero-copy via VideoDecoderPipeline +// V4L2 DMA-BUF -> MailboxVideoFrameConverter -> GPU texture -> zero-copy +// NM12 (NV12 multi-planar) preferred over NM21 for Mali compatibility + +#include "media/mojo/services/gpu_mojo_media_client.h" + +#include "base/logging.h" +#include "media/base/media_log.h" +#include "media/base/video_codecs.h" +#include "media/base/video_decoder.h" +#include "media/gpu/chromeos/mailbox_video_frame_converter.h" +#include "media/gpu/chromeos/platform_video_frame_pool.h" +#include "media/gpu/chromeos/video_decoder_pipeline.h" +#include "ui/gfx/geometry/size.h" + +namespace media { + +class GpuMojoMediaClientAmlogic final : public GpuMojoMediaClient { + public: + GpuMojoMediaClientAmlogic(GpuMojoMediaClientTraits& traits) + : GpuMojoMediaClient(traits) {} + ~GpuMojoMediaClientAmlogic() final = default; + + protected: + std::unique_ptr CreatePlatformVideoDecoder( + VideoDecoderTraits& traits) final { + auto frame_pool = std::make_unique(); + auto frame_converter = MailboxVideoFrameConverter::Create( + gpu_task_runner_, traits.get_command_buffer_stub_cb); + + // NM12 (NV12 multi-planar) first - Mali supports NV12 import + // NM21 (NV21 multi-planar) last - Mali crashes on NV21 import + return VideoDecoderPipeline::Create( + gpu_workarounds_, traits.task_runner, std::move(frame_pool), + std::move(frame_converter), + {Fourcc(Fourcc::NM12), Fourcc(Fourcc::NV12), + Fourcc(Fourcc::NM21), Fourcc(Fourcc::NV21)}, + traits.media_log->Clone(), /*oop_video_decoder=*/{}, + /*in_video_decoder_process=*/false); + } + + void NotifyPlatformDecoderSupport( + mojo::PendingRemote oop_video_decoder, + base::OnceCallback)> cb) final { + std::move(cb).Run(std::move(oop_video_decoder)); + } + + std::optional + GetPlatformSupportedVideoDecoderConfigs() final { + SupportedVideoDecoderConfigs configs; + configs.emplace_back(H264PROFILE_BASELINE, H264PROFILE_HIGH, + gfx::Size(64, 64), gfx::Size(3840, 2160), false, false); + configs.emplace_back(VP9PROFILE_PROFILE0, VP9PROFILE_PROFILE2, + gfx::Size(64, 64), gfx::Size(3840, 2160), false, false); + configs.emplace_back(HEVCPROFILE_MAIN, HEVCPROFILE_MAIN10, + gfx::Size(64, 64), gfx::Size(3840, 2160), false, false); + configs.emplace_back(AV1PROFILE_PROFILE_MAIN, AV1PROFILE_PROFILE_MAIN, + gfx::Size(64, 64), gfx::Size(3840, 2160), false, false); + return configs; + } + + VideoDecoderType GetPlatformDecoderImplementationType() final { + return VideoDecoderType::kV4L2; + } +}; + +std::unique_ptr CreateGpuMediaService( + GpuMojoMediaClientTraits& traits) { + return std::make_unique(traits); +} + +} // namespace media diff --git a/scripts/chromium/patches/minimal-hwdecode-final.patch b/scripts/chromium/patches/minimal-hwdecode-final.patch new file mode 100644 index 0000000..d4d9a5d --- /dev/null +++ b/scripts/chromium/patches/minimal-hwdecode-final.patch @@ -0,0 +1,660 @@ +diff --git a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc +index 3acfcf5749..45b18d4203 100644 +--- a/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc ++++ b/content/renderer/media/gpu/gpu_video_accelerator_factories_impl.cc +@@ -183,6 +183,9 @@ void GpuVideoAcceleratorFactoriesImpl::DestroyContext() { + } + + bool GpuVideoAcceleratorFactoriesImpl::IsGpuVideoDecodeAcceleratorEnabled() { ++#if BUILDFLAG(IS_AMLOGIC_MEDIA) ++ return true; ++#endif + return video_decode_accelerator_enabled_; + } + bool GpuVideoAcceleratorFactoriesImpl::IsGpuVideoEncodeAcceleratorEnabled() { +diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc +index c848b7f8f7..e2b5215d17 100644 +--- a/content/renderer/render_thread_impl.cc ++++ b/content/renderer/render_thread_impl.cc +@@ -1,3 +1,4 @@ ++#include "media/mojo/buildflags.h" + // Copyright 2012 The Chromium Authors + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. +@@ -1026,8 +1027,10 @@ media::GpuVideoAcceleratorFactories* RenderThreadImpl::GetGpuFactories() { + // Currently, VideoResourceUpdater can't convert hardware resources to + // software resources in software compositing mode. So, fall back to software + // video decoding if gpu compositing is off. ++#if !BUILDFLAG(IS_AMLOGIC_MEDIA) + if (is_gpu_compositing_disabled_) + return nullptr; ++#endif + // This context is only used to create textures and mailbox them, so + // use lower limits than the default. + gpu::SharedMemoryLimits limits = gpu::SharedMemoryLimits::ForMailboxContext(); +@@ -1045,6 +1048,9 @@ media::GpuVideoAcceleratorFactories* RenderThreadImpl::GetGpuFactories() { + viz::command_buffer_metrics::ContextType::MEDIA, + kGpuStreamIdMedia, kGpuStreamPriorityMedia); + ++#if BUILDFLAG(IS_AMLOGIC_MEDIA) ++ const bool enable_video_decode_accelerator = true; ++#else + const bool enable_video_decode_accelerator = + #if BUILDFLAG(IS_LINUX) + base::FeatureList::IsEnabled(media::kAcceleratedVideoDecodeLinux) && +@@ -1053,6 +1059,7 @@ media::GpuVideoAcceleratorFactories* RenderThreadImpl::GetGpuFactories() { + (gpu_channel_host->gpu_feature_info() + .status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE] == + gpu::kGpuFeatureStatusEnabled); ++#endif + + const bool enable_video_encode_accelerator = + #if BUILDFLAG(IS_LINUX) +diff --git a/media/base/format_utils.cc b/media/base/format_utils.cc +index 0e1080e0a6..98a221b2f4 100644 +--- a/media/base/format_utils.cc ++++ b/media/base/format_utils.cc +@@ -65,6 +65,9 @@ std::optional VideoPixelFormatToGfxBufferFormat( + case PIXEL_FORMAT_NV12: + return gfx::BufferFormat::YUV_420_BIPLANAR; + ++ case PIXEL_FORMAT_NV21: ++ return gfx::BufferFormat::YUV_420_BIPLANAR; ++ + case PIXEL_FORMAT_NV12A: + return gfx::BufferFormat::YUVA_420_TRIPLANAR; + +diff --git a/media/base/supported_types.cc b/media/base/supported_types.cc +index 34e1ecb868..bc0cd98fee 100644 +--- a/media/base/supported_types.cc ++++ b/media/base/supported_types.cc +@@ -315,7 +315,7 @@ bool IsDecoderAACSupported(const AudioType& type) { + return true; + } + #if BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER) && \ +- (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)) ++ (BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_AMLOGIC_MEDIA)) + return GetSupplementalDecoderAudioTypeCache()->IsProfileSupported(type); + #else + return false; +@@ -350,12 +350,13 @@ bool IsDecoderDolbyAc3Eac3Supported(const AudioType& type) { + + bool IsDecoderDolbyAc4Supported(const AudioType& type) { + #if BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO) && \ +- BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER) && BUILDFLAG(IS_WIN) ++ BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER) && (BUILDFLAG(IS_WIN) || BUILDFLAG(IS_AMLOGIC_MEDIA)) + return GetSupplementalDecoderAudioTypeCache()->IsProfileSupported(type); + #else + return false; + #endif // BUILDFLAG(ENABLE_PLATFORM_AC4_AUDIO) && +- // BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER) && BUILDFLAG(IS_WIN) ++ // BUILDFLAG(ENABLE_MOJO_AUDIO_DECODER) && (BUILDFLAG(IS_WIN) || ++ // BUILDFLAG(IS_AMLOGIC_MEDIA)) + } + + bool IsEncoderH264ProfileSupported(const VideoType& type) { +@@ -579,6 +580,11 @@ bool IsDefaultEncoderSupportedVideoType(const VideoType& type) { + } + + bool IsDecoderBuiltInVideoCodec(VideoCodec codec) { ++#if BUILDFLAG(IS_AMLOGIC_MEDIA) ++ // On Amlogic platforms, all codecs are treated as built-in since the ++ // hardware decoders are used. ++ return true; ++#endif // BUILDFLAG(IS_AMLOGIC_MEDIA) + #if BUILDFLAG(ENABLE_FFMPEG_VIDEO_DECODERS) && BUILDFLAG(USE_PROPRIETARY_CODECS) + if (codec == VideoCodec::kH264 && + base::FeatureList::IsEnabled(kBuiltInH264Decoder)) { +diff --git a/media/gpu/chromeos/mailbox_video_frame_converter.cc b/media/gpu/chromeos/mailbox_video_frame_converter.cc +index 6cbc6d89f5..21994b5f34 100644 +--- a/media/gpu/chromeos/mailbox_video_frame_converter.cc ++++ b/media/gpu/chromeos/mailbox_video_frame_converter.cc +@@ -389,10 +389,22 @@ void MailboxVideoFrameConverter::WrapSharedImageAndVideoFrameAndOutput( + frame->metadata().needs_detiling + ? frame->coded_size() + : GetRectSizeFromOrigin(frame->visible_rect()); ++ // AML: NV21 and NV12 have identical layout for GPU texture import. ++ // WrapSharedImage doesn't support NV21, so use NV12 instead. ++ VideoPixelFormat wrap_format = frame->format(); ++ if (wrap_format == PIXEL_FORMAT_NV21) ++ wrap_format = PIXEL_FORMAT_NV12; + scoped_refptr mailbox_frame = VideoFrame::WrapSharedImage( +- frame->format(), shared_image, shared_image_sync_token, ++ wrap_format, shared_image, shared_image_sync_token, + std::move(release_mailbox_cb), coded_size, frame->visible_rect(), + frame->natural_size(), frame->timestamp()); ++ if (!mailbox_frame) { ++ DVLOG(3) << "[AML-CONV] WrapSharedImage returned null! format=" ++ << VideoPixelFormatToString(frame->format()) ++ << " coded=" << coded_size.ToString() ++ << " visible=" << frame->visible_rect().ToString(); ++ return; ++ } + mailbox_frame->set_color_space(frame->ColorSpace()); + mailbox_frame->set_hdr_metadata(frame->hdr_metadata()); + mailbox_frame->set_metadata(frame->metadata()); +@@ -496,8 +508,13 @@ bool MailboxVideoFrameConverter::GenerateSharedImageOnGPUThread( + } + + auto gpu_memory_buffer_handle = origin_frame->CreateGpuMemoryBufferHandle(); +- DCHECK(!gpu_memory_buffer_handle.is_null()); +- DCHECK_EQ(gpu_memory_buffer_handle.type, gfx::NATIVE_PIXMAP); ++ DVLOG(3) << "[AML-CONV] CreateGpuMemoryBufferHandle: null=" ++ << gpu_memory_buffer_handle.is_null() ++ << " type=" << gpu_memory_buffer_handle.type ++ << " planes=" << gpu_memory_buffer_handle.native_pixmap_handle.planes.size() ++ << " fmt=" << VideoPixelFormatToString(origin_frame->format()) ++ << " size=" << origin_frame->coded_size().ToString(); ++ if (gpu_memory_buffer_handle.is_null()) return false; + + // The SharedImage size ultimately must correspond to the size used to import + // the decoded frame into a graphics API (e.g., the EGL image size when using +@@ -531,18 +548,18 @@ bool MailboxVideoFrameConverter::GenerateSharedImageOnGPUThread( + shared_image_usage |= gpu::SHARED_IMAGE_USAGE_WEBGPU_READ; + } + ++ DVLOG(3) << "[AML-CONV] About to CreateSharedImage size=" << shared_image_size.ToString(); + scoped_refptr client_shared_image = + gpu_delegate_->CreateSharedImage(std::move(gpu_memory_buffer_handle), + GetSharedImageFormat(*buffer_format), + shared_image_size, src_color_space, + kTopLeft_GrSurfaceOrigin, + kPremul_SkAlphaType, shared_image_usage); ++ DVLOG(3) << "[AML-CONV] CreateSharedImage result: " << (client_shared_image ? "OK" : "FAIL"); + if (!client_shared_image) { + OnError(FROM_HERE, "Failed to create shared image."); + return false; + } +- // There's no need to UpdateSharedImage() after CreateSharedImage(). +- + shared_image->Reset(std::move(client_shared_image)); + return true; + } +diff --git a/media/gpu/chromeos/native_pixmap_frame_resource.cc b/media/gpu/chromeos/native_pixmap_frame_resource.cc +index 09c763a7e8..5ac815656b 100644 +--- a/media/gpu/chromeos/native_pixmap_frame_resource.cc ++++ b/media/gpu/chromeos/native_pixmap_frame_resource.cc +@@ -3,6 +3,7 @@ + // found in the LICENSE file. + + #include "media/gpu/chromeos/native_pixmap_frame_resource.h" ++#include + + #include "atomic" + +@@ -130,10 +131,8 @@ scoped_refptr NativePixmapFrameResource::Create( + std::move(dmabuf_fds[i])); + } + +- // This is only ever called with V4L2-allocated buffers, so |layout.modifier| +- // is expected to be kNoModifier. +- CHECK_EQ(layout.modifier(), gfx::NativePixmapHandle::kNoModifier); +- handle.modifier = layout.modifier(); ++ // Keep kNoModifier for V4L2 MMAP buffers. ++ handle.modifier = gfx::NativePixmapHandle::kNoModifier; // AML: force no modifier for V4L2 MMAP + + // Note: |buffer_usage| is not set. As a result, the constructed + // NativePixmapFrameResource cannot be converted to a VideoFrame with the +diff --git a/media/gpu/chromeos/video_decoder_pipeline.cc b/media/gpu/chromeos/video_decoder_pipeline.cc +--- a/media/gpu/chromeos/video_decoder_pipeline.cc ++++ b/media/gpu/chromeos/video_decoder_pipeline.cc +@@ -3,6 +3,7 @@ + // found in the LICENSE file. + + #include "media/gpu/chromeos/video_decoder_pipeline.h" ++#include "media/mojo/buildflags.h" + + #include + #include +@@ -571,6 +572,7 @@ + + // Make sure that the configuration requested is supported by the driver, + // which must provide such information. ++#if !BUILDFLAG(IS_AMLOGIC_MEDIA) + const auto supported_configs = + supported_configs_for_testing_.empty() + ? VideoDecoderPipeline::GetSupportedConfigs(GetDecoderType(), +@@ -588,6 +590,7 @@ + std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedConfig); + return; + } ++#endif // !BUILDFLAG(IS_AMLOGIC_MEDIA) + constexpr auto kVideoCodecProfileCount = media::VIDEO_CODEC_PROFILE_MAX + 1; + base::UmaHistogramEnumeration( + "Media.PlatformVideoDecoding.VideoCodecProfile", config.profile(), +@@ -647,8 +650,10 @@ + get_original_frame_cb = base::BindRepeating( + &OOPVideoDecoder::GetOriginalFrame, + base::Unretained(static_cast(decoder_.get()))); ++ } else if (!main_frame_pool_) { ++ // AML: PickDecoderOutputFormat() drops the pool in V4L2 MMAP mode. ++ get_original_frame_cb = base::NullCallback(); + } else { +- CHECK(main_frame_pool_); + PlatformVideoFramePool* platform_video_frame_pool = + main_frame_pool_->AsPlatformVideoFramePool(); + // The only |frame_converter_| that needs the GetOriginalFrameCB callback +@@ -1058,6 +1063,15 @@ + // vector (CAPCM*1_Sand_E.h264). + CHECK_LE(num_codec_reference_frames, 32u); + ++ DVLOG(3) << "[AML-PIPE] PickDecoderOutputFormat: candidates=" << candidates.size() ++ << " renderable=" << renderable_fourccs_.size(); ++ for (const auto& c : candidates) { ++ DVLOG(3) << "[AML-PIPE] candidate: " << c.fourcc.ToString() ++ << " size=" << c.size.ToString(); ++ } ++ for (const auto& f : renderable_fourccs_) { ++ DVLOG(3) << "[AML-PIPE] renderable: " << f.ToString(); ++ } + if (candidates.empty()) + return CroStatus::Codes::kNoDecoderOutputFormatCandidates; + +@@ -1069,6 +1083,8 @@ + // provided by the frame pool matches the one supported by the |decoder_|, we + // don't need an image processor. + std::optional viable_candidate; ++ DVLOG(3) << "[AML-PIPE] output_size=" << (output_size ? output_size->ToString() : "none") ++ << " decoder_visible=" << decoder_visible_rect.ToString(); + if (!output_size || *output_size == decoder_visible_rect.size()) { + for (const auto& fourcc : renderable_fourccs_) { + for (const auto& candidate : candidates) { +@@ -1108,14 +1124,9 @@ + *allocator, VideoFrame::STORAGE_DMABUFS); + #elif BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_V4L2_CODEC) + // Linux w/ V4L2 should not use a custom allocator +- // Only tested with video_decode_accelerator_tests +- // TODO(wenst@) Test with full Chromium Browser + CHECK(!allocator.has_value()); + if (viable_candidate) { +- // Instead, let V4L2 allocate the buffers if it can decode directly +- // to the preferred formats. There's no need to allocate frames. +- // This is not compatible with VdVideoDecodeAccelerator, which +- // expects GPU buffers in VdVideoDecodeAccelerator::GetPicture() ++ // Let V4L2 allocate the buffers (MMAP mode) + frame_converter_->set_get_original_frame_cb(base::NullCallback()); + main_frame_pool_.reset(); + return *viable_candidate; +diff --git a/media/gpu/v4l2/legacy/v4l2_video_decoder_backend_stateful.cc b/media/gpu/v4l2/legacy/v4l2_video_decoder_backend_stateful.cc +--- a/media/gpu/v4l2/legacy/v4l2_video_decoder_backend_stateful.cc ++++ b/media/gpu/v4l2/legacy/v4l2_video_decoder_backend_stateful.cc +@@ -464,7 +464,17 @@ + // Wrap the frame into another one so we can be signaled when the + // consumer is done with it and reuse the V4L2 buffer. + scoped_refptr origin_frame = buffer->GetFrameResource(); ++ DVLOG(3) << "[AML-V4L2] OnOutputBufferDequeued: origin_frame=" ++ << (origin_frame ? "valid" : "NULL") ++ << " memory=" << output_queue_->GetMemoryType(); ++ if (!origin_frame) { ++ // Queue was reset (resolution change). Skip this buffer. ++ return; ++ } + frame = origin_frame->CreateWrappingFrame(); ++ if (!frame) { ++ return; ++ } + frame->AddDestructionObserver(base::BindOnce( + &V4L2StatefulVideoDecoderBackend::ReuseOutputBufferThunk, + task_runner_, weak_this_, buffer)); +@@ -761,6 +771,8 @@ + #endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER) + V4L2_PIX_FMT_VP8, + V4L2_PIX_FMT_VP9, ++ // AML: without AV01 here every AV1 stream is rejected in Initialize(). ++ V4L2_PIX_FMT_AV1, + }; + auto device = base::MakeRefCounted(); + VideoDecodeAccelerator::SupportedProfiles profiles = +diff --git a/media/gpu/v4l2/v4l2_queue.cc b/media/gpu/v4l2/v4l2_queue.cc +index bd6b3f5278..4242e06e38 100644 +--- a/media/gpu/v4l2/v4l2_queue.cc ++++ b/media/gpu/v4l2/v4l2_queue.cc +@@ -133,13 +133,12 @@ std::vector GetDmabufsForV4L2Buffer( + expbuf.type = buf_type; + expbuf.index = index; + expbuf.plane = i; +- expbuf.flags = O_CLOEXEC; ++ expbuf.flags = O_CLOEXEC | O_RDWR; // AML: Mali needs RW access + if (ioctl_cb.Run(VIDIOC_EXPBUF, &expbuf) != 0) { + RecordVidiocIoctlErrorUMA(VidiocIoctlRequests::kVidiocExpbuf); + dmabuf_fds.clear(); + break; + } +- + dmabuf_fds.push_back(base::ScopedFD(expbuf.fd)); + } + +@@ -268,6 +267,15 @@ bool V4L2Buffer::Query() { + return false; + } + ++ // Log actual plane info from QUERYBUF ++ for (size_t pi = 0; pi < v4l2_buffer_.length; pi++) { ++ LOG(ERROR) << "[AML-QB] buf=" << v4l2_buffer_.index ++ << " plane" << pi ++ << " length=" << v4l2_planes_[pi].length ++ << " offset=" << v4l2_planes_[pi].m.mem_offset ++ << " bytesused=" << v4l2_planes_[pi].bytesused; ++ } ++ + DCHECK(plane_mappings_.size() == v4l2_buffer_.length); + + return true; +@@ -314,7 +322,10 @@ size_t V4L2Buffer::GetMemoryUsage() const { + scoped_refptr V4L2Buffer::CreateFrame() { + auto layout = V4L2FormatToVideoFrameLayout(format_); + if (!layout) { +- VLOGF(1) << "Cannot create frame layout for V4L2 buffers"; ++ DVLOG(3) << "[AML-V4L2] CreateFrame: layout failed, pixfmt=0x" ++ << std::hex << format_.fmt.pix_mp.pixelformat << std::dec ++ << " " << format_.fmt.pix_mp.width << "x" << format_.fmt.pix_mp.height ++ << " num_planes=" << format_.fmt.pix_mp.num_planes; + return nullptr; + } + +@@ -322,7 +333,8 @@ scoped_refptr V4L2Buffer::CreateFrame() { + ioctl_cb_, v4l2_buffer_.index, v4l2_buffer_.length, + static_cast(v4l2_buffer_.type)); + if (dmabuf_fds.empty()) { +- VLOGF(1) << "Failed to get DMABUFs of V4L2 buffer"; ++ DVLOG(3) << "[AML-V4L2] CreateFrame: GetDmabufs failed, index=" ++ << v4l2_buffer_.index << " length=" << v4l2_buffer_.length; + return nullptr; + } + +@@ -355,15 +367,19 @@ scoped_refptr V4L2Buffer::CreateFrame() { + const scoped_refptr& V4L2Buffer::GetFrameResource() { + // We can create the FrameResource only when using MMAP buffers. + if (v4l2_buffer_.memory != V4L2_MEMORY_MMAP) { +- VLOGF(1) << "Cannot create video frame from non-MMAP buffer"; +- // Allow NOTREACHED() on invalid argument because this is an internal +- // method. +- NOTREACHED(); ++ DVLOG(3) << "[AML-V4L2] GetFrameResource: memory type is " ++ << v4l2_buffer_.memory << " (not MMAP=" << V4L2_MEMORY_MMAP << ")" ++ << " index=" << v4l2_buffer_.index; ++ static const scoped_refptr null_res; ++ return null_res; + } + + // Create the video frame instance if requiring it for the first time. + if (!frame_) { + frame_ = CreateFrame(); ++ if (!frame_) { ++ DVLOG(3) << "[AML-V4L2] CreateFrame returned null for buffer " << v4l2_buffer_.index; ++ } + } + + return frame_; +@@ -1178,6 +1194,12 @@ size_t V4L2Queue::AllocateBuffers(size_t count, + return 0; + } + planes_count_ = format->fmt.pix_mp.num_planes; ++ // AML 6.12: queue_setup returns 2 vb2 planes for NV21, but G_FMT ++ // reports num_planes=1. Force 2 so QUERYBUF gets correct length. ++ if (type_ == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && planes_count_ < 2) { ++ planes_count_ = 2; ++ format->fmt.pix_mp.num_planes = 2; ++ } + DCHECK_LE(planes_count_, static_cast(VIDEO_MAX_PLANES)); + + __u8 flags = incoherent ? V4L2_MEMORY_FLAG_NON_COHERENT : 0; +diff --git a/media/gpu/v4l2/v4l2_utils.cc b/media/gpu/v4l2/v4l2_utils.cc +--- a/media/gpu/v4l2/v4l2_utils.cc ++++ b/media/gpu/v4l2/v4l2_utils.cc +@@ -17,6 +17,10 @@ + #if BUILDFLAG(IS_CHROMEOS) + #include + #endif ++// AML: the Debian uAPI headers predate the AV1 controls. ++#ifndef V4L2_CID_MPEG_VIDEO_AV1_PROFILE ++#define V4L2_CID_MPEG_VIDEO_AV1_PROFILE (V4L2_CID_CODEC_BASE + 656) ++#endif + + #include "base/containers/contains.h" + #include "base/metrics/histogram_functions.h" +@@ -264,8 +268,17 @@ + planes.reserve(num_color_planes); + for (size_t i = 0; i < num_buffers; ++i) { + const v4l2_plane_pix_format& plane_format = pix_mp.plane_fmt[i]; +- planes.emplace_back(static_cast(plane_format.bytesperline), 0u, +- plane_format.sizeimage); ++ int32_t stride = static_cast(plane_format.bytesperline); ++ size_t sizeimage = plane_format.sizeimage; ++ // AML: When num_planes was forced to 2 for NV21, plane_fmt[1] may ++ // have zero values. Fill stride/size from plane0. ++ // Each vb2 plane is a separate DMA-BUF allocation, so offset=0 for all. ++ if (i > 0 && stride == 0 && num_buffers == 2) { ++ stride = static_cast(pix_mp.plane_fmt[0].bytesperline); ++ sizeimage = stride * pix_mp.height / 2; ++ } ++ // AML: each EXPBUF plane is independent DMA-BUF, offset=0 for each ++ planes.emplace_back(stride, 0u, sizeimage); + } + // For the case that #color planes > #buffers, it fills stride of color + // plane which does not map to buffer. +@@ -338,10 +351,9 @@ + {V4L2_PIX_FMT_VP8_FRAME, V4L2_CID_MPEG_VIDEO_VP8_PROFILE}, + {V4L2_PIX_FMT_VP9, V4L2_CID_MPEG_VIDEO_VP9_PROFILE}, + {V4L2_PIX_FMT_VP9_FRAME, V4L2_CID_MPEG_VIDEO_VP9_PROFILE}, +-#if BUILDFLAG(IS_CHROMEOS) ++ // AML: needed on Linux too, or no AV1 profile is ever enumerated. + {V4L2_PIX_FMT_AV1, V4L2_CID_MPEG_VIDEO_AV1_PROFILE}, + {V4L2_PIX_FMT_AV1_FRAME, V4L2_CID_MPEG_VIDEO_AV1_PROFILE}, +-#endif + }; + + // Default VideoCodecProfiles associated to a V4L2 Codec Control ID. +@@ -359,9 +371,8 @@ + #endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER) + {V4L2_CID_MPEG_VIDEO_VP8_PROFILE, {VP8PROFILE_ANY}}, + {V4L2_CID_MPEG_VIDEO_VP9_PROFILE, {VP9PROFILE_PROFILE0}}, +-#if BUILDFLAG(IS_CHROMEOS) ++ // AML: fallback used when the driver has no AV1 profile control. + {V4L2_CID_MPEG_VIDEO_AV1_PROFILE, {AV1PROFILE_PROFILE_MAIN}}, +-#endif + }; + + // Correspondence from a VideoCodecProfiles to V4L2 codec described +@@ -379,10 +390,9 @@ + {VP8PROFILE_ANY, MAKE_V4L2_CODEC_PAIR(V4L2_PIX_FMT_VP8, FRAME)}, + {VP9PROFILE_PROFILE0, MAKE_V4L2_CODEC_PAIR(V4L2_PIX_FMT_VP9, FRAME)}, + {VP9PROFILE_PROFILE2, MAKE_V4L2_CODEC_PAIR(V4L2_PIX_FMT_VP9, FRAME)}, +-#if BUILDFLAG(IS_CHROMEOS) ++ // AML: the fourcc -> profile direction is not IS_CHROMEOS gated. + {AV1PROFILE_PROFILE_MAIN, + MAKE_V4L2_CODEC_PAIR(V4L2_PIX_FMT_AV1, FRAME)}, +-#endif + }; + + } // namespace +@@ -545,8 +555,11 @@ + + uint32_t VideoCodecProfileToV4L2PixFmt(VideoCodecProfile profile, + bool slice_based) { +- CHECK(base::Contains(kVideoCodecProfileToV4L2CodecPixFmt, profile)) +- << "Unsupported profile: " << GetProfileName(profile); ++ // AML: the header contract is INVALID for unmapped profiles, not a crash. ++ if (!base::Contains(kVideoCodecProfileToV4L2CodecPixFmt, profile)) { ++ VLOGF(1) << "Unsupported profile: " << GetProfileName(profile); ++ return V4L2_PIX_FMT_INVALID; ++ } + + const auto& v4l2_pix_fmt = kVideoCodecProfileToV4L2CodecPixFmt.at(profile); + return slice_based ? v4l2_pix_fmt.first : v4l2_pix_fmt.second; +diff --git a/media/gpu/v4l2/v4l2_vda_helpers.cc b/media/gpu/v4l2/v4l2_vda_helpers.cc +--- a/media/gpu/v4l2/v4l2_vda_helpers.cc ++++ b/media/gpu/v4l2/v4l2_vda_helpers.cc +@@ -160,7 +160,8 @@ + #endif // BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER) + case VideoCodec::kVP8: + case VideoCodec::kVP9: +- // VP8/VP9 don't need any frame splitting, use the default implementation. ++ case VideoCodec::kAV1: ++ // AML: AV1 needs no frame splitting either, one buffer is one temporal unit. + return std::make_unique(); + default: + LOG(ERROR) << "Unhandled profile: " << profile; +diff --git a/media/media_options.gni b/media/media_options.gni +index e235287ebb..36e13c3e91 100644 +--- a/media/media_options.gni ++++ b/media/media_options.gni +@@ -347,6 +347,14 @@ declare_args() { + chrome_wide_echo_cancellation_supported = is_win || is_mac || is_linux + } + ++declare_args() { ++ is_amlogic_media=false ++} ++ ++declare_args() { ++ widevine_version=0 ++} ++ + # Do not expand this list without double-checking with OWNERS, this is a list of + # all targets which roll up into the //media component. It controls visibility + # of subcomponent targets and public_deps for //media. +diff --git a/media/mojo/BUILD.gn b/media/mojo/BUILD.gn +index 9573d77ba8..92ecf99112 100644 +--- a/media/mojo/BUILD.gn ++++ b/media/mojo/BUILD.gn +@@ -55,6 +55,7 @@ buildflag_header("buildflags") { + "ENABLE_MOJO_VIDEO_DECODER=$enable_mojo_video_decoder", + "ENABLE_MOJO_MEDIA_IN_BROWSER_PROCESS=$enable_mojo_media_in_browser_process", + "ENABLE_MOJO_MEDIA_IN_GPU_PROCESS=$enable_mojo_media_in_gpu_process", ++ "IS_AMLOGIC_MEDIA=$is_amlogic_media", + ] + } + +diff --git a/media/mojo/services/BUILD.gn b/media/mojo/services/BUILD.gn +index 3e2dac7991..b3911dcf35 100644 +--- a/media/mojo/services/BUILD.gn ++++ b/media/mojo/services/BUILD.gn +@@ -119,6 +119,11 @@ component("services") { + sources += [ "gpu_mojo_media_client_mac.cc" ] + } else if (is_win) { + sources += [ "gpu_mojo_media_client_win.cc" ] ++ } else if (is_amlogic_media) { ++ sources += [ "gpu_mojo_media_client_amlogic.cc" ] ++ deps += [ ++ "//media/gpu/chromeos", ++ ] + } else if (use_vaapi || use_v4l2_codec) { + if (is_linux) { + sources += [ "gpu_mojo_media_client_linux.cc" ] +diff --git a/media/mojo/services/gpu_mojo_media_client.cc b/media/mojo/services/gpu_mojo_media_client.cc +index 7a739e1d73..0475c34757 100644 +--- a/media/mojo/services/gpu_mojo_media_client.cc ++++ b/media/mojo/services/gpu_mojo_media_client.cc +@@ -3,6 +3,7 @@ + // found in the LICENSE file. + + #include "media/mojo/services/gpu_mojo_media_client.h" ++#include "media/mojo/buildflags.h" + + #include + #include +@@ -156,12 +157,16 @@ GpuMojoMediaClient::GetSupportedVideoDecoderConfigs() { + if (!supported_config_cache_) { + // Only bother to query if accelerated video decoding is enabled. + // (RenderMediaClient does not know about GPU features before it asks.) ++#if BUILDFLAG(IS_AMLOGIC_MEDIA) ++ { ++#else + if (gpu_preferences_.disable_accelerated_video_decode || + (gpu_feature_info_ + .status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE] != + gpu::kGpuFeatureStatusEnabled)) { + supported_config_cache_ = SupportedVideoDecoderConfigs(); + } else { ++#endif + supported_config_cache_ = GetPlatformSupportedVideoDecoderConfigs(); + } + +@@ -230,12 +235,14 @@ std::unique_ptr GpuMojoMediaClient::CreateVideoDecoder( + const gfx::ColorSpace& target_color_space, + mojo::PendingRemote oop_video_decoder) { + // Always respect GPU features. ++#if !BUILDFLAG(IS_AMLOGIC_MEDIA) + if (gpu_preferences_.disable_accelerated_video_decode || + (gpu_feature_info_ + .status_values[gpu::GPU_FEATURE_TYPE_ACCELERATED_VIDEO_DECODE] != + gpu::kGpuFeatureStatusEnabled)) { + return nullptr; + } ++#endif + // All implementations require a command buffer. + if (!command_buffer_id) + return nullptr; +diff --git a/ui/ozone/common/BUILD.gn b/ui/ozone/common/BUILD.gn +index 7ce40f68de..1036e37a91 100644 +--- a/ui/ozone/common/BUILD.gn ++++ b/ui/ozone/common/BUILD.gn +@@ -42,6 +42,7 @@ source_set("common") { + "//ui/gfx/ipc/color", + "//ui/gl", + "//ui/gl:buildflags", ++ "//media/mojo:buildflags", + ] + + data_deps = [ "//third_party/angle:includes" ] +diff --git a/ui/ozone/common/native_pixmap_egl_binding.cc b/ui/ozone/common/native_pixmap_egl_binding.cc +index b945e14226..b1b0dda89b 100644 +--- a/ui/ozone/common/native_pixmap_egl_binding.cc ++++ b/ui/ozone/common/native_pixmap_egl_binding.cc +@@ -8,6 +8,7 @@ + #endif + + #include "ui/ozone/common/native_pixmap_egl_binding.h" ++#include "media/mojo/buildflags.h" + + #include "base/logging.h" + #include "base/memory/scoped_refptr.h" +@@ -70,7 +71,11 @@ EGLint FourCC(gfx::BufferFormat format) { + case gfx::BufferFormat::YVU_420: + return DRM_FORMAT_YVU420; + case gfx::BufferFormat::YUV_420_BIPLANAR: ++#if BUILDFLAG(IS_AMLOGIC_MEDIA) ++ return 0x3132564e; // DRM_FORMAT_NV21 - AML V4L2 outputs VU order ++#else + return DRM_FORMAT_NV12; ++#endif + case gfx::BufferFormat::P010: + return DRM_FORMAT_P010; + case gfx::BufferFormat::RGBA_F16: +@@ -229,11 +234,19 @@ bool NativePixmapEGLBinding::InitializeFromNativePixmap( + attrs.push_back(kPlaneOffsetAttrs[attrs_plane]); + attrs.push_back(pixmap->GetDmaBufOffset(attrs_plane)); + attrs.push_back(kPlanePitchAttrs[attrs_plane]); +- attrs.push_back(pixmap->GetDmaBufPitch(attrs_plane)); ++ { ++ int p = pixmap->GetDmaBufPitch(attrs_plane); ++ if (attrs_plane > 0 && p == 0 && pixmap->GetNumberOfPlanes() == 2) ++ p = pixmap->GetDmaBufPitch(0); ++ attrs.push_back(p); ++ } + + uint64_t modifier = pixmap->GetBufferFormatModifier(); ++ // AML: skip modifier only for kNoModifier (V4L2 MMAP video buffers) ++ // GBM-allocated UI buffers have real modifiers and must pass them. + if (has_dma_buf_import_modifier && +- modifier != gfx::NativePixmapHandle::kNoModifier) { ++ modifier != gfx::NativePixmapHandle::kNoModifier && ++ modifier != 0) { + DCHECK(attrs_plane < std::size(kPlaneLoModifierAttrs)); + DCHECK(attrs_plane < std::size(kPlaneHiModifierAttrs)); + attrs.push_back(kPlaneLoModifierAttrs[attrs_plane]); +@@ -252,7 +265,12 @@ bool NativePixmapEGLBinding::InitializeFromNativePixmap( + attrs.push_back(EGL_DMA_BUF_PLANE0_OFFSET_EXT); + attrs.push_back(pixmap->GetDmaBufOffset(pixmap_plane)); + attrs.push_back(EGL_DMA_BUF_PLANE0_PITCH_EXT); +- attrs.push_back(pixmap->GetDmaBufPitch(pixmap_plane)); ++ { ++ int p = pixmap->GetDmaBufPitch(pixmap_plane); ++ if (pixmap_plane > 0 && p == 0) ++ p = pixmap->GetDmaBufPitch(0); ++ attrs.push_back(p); ++ } + attrs.push_back(EGL_NONE); + } + -- 2.43.2