2. GStreamer基础概念

嵌入式环境搭建,说白了就是给目标板子「定制」一套能跑的 GStreamer。跟 PC 上 apt install 完事不一样,嵌入式你得自己动手,从工具链到源码,再到裁剪,每一步都可能踩坑。我个人习惯先把整个流程拆成三块:工具链、编译、裁剪。咱们一块一块来。

核心思路: 交叉编译 = 在 PC 上编译,在 ARM/MIPS/RISC-V 上运行。工具链是桥,源码是砖,裁剪是去掉多余的水泥。

2.1 交叉编译工具链配置

工具链选不对,后面全白干。我见过不少新手拿着 Linaro 的 GCC 去编译 glib,结果链接阶段报一堆莫名其妙的错。嗯,这里要注意:工具链必须跟目标系统的 libc 版本、内核头文件、CPU 架构 严格匹配。

2.1.1 工具链获取方式

  • 官方 SDK: 厂家提供,最省心。比如 NXP 的 Yocto BSP、Rockchip 的 buildroot 输出。我建议优先用这个。
  • Linaro 预编译工具链: 适合 ARM 通用平台。下载 gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf.tar.xz 这类包。
  • crosstool-NG 自编译: 适合特殊架构或定制 glibc。我曾经为了一个 MIPS 老平台,用 crosstool-NG 折腾了两天才搞定。

2.1.2 环境变量配置

解压工具链后,关键是把 bin 目录加入 PATH,并设置 CC、LD 等变量。我个人习惯写一个 setenv.sh 脚本,每次编译前 source 一下:

#!/bin/bash
export TOOLCHAIN_PATH=/opt/gcc-linaro-7.5.0-arm-linux-gnueabihf
export PATH=$TOOLCHAIN_PATH/bin:$PATH

export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++
export LD=arm-linux-gnueabihf-ld
export AR=arm-linux-gnueabihf-ar
export STRIP=arm-linux-gnueabihf-strip

export SYSROOT=$TOOLCHAIN_PATH/arm-linux-gnueabihf/libc
export CFLAGS="--sysroot=$SYSROOT -march=armv7-a -mfpu=neon -mfloat-abi=hard"
export LDFLAGS="--sysroot=$SYSROOT"
小技巧: --sysroot 告诉编译器去哪找头文件和库。如果漏了,编译时可能找到 PC 上的 /usr/include,链接时就会报错。

2.2 GStreamer 源码编译

GStreamer 依赖一堆库:glib、libffi、zlib、libxml2 等等。你想想看,如果一个个手动编译,光依赖就能让你崩溃。我建议用 Meson 构建系统,它比 autotools 快得多,而且依赖管理清晰。

2.2.1 依赖库交叉编译

先编译基础库,顺序很重要:

  1. zlib: 最简单的,./configure --prefix=$SYSROOT/usr --host=arm-linux-gnueabihf 就行。
  2. libffi: 注意要启用 --with-sysroot。
  3. glib: 这是大头。Meson 交叉编译需要写一个 cross-file.txt。

这是我的 cross-file.txt 示例:

[binaries]
c = 'arm-linux-gnueabihf-gcc'
cpp = 'arm-linux-gnueabihf-g++'
ar = 'arm-linux-gnueabihf-ar'
strip = 'arm-linux-gnueabihf-strip'
pkgconfig = 'arm-linux-gnueabihf-pkg-config'

[host_machine]
system = 'linux'
cpu_family = 'arm'
cpu = 'armv7hl'
endian = 'little'

[properties]
sys_root = '/opt/gcc-linaro-7.5.0-arm-linux-gnueabihf/arm-linux-gnueabihf/libc'
pkg_config_libdir = '/opt/gcc-linaro-7.5.0-arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib/pkgconfig'

然后编译 glib:

meson setup builddir --cross-file cross-file.txt \
  -Dprefix=$SYSROOT/usr \
  -Dlibdir=lib \
  -Dbuildtype=release
ninja -C builddir
ninja -C builddir install
注意: 如果目标板子没有 FPU(软浮点),-mfloat-abi=soft 必须加上,否则 glib 的原子操作会崩溃。我曾经在一个 ARM926EJ-S 上吃过这个亏,排查了整整一下午。

2.2.2 GStreamer 核心编译

依赖搞定后,编译 GStreamer 本身反而简单。核心模块包括:

  • gstreamer: 核心框架
  • gst-plugins-base: 基础插件(appsrc、fakesink、typefind 等)
  • gst-plugins-good: 常用编解码封装(wavparse、qtdemux 等)
  • gst-plugins-bad: 实验性插件(需要时才加)
  • gst-rtsp-server: 如果要做流媒体服务

编译命令类似,以 gstreamer 为例:

