跨平台相机适配方案总结

当硬件平台从高通8155 + ToF 相机 换成其他芯片+其他相机时,获取视频流的架构需要分层设计,只需替换底层适配层,上层业务代码可以保持不变。

分层架构设计

scss 复制代码
┌─────────────────────────────────────────────────────────────────────────────────────┐
│                           跨平台相机适配架构                                           │
├─────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │                        业务层(与硬件无关)                                    │    │
│  │  • ToFPipelineManager(解算、队列、统计)                                     │    │
│  │  • DisplayController(本地显示)                                             │    │
│  │  • LiveStreamController(推流)                                              │    │
│  └─────────────────────────────────────────────────────────────────────────────┘    │
│                                       ↓                                              │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │                        抽象接口层(ICameraWrapper)                          │    │
│  │  • open() / close()                                                        │    │
│  │  • startStream() / stopStream()                                            │    │
│  │  • getFrame() / returnFrame()                                              │    │
│  │  • setFrameCallback()                                                      │    │
│  └─────────────────────────────────────────────────────────────────────────────┘    │
│                                       ↓                                              │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │                        平台适配层(替换部分)                                  │    │
│  ├─────────────────┬─────────────────┬─────────────────┬─────────────────────┤    │
│  │ 高通8155+ToF    │ 瑞芯微+RGB      │ NVIDIA+深度相机  │ 树莓派+USB相机     │    │
│  │ (EVS HAL)       │ (V4L2)          │ (GStreamer)      │ (libcamera)        │    │
│  └─────────────────┴─────────────────┴─────────────────┴─────────────────────┘    │
│                                       ↓                                              │
│  ┌─────────────────────────────────────────────────────────────────────────────┐    │
│  │                        硬件层                                                │    │
│  │  ToF相机 │ USB摄像头 │ MIPI CSI │ 网络相机                                   │    │
│  └─────────────────────────────────────────────────────────────────────────────┘    │
│                                                                                      │
└─────────────────────────────────────────────────────────────────────────────────────┘

不同平台的适配实现

1. 高通8155 + ToF相机(EVS HAL)

arduino 复制代码
// ============================================================================
// EvsCameraWrapper.cpp - 高通 EVS 适配
// ============================================================================

class EvsCameraWrapper : public ICameraWrapper {
public:
    bool open(const std::string& deviceId) override {
        // 使用你的 StreamHandler
        mStreamHandler = new StreamHandler();
        return mStreamHandler->start(deviceId.c_str(), 1, HAL_PIXEL_FORMAT_RAW16);
    }
    
    bool startStream() override {
        // 注册回调
        mStreamHandler->setFrameCallback([this](const CopiedFrame& frame) {
            FrameData data;
            data.data = frame.data.data();
            data.size = frame.data.size();
            data.width = frame.width;
            data.height = frame.height;
            data.format = frame.format;
            data.timestamp = frame.timestamp;
            if (mCallback) mCallback(data);
        });
        return true;
    }
    
    // 需要配合 deliverFrame_1_1 的 doneWithFrame
    void returnFrame(const FrameData& frame) override {
        // EVS 需要归还缓冲区
        if (frame.nativeHandle) {
            // 调用 doneWithFrame
        }
    }
    
private:
    sp<StreamHandler> mStreamHandler;
    FrameCallback mCallback;
};

2. 瑞芯微 + USB摄像头(V4L2)

ini 复制代码
// ============================================================================
// V4l2CameraWrapper.cpp - V4L2 适配(Linux 标准接口)
// ============================================================================

