☰
全志V3S音视频开发:U-Boot、内核、buildroot与Qt5全流程
2026/10/29 13:14:28 网站建设 项目流程

简介:这是一份围绕全志V3S芯片展开的嵌入式音视频开发实战文档,面向具备一定Linux与嵌入式基础、希望打通从系统移植到音视频应用开发的研发人员。内容从交叉编译环境、U-Boot与Linux内核配置、Buildroot根文件系统制作讲起,逐步覆盖2.4寸TFT-LCD屏幕、按键、声卡、摄像头等外设驱动,并延伸到SDL2与Qt5的移植及图形、音频、视频支持配置。后半部分结合C++ Qt5界面开发,给出音乐播放器、图片显示、视频播放器与摄像头模块的设计实现思路,同时涉及FFmpeg音视频解码处理。压缩包内为1个PDF文件,大小约12.26MB,按章节组织,便于顺序阅读和按模块查阅。已有329人学习,适合用作嵌入式音视频项目的系统化参考,也可帮助读者梳理环境搭建、外设驱动、GUI移植与媒体处理之间的完整链路,减少在工具链配置和模块联调中的盲目试错。

1. V3S 上的音视频开发,卡人的往往不是 Qt 而是前面那五步

拿到一块 V3S 核心板,多数人的第一反应是先把屏点亮、把 Qt 程序跑起来,结果两天过去还停在「内核能启动但网口没通」,文件传不进去,调试全靠拔插 SD 卡。这份《全志v3s音视频开发.pdf》的价值就在这里:它把交叉工具链、U-boot、5.4.31 内核、buildroot 根文件系统、ALSA 与 Qt5 界面按依赖顺序串成一条线,每一段都给到可执行的命令和要改的设备树节点。它适合手上已经有板子、会基本 Linux 命令、想把音乐播放器、图片浏览、视频播放和摄像头预览这几块界面真正跑起来的开发者;只想裸机点灯的话,前面几章的收益不大。我按文档顺序走了一遍,把几个必须自己补的参数和翻车点记在下面。

2. 工具链与源码树:qz 目录里七份文件怎么摆才不返工

先讲结论:这套流程里 80% 的返工来自目录和路径,不是来自代码。文档里给了七份源码,每一份的宿主目录不同、解压命令不同,一旦解压位置和后面填的路径对不上,你会看到一堆看似是「编译器坏了」的报错,其实是路径指错。

2.1 七份源码的用途与解压位置

把清单摊开看,大致分为三类:宿主机侧的开发环境(交叉工具链、Qt Creator)、板端运行环境(buildroot 生成的根文件系统)、板端固件(U-boot、内核)。ALSA 的两个包属于音频链路,既可以交给 buildroot 的包管理去拉,也可以自己交叉编译,两种方式我都试过,建议先用 buildroot 拉,出问题再退回手工编译,这样能少折腾一轮。

源码文件用途建议解压位置
gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf.tar.xz交叉工具链,全流程的唯一编译器~/qz/tools
u-boot-v3s-current.zip板级 bootloader,负责 SPL 与网络引导~/qz/tools
linux-5.4.31.tar.gz内核源码,含设备树编译~/qz/kernel
buildroot-2021.02.1.tar.xz生成 rootfs,同时负责 Qt5、ALSA 打包~/qz/tools
alsa-lib-1.2.2.tar.bz2音频应用依赖的底层库~/qz/driver
alsa-utils-1.2.2.tar.bz2aplay、amixer、arecord 等调试工具~/qz/driver
t-opensource-linux-x64-5.14.2.run宿主机 Qt Creator,写界面用家目录直接执行

目录骨架按「应用 / 工具 / 内核」三分,是我见过最不容易乱的一种分法,后面找 bin 目录、找 dtb、找 rootfs 镜像时都不用翻历史命令。

# 在宿主机家目录下建工程骨架:driver 放应用与音频库,tools 放工具链与 bootloader,kernel 放内核 mkdir -p ~/qz/{driver,tools,kernel} # 确认三个目录都建出来了,后面所有解压都落在这里 ls -l ~/qz

