使用Android NDK Camera2经验总结
2023年03月30日 NDK Camera
参考文章:https://blog.csdn.net/daihuimaozideren/article/details/101235393
项目链接:https://github.com/qi-xmu/Android-ndk-camera-zh.git
项目基于官方NDK Camera2 texture sample,添加了详细的中文注释,点个Star!!!谢谢!
第一部分 程序入口逻辑
首先需要做相机权限检查和相机的类型检查,这里使用的相机必须满足Camera2的最低要求。
然后程序的启动流程如下:
红色部分需要通过NDK实现对应的功能。
第二部分 获取相机设置信息
以上图像中存在几个重要的变量:
ACameraManager
:使用ACameraManager_create
构建,使用完成需要ACameraManager_delete
删除释放内存。ACameraList
:通过ACameraManage_getCameraIdList
使用ACameraManager
获取手机所有的相机设备(包括前置镜头,后置镜头,外置镜头)使用完成之后需要ACameraManager_deleteCameraList
释放内存。CameraID
:ACameraIdList
类型中存在cameraIds
,作为相机的唯一标识符。ACameraMetadata
:通过ACameraManager_getCameraCharacteristics
使用CameraID
和ACameraManager
获取相机的元数据。使用完成之后需要通过ACameraMetadata_free
释放内存。- 通过
ACameraMetadata_getAllTags
可以获取所有的相机的标签数量和标签值。这个Tags是一个键值对系统,可以根据标签获取相应的数据,也可以通过设置标签纸去更改相机的参数。 - 通过
ACameraMetadata_getConstEntry
使用ACameraMetadata
和标签名可以获取对应的标签值。 - 通过
ACaptureRequest_setEntry_xx
使用request
和标签名可以设置对应的标签值。
第三部分 启动相机会话
各个对象之间的关系:
最终得到的是ACameraRequest
和ACaptureSessionOutputContainer
两个对象,通过ACameraCaptureSession_setRepeatingRequest
可以实现不断地相同地相机请求,达到相机预览的效果。使用ACameraCaptureSession_stopRepeating
函数停止预览。
以上所有变量地申请都需要最后通过
xxx_delete
方法释放内存。防止内存泄露造成错误。
第四部分 获取图像数据
图像读取思路
设置图像格式和图像回调的
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);
}
本文来自博客园,作者:qi-xmu,转载请注明原文链接:https://www.cnblogs.com/qi-xmu/p/17287888.html