高通 8155 车机平台下 ToF 相机实时数据流采集链路

9 阅读15分钟

ToF 工程说明

1. 工程概览

本工程面向 高通 8155 + ToF 相机 场景,当前主程序为 tof_stream,实现了一条完整的实时数据链路:

  1. 通过 EVS 打开相机并持续接收图像流。
  2. 将相机原始数据拷贝为用户态连续内存。
  3. 调用厂商 DepthProcessor 完成 ToF 解算。
  4. 得到 depthir 两路结果。
  5. 将结果回调给上层。
  6. 通过 AVS 推送到客户端会话。
  7. 可选在设备本地通过 EGL + OpenGL ES 显示深度伪彩和 IR 灰度预览。
  8. 可选异步保存 raw/depth/ir 到本地文件。

当前代码已经从早期版本演进为:

  • 支持 EVS 事件热插拔恢复。
  • 支持 binder died 后自动重新 openCamera + startVideoStream
  • 支持本地显示模块 DisplayController
  • 支持通过配置开关启用或关闭本地显示。
  • 推流控制器已升级为“缓冲 + 限流 + 分发 + 编码协调 + 重连管理”模式。
  • 已移除流控制器中的命令处理和手动保存职责,相关功能不再由 LiveStreamController 承担。

2. 当前构建产物

按当前 Android.bp,实际注册的主要构建目标是:

  • tof_stream
    • 完整应用,包含采集、解算、推流、可选本地显示、可选文件保存。

源码目录中仍保留以下封装/示例代码:

  • samples/streamcapture.h
  • samples/streamcapture.cpp
  • samples/streamcapture_demo.cpp
  • samples/README_streamcapture.md

但这些模块当前没有在 Android.bp 中注册为独立产物,因此不会随当前配置自动构建。

3. 目录与模块

3.1 入口

  • srcs/main.cpp
    • 程序入口。
    • 负责组装 ToFPipelineManagerLiveStreamControllerDisplayController
    • 负责读取命令行参数并决定是否启用本地显示。

3.2 采集层

  • EvsStream/StreamHandler.h
  • srcs/StreamHandler.cpp
    • 对接 EVS。
    • 打开相机并启动视频流。
    • 接收 deliverFrame_1_1() 回调。
    • 将 HAL buffer 拷贝成紧凑连续内存。
    • 处理 EVS 热插拔事件和 binder 死亡恢复。

3.3 流水线核心

  • include/ToFPipelineManager.h
  • srcs/ToFPipelineManager.cpp
    • 管理采集、解算、统计线程。
    • 管理原始帧队列、显示帧队列。
    • 调用 SDK 解算。
    • 可选提交异步保存任务。
    • 通过回调把 ProcessedFrameData 交给上层。

3.4 保存层

  • include/AsyncFileWriter.h
  • srcs/AsyncFileWriter.cpp
    • 独立文件写线程。
    • saveRawData / saveDepthData / saveIRData 任一开启时才初始化并启动。

3.5 推流控制层

  • EvsStream/LiveStreamController.h
  • srcs/LiveStreamController.cpp
    • 接收 ProcessedFrameData
    • 使用有上限的帧缓冲做平滑处理。
    • 按目标 FPS 限流。
    • 向预览/录像/AI 回调分发数据。
    • 负责推流后端的连接检测和重连调度。

3.6 推流执行层

  • EvsStream/StreamPusher.h
  • srcs/StreamPusher.cpp
    • 动态加载 libalgorithm_visual_service.so
    • 注册 AVS session。
    • 维护内部推流队列。
    • 进行图像编码等待、元数据序列化等待。
    • 将图像和元数据真正推送到 session。

3.7 本地显示层

  • display/DisplayController.h
  • display/DisplayController.cpp
  • display/SimpleRenderer.h
  • display/SimpleRenderer.cpp
  • display/createNativeWindow.cpp
    • DisplayController 负责显示线程、深度伪彩转换和渲染调度。
    • SimpleRenderer 负责 EGL + OpenGL ES 2.0 渲染。
    • 当前支持本地双窗口显示:
      • 深度图伪彩显示
      • IR 灰度图显示

4. 核心数据结构

文件:include/FrameDataTypes.h

4.1 原始帧