class V4l2CameraWrapper : public ICameraWrapper {
public:
    bool open(const std::string& deviceId) override {
        // /dev/video0
        mFd = open(deviceId.c_str(), O_RDWR);
        if (mFd < 0) return false;
        
        // 配置格式
        struct v4l2_format fmt = {};
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
        fmt.fmt.pix.width = 640;
        fmt.fmt.pix.height = 480;
        ioctl(mFd, VIDIOC_S_FMT, &fmt);
        
        // 请求缓冲区
        struct v4l2_requestbuffers req = {};
        req.count = 4;
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory = V4L2_MEMORY_MMAP;
        ioctl(mFd, VIDIOC_REQBUFS, &req);
        
        // 映射缓冲区
        for (int i = 0; i < 4; i++) {
            struct v4l2_buffer buf = {};
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            ioctl(mFd, VIDIOC_QUERYBUF, &buf);
            
            mBuffers[i].data = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
                                     MAP_SHARED, mFd, buf.m.offset);
            mBuffers[i].size = buf.length;
        }
        
        return true;
    }
    
    bool startStream() override {
        // 入队所有缓冲区
        for (int i = 0; i < 4; i++) {
            struct v4l2_buffer buf = {};
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            buf.index = i;
            ioctl(mFd, VIDIOC_QBUF, &buf);
        }
        
        // 启动流
        ioctl(mFd, VIDIOC_STREAMON, &type);
        
        // 启动捕获线程
        mCaptureThread = std::thread([this]() { captureLoop(); });
        return true;
    }
    
    void captureLoop() {
        while (mRunning) {
            struct v4l2_buffer buf = {};
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            
            // 阻塞等待帧
            ioctl(mFd, VIDIOC_DQBUF, &buf);
            
            // 转换为统一格式
            FrameData frame;
            frame.data = (uint8_t*)mBuffers[buf.index].data;
            frame.size = buf.bytesused;
            frame.timestamp = getTimestamp();
            
            if (mCallback) mCallback(frame);
            
            // 重新入队
            ioctl(mFd, VIDIOC_QBUF, &buf);
        }
    }
    
private:
    int mFd = -1;
    struct Buffer {
        void* data;
        size_t size;
    } mBuffers[4];
    std::thread mCaptureThread;
    FrameCallback mCallback;
};

3. NVIDIA Jetson + 深度相机(GStreamer)

arduino 复制代码
// ============================================================================
// GstCameraWrapper.cpp - GStreamer 适配
// ============================================================================

class GstCameraWrapper : public ICameraWrapper {
public:
    bool open(const std::string& deviceId) override {
        // 构建 pipeline
        // nvarguscamerasrc ! video/x-raw(memory:NVMM) ! nvvidconv ! appsink
        std::string pipeline = 
            "nvarguscamerasrc sensor-id=" + deviceId + " ! "
            "video/x-raw,width=640,height=480 ! "
            "appsink name=sink emit-signals=true max-buffers=2 drop=true";
        
        mPipeline = gst_parse_launch(pipeline.c_str(), NULL);
        mSink = gst_bin_get_by_name(GST_BIN(mPipeline), "sink");
        
        // 设置回调
        gst_app_sink_set_callbacks(GST_APP_SINK(mSink), &sCallbacks, this, NULL);
        
        return true;
    }
    
    bool startStream() override {
        gst_element_set_state(mPipeline, GST_STATE_PLAYING);
        
        // 启动处理线程
        mProcessThread = std::thread([this]() { processLoop(); });
        return true;
    }
    
    static GstFlowReturn onNewSample(GstAppSink* sink, gpointer user_data) {
        auto* self = static_cast<GstCameraWrapper*>(user_data);
        GstSample* sample = gst_app_sink_pull_sample(sink);
        
        GstBuffer* buffer = gst_sample_get_buffer(sample);
        GstMapInfo map;
        gst_buffer_map(buffer, &map, GST_MAP_READ);
        
        FrameData frame;
        frame.data = map.data;
        frame.size = map.size;
        
        if (self->mCallback) self->mCallback(frame);
        
        gst_buffer_unmap(buffer, &map);
        gst_sample_unref(sample);
        
        return GST_FLOW_OK;
    }
    
private:
    GstElement* mPipeline = nullptr;
    GstAppSink* mSink = nullptr;
    static GstAppSinkCallbacks sCallbacks;
};

4. 树莓派 + 相机模块(libcamera)