参数上没什么玄机,唯一要提醒的是别把整个工程放在/tmp或者带空格、带中文的路径下。buildroot 的 Kconfig 里填路径是纯文本,带空格会直接把参数截断,报出来的错是「toolchain not found」,新手很难联想到是目录名的问题。

2.2 交叉编译器:解压、取路径、写进 .bashrc

交叉编译器的本质是在 x86_64 宿主上跑,产出 ARM 指令。它之所以叫「交叉」,是因为编译器和产物不在同一个架构上。整条流程里所有涉及「编译」的步骤——U-boot、内核、Qt 应用、自己写的小测试——都必须用它,不能混用宿主机自带的 gcc,这一点后面避坑章节还会展开。

# 进入工具目录解压,压缩包是 xz 格式,用 -xvf 让 tar 自动识别 cd ~/qz/tools tar -xvf gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf.tar.xz # 进入 bin 目录,拿到绝对路径,这个路径后面要填到 .bashrc 和 buildroot 里 cd gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf/bin pwd

pwd输出的那一行务必原样复制,不要凭记忆手敲。我见过最典型的一次事故是把arm-linux-gnueabihf敲成了arm-linux-gnueabi(少一个 hf),结果编译器还是能找到,只是编出来的程序是软浮点 ABI,跑到板子上直接段错误,查了两小时才发现是工具链选错。

# 把工具链 bin 目录追加到 PATH 末尾,注意用绝对路径,别用 $HOME 简写 vim ~/.bashrc # 文件末尾追加下面这一行,路径换成你自己 pwd 的结果 export PATH=$PATH:/home/dev/qz/tools/gcc-linaro-6.5.0-2018.12-x86_64_arm-linux-gnueabihf/bin # 让配置立刻生效,不用重开终端 source ~/.bashrc # 验证 PATH 是否生效,能打印出版本号就说明找到了 arm-linux-gnueabihf-gcc -v

提示:~/.bashrc只对当前用户、交互式 shell 生效。如果你习惯用sudo编译,sudo默认不继承用户 PATH,会报 command not found,这时候用sudo -E或者干脆别用 sudo 编译源码。

2.3 三步验证:版本、文件类型、产物架构

配置完不要急着往下走,花两分钟做三个验证,能挡掉后面一大半的玄学问题。第一步看编译器自身版本,第二步看它是不是 ARM 架构的可执行文件(说明你没装错包),第三步编一个 hello 程序再看产物架构。

# 第一步:编译器版本,确认输出里有 6.5.0 和 arm-linux-gnueabihf arm-linux-gnueabihf-gcc -v # 第二步:看编译器本体是不是 x86-64 宿主机程序,正常应输出 x86-64 file $(which arm-linux-gnueabihf-gcc) # 第三步:编一个最小程序,看产物是不是 ARM cat > /tmp/hello.c <<'EOF' #include <stdio.h> int main(void) { printf("v3s ok\n"); return 0; } EOF arm-linux-gnueabihf-gcc /tmp/hello.c -o /tmp/hello_arm file /tmp/hello_arm readelf -h /tmp/hello_arm | grep Machine

第三步的输出里,file应该显示ELF 32-bit LSB executable, ARM, EABI5,readelf的 Machine 字段应该是ARM。如果 Machine 显示Advanced Micro Devices X86-64,说明你调用的还是宿主机 gcc,PATH 没生效或者被前面的路径覆盖了。这两个检查动作我到现在每次换机器都会做一遍,比事后在板子上看段错误日志省事得多。

file和readelf都不需要板子参与,纯宿主机侧验证,所以能在一分钟内给出确定结论。这也是我建议把它写进脚本的原因:换工具链版本、换同事接手、换 CI 机器,跑一遍就知道环境对不对。

2.4 路径写死与环境变量的取舍

文档里 buildroot 那一节填的是绝对路径,很多人会想改成$HOME/qz/...或者$(pwd),看着更「优雅」。我的经验是:在 Kconfig 里填路径时别玩变量,老老实实写绝对路径。原因很直接,buildroot 解析这个字段时不做 shell 展开,你写$HOME它就当字面量处理,报错信息又不会告诉你「变量没展开」,只会说找不到 gcc。

