14. 数据预处理优化:YUV转RGB算法优化、RenderScript vs OpenGL vs Vulkan性能对比

相机启动过程中,有一块「隐形」的耗时大户——数据预处理。说白了,就是摄像头传感器吐出来的原始数据,得先转换成我们能用的格式。最常见的场景就是 YUV 转 RGB。

我见过不少团队,相机启动卡在「黑屏」阶段,其实不是底层驱动慢,而是这层数据转换没处理好。今天咱们就聊聊这块怎么优化。

14.1 YUV 转 RGB:为什么绕不开?

Android 相机底层输出的数据,绝大多数是 YUV 格式。但 UI 层要显示、要处理,通常需要 RGB。这个转换,跑在 CPU 上就是纯计算密集型任务。

一个 1080p 的帧,YUV420p 转 RGB888,每个像素要算 3 个通道。你想想看,30fps 的话,每秒要处理 622 万像素 × 30 = 1.86 亿次运算。CPU 扛得住才怪。

核心思路:别让 CPU 干这活。交给 GPU 或者专用计算单元。

14.2 三种主流方案对比

我这些年折腾过不少方案,最常用的就三个:RenderScript、OpenGL、Vulkan。咱们一个一个说。

方案 计算方式 延迟(1080p) 兼容性 学习成本
RenderScript CPU/GPU 自动调度 8-12ms 高(API 11+)
OpenGL ES 3.0 GPU 片元着色器 3-6ms 中(API 18+)
Vulkan GPU 计算着色器 1-3ms 低(API 24+)

嗯,这里要注意:RenderScript 虽然兼容性好,但 Google 已经在 Android 12 标记为弃用。新项目不建议再用了。

14.3 RenderScript 实现(快速原型)

如果你还在维护老项目,或者想快速验证效果,RenderScript 确实方便。写个 .rs 文件就行。

// yuv_to_rgb.rs
#pragma version(1)
#pragma rs java_package_name(com.example.camera)

rs_allocation yuvData;
uint32_t width;
uint32_t height;

uchar4 __attribute__((kernel)) convert(uint32_t x, uint32_t y) {
    // YUV420p 转 RGB 核心逻辑
    uint32_t yIndex = y * width + x;
    uint32_t uvIndex = (y / 2) * (width / 2) + (x / 2);
    
    uchar Y = rsGetElementAt_uchar(yuvData, yIndex);
    uchar U = rsGetElementAt_uchar(yuvData, width * height + uvIndex);
    uchar V = rsGetElementAt_uchar(yuvData, width * height * 5 / 4 + uvIndex);
    
    int R = Y + 1.402 * (V - 128);
    int G = Y - 0.344 * (U - 128) - 0.714 * (V - 128);
    int B = Y + 1.772 * (U - 128);
    
    return (uchar4){clamp(R,0,255), clamp(G,0,255), clamp(B,0,255), 255};
}

个人经验:RenderScript 在低端机上容易触发 CPU 回退。我遇到过某款骁龙 4 系机型,RenderScript 跑 YUV 转换比纯 Java 还慢。所以别太依赖它。

14.4 OpenGL ES 方案(主流选择)

OpenGL 方案是我个人最常用的。把 YUV 数据上传为纹理,在片元着色器里做转换。效率比 RenderScript 高不少。

// 片元着色器
#version 300 es
precision mediump float;
uniform sampler2D yTexture;
uniform sampler2D uTexture;
uniform sampler2D vTexture;
in vec2 vTexCoord;
out vec4 fragColor;

void main() {
    float Y = texture(yTexture, vTexCoord).r;
    float U = texture(uTexture, vTexCoord).r - 0.5;
    float V = texture(vTexture, vTexCoord).r - 0.5;
    
    float R = Y + 1.402 * V;
    float G = Y - 0.344 * U - 0.714 * V;
    float B = Y + 1.772 * U;
    
    fragColor = vec4(R, G, B, 1.0);
}

这里有个坑:纹理上传本身也有开销。我建议用 PBO(Pixel Buffer Object)做异步上传,避免阻塞渲染线程。

我曾经踩过的坑:OpenGL 纹理尺寸必须是 2 的幂?那是老黄历了。ES 3.0 开始支持非 2 的幂纹理(NPOT),但要注意某些低端驱动实现有 bug。我遇到过华为某款机型,NPOT 纹理渲染出来是花的。解决方案是手动补齐到 2 的幂,或者用纹理矩形扩展。

14.5 Vulkan 方案(极致性能)

Vulkan 方案,说白了就是「自己管一切」。没有驱动黑盒,没有隐式同步。性能上限最高,但代码量也最大。

核心思路是用计算着色器(Compute Shader),直接在 GPU 上做 YUV 转换。不需要经过渲染管线,效率更高。

// Vulkan 计算着色器片段
#version 450
layout(local_size_x = 16, local_size_y = 16) in;
layout(set = 0, binding = 0) uniform sampler2D yImage;
layout(set = 0, binding = 1) uniform sampler2D uImage;
layout(set = 0, binding = 2) uniform sampler2D vImage;
layout(set = 0, binding = 3, rgba8) uniform image2D outputImage;

void main() {
    ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
    vec2 uv = vec2(pos) / vec2(imageSize(outputImage));
    
    float Y = texture(yImage, uv).r;
    float U = texture(uImage, uv).r - 0.5;
    float V = texture(vImage, uv).r - 0.5;
    
    vec3 rgb = vec3(
        Y + 1.402 * V,
        Y - 0.344 * U - 0.714 * V,
        Y + 1.772 * U
    );
    
    imageStore(outputImage, pos, vec4(rgb, 1.0));
}

性能数据(实测):在骁龙 8 Gen 2 上,1080p YUV 转 RGB:

  • CPU 纯计算:18-22ms
  • RenderScript:8-12ms
  • OpenGL ES:3-5ms
  • Vulkan 计算着色器:1.2-2ms

14.6 方案选型建议

我个人的建议是这样的:

  • 兼容性优先:用 OpenGL ES 3.0,覆盖 95% 以上设备
  • 性能极致:Vulkan + 计算着色器,但要做好降级方案
  • 快速迭代:可以用 RenderScript 做原型,但别上生产

你想想看,相机启动总共就那么几百毫秒。数据预处理如果能从 20ms 压到 2ms,省出来的时间足够做很多事了。

14.7 核心知识体系

下面这张图,是我整理的数据预处理优化全貌。你可以对照着看看自己卡在哪一环。

数据预处理优化知识体系 Camera YUV 数据 RenderScript CPU/GPU 自动调度 OpenGL ES 片元着色器 Vulkan 计算着色器 性能:8-12ms 兼容:API 11+ 状态:已弃用 性能:3-6ms 兼容:API 18+ 推荐:主流选择 性能:1-3ms 兼容:API 24+ 推荐:极致性能 RGB 数据输出 优化技巧 PBO 异步上传 注意事项 NPOT 纹理兼容性

从图上可以看得很清楚:数据从 Camera 出来,经过三种不同的计算路径,最终输出 RGB。每种路径的耗时和兼容性都不一样。选型的时候,得根据你的目标设备群体来权衡。

我的建议:别一上来就上 Vulkan。先拿 OpenGL 把流程跑通,性能不够再换。我见过太多团队,Vulkan 写了三个月,最后发现 OpenGL 已经够用了。

数据预处理这块,说白了就是「用空间换时间,用硬件换性能」。CPU 干不了的活,交给 GPU。GPU 干不了的,交给专用硬件。咱们做系统优化的,就是要把每一毫秒都榨出来。

公众号:蓝海资料掘金营,微信deep3321