scss 复制代码
// ============================================================================
// LibcameraWrapper.cpp - libcamera 适配
// ============================================================================

class LibcameraWrapper : public ICameraWrapper {
public:
    bool open(const std::string& deviceId) override {
        // 创建相机管理器
        mCameraManager = std::make_unique<CameraManager>();
        mCameraManager->start();
        
        // 获取相机
        auto cameras = mCameraManager->cameras();
        for (auto& cam : cameras) {
            if (cam->id() == deviceId) {
                mCamera = cam;
                break;
            }
        }
        
        // 配置
        mCamera->acquire();
        std::unique_ptr<CameraConfiguration> config = mCamera->generateConfiguration(
            { StreamRole::Viewfinder });
        config->at(0).size = Size(640, 480);
        config->at(0).pixelFormat = formats::YUYV;
        mCamera->configure(config.get());
        
        // 分配缓冲区
        mAllocator = std::make_unique<FrameBufferAllocator>(mCamera);
        mAllocator->allocate(mCamera->streams()[0]);
        
        return true;
    }
    
    bool startStream() override {
        mCamera->requestStarted.connect(this, &LibcameraWrapper::requestComplete);
        mCamera->start();
        
        // 提交请求
        for (int i = 0; i < 3; i++) {
            std::unique_ptr<Request> request = mCamera->createRequest();
            request->addBuffer(mCamera->streams()[0], 
                               mAllocator->allocate(mCamera->streams()[0]));
            mCamera->queueRequest(request.get());
        }
        return true;
    }
    
    void requestComplete(Request* request) {
        FrameBuffer* buffer = request->buffers().begin()->second;
        
        FrameData frame;
        frame.data = (uint8_t*)buffer->planes()[0].fd.get();
        frame.size = buffer->planes()[0].length;
        
        if (mCallback) mCallback(frame);
        
        request->reuse();
        mCamera->queueRequest(request);
    }
    
private:
    std::unique_ptr<CameraManager> mCameraManager;
    std::shared_ptr<Camera> mCamera;
    std::unique_ptr<FrameBufferAllocator> mAllocator;
};

业务层使用方式(无需修改)

arduino 复制代码
// ============================================================================
// ToFPipelineManager.cpp - 业务层代码(平台无关)
// ============================================================================

class ToFPipelineManager {
public:
    void initialize(std::shared_ptr<ICameraWrapper> camera) {
        mCamera = camera;  // 注入相机适配器
    }
    
    bool start() {
        // 打开相机
        if (!mCamera->open("camera0")) {
            return false;
        }
        
        // 设置回调
        mCamera->setFrameCallback([this](const FrameData& frame) {
            onFrameReceived(frame);
        });
        
        // 启动流
        return mCamera->startStream();
    }
    
    void onFrameReceived(const FrameData& frame) {
        // 这里统一处理,不管相机是什么平台
        // 1. 解算深度
        // 2. 推流
        // 3. 本地显示
        // ... 代码完全不变
    }
    
private:
    std::shared_ptr<ICameraWrapper> mCamera;
};

// ============================================================================
// main.cpp - 根据平台选择具体实现
// ============================================================================

int main() {
    // 平台特定的相机适配器
    std::shared_ptr<ICameraWrapper> camera;
    
#if defined(PLATFORM_QCOM_8155)
    camera = std::make_shared<EvsCameraWrapper>();
#elif defined(PLATFORM_ROCKCHIP)
    camera = std::make_shared<V4l2CameraWrapper>();
#elif defined(PLATFORM_NVIDIA_JETSON)
    camera = std::make_shared<GstCameraWrapper>();
#elif defined(PLATFORM_RASPBERRY_PI)
    camera = std::make_shared<LibcameraWrapper>();
#endif
    
    // 业务层代码完全不变
    ToFPipelineManager pipeline;
    pipeline.initialize(camera);
    pipeline.start();
    
    return 0;
}

不同平台对比总结