反过来,.bashrc里的 PATH 用绝对路径也没问题,但如果你经常在不同的工程之间切换,可以在 shell 里临时覆盖:export CROSS_COMPILE=arm-linux-gnueabihf-加export PATH=...:$PATH,用完source回去。这种做法在同时维护两个不同架构板子时很实用,避免工具链串台。

还有一点:Qt Creator 那个.run包是 x86_64 宿主机的 IDE,和交叉编译器是两回事。新手容易把它当成「板子上的 Qt」,其实它只负责在你电脑上写代码、配构建套件,真正跑在板子上的是 buildroot 里编出来的 Qt 库加上你交叉编译出的程序。IDE 的构建套件里要手动指定编译器为arm-linux-gnueabihf-g++,sysroot 指向 buildroot 的output/staging,这两个填错,Qt 项目连语法提示都是错的。

3. U-boot 与 5.4.31 内核:把 emac 网口从设备树里救出来

网络这条链是整份文档里最容易卡住的部分,因为它同时涉及 U-boot 的设备树、内核的设备树和两边的 menuconfig 选项,任何一处漏配,现象都是「网口不亮」或者「PHY 找不到」。我按「先让 U-boot 能 ping 通,再让内核能挂 NFS」的顺序拆。

3.1 U-boot 的 defconfig 与 GMAC 选项

V3S 的 U-boot 用现成的板级 defconfig 起步,这一步只是把.config生成出来,不改任何代码。

cd ~/qz/tools unzip u-boot-v3s-current.zip cd u-boot-v3s-current # 用板级默认配置生成 .config,ARCH 和 CROSS_COMPILE 必须显式给 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- LicheePi_Zero_defconfig

命令末尾打印configuration written to .config就说明成功,这句话只代表配置写进去了,不代表配置内容对。接着进 menuconfig 把 GMAC 打开,路径是Device Drivers -> Network device support -> Ethernet driver support -> Allwinner sun8i GMAC support,选成内置(<*>)而不是模块,U-boot 阶段没有模块加载机制。

# 打开菜单配置,确认 Allwinner sun8i GMAC support 是 <*> 而不是 < > make ARCH=arm menuconfig # 编译,-j 数量按宿主机核数给,U-boot 编得快,一般一两分钟 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j$(nproc) # 产物是带 SPL 的整包,烧录就烧这个文件 ls -l u-boot-sunxi-with-spl.bin

u-boot-sunxi-with-spl.bin这个名字里的 with-spl 很关键:V3S 上电后 BROM 会从 SD 卡的固定偏移读 SPL,再由 SPL 拉起 U-boot 主体。所以烧录时必须用整包,不能只烧u-boot.bin,否则板子完全无输出,看起来像「板子坏了」。

3.2 U-boot 侧设备树:emac 与 phy 节点

U-boot 认识的网口信息来自它自己那份 dts。V3S 用的是内部 PHY,所以要把 emac 节点打开并指向内部 phy0。

/* 文件:arch/arm/dts/sun8i-v3s-licheepi-zero.dts */ /* 在 aliases 里给网口起个别名,U-boot 命令行里就能直接引用 */ aliases { ethernet0 = &emac; }; /* 文件末尾追加:打开 emac,指定使用内部 PHY,MII 模式 */ &emac { phy = <&phy0>; phy-mode = "mii"; allwinner,use-internal-phy; allwinner,leds-active-low; status = "okay"; phy0: ethernet-phy@0 { reg = <1>; /* 内部 PHY 的 MDIO 地址是 1,不是 0 */ }; };

这里有个细节值得单独说:节点名叫ethernet-phy@0,但reg = <1>。节点名里的 0 只是命名习惯,真正决定 MDIO 地址的是reg。我第一次看这段的时候以为写错了,改成@1反而编译报警告。以reg为准,别动节点名。

另外还要在sun8i-v3s.dtsi的soc {}里补上引脚复用和 emac 控制器节点,引脚组包含 PD0 到 PD17 中除 PD6、PD11、PD14 之外的十几根线,驱动能力给 40mA,上下拉给 NO_PULL。这一段照抄即可,唯一要检查的是compatible字符串:U-boot 侧用的是allwinner,sun8i-h3-emac(复用 H3 的驱动),内核侧用的是allwinner,sun8i-v3s-emac,两边不一样,抄错会出现「驱动 probe 了但网口不工作」。

