
第 1 章讲了 RAW → YUV → JPEG 的概念。现在要讲真正的硬件实现——Qualcomm Spectra ISP(在 xxx 内部代号 xxx)是怎么把这些处理步骤组织成 IFE、BPS、IPE 三个硬件流水级的。
这章的重要性:不理解 ISP 管线,你就看不懂 chromatix tuning 文件、不知道 ISP overflow 是怎么回事、不知道为什么某些分辨率/帧率组合不支持。
关键认知:Preview 和 Snapshot 走的是不同的 ISP 路径。不是所有帧都经过 BPS。
xxx Spectra 480 的 IFE 硬件可以同时输出两路:
IFE 输出 1: Bayer RAW → BPS(全质量路径,Snapshot)
IFE 输出 2: YUV420 → IPE(快速路径,Preview/Video)
Sensor → IFE → IPE → Display/MediaCodec
↘
Stats Node
IFE 内部集成了轻量级的 Bayer→YUV 转换硬件,可以直接输出 YUV420,跳过 BPS。这条路径延迟最低,适合预览和录像。
Sensor → IFE → BPS → IPE → JPEG
↘
Stats Node
BPS 做完整的 Bayer 域处理(Demosaic、高级降噪、CCM),质量最高但耗时更长。这条路径只在拍照时使用。
| 模块 | 输入 | 做什么 | 预览用? | 拍照用? | 源码位置 |
|---|---|---|---|---|---|
| IFE | MIPI RAW | 预处理 + Stats | ✅ | ✅ | camx/src/hwl/ife/ |
| BPS | Bayer RAW | Demosaic + 降噪 + CCM | ❌ | ✅ | camx/src/hwl/bps/ |
| IPE | YUV/RGB | 后处理 + 缩放 | ✅ | ✅ | camx/src/hwl/ipe/ |
| JPEG | YUV | 编码 | ❌ | ✅ | camx/src/hwl/jpeg/ |
💡 从源码验证:Preview 管线的 Link 定义(
g_pipelines.h)只有IFE → IPE,没有 BPS 节点。BPS(Node ID 65539)只出现在 Snapshot/Reprocess 管线中。
IFE 是 ISP 管线的第一站,直接接收 Sensor 通过 MIPI 送来的 RAW 数据。
MIPI CSI 输入(RAW10 / RAW12 / RAW14)
│
① Black Level Subtraction(黑电平校正)
│ 传感器即使全黑也会输出非零值(暗电流)
│ 黑电平 = 镜头完全遮住时像素输出的平均值
│ RAW_corrected = RAW_original - black_level
│ 如果不做校正 → 图像发灰(黑色不黑)
↓
② Linearization(线性化)
│ 某些 sensor 的输出不是严格线性的
│ 通过查表(LUT)补偿非线性
│ IFE 内部有一个硬件 LUT 模块
↓
③ Bad Pixel Correction(坏点校正)
│ sensor 上某些像素可能永久损坏(静态坏点)或间歇异常(动态坏点)
│ 静态坏点: 生产时检测 → 写入 OTP → IFE 从 OTP 读取坏点表 → 校正
│ 动态坏点: 实时检测异常亮度像素 → 用邻居插值替代
↓
④ Lens Shading Correction(镜头阴影校正)
│ 镜头边缘进光量少于中心 → 边缘偏暗(暗角)
│ LSC = 为每个像素位置乘一个增益系数
│ 增益系数从 chromatix LSC table 加载(17×13 网格)
│ 中心增益 ≈ 1.0,边缘增益 ≈ 1.2~1.5
↓
⑤ Stats 提取(AE/AWB/AF 统计数据采集)
│ IFE 硬件从画面中提取统计信息
│ 不是全图:只采集预定义的 N×M 个 ROI 区域
│ AE Stats: 每个区域的亮度直方图
│ AWB Stats: 每个区域的 R/G、B/G 比例
│ AF Stats: 对比度值或相位差(PDAF 像素)
│ → 发给 Stats Node → 3A 算法使用
↓
Bayer RAW 输出 → 送往 BPS
// camx/src/core/camxnode.h
BufferCountForIFE = 4; // IFE 是入口,需要更多 Buffer
为什么是 4?
→ BPS 在用一个,Stats Node 在用另一个
→ Sensor 在写入第三个,第四个作为备用
→ 少于 4 个,流水线容易堵
camx/src/hwl/ife/ → IFE Node 的主实现
camx/src/hwl/xxx/ → xxx ISP 的寄存器定义
(xxx 是 xxx 上 ISP 的内部代号)
IFE Node 的核心工作:
1. 接收 MIPI 数据 → 通过 CSL 从 Kernel 获取 Buffer
2. 配置 IFE 硬件模块 → 写 ISP 寄存器(BLS/BPC/LSC 参数来自 chromatix)
3. 触发硬件处理 → 等待 Fence 信号
4. 提取 Stats → 发给 Stats Node
BPS 做的是 ISP 中计算量最大的工作——只在拍照时启用。预览不走 BPS。
IFE → Bayer RAW
│
① Demosaic(去马赛克)
│ 核心: 把 Bayer 排列的单色像素插值为完整 RGB
│ 第 1 章讲过概念,这里讲实现:
│ BPS 的 Demosaic 硬件做了三件事:
│ a) 边缘方向检测 — 判断这个像素处于水平/垂直/对角边缘
│ b) 沿边缘插值 — 避免跨边缘产生模糊
│ c) 伪彩色抑制 — 防止彩色边缘出现假色(如紫边)
↓
② Spatial Denoise(空域去噪)
│ 在当前帧内部做降噪
│ 对于平坦区域(如天空)→ 多平滑(降噪)
│ 对于边缘区域(如建筑边缘)→ 少平滑(保留细节)
│ 强度由 chromatix 中的 denoise 参数控制
↓
③ Temporal Denoise(时域去噪,可选)
│ 利用前一帧的信息帮助当前帧降噪
│ 需要 motion detection — 检测画面是否运动
│ 静止区域 → 多帧平均降噪
│ 运动区域 → 只用空域降噪(避免鬼影)
↓
④ Color Correction Matrix(颜色校正)
│ sensor 的 RGB 响应 ≠ 人眼看到的 RGB
│ CCM 是一个 3×3 矩阵:
│ [R_out] [M11 M12 M13] [R_in]
│ [G_out] = [M21 M22 M23] [G_in]
│ [B_out] [M31 M32 M33] [B_in]
│ CCM 参数来自 chromatix,针对不同色温有不同的矩阵
↓
⑤ Gamma 校正
│ 传感器的 RAW 是线性的(光强 ×2 → RAW 值 ×2)
│ 但人眼对暗部更敏感(非线性感知)
│ Gamma = 把线性值映射到非线性空间
│ 典型 Gamma 曲线: output = input^(1/2.2)
│ 这一步为后续的 YUV 处理做准备
↓
RGB/YUV 输出 → 送往 IPE
camx/src/hwl/bps/camxchinodebps.cpp → BPS Node 实现
IPE 负责 YUV 域的最终处理。
BPS → RGB/YUV
│
① RGB→YUV 转换(如果 BPS 输出的是 RGB)
│ Y = 0.299R + 0.587G + 0.114B (亮度)
│ U = -0.169R - 0.331G + 0.5B (蓝色差)
│ V = 0.5R - 0.419G - 0.081B (红色差)
│ 这是 BT.601 标准,硬件固定实现
↓
② Chroma Denoise(色度降噪)
│ 色度噪声比亮度噪声更显眼(人眼敏感)
│ 对 U/V 通道做额外的降噪
│ CNR 强度由 chromatix 控制
↓
③ Sharpen(锐化)
│ 增强边缘对比度
│ 原理: 提取高频部分 → 乘以增益 → 加回原图
│ 可控参数: sharpening strength, radius, threshold
│ 过度锐化 → 边缘出现白色光晕(halo)
↓
④ 缩放 / 裁切
│ 把 ISP 输出的分辨率缩放到 App 请求的尺寸
│ 如 sensor 输出 4000×3000 → 缩放到 1920×1080(预览)
│ 缩放算法: 双线性 / 双三次(由 ISP 硬件固定实现)
│ 裁切: 对应 App 的 SCALER_CROP_REGION
↓
YUV420(NV12)输出 → Display / JPEG / MediaCodec
// 锐化强度
builder.set(CaptureRequest.EDGE_MODE, EDGE_MODE_HIGH);
// 降噪强度
builder.set(CaptureRequest.NOISE_REDUCTION_MODE, NR_MODE_HIGH);
// 颜色模式
builder.set(CaptureRequest.COLOR_CORRECTION_MODE, CC_MODE_FAST);
// 裁切区域
builder.set(CaptureRequest.SCALER_CROP_REGION, cropRect);
这些参数到了 HAL 层,最终变成写入 IPE 寄存器的值。
camx/src/hwl/ipe/camxchinodeipe.cpp → IPE Node 实现
第 21 章会详细讲 AE/AWB/AF,这里先建立 ISP 和 chromatix 的联系:
每个 ISP 模块的"调整旋钮"都来自 chromatix 参数:
IFE:
- 黑电平值 → BLS calibration data
- 坏点表 → BPC map
- LSC table → Lens shading gain grid
BPS:
- Denoise 强度 → Spatial/Temporal NR strength
- CCM 3×3 矩阵 → Color Correction Matrix
- Gamma 曲线 → Gamma LUT (256 points)
IPE:
- Sharpen 强度 → Edge enhancement strength
- CNR 强度 → Chroma denoise strength
- Tuning 参数 → 其他可调的 ISP 寄存器
所有这些参数都从 /vendor/etc/camera/chromatix/{sensor_name}/ 加载。
💡 chromatix 文件就是 ISP 的"调音台"。每个模块都有一组旋钮(参数),调好了图像就好,调不好就偏色/噪点多/锐化过度。这就是 Tuning 工程师的日常工作——不停地调这些参数。
以 12MP sensor(4000×3000)为例,对比同一帧在 Preview 和 Snapshot 下的不同路径。
① Sensor 输出:
RAW10, 4000×3000, 10bit/pixel, ~15MB/帧
② IFE 接收:
MIPI CSI → CSIPHY/CSID 解码 → IFE 输入 Buffer
IFE 处理: BLS → Linearization → BPC → LSC
处理速度: ~2ms
输出 1: YUV420 → IPE(内置轻量 Bayer→YUV 转换,不走 BPS)
输出 2: Stats → Stats Node
同时: 提取 Stats → AE 算曝光 / AWB 算色温 / AF 算焦点
③ IPE 接收(直接来自 IFE):
IFE 输出 YUV ←→ IPE 输入 (跳过 BPS)
IPE 处理: CNR → 锐化 → 缩放到 1920×1080
处理速度: ~2ms
输出: YUV420 NV12 (1920×1080) ≈ 3MB
④ 输出到 Display:
IPE 输出 Buffer → queueBuffer → BufferQueue → SurfaceFlinger
总 ISP 延迟: ~4ms(远小于 30fps 的 33ms 帧间隔)
① Sensor 输出: 同上
② IFE 接收: 同上
IFE 输出 1: Bayer RAW → BPS(全质量路径,不是 YUV420)
IFE 输出 2: Stats → Stats Node
③ BPS 接收(拍照时才激活):
IFE 输出 Bayer RAW → BPS 输入
BPS 处理: Demosaic → 去噪 → CCM → Gamma
处理速度: ~3.5ms(比 IFE 的轻量转换慢,但质量更高)
输出: RGB 8bit×3 (4000×3000) ≈ 36MB
④ IPE 接收(来自 BPS):
BPS 输出 RGB → IPE 输入
IPE 处理: RGB→YUV → CNR → 锐化(非缩放,保持全分辨率)
输出: YUV420 (4000×3000 或按需缩放)
⑤ JPEG 编码:
IPE 输出 YUV → JPEG Node → JPEG 文件
总 ISP 延迟: ~8ms + JPEG 编码 ~80ms
Preview 路径 Snapshot 路径
─────────── ────────────
流经模块: IFE → IPE IFE → BPS → IPE
BPS: 跳过 启用
Demosaic: IFE 内置轻量版 BPS 完整版(质量最好)
延迟: ~4ms ~8ms + JPEG
适用场景: 预览/录像 拍照
IFE(图像前端): RAW → RAW
黑电平校正 → 线性化 → 坏点校正 → LSC → Stats 提取
源码: camx/src/hwl/ife/
BPS(Bayer 处理): Bayer → RGB/YUV
Demosaic → 空域/时域去噪 → CCM → Gamma
源码: camx/src/hwl/bps/
IPE(图像后处理): RGB/YUV → YUV
RGB→YUV 转换 → 色度降噪 → 锐化 → 缩放
源码: camx/src/hwl/ipe/
JPEG(编码): YUV → JPEG
源码: camx/src/hwl/jpeg/
关键联系:
chromatix = ISP 的参数文件
每个 ISP 模块的"旋钮"都在 chromatix 中
查看你的设备上 chromatix 文件:
adb shell ls /vendor/etc/camera/chromatix/
开启 Node 处理时间分析,确认各 ISP 模块的耗时:
echo "dumpNodeProcessingInfo=1" >> camxoverridesettings.txt
# logcat 中搜 "NodeProcessing"
在你的源码中找到 IFE/BPS/IPE 的 Node 实现文件,确认路径和本章描述一致。