平台 接口类型 适配难度 关键点
高通8155/MTK8678 EVS HAL 需要处理 buffer 生命周期(doneWithFrame)
瑞芯微/全志 V4L2 Linux 标准接口,代码通用
NVIDIA Jetson GStreamer 硬件加速编码,pipeline 灵活
树莓派 libcamera 新标准,支持 CSI 相机
PC调试 OpenCV 快速原型开发
网络相机 RTSP 网络传输,延迟较高
迁移类型 需修改的代码 工作量
高通8155 → 瑞芯微 仅适配层(约500行) 1周
高通8155 → Jetson 适配层 + 编码推流 2周
ToF → RGB相机 解算层全部重写 1-3个月
同一平台不同型号 几乎无修改 1天

高通8155平台下换相机的分析

高通8155支持的相机接口

接口类型 说明 EVS是否支持 代码改动
MIPI CSI 车载相机主流接口 ✅ 支持 无需改代码,改设备节点
USB (UVC) USB摄像头 ⚠️ 通过V4L2 需写V4L2适配层
RGB (RAW/ YUV) 传感器原始输出 ✅ 支持 无需改代码
ToF 深度相机 ✅ 支持 当前已实现
GMSL/FPD-Link 长距离传输 ✅ 支持 无需改代码

换不同类型相机的改动分析

场景1:高通8155 + 同接口RGB相机(不改代码)

arduino 复制代码
// 只需修改 cameraID 和 format
// 你的代码已经支持,无需修改

// 之前(ToF)
const char* cameraID = "tof";
int32_t format = HAL_PIXEL_FORMAT_RAW16;  // 32

// 改成(RGB)
const char* cameraID = "camera0";
int32_t format = HAL_PIXEL_FORMAT_YUYV;   // 或 YUV_420_888

// 其他代码完全不变!

场景2:高通8155 + USB相机(需要适配层)

arduino 复制代码
// USB相机走的是 V4L2,不走 EVS HAL
// 需要写一个适配层,但上层代码不变

// 新增适配层
class UsbCameraAdapter : public ICameraSource {
public:
    bool open(const char* device) override {
        // V4L2 操作
        fd = open("/dev/video0", O_RDWR);
        ioctl(fd, VIDIOC_S_FMT, &fmt);
        // ...
    }
    
    bool startStream() override {
        // 启动 V4L2 采集线程
    }
    
    void setFrameCallback(FrameCallback cb) override {
        // 转换为统一格式后回调
    }
};

// 上层 ToFPipelineManager 不需要改!
// 只是注入不同的相机源

场景3:高通8155 + MIPI CSI Raw相机(不改代码)

ini 复制代码
// MIPI CSI Raw 相机同样走 EVS HAL
// 只需修改 cameraId

mEvsCamera = mEvsEnumerator->openCamera_1_1("mipi_raw", targetCfg);
// RAW 数据格式可能不同,需要配置正确的格式
targetCfg.format = HAL_PIXEL_FORMAT_RAW10;  // 或其他

各平台开发环境完整对比

完整对比表

