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