struct RawFrameData {
    uint64_t frameId;
    uint64_t timestampNs;
    uint32_t width;
    uint32_t height;
    uint32_t stride;
    std::vector<uint8_t> data;
};
  • data 保存从 EVS 拷贝出的原始连续内存。
  • 当前主路径使用 RAW16

4.2 解算后帧

struct ProcessedFrameData {
    uint64_t frameId;
    uint64_t timestampNs;
    uint32_t depthWidth;
    uint32_t depthHeight;
    uint32_t irWidth;
    uint32_t irHeight;
    float exposureTime;
    float temperature;
    std::vector<uint8_t> depthData;
    std::vector<uint8_t> irData;
    std::vector<uint8_t> rawData;
};
  • depthDatauint16_t 存储。
  • irDatauint8_t 存储。
  • rawData 作为原始帧副本,用于保存或调试。

4.3 EVS 拷贝帧

文件:EvsStream/StreamHandler.h

struct CopiedFrame {
    std::vector<uint8_t> data;
    uint32_t width;
    uint32_t height;
    uint32_t stride;
    uint32_t format;
    uint64_t timestamp;
};
  • CopiedFrameStreamHandler 从 EVS/HAL buffer 拷贝到用户态后的中间帧结构。
  • 它和原始 BufferDesc 的区别是:
    • 生命周期完全由应用控制
    • 可以在归还 HAL buffer 后继续使用
    • 便于后续采集线程和解算线程异步处理

4.4 文件写任务

文件:include/AsyncFileWriter.h

struct WriteTask {
    std::string filename;
    std::vector<uint8_t> data;
    uint64_t frameId;
    bool isCritical;
};
  • WriteTask 是文件写线程真正消费的任务结构。
  • data 保存待写入磁盘的二进制内容。
  • isCritical 用于在退出时区分关键任务,必要时等待其落盘完成。

4.5 AVS 推流帧

文件:EvsStream/StreamPusher.h

struct FrameData {
    unsigned char* depth_data;
    unsigned char* ir_data;
    size_t depth_size;
    size_t ir_size;
    int width;
    int height;
    int channels;
    uint64_t frame_id;
    uint64_t timestamp;
};
  • 这是 StreamPusher 内部排队等待推送的帧结构。
  • depth_datair_data 是独立申请并复制的堆内存。
  • 这样上游提交后即可返回,不需要等待真正推流完成。

4.6 无锁队列 LockFreeQueue<T>

文件:include/LockFreeQueue.h

template<typename T>
class LockFreeQueue {
public:
    bool tryPush(T&& item);
    bool forcePush(T&& item);
    bool tryPop(T& item);
    size_t tryPopBatch(T* items, size_t maxCount);
};

当前实现特点:

  • 基于环形缓冲区。
  • 使用原子变量 head_tail_ 管理读写位置。
  • 适合高频、简单的单生产者/单消费者流式搬运场景。
  • tryPush() 队列满时返回 false
  • forcePush() 队列满时覆盖最老元素。
  • tryPopBatch() 支持批量取帧,减少频繁调用开销。

4.7 工程里的队列与缓存结构

4.7.1 EVS 拷贝帧队列

  • 类型:std::queue<CopiedFrame>
  • 名称:StreamHandler::mFrameDataQueue
  • 保护方式:mFrameBufferMutex + mFrameBufferCV
  • 作用:
    • 保存从 EVS 回调中拷贝出的最新帧
    • captureLoop() 消费
    • 热插拔或断线时支持清队列

4.7.2 原始帧无锁队列

  • 类型:LockFreeQueue<RawFrameData>
  • 名称:ToFPipelineManager::rawQueue_
  • 生产者:captureLoop()
  • 消费者:processLoop()
  • 作用:
    • 采集线程和解算线程解耦
    • 避免高频路径上的锁竞争

4.7.3 显示帧无锁队列

  • 类型:LockFreeQueue<ProcessedFrameData>
  • 名称:ToFPipelineManager::procQueue_
  • 生产者:processLoop()
  • 消费者:DisplayController::displayThreadFunc()
  • 作用:
    • 把最新解算结果提供给本地显示
    • 避免显示线程阻塞主链路