3.3 内核侧设备树:mdio-mux 与 int_mii_phy

内核 5.4.31 的 dts 结构和 U-boot 那份差别不小,V3S 的 PHY 挂在 mdio-mux 下面,而不是直接挂在 emac 上。这是很多人卡住的地方:照抄 U-boot 的phy = <&phy0>到内核里,编译能过,启动后 emac probe 失败,日志里是no phy found。

/* 文件:arch/arm/boot/dts/sun8i-v3s.dtsi,在 soc 节点内补 emac 与 mdio-mux */ emac: ethernet@1c30000 { compatible = "allwinner,sun8i-v3s-emac"; syscon = <&syscon>; reg = <0x01c30000 0x10000>; interrupts = <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "macirq"; resets = <&ccu RST_BUS_EMAC>; reset-names = "stmmaceth"; clocks = <&ccu CLK_BUS_EMAC>; clock-names = "stmmaceth"; phy-handle = <&int_mii_phy>; phy-mode = "mii"; status = "disabled"; mdio: mdio { compatible = "snps,dwmac-mdio"; }; mdio_mux: mdio-mux { compatible = "allwinner,sun8i-h3-mdio-mux"; mdio-parent-bus = <&mdio>; /* 同一时刻只有一个 MDIO 可用,内部 PHY 走通道 1 */ internal_mdio: mdio@1 { compatible = "allwinner,sun8i-h3-mdio-internal"; reg = <1>; int_mii_phy: ethernet-phy@1 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <1>; clocks = <&ccu CLK_BUS_EPHY>; resets = <&ccu RST_BUS_EPHY>; }; }; }; };

关键三点:compatible是 v3s 版本;phy-handle指向int_mii_phy而不是phy0;reg仍然是 1。板级 dts 里只需要把&emac的status打开、把phy-mode设成 mii、把allwinner,leds-active-low补上,其余交给 dtsi。

3.4 内核 defconfig、NFS 与 uImage 的 LOADADDR

内核配置分两步:先拉sunxi_defconfig拿到一份能启动的基线,再在 menuconfig 里补网络和 NFS。

cd ~/qz/kernel tar -xvf linux-5.4.31.tar.gz cd linux-5.4.31 # 基线配置,sunxi 系列通用 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- sunxi_defconfig # 补 GMAC 与 NFS 相关选项 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

menuconfig 里要动的项集中在两处。Device Drivers -> Network device support -> Ethernet driver support下面把STMicroelectronics devices和STMMAC Platform bus support打开,再把Allwinner sun8i GMAC support选成内置;Networking support -> Networking options下面把NFS client support以及 v2、v3、v4 打开,并勾上Root file system on NFS,这一项不开,内核启动到最后会报VFS: Unable to mount root fs。调试阶段用 NFS 挂根文件系统比反复烧 SD 卡快一个数量级,强烈建议先配通。

# 编 uImage,LOADADDR 是装载地址,V3S 的 DRAM 从 0x40000000 起,uImage 放 0x41000040 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j12 uImage LOADADDR=0x41000040 # 单独编设备树,产物在 arch/arm/boot/dts 下 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs # 拷到 tftp 目录,供 U-boot 网络加载 cp arch/arm/boot/uImage /tftpboot/ cp arch/arm/boot/dts/sun8i-v3s-licheepi-zero.dtb /tftpboot/

LOADADDR=0x41000040里的偏移 0x01000040 是给内核镜像留的位置,不能省。省掉的话 uImage 头里记录的加载地址会变成默认值,U-boot 的bootm会报Bad Magic Number或者加载到错误位置直接跑飞。

3.5 烧录与 U-boot 网络验证

烧录这一步用 dd,写的是整包,偏移 8KB。

# 先确认 SD 卡设备名,别写错成系统盘,写错不可恢复 lsblk # seek=8 表示从 8KB 偏移开始写,V3S 的 BROM 从这个位置读 SPL sudo dd if=u-boot-sunxi-with-spl.bin of=/dev/sdX bs=1024 seek=8 conv=fsync

