从 24FPS 到稳态 60:用 react-native-vision-camera 搭一条实时相机滤镜管线
【免费下载链接】react-native-vision-camera📸 A powerful, high-performance React Native Camera library.项目地址: https://gitcode.com/GitHub_Trending/re/react-native-vision-camera
预览画面刚叠上第一层滤镜,帧率就从 60 掉到 24。这篇文章用 react-native-vision-camera 的帧输出体系搭一条实时相机特效管线:从帧处理原理到 Skia 绘制、原生插件调用,再到性能排查和上架清单,读完你能把自己的 60FPS 管线接起来。
先让画面动起来
5 行代码跑通帧处理管线
先跳过原理,把它跑起来。依赖装四个:react-native-vision-camera、工作线程模块react-native-vision-camera-worklets、图形库@shopify/react-native-skia,以及两者的桥接包react-native-vision-camera-skia。
帧处理体系的核心是一个 worklet(把 JS 函数丢到原生工作线程执行的小包装)。onFrame会按相机帧率被原生管线反复调用,参数frame是 GPU 缓冲区的引用,不是像素的拷贝——这也是它能快的前提。
const frameOutput = useFrameOutput({ onFrame(frame) { 'worklet' console.log(`收到 ${frame.width}x${frame.height} 帧`) frame.dispose() // 归还缓冲区,必做 }, }) // <Camera device={device} isActive outputs={[frameOutput]} />重点看最后两行:Frame来自一个固定大小的缓冲池,不及时dispose()归还,池子耗尽后管线直接停摆,后面的帧全部被丢。旧版 API 叫useFrameProcessor(V1–V4 时期称 Frame Processors),现在统一改名useFrameOutput,概念没变。
逐像素循环为什么扛不住,Skia 怎么接
先说清楚瓶颈在哪。getPixelBuffer()可能触发一次 GPU→CPU 的下载,1080p RGB 一帧就是约 6MB 的跨端搬运;JS 里再 for 循环改一遍数组,又要几毫秒,16ms 的预算瞬间打光。简单滤镜这样做能撑住,滤镜一复杂就顶不住,该把画面上 GPU——这正是 Skia(硬件加速的 2D 图形引擎,跑在 GPU 上)的活。
<SkiaCamera />帮你省掉手动接管线:onFrame里通过render(...)回调拿到frameTexture(原始帧的纹理)和一块 Skia canvas,你在 canvas 上画什么,最终就渲染到什么。
Skia 三种基础画法:框、贴图、shader
第一步,画框——验证绘制链路通不通。注意 canvas 的坐标系就是 Frame 的像素坐标系,画出来直接对齐画面:
render(({ frameTexture, canvas }) => { canvas.drawImage(frameTexture, 0, 0) // 原始帧 paint.setColor(Skia.Color('#FF3B30')) canvas.drawRect(Skia.XYWHRect(0, 0, 1080, 1920), paint) })第二步,贴图——贴纸、AR 面具都靠它。源矩形和目标矩形不同,缩放只是改第二个参数的事:
const sticker = Skia.Image.MakeFromEncoded(require('./sticker.png')) // render 回调内 canvas.drawImageRect(sticker, Skia.XYWHRect(0, 0, sticker.width, sticker.height), Skia.XYWHRect(40, 40, 200, 200))第三步,shader 模糊——shader 是写给 GPU 执行的小程序,这里用 RuntimeEffect 做个复古色调,换成模糊核子就是美颜滤镜:
const effect = Skia.RuntimeEffect.Make(` uniform shader image; half4 main(vec2 p) { return image.eval(p) * half4(1, 0.8, 0.6, 1); } `) paint.setImageFilter(Skia.ImageFilter.MakeRuntimeShader( Skia.RuntimeShaderBuilder(effect, { image: frameTexture })))顺带一提pixelFormat:默认选'yuv',带宽比 rgb 省约 2.6 倍,Skia 也能直接吃 YUV;只有下游算法只认 RGB 时才切'rgb'。
把算法推回原生层
注册 → 回调 → 回传:原生插件的三段链路
检测模型一上,JS 层就到头了。原生插件补的就是这块。当前实现走 Nitro Module(把 TS 接口定义生成原生 Swift/Kotlin 绑定的代码生成器),链路分三段:
- 注册:在 spec 里定义一个接收
Frame的 HybridObject 接口,用nitrogen生成绑定,插件随模块系统自动加载; - 回调:
onFrameworklet 里直接plugin.call(frame),帧零拷贝进入原生; - 回传:结果直接作为返回值,或写入共享队列由 JS 侧读取。
JS 侧就这么两行,重点在call不经过任何桥接:
const frameOutput = useFrameOutput({ onFrame(frame) { faceDetector.call(frame) // 直接跨进原生 frame.dispose() }, })Android 侧收到的是HybridFrameSpec包装,转成 VisionCamera 公开的NativeFrame就能拿到真实像素缓冲:
class HybridFaceDetector : HybridFaceDetectorSpec() { fun call(frame: HybridFrameSpec) { val native = frame as NativeFrame // 解出真实像素缓冲 // 1. 把 native 像素喂给 MLKit 等检测库 // 2. 结果回传 JS(返回值或写入共享队列) } }Swift 侧形态完全一致(实现call(frame:)即可),细节看原生插件文档。
60FPS 稳不住的 6 个常见原因
⚠️ 按出现频率排:
- 处理分辨率跟着预览走:预览拉满 4K,特效管线也按 4K 算,GPU 先顶不住。对策:给 Frame Output 用约束(constraints)单独谈一个更低的处理分辨率。
- 重检测每帧都跑:人脸检测 15FPS 肉眼已经平滑。对策:用 runAtTargetFps(V4 及更早)或在 onFrame 里手动计帧跳帧。
- onFrame 里反复 new Skia 对象:每帧新建 Paint/Image,GC 锯齿直接拉高卡顿。对策:worklet 外创建一次,缓存复用。
- pixelFormat 选成 rgb:转换开销和带宽都是 yuv 的约 2.6 倍。对策:默认
'yuv',算法只认 RGB 再切换。 - 原生插件阻塞帧线程:
call(...)跑在帧工作线程上,里面同步磁盘、网络调用会卡住整条帧管线。对策:插件只算,重活丢后台线程异步回传。 - 缓冲没归还:
Frame不 dispose,或 Skia 导入的 NativeBuffer 不释放,池满管线停摆。对策:try/finally 里dispose();MakeImageFromNativeBuffer配image.dispose()+nativeBuffer.release()。
展开见 Frame Output 指南与 Performance。
上架前的发布清单
✅ 发布前逐项过一遍,五项都不长:
| 项目 | 检查点 |
|---|---|
| 相机权限 | iOS 的NSCameraUsageDescription、AndroidManifest 的CAMERA |
| Bitcode | 建议ENABLE_BITCODE=NO(原生插件代码可能不兼容) |
| 混淆规则 | proguard 保留原生模块包,如-keep class com.margelo.nitro.** { *; } |
| 最低系统版本 | 对照包 README 对齐 App 的 minSdk / minimumOSVersion |
| 真机验证 | 连跑 10 分钟,帧率稳、内存不爬升再发 |
再往深走
这套"相机吐缓冲、worklet 决定每帧干什么、重活下发生层"的架构,就是绝大多数相机特效类功能的地基。两个值得继续挖的方向:
- AI 风格迁移:在原生插件里挂 LiteRT / CoreML 模型,对 Frame 做人像分割和风格迁移,结果用 Skia shader 叠加回去;LiteRT 支持 YUV 直入,少一道转换。
- 多相机协同:用 CameraSession 同时接前后摄,做"后摄录屏 + 前摄人脸"这类双画面特效,见多相机文档。
细节以官方文档为准:Frame Output 指南、Skia 绘制、原生插件、FPS 调优。
【免费下载链接】react-native-vision-camera📸 A powerful, high-performance React Native Camera library.项目地址: https://gitcode.com/GitHub_Trending/re/react-native-vision-camera
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考