4.7.4 文件写任务队列

  • 类型:std::queue<WriteTask>
  • 名称:AsyncFileWriter::taskQueue_
  • 保护方式:queueMutex_ + queueCV_
  • 作用:
    • 后台写线程异步消费保存任务
    • 避免解算线程直接做磁盘 IO

4.7.5 流控制器缓冲队列

  • 类型:std::deque<tof::ProcessedFrameData>
  • 名称:LiveStreamController::frameBuffer_
  • 保护方式:frameMutex_ + frameCV_
  • 作用:
    • 作为推流控制层的环形缓冲
    • 配合限流、断线缓冲和多路分发

4.7.6 AVS 推流队列

  • 类型:std::queue<StreamPusher::FrameData>
  • 名称:StreamPusher::frame_queue_
  • 保护方式:queue_mutex_ + queue_cv_
  • 作用:
    • 存放真正等待送往 AVS 的帧
    • StreamPusher::pushLoop() 后台消费

4.8 为什么有些地方用无锁队列,有些地方不用

本工程没有强行把所有队列都改成无锁队列,而是按用途选型:

  • 高频数据搬运
    • LockFreeQueue
    • 例如 rawQueue_procQueue_
  • 需要等待、唤醒、清空、重连协调的控制链路
    • std::queue / std::deque + mutex + condition_variable
    • 例如 mFrameDataQueuetaskQueue_frameBuffer_frame_queue_

这样做的原因是:

  • 无锁队列适合简单高速搬运
  • 加锁队列更适合复杂控制逻辑
  • 两者混用更符合当前工程实际需求

5. 启动命令速查表

5.1 命令格式

tof_stream <cameraId> <targetFps> <pixelFormat> <enableLocalDisplay>

参数说明:

  • cameraId
    • EVS 相机 ID,例如 1
  • targetFps
    • 目标帧率,例如 30
  • pixelFormat
    • EVS 像素格式,当前支持:
      • raw16
      • rgba
  • enableLocalDisplay
    • 是否启用设备本地显示,支持:
      • 1 / 0
      • true / false
      • on / off
      • yes / no

5.2 常用命令

只做采集 + 解算 + 推流

tof_stream 1 30 raw16 0

适用场景:

  • 设备侧只负责采集、解算、推流
  • 不需要本地屏幕显示
  • 不需要额外显示线程和 OpenGL 渲染

采集 + 解算 + 推流 + 本地显示

tof_stream 1 30 raw16 1

适用场景:

  • 需要在车机屏幕上确认相机正在取流
  • 需要同时看深度伪彩图和 IR 图

使用 RGBA 格式调试

tof_stream 1 30 rgba 1

适用场景:

  • EVS/HAL 侧需要切到 RGBA_8888 做联调
  • 用于验证非 RAW16 模式下的链路

5.3 当前默认运行习惯

如果你当前项目目标是:

  • ToF 相机取流
  • SDK 解算
  • AVS 推流到客户端
  • 本地屏幕只作为辅助预览

推荐优先使用:

tof_stream 1 30 raw16 1

如果你只关心设备侧后台服务,不需要本地预览:

tof_stream 1 30 raw16 0

6. 当前主流程

6.1 启动流程

main() 当前执行顺序:

  1. 配置 HIDL 线程池。
  2. 构造 ToFPipelineManager::Config
  3. 读取命令行参数:
    • argv[1]cameraId
    • argv[2]targetFps
    • argv[3]:EVS 像素格式,支持 raw16 / rgba
    • argv[4]:本地显示开关,支持 1/0true/falseon/off
  4. 初始化 ToFPipelineManager
  5. 根据 enableLocalDisplay 决定是否启动 DisplayController
  6. 启动 ToFPipelineManager
  7. 初始化并启动 LiveStreamController
  8. 进入主循环,周期性打印统计信息。

6.2 主数据流

EVS Camera
  -> StreamHandler::deliverFrame_1_1()
  -> CopiedFrame
  -> ToFPipelineManager::captureLoop()
  -> RawFrameData
  -> rawQueue_
  -> ToFPipelineManager::processLoop()
  -> DepthProcessor
  -> ProcessedFrameData
  -> submitToWriter()           -> AsyncFileWriter
  -> frameCallback_(out)
  -> main.cpp::onFrameReady()
  -> LiveStreamController::submitFrame()
  -> LiveStreamController::pushThreadFunc()
  -> StreamPusher::pushFrame()
  -> StreamPusher::pushLoop()
  -> avs_push_to_session()

