← 返回课程

CameraService架构

Camera全栈开发(Qcom Camx) 第 9 / 30 节

第 7 章:CameraService 架构


本章导读

前面两章讲了怎么用 Camera2 API 写 App。但你有没有想过——你的 openCamera() 调用发出去之后,到底是谁在处理?

答案是 CameraService——一个运行在 system_server 进程里的 Android 系统服务。它是整条 Camera 调用链的中转站,负责把 App 的请求转发给 HAL,再把 HAL 的结果转发回 App。

理解 CameraService 有三个实际作用:

  1. 定位问题 — 知道每一层在哪个进程里,崩溃时才能判断是谁崩了
  2. 理解 HAL 的设计 — HAL3 接口之所以只有 6 个函数,是因为 CameraService 做了大量服务工作
  3. 读懂 dumpsysdumpsys media.camera 的输出来自 CameraService,不懂它的结构就读不懂输出

7.1 CameraService 是什么

7.1.1 它是什么

CameraService 是 Android 的系统服务(System Service)——和 ActivityManager、WindowManager、PackageManager 性质相同。它由 SystemServer 在系统启动时注册到 ServiceManager 中,所有 App 通过 Binder 访问它。

7.1.2 它在哪

对照这个图来理解本节

Android 的进程布局:

┌─────────┐    Binder     ┌─────────────────┐    HIDL     ┌──────────────────┐
│   App   │ ←──────────→ │ system_server    │ ←────────→ │ CameraProvider   │
│ (相机/  │              │  ┌─────────────┐ │            │  ┌─────────────┐ │
│  微信/  │              │  │CameraService│ │            │  │ camera.qcom │ │
│  抖音)  │              │  │             │ │            │  │  .so        │ │
└─────────┘              │  │DeviceClient │ │            │  │ (CamX/CHI)  │ │
                         │  └─────────────┘ │            │  └─────────────┘ │
                         │  CameraService   │            │  CameraProvider  │
                         │  运行在这里      │            │  独立进程        │
                         └─────────────────┘            └──────────────────┘

三个关键进程

进程 里面有什么 崩溃了会怎样
App 进程 你的 App 代码 仅 App 退出
system_server CameraService(Java/C++) 手机重启! system_server 是 Android 的核心进程
CameraProvider CamX + CHI(HAL 实现) Provider 重启,手机不受影响

💡 为什么 CameraService 在 system_server 里?

因为 CameraService 需要做权限检查(调用 PackageManager)、需要和 SurfaceFlinger 配合(Buffer 管理)、需要 Binder 支持(跨进程通信)。这些设施都在 system_server 里。

HAL 不在 system_server 里——因为 HAL 是厂商代码,可能不稳定。把它放到独立的 CameraProvider 进程中,就是 Treble(Android 8)最重要的改动。这章的 7.4 节会详细讲。


7.2 CameraService 是怎么启动的

当手机开机时,SystemServer 会按顺序启动所有系统服务。CameraService 的启动是比较晚的——因为它的依赖(HAL Provider)需要等底层初始化完才能用。

手机开机
  ↓
init 进程启动
  ↓
Zygote 启动 → SystemServer 启动
  ↓
SystemServer.startOtherServices()
  ↓
① System.loadLibrary("camera_client")        — 加载 native 库
  ↓
② new CameraService()                         — 创建服务实例
  ↓
③ ServiceManager.addService("media.camera")   — 注册到 ServiceManager
   ↓                                             (这样 App 才能通过 Binder 找到它)
④ CameraService.onFirstRef()                  — 首次被引用时
  ↓   扫描 HIDL/AIDL 注册表
  ↓   找到所有 "android.hardware.camera.provider" 服务
  ↓   每个 Provider 调用 get_number_of_cameras()
  ↓   收集所有 Camera ID
  ↓
⑤ 相机就绪,等待 App 连接

验证 CameraService 是否正常运行:

adb shell dumpsys media.camera
# 有输出 → CameraService 正常,Provider 正常