meson setup builddir --cross-file cross-file.txt \
  -Dprefix=/usr \
  -Dlibdir=lib \
  -Dbuildtype=release \
  -Dgst_debug=false \
  -Dptp=disabled \
  -Dnet=disabled
ninja -C builddir
DESTDIR=/path/to/target/rootfs ninja -C builddir install
为什么用 DESTDIR? 这样安装到临时目录,方便后续打包成 .tar.gz 或 .ipk,不会污染 PC 的系统目录。

2.3 最小化裁剪策略

嵌入式设备存储空间金贵,Flash 可能只有 16MB 甚至 8MB。GStreamer 全编译出来动辄几十 MB,必须裁剪。我总结了一套「三步裁剪法」:

2.3.1 第一步:功能裁剪(Meson 选项)

编译时关掉不需要的功能:

Meson 选项 作用 裁剪建议
-Dgst_debug=false 关闭调试日志 Release 版必关,省 30% 体积
-Dptp=disabled 关闭 PTP 时钟同步 非音视频同步场景可关
-Dnet=disabled 关闭网络功能 纯本地播放可关
-Dpython=disabled 关闭 Python 绑定 嵌入式几乎不用
-Dexamples=disabled 不编译示例 必关
-Dtests=disabled 不编译测试 必关

2.3.2 第二步:插件裁剪(按需加载)

GStreamer 插件是动态库,每个 .so 文件都可以单独删除。我建议先编译所有插件,然后用 strip 和 rm 清理:

# 只保留必要插件
cd /path/to/target/rootfs/usr/lib/gstreamer-1.0
rm -f libgstalsa.so      # 不用 ALSA 音频
rm -f libgstpulse.so     # 不用 PulseAudio
rm -f libgstv4l2.so      # 不用 V4L2 视频采集
rm -f libgstx264.so      # 不用 H.264 编码
# ... 按需删除

# strip 所有保留的 .so
arm-linux-gnueabihf-strip --strip-unneeded *.so

一个典型的 IP Camera 场景,只需要保留:

  • libgstcoreelements.so(核心元素)
  • libgstapp.so(appsrc/appsink)
  • libgstrtp.so(RTP 打包)
  • libgstvideoparsersbad.so(H.264 解析)
  • libgstjpeg.so(JPEG 编码)

2.3.3 第三步:链接时裁剪(LTO + 符号表)

如果还嫌大,可以启用 LTO(链接时优化):

meson setup builddir --cross-file cross-file.txt \
  -Db_lto=true \
  -Dc_args="-ffunction-sections -fdata-sections" \
  -Dc_link_args="-Wl,--gc-sections -Wl,-s"

这些选项会去掉未使用的函数和数据段。我曾在某个项目中,靠 LTO 把 GStreamer 核心库从 2.1MB 砍到了 1.3MB。

裁剪效果参考(ARM Cortex-A7,256MB Flash):
全编译:~18MB(含所有插件)
功能裁剪后:~8MB
插件裁剪后:~3.5MB
LTO 后:~2.8MB

2.4 整体流程框架

下面这张图概括了从工具链到裁剪的完整流程,我建议你把它打印出来贴在工位上:

嵌入式 GStreamer 部署流程 嵌入式 GStreamer 部署流程 1. 工具链配置 2. 依赖库编译 3. GStreamer 编译 4. 最小化裁剪 5. 部署到目标板 裁剪三大策略 ① Meson 功能选项 ② 插件 .so 按需删除 ③ LTO + 符号表裁剪 ④ strip 去除调试信息 常见踩坑点 • sysroot 路径错误 • 软浮点/硬浮点不匹配 • glib 原子操作崩溃 • 插件依赖链断裂

2.5 验证与测试

编译部署后,别急着跑复杂 pipeline。先用最简单的命令验证:

# 在目标板子上执行
gst-launch-1.0 --version
gst-inspect-1.0 --plugin  # 查看已安装插件列表
gst-launch-1.0 fakesrc ! fakesink  # 测试核心框架是否正常

如果 fakesrc ! fakesink 能跑通,说明核心框架没问题。然后逐步加元素,比如 audiotestsrc ! fakesink 测试音频路径。我曾经遇到一个情况,fakesrc 正常,但 videotestsrc 报错,最后发现是 libgstvideo 库没拷全。嗯,这种问题只能靠耐心排查。

避坑指南: 我曾经在裁剪时删掉了 libgsttypefindfunctions.so,结果所有媒体文件都无法自动识别类型。这个插件虽然小,但必不可少。建议保留 coreelements、typefindfunctions、app 这三个基础插件,其他按需裁剪。

好了,嵌入式环境搭建就这些。工具链配好、依赖编好、裁剪到位,你的 GStreamer 就能在资源受限的板子上跑得飞起。记住:裁剪不是越狠越好,够用就行。