系列文章
动手入门第三方算法集成系列:
【Camera专题】HAL1- 实现第三方算法并集成到Android系统
【Camera专题】HAL1- 以SO库或a库的方式集成第三方算法
【Camera专题】HAL1- 多帧降噪算法的集成(实战1)
一、前言
最近自己学了一下Camera数据流的知识,如何运用这些知识呢?
最好的方式就是加入第三方算法。当然,虽然学习都是以HelloWorld的方式;
但自己实现的算法,还是要能看到效果的,也要容易理解。
平台:高通8909
版本:HAL1
需要掌握的一些知识点
- 1.熟悉Camera数据流,至少知道回调函数在哪里
- 2.了解YUV的相关知识
- 3.掌握相关的结构体
- 4.掌握HAL层的一些API
- 5.掌握JNI的知识
- 6.动手实践第三方算法,并植入系统。
第三方算法的集成方式
- APP集成
这就需要你会java并且懂JNI - Hal层集成
需要你熟悉Camera数据流,YUV的相关知识,HAL层的一些API。
第三方算法集成推荐博客
传送门
这是我同事的博客,以前专门做第三方算法移植的,现在做framework层了。
很多知识我也是看他的博客学习的,下面的知识点就是参考他的。
然后我加入自己的实践,学以致用!
二、知识点
1.HAL1-Camera数据流的回调函数
参考我同事写的文章
高通(QCOM)平台HAL层获取预览/拍照/录像YUV数据
这里做一些总结:(以下是HAL1的函数)
1.Preview流 回调函数
- 正常模式下
根据平台不同,会调用都下面其中一个函数。
hardware/qcom/camera/QCamera2QCameraHWICallbacks.cpp
1. QCamera2HardwareInterface::preview_stream_cb_routine(...)
2. QCamera2HardwareInterface::synchronous_stream_cb_routine(...)
- no-display-mode模式下
no-display-mode是指App打开Camera后, 在startPreview之前设置参数Parameters.set("no-display-mode", "1");,
然后可以不设置预览surface进行预览拍照(主要用于双摄项目中, 副摄设置no-display-mode, 对用户不可见).
1. QCamera2HardwareInterface::nodisplay_preview_stream_cb_routine(...)
2.Snapshot流 回调函数
- 非ZSL模式
hardware/qcom/camera/QCamera2/QCameraHWICallbacks.cpp
hardware/qcom/camera/QCamera2/QCameraPostProc.cpp
1. QCamera2HardwareInterface::capture_channel_cb_routine(...)
2. QCameraPostProcessor::processPPData(...)
- ZSL模式
1. QCamera2HardwareInterface::zsl_channel_cb(...)
2. QCameraPostProcessor::processPPData(...)
3.Video流 回调函数
- 录像预览流数据
hardware/qcom/camera/QCamera2/QCameraHWICallbacks.cpp
1. QCamera2HardwareInterface::video_stream_cb_routine(...)
- 录像拍照流数据
1.QCamera2HardwareInterface::snapshot_channel_cb_routine(...)
小结:
以上的函数,都可拿到 void* data 类型的YUV数据,本质上就是 unsigned char* 类型的数组
数据范围[0-255]
如果你打印出来,你可以看到 0x00到0xff的数据
2.YUV的一些基础知识
关于YUV的知识,可以自行度娘或者谷歌学习。
以下总结一些我觉得应该知道的:
YUV的含义
YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,
它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,
只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。
并且,YUV不像RGB那样要求三个独立的视频信号同时传输,所以用YUV方式传送占用极少的频宽。
YUV 采样(其中一种)
YUV 4:2:0采样,每四个Y共用一组UV分量一个YUV占8+2+2 = 12bits 1.5个字节。
需要占用的内存:w * h * 3 / 2=w * h * 1.5
内存则是:yyyyyyyyuuvv
存放方式是一个一维数组,但是你可以根据宽x高把他看作二维数组来理解,如下图。
YUV420sp格式如下图
分辨率为8X4的YUV图像,它们的格式如下图:
我们经常看到的Android或者IOS摄像头采集到的NV12或者NV21数据,其实本质上是YUV,即YUV420sp。
数据存放的格式【YYYYUV】,先放完所有的Y数据,然后在放UV数据。
3.掌握相关的结构体
1. mm_camera_buf_def_t:数据流 帧缓冲区 结构体
hardware/qcom/camera/QCamera2/stack/common/mm_camera_interface.h
typedef struct {
uint32_t stream_id;//标识流对象的id,跟人的身份证类似
cam_stream_type_t stream_type;//数据流类型
uint32_t buf_idx;//放在内存中的 buf索引
uint8_t is_uv_subsampled;
struct timespec ts; //时间戳,在调用DQBUF时填充
uint32_t frame_idx;//帧序列编号,待DQBUF填充
int8_t num_planes;//帧缓冲区的平面数,在mem分配期间填充
struct v4l2_plane planes[VIDEO_MAX_PLANES];//帧缓冲区的平面信息,将在mem分配期间填充
int fd; //帧缓冲区的文件描述符
void *buffer;//指向帧缓冲区的指针
size_t frame_len;//帧长度
void *mem_info;//指向附加mem信息的用户特定指针
} mm_camera_buf_def_t;
这个结构体里,最重要的信息:
指向帧缓冲区的指针
void* data 类型的YUV数据,本质上就是 unsigned char* 类型的数组
帧长度
2. mm_camera_super_buf_t
hardware/qcom/camera/QCamera2/stack/common/mm_camera_interface.h
typedef struct {
uint32_t camera_handle;//唯一标识相机对象
uint32_t ch_id;//通道id
uint32_t num_bufs;//super buf 中的缓冲区数,不能超过4
mm_camera_buf_def_t* bufs[MAX_STREAM_NUM_IN_BUNDLE];
//bundle中的缓冲区数组
} mm_camera_super_buf_t;
这里的super_buf"继承"了父类mm_camera_buf_def_t,
(C语言的继承就是结构体里包含其他结构体)。
加入了通道信息ch_id、缓冲区数。
3. cam_frame_len_offset_t 和cam_mp_len_offset_t
hardware/qcom/camera/QCamera2/stack/common/cam_types.h
typedef struct{
uint32_t len;
uint32_t offset;
int32_t offset_x;
int32_t offset_y;
int32_t stride;
int32_t scanline;
int32_t width; /* width without padding */
int32_t height; /* height without padding */
} cam_mp_len_offset_t;
typedef struct {
uint32_t num_planes;
union {
cam_sp_len_offset_t sp;
cam_mp_len_offset_t mp[VIDEO_MAX_PLANES];
};
uint32_t frame_len;
} cam_frame_len_offset_t;
非对齐(非填充)的宽高
- width
- height
对齐(填充)后的宽高
- int32_t stride;
- nt32_t scanline;
视频尺寸对齐
#define VIDEO_BUF_ALLIGN(size, allign) (((size) + (allign-1)) & (typeof(size))(~(allign-1)))
预览数据对齐
#define IMAGE_ALIGN(x, mask) (((x) + (mask) - 1) & ~((mask) - 1))
4. cam_dimension_t
hardware/qcom/camera/QCamera2/stack/common/cam_types.h
typedef struct {
int32_t width;
int32_t height;
} cam_dimension_t;
图片的实际宽高
对齐(填充)概念
在高通平台, YUV数据一般会有对齐, 对齐是指为了处理效率更高, 图片宽高必须是某些数的整数倍(如 32或者64),
当然为什么对齐后处理效率更高, 这个好像是由于硬件设计的一些特性, 详细就不太清楚了. 如果图片宽高不是(32或者64)倍数,
对齐过后会在原图片右侧和下方留下无效像素, 当然经过JPEG硬件编码过后会被裁剪,
所以App层看到的是正常的, 只不过我们在HAL层获取的YUV数据是有无效像素的,
4.HAL层相关的API
如果你看过我之前写的博客,应该知道,数据流都是存放在通道里的!
一个通道里面可以有多个数据流。
1.获取 通道[QCameraChannel]
通道类型:
- QCAMERA_CH_TYPE_VIDEO(video)
录像预览通道 - QCAMERA_CH_TYPE_SNAPSHOT(video snap)
录像拍照通道 - QCAMERA_CH_TYPE_ZSL(zsl)
ZSL通道 - QCAMERA_CH_TYPE_CAPTURE(capture)
拍照通道
相关API:
示例(zsl_channel_cb()中获取channel):
QCamera2HardwareInterface *pme = (QCamera2HardwareInterface *)userdata;
QCameraChannel *pChannel = pme->m_channels[QCAMERA_CH_TYPE_ZSL];
其他通道类似。
2.获取 数据流
- 方式一
mm_camera_buf_def_t* yuvFrame = NULL;//定义数据流缓冲区结构体
QCameraStream* stream = NULL;//数据流
// frame 为 mm_camera_super_buf_t 类型
for (uint32_t i = 0; i < frame->num_bufs; i++) {
//通过getStreamByHandle找到数据流
stream = pChannel->getStreamByHandle(frame->bufs[i]->stream_id);
if (stream != NULL) {
// 通过isOrignalTypeOf 找到拍照数据等其他数据
if (stream->isOrignalTypeOf(CAM_STREAM_TYPE_SNAPSHOT)) {
yuvFrame = frame->bufs[i];
break;
}
}
}
- 方式二
preview_stream_cb_routine(mm_camera_super_buf_t *super_frame,
QCameraStream * stream,
void *userdata) {
···
mm_camera_buf_def_t *frame = super_frame->bufs[0];
···
}
在我们的回调函数中,通过 mm_camera_buf_def_t *frame = super_frame->bufs[0];
就可以拿到mm_camera_buf_def_t 的YUV数据了。
3.获取 数据流里面的相关信息
- 1.数据流的地址
mm_camera_buf_def_t *frame;
unsigned char * yuvDta = (unsigned char *)frame->buffer;
- 2.数据流长度
mm_camera_buf_def_t *frame;
int len = frame->len;
- 3.数据流的宽高(对齐和非对齐)
cam_frame_len_offset_t offset;
memset(&offset, 0, sizeof(cam_frame_len_offset_t));
cam_dimension_t dim;
memset(&dim, 0, sizeof(dim));
//stream为 QCameraStream*
stream->getFrameOffset(offset);
stream->getFrameDimension(dim);
图片实际宽为:dim.width, 高为:dim.height,
对齐后的宽为:offset.mp[0].stride, 高为:offset.mp[0].scanline
4.JNI的知识
推荐2个视频学习
Android-JNI入门
Android-JNI进阶
当然网上也有许多关于JNI的博客可以学习。
本文是基于HAL层集成第三方算法,JNI这部分用不到,你可以先不用学习。
还是那句话,纸上得来终觉浅,绝知此事要躬行!
三、实践
1、自己实现一个“”牛逼“”的算法
因为目前手上没有第三方算法,所以我们就自己动手实现一个。
为了看到效果,我们把数据流的一半变成灰色,另一半正常。
- 0-黑色
- 128-灰色
- 255-白色
hardware/qcom/camera/QCamera2/HAL/QCamera2HWICallbacks.cpp
加入以下代码
void YUV2Gray(unsigned char* srcYuv,int w,int h) {
int mid = w*h/2;//找到图像中间位置
unsigned char* startP = srcYuv + mid;//指针移到中间
//处理Y数据,uv数据是色彩信息 我们不管
for(int i=0;i
YUV数据中,前面W * H是Y数据,后面W * H/2是UV数据。
那我们要定位到图像的中间位置,就是W * H/2.
举个例子:
宽:8 高:4的YUV 数据如下图
那么图像的中间就是Y17对的数据,即4x8/2=16(数组是从0开始计算的)。
算法很简单,麻雀虽小五脏俱全。
2、把算法集成到HAL层
我们要对预览流进行处理,那么在相应的回调函数修改即可。
hardware/qcom/camera/QCamera2/HAL/QCamera2HWICallbacks.cpp
//自定义第三方算法
void YUV2Gray(unsigned char* srcYuv,int w,int h) {
int mid = w*h/2;
unsigned char* startP = srcYuv + mid;
for(int i=0;ibufs[0]->mem_info;
if (pme == NULL) {
ALOGE("%s: Invalid hardware object", __func__);
free(super_frame);
return;
}
if (memory == NULL) {
ALOGE("%s: Invalid memory object", __func__);
free(super_frame);
return;
}
mm_camera_buf_def_t *frame = super_frame->bufs[0];
if (NULL == frame) {
ALOGE("%s: preview frame is NLUL", __func__);
free(super_frame);
return;
}
//获取YUV数据地址
++ unsigned char* yuv = (unsigned char *)frame->buffer;
++ cam_dimension_t dim;
//获取实际宽高
++ stream->getFrameDimension(dim);
++ cam_frame_len_offset_t offset;
//获取对齐后宽高
++ stream->getFrameOffset(offset);
++ ALOGE("%s: zcf frame_len=%d dim.w=%d,diw.h=%d,对齐后w=%d,高=%d",
__func__,
frame->frame_len,dim.width,dim.height,
offset.mp[0].stride,offset.mp[0].scanline);
//获取调用第三方算法
++ YUV2Gray(yuv,offset.mp[0].offset.mp[0].scanline);
···
}
我们把宽高打印出来看看:
实际宽高:360x320
对齐宽高:384x320
实际上数据要做对齐:360/32=11.25 不能整除,系统会自动对齐,填充数据整除。
因此buf_len= 384x320x1.5=184320.
为啥log是188416呢,因为还有别的一些无效信息被填充,因此会大一些。
编译
你也可以编译整个系统:
make -j32 2>&1 | tee mlog
然后刷机验证。
或者
mmm hardware/qcom/camera/QCamera2/HAL/
编译完成后会在以下目录:
out/target/product/【项目名】/system/lib/hw
生成camera.msm8909.so库
adb push camera.msm8909.so system/lib/hw
然后重启生效
3.结果
可以看到,屏幕的一半变成了灰色!
学到这,你应该懂得如何集成第三方算法了!
虽然自己写的这个算法不牛逼,甚至有点丑陋,
不过集成的基本思路就是这样!
4.小结
集成算法的步骤:
- 1.弄明白第三方算法的接口
- 2.熟悉整个数据流,知道在哪里改
那么集成算法有哪些技术含量呢?
- 1.熟悉数据流
- 2.懂得评估算法,从内存,耗时,效果三个方面
- 3.流程和策略上的优化
- 4.多帧处理,双摄甚至多摄的集成等
下篇文章,我们以so库的形式集成第三方算法!
继续当一名咸鱼(* ̄︶ ̄)!