烧完插卡上电,串口进 U-boot 命令行,设好四个网络参数再 ping 宿主机。这套参数里serverip是宿主机的 tftp 服务地址,ipaddr是板子地址,两者必须在同一网段。

# U-boot 命令行内执行 setenv ipaddr 192.168.10.202 setenv gatewayip 192.168.10.1 setenv serverip 192.168.10.201 setenv netmask 255.255.255.0 saveenv # ping 通说明 U-boot 侧 emac 驱动和 PHY 都正常 ping 192.168.10.201

host 192.168.10.201 is alive这一行出来,才说明 U-boot 侧的设备树和 menuconfig 都配对。ping 不通时先看网口灯:灯不亮查引脚复用,灯亮但不通查 IP 和网线,别一上来就怀疑驱动。这个判断顺序能省掉很多瞎改设备树的时间。

4. buildroot 根文件系统:外部工具链、ALSA 与 Qt5 的依赖顺序

rootfs 这章是整个流程的收口,Qt5、SDL2、alsa-lib 全在这里被编出来。配置项多,但真正影响成败的只有两组:Target options 和 Toolchain。前者决定 ABI,后者决定能不能找到 libc。

4.1 Target options:cortex-A7 与 EABIhf 必须和工具链一致

V3S 是 Cortex-A7 核,浮点走 VFPv3,ABI 是 EABIhf(硬浮点)。这四个值和上面那个arm-linux-gnueabihf工具链的前缀是一一对应的,任何一项不匹配,编出来的 rootfs 里的动态链接器路径都会和程序里记录的对不上,运行时就是No such file or directory,而这个报错其实是找不到/lib/ld-linux-armhf.so.3。

配置项取值对应关系
Target ArchitectureARM (little endian)对应 arm 前缀
Target Architecture Variantcortex-A7对应 V3S 内核
Target ABIEABIhf对应 gnueabihf 里的 hf
Floating point strategyVFPv3对应硬浮点指令集
ARM instruction setARM32 位指令,不是 Thumb

这五项定完,buildroot 才知道该用哪套头文件和库去匹配外部工具链。改动其中任何一项,都建议把output/目录整个删掉重编,增量编译在这种场景下不可信,残留的目标文件会带着旧 ABI 混进去。

4.2 External toolchain 的四个字段,错一个就编不出 libc

工具链选「外部预装」这条路,意味着 buildroot 不自建编译器,直接复用你解压出来的那套。要填的字段有四个,互相之间必须自洽。

cd ~/qz/tools tar -xvf buildroot-2021.02.1.tar.xz cd buildroot-2021.02.1 make ARCH=arm menuconfig

Toolchain菜单下的填写顺序是:类型选External toolchain,来源选Pre-installed toolchain,路径填pwd得到的那个 bin 目录的上一级(也就是不带/bin的根目录),前缀填arm-linux-gnueabihf(不带末尾的短横线),gcc 版本选 6.x,内核头文件版本选 5.4.x,C 库选 glibc。下面三个勾选项按需打开:SSP、RPC、C++ 支持,其中 C++ 必须打开,否则 Qt5 编不过。

最容易错的是路径:填到bin目录里,buildroot 会去bin/bin/arm-linux-gnueabihf-gcc找编译器,报错信息只说找不到工具链,不说找的是哪个路径。我一般的做法是填完之后在output/build/下随便找个包看Makefile里记录的TARGET_CC,确认它指向的确实是 qz 目录下那套。

4.3 包选择顺序:alsa-lib → alsa-utils → SDL2 → Qt5

buildroot 会自动处理依赖,但 Qt5 的选项组合很挑,选错模块组合会出现「编过了但板子上少个插件」的情况。我的选包顺序是:先音频底座,再多媒体,再 GUI。

