RK3562 Android 系统中集成 GStreamer CLI + V4L2 插件的完整移植方案,重点难点在于:预编译产物整理、Android.bp 自动生成、vendor 路径安装、运行时环境变量注入,以及 Android 动态链接 namespace 限制的排查。
正点原子RK3562J开发板瑞芯微Linux开发板ARM工业控制AI人工智能
RK3562 Android14 集成 GStreamer 1.24.13(CLI + V4L2 插件)完整移植方案
- 一、目标与约束
- 1、目标
- 2、约束/注意
- 二、新建目录结构(external/gstreamer)
- 三、下载 GStreamer Android prebuilts 与 Cerbero 源码
- 1、GStreamer 1.24.13 Android prebuilts
- 2、Cerbero 1.24.13 源码(构建工具链与模块)
- 四、将 prebuilts 合入 SDK 目录
- 五、使用 Cerbero 生成 CLI 工具与 V4L2 插件
- 1、Cerbero bootstrap(准备 build-tools 与依赖)
- 2、构建 gstreamer-1.0(生成 CLI + 核心库)
- 3、拷贝 Cerbero 产物到 SDK prebuilts
- 4、构建 gst-plugins-good 的 video4linux2(v4l2src)
- 六、将 gstreamer 模块加入系统镜像打包清单
- 七、编写 GStreamer CLI 环境注入脚本(gst-env)
- 八、生成/维护 external/gstreamer/Android.bp
- 九、生成系统镜像并烧录
- 十、 运行验证(设备端)
- 1、验证文件是否进镜像
- 2、验证环境注入是否生效
一、目标与约束
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 目录,并创建子目录:
mkdir-pexternal/gstreamer/prebuilts/1.24.13mkdir-pexternal/gstreamer/scriptsmkdir-pexternal/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 包):
- https://gstreamer.freedesktop.org/data/pkg/android/
说明:
- 该包通常包含 Android 平台的预编译库/插件(具体内容以包内为准)。
- 实际项目中常见做法是:以官方 prebuilts 为基础,再用 Cerbero 补齐 CLI/插件/依赖。
2、Cerbero 1.24.13 源码(构建工具链与模块)
下载地址:
- https://github.com/GStreamer/cerbero
说明:
- Cerbero 是 GStreamer 官方的跨平台构建系统,可生成 Android 的 gst-launch、gst-inspect、gst-plugin-scanner 以及多种插件(含 video4linux2)。
四、将 prebuilts 合入 SDK 目录
解压下载到的 GStreamer Android 包,将 arm64 目录拷贝到:
external/gstreamer/prebuilts/1.24.13/arm64示例:
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 与依赖)
python3 cerbero-uninstalled-cconfig/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 + 核心库)
./cerbero-uninstalled-cconfig/cross-android-arm64.cbc build gstreamer-1.03、拷贝 Cerbero 产物到 SDK prebuilts
将 Cerbero 的产物拷贝到 external/gstreamer/prebuilts/1.24.13/arm64/ 对应目录:
cp-acerbero-1.24.13/build/dist/android_arm64/bin/gst-launch-1.0\<SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/cp-acerbero-1.24.13/build/dist/android_arm64/bin/gst-inspect-1.0\<SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/cp-acerbero-1.24.13/build/dist/android_arm64/bin/gst-plugin-scanner\<SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/bin/cp-acerbero-1.24.13/build/dist/android_arm64/lib/*.so\<SDK_PATH>/external/gstreamer/prebuilts/1.24.13/arm64/lib/cp-acerbero-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)
构建:
python3 cerbero-uninstalled-cconfig/cross-android-arm64.cbc-vbuild gst-plugins-good-1.0检查插件是否生成:
findcerbero-1.24.13/build/dist/android_arm64\-name"libgstvideo4linux2.so*"-o-name"*video4linux2*"拷贝插件到 SDK:
cp-acerbero-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 中追加:
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)
文件路径:
<SDK_PATH>/external/gstreamer/scripts/gst_env.sh内容:
#!/system/bin/sh# external/gstreamer/scripts/gst_env.sh# Installed as /vendor/bin/gst-env# detect 64/32 bitif[-d/vendor/lib64];then VND_LIB=/vendor/lib64 SYS_LIB=/system/lib64elseVND_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_PATHLDLP="${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 pathsexport GST_PLUGIN_SYSTEM_PATH_1_0="${GST_PLUGINS_DIR}"export GST_PLUGIN_PATH_1_0="${GST_PLUGINS_DIR}"# plugin scannerif[-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 envif[$# -eq 0 ]; thenecho"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}"exit0 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
#!/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__importannotationsimportargparseimportosfrompathlibimportPathfromtypingimportList,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 namingENV_SH_MODULE="gst_runtime_env_sh"# PRODUCT_PACKAGES uses thisENV_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"defsanitize_module_token(s:str)->str:"""Convert to a safe token for module name (keep stem itself untouched)."""out=[]forchins:ifch.isalnum():out.append(ch)else:out.append("_")token="".join(out)while"__"intoken:token=token.replace("__","_")returntoken.strip("_")defparse_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."infilename:base,rest=filename.split(".so",1)# rest begins with ".<ver>"returnbase,".so"+restiffilename.endswith(".so"):returnfilename[:-3],None# fallback (rare)stem,ext=os.path.splitext(filename)returnstem,extifextelseNonedeflist_so_files(dirpath:Path)->List[Path]:"""List *.so* files (including versioned .so.X) in a directory, sorted."""ifnotdirpath.is_dir():return[]files=[]forpindirpath.iterdir():ifnotp.is_file():continueif".so"notinp.name:continuefiles.append(p)returnsorted(files,key=lambdax:x.name)defgen_cc_prebuilt_binary(module_name:str,stem:str,rel_src:str)->str:returnf"""cc_prebuilt_binary {{ name: "{module_name}", vendor: true, stem: "{stem}", compile_multilib: "64", srcs: ["{rel_src}"], strip: {{ none: true }}, check_elf_files: false, }} """defgen_cc_prebuilt_shared(module_name:str,stem:str,rel_src:str,rel_install:str,suffix:Optional[str])->str:suffix_line=f' suffix: "{suffix}",\n'ifsuffixelse""returnf"""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, }} """defgen_sh_binary(rel_src:str)->str:returnf"""sh_binary {{ name: "{ENV_SH_MODULE}", vendor: true, src: "{rel_src}", filename: "{ENV_SH_FILENAME}", }} """defgen_phony(required:List[str])->str:req_lines="\n".join([f' "{x}",'forxinrequired])returnf"""phony {{ name: "gstreamer_runtime_bundle", required: [{req_lines}], }} """defautodetect_version(prebuilts_dir:Path)->str:vers=[p.nameforpinprebuilts_dir.iterdir()ifp.is_dir()]ifnotvers:raiseSystemExit(f"No version dirs under:{prebuilts_dir}")vers.sort()iflen(vers)==1:returnvers[0]# prefer highest lexicographically; adjust if you use different schemereturnvers[-1]defmain()->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/gstreamerprebuilts=root/"prebuilts"version=args.versionorautodetect_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()ifargs.outelse(root/"Android.bp")# --- start generating ---chunks:List[str]=[]required:List[str]=[]chunks.append("""package { default_applicable_licenses: ["Android-Apache-2.0"], } """)# sh envscripts_rel="scripts/gst_env.sh"ifnot(root/scripts_rel).is_file():raiseSystemExit(f"Missing:{root/scripts_rel}")chunks.append(gen_sh_binary(scripts_rel))required.append(ENV_SH_MODULE)# binariesforfname,(mod,stem)inBIN_MAP.items():src=bin_dir/fnameifnotsrc.is_file():raiseSystemExit(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*)forsoinlist_so_files(lib_dir):ifso.parent.name=="gstreamer-1.0":continuestem,suffix=parse_stem_suffix(so.name)# module name: gstlib_<stem>[_so_x_y]suffix_tag=""ifsuffixandsuffix!=".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*)forsoinlist_so_files(plugin_dir):stem,suffix=parse_stem_suffix(so.name)# keep a stable module name for v4l2 plugin to match device.mkifso.name==V4L2_PLUGIN_SO:mod=V4L2_PLUGIN_MODULEelse:suffix_tag=""ifsuffixandsuffix!=".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()执行脚本指令:
python3 gen_android_bp.py--version1.24.13--archarm64九、生成系统镜像并烧录
编译:
sourcebuild/envsetup.sh lunch rk3562_atk-userdebug ./build.sh-Au-J16烧录 update.img 到开发板。
十、 运行验证(设备端)
1、验证文件是否进镜像
ls-l/vendor/bin/gst-envls-l/vendor/bin/gst-launch-1.0ls-l/vendor/lib64/gstreamer-private/libgstreamer-1.0.sols-l/vendor/lib64/gstreamer-private/gstreamer-1.0/libgstvideo4linux2.so2、验证环境注入是否生效
gst-env gst-env gst-launch-1.0--versiongst-env gst-inspect-1.0 v4l2src