RK3562 Android 系统中集成 GStreamer CLI V4L2 插件的完整移植方案重点难点在于预编译产物整理、Android.bp 自动生成、vendor 路径安装、运行时环境变量注入以及 Android 动态链接 namespace 限制的排查。正点原子RK3562J开发板瑞芯微Linux开发板ARM工业控制AI人工智能RK3562 Android14 集成 GStreamer 1.24.13CLI V4L2 插件完整移植方案一、目标与约束1、目标2、约束/注意二、新建目录结构external/gstreamer三、下载 GStreamer Android prebuilts 与 Cerbero 源码1、GStreamer 1.24.13 Android prebuilts2、Cerbero 1.24.13 源码构建工具链与模块四、将 prebuilts 合入 SDK 目录五、使用 Cerbero 生成 CLI 工具与 V4L2 插件1、Cerbero bootstrap准备 build-tools 与依赖2、构建 gstreamer-1.0生成 CLI 核心库3、拷贝 Cerbero 产物到 SDK prebuilts4、构建 gst-plugins-good 的 video4linux2v4l2src六、将 gstreamer 模块加入系统镜像打包清单七、编写 GStreamer CLI 环境注入脚本gst-env八、生成/维护 external/gstreamer/Android.bp九、生成系统镜像并烧录十、 运行验证设备端1、验证文件是否进镜像2、验证环境注入是否生效一、目标与约束1、目标在 Android 系统镜像中集成 GStreamer 1.24.13 的 CLI 工具gst-launch-1.0gst-inspect-1.0gst-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、pluginsscripts/运行时环境注入脚本gst-envtools/用于生成/修补 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-agstreamer_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常见前置依赖Ubuntupython3、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 的 video4linux2v4l2src构建python3 cerbero-uninstalled-cconfig/cross-android-arm64.cbc-vbuild gst-plugins-good-1.0检查插件是否生成findcerbero-1.24.13/build/dist/android_arm64\-namelibgstvideo4linux2.so*-o-name*video4linux2*拷贝插件到 SDKcp-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-privateGST_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)UIDid-u 2/dev/nullif[-z${UID}];then UID0 fi export GST_REGISTRY_1_0/data/local/tmp/gst-registry-1.0.${UID}.binexport GST_REGISTRY_REUSE_PLUGIN_SCANNER0 export GST_DEBUG_NO_COLOR1# no args: print envif[$# -eq 0 ]; thenechoVND_LIB${VND_LIB}echoSYS_LIB${SYS_LIB}echoGST_RT_DIR${GST_RT_DIR}echoGST_PLUGINS_DIR${GST_PLUGINS_DIR}echoLD_LIBRARY_PATH${LD_LIBRARY_PATH}echoGST_PLUGIN_SYSTEM_PATH_1_0${GST_PLUGIN_SYSTEM_PATH_1_0}echoGST_PLUGIN_SCANNER${GST_PLUGIN_SCANNER}echoGST_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_MODULEgst_runtime_env_sh# PRODUCT_PACKAGES uses thisENV_SH_FILENAMEgst-env# installed command name# If your device.mk uses libgstvideo4linux2 (recommended), keep this special-case name.V4L2_PLUGIN_SOlibgstvideo4linux2.soV4L2_PLUGIN_MODULElibgstvideo4linux2defsanitize_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:tokentoken.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,restfilename.split(.so,1)# rest begins with .verreturnbase,.sorestiffilename.endswith(.so):returnfilename[:-3],None# fallback (rare)stem,extos.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.sonotinp.name:continuefiles.append(p)returnsorted(files,keylambdax:x.name)defgen_cc_prebuilt_binary(module_name:str,stem:str,rel_src:str)-str:returnfcc_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_linef suffix: {suffix},\nifsuffixelsereturnfcc_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:returnfsh_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])returnfphony {{ name: gstreamer_runtime_bundle, required: [{req_lines}], }} defautodetect_version(prebuilts_dir:Path)-str:vers[p.nameforpinprebuilts_dir.iterdir()ifp.is_dir()]ifnotvers:raiseSystemExit(fNo version dirs under:{prebuilts_dir})vers.sort()iflen(vers)1:returnvers[0]# prefer highest lexicographically; adjust if you use different schemereturnvers[-1]defmain()-None:apargparse.ArgumentParser()ap.add_argument(--version,defaultNone,helpprebuilts version dir, e.g. 1.24.13)ap.add_argument(--arch,defaultarm64,helpprebuilts arch dir, default: arm64)ap.add_argument(--out,defaultNone,helpoutput Android.bp path (default: root/Android.bp))argsap.parse_args()rootPath(__file__).resolve().parent.parent# external/gstreamerprebuiltsroot/prebuiltsversionargs.versionorautodetect_version(prebuilts)archargs.arch baseprebuilts/version/arch bin_dirbase/binlib_dirbase/libplugin_dirlib_dir/gstreamer-1.0out_pathPath(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_relscripts/gst_env.shifnot(root/scripts_rel).is_file():raiseSystemExit(fMissing:{root/scripts_rel})chunks.append(gen_sh_binary(scripts_rel))required.append(ENV_SH_MODULE)# binariesforfname,(mod,stem)inBIN_MAP.items():srcbin_dir/fnameifnotsrc.is_file():raiseSystemExit(fMissing binary:{src})relsrc.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.namegstreamer-1.0:continuestem,suffixparse_stem_suffix(so.name)# module name: gstlib_stem[_so_x_y]suffix_tagifsuffixandsuffix!.so:suffix_tag_so_sanitize_module_token(suffix.replace(.so.,).replace(.so,))modfgstlib_{sanitize_module_token(stem)}{suffix_tag}relso.relative_to(root).as_posix()chunks.append(gen_cc_prebuilt_shared(module_namemod,stemstem,rel_srcrel,rel_installgstreamer-private,suffixsuffix,))required.append(mod)# plugins (lib/gstreamer-1.0/*.so*)forsoinlist_so_files(plugin_dir):stem,suffixparse_stem_suffix(so.name)# keep a stable module name for v4l2 plugin to match device.mkifso.nameV4L2_PLUGIN_SO:modV4L2_PLUGIN_MODULEelse:suffix_tagifsuffixandsuffix!.so:suffix_tag_so_sanitize_module_token(suffix.replace(.so.,).replace(.so,))modfgstplugin_{sanitize_module_token(stem)}{suffix_tag}relso.relative_to(root).as_posix()chunks.append(gen_cc_prebuilt_shared(module_namemod,stemstem,rel_srcrel,rel_installgstreamer-private/gstreamer-1.0,suffixsuffix,))required.append(mod)# phony bundle (sorted for stable diffs)required_sortedsorted(set(required))chunks.append(gen_phony(required_sorted))out_text.join(chunks)out_path.write_text(out_text,encodingutf-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