# 仍在 buildroot 顶层目录,进 menuconfig 后按下面顺序勾选 # Target packages -> Audio and video applications # [*] alsa-utils # 提供 aplay / arecord / amixer,调试声卡靠它 # Target packages -> Libraries -> Audio/Sound # [*] alsa-lib # alsa-utils 的依赖,勾上 utils 会自动带上 # Target packages -> Libraries -> Graphics # [*] SDL2 # 视频与摄像头预览的画面输出可以走它 # [*] SDL2_image # 图片显示模块用 # Target packages -> Graphic libraries and applications -> Qt5 # [*] qt5base # 基础库 # [*] eglfs # 无桌面环境直接用 KMS/DRM 出图,V3S 上首选 # [*] linuxfb # 备用,显卡驱动不完整时退到 fbdev # [*] gif / jpeg # 图片格式支持,按界面需要选 # [*] qt5multimedia # 音乐与视频播放的接口层

有一点值得提前说:eglfs和linuxfb同时编进去不冲突,运行时用QT_QPA_PLATFORM环境变量切换。调试阶段两个都留着,比只编一个然后发现跑不起来再回来重编划算。V3S 的显示控制器能力有限,eglfs在部分屏幕时序下会黑屏,这时退到linuxfb往往能亮。

编译就是一条命令,时间取决于宿主机核数,第一次全量编 Qt5 大概要一到两小时。

# -j 数量不要超过宿主机核数太多,Qt5 编译吃内存,内存小的机器容易 OOM make -j$(nproc) # 产物目录,rootfs.tar 就是要烧到第二个分区的东西 ls -l output/images/

output/images/下会有rootfs.tar、zImage(如果开了内核选项)等文件。烧录方式有两种,一种是 dd 一个整盘镜像,另一种是把rootfs.tar直接解压到 SD 卡的 rootfs 分区。后者在调试阶段更灵活,换库不用重烧整卡。

# 把 rootfs 解到已挂载的 SD 卡根分区,注意目标路径末尾的斜杠不能少 sudo tar -xpf output/images/rootfs.tar -C /media/dev/rootfs/ sync

-p保留权限位,少了这个参数,/dev下的设备节点和busybox的 suid 位都会丢,启动后一堆权限报错。sync是必须的,SD 卡缓存没刷完就拔卡,文件系统大概率损坏。

4.4 第一次上电要看的三个地方

板子起来后别急着跑 Qt,先确认三件事。第一,cat /proc/cpuinfo能看到 ARMv7 Processor rev 5 之类的信息,说明内核和 CPU 对上了。第二,ip addr能看到 eth0 并且能 ping 通宿主机,说明内核侧 emac 设备树是对的。第三,aplay -l能列出声卡,说明 ALSA 这层通了。

第三点在 V3S 上经常翻车,因为声卡走的是 I2S 加外部 codec,设备树里没配 codec 节点就一个声卡都看不到。文档里声卡部分讲的是配置思路,具体 codec 型号按板子走,我这里只能说排查顺序:先看dmesg | grep -i codec,再看/proc/asound/cards,最后看设备树里 i2s 节点有没有status = "okay"。

5. 避坑与排查:五类翻车现场的现象、原因和解决

这一章全是踩过的坑,按「现象 → 原因 → 解决」写,遇到同款现象可以直接对号入座。

5.1 编译一切正常,板子上跑起来 Illegal instruction

现象:宿主机编译零警告,拷到板子上执行直接Illegal instruction,连main的第一行都没进去。原因通常是工具链前缀少了hf,或者 buildroot 里 Target ABI 选了 EABI 而不是 EABIhf,导致产物里用的是软浮点调用约定,而板子上的动态链接器是硬浮点版本。解决:先readelf -A 你的程序 | grep VFP看有没有 VFP 寄存器参数,再file看是不是EABI5,两边对不上就回去查工具链前缀和 buildroot 的 ABI 选项,改完整目录重编,别增量。

5.2 U-boot 能 ping 通,内核启动后 emac 找不到 PHY

现象:U-boot 阶段网络一切正常,内核起来后dmesg里刷no phy found或者stmmac: failed to probe。原因是内核和 U-boot 用的是两套 dts,U-boot 里phy = <&phy0>这种写法在内核里不成立,内核走的是 mdio-mux 结构。解决:按内核那份 dtsi 补mdio-mux、internal_mdio、int_mii_phy三层节点,phy-handle指向int_mii_phy,reg保持 1,compatible用allwinner,sun8i-v3s-emac。改完只重编 dtb,不用重编整个内核。