adb shell service check media.camera
# Service media.camera: found → 服务已注册
# Service media.camera: not found → 没注册,启动失败

adb shell ps -A | grep camera
# 可以看到 CameraProvider 进程

7.3 CameraService 的内部结构

CameraService 不是一个巨大的单体。它内部有几个关键组件:

CameraService
  │
  ├── CameraProviderManager
  │     └── 管理所有 CameraProvider(内置/USB/虚拟)
  │         └── 每个 Provider 通过 HIDL/AIDL 连接
  │
  ├── CameraDeviceClient (每个 App 连接一个)
  │     └── App 1, 连接 camera 0 → CameraDeviceClient[0]
  │     └── App 2, 连接 camera 0 → 创建失败!(已被占用)
  │
  ├── Camera3Device (每个 camera 对应一个)
  │     └── 封装 camera3_device_t 的操作
  │     └── 管理 HAL 的 request queue
  │
  └── CameraService::BasicClient (基类)
        └── 管理每个客户端连接的权限和优先级

7.3.1 CameraProviderManager — 管理 Provider

CameraProviderManager 的职责:
  - 扫描系统中有哪些 CameraProvider
  - 向每个 Provider 查询它有多少个 Camera
  - 汇总成"所有可用的 Camera ID"列表

Provider 类型:
  "internal/0"  → 内置后摄 + 前摄(高通平台)
                  Provider 名: android.hardware.camera.provider@2.4
  "external/0"  → USB 摄像头(如果插入了外接摄像头)
  "virtual/0"   → 虚拟摄像头(某些调试工具会注册)

7.3.2 CameraDeviceClient — 每个 App 连接一个

当 App 调用 openCamera(cameraId) 时,CameraService 创建 CameraDeviceClient

openCamera("0") 触发的内部流程:

① 权限检查
   检查 App 是否声明了 android.permission.CAMERA

② 可用性检查
   cameraId "0" 是否存在?
   是否被其他 App 占用?

③ 创建 CameraDeviceClient
   CameraDeviceClient[0] = new CameraDeviceClient(cameraId, appUid)

④ 调用 Provider
   通过 HIDL/AIDL 调用 CameraProvider,打开 HAL 的 camera3_device_t

⑤ 返回 Binder 代理
   把 CameraDeviceClient 的 Binder 代理返回给 App
   App 拿到的是 Binder 代理,不是真正的对象引用

一个物理相机同一时间只能被一个 App 占用。

如果 App 2 在 App 1 已经占着后摄时尝试 openCamera("0")

App 2 收到异常: CameraAccessException(CAMERA_ERROR_IN_USE)
App 1 不受影响

7.3.3 Camera3Device — HAL 层的封装

Camera3Device 是 CameraService 内部对 HAL camera3_device_t 的封装:

Camera3Device 的核心工作:

① 请求排队
   App 可能连续发多个 capture request
   Camera3Device 把它们排成队列,逐个发给 HAL

② 请求状态跟踪
   记住每个 request 是哪个 App 发的、发的什么参数
   当 HAL 返回 result 时,匹配回对应的 App

③ 错误处理
   如果 HAL process_capture_request() 返回错误
   Camera3Device 把错误转成 App 能理解的 onCaptureFailed()

④ 资源管理
   跟踪每个 Stream 的 Buffer 使用
   在 Session 关闭时确保 Buffer 被释放

7.4 Treble 架构前后——最重要的架构变化

如果你只会一个 Camera 架构的概念,记住这个:Treble 让 HAL 崩溃不再导致手机重启。

7.4.1 Treble 之前(Android 7.x)

mediaserver 进程(native)
  ├── CameraService(native 服务)
  └── Camera HAL(C++,厂商提供)
        └── camera.qcom.so(dlopen 加载)
            └── CamX / mm-camera

Camera HAL 运行在 mediaserver 进程里(和 CameraService 同属一个 native 进程,并不是 Java 的 system_server)。

如果 HAL 中有一个空指针:
  camera.qcom.so → SIGSEGV → mediaserver 进程崩溃
  → 相机服务不可用(手机不必重启,重启相机 App 即可)

