RK3588 多媒体设备与调试工具分析

张开发
2026/4/21 18:05:03 15 分钟阅读

分享文章

RK3588 多媒体设备与调试工具分析
一、设备路径总览基于RK3588的实际硬件架构各类设备的/dev路径如下┌─────────────────────────────────────────────────────────────────────────────────────┐ │ RK3588 设备路径映射表 │ ├─────────────────────────────────────────────────────────────────────────────────────┤ │ │ │ 摄像头设备 (V4L2) │ │ ├── /dev/video0 ── 原始数据流通道 (VICAP raw) │ │ ├── /dev/video1 ── 原始数据流通道 │ │ ├── /dev/video2 ── 原始数据流通道 │ │ ├── /dev/video3 ── 原始数据流通道 │ │ ├── /dev/video4-7 ── 缩放处理通道 (rkcif_scale) │ │ ├── /dev/video8-10 ── 工具调试通道 (rkcif_tools) │ │ ├── /dev/video11-17 ── ISP处理后输出 (彩色NV12/YUV) │ │ ├── /dev/video18 ── ISP统计数据 (给3A算法) │ │ ├── /dev/video19 ── 3A参数写入 │ │ ├── /dev/media0 ── VICAP拓扑控制 │ │ └── /dev/media1 ── ISP拓扑控制 │ │ │ │ 声卡设备 (ALSA) │ │ ├── /dev/snd/controlC0 ── 声卡0控制 (ES8388板载音频) │ │ ├── /dev/snd/pcmC0D0c ── 声卡0录音 │ │ ├── /dev/snd/pcmC0D0p ── 声卡0播放 │ │ ├── /dev/snd/controlC1 ── 声卡1控制 (HDMI音频) │ │ ├── /dev/snd/pcmC1D0p ── 声卡1播放 │ │ └── /dev/snd/controlC2 ── 声卡2控制 (DP音频) │ │ │ │ ️ 显示设备 (DRM) │ │ ├── /dev/dri/card0 ── 主显示卡 (DRM主设备) │ │ ├── /dev/dri/card1 ── 第二显示卡 (多屏扩展) │ │ ├── /dev/dri/renderD128 ── GPU渲染设备 │ │ └── /dev/dri/by-path/ ── 设备路径别名 │ │ │ │ 编解码设备 (MPP) │ │ ├── /dev/mpp_service ── MPP编解码服务 │ │ ├── /dev/rga ── RGA 2D加速设备 │ │ └── /dev/iep ── IEP图像处理 │ │ │ └─────────────────────────────────────────────────────────────────────────────────────┘二、设备枚举代码实现2.1 通用设备枚举框架/** * file device_enumerator.h * brief 统一设备枚举器 - 扫描所有摄像头/声卡/显示设备 */ ​ #ifndef DEVICE_ENUMERATOR_H #define DEVICE_ENUMERATOR_H ​ #include stdio.h #include stdlib.h #include string.h #include dirent.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/videodev2.h #include alsa/asoundlib.h #include xf86drm.h #include xf86drmMode.h ​ /* 摄像头设备枚举 */ ​ /** * brief V4L2设备类型 */ typedef enum { V4L2_TYPE_UNKNOWN, V4L2_TYPE_RAW_CAPTURE, // 原始数据 (video0-3) V4L2_TYPE_SCALER, // 缩放通道 (video4-7) V4L2_TYPE_TOOL, // 调试工具 (video8-10) V4L2_TYPE_ISP_MAINPATH, // ISP主路径 (video11) V4L2_TYPE_ISP_SELFPATH, // ISP自路径 (video12-17) V4L2_TYPE_ISP_STATS, // ISP统计 (video18) V4L2_TYPE_ISP_PARAMS // ISP参数 (video19) } v4l2_device_type_t; ​ /** * brief V4L2设备信息 */ typedef struct { char dev_path[32]; // /dev/videoX char name[64]; // 设备名称 v4l2_device_type_t type; // 设备类型 int card_index; // 声卡序号 (card X) int device_index; // 设备序号 int bus_info[32]; // 总线信息 uint32_t capabilities; // 设备能力 int width; // 当前宽度 int height; // 当前高度 uint32_t pixelformat; // 像素格式 } v4l2_device_info_t; ​ /** * brief 枚举所有V4L2设备 * param devices 输出设备数组 * param max_count 最大数量 * return 实际找到的设备数量 */ int enumerate_v4l2_devices(v4l2_device_info_t* devices, int max_count) { DIR* dir; struct dirent* entry; int count 0; dir opendir(/dev); if (!dir) { perror(opendir /dev); return -1; } while ((entry readdir(dir)) ! NULL count max_count) { // 只处理video设备 if (strncmp(entry-d_name, video, 5) ! 0) { continue; } snprintf(devices[count].dev_path, sizeof(devices[count].dev_path), /dev/%s, entry-d_name); // 打开设备获取信息 int fd open(devices[count].dev_path, O_RDWR); if (fd 0) { continue; } struct v4l2_capability cap; if (ioctl(fd, VIDIOC_QUERYCAP, cap) 0) { strncpy(devices[count].name, (char*)cap.card, sizeof(devices[count].name)); devices[count].capabilities cap.capabilities; // 根据驱动名称判断设备类型 if (strstr((char*)cap.driver, rkcif)) { // 解析video编号判断具体类型 int video_num atoi(entry-d_name 5); if (video_num 0 video_num 3) { devices[count].type V4L2_TYPE_RAW_CAPTURE; } else if (video_num 4 video_num 7) { devices[count].type V4L2_TYPE_SCALER; } else if (video_num 8 video_num 10) { devices[count].type V4L2_TYPE_TOOL; } } else if (strstr((char*)cap.driver, rkisp)) { int video_num atoi(entry-d_name 5); if (video_num 11) { devices[count].type V4L2_TYPE_ISP_MAINPATH; } else if (video_num 12 video_num 17) { devices[count].type V4L2_TYPE_ISP_SELFPATH; } else if (video_num 18) { devices[count].type V4L2_TYPE_ISP_STATS; } else if (video_num 19) { devices[count].type V4L2_TYPE_ISP_PARAMS; } } // 获取当前格式 struct v4l2_format fmt; fmt.type V4L2_BUF_TYPE_VIDEO_CAPTURE; if (ioctl(fd, VIDIOC_G_FMT, fmt) 0) { devices[count].width fmt.fmt.pix.width; devices[count].height fmt.fmt.pix.height; devices[count].pixelformat fmt.fmt.pix.pixelformat; } } close(fd); count; } closedir(dir); return count; } ​ /** * brief 打印V4L2设备信息 */ void print_v4l2_devices(v4l2_device_info_t* devices, int count) { printf(\n V4L2摄像头设备列表 \n); printf(%-15s %-30s %-15s %-10s %-10s\n, 设备节点, 设备名称, 类型, 分辨率, 格式); printf(--------------------------------------------------------\n); for (int i 0; i count; i) { const char* type_str; switch (devices[i].type) { case V4L2_TYPE_RAW_CAPTURE: type_str 原始数据; break; case V4L2_TYPE_SCALER: type_str 缩放通道; break; case V4L2_TYPE_TOOL: type_str 调试工具; break; case V4L2_TYPE_ISP_MAINPATH: type_str ISP主路径; break; case V4L2_TYPE_ISP_SELFPATH: type_str ISP子路径; break; case V4L2_TYPE_ISP_STATS: type_str ISP统计; break; case V4L2_TYPE_ISP_PARAMS: type_str ISP参数; break; default: type_str 未知; break; } char fourcc[5]; fourcc[0] devices[i].pixelformat 0xFF; fourcc[1] (devices[i].pixelformat 8) 0xFF; fourcc[2] (devices[i].pixelformat 16) 0xFF; fourcc[3] (devices[i].pixelformat 24) 0xFF; fourcc[4] \0; printf(%-15s %-30s %-15s %4dx%-4d %-10s\n, devices[i].dev_path, devices[i].name, type_str, devices[i].width, devices[i].height, fourcc); } } ​ ​ /* 声卡设备枚举 */ ​ /** * brief 声卡设备信息 */ typedef struct { int card_id; // card X char name[64]; // 声卡名称 char long_name[128]; // 详细名称 int pcm_count; // PCM设备数量 int playback_count; // 播放设备数量 int capture_count; // 录音设备数量 } alsa_device_info_t; ​ /** * brief 枚举所有ALSA声卡设备 */ int enumerate_alsa_devices(alsa_device_info_t* devices, int max_count) { int count 0; int card -1; printf(\n ALSA声卡设备列表 \n); // 使用snd_card_next遍历所有声卡 if (snd_card_next(card) 0 || card 0) { // 降级扫描/proc/asound/cards FILE* fp fopen(/proc/asound/cards, r); if (fp) { char line[256]; while (fgets(line, sizeof(line), fp) count max_count) { if (sscanf(line, %d [%[^]]]: %[^\n], devices[count].card_id, devices[count].name, devices[count].long_name) 3) { count; } } fclose(fp); } return count; } while (card 0 count max_count) { devices[count].card_id card; // 获取声卡信息 snd_ctl_t* ctl; char ctl_name[32]; snprintf(ctl_name, sizeof(ctl_name), hw:%d, card); if (snd_ctl_open(ctl, ctl_name, 0) 0) { snd_ctl_card_info_t* info; snd_ctl_card_info_alloca(info); snd_ctl_card_info(ctl, info); strncpy(devices[count].name, snd_ctl_card_info_get_name(info), sizeof(devices[count].name)); strncpy(devices[count].long_name, snd_ctl_card_info_get_longname(info), sizeof(devices[count].long_name)); snd_ctl_close(ctl); } // 统计PCM设备 devices[count].pcm_count 0; devices[count].playback_count 0; devices[count].capture_count 0; char dev_pattern[64]; snprintf(dev_pattern, sizeof(dev_pattern), /dev/snd/pcmC%uD*, card); DIR* dir opendir(/dev/snd); if (dir) { struct dirent* entry; while ((entry readdir(dir)) ! NULL) { if (strstr(entry-d_name, dev_pattern 10)) { // 简单匹配 devices[count].pcm_count; if (strstr(entry-d_name, p)) { devices[count].playback_count; } else if (strstr(entry-d_name, c)) { devices[count].capture_count; } } } closedir(dir); } count; snd_card_next(card); } return count; } ​ /** * brief 打印ALSA设备信息 */ void print_alsa_devices(alsa_device_info_t* devices, int count) { printf(\n ALSA声卡设备列表 \n); printf(%-8s %-20s %-30s %-8s %-8s\n, Card, 名称, 描述, 播放, 录音); printf(--------------------------------------------------------\n); for (int i 0; i count; i) { printf(card%-3d %-20s %-30s %3d个 %3d个\n, devices[i].card_id, devices[i].name, devices[i].long_name, devices[i].playback_count, devices[i].capture_count); } } ​ ​ /* 显示设备枚举 */ ​ /** * brief DRM显示设备信息 */ typedef struct { char drm_path[32]; // /dev/dri/cardX int card_id; // card ID int has_drm; // 是否支持DRM int connector_count; // 连接器数量 int crtc_count; // CRTC数量 int encoder_count; // 编码器数量 char connector_name[64]; // 连接器名称 (HDMI-A-1, DSI-1等) int width; // 当前分辨率宽度 int height; // 当前分辨率高度 int is_connected; // 是否连接显示器 } drm_device_info_t; ​ /** * brief 枚举所有DRM显示设备 */ int enumerate_drm_devices(drm_device_info_t* devices, int max_count) { int count 0; DIR* dir; struct dirent* entry; dir opendir(/dev/dri); if (!dir) { return -1; } while ((entry readdir(dir)) ! NULL count max_count) { if (strncmp(entry-d_name, card, 4) ! 0) { continue; } snprintf(devices[count].drm_path, sizeof(devices[count].drm_path), /dev/dri/%s, entry-d_name); int fd open(devices[count].drm_path, O_RDWR); if (fd 0) { continue; } // 获取DRM能力 drmModeRes* res drmModeGetResources(fd); if (res) { devices[count].has_drm 1; devices[count].connector_count res-count_connectors; devices[count].crtc_count res-count_crtcs; devices[count].encoder_count res-count_encoders; // 获取连接器信息 for (int i 0; i res-count_connectors; i) { drmModeConnector* connector drmModeGetConnector(fd, res-connectors[i]); if (connector) { if (connector-connection DRM_MODE_CONNECTED) { devices[count].is_connected 1; // 获取连接器类型名称 switch (connector-connector_type) { case DRM_MODE_CONNECTOR_HDMIA: snprintf(devices[count].connector_name, sizeof(devices[count].connector_name), HDMI-A-%d, connector-connector_type_id); break; case DRM_MODE_CONNECTOR_DSI: snprintf(devices[count].connector_name, sizeof(devices[count].connector_name), DSI-%d, connector-connector_type_id); break; case DRM_MODE_CONNECTOR_eDP: snprintf(devices[count].connector_name, sizeof(devices[count].connector_name), eDP-%d, connector-connector_type_id); break; default: snprintf(devices[count].connector_name, sizeof(devices[count].connector_name), Unknown-%d, connector-connector_type); break; } // 获取当前模式 if (connector-count_modes 0) { devices[count].width connector-modes[0].hdisplay; devices[count].height connector-modes[0].vdisplay; } } drmModeFreeConnector(connector); } } drmModeFreeResources(res); } close(fd); count; } closedir(dir); return count; } ​ /** * brief 打印DRM设备信息 */ void print_drm_devices(drm_device_info_t* devices, int count) { printf(\n DRM显示设备列表 \n); printf(%-15s %-5s %-10s %-10s %-10s %-15s %s\n, 设备节点, Card, 连接器, CRTC, 编码器, 输出端口, 分辨率); printf(--------------------------------------------------------\n); for (int i 0; i count; i) { printf(%-15s %-5d %-10d %-10d %-10d %-15s %dx%d\n, devices[i].drm_path, devices[i].card_id, devices[i].connector_count, devices[i].crtc_count, devices[i].encoder_count, devices[i].connector_name[0] ? devices[i].connector_name : 未连接, devices[i].width, devices[i].height); } } ​ #endif // DEVICE_ENUMERATOR_H三、调试工具分析3.1 V4L2摄像头调试工具集#!/bin/bash # v4l2_debug.sh - V4L2摄像头调试工具集 ​ echo V4L2摄像头调试工具 ​ # 1. 列出所有视频设备 echo 1. 列出所有视频设备: v4l2-ctl --list-devices ​ # 2. 查看设备详细能力 (指定video节点) echo -e \n2. 查看video0设备能力: v4l2-ctl -d /dev/video0 --all ​ # 3. 查看设备支持的像素格式 echo -e \n3. 查看支持的像素格式: v4l2-ctl -d /dev/video0 --list-formats-ext ​ # 4. 查看Media Controller拓扑 (关键!) echo -e \n4. 查看media0拓扑 (VICAP): media-ctl -d /dev/media0 --print-topology ​ echo -e \n5. 查看media1拓扑 (ISP): media-ctl -d /dev/media1 --print-topology ​ # 6. 设置视频格式 echo -e \n6. 设置视频格式为1920x1080 NV12: v4l2-ctl -d /dev/video0 --set-fmt-videowidth1920,height1080,pixelformatNV12 ​ # 7. 抓取一帧到文件 echo -e \n7. 抓取一帧到文件: v4l2-ctl -d /dev/video0 --stream-mmap --stream-count1 --stream-toframe.raw ​ # 8. 查看V4L2子设备格式 echo -e \n8. 查看子设备格式: media-ctl -d /dev/media1 --get-v4l2 rkisp0-isp-subdev:0 ​ # 9. 配置数据路由 echo -e \n9. 配置ISP数据路由: media-ctl -d /dev/media1 --set-v4l2 rkisp0-isp-subdev:0[fmt:SBGGR10/4224x3136] media-ctl -d /dev/media1 --set-v4l2 rkisp0-isp-subdev:2[fmt:YUYV8_2X8/1920x1080]3.2 ALSA声卡调试工具集#!/bin/bash # alsa_debug.sh - ALSA声卡调试工具集 ​ echo ALSA声卡调试工具 ​ # 1. 列出所有播放设备 echo 1. 列出所有播放设备: aplay -l ​ # 2. 列出所有录音设备 echo -e \n2. 列出所有录音设备: arecord -l ​ # 3. 查看声卡详细状态 echo -e \n3. 查看声卡0状态: cat /proc/asound/card0/status ​ # 4. 查看声卡PCM设备信息 echo -e \n4. 查看PCM设备信息: cat /proc/asound/card0/pcm0p/info ​ # 5. 查看声卡codec信息 echo -e \n5. 查看codec信息: cat /proc/asound/card0/codec#0 ​ # 6. 测试录音 (录制5秒) echo -e \n6. 测试录音5秒: arecord -D hw:0,0 -f S16_LE -r 48000 -c 2 -d 5 test.wav ​ # 7. 测试播放 echo -e \n7. 测试播放: aplay -D hw:0,0 test.wav ​ # 8. 查看ALSA配置拓扑 echo -e \n8. 查看ALSA配置: cat /proc/asound/cards ​ # 9. 使用amixer调节音量 echo -e \n9. 查看音量控制: amixer -c 0 scontrols amixer -c 0 get Master3.3 DRM显示调试工具集#!/bin/bash # drm_debug.sh - DRM显示调试工具集 ​ echo DRM显示调试工具 ​ # 1. 查看DRM设备列表 echo 1. 查看DRM设备: ls -la /dev/dri/ ​ # 2. 使用modetest查看显示信息 (关键工具!) echo -e \n2. 查看显示模式信息: modetest -D /dev/dri/card0 ​ # 3. 查看连接器信息 echo -e \n3. 查看连接器详细信息: modetest -D /dev/dri/card0 -c ​ # 4. 查看CRTC信息 echo -e \n4. 查看CRTC信息: modetest -D /dev/dri/card0 -e ​ # 5. 查看帧缓冲信息 echo -e \n5. 查看帧缓冲: modetest -D /dev/dri/card0 -f ​ # 6. 测试显示输出 (显示彩色条纹) echo -e \n6. 测试显示输出: modetest -D /dev/dri/card0 -s 3233:1920x1080 ​ # 7. 查看VOP状态 (RK3588专用) echo -e \n7. 查看VOP状态: cat /sys/kernel/debug/dri/0/summary ​ # 8. 查看当前显示模式 echo -e \n8. 查看当前显示模式: cat /sys/class/drm/card0-HDMI-A-1/modes ​ # 9. 强制设置分辨率 echo -e \n9. 设置分辨率: echo 1920x1080 /sys/class/drm/card0-HDMI-A-1/mode3.4 综合调试脚本#!/bin/bash # device_debug.sh - 综合设备调试脚本 ​ echo RK3588 设备调试报告 echo 时间: $(date) echo ​ # 1. 系统信息 echo 1. 系统信息 uname -a cat /proc/device-tree/model ​ # 2. 设备树信息 echo -e \n 2. 设备树信息 ls -la /sys/firmware/devicetree/base/__symbols__/ ​ # 3. 内存信息 echo -e \n 3. CMA内存状态 cat /proc/meminfo | grep -E CmaTotal|CmaFree|CmaAllocated ​ # 4. V4L2设备 echo -e \n 4. V4L2设备 v4l2-ctl --list-devices ​ # 5. Media拓扑 echo -e \n 5. Media0拓扑 (VICAP) media-ctl -d /dev/media0 --print-topology 2/dev/null || echo 无media0设备 ​ echo -e \n 6. Media1拓扑 (ISP) media-ctl -d /dev/media1 --print-topology 2/dev/null || echo 无media1设备 ​ # 6. 显示设备 echo -e \n 7. DRM显示设备 ls -la /dev/dri/ ​ # 7. 声卡设备 echo -e \n 8. ALSA声卡设备 cat /proc/asound/cards ​ # 8. MPP/RGA状态 echo -e \n 9. 硬件加速状态 ls -la /dev/mpp_service /dev/rga 2/dev/null ​ # 9. IRQ统计 echo -e \n 10. 相关IRQ统计 cat /proc/interrupts | grep -E rkcif|rkisp|rga|mpp|drm四、设备树配置与节点映射4.1 设备树路径查找RK3588的设备树文件位于内核源码目录# 查找当前使用的设备树 cat /proc/device-tree/model cat /proc/device-tree/compatible ​ # 设备树源码位置 (SDK中) ls kernel/arch/arm64/boot/dts/rockchip/rk3588*.dts ​ # 常见设备树文件 # - rk3588-evb1-v10.dts (评估板) # - rk3588-firefly-itx-3588j.dts (Firefly) # - rk3588-toybrick.dts (Toybrick) # - rk3588-lubancat.dts (鲁班猫)4.2 设备节点映射原理V4L2设备节点的创建遵循特定规律摄像头数据流路径: ┌─────────────┐ │ MIPI摄像头 │ (硬件) └──────┬──────┘ │ MIPI CSI信号 ▼ ┌─────────────┐ │ DPHY │ (物理层) └──────┬──────┘ │ ▼ ┌─────────────┐ │ CSI2 │ (协议解析) └──────┬──────┘ │ ▼ ┌─────────────┐ │ VICAP │ ──▶ /dev/video0~3 (原始Bayer) │ (rk-cif) │ ──▶ /dev/video4~7 (缩放) └──────┬──────┘ ──▶ /dev/video8~10 (工具) │ ▼ ┌─────────────┐ │ ISP │ ──▶ /dev/video11 (主路径) │ (rkisp) │ ──▶ /dev/video12~17 (子路径) └─────────────┘ ──▶ /dev/video18 (统计) ──▶ /dev/video19 (参数)4.3 设备节点对应关系表硬件模块设备节点数据格式用途VICAP原始通道/dev/video0-3Bayer RAW原始图像采集VICAP缩放通道/dev/video4-7Bayer RAW缩小分辨率采集VICAP工具通道/dev/video8-10Bayer RAW调试用ISP主路径/dev/video11NV12/YUV主图像输出ISP子路径/dev/video12-17NV12/YUV多路输出ISP统计/dev/video18元数据3A算法输入ISP参数/dev/video19元数据3A算法输出五、调试工具对比分析工具用途关键命令输出信息v4l2-ctlV4L2设备控制--list-devices,--all,--list-formats-ext设备列表、能力、格式media-ctlMedia Controller拓扑--print-topology,--set-v4l2数据流拓扑图modetestDRM显示测试-c,-e,-f连接器、编码器、CRTC信息aplay/arecordALSA音频测试-l,-L播放/录音设备列表amixerALSA混音控制scontrols,get,set音量、开关控制cat /proc/asound/*ALSA内核信息cards,pcm*/info声卡状态cat /sys/kernel/debug/dri/*/summaryDRM调试-VOP状态、图层信息六、总结设备类型设备路径枚举工具调试工具摄像头/dev/video*,/dev/media*v4l2-ctl --list-devicesv4l2-ctl,media-ctl声卡/dev/snd/*,/proc/asound/*aplay -l,arecord -laplay,amixer显示/dev/dri/card*ls /dev/dri/modetest关键调试逻辑先用v4l2-ctl --list-devices查看有哪些摄像头设备用media-ctl --print-topology查看数据流拓扑理解设备间连接关系用modetest -c查看显示连接器状态用aplay -l和arecord -l查看声卡设备

更多文章