5.3 aplay 有设备但没声音

现象:aplay -l能列出 card 0,aplay test.wav不报错,喇叭没声。原因分三层:mixer 通道被静音、codec 供电或时钟没使能、I2S 引脚复用没配对。解决顺序是先用amixer sset 'Headphone' 80% unmute把常见通道打开,再amixer contents看有没有值为 0 的关键控件,然后dmesg里找 codec 的 probe 日志,最后才去查设备树引脚。别一上来就改 dts,我见过三次里有两次只是默认静音。

5.4 Qt5 程序起不来,报 eglfs 相关错误

现象:板子上执行 Qt 程序,报EGL Error: Could not create the EGL surface或者直接黑屏退出。原因是 eglfs 依赖的 DRM 设备节点没起来,或者屏幕时序不被内核认可。解决:先用QT_QPA_PLATFORM=linuxfb ./app验证程序本身没问题,能跑就说明是显示后端的事;再看/dev/fb0是否存在、fbset输出的分辨率对不对;最后才回头查设备树的 display 节点。调试阶段我一般常备一个启动脚本,里面用环境变量切后端,省得每次重编。

5.5 烧录后分区错乱、内核 panic 找不到 rootfs

现象:U-boot 能起来,内核启动到一半报VFS: Unable to mount root fs on unknown-block。原因通常是 rootfs 分区没格式化好、bootargs里的root=写错分区号,或者 SD 卡分区表和 U-boot 的偏移冲突。解决:先lsblk确认分区布局,bootargs里root=/dev/mmcblk0p2要和实际一致;再确认第一个分区从 1MB 或更大偏移开始,给 U-boot 留够空间;最后tar -xpf解 rootfs 后一定要sync。

6. 界面跑通之后的三个验证动作:延迟、帧率和音频时钟

Qt5 界面能显示,只是「能跑」,离「能用」还差一轮验证。我一般固定做三个动作,每个动作对应一个可量化指标,出问题能定位到具体一层。

验证项操作合格线不合格时先查
界面刷新延迟触摸按键后观察界面响应目视无可感延迟按键消抖参数、事件循环里有没有阻塞调用
视频帧率播放一段已知帧率的本地视频不低于 24fpsFFmpeg 解码线程数、linuxfb与eglfs后端选择
音频时钟播放 3 分钟音乐听是否逐渐失步无明显漂移I2S 主从模式、codec 时钟源、ALSA buffer 大小

视频这一项最容易被忽略。V3S 没有硬件解码,全靠软件解,分辨率一高就掉帧。我的做法是先把视频降到 480x272 左右,确认能稳定 24fps,再往上加分辨率找拐点,而不是一上来就播 1080p 然后怀疑驱动。解码线程数也不是越多越好,A7 单核跑多线程解码反而增加调度开销,avcodec的thread_count设成 1 到 2 通常更稳。

音频失步这一项更隐蔽。表现是播放前两分钟正常,后面人声越来越慢,最后干脆卡住。原因多半是 I2S 工作在从模式、codec 提供时钟,而 codec 的晶振精度不够,或者 ALSA 的 period 设得太小导致频繁 underrun。aplay -D hw:0,0 --period-size=1024 --buffer-size=8192这类参数可以先用命令行试,找到稳定值再写进程序。

还有一种情况是界面动画流畅但音频断续。这通常是 Qt 的事件循环和音频回调抢 CPU,把音频播放放到独立线程,用QAudioOutput的 pull 模式喂数据,比在主线程里 push 稳妥。这类问题没有通用参数,只能靠听和看dmesg里的 underrun 计数一点点调。

我第一次在这块板子上做音视频界面,图省事把解码、渲染、音频全塞在主线程里,结果界面一拖动窗口音乐就卡,查了一整天才意识到是事件循环被占住。从那以后,凡是涉及音视频的功能,我都强制先拆线程、再做界面,验证顺序也固定成「先音频时钟、再视频帧率、最后界面交互」。希望这些顺序上的经验,能帮你少走两天弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询