对于 OEM 来说,每次改 HAL 代码都心惊胆战——一个内存越界能让手机变成砖。

7.4.2 Treble 之后(Android 8+)

system_server 进程             CameraProvider 独立进程
┌──────────────────────┐       ┌─────────────────────────┐
│ CameraService        │ HIDL  │ CameraProvider@2.4      │
│ (Java/C++)           │ ←───→ │  ├── camera_module_t    │
│                      │       │  ├── camera3_device_t   │
│ CameraProviderMgr    │       │  └── camera.qcom.so     │
│ CameraDeviceClient   │       │      (CamX + CHI)       │
└──────────────────────┘       └─────────────────────────┘

HAL 被移到了独立进程中。

现在 HAL 崩溃时发生什么:

① camera.qcom.so 崩溃 → CameraProvider 进程死亡
② 内核通知 system_server:"HAL 进程挂了"
③ CameraService 收到 Binder death notification
④ CameraService 标记该 Provider 为"不可用"
⑤ 如果 App 正在用相机 → App 收到 onError(ERROR_CAMERA_SERVICE)
⑥ CameraService 尝试重启 Provider 进程
⑦ 如果重启成功 → App 可以重新 openCamera()
⑧ **system_server 不受影响!手机不会重启!**

💡 这对开发意味着什么

Treble 之前你 debug HAL 时如果搞崩了,手机会重启——调试效率极低。
Treble 之后你可以大胆地在 HAL 代码里加日志、改参数——崩了最多 Provider 重启,不影响系统。

7.4.3 通信方式的演进:HIDL → AIDL

Treble (Android 8)     → 引入 HIDL,HAL 进程隔离
                         Camera HAL 接口: camera.provider@2.4

Android 13              → 弃用 HIDL,统一到 AIDL
                         Camera HAL 接口: android.hardware.camera.provider

为什么换?
  HIDL 需要专门的 hidl-gen 工具链,额外的语言和构建规则
  AIDL 复用了 Android 已有的 Binder IPC 基础设施
  维护一套工具链比维护两套更容易

7.5 一次 openCameracapture 的完整路由

把前面的知识串起来,走一遍完整流程:

阶段 1:打开相机

App 调用 CameraManager.openCamera("0", callback, handler)
  │
  ▼ Binder IPC(跨进程:App → system_server)
CameraService.openCamera("0", appUid)
  │
  ├─ 权限检查:App 有 CAMERA 权限吗?
  ├─ 可用性检查:Camera "0" 空闲吗?
  ├─ CameraDeviceClient 创建
  │
  ▼ HIDL/AIDL(跨进程:system_server → Provider)
CameraProvider.open("0")
  │
  ▼ 加载 HAL(同进程内:dlopen)
camera_module_t.open("0") → camera3_device_t
  │
  ▼ CamX 内部
ChiContext::Create() → HALDevice 初始化
  │
  ▼ 回调链
Provider → CameraService → App
  │
App 收到 onOpened(cameraDevice) ← CameraDevice 的 Binder 代理

阶段 2:配置 Stream(createCaptureSession)

App 调用 cameraDevice.createCaptureSession(surfaces, callback)
  │
  ▼ Binder
CameraService.configureStreams()
  │
  ▼ HIDL
CameraProvider.configure_streams(streamConfig)
  │
  ▼ CamX 内部
UsecaseSelector::Select()       → 选 Usecase
Usecase::Create()               → 创建 Pipeline
Pipeline::AcquireResources()    → 分配 Buffer
  │
  ▼ 完成
App 收到 onConfigured(session)

阶段 3:处理帧(capture / setRepeatingRequest)

App 调用 session.setRepeatingRequest(request, callback)
  │
  ▼ Binder
CameraService.submitRequestList()
  │
  ▼ HIDL
CameraProvider.process_capture_request()
  │
  ▼ CamX 内部