平台 构建系统 操作系统 构建文件 开发环境 编译器 调试方法 工具链文件
高通8155 Soong Android Android.bp AOSP源码/NDK Clang (预置) adb gdb / lldb build/make/core/combo/*.mk
MTK8678 Soong Android Android.bp AOSP源码/NDK Clang (预置) adb gdb / lldb 同上(Android标准)
瑞芯微 CMake/Make Linux CMakeLists.txt Linux (Buildroot/Yocto) ARM GCC gdb 远程调试 arm-linux-gnueabihf.toolchain.cmake
全志 CMake/Make Linux CMakeLists.txt Linux (Tina SDK) ARM GCC gdb 远程调试 arm-openwrt-linux.cmake
NVIDIA Jetson CMake Linux CMakeLists.txt Ubuntu (JetPack) GCC / NVCC gdb + nsight-sysroot 原生(无需交叉编译)
树莓派 CMake Linux CMakeLists.txt Raspberry Pi OS GCC gdb 原生或交叉编译
PC调试 CMake Ubuntu CMakeLists.txt Ubuntu / Windows GCC / MSVC gdb / Visual Studio 原生(无需工具链)

NDK vs AOSP 源码开发对比

方面 NDK 开发 AOSP 源码开发
使用场景 独立应用/库开发 系统级集成、HAL 开发
构建系统 CMake / ndk-build Soong (Android.bp)
访问系统接口 有限(通过 JNI/系统API) 完整(可调用任何内部接口)
EVS HAL 访问 ❌ 无法直接访问 ✅ 可以直接调用
编译环境 独立 NDK + CMake 完整 AOSP 源码
输出位置 /data/local/tmp/ 或 APK /vendor/lib/、/system/lib/
对比项 NDK 独立开发 AOSP 源码开发
适用场景 独立应用、普通库、算法库 系统服务、HAL 层、需要系统权限
EVS HAL 访问 ❌ 无法直接访问 ✅ 完整访问
HIDL 接口 ❌ 无法直接调用 ✅ 可直接调用
系统私有头文件 ❌ 无 ✅ 完整
编译速度 ✅ 快(只编译你的代码) ❌ 慢(需编译整个系统或模块)
环境配置 ✅ 简单(下载 NDK 即可) ❌ 复杂(需下载 AOSP 源码)
调试难度 ✅ 简单 ⚠️ 较复杂
输出位置 /data/local/tmp/ /vendor/lib/、/system/lib/
权限 普通应用权限 系统权限
依赖管理 手动管理 Android.bp 自动管理

不能用NDK的场景(必须AOSP)

高通8155和MTK 的 EVS HAL 开发必须用 AOSP ,普通库可用 NDK ,但有明确的边界:只能编译不依赖系统私有接口的代码。

场景 是否可用 原因
EVS HAL调用 ❌ 不可用 头文件不在NDK中
HIDL接口 ❌ 不可用 需要系统源码
Binder IPC ❌ 不可用 需要系统私有API
GraphicBuffer ❌ 不可用 需要ui库头文件
SurfaceFlinger ❌ 不可用 系统服务
Camera HAL ❌ 不可用 不在NDK标准中
Vendor分区写入 ⚠️ 需要root 普通NDK无权限
系统属性 ⚠️ 有限支持 只能读,部分可写
  • 算法、计算、工具类 → 用NDK
  • EVS HAL、系统服务 → 用AOSP
  • 两者结合 → NDK编译算法库,AOSP中链接使用
相关推荐
华清远见IT开放实验室17 小时前
硬核根基,智能载体:华清远见嵌入式“硬件+仿真+课程+师资”产教融合与实践教学方案
linux·人工智能·stm32·物联网·嵌入式·虚拟仿真
用户8055336980320 小时前
嵌入式Linux驱动开发——Pinctrl 子系统架构深度解析
linux·嵌入式
ziyi程序员21 小时前
[嵌入式]嵌入式在线仿真平台 —— Wokwi 入门指南
嵌入式
Jason_zhao_MR1 天前
RK3506工业网关:如何打通现场采集、无线传输与行业规约接入?
linux·嵌入式硬件·物联网·系统架构·嵌入式
济6171 天前
ROS开发专栏---话题 Topic + 消息 Message 完整实战---适配Ubuntu 22.04
嵌入式·ros2·机器人开发
济6171 天前
ROS开发专栏---机器人运动控制 ---适配Ubuntu 22.04
嵌入式·ros2·机器人开发
tom02182 天前
软考中级《嵌入式系统设计师》全套备考资料(真题 + 教材 + 笔记)
笔记·嵌入式·软考·自学·电子技术·电子资料·变成
番茄灭世神2 天前
PN学堂GD32教程第21篇——WiFiIOT
c语言·stm32·单片机·嵌入式·gd32
NPE~2 天前
[嵌入式]嵌入式在线仿真平台 —— Wokwi 入门指南
stm32·嵌入式·esp32·教程·平台