正点原子 RK3562 Android14 集成 GStreamer 1.24.13(CLI + V4L2 插件)完整移植方案

RK3562 Android 系统中集成 GStreamer CLI + V4L2 插件的完整移植方案,重点难点在于:预编译产物整理、Android.bp 自动生成、vendor 路径安装、运行时环境变量注入,以及 Android 动态链接 namespace 限制的排查。

正点原子RK3562J开发板瑞芯微Linux开发板ARM工业控制AI人工智能

RK3562 Android14 集成 GStreamer 1.24.13(CLI + V4L2 插件)完整移植方案

  • 一、目标与约束
  • 二、新建目录结构(external/gstreamer)
  • [三、下载 GStreamer Android prebuilts 与 Cerbero 源码](#三、下载 GStreamer Android prebuilts 与 Cerbero 源码)
    • [1、GStreamer 1.24.13 Android prebuilts](#1、GStreamer 1.24.13 Android prebuilts)
    • [2、Cerbero 1.24.13 源码(构建工具链与模块)](#2、Cerbero 1.24.13 源码(构建工具链与模块))
  • [四、将 prebuilts 合入 SDK 目录](#四、将 prebuilts 合入 SDK 目录)
  • [五、使用 Cerbero 生成 CLI 工具与 V4L2 插件](#五、使用 Cerbero 生成 CLI 工具与 V4L2 插件)
    • [1、Cerbero bootstrap(准备 build-tools 与依赖)](#1、Cerbero bootstrap(准备 build-tools 与依赖))
    • [2、构建 gstreamer-1.0(生成 CLI + 核心库)](#2、构建 gstreamer-1.0(生成 CLI + 核心库))
    • [3、拷贝 Cerbero 产物到 SDK prebuilts](#3、拷贝 Cerbero 产物到 SDK prebuilts)
    • [4、构建 gst-plugins-good 的 video4linux2(v4l2src)](#4、构建 gst-plugins-good 的 video4linux2(v4l2src))
  • [六、将 gstreamer 模块加入系统镜像打包清单](#六、将 gstreamer 模块加入系统镜像打包清单)
  • [七、编写 GStreamer CLI 环境注入脚本(gst-env)](#七、编写 GStreamer CLI 环境注入脚本(gst-env))
  • [八、生成/维护 external/gstreamer/Android.bp](#八、生成/维护 external/gstreamer/Android.bp)
  • 九、生成系统镜像并烧录
  • [十、 运行验证(设备端)](#十、 运行验证(设备端))

一、目标与约束

1、目标

  • 在 Android 系统镜像中集成 GStreamer 1.24.13 的 CLI 工具:
    • gst-launch-1.0
    • gst-inspect-1.0
    • gst-plugin-scanner
  • 集成并可用 V4L2 插件(v4l2src 对应 libgstvideo4linux2.so
  • 提供 gst-env(/vendor/bin/gst-env)脚本,运行时注入必要环境变量,使 CLI 能正确加载 vendor 私有
    runtime 与插件,并把 registry 写入可写目录。

2、约束/注意

  • Android 动态链接与 namespace 限制会导致:插件 .so "not accessible"、依赖库找不到、SONAME 不匹配等问题。gst-env
    只能解决"路径与环境变量",不能解决"依赖库缺失/版本号不匹配/namespace 不可见"。

二、新建目录结构(external/gstreamer)

在 SDK external/ 下新建 gstreamer 目录,并创建子目录:

bash 复制代码
mkdir -p external/gstreamer/prebuilts/1.24.13
mkdir -p external/gstreamer/scripts
mkdir -p external/gstreamer/tools

目录说明:

  • prebuilts/1.24.13/:存放 GStreamer Android 预编译产物(bin、lib、plugins)
  • scripts/:运行时环境注入脚本(gst-env)
  • tools/:用于生成/修补 Android.bp 的脚本工具

注意:这里"prebuilts"存放的是预编译二进制与 so,不是"源码"。

三、下载 GStreamer Android prebuilts 与 Cerbero 源码

1、GStreamer 1.24.13 Android prebuilts

下载地址(官方 Android 包):

说明:

  • 该包通常包含 Android 平台的预编译库/插件(具体内容以包内为准)。
  • 实际项目中常见做法是:以官方 prebuilts 为基础,再用 Cerbero 补齐 CLI/插件/依赖。

2、Cerbero 1.24.13 源码(构建工具链与模块)

下载地址:

说明:

  • Cerbero 是 GStreamer 官方的跨平台构建系统,可生成 Android 的 gst-launch、gst-inspect、gst-plugin-scanner 以及多种插件(含 video4linux2)。

四、将 prebuilts 合入 SDK 目录

解压下载到的 GStreamer Android 包,将 arm64 目录拷贝到:

bash 复制代码
external/gstreamer/prebuilts/1.24.13/arm64

示例:

bash 复制代码
cp -a <gstreamer_android_pkg>/arm64 external/gstreamer/prebuilts/1.24.13/

说明:

  • RK3562 为 arm64 平台,选择 arm64 目录即可。

五、使用 Cerbero 生成 CLI 工具与 V4L2 插件

建议在独立目录下操作 Cerbero(例如 tools/cerbero-1.24.13/),避免污染 Android 源码树。

1、Cerbero bootstrap(准备 build-tools 与依赖)

bash 复制代码
python3 cerbero-uninstalled -c config/cross-android-arm64.cbc bootstrap

常见前置依赖(Ubuntu):

  • python3、pip3、ninja、meson、pkg-config、cmake、autoconf/automake/libtool、gettext、bison、flex、gperf 等

2、构建 gstreamer-1.0(生成 CLI + 核心库)

bash 复制代码
./cerbero-uninstalled -c config/cross-android-arm64.cbc build gstreamer-1.0

3、拷贝 Cerbero 产物到 SDK prebuilts

将 Cerbero 的产物拷贝到 external/gstreamer/prebuilts/1.24.13/arm64/ 对应目录:

bash 复制代码
cp -a cerbero-1.24.13/build/dist/android_arm64/bin/gst-launch-1.0 \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/

cp -a cerbero-1.24.13/build/dist/android_arm64/bin/gst-inspect-1.0 \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/

cp -a cerbero-1.24.13/build/dist/android_arm64/bin/gst-plugin-scanner \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/

cp -a cerbero-1.24.13/build/dist/android_arm64/lib/*.so \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/lib/

cp -a cerbero-1.24.13/build/dist/android_arm64/lib/gstreamer-1.0/*.so \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/lib/gstreamer-1.0/

4、构建 gst-plugins-good 的 video4linux2(v4l2src)

构建:

bash 复制代码
python3 cerbero-uninstalled -c config/cross-android-arm64.cbc -v build gst-plugins-good-1.0

检查插件是否生成:

bash 复制代码
find cerbero-1.24.13/build/dist/android_arm64 \
  -name "libgstvideo4linux2.so*" -o -name "*video4linux2*"

拷贝插件到 SDK:

bash 复制代码
cp -a cerbero-1.24.13/build/dist/android_arm64/lib/gstreamer-1.0/libgstvideo4linux2.so \
  <SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/lib/gstreamer-1.0/

六、将 gstreamer 模块加入系统镜像打包清单

在 device/rockchip/rk3562/device.mk 中追加:

bash 复制代码
PRODUCT_PACKAGES += \
    gst_launch_1_0 \
    gst_inspect_1_0 \
    gst_plugin_scanner \
    gst_runtime_env_sh \
    libgstvideo4linux2

说明(关键):

  • PRODUCT_PACKAGES 的名字必须与 Android.bp 中 name: 一致。
  • 如果名字不一致,会导致你"以为打包了,实际没进镜像"。

七、编写 GStreamer CLI 环境注入脚本(gst-env)

文件路径:

bash 复制代码
<SDK_PATH>/external/gstreamer/scripts/gst_env.sh

内容:

powershell 复制代码
#!/system/bin/sh
# external/gstreamer/scripts/gst_env.sh
# Installed as /vendor/bin/gst-env

# detect 64/32 bit
if [ -d /vendor/lib64 ]; then
    VND_LIB=/vendor/lib64
    SYS_LIB=/system/lib64
else
    VND_LIB=/vendor/lib
    SYS_LIB=/system/lib
fi

GST_RT_DIR="${VND_LIB}/gstreamer-private"
GST_PLUGINS_DIR="${VND_LIB}/gstreamer-private/gstreamer-1.0"

# LD_LIBRARY_PATH
LDLP="${GST_RT_DIR}:${VND_LIB}:${SYS_LIB}"
if [ -n "${LD_LIBRARY_PATH}" ]; then
    LDLP="${LDLP}:${LD_LIBRARY_PATH}"
fi
export LD_LIBRARY_PATH="${LDLP}"

# plugin paths
export GST_PLUGIN_SYSTEM_PATH_1_0="${GST_PLUGINS_DIR}"
export GST_PLUGIN_PATH_1_0="${GST_PLUGINS_DIR}"

# plugin scanner
if [ -x /vendor/bin/gst-plugin-scanner ]; then
    export GST_PLUGIN_SCANNER=/vendor/bin/gst-plugin-scanner
elif [ -x /system/bin/gst-plugin-scanner ]; then
    export GST_PLUGIN_SCANNER=/system/bin/gst-plugin-scanner
fi

# registry (writable)
UID=`id -u 2>/dev/null`
if [ -z "${UID}" ]; then
    UID=0
fi
export GST_REGISTRY_1_0="/data/local/tmp/gst-registry-1.0.${UID}.bin"
export GST_REGISTRY_REUSE_PLUGIN_SCANNER=0
export GST_DEBUG_NO_COLOR=1

# no args: print env
if [ $# -eq 0 ]; then
    echo "VND_LIB=${VND_LIB}"
    echo "SYS_LIB=${SYS_LIB}"
    echo "GST_RT_DIR=${GST_RT_DIR}"
    echo "GST_PLUGINS_DIR=${GST_PLUGINS_DIR}"
    echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"
    echo "GST_PLUGIN_SYSTEM_PATH_1_0=${GST_PLUGIN_SYSTEM_PATH_1_0}"
    echo "GST_PLUGIN_SCANNER=${GST_PLUGIN_SCANNER}"
    echo "GST_REGISTRY_1_0=${GST_REGISTRY_1_0}"
    exit 0
fi

exec "$@"

脚本作用总结:

  • 将 /vendor/lib64/gstreamer-private 注入 LD_LIBRARY_PATH
  • 将插件目录注入 GST_PLUGIN_PATH_1_0
  • 指定 scanner 路径
  • 将 registry 写入 /data/local/tmp(避免只读分区写失败)
  • 作为 wrapper 执行后续命令:gst-env gst-launch-1.0 ...

八、生成/维护 external/gstreamer/Android.bp

编写脚本 <SDK_PATH>/external/gstreamer/tools/gen_android_bp.py

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Generate external/gstreamer/Android.bp from prebuilts.

Layout (expected):
external/gstreamer/
  prebuilts/<version>/<arch>/
    bin/gst-launch-1.0
    bin/gst-inspect-1.0
    bin/gst-plugin-scanner
    lib/*.so*
    lib/gstreamer-1.0/*.so*
  scripts/gst_env.sh
  tools/gen_android_bp.py   (this script)

Output:
  external/gstreamer/Android.bp
"""

from __future__ import annotations

import argparse
import os
from pathlib import Path
from typing import List, Optional, Tuple


BIN_MAP = {
    "gst-launch-1.0": ("gst_launch_1_0", "gst-launch-1.0"),
    "gst-inspect-1.0": ("gst_inspect_1_0", "gst-inspect-1.0"),
    "gst-plugin-scanner": ("gst_plugin_scanner", "gst-plugin-scanner"),
}

# Make these match your device.mk naming
ENV_SH_MODULE = "gst_runtime_env_sh"      # PRODUCT_PACKAGES uses this
ENV_SH_FILENAME = "gst-env"               # installed command name

# If your device.mk uses libgstvideo4linux2 (recommended), keep this special-case name.
V4L2_PLUGIN_SO = "libgstvideo4linux2.so"
V4L2_PLUGIN_MODULE = "libgstvideo4linux2"


def sanitize_module_token(s: str) -> str:
    """Convert to a safe token for module name (keep stem itself untouched)."""
    out = []
    for ch in s:
        if ch.isalnum():
            out.append(ch)
        else:
            out.append("_")
    token = "".join(out)
    while "__" in token:
        token = token.replace("__", "_")
    return token.strip("_")


def parse_stem_suffix(filename: str) -> Tuple[str, Optional[str]]:
    """
    Return (stem, suffix_prop).
    - For libcrypto.so           -> ("libcrypto", None)      # default .so
    - For libcrypto.so.1.1       -> ("libcrypto", ".so.1.1") # needs suffix property
    - For libopenjp2.so.7        -> ("libopenjp2", ".so.7")
    """
    if ".so." in filename:
        base, rest = filename.split(".so", 1)  # rest begins with ".<ver>"
        return base, ".so" + rest
    if filename.endswith(".so"):
        return filename[:-3], None
    # fallback (rare)
    stem, ext = os.path.splitext(filename)
    return stem, ext if ext else None


def list_so_files(dirpath: Path) -> List[Path]:
    """List *.so* files (including versioned .so.X) in a directory, sorted."""
    if not dirpath.is_dir():
        return []
    files = []
    for p in dirpath.iterdir():
        if not p.is_file():
            continue
        if ".so" not in p.name:
            continue
        files.append(p)
    return sorted(files, key=lambda x: x.name)


def gen_cc_prebuilt_binary(module_name: str, stem: str, rel_src: str) -> str:
    return f"""cc_prebuilt_binary {{
    name: "{module_name}",
    vendor: true,

    stem: "{stem}",
    compile_multilib: "64",
    srcs: ["{rel_src}"],
    strip: {{ none: true }},
    check_elf_files: false,
}}

"""


def gen_cc_prebuilt_shared(module_name: str,
                           stem: str,
                           rel_src: str,
                           rel_install: str,
                           suffix: Optional[str]) -> str:
    suffix_line = f'    suffix: "{suffix}",\n' if suffix else ""
    return f"""cc_prebuilt_library_shared {{
    name: "{module_name}",
    vendor: true,

    compile_multilib: "64",
    stem: "{stem}",
    relative_install_path: "{rel_install}",
    srcs: ["{rel_src}"],
{suffix_line}    strip: {{ none: true }},
    check_elf_files: false,
}}

"""


def gen_sh_binary(rel_src: str) -> str:
    return f"""sh_binary {{
    name: "{ENV_SH_MODULE}",
    vendor: true,
    src: "{rel_src}",
    filename: "{ENV_SH_FILENAME}",
}}

"""


def gen_phony(required: List[str]) -> str:
    req_lines = "\n".join([f'        "{x}",' for x in required])
    return f"""phony {{
    name: "gstreamer_runtime_bundle",
    required: [
{req_lines}
    ],
}}
"""


def autodetect_version(prebuilts_dir: Path) -> str:
    vers = [p.name for p in prebuilts_dir.iterdir() if p.is_dir()]
    if not vers:
        raise SystemExit(f"No version dirs under: {prebuilts_dir}")
    vers.sort()
    if len(vers) == 1:
        return vers[0]
    # prefer highest lexicographically; adjust if you use different scheme
    return vers[-1]


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--version", default=None, help="prebuilts version dir, e.g. 1.24.13")
    ap.add_argument("--arch", default="arm64", help="prebuilts arch dir, default: arm64")
    ap.add_argument("--out", default=None, help="output Android.bp path (default: <root>/Android.bp)")
    args = ap.parse_args()

    root = Path(__file__).resolve().parent.parent  # external/gstreamer
    prebuilts = root / "prebuilts"
    version = args.version or autodetect_version(prebuilts)
    arch = args.arch

    base = prebuilts / version / arch
    bin_dir = base / "bin"
    lib_dir = base / "lib"
    plugin_dir = lib_dir / "gstreamer-1.0"

    out_path = Path(args.out).resolve() if args.out else (root / "Android.bp")

    # --- start generating ---
    chunks: List[str] = []
    required: List[str] = []

    chunks.append("""package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

""")

    # sh env
    scripts_rel = "scripts/gst_env.sh"
    if not (root / scripts_rel).is_file():
        raise SystemExit(f"Missing: {root / scripts_rel}")
    chunks.append(gen_sh_binary(scripts_rel))
    required.append(ENV_SH_MODULE)

    # binaries
    for fname, (mod, stem) in BIN_MAP.items():
        src = bin_dir / fname
        if not src.is_file():
            raise SystemExit(f"Missing binary: {src}")
        rel = src.relative_to(root).as_posix()
        chunks.append(gen_cc_prebuilt_binary(mod, stem, rel))
        required.append(mod)

    # runtime libs (lib/*.so*)
    for so in list_so_files(lib_dir):
        if so.parent.name == "gstreamer-1.0":
            continue
        stem, suffix = parse_stem_suffix(so.name)

        # module name: gstlib_<stem>[_so_x_y]
        suffix_tag = ""
        if suffix and suffix != ".so":
            suffix_tag = "_so_" + sanitize_module_token(suffix.replace(".so.", "").replace(".so", ""))
        mod = f"gstlib_{sanitize_module_token(stem)}{suffix_tag}"

        rel = so.relative_to(root).as_posix()
        chunks.append(gen_cc_prebuilt_shared(
            module_name=mod,
            stem=stem,
            rel_src=rel,
            rel_install="gstreamer-private",
            suffix=suffix,
        ))
        required.append(mod)

    # plugins (lib/gstreamer-1.0/*.so*)
    for so in list_so_files(plugin_dir):
        stem, suffix = parse_stem_suffix(so.name)

        # keep a stable module name for v4l2 plugin to match device.mk
        if so.name == V4L2_PLUGIN_SO:
            mod = V4L2_PLUGIN_MODULE
        else:
            suffix_tag = ""
            if suffix and suffix != ".so":
                suffix_tag = "_so_" + sanitize_module_token(suffix.replace(".so.", "").replace(".so", ""))
            mod = f"gstplugin_{sanitize_module_token(stem)}{suffix_tag}"

        rel = so.relative_to(root).as_posix()
        chunks.append(gen_cc_prebuilt_shared(
            module_name=mod,
            stem=stem,
            rel_src=rel,
            rel_install="gstreamer-private/gstreamer-1.0",
            suffix=suffix,
        ))
        required.append(mod)

    # phony bundle (sorted for stable diffs)
    required_sorted = sorted(set(required))
    chunks.append(gen_phony(required_sorted))

    out_text = "".join(chunks)
    out_path.write_text(out_text, encoding="utf-8")

    print("OK:")
    print(f"  version : {version}")
    print(f"  arch    : {arch}")
    print(f"  out     : {out_path}")


if __name__ == "__main__":
    main()

执行脚本指令:

bash 复制代码
python3 gen_android_bp.py --version 1.24.13 --arch arm64

九、生成系统镜像并烧录

编译:

bash 复制代码
source build/envsetup.sh
lunch rk3562_atk-userdebug
./build.sh -Au -J16

烧录 update.img 到开发板。

十、 运行验证(设备端)

1、验证文件是否进镜像

bash 复制代码
ls -l /vendor/bin/gst-env
ls -l /vendor/bin/gst-launch-1.0
ls -l /vendor/lib64/gstreamer-private/libgstreamer-1.0.so
ls -l /vendor/lib64/gstreamer-private/gstreamer-1.0/libgstvideo4linux2.so

2、验证环境注入是否生效

bash 复制代码
gst-env
gst-env gst-launch-1.0 --version
gst-env gst-inspect-1.0 v4l2src
相关推荐
Quinn271 天前
正点原子 RK3562 Android14 Ubuntu 编译 SDK 环境准备:依赖、repo 与 Swap 配置一次搞定
linux·运维·ubuntu·mpu·正点原子·rk3562·arm linux
Quinn272 天前
正点原子 RK3562 Android14 双 IMX335 摄像头调试:从驱动链路到 Camera HAL 枚举排查
正点原子·rk3562·arm linux
Quinn272 天前
正点原子 STM32MP257 修复异核 FreeRTOS+OpenAMP 例程里 SysTick 延时异常的问题
stm32·嵌入式硬件·正点原子·arm linux
安庆平.Я4 个月前
STM32——MPU(内存保护)
stm32·单片机·嵌入式硬件·mpu
Industio_触觉智能5 个月前
瑞芯微RK3568平台FFmpeg硬件编解码移植及性能测试实战攻略
ffmpeg·rk3588·rk3568·瑞芯微·rk3562·rk3576
Industio_触觉智能6 个月前
瑞芯微RK3562平台FFmpeg硬件编解码移植及性能测试实战攻略
ffmpeg·视频编解码·瑞芯微·rk3562·触觉智能
是专家不是砖家6 个月前
rk3562 udp发送带宽500Mbps出现丢包问题
网络·网络协议·udp·rk3562·udp丢包·t507
Industio_触觉智能7 个月前
RK3562核心板/开发板RT-Linux系统实时性及硬件中断延迟测试
linux·嵌入式开发·瑞芯微·rk3562·rt linux·xenomai rt·preempt_rt
Industio_触觉智能7 个月前
瑞芯微RK35XX系列FFmpeg硬件编解码实测,详细性能对比!
ffmpeg·rk3588·rk3568·编解码·rk3562·rk3576