使用Android NDK Camera2经验总结

发布时间 2023-04-04 21:07:32作者: qi-xmu

2023年03月30日 NDK Camera

参考文章:https://blog.csdn.net/daihuimaozideren/article/details/101235393

第一部分 程序入口逻辑

首先需要做相机权限检查和相机的类型检查,这里使用的相机必须满足Camera2的最低要求。

然后程序的启动流程如下:

img

红色部分需要通过NDK实现对应的功能。


第二部分 获取相机设置信息

img

以上图像中存在几个重要的变量:

  • ACameraManager:使用ACameraManager_create构建,使用完成需要ACameraManager_delete删除释放内存。
  • ACameraList:通过ACameraManage_getCameraIdList使用ACameraManager获取手机所有的相机设备(包括前置镜头,后置镜头,外置镜头)使用完成之后需要ACameraManager_deleteCameraList释放内存。
  • CameraIDACameraIdList类型中存在cameraIds,作为相机的唯一标识符。
  • ACameraMetadata:通过ACameraManager_getCameraCharacteristics使用CameraIDACameraManager获取相机的元数据。使用完成之后需要通过ACameraMetadata_free释放内存。
  • 通过ACameraMetadata_getAllTags可以获取所有的相机的标签数量和标签值。这个Tags是一个键值对系统,可以根据标签获取相应的数据,也可以通过设置标签纸去更改相机的参数。
  • 通过ACameraMetadata_getConstEntry使用ACameraMetadata和标签名可以获取对应的标签值。
  • 通过ACaptureRequest_setEntry_xx使用request和标签名可以设置对应的标签值。

第三部分 启动相机会话

img

各个对象之间的关系:

img

最终得到的是ACameraRequestACaptureSessionOutputContainer两个对象,通过ACameraCaptureSession_setRepeatingRequest可以实现不断地相同地相机请求,达到相机预览的效果。使用ACameraCaptureSession_stopRepeating函数停止预览。

以上所有变量地申请都需要最后通过xxx_delete方法释放内存。防止内存泄露造成错误。

第四部分 获取图像数据

img

图像读取思路

设置图像格式和图像回调的AImageReader_new获取一个AImageReader对象。该对象再通过第三部分的内容设置为相机的数据输出。

成功获取图像数据之后,数据处理代码示例如下:

void onImageAvailable(void *context, AImageReader *reader) {
    media_status_t res;
    // 获取图像的格式
    int32_t img_fmt;
    int32_t width, height;
    res = AImageReader_getFormat(reader, &img_fmt);
    if (res) LOG_ERR("AImageReader_getFormat error");
    res = AImageReader_getWidth(reader, &width);
    if (res) LOG_ERR("AImageReader_getFormat error");
    res = AImageReader_getHeight(reader, &height);
    if (res) LOG_ERR("AImageReader_getWidth error");

    AImage *image;
    res = AImageReader_acquireNextImage(reader, &image);
    if (res) LOG_ERR("AImageReader_acquireNextImage  error");

    // 获取图像的时间戳
    int64_t image_timestamp;
    AImage_getTimestamp(image, &image_timestamp);
    uint8_t *y_data, *u_data, *v_data;
    int32_t y_len = 0, u_len = 0, v_len = 0;
    if (AIMAGE_FORMAT_YUV_420_888 == img_fmt) {
        // 获取各个分量的指针,这个地方存在一个问题,这里的数据结构如下
        // YY ... YYYY (repeat width * height) U V U V ..... (total width * height /2);
        // 数据总长度为 width * height * 3 / 2
        res = AImage_getPlaneData(image, 0, &y_data, &y_len);
        if (res) LOG_ERR("AImage_getPlaneData 0 error");
        res = AImage_getPlaneData(image, 1, &u_data, &u_len);
        if (res) LOG_ERR("AImage_getPlaneData 1 error");
        res = AImage_getPlaneData(image, 2, &v_data, &v_len);
        if (res) LOG_ERR("AImage_getPlaneData 2 error");
//        LOG_WARN("0bit %x %x %x %x", y_data[0], y_data[1], y_data[2], y_data[3]);
    } else {
        // 其他格式
        int32_t image_buffer_len = 0;
        uint8_t *image_raw_buffer;
        res = AImage_getPlaneData(image, 0, &image_raw_buffer, &image_buffer_len);
        if (res) LOG_ERR("AImage_getPlaneData 0 error");
    }

    // 获取一行的像素长度 >= width (内存对齐的原因)
    int32_t rowStride;
    AImage_getPlaneRowStride(image, 0, &rowStride);

    // 这里传入的上下文 为 CameraEngine对象
    auto *cam_eng = reinterpret_cast<CameraEngine *>(context);
    //  获取 surface 生成的 NativeWindow对象 用于前端显示
    ANativeWindow *window = cam_eng->GetSurfaceNativeWindow();

    // 获取图像图像的格式
    ANativeWindow_setBuffersGeometry(window, width, height, img_fmt);


    ANativeWindow_Buffer aw_buffer;
    ANativeWindow_acquire(window);
    ANativeWindow_lock(window, &aw_buffer, nullptr);
    auto *bits = reinterpret_cast<uint8_t *>(aw_buffer.bits);
    if (AIMAGE_FORMAT_YUV_420_888 == img_fmt) {
        memcpy(bits, y_data, y_len + u_len + 1);
    } else if (AIMAGE_FORMAT_JPEG == img_fmt) {
//        memcpy(bits, image_raw_buffer, image_buffer_len);
        LOG_WARN("Can not directly show jpeg.");
    } else if (AIMAGE_FORMAT_RGBA_8888 == img_fmt) {

    }

    ANativeWindow_unlockAndPost(window);
    ANativeWindow_release(window);
    AImage_delete(image);
}