Usecase::SubmitChiRequest()
  → Session::ProcessCaptureRequest()
    → Pipeline::ProcessRequest()
      → Node::ProcessRequest()
        → CSLSubmit() → ioctl → Kernel → ISP HW
                                         │
  ┌──────────────────────────────────────┘
  ▼ ISP 硬件处理完成 → Fence Signal
CSLFenceSignaled() → Node::ProcessResult()
  → Pipeline::ProcessPipelineResult()
    → Session::ProcessPipelineResult()
      → Usecase::ProcessCaptureResult()
        │
  ▼ HIDL
CameraProvider.process_capture_result()
  │
  ▼ Binder
CameraService → App
  │
App 收到 onCaptureCompleted(result) ← 每帧一次回调

阶段 4:Buffer 是怎么到达 App 的——反向路径详解

上一节画了函数调用链,但没有解释数据本身是怎么从 ISP 到 App 的 Surface 上的。这一节专门讲这个。

整个过程分三步:HAL 生产 → BufferQueue 传递 → App 消费。

第一步:HAL 生产 Buffer — ISP 写、HAL 交

ISP 硬件处理完一帧(IFE → IPE,预览主路径;快照才经 BPS 做完整 demosaic)
  │
  ▼ ISP DMA 引擎
把 YUV 数据写入一块 Gralloc Buffer(物理连续内存)
这块内存在 configure_streams 时就分配好了
  │
  ▼ HAL 拿到 Buffer 后
Node::ProcessRequestResult() 中调用:
  1. CacheOps(invalidate) — 确保 CPU 能看到最新的硬件写入
  2. 把 buffer handle 放入 result 结构中
  │
  ▼ HAL → Provider
camera3_capture_result_t result = {
    .frame_number = 42,
    .result = metadata,           // AE/AF/AWB 等参数回读
    .output_buffers = &{         // ← 图像数据在这里
        .stream = previewStream,
        .buffer = &bufferHandle,   // Gralloc buffer 的句柄
        .status = BUFFER_STATUS_OK,
        .acquire_fence = -1,
        .release_fence = -1,
    },
    .num_output_buffers = 1,
};

关键点process_capture_result() 传递的不是图像数据本身,而是 Gralloc buffer 的句柄(handle)。真正的像素数据已经在 ISP 写入的物理内存里了。传递 handle 避免了拷贝整帧数据。

第二步:BufferQueue 传递——从 HAL 到 Surface

HAL 调了 process_capture_result() 之后,CameraService 做了什么?

CameraService 收到 result
  │
  ▼ 取出 output_buffers[0].buffer — 这是个 buffer_handle_t
CameraService 找到这个 buffer 对应的 Stream
  │
  ▼ 调用 IGraphicBufferProducer.queueBuffer()
把 buffer handle 交还给 BufferQueue
  │
  ▼ BufferQueue 内部
  - 把 buffer 标记为 QUEUED 状态
  - 通知 Consumer 端:"有新数据了"
  │
  ▼ Consumer 端(App 的 Surface)
收到通知 → 可以 consume 了

BufferQueue 的核心作用:它是 HAL(Producer)和 App(Consumer)之间的共享内存管理器。HAL 生产和归还 Buffer,App 获取和释放 Buffer。整帧图像数据从来没有被拷贝过——始终在原地,只传 handle。

物理内存中的一帧 Buffer 的生命周期:

ISP DMA 写入(HAL Producer)
  → BufferQueue.queueBuffer()(handle 交给 BufferQueue)
    → Consumer acquireBuffer()(App 拿到 handle)
      → SurfaceFlinger 合成到屏幕(读同一块物理内存)
        → 或 ImageReader 通过 lockCanvas/lockImage 映射到 CPU 虚拟地址
          → Consumer releaseBuffer()(handle 回收)
            → HAL dequeueBuffer()(再次拿去用)

第三步:App 消费——两种路径

路径 A:显示到屏幕(SurfaceView / TextureView)

BufferQueue Consumer: SurfaceFlinger

流程:
  HAL queueBuffer() → BufferQueue → SurfaceFlinger acquireBuffer()
    → SurfaceFlinger 用 GPU 把 buffer 合成到屏幕 framebuffer
    → SurfaceFlinger releaseBuffer() → buffer 回空闲池

