ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

RK3588 Android12屏幕适配:方向、分辨率、密度与多屏调试

RK3588 Android12屏幕适配:方向、分辨率、密度与多屏调试 1. 先弄清RK3588 Android12里到底有几个阀门在管屏幕玩RK3588这板子的人十个里有八个在第一次点屏的时候被屏幕方向、分辨率、密度这三件事绊过。原因不复杂RK3588的显示子系统比早年的RK3288、RK3399复杂得多VOP从一代升到了2.0/3.0一颗芯片能同时挂HDMI0、HDMI1、DP、eDP、MIPI DSI0/DSI1、RGB/BT1120好几路输出每一路都能独立配方向和分辨率。你在设备树里改一个数未必能落到Android那一层你在build.prop里改一个属性未必能压过VOP的物理时序。所以真正动手之前得先把哪个参数在哪一层生效这条线捋清楚不然就是反复烧录、反复重启一天下来屏还是歪的。我自己第一次在RK3588上做竖屏一体机的时候就是这么过来的ro.sf.hwrotation设了90开机画面确实竖过来了但开机logo还是横的触摸也是反的密度更是让设置界面挤成一团。后来才明白这三件事其实是三个独立的问题分别由设备树、厂商HWC、Android框架三层各自管一段。1.1 从VOP到SurfaceFlinger一条显示链路的分层把RK3588 Android12的显示链路拆开看从上到下大致是这么几层应用层决定自己要画多大、要不要跟随系统旋转。WindowManager / ActivityManager根据user_rotation和屏幕的rotation设定窗口朝向。SurfaceFlinger把所有图层合成到一个buffer里它的输入尺寸由DisplayDevice的mDisplayWidth/mHeight决定。HWC硬件合成器Rockchip自己实现的HWC会读ro.sf.hwrotation在把图层交给VOP之前做一次硬件旋转。DRM/KMS驱动把最终buffer的格式、尺寸、时序告诉VOP。VOP2/VOP3真正的显示控制器输出时序给MIPI DSI、HDMI这些接口。Panel/桥接芯片最后落到物理屏。关键点在于ro.sf.hwrotation改的是HWC那一层dts里的panel-timing改的是VOP那一层而wm size改的只是SurfaceFlinger那一层。三者不在同一个位置所以它们的表现也完全不同——设备树改错了会黑屏HWC属性改错了会方向对但触摸错wm size改错了会糊。1.2 三个参数各自在哪儿生效优先级怎么排我习惯按改动粒度来排优先级从底层往上参数生效位置典型文件/命令影响范围分辨率物理VOP Panel时序kernel-5.10/arch/arm64/boot/dts/rockchip/xxx.dtsi整块屏全局屏幕方向物理HWC旋转ro.sf.hwrotation合成结果全局屏幕方向逻辑WindowManagersettings put system user_rotation应用层可见的朝向密度SurfaceFlingerro.sf.lcd_density/wm density所有按dp布局的界面逻辑分辨率SurfaceFlingerwm size缩放后的显示尺寸这里有个很多人会搞混的地方物理分辨率和逻辑分辨率是两码事。物理分辨率是VOP实际吐出去的像素数由设备树决定改不了运行时的逻辑分辨率是Android认为屏幕有多大wm size能改。你在一块1024x600的屏上执行wm size 1920x1080屏幕不会变清晰只会把1080p的画面缩放下来反而更糊——因为多了两次采样。注意wm size/wm density改的是persist.sys.display.size和persist.sys.display.density这类运行时属性重启后需要重新设。要做成永久生效还是得回到device.mk或system.prop里写ro.sf.lcd_density。2. 屏幕方向物理旋转和逻辑旋转不是一回事方向这件事最容易踩的坑就是看起来对了。你改完HWC旋转开机动画竖过来了以为搞定结果一进桌面发现截图是横的、相机预览是反的、坐标轴全乱。这是因为Android里方向至少有三个概念在同时起作用它们各自服务不同的目的。2.1 ro.sf.hwrotation改的是合成阶段的旋转ro.sf.hwrotation是Rockchip BSP里最常见的一个属性值一般是0、90、180、270。它的作用点在HWC里HWC收到SurfaceFlinger传来的图层后如果检测到这个属性不为0就会在合成时对输出做一次旋转再交给VOP。配置位置通常在device/rockchip/rk3588/xxx/system.prop 或者 device/rockchip/rk3588/xxx/BoardConfig.mk 里的 PRODUCT_PROPERTY_OVERRIDES写法很简单PRODUCT_PROPERTY_OVERRIDES \ ro.sf.hwrotation90但它的代价是旋转后的画面是硬件转出来的WindowManager并不知道。也就是说SurfaceFlinger的DisplayDevice依然是按照未旋转的宽高来算的只是物理输出被转了90度。这在竖屏一体机、广告机这类整机固定朝向的场景里够用但如果你还要支持传感器自动旋转就会打架。我个人的经验是如果整机是固定竖屏比如电梯广告机、KTV点歌屏用ro.sf.hwrotation90最省事如果要做平板形态、要支持自动旋转那就别用HWC旋转老老实实走user_rotation。2.2 开机默认朝向与运行时锁定的区别不想要HWC那套就得分两步走一步设默认朝向一步锁住不让它乱转。Android 12里运行时的方向由Settings.System.USER_ROTATION和ACCELEROMETER_ROTATION共同决定。手动指定# 关掉重力感应自动旋转 adb shell settings put system accelerometer_rotation 0 # 指定朝向00度190度2180度3270度 adb shell settings put system user_rotation 1也可以直接用wm命令adb shell wm set-user-rotation lock 1如果想让这个值在出厂时就定死可以在框架层改默认值或者更干净的做法是在产品配置里覆盖config_defaultRotation这类资源。Rockchip的BSP里一般会在frameworks/base/core/res/res/values/config.xml附近找到相关的默认值定义。但对于大多数RK3588项目来说更推荐的做法是在device.mk里写一个开机执行的脚本在init.rc里用on boot阶段把settings put执行一遍。这样既能保证默认朝向又不会把框架改得乱七八糟后续升级BSP也不用重新merge。2.3 旋转90度之后触摸坐标为什么必然错这是我最想强调的一点屏幕转了触摸不转坐标必然错位。原因很简单触摸屏报上来的是它在自己坐标系里的原始坐标比如一颗GT911物理上贴在屏上x轴对应屏的短边。你把显示内容转了90度但触摸驱动还是按原来的轴序上报于是你点左下角系统认为你点了左上角。解决办法在设备树里Rockchip的触摸节点一般支持这几个属性gt911 { compatible goodix,gt911; reg 0x5d; interrupt-parent gpio3; interrupts RK_PB2 IRQ_TYPE_LEVEL_LOW; reset-gpios gpio3 RK_PC4 GPIO_ACTIVE_LOW; irq-gpios gpio3 RK_PB2 GPIO_ACTIVE_HIGH; touchscreen-inverted-x; touchscreen-inverted-y; touchscreen-swapped-x-y; };touchscreen-swapped-x-y交换x和y轴旋转90/270度时基本都要加。touchscreen-inverted-x/touchscreen-inverted-y把某个方向翻过来对应旋转180度或者镜像的情况。90度和270度的区别往往就体现在这两个inverted上。我的做法是准备四个组合swapped关/inverted全关swapped开/inverted-x开swapped开/inverted-y开swapped开/inverted全关烧一遍试一次一般两轮就能定下来。实操心得如果面板方向是竖屏但你要显示横屏内容那么显示旋转和触摸旋转必须是同向抵消的。很多人只改显示不改触摸就会出现点哪都不对的诡异现象。定位这类问题时最快的办法是在开发者选项里打开指针位置直接看触点坐标跑哪去了。3. 分辨率DTS固定时序与运行时wm size的边界分辨率这块我见过太多人一上来就去搜RK3588怎么改分辨率然后被一堆wm size的答案带偏。wm size是调试用的不是量产的方案。真正决定分辨率的是设备树里的panel-timing。3.1 从panel-timing反推像素时钟RK3588的MIPI DSI或者RGB屏时序都写在panel-timing节点里。以一个1024x600的MIPI屏为例display-timings { native-mode timing0; timing0: timing0 { clock-frequency 51200000; /* 51.2MHz */ hactive 1024; hfront-porch 160; hback-porch 160; hsync-len 10; vactive 600; vfront-porch 12; vback-porch 23; vsync-len 1; hsync-active 0; vsync-active 0; de-active 0; pixelclk-active 0; }; };这几个数不能随便填它们之间是有关联的。像素时钟的计算公式是h_total hactive hfront-porch hback-porch hsync-len v_total vactive vfront-porch vback-porch vsync-len clock-frequency ≈ h_total × v_total × 刷新率拿上面的数代进去h_total 1024 160 160 10 1354 v_total 600 12 23 1 636 刷新率 51200000 / (1354 × 636) ≈ 59.45 Hz差不多60Hz合理。如果你把clock-frequency改成65000000而不动其它参数刷新率会飙到75Hz左右屏可能不亮或者抖动。所以改分辨率的时候必须同时按屏厂规格书重算这几组param而不是只改hactive/vactive。3.2 wm size只是改了逻辑尺寸不是改了屏wm size这个命令很多人当救命稻草用但它做的事只有一件告诉SurfaceFlinger我认为屏幕是这么大。# 查看当前逻辑分辨率 adb shell wm size # 输出类似Physical size: 1024x600 # 临时改成1280x800 adb shell wm size 1280x800 # 恢复 adb shell wm size reset改了之后会发生什么SurfaceFlinger会把合成输出从1280x800缩放到1024x600的物理帧上。画面不会更清晰只会更糊而且触摸坐标也要跟着换算。所以wm size只适合在调试阶段快速验证布局绝对不能作为量产方案。真正需要改物理分辨率的时候你要动的是panel-timing。而如果你用的是HDMI输出那就更简单——HDMI走的是EDID协商Rockchip的DRM驱动会读显示器的EDID自动挑一个支持的模式。想强制指定HDMI分辨率就在设备树或者命令行里指定mode比如# 查看HDMI支持的模式 cat /sys/class/drm/card0-HDMI-A-1/modes # 运行时强制指定 adb shell settings put global hdmi_resolution 1920x10803.3 VOP带宽和分辨率上限的实际约束RK3588有VOP2/VOP3多路输出。每一路输出都有带宽限制同时开多屏大分辨率的时候容易碰到天花板。大概的经验值是单个VOP图层在1920x108060Hz以内比较稳4K60Hz需要走VOP3或者专门的通道。如果同时开HDMI 4K和MIPI 1080p再加上多个视频图层叠加就可能出现撕裂、掉帧、甚至VOP报错。排查这个最直接的工具是Rockchip提供的debugfscat /sys/kernel/debug/dri/0/summary这个输出会列出当前VOP的配置包括每个plane的分辨率、格式、是否启用。如果你看到某个plane的格式是XR24这种带alpha的大格式又会显著增加带宽消耗。降带宽最有效的三招减少叠加层数、缩小分辨率、把不透明图层换成RGB565这类低带宽格式。注意VOP的带宽不是只算最终的显示分辨率而是所有参与合成的图层加在一起。一个全屏背景加两个半屏视频窗口实际带宽可能比单开4K还高。这是我在做多窗口视频墙项目时踩过的坑——单屏测试丝滑三窗口一起开就掉帧。4. density给多少才合适从PPI算起别凭感觉填密度这个参数很多人随手填240或者320结果界面要么巨大要么小到看不清。其实density是有计算依据的它来源于屏幕的PPI。4.1 手算PPI并映射到Android density档位PPI的计算公式是PPI sqrt(水平像素² 垂直像素²) / 屏幕对角线英寸数Android的density档位大致对应density值档位名典型PPI范围120ldpi~120160mdpi~160213tvdpi~213240hdpi~240320xhdpi~320480xxhdpi~480640xxxhdpi~640举几个RK3588项目里常见的屏7寸 1024x600sqrt(1024² 600²) / 7 1186.8 / 7 ≈ 169.5→ 取160或240看你的UI设计基准。8寸 1280x8001509.4 / 8 ≈ 188.7→ 常用213或240。10.1寸 1280x8001509.4 / 10.1 ≈ 149.4→160比较合适。15.6寸 1920x10802202.9 / 15.6 ≈ 141.2→160。21.5寸 1920x10802202.9 / 21.5 ≈ 102.5→120或160。这里有个经验法则PPI算出来之后向下取最近的档位通常比向上取更好用。因为向上取比如169取240会让界面元素整体放大一屏显示的东西变少向下取169取160界面更紧凑但对触摸精度要求高一点。工业控制屏我一般取大一号看得清消费平板我取小一号信息密度高。4.2 ro.sf.lcd_density和wm density的作用范围差异和分辨率一样density也有两层。ro.sf.lcd_density是只读属性在开机时被SurfaceFlinger读取作为初始density。它写在system.prop或者device.mk里PRODUCT_PROPERTY_OVERRIDES \ ro.sf.lcd_density240wm density是运行时的# 查看 adb shell wm density # 修改 adb shell wm density 240 # 恢复 adb shell wm density reset二者的关系是wm density会写到persist.sys.display.density覆盖掉ro.sf.lcd_density的初始值。所以调试阶段用wm density快速试确定之后就写回ro.sf.lcd_density这是标准流程。提示改完density之后有些系统应用尤其是Launcher和Settings需要重启进程才能完全生效adb shell am force-stop com.android.settings比整个重启快得多。4.3 改完density布局跑偏的定位顺序改density之后如果界面乱了别急着回退按这个顺序查先确认density真的生效了adb shell wm density看输出是不是你设的值。确认是不是smallestWidth问题很多应用会根据swNdp加载不同布局。density变了sw也跟着变可能从sw600dp跳到了sw720dp加载了完全不同的布局文件。看是不是自定义View写死了px这是最常见的坑。如果你的应用里大量用px而不是dpdensity一变全乱。这种问题改density是治标改代码才是治本。确认系统UI也一起变了状态栏、导航栏的高度是按density算的如果只有应用变了系统没变说明有地方缓存了旧值。我自己的项目里现在都强制一条规矩所有布局尺寸一律用dp和sp硬编码px的代码在review阶段直接打回。踩过两次这种坑之后density改动就是改一个数字的事再也不用担心布局。5. 多屏与产品差异化别把公共代码改成私货RK3588最大的卖点之一就是能同时驱动多路显示但这也带来了一个新问题主屏和副屏的分辨率、方向、密度往往是不同的而Rockchip的公共代码只能给一套默认值。5.1 主屏副屏的分辨率、方向独立配置在设备树这一层每一路输出都有自己的节点天然是独立的/* MIPI DSI0 主屏 */ dsi0 { status okay; }; dsi0_in_vp0 { status okay; }; /* HDMI0 副屏 */ hdmi0 { status okay; }; hdmi0_in_vp1 { status okay; };分辨率分别在各自的panel-timingMIPI和EDIDHDMI里决定。方向则要看HWC怎么处理——ro.sf.hwrotation是全局的对多屏来说就不太够用了。这时候一般有两种做法软件层处理由应用自己感知是哪块屏分别设置窗口朝向。改HWC逻辑让HWC根据display id来决定是否旋转。这需要改hardware/rockchip/hwcomposer下的代码工作量大但一劳永逸。如果不是必须两屏都竖屏我的建议是能不动HWC就不动优先用应用层适配改动可控、升级友好。5.2 HDMI热插拔时的参数重协商HDMI是可以热插拔的插上之后DRM会重新读EDID协商出一个新的分辨率。这个过程会触发一次hotplug事件SurfaceFlinger收到后要重新配置DisplayDevice。这里有三个常见的坑插上HDMI之后主屏花屏往往是VOP的VP分配冲突了两个输出抢了同一个VP。要检查dsi0_in_vp0、hdmi0_in_vp1这些绑定关系。拔掉HDMI之后应用崩溃应用没有处理onDisplayRemoved回调还在往已经消失的Display上画。热插拔后density变了HDMI的density有时候是按EDID里的物理尺寸算的如果EDID没写清楚会算出一个奇怪的值。可以在框架层强制覆盖。5.3 用overlay和产品级配置隔离改动这是我最想推荐的一条工程实践别直接改Rockchip的公共代码用产品级的overlay和配置覆盖。具体怎么做目录结构上每个产品建一个自己的目录比如device/rockchip/rk3588/product_a/。系统属性写在product_a/system.prop通过PRODUCT_PROPERTY_OVERRIDES引入。资源覆盖比如config_defaultRotation用RRORuntime Resource Overlay或者编译时overlay。设备树用rk3588-product_a.dts只在产品自己的dts里改屏相关的节点。这样做的好处是Rockchip更新BSP的时候你在公共代码里没有diffmerge起来几乎没有冲突。我做过一个项目前期图省事直接改了公共的system.prop后来BSP从Android 12升到13光是解冲突就花了两天血泪教训。6. 实机验证参数到底生效没有用这几条命令说话改完参数烧录最怕的就是看起来没问题。要确认参数真的生效了得靠命令去验证而不是靠眼睛看。6.1 debugfs和dumpsys的交叉验证我常用的验证组合是这几条# 1. 看VOP实际配置分辨率、格式、plane cat /sys/kernel/debug/dri/0/summary # 2. 看SurfaceFlinger认为的显示参数 adb shell dumpsys SurfaceFlinger | grep -A 20 DisplayDeviceInfo # 3. 看WindowManager里屏幕的实际尺寸和density adb shell dumpsys display | grep -E mBaseDisplayInfo|density|DisplayInfo # 4. 看当前朝向 adb shell dumpsys window | grep -E mRotation|mLastOrientationdri/0/summary这个输出特别有用它会明确告诉你VOP当前输出的mode是不是你DTS里写的那个。如果这里显示的模式和你配置的不一样那说明在DTS这一层就没生效改Android属性也没用。我遇到过一次DTS改了分辨率但summary里还是旧的。查了半天发现是设备树里有多个panel节点native-mode指错了实际生效的是另一个。这种问题只能靠debugfs抓出来光看代码是看不出的。6.2 花屏、黑屏、错位的排查顺序出现显示异常时我有一套固定的排查顺序从底层往上层推现象优先怀疑验证手段完全不亮背光、上电时序、DTS时钟量背光电压看dmesg里DSI初始化日志花屏、雪花时序参数错误、lane配置核对panel-timing和屏厂规格书颜色不对像素格式、DE/HSYNC极性检查de-active、pixelclk-active图像偏移porch参数不对微调hback-porch/vback-porch方向错HWC旋转配置看ro.sf.hwrotation触摸错位触摸轴序配置打开指针位置看坐标这张表是我这些年攒下来的基本覆盖了九成以上的屏问题。方向错和触摸错位是两回事一定要分开排查混在一起想会越查越乱。6.3 几个我在项目里反复踩到的具体坑最后分享几个具体的、文档里不会写的坑坑一改了ro.sf.hwrotation但开机logo没转。开机logo走的是uboot和kernel的启动画面跟Android的HWC无关。要转logo得在uboot的显示配置里改或者在kernel的logo节点里处理。这个我是被客户投诉了才知道的。坑二wm density改完dumpsys display里看还是旧值。因为dumpsys display读的是DisplayManagerService的缓存而density是SurfaceFlinger层改的。要看真实值用adb shell wm density。坑三同一块板子换一个批次的面板就不亮了。面板厂的固件版本不同时序稍有差异。解决办法一是把porch参数放宽一点留余量二是在DTS里加多个panel-timing运行时动态选。坑四横屏改竖屏之后摄像头预览拉伸。摄像头预览的尺寸是独立的跟屏幕方向没关系但应用如果用了全屏预览就会跟着屏幕比例变。这种问题要在Camera的preview size里单独配。坑五多屏的时候ro.sf.lcd_density对HDMI副屏也生效了。结果主屏正常副屏字大得离谱。这种场景下要在应用层用Configuration分别处理或者改HWC让每个display各自取density。说到底RK3588的屏幕配置不是改一个数字的事而是要顺着VOP、HWC、SurfaceFlinger、WindowManager这条链搞明白每个参数各自在哪儿说话。我现在的习惯是新项目点屏的时候先在一张纸上把这几层画出来把每个要改的参数标到对应的层上然后从下往上改、从下往上验。这样虽然前期慢一点但基本不会出现改了半天发现方向没对其实是触摸没对这种返工。毕竟板子烧一次要等好几分钟思路清楚比手快重要得多。
返回列表