电脑端写代码 → 交叉编译 → scp 到开发板 → Wayland/Weston 显示摄像头画面
这篇笔记记录了一次完整的 RK3588 摄像头图形应用开发过程。最终跑通的不是传统的 /dev/fb0 framebuffer 路线,而是基于 V4L2 采集 + GStreamer 管线 + Wayland/Weston 显示的现代化方案。
目录
- 完整数据链路
- 板端摄像头和显示链路确认
- 电脑端交叉编译环境
- 每次打开新终端后设置环境变量
- 源代码:rk_camera_preview.c
- 交叉编译命令
- [部署到 RK3588](#部署到 RK3588)
- 踩坑总结
- 把环境变量写成脚本
- 编译脚本
- 部署脚本
- 最终开发闭环
完整数据链路
MIPI CSI 摄像头
↓
RKISP / V4L2
↓
/dev/video-camera0(即 /dev/video22)
↓
GStreamer v4l2src
↓
NV12 1280×720
↓
waylandsink
↓
Weston / Wayland
↓
MIPI 屏幕
1. 板端摄像头和显示链路确认
1.1 显示端:不是 /dev/fb0,而是 Weston/Wayland
一开始我写了 fb_colorbar.c 去写 /dev/fb0,程序能运行,dd if=/dev/urandom of=/dev/fb0 也能写入,但屏幕没有任何变化。
后来确认系统里实际运行的是这些图形相关进程:
/usr/bin/weston
/usr/libexec/weston-keyboard
/usr/libexec/weston-desktop-shell
/opt/ui/systemui
这说明屏幕界面不是传统 framebuffer 直接显示,而是走的现代图形栈:
应用程序
↓
Wayland
↓
Weston
↓
DRM/KMS
↓
MIPI 屏幕
为了验证 Wayland 显示路径是通的,运行了官方 demo:
bash
export XDG_RUNTIME_DIR=/var/run
export WAYLAND_DISPLAY=wayland-0
weston-flower
weston-simple-shm
屏幕上分别出现了随机图形和颜色旋转变化,说明 Weston/Wayland 显示路径完全正常。
1.2 摄像头端:确认 /dev/video-camera0
板子上有很多 /dev/video* 节点,最终确认:
/dev/video-camera0 -> /dev/video22
/dev/video22 = rkisp_mainpath
v4l2-ctl 查询结果:
Driver name : rkisp_v6
Card type : rkisp_mainpath
Device : /dev/video-camera0
Format : NV12
Resolution : 1280x720
Size Image : 1382400 bytes
其中帧大小正好对应 NV12/YUV420:
1280 × 720 × 1.5 = 1382400 bytes
验证采集(抓 30 帧):
bash
v4l2-ctl -d /dev/video-camera0 \
--set-fmt-video=width=1280,height=720,pixelformat=NV12 \
--stream-mmap=4 \
--stream-count=30
输出了 30 个 <,说明摄像头能正常出帧。
保存一帧到文件:
bash
v4l2-ctl -d /dev/video-camera0 \
--set-fmt-video=width=1280,height=720,pixelformat=NV12 \
--stream-mmap=4 \
--stream-count=1 \
--stream-to=/userdata/frame_1280x720_nv12.yuv
文件大小 1.4M,符合预期。
最后用 GStreamer 成功显示画面:
bash
export XDG_RUNTIME_DIR=/var/run
export WAYLAND_DISPLAY=wayland-0
gst-launch-1.0 v4l2src device=/dev/video-camera0 ! \
video/x-raw,format=NV12,width=1280,height=720,framerate=30/1 ! \
waylandsink
这一步同时确认了三件事:
- 摄像头采集 OK
- GStreamer 管线 OK
- Wayland/Weston 显示 OK
2. 电脑端交叉编译环境
SDK 路径:
/home/nash/rk3588/rk3588_linux
⚠️ 踩坑预告 :最开始误以为工具链在
output/host,但实际没有这个目录。
正确的工具链目录是:
/home/nash/rk3588/rk3588_linux/buildroot/output/rockchip_atk_dlrk3588
对应的环境变量:
bash
RK_OUT=/home/nash/rk3588/rk3588_linux/buildroot/output/rockchip_atk_dlrk3588
RK_HOST=$RK_OUT/host
RK_SYSROOT=$RK_HOST/aarch64-buildroot-linux-gnu/sysroot
在 $RK_HOST/bin 里确认有这些工具:
aarch64-buildroot-linux-gnu-gcc -> toolchain-wrapper
aarch64-buildroot-linux-gnu-g++ -> toolchain-wrapper
aarch64-buildroot-linux-gnu-strip
aarch64-buildroot-linux-gnu-readelf
其中 aarch64-buildroot-linux-gnu-gcc 是 Buildroot 的 wrapper,应该优先使用它,而不是直接调用 .br_real。
3. 每次打开新终端后设置环境变量
每次重新开 Ubuntu 终端,都先执行:
bash
export RK_OUT=/home/nash/rk3588/rk3588_linux/buildroot/output/rockchip_atk_dlrk3588
export RK_HOST=$RK_OUT/host
export RK_SYSROOT=$RK_HOST/aarch64-buildroot-linux-gnu/sysroot
export PATH=$RK_HOST/bin:$PATH
export CC=$RK_HOST/bin/aarch64-buildroot-linux-gnu-gcc
export CXX=$RK_HOST/bin/aarch64-buildroot-linux-gnu-g++
export PKG_CONFIG=$RK_HOST/bin/pkg-config
export PKG_CONFIG_SYSROOT_DIR=$RK_SYSROOT
export PKG_CONFIG_LIBDIR=$RK_SYSROOT/usr/lib/pkgconfig:$RK_SYSROOT/usr/share/pkgconfig
export LD_LIBRARY_PATH=$RK_HOST/lib:$RK_HOST/usr/lib:$LD_LIBRARY_PATH
验证:
bash
echo "CC=$CC"
echo "PKG_CONFIG=$PKG_CONFIG"
$CC --version
$PKG_CONFIG --version
$PKG_CONFIG --cflags --libs gstreamer-1.0
正常时 pkg-config 会输出类似:
-I.../sysroot/usr/include/gstreamer-1.0
-I.../sysroot/usr/include/glib-2.0
...
-L.../sysroot/usr/lib
-lgstreamer-1.0 -lgobject-2.0 -lglib-2.0
4. 源代码:rk_camera_preview.c
电脑端创建项目:
bash
mkdir -p ~/rk3588/apps/rk_camera_preview
cd ~/rk3588/apps/rk_camera_preview
vim rk_camera_preview.c
完整源码:
c
#include <gst/gst.h>
#include <glib.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static GMainLoop *loop = NULL;
static GstElement *pipeline = NULL;
static void handle_sigint(int sig)
{
(void)sig;
if (loop) {
g_main_loop_quit(loop);
}
}
static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data)
{
(void)bus;
GMainLoop *main_loop = (GMainLoop *)data;
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_EOS:
g_print("End of stream.\n");
g_main_loop_quit(main_loop);
break;
case GST_MESSAGE_ERROR: {
GError *err = NULL;
gchar *debug_info = NULL;
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("GStreamer ERROR: %s\n", err ? err->message : "unknown error");
g_printerr("Debug info: %s\n", debug_info ? debug_info : "none");
if (err) {
g_error_free(err);
}
if (debug_info) {
g_free(debug_info);
}
g_main_loop_quit(main_loop);
break;
}
case GST_MESSAGE_WARNING: {
GError *err = NULL;
gchar *debug_info = NULL;
gst_message_parse_warning(msg, &err, &debug_info);
g_printerr("GStreamer WARNING: %s\n", err ? err->message : "unknown warning");
g_printerr("Debug info: %s\n", debug_info ? debug_info : "none");
if (err) {
g_error_free(err);
}
if (debug_info) {
g_free(debug_info);
}
break;
}
default:
break;
}
return TRUE;
}
int main(int argc, char *argv[])
{
const char *device = "/dev/video-camera0";
int width = 1280;
int height = 720;
int fps = 30;
if (argc >= 2) {
device = argv[1];
}
if (argc >= 4) {
width = atoi(argv[2]);
height = atoi(argv[3]);
}
if (argc >= 5) {
fps = atoi(argv[4]);
}
/*
* 当前 RK3588 Buildroot 系统里,Weston 的 socket 已确认在:
*
* XDG_RUNTIME_DIR=/var/run
* WAYLAND_DISPLAY=wayland-0
*
* 从 SSH 启动程序时,终端环境里可能没有这些变量,
* 所以这里自动补上。
*/
if (g_getenv("XDG_RUNTIME_DIR") == NULL) {
g_setenv("XDG_RUNTIME_DIR", "/var/run", TRUE);
}
if (g_getenv("WAYLAND_DISPLAY") == NULL) {
g_setenv("WAYLAND_DISPLAY", "wayland-0", TRUE);
}
signal(SIGINT, handle_sigint);
signal(SIGTERM, handle_sigint);
gst_init(&argc, &argv);
loop = g_main_loop_new(NULL, FALSE);
char pipeline_desc[1024];
snprintf(
pipeline_desc,
sizeof(pipeline_desc),
"v4l2src device=%s ! "
"video/x-raw,format=NV12,width=%d,height=%d,framerate=%d/1 ! "
"queue ! "
"waylandsink sync=false",
device,
width,
height,
fps
);
g_print("Starting RK3588 camera preview...\n");
g_print("Device: %s\n", device);
g_print("Format: NV12 %dx%d @ %d fps\n", width, height, fps);
g_print("XDG_RUNTIME_DIR=%s\n", g_getenv("XDG_RUNTIME_DIR"));
g_print("WAYLAND_DISPLAY=%s\n", g_getenv("WAYLAND_DISPLAY"));
g_print("Pipeline:\n%s\n", pipeline_desc);
GError *error = NULL;
pipeline = gst_parse_launch(pipeline_desc, &error);
if (!pipeline) {
g_printerr("Failed to create pipeline.\n");
if (error) {
g_printerr("Error: %s\n", error->message);
g_error_free(error);
}
g_main_loop_unref(loop);
return 1;
}
GstBus *bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
gst_bus_add_watch(bus, bus_call, loop);
gst_object_unref(bus);
GstStateChangeReturn ret = gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_printerr("Failed to set pipeline to PLAYING.\n");
gst_object_unref(pipeline);
g_main_loop_unref(loop);
return 1;
}
g_print("Camera preview is running. Press Ctrl+C to stop.\n");
g_main_loop_run(loop);
g_print("Stopping camera preview...\n");
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
g_main_loop_unref(loop);
return 0;
}
这个程序本质上等价于:
bash
gst-launch-1.0 v4l2src device=/dev/video-camera0 ! \
video/x-raw,format=NV12,width=1280,height=720,framerate=30/1 ! \
waylandsink
只是把它封装成了一个真正的 C 应用,并自动补全了 Wayland 环境变量。
5. 交叉编译命令
进入项目目录:
bash
cd ~/rk3588/apps/rk_camera_preview
推荐先拆出 CFLAGS 和 LIBS,不要直接一行嵌套,方便排错:
bash
CFLAGS="$($PKG_CONFIG --cflags gstreamer-1.0)"
LIBS="$($PKG_CONFIG --libs gstreamer-1.0)"
echo "$CFLAGS"
echo "$LIBS"
编译:
bash
$CC rk_camera_preview.c -o rk_camera_preview $CFLAGS $LIBS
成功后目录里会出现:
rk_camera_preview
rk_camera_preview.c
检查可执行文件 (建议用系统自带的 /usr/bin/file):
bash
/usr/bin/file rk_camera_preview
或者用交叉工具链的 readelf:
bash
$RK_HOST/bin/aarch64-buildroot-linux-gnu-readelf -h rk_camera_preview | grep Machine
期望输出:
Machine: AArch64
可选,压缩体积:
bash
$RK_HOST/bin/aarch64-buildroot-linux-gnu-strip rk_camera_preview
6. 部署到 RK3588
RK3588 已经能通过 WiFi/SSH 访问,Ubuntu 和 RK3588 在同一 WiFi 下可以用 SSH 和 scp 传文件,常用目标路径是 /userdata/。
在 Ubuntu 电脑端执行:
bash
scp rk_camera_preview root@192.168.0.135:/userdata/
登录开发板:
bash
ssh root@192.168.0.135
运行:
bash
cd /userdata
chmod +x rk_camera_preview
export XDG_RUNTIME_DIR=/var/run
export WAYLAND_DISPLAY=wayland-0
./rk_camera_preview
也可以显式传参:
bash
./rk_camera_preview /dev/video-camera0 1280 720 30
停止程序:Ctrl + C
7. 踩坑总结
坑 1:误以为工具链在 output/host
一开始执行:
bash
find output/host -name "*gcc"
报错:
find: 'output/host': 没有那个文件或目录
原因是 SDK 输出目录不是顶层 output/host,而是 buildroot/output/rockchip_atk_dlrk3588/host。正确的环境变量见第 2、3 节。
坑 2:find -type f 找不到 gcc
一开始用:
bash
find . -type f -name "aarch64-buildroot-linux-gnu-gcc"
没有结果。原因是 aarch64-buildroot-linux-gnu-gcc 是符号链接(指向 toolchain-wrapper),而 -type f 会过滤掉符号链接。
正确查法:
bash
ls -l $RK_HOST/bin | grep -Ei "aarch64|gcc|g\+\+|cc"
或者:
bash
find -L $RK_HOST/bin -maxdepth 1 \
\( -name "*gcc*" -o -name "*g++*" -o -name "*cc" -o -name "*c++" \) \
2>/dev/null
坑 3:pkg-config 找不到 libpkgconf.so.3
执行:
bash
$PKG_CONFIG --cflags --libs gstreamer-1.0
报错:
error while loading shared libraries: libpkgconf.so.3:
cannot open shared object file: No such file or directory
原因是 Buildroot 的 host 版 pkg-config/pkgconf 依赖 $RK_HOST/lib/libpkgconf.so.3,但宿主机动态库搜索路径没有包含 $RK_HOST/lib。
解决:
bash
export LD_LIBRARY_PATH=$RK_HOST/lib:$RK_HOST/usr/lib:$LD_LIBRARY_PATH
坑 4: C C 、 CC、 CC、PKG_CONFIG 环境变量丢失
曾经执行:
bash
$CC rk_camera_preview.c \
-o rk_camera_preview \
$($PKG_CONFIG --cflags --libs gstreamer-1.0)
结果报:
--cflags:未找到命令
rk_camera_preview.c:未找到命令
原因是当前终端里的 CC 或 PKG_CONFIG 变量为空,shell 把命令解析乱了。解决方法是每次打开新终端都重新执行完整环境变量设置,或者写成脚本(见第 8 节)。
坑 5:第一次编译缺少 gst/gst.h
曾经执行:
bash
$CC rk_camera_preview.c -o rk_camera_preview $CFLAGS $LIBS
报:
fatal error: gst/gst.h: No such file or directory
原因是当时 CFLAGS、LIBS 还没有设置。正确顺序是先取再编译:
bash
CFLAGS="$($PKG_CONFIG --cflags gstreamer-1.0)"
LIBS="$($PKG_CONFIG --libs gstreamer-1.0)"
$CC rk_camera_preview.c -o rk_camera_preview $CFLAGS $LIBS
坑 6:file rk_camera_preview 报 magic 文件错误
执行:
bash
file rk_camera_preview
报:
file: could not find any valid magic files!
这不是编译失败,而是因为 PATH 前面加了 $RK_HOST/bin,调用到了 Buildroot host 里的 file,它找不到自己的 magic 数据库。
解决:
bash
/usr/bin/file rk_camera_preview
或者:
bash
$RK_HOST/bin/aarch64-buildroot-linux-gnu-readelf -h rk_camera_preview | grep Machine
坑 7:weston-simple-dmabuf-v4l 默认设备不对
执行:
bash
weston-simple-dmabuf-v4l
报:
/dev/video0: No such device
原因是它默认使用 /dev/video0,但实际摄像头预览节点是 /dev/video-camera0 -> /dev/video22,并且默认格式是 NV12 1280×720,而 weston-simple-dmabuf-v4l 默认格式是 YUYV。
不过这不是主线问题,因为 GStreamer 已经验证可用。
8. 把环境变量写成脚本
为了避免每次手动 export,在 Ubuntu 电脑端创建:
bash
vim ~/rk3588/env_rk3588_gst.sh
写入:
bash
#!/bin/bash
export RK_OUT=/home/nash/rk3588/rk3588_linux/buildroot/output/rockchip_atk_dlrk3588
export RK_HOST=$RK_OUT/host
export RK_SYSROOT=$RK_HOST/aarch64-buildroot-linux-gnu/sysroot
export PATH=$RK_HOST/bin:$PATH
export CC=$RK_HOST/bin/aarch64-buildroot-linux-gnu-gcc
export CXX=$RK_HOST/bin/aarch64-buildroot-linux-gnu-g++
export PKG_CONFIG=$RK_HOST/bin/pkg-config
export PKG_CONFIG_SYSROOT_DIR=$RK_SYSROOT
export PKG_CONFIG_LIBDIR=$RK_SYSROOT/usr/lib/pkgconfig:$RK_SYSROOT/usr/share/pkgconfig
export LD_LIBRARY_PATH=$RK_HOST/lib:$RK_HOST/usr/lib:$LD_LIBRARY_PATH
echo "RK3588 cross compile environment loaded."
echo "RK_OUT=$RK_OUT"
echo "RK_HOST=$RK_HOST"
echo "RK_SYSROOT=$RK_SYSROOT"
echo "CC=$CC"
echo "PKG_CONFIG=$PKG_CONFIG"
以后使用:
bash
source ~/rk3588/env_rk3588_gst.sh
⚠️ 必须用
source,不能直接执行./env_rk3588_gst.sh,因为直接执行只会在子 shell 里生效,当前终端拿不到环境变量。
9. 编译脚本
在项目目录创建:
bash
cd ~/rk3588/apps/rk_camera_preview
vim build.sh
写入:
bash
#!/bin/bash
set -e
source ~/rk3588/env_rk3588_gst.sh
CFLAGS="$($PKG_CONFIG --cflags gstreamer-1.0)"
LIBS="$($PKG_CONFIG --libs gstreamer-1.0)"
echo "CFLAGS=$CFLAGS"
echo "LIBS=$LIBS"
$CC rk_camera_preview.c -o rk_camera_preview $CFLAGS $LIBS
$RK_HOST/bin/aarch64-buildroot-linux-gnu-strip rk_camera_preview || true
/usr/bin/file rk_camera_preview || true
$RK_HOST/bin/aarch64-buildroot-linux-gnu-readelf -h rk_camera_preview | grep Machine || true
echo "Build done."
赋予权限并编译:
bash
chmod +x build.sh
./build.sh
10. 部署脚本
在项目目录创建:
bash
vim deploy.sh
写入:
bash
#!/bin/bash
set -e
BOARD_IP=192.168.0.135
scp rk_camera_preview root@$BOARD_IP:/userdata/
echo "Deploy done."
echo "Run on RK3588:"
echo " cd /userdata"
echo " chmod +x rk_camera_preview"
echo " export XDG_RUNTIME_DIR=/var/run"
echo " export WAYLAND_DISPLAY=wayland-0"
echo " ./rk_camera_preview"
赋予权限并部署:
bash
chmod +x deploy.sh
./deploy.sh
11. 最终开发闭环
Ubuntu 电脑写 C 代码
↓
source 交叉编译环境
↓
aarch64-buildroot-linux-gnu-gcc 编译
↓
scp 到 RK3588 /userdata
↓
SSH 登录 RK3588
↓
运行程序
↓
通过 Wayland/Weston 在 MIPI 屏显示摄像头画面
核心命令浓缩版:
bash
# ===== Ubuntu 电脑端 =====
source ~/rk3588/env_rk3588_gst.sh
cd ~/rk3588/apps/rk_camera_preview
CFLAGS="$($PKG_CONFIG --cflags gstreamer-1.0)"
LIBS="$($PKG_CONFIG --libs gstreamer-1.0)"
$CC rk_camera_preview.c -o rk_camera_preview $CFLAGS $LIBS
scp rk_camera_preview root@192.168.0.135:/userdata/
bash
# ===== RK3588 端 =====
cd /userdata
chmod +x rk_camera_preview
export XDG_RUNTIME_DIR=/var/run
export WAYLAND_DISPLAY=wayland-0
./rk_camera_preview /dev/video-camera0 1280 720 30