这条路径上 App 代码碰不到像素数据。
PRIVATE 格式的 buffer 直接被 GPU 消费,效率最高。

路径 B:App 代码读取像素(ImageReader)

BufferQueue Consumer: ImageReader

流程:
  HAL queueBuffer() → BufferQueue → ImageReader acquireBuffer()
    → Image 对象可用 → onImageAvailable() 回调触发
    → App 代码中:
        Image image = reader.acquireLatestImage();
        Image.Plane[] planes = image.getPlanes();
        ByteBuffer yBuffer = planes[0].getBuffer();  // CPU 现在可以读了
        // 处理像素...
        image.close();  // 必须!releaseBuffer() 回到空闲池

这条路径涉及一次 CPU 映射:Gralloc buffer → CPU 虚拟地址空间。
YUV_420_888 和 JPEG 格式的 buffer 走这条路径。

为什么第 6 章的 YUV 拷贝那么麻烦?

现在回头看第 6 章讲的"YUV 数据拷贝时要按 RowStride 逐行操作",原因就很清楚了:

Gralloc buffer 在物理内存中:
  排列由硬件 DMA 的对齐要求决定
  RowStride 可能大于 width(多出的叫 padding)
  如果你直接用 width 来遍历 → 读到了 padding → 花屏

所以正确的做法是:
  按 RowStride 跳到每一行的起始位置
  只拷贝前 width 个有效字节
  跳过行尾的 padding

阶段 6:关键日志

logcat 中的每一层标识:

App → CameraService:         (Java 层日志,不经过 CamX)
CameraService → Provider:    [CameraService] (logcat)
Provider → HAL:              [CamX] [HAL ]  camxhal3entry.cpp
HAL → CamX:                  [CHI]  chxusecase.cpp
CamX → Session:              [CamX] [CORE] camxsession.cpp
Session → Pipeline:          [CamX] [CORE] camxpipeline.cpp
Pipeline → Node:             [CamX] [CORE] camxnode.cpp
HAL → Kernel:                [CSL]
Kernel → ISP:                dmesg

7.6 CameraService 的状态管理

CameraService 内部维护了每个相机的简单状态。它比 CamX Pipeline 的 8 状态状态机粗粒度得多:

CameraService 的状态(每个 Camera 一个):

  CLOSED  ──→  OPEN  ──→  ACTIVE  ──→  CLOSED
                │           │
    相机已打开    │    正在处理请求
    Stream 未配置  │    (capture/repeating)

  状态转换:
    CLOSED → OPEN:   openCamera() 成功
    OPEN   → ACTIVE: configureStreams() 成功
    ACTIVE → OPEN:   Stream 被 flush 或关闭
    OPEN   → CLOSED: disconnect() 或 close()
    ACTIVE → CLOSED: 直接 close()(跳过 flush)

和 CamX Pipeline 状态机的区别

CameraService 的状态机是"业务级别"的——关心相机是否打开、是否在拍照。它不需要关心 IFE 的 buffer 数量、BPS 的依赖是否满足。

CamX Pipeline 的 8 状态状态机(第 13 章)是"硬件控制级别"的——需要精确管理 ISP 硬件资源。两者粒度完全不同。


7.7 HAL 崩溃恢复——完整流程

这是实际开发中最常见的场景之一:你改了 HAL 代码,push 到设备上,一测试崩了。然后呢?

① HAL 崩溃 — SIGSEGV / null pointer / assert fail
   camera.qcom.so → crash → CameraProvider 进程被 kill

② 内核通知:SIGCHLD → init → CameraProvider 进程死亡

③ CameraService 感知
   方式:Binder Death Notification
   CameraService 和 CameraProvider 之间有一个 Binder 连接
   Provider 进程死亡 → Binder 连接断开 → CameraService 收到通知