6.3 本地显示流

ProcessedFrameData
  -> ToFPipelineManager::procQueue_
  -> DisplayController::displayThreadFunc()
  -> 深度图伪彩转换 / IR 灰度显示
  -> SimpleRenderer

6.4 总链路图

                    +---------------------------+
                    |       相机 / EVS HAL      |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | StreamHandler::start()    |
                    | openCamera / startStream  |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | deliverFrame_1_1()        |
                    | 拷贝 HAL buffer           |
                    | 异步 doneWithFrame        |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | mFrameDataQueue           |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | captureLoop()             |
                    | CopiedFrame -> RawFrame   |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | rawQueue_                 |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | processLoop()             |
                    | DepthProcessor 解算       |
                    +-------------+-------------+
                                  |
                                  v
                    +---------------------------+
                    | ProcessedFrameData        |
                    +------+------+-------------+
                           |      |
             可选保存      |      | 回调给上层
                           |      v
                           |   +------------------------+
                           |   | onFrameReady()         |
                           |   | submitFrame()          |
                           |   +-----------+------------+
                           |               |
                           |               v
                           |   +------------------------+
                           |   | LiveStreamController   |
                           |   | 缓冲 / 限流 / 分发     |
                           |   | 重连管理               |
                           |   +-----------+------------+
                           |               |
                           |               v
                           |   +------------------------+
                           |   | StreamPusher           |
                           |   | 编码等待 / 序列化等待  |
                           |   | avs_push_to_session    |
                           |   +-----------+------------+
                           |               |
                           |               v
                           |   +------------------------+
                           |   | 客户端 / AVS Session   |
                           |   +------------------------+
                           |
                           v
               +---------------------------+
               | submitToWriter()          |
               | AsyncFileWriter           |
               +---------------------------+

ProcessedFrameData
  -> procQueue_
  -> DisplayController
  -> SimpleRenderer
  -> 设备本地显示

热插拔 / 异常恢复:
  STREAM_STOPPED -> 清队列 / 标记离线 / 暂停采集
  STREAM_STARTED -> 恢复在线 / resumeVideoStream
  binder died    -> reopenCamera / startVideoStream

7. 热插拔与断线恢复

当前 StreamHandler 已具备热插拔和服务级恢复能力。

7.1 EVS 事件热插拔

StreamHandler 监听 EVS 事件:

  • STREAM_STOPPED
    • 认为相机拔掉或掉线。
    • 清空采集队列。
    • 标记相机离线。
    • 尝试暂停视频流。
  • STREAM_STARTED
    • 认为相机重新上线。
    • 恢复在线状态。
    • 优先尝试 resumeVideoStream() 恢复流。

7.2 Binder 死亡恢复

当相机服务或 binder 直接失效时:

  1. onCameraBinderDied() 标记待重连。
  2. 清空内部帧队列并标记离线。
  3. 采集线程检测到重连请求后,按退避间隔尝试恢复。
  4. 重新执行:
    • 获取 enumerator
    • openCamera
    • setMaxFramesInFlight
    • startVideoStream
  5. 成功后继续采集。

7.3 熔断机制

ToFPipelineManager 保留了连续错误计数和熔断恢复逻辑,用于解算异常时的容错,不替代热插拔恢复,但与其配合工作。

8. 保存功能

保存仍然保留在 pipeline 中,但默认关闭。

8.1 默认配置

main.cpp 中默认:

config.saveRawData = false;
config.saveDepthData = false;
config.saveIRData = false;

8.2 当前行为

  • 三个保存开关都为 false 时:
    • 不会创建 AsyncFileWriter
    • 不会启动文件 IO 线程
    • 不会写磁盘
  • 任一开关为 true 时:
    • 初始化 AsyncFileWriter
    • 解算成功后按配置异步写入文件

9. 本地显示功能

本地显示当前已经接通,不再是保留代码。

9.1 当前能力

  • 支持通过 DisplayController 启停。
  • 支持从 ToFPipelineManager::getLatestFrame() 读取最新处理帧。
  • 支持深度图伪彩显示。
  • 支持 IR 灰度显示。
  • 支持通过配置或命令行关闭。

9.2 开关控制

