news 2026/9/18 23:23:10

从 24FPS 到稳态 60:用 react-native-vision-camera 搭一条实时相机滤镜管线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从 24FPS 到稳态 60:用 react-native-vision-camera 搭一条实时相机滤镜管线

从 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 绑定的代码生成器),链路分三段:

  1. 注册:在 spec 里定义一个接收Frame的 HybridObject 接口,用nitrogen生成绑定,插件随模块系统自动加载;
  2. 回调onFrameworklet 里直接plugin.call(frame),帧零拷贝进入原生;
  3. 回传:结果直接作为返回值,或写入共享队列由 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()MakeImageFromNativeBufferimage.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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 23:16:59

用Python与正则解析.doc复习题,打造命令行自测工具

简介&#xff1a;人教版高一英语必修二总复习单项选择题是一份面向高一学生与英语教师的复习资料&#xff0c;针对必修二常考语法点和词汇搭配设计&#xff0c;可帮助练习者在考前快速梳理易错考点&#xff0c;也能为教师选题组卷或课堂小测提供现成素材。题目围绕高频短语、定…

作者头像 李华
网站建设 2026/9/18 23:15:35

BabelDOC PDF翻译教程:3分钟拿到双语对照版,公式和排版不动

BabelDOC PDF翻译教程&#xff1a;3分钟拿到双语对照版&#xff0c;公式和排版不动 【免费下载链接】BabelDOC Yet Another Document Translator 项目地址: https://gitcode.com/GitHub_Trending/ba/BabelDOC BabelDOC 是一个开源的 PDF 翻译工具&#xff0c;能把英文 P…

作者头像 李华
网站建设 2026/9/18 23:15:27

智能巡检机器人初识:人机认知对齐四步法

1. 为什么“初识”两个字比“智能巡检机器人”本身更值得深挖“初识智能巡检机器人”——这个标题乍看平平无奇&#xff0c;像极了某本教材第一章的节名&#xff0c;或是某场内部培训PPT的第一页。但恰恰是“初识”这两个字&#xff0c;暴露了当前行业最真实、也最容易被忽略的…

作者头像 李华