④ CameraService 清理
   notifyAllClients(ERROR_CAMERA_SERVICE)
   → 所有正在使用相机的 App 收到 onError() 回调
   → CameraDeviceClient 被清理
   → CameraProviderManager 标记 Provider 为"不可用"

⑤ Provider 重启
   Android init 进程检测到 CameraProvider 死亡
   → 根据 init.rc 中的配置重启它
   → 新的 Provider 进程启动 → 重新注册 HIDL/AIDL 服务

⑥ CameraService 重新发现
   CameraProviderManager 检测到新的 Provider 注册
   → 重新连接
   → 相机恢复可用

⑦ App 重试
   App 收到 onError() → 显示提示 → 用户重试
   → App 再次调用 openCamera() → 成功

💡 开发时的实用技巧

# 手动模拟 HAL 崩溃
adb shell killall -9 android.hardware.camera.provider@2.4-service

# 观察恢复过程
adb logcat | grep -E "CameraService|Provider|death"

7.8 常见问题

Q1: 所有 App 相机都打不开,但 Provider 进程还在

问题不在 HAL 层,在 CameraService 层。
可能的原因:
  - CameraService 没有注册到 ServiceManager(检查 service check media.camera)
  - CameraProviderManager 没有发现 Provider
  - Binder 通信超时

排查:
  adb shell dumpsys media.camera   → 看有没有输出
  adb shell service check media.camera  → 看服务是否注册

Q2: 个别 App 相机打不开,其他 App 正常

几乎可以确定是该 App 的问题:
  - 权限没声明(AndroidManifest.xml 中缺 CAMERA 权限)
  - 运行时权限被拒绝
  - App 在后台被限制了相机访问(Android 9+ 后台相机限制)
  - App 代码异常(没正确处理 onDisconnected)

Q3: Provider 进程频繁重启

看 logcat:
  adb logcat | grep -E "CameraProvider|death|restart"

可能原因:
  - HAL 代码中有偶发的崩溃(通常和特定场景/特定 sensor 相关)
  - 内存不足导致 Provider 被 OOM killer 杀掉
  - 多个 App 竞争打开相机导致的竞态条件

解决:先抓 tombstone,再分析崩溃栈。tombstone 在 /data/tombstones/

### Q4: dumpsys media.camera 输出为空

说明 CameraService 没有正常连接到 Provider。
排查:
① adb shell ps -A | grep camera → Provider 进程在吗?
② 不在 → Provider 启动失败,看 logcat 中 HAL 加载日志
③ 在但 dumpsys 为空 → CameraService 和 Provider 之间的 HIDL/AIDL 连接断了


---

## 7.9 本章总结

CameraService 的核心要点
──────────────────────────────────────

① 位置
运行在 system_server 进程
是 App 和 HAL 之间的中转站

② 内部结构
CameraProviderManager — 管理 Provider
CameraDeviceClient — 每个 App 连接一个
Camera3Device — 封装 HAL 的 camera3_device_t

③ Treble 架构(最核心的变化)
之前:HAL 在 mediaserver 中 → HAL 崩溃 = 相机服务挂掉(手机不重启)
之后:HAL 在独立 Provider 进程 → HAL 崩溃 = Provider 重启,手机不受影响

④ 跨进程通信
App → CameraService: Binder
CameraService → Provider: HIDL(8.0~12)→ AIDL(13+)
Provider → HAL: 函数调用(同进程,dlopen)
HAL → Kernel: ioctl

⑤ 调试
dumpsys media.camera — 查看 CameraService 状态
service check media.camera — 确认服务是否注册
ps -A | grep camera — 确认 Provider 进程是否在跑


---

## 动手验证

1. 执行 `adb shell service list | grep camera`,看 CameraService 的服务名。

2. 执行 `adb shell ps -A | grep camera`,记录 Provider 的 PID。然后:
   ```bash
   adb shell killall -9 android.hardware.camera.provider@2.4-service

再执行 ps -A | grep camera——PID 变了吗?说明 Provider 被重启了。

  1. 打开相机 App,然后在另一个终端执行 killall,观察 App 的行为——是崩溃了还是收到了 onError?

常见误解