ToFPipelineManager::Config 中新增:

bool enableLocalDisplay = true;

main.cpp 中第 4 个命令行参数可覆盖该值:

tof_stream 1 30 raw16 1
tof_stream 1 30 raw16 0

分别表示:

  • 1:启用本地显示
  • 0:禁用本地显示

10. 推流控制器现状

LiveStreamController 已不再负责远程命令处理或手动保存,而是聚焦流控制本身。

10.1 已实现职责

  • 帧缓存
    • 使用有容量限制的 deque 作为缓冲区。
  • 帧率控制
    • targetPushFps 控制送往 AVS 的节奏。
  • 数据分发
    • 提供 preview / record / ai 三类回调入口。
  • 推流管理
    • 检查连接数。
    • 连接断开时保留缓冲并周期性重连。
  • 编码协调
    • 推流前等待图像编码完成。
    • 推流前等待元数据序列化完成。

10.2 StreamPusher 当前职责

  • dlopen 动态加载 libalgorithm_visual_service.so
  • 注册 session
  • 获取连接数
  • 接收帧入队
  • 队列满时丢弃最旧帧
  • 在内部线程中调用:
    • avs_set_image_data()
    • avs_wait_image_encode()
    • avs_set_serialize_data()
    • avs_wait_serialize()
    • avs_push_to_session()

10.3 当前不再承担的职责

以下能力已从流控制器代码路径中去掉或不再启用:

  • AVS 命令处理
  • start_capture / stop_capture / save_frame / get_status
  • 流控制器内的手动保存文件

如果客户端需要这些功能,建议在客户端界面或更上层业务模块实现。

10.4 AVS 是什么

这里的 AVS,对应当前工程里动态加载的 libalgorithm_visual_service.so,可以理解成项目现有的一套“算法数据分发 / 会话推送服务”。

在本工程里,它主要承担这些职责:

  • 注册会话 session
  • 维护客户端连接数
  • 接收图像和元数据
  • 把深度图、IR 图和 JSON 元信息推送给客户端

当前 StreamPusher 实际依赖它提供的接口包括:

  • avs_register_session()
  • avs_get_session_connections()
  • avs_set_image_data()
  • avs_wait_image_encode()
  • avs_set_serialize_data()
  • avs_wait_serialize()
  • avs_push_to_session()

也就是说,当前代码里的“推流”本质上不是 RTSP/RTMP 标准推流,而是“通过 AVS 会话把算法结果发给客户端”。

10.5 如果没有 libalgorithm_visual_service.so,怎么实现推流

如果没有这套库,核心思路是:

  • 保留 LiveStreamController
  • 替换 StreamPusher
  • 改成你们自己的传输协议和客户端接收方式

可选实现方案:

方案一:自定义 TCP / Unix Socket 推送原始结果

适合场景:

  • 设备端和客户端都由自己控制
  • 只想快速把 depth/ir/meta 发出去

实现方式:

  • 新写一个 SocketStreamPusher
  • 用 socket 发送:
    • 帧头
    • 深度图二进制
    • IR 图二进制
    • JSON 元数据

优点:

  • 实现直接
  • 不依赖第三方推流库

缺点:

  • 需要自己定义协议
  • 需要自己处理断线重连、粘包拆包、客户端解析

方案二:WebSocket 推送

适合场景:

  • 客户端是网页或 H5
  • 希望直接在浏览器端接收

实现方式:

  • 设备端起一个 WebSocket 服务或客户端
  • 深度图、IR 图和元数据按二进制帧或 JSON + 二进制方式发送

优点:

  • 和前端联调方便
  • 适合你现在“推到客户端界面显示”的需求

缺点:

  • 需要自己处理大帧传输效率
  • 需要前端自己解析和显示深度/IR 数据

方案三:RTSP / RTP / WebRTC 推视频流

适合场景:

  • 客户端更习惯接标准视频流
  • 只关心“看画面”,不强依赖原始深度值

实现方式:

  • 把深度图先转换成伪彩 RGB
  • 把 IR 图转成灰度视频
  • MediaCodecGStreamerFFmpeg 或平台编码器编码为 H264/H265
  • 通过 RTSP / RTP / WebRTC 输出

优点:

  • 标准协议
  • 客户端播放器兼容性好

