RK3588 摄像头图形应用开发笔记

电脑端写代码 → 交叉编译 → scp 到开发板 → Wayland/Weston 显示摄像头画面

这篇笔记记录了一次完整的 RK3588 摄像头图形应用开发过程。最终跑通的不是传统的 /dev/fb0 framebuffer 路线,而是基于 V4L2 采集 + GStreamer 管线 + Wayland/Weston 显示的现代化方案。


目录


完整数据链路

复制代码
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

推荐先拆出 CFLAGSLIBS,不要直接一行嵌套,方便排错:

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:未找到命令

原因是当前终端里的 CCPKG_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

原因是当时 CFLAGSLIBS 还没有设置。正确顺序是先取再编译:

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

相关推荐
NashSKY1 天前
RK3588 Linux SDK 编译、烧录与 MIPI 屏配置流程
linux·rk3588
是专家不是砖家2 天前
RK3588 下位机搜索不到问题排查
rk3588·can-oepn·声光报警器·udp收不到数据
郭涤生4 天前
不同主机之间网络通信-以太网连接复习
开发语言·rk3588
郭涤生4 天前
飞凌 RK3588 开发板同显 / 异显模式切换
c++·rk3588
楼兰公子4 天前
RK3588 + Buildroot + Linux 7.0** 环境的内核调试进阶课题
rk3588·kernel·进阶调试
楼兰公子5 天前
网络子系统学习与开发教程
rk3588·net
长安第一美人6 天前
工业级实时监控系统开发:PHP+ZMQ+JS 前后端分离架构全解析
前端·嵌入式硬件·架构·交互·rk3588·zmq后端
楼兰公子9 天前
SoC 原理图与 PCB 设计实战课程大纲
rk3588·rpi4b·kicad10
月光技术杂谈10 天前
深度解析:基于Docker跨架构构建RK3588嵌入式rootfs的原理、边界与最佳实践
docker·容器·rootfs·rk3588·openeuler·欧拉·文件系统构建