6、Camera 数据流 Pipeline:从Sensor到ISP再到编码器
大家好,我是你们的老朋友。今天我们来聊聊Camera Framework里最核心的一条主线——数据流Pipeline。
说白了,就是一张照片或者一段视频,从光线进入镜头,到最后存到手机里,中间到底经历了什么。我当年刚接触Android Camera时,最困惑的就是这堆Buffer到底怎么流转的。今天咱们一次性把它理清楚。
6.1 整体数据流概览
先看一张我画的流程图,帮你建立整体认知:
嗯,这张图你看懂了吗?从Sensor采集原始数据,到ISP做图像处理,再到BufferQueue做缓冲调度,最后送到编码器压缩存储。每一步都有讲究。
6.2 Sensor到ISP:原始数据的诞生
Sensor是Pipeline的起点。它把光信号转成电信号,输出RAW格式数据。我习惯把RAW比作「数字底片」——未经处理,信息最完整。
但RAW数据有个特点:它只包含每个像素的亮度值,颜色信息靠Bayer阵列排列。所以必须送到ISP去「显影」。
关键点:ISP做的事情包括黑电平校正、去马赛克、白平衡、色彩校正、Gamma校正、降噪、锐化等。说白了,就是把RAW变成人眼看着舒服的YUV或RGB图像。
我在项目中遇到过一个问题:某款Sensor输出的RAW数据有固定模式噪声,ISP参数没调好,拍出来的照片全是条纹。后来发现是黑电平校正的参考值不对。嗯,这种坑踩过一次就记住了。
6.3 BufferQueue机制:数据的中转站
ISP处理完的数据,不会直接扔给编码器。中间有个BufferQueue,它是整个Pipeline的「蓄水池」和「调度器」。
为什么需要它?你想想看,Sensor和ISP的产帧速度是固定的(比如30fps),但编码器的处理速度可能波动。如果没有BufferQueue,要么丢帧,要么卡死。
| 角色 | 组件 | 职责 |
|---|---|---|
| 生产者 | Camera HAL / ISP | 填充图像数据到Buffer |
| 消费者 | 编码器 / 预览Surface | 从Buffer取出数据消费 |
| 缓冲区 | BufferQueue | 管理Buffer的分配与流转 |
BufferQueue的核心接口就两个:dequeueBuffer和queueBuffer。生产者调用dequeueBuffer拿一个空Buffer,填好数据后queueBuffer还回去。消费者从队列里取走填好的Buffer,处理完再还回来。
我的经验:BufferQueue的深度一般设为3~5个Buffer。太少了容易丢帧,太多了延迟变大。我曾经在低端机上把深度从4改成6,结果拍照延迟从100ms飙到180ms。所以这个值要谨慎调。
6.4 Stream配置与Surface绑定
在Camera Framework里,Stream就是一条数据通道。每个Stream对应一个Surface,Surface就是数据的最终目的地。
举个例子:你要同时做预览和拍照,那就需要两个Stream:
- 预览Stream:绑定到PreviewSurface,输出YUV数据,分辨率1920x1080
- 拍照Stream:绑定到ImageReader Surface,输出JPEG数据,分辨率4000x3000
配置Stream时,需要指定格式、分辨率、使用场景等参数。我建议你在配置时特别注意格式匹配——如果Stream配置的是YUV,但Surface期望的是RGBA,那数据流就会断掉。
// 伪代码:配置Stream
CameraStreamConfig config;
config.streamType = STREAM_PREVIEW;
config.format = HAL_PIXEL_FORMAT_YCbCr_420_888;
config.width = 1920;
config.height = 1080;
config.usage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_RENDER;
// 绑定Surface
Surface previewSurface = new Surface(previewTexture);
session.setRepeatingRequest(captureRequest, callback, handler);
注意:Surface的创建和绑定必须在同一个线程中完成。我曾经因为跨线程操作Surface导致ANR,排查了半天才发现是线程安全问题。
6.5 DequeueBuffer与QueueBuffer流程
这是BufferQueue最核心的两个操作。我画个时序图帮你理解:
流程其实很简单:
- 生产者调用dequeueBuffer,从BufferQueue的空闲队列拿一个Buffer
- 生产者往Buffer里写入数据(比如一帧YUV图像)
- 生产者调用queueBuffer,把填好的Buffer放回BufferQueue的填充队列
- 消费者从填充队列取出Buffer,开始处理(比如编码)
- 消费者处理完后,释放Buffer,让它回到空闲队列
- 循环往复,每一帧都走这个流程
避坑指南:我曾经在调试4K录像时发现帧率只有15fps,正常应该是30fps。排查后发现是dequeueBuffer超时了——因为消费者处理太慢,空闲队列里没有Buffer可用。解决方案是增加BufferQueue深度,或者优化编码器性能。
6.6 从Buffer到编码器:最后一公里
数据从BufferQueue出来后,就进入编码器了。编码器把YUV数据压缩成H.264或H.265码流,然后封装成MP4文件。
这里有个细节:编码器通常需要输入Buffer是连续的内存块,但Camera输出的Buffer可能是分块的(比如Android的YV12格式)。所以中间可能需要做一次内存拷贝或格式转换。
我建议你在做性能优化时,重点关注这个环节。因为内存拷贝是耗CPU的,能避免就避免。有些平台支持直接传递GraphicBuffer给编码器,零拷贝,效率高很多。
小技巧:如果编码器支持,尽量使用OMX.google.h264.encoder的硬件加速版本。软件编码在1080P以上分辨率时,CPU占用率会飙到80%以上,手机发烫严重。
6.7 总结一下
Camera数据流Pipeline,说白了就是一条从Sensor到编码器的流水线。中间经过ISP处理、BufferQueue缓冲、Surface绑定,每一步都有坑,但也都有优化空间。
我个人觉得,理解BufferQueue机制是掌握Camera Framework的关键。你只要搞懂了dequeueBuffer和queueBuffer的流转逻辑,整个Pipeline就通了一半。
好了,今天的内容就到这里。记住,数据流是Camera的灵魂,Buffer是数据流的载体。把这两点吃透,后面学Stream、Session、Request都会轻松很多。
公众号:蓝海资料掘金营,微信deep3321