缺点:

  • 原始深度数值会丢失或需要另发
  • 要增加编码与视频传输链路

方案四:gRPC / HTTP 上传结构化帧

适合场景:

  • 客户端更偏算法服务或后端服务
  • 不一定要实时视频显示

实现方式:

  • 定义 protobuf 或 HTTP 二进制接口
  • 每帧上传 depth/ir/meta

优点:

  • 接口规范
  • 后端服务接入方便

缺点:

  • 更适合“数据服务”,不如视频协议适合实时预览

10.6 当前工程里最推荐的替代方式

如果没有 libalgorithm_visual_service.so,结合你这个项目现状,优先建议:

  1. 如果目标是“客户端界面显示”
    • WebSocketRTSP/WebRTC
  2. 如果目标是“把深度/IR 原始结果给另一个程序或算法服务”
    • 用自定义 TCPgRPC

对应代码结构建议:

  • LiveStreamController 保留
  • StreamPusher 抽象成接口
  • 新增:
    • AvsStreamPusher
    • WebSocketStreamPusher
    • RtspStreamPusher
    • SocketStreamPusher

这样主流程不变:

ProcessedFrameData
  -> LiveStreamController
  -> XxxStreamPusher
  -> 客户端

也就是说,真正可替换的是“推流执行层”,不是整个 pipeline。

11. 线程模型

当前主要线程如下:

  • HIDL / EVS 回调线程
    • 接收 EVS buffer 并做用户态拷贝。
  • 采集线程 captureLoop()
    • StreamHandler 取拷贝帧并送入 rawQueue_
  • 解算线程 processLoop()
    • 调用 SDK 解算并产出 ProcessedFrameData
  • 统计线程 statsLoop()
    • 计算 FPS 与统计指标。
  • 文件写线程 AsyncFileWriter::writerLoop()
    • 仅在保存功能开启时存在。
  • 推流控制线程 LiveStreamController::pushThreadFunc()
    • 做限流、分发、重连控制。
  • AVS 推流线程 StreamPusher::pushLoop()
    • 真正调用 AVS 接口出流。
  • 显示线程 DisplayController::displayThreadFunc()
    • 仅在本地显示开启时存在。

12. 默认配置与运行建议

12.1 当前默认运行形态

默认启用:

  • EVS 采集
  • SDK 解算
  • 热插拔恢复
  • binder died 自动重连
  • AVS 推流
  • 本地显示

默认关闭:

  • 原始帧保存
  • 深度图保存
  • IR 图保存

12.2 推荐命令

tof_stream 1 30 raw16 1

含义:

  • cameraId = 1
  • targetFps = 30
  • EVS 像素格式为 RAW16
  • 启用本地显示

如果只需要采集、解算、推流,不需要设备本地显示:

tof_stream 1 30 raw16 0

13. 依赖

当前 Android.bptof_stream 依赖以下关键库:

  • libdepth_processor
  • libspectre
  • libimagerwrapper
  • libalgorithm_visual_service
  • android.hardware.automotive.evs@1.0
  • android.hardware.automotive.evs@1.1
  • libui
  • libgui
  • libEGL
  • libGLESv2

其中:

  • libalgorithm_visual_service 用于 AVS 推流。
  • libEGL / libGLESv2 用于本地显示。

14. 建议阅读顺序

建议按以下顺序理解当前工程:

  1. srcs/main.cpp
  2. include/ToFPipelineManager.h
  3. srcs/ToFPipelineManager.cpp
  4. EvsStream/StreamHandler.h
  5. srcs/StreamHandler.cpp
  6. display/DisplayController.h
  7. display/DisplayController.cpp
  8. display/SimpleRenderer.h
  9. display/SimpleRenderer.cpp
  10. EvsStream/LiveStreamController.h
  11. srcs/LiveStreamController.cpp
  12. EvsStream/StreamPusher.h
  13. srcs/StreamPusher.cpp

15. 一句话总结

当前工程已经从“采集 + 解算 + 可选保存”的基础版本,演进为一套面向设备常驻运行的 ToF 服务:

EVS 采集
  -> 热插拔 / binder died 自动恢复
  -> SDK 解算
  -> 可选本地显示
  -> 可选异步保存
  -> 流控制器缓冲 / 限流 / 分发 / 重连
  -> AVS 推流到客户端