SA8155P QCOM 车载camera 模块
1. 前言
车载和手机的Camera系统是两套不一样的架构,手机Camera系统最终生成符合人眼感官的图像,需要考虑多样化的场景,如美颜、夜景提亮、降噪、虚化等;而车载Camera系统AIS(Automotive Imaging System)的图像大部分是给自动驾驶等机器识别使用,更多考虑的是远距离传输、多摄像头图像处理等场景。因此两套系统不管是从硬件结构还是从软件框架上差异都很大。
2. 简介
车载Camera的基本组成如下,黄色的箭头代表数据的传输,蓝色的箭头代表控制信号的传输。
ISP图像处理芯片有的会放置在摄像头这边,把处理后的信号传输给到主机,有的是不放置ISP芯片,由主机那边的内置ISP芯片进行图像处理,这样摄像头端的散热会好很多,辐射也小。
这里跟手机Camera最大的差别是多了一个串行器和解串器。我们知道相机传感器或ISP的图像数据输出总线是MIPI CSI标准,其特点是高速穿行,但是传输总线距离较短,否则容易受到干扰无法保证信号的完整性,所以在车辆上,我们需要将其转换成例如GMSL等适合在车上长距离传输的高速总线标准进行传输,所以相机模组内部通常会通过串行板进行总线的转换。另外同轴电缆既可以用来为模组提供电源,也可以传输图像数据。如一些项目,串行器使用MAX92745,解串器是MAX9296A。
总的来说,手机系统上,SOC对接camera硬件,可以直接操作摄像头;而车载系统上,SOC对接的是解串器,camera的配置、上下电需要摄像头模组内的串行器来控制,另外摄像头的供电和信号传输是通过同轴电缆完成的。
车载Camera最经典的使用场景是360°全景图像系统(AVM:Around View Monitor),其硬件拓扑图如下。
原理是利用最少4个广角摄像头的数据合成一幅360°图像的画面。
高清摄像头项目有2种配置方式,分为泊车摄像头(前、后、左、有)和倒车后视摄像头。泊车摄像头和倒车后视摄像头除了Lens其他硬件方案一样。
泊车摄像头系统由安装于车身前、后、左、右四路高清摄像头和智能域控制器组成,域控制器通过同轴线(POC)给摄像头供电,摄像头经过LVDS串行器将视频信号传输给域控制器。摄像头进行图像采集,域控制器对图像进行裁剪、畸变校正、图像拼接、2D/3D视角切换、轨迹线生成等处理,XPU处理完后送入中央控制域进行显示。如下图高清全景系统连接示意图。
倒车后视摄像头系统由安装于车身后部高清摄像头和中央域控制器组成,摄像头进行图像采集,中央域控制器对图像进行裁减、轨迹线生成、显示屏显示等处理。如下图倒车后视摄像头系统连接示意图。
3. AIS系统简介
qcom AIS软件框架图如下。
AIS Server作为一个守护进程运行在Native层,该服务通过封装好的平台相关库最终使用IOCTL和Kernel进行交互,使用中断产生事件,并以V4L2视频框架进行驱动;
同时AIS Server通过Socket和AIS Client交互,经过封装的Socket实现了向对端发送指令并取回对端返回的数据,其实就是实现了IPC。
最新的软件框架图如下。
其实变化不大,主要在Configurer管理的硬件模块,这个跟芯片平台是强相关的。
4. 车载Camera模块
车载Camera模块从上到下涉及Camera APP、Framework、CameraService、CameraProvider、AISService。因车载Camera使用场景比较单一简单,不需要复杂的逐帧控制等需求,当前使用的是Camera1 API。其中APP、Framework层是Android通用接口,这里不予介绍。
关键流程的软件架构图如下。
4.1 CameraService
CameraService注册在Native ServiceManager,使用binder和Framework进行通信。
4.2 CameraProvider
CameraProvider注册在hwServiceManager,使用hwbinder和CameraService进行通信(HwBinder机制可以跨System和Vendor分区使用,使用HIDL接口)。同时使用Socket和ais_service进行通信。
这里涉及到厂商对camera device的具体实现,脑图就不贴上来了。
4.3 ais_service
ais_service是高通针对车载系统的Camera HAL实现,作为后台服务程序运行在native层。
在手机系统里,这部分代码是运行在camera.provider进程的。车载系统把这部分代码独立成一个服务的原因,我猜测是为了适配快速倒车的场景。快速倒车是指Android系统还没有完全起来,用户切换到倒车模式时把倒车影像显示出来。Android启动完成(显示桌面)需要比较长的时间(10s以上),而倒车影像不必等framework启动完成,就可以通过native进程完成图像显示。实现原理是native层的client拉通display和ais_service,把图像数据显示到大屏。native层测试用例可参考qcarcam_test。
ais_server作为camera HAL层的核心,内部有跟平台和硬件强相关的配置信息。可参考高通文档(80-pg469-93_e_sa6155_sa8155_sa8195_automotive_camera_ais_customization_guide.pdf)。
首先是跟平台强相关的CameraBoardType结构体。
/**
* This structure defines the board specific configurable items related to the
* camera subsystem.
*/
typedef struct
{
CameraHwBoardType boardType;
/**< Board type name */
char boardName[MAX_CAMERA_DEVICE_NAME];
/**< Camera configurations */
CameraSensorBoardType camera[MAX_NUM_CAMERA_INPUT_DEVS];
/**< I2C device configurations*/
CameraI2CDeviceType i2c[MAX_NUM_CAMERA_I2C_DEVS];
} CameraBoardType;
该结构体描述了连接到camera子系统的硬件配置信息,以及连接方式等。编译到动态库libais_config.so,通过接口GetCameraConfigInterface()返回ICameraConfig结构体给外部使用。
i2c设备跟平台芯片强相关,一般不需要我们修改。
/**
* This structure describes I2C Devices
*/
typedef struct {
//目前只支持 CAMERA_I2C_TYPE_CCI
CameraI2CType i2ctype; /**< CCI or I2C port */
//CCI Core ID
uint32 device_id; /**< CCI device number */
//master ID within a CCI Core
uint32 port_id; /**< I2C port number */
//device_id + port_id 能确定一个唯一的 CCI
CameraGPIOPinType sda_pin; /**< I2C SDA pin configuration */
CameraGPIOPinType scl_pin; /**< I2C SCL pin configuration */
char i2cDevname[MAX_I2C_DEVICE_NAME]; /**< i2c port used by sensor */
} CameraI2CDeviceType;
每个输入设备被描述为CameraSensorBoardType,包括解串器的信息,CSI、I2C、GPIO、中断的配置信息等。
/**
* This structure defines the board specific configurable items for a given
* camera sensor.
*/
typedef struct
{
/**< unique device id */
CameraDeviceIDType devId; //只要保证唯一即可,一般用定义好的枚举变量
/**< Driver config */
CameraDeviceConfigType devConfig;
/**< Driver loading info */
CameraDeviceDriverInfoType driverInfo; //需要加载的输入设备动态库信息
/**< CSI info */
CameraCsiInfo csiInfo; //CSI信息
/*GPIO config*/
CameraGPIOPinType gpioConfig[CAMERA_GPIO_MAX]; //GPIO信息,从代码上看没用,应该是被具体解串器的配置信息里取代了
/*Interrupt config*/
CameraSensorGPIO_IntrPinType intr[MAX_CAMERA_DEV_INTR_PINS]; //中断配置信息,从代码看只能配置一个GPIO为中断
/**< I2C port */
CameraI2CPortType i2cPort; //I2C信息,其中port_id指定CCI master的索引(CAMERA_I2C_TYPE_CCI),但真正决定cci master的是dtsi里的 cci-master = <1>
} CameraSensorBoardType;
/**
* This structure describes I2C port used by the camera
*/
typedef struct {
CameraI2CType i2ctype; /**< CCI or I2C port */
//device_id + port_id 会匹配到上面i2c信息里的 CCI ID
uint32 device_id; /**< CCI device number */
uint32 port_id; /**< I2C port number */
I2CSpeedType speed; /**< I2C port speed */
} CameraI2CPortType;
CameraDeviceConfigType配置 input device 信息。
/**
* This structure defines input device configuration
*/
typedef struct {
/**< sub device id in case of multiple devices sharing same driver */
uint32 subdevId; //取0-3,用以MAX9296区分输入端,一般设0
/* type = 0 will use lib default.Change type to 1 to apply config below */
uint32 type; //最终在MAX9296A的max9296a_set_default函数生效,设置位0,则用9296的default配置,设置为1,则用下面的配置
uint32 numSensors;
uint32 opMode;
//下面三个没有使用
uint32 masterSocId; /**< Master soc which control deserializer in multi SOC enviroment */
uint32 socMap; /**< Soc map for de-serializer connection with soc */
uint32 accessMode; /**< I2C access for soc with the deserializer */
int32 gpio[MAX_NUM_INPUT_DEV_INTERNAL_GPIO];
CameraPowerSaveModeType powerSaveMode;
CameraDeviceSensorConfigType sensors[MAX_NUM_INPUT_DEV_SENSORS];
} CameraDeviceConfigType;
CameraCsiInfo配置了csi的信息,需要结合硬件电路图确定de-serializer的csi输出口。
CameraChannelInfoType定义了QCarCam IDs到streams的映射。
/**
* This structure defines the mapping of unique qcarcam descriptor to
* input sources as well as the default operation mode.
*/
typedef struct
{
/**
* unique qcarcam inputId that maps to <devId, srcId>
*/
uint32 aisInputId; //映射到inputSrc的唯一ID,HAL层使用,如qcarcam_config_singleid.xml里配置的input_id
/**
* unique <devId, srcId> of input id and default operation mode
*/
struct
{
uint32 devId; //匹配CameraSensorBoardType里的devId
uint32 srcId; //匹配sensor的subchannels信息里的src_id,最终匹配到modes
}inputSrc[MAX_CHANNEL_INPUT_SRCS];
qcarcam_opmode_type opMode; //选择对应的Pproc进行post process
} CameraChannelInfoType;
qcarcam_opmode_type 可以选择具体的 Pproc。
typedef enum {
QCARCAM_OPMODE_RAW_DUMP,
QCARCAM_OPMODE_SHDR,
QCARCAM_OPMODE_INJECT,
QCARCAM_OPMODE_PAIRED_INPUT,
QCARCAM_OPMODE_DEINTERLACE,
QCARCAM_OPMODE_MAX
} qcarcam_opmode_type;
//对应的ProcChain
/**
* Proc Chain Definitions
*/
static AisProcChainDefType* g_ProcChainDefs[QCARCAM_OPMODE_MAX] =
{
[QCARCAM_OPMODE_RAW_DUMP] = &RawdumpProcChainDef,
[QCARCAM_OPMODE_SHDR] = &SHDR_ProcChainDef,
[QCARCAM_OPMODE_INJECT] = &InjectProcChainDef,
[QCARCAM_OPMODE_PAIRED_INPUT] = &PairedInput_ProcChainDef,
[QCARCAM_OPMODE_DEINTERLACE] = &DeinterlaceProcChainDef,
};
//默认是 QCARCAM_OPMODE_RAW_DUMP
/**
* RAW Dump usecase
*/
static AisBuflistDefType RawdumpBuflist[] =
{
{
.id = AIS_BUFLIST_OUTPUT_USR,
.GetBuf = GetBufferDefault,
.PutBuf = BuflistEnqIfe,
.AllocBuf = NULL,
.allocParams = {
.allocType = AIS_BUFLIST_ALLOC_NONE,
.maxBuffers = QCARCAM_MAX_NUM_BUFFERS,
.matchBuflistId = 0,
.fmt = (qcarcam_color_fmt_t)0, .width = 0, .height = 0, .stride = 0
}
},
};
// 只有一个 pProcChain
static AisProcChainType RawdumpPProc[] =
{
{ AIS_PPROC_USR_DONE, NULL, 0, {AIS_BUFLIST_OUTPUT_USR}, {} },
};
static AisProcChainDefType RawdumpProcChainDef =
{
.pProcChain = RawdumpPProc,
.nProc = STD_ARRAY_SIZE(RawdumpPProc),
.pBufferlistDef = RawdumpBuflist,
.nBuflist = STD_ARRAY_SIZE(RawdumpBuflist)
};
CameraResult AisProcChainManagerPrivate::Init(void)
{
CameraResult rc = CAMERA_SUCCESS;
uint32 i;
//PProcId 对应到具体的实现类
m_pPProc[AIS_PPROC_USR_DONE] = AisPProcUsrDone::Create();
m_pPProc[AIS_PPROC_ISP] = AisPProcIsp::Create();
m_pPProc[AIS_PPROC_GPU] = AisPProcGpu::Create();
m_pPProc[AIS_PPROC_FRAMESYNC] = AisPprocFrameSync::Create();
for (i = 0; i < AIS_PPROC_MAX; i++)
{
if (!m_pPProc[i])
{
AIS_LOG(PPROC_MGR, FATAL, "Failed to create PPROC %d", i);
rc = CAMERA_EFAILED;
break;
}
}
return rc;
}
其中aisInputId是给native层使用的,和framework传下来的cameraId的对应关系在文件/vendor/bin/input_mapping_id.xml中。
<input_mapping_info>
<!-- input_id:framework传递的cameraId,mapping_id:HAL层的aisInputId -->
<input_device>
<properties input_id="0" mapping_id="0" />
</input_device>
<input_device>
<properties input_id="1" mapping_id="1" />
</input_device>
<input_device>
<properties input_id="2" mapping_id="4" />
</input_device>
<input_device>
<properties input_id="3" mapping_id="3" />
</input_device>
<input_device>
<properties input_id="4" mapping_id="4" />
</input_device>
</input_mapping_info>
然后是具体解串器的配置信息,如MAX9296A。最重要的是结构体sensor_lib_t。
typedef struct
{
/* private context - must be first element */
void* priv_ctxt;
/* close lib function ptr */
int (*sensor_close_lib)(void* ctxt);
/* channels */
img_src_channel_t channels[MAX_IMAGE_SOURCES]; //channels的输出格式,如图像格式,长宽,帧率
unsigned int num_channels;
/* subchannels */
img_src_subchannel_t subchannels[MAX_IMAGE_SOURCES];
unsigned int num_subchannels;
/* sensor slave info */
struct camera_sensor_slave_info sensor_slave_info;
/* 重要
struct camera_sensor_slave_info {
char sensor_name[32]; //sensor name,framework用来打印log
enum sensor_camera_id camera_id; //对应到CameraSensorBoardType的索引
unsigned short slave_addr; //I2C从设备地址
enum camera_i2c_freq_mode i2c_freq_mode;
enum camera_i2c_reg_addr_type addr_type; //I2C地址字节数
/* 具体由厂商给出的地址格式决定,如地址是0x0313,则为addr_type=CAMERA_I2C_WORD_ADDR,若是0x12,则为CAMERA_I2C_BYTE_ADDR
enum camera_i2c_reg_addr_type {
CAMERA_I2C_BYTE_ADDR = 1,
CAMERA_I2C_WORD_ADDR,
CAMERA_I2C_3B_ADDR,
CAMERA_I2C_ADDR_TYPE_MAX
};
*/
enum camera_i2c_data_type data_type; //跟addr_type类似,I2C数据的字节数
struct sensor_id_info_t sensor_id_info;
struct camera_power_setting_array power_setting_array; //上电和下电时序
unsigned char is_init_params_valid;
};
*/
/* sensor output settings */
sensor_output_t sensor_output; //unused
/* sensor output register address */
struct sensor_output_reg_addr_t output_reg_addr;
/* sensor exposure gain register address */
struct sensor_exp_gain_info_t exp_gain_info;
/* sensor aec info */
sensor_aec_data_t aec_info;
/* number of frames to skip after start stream info */
unsigned short sensor_num_frame_skip; //unused
/* number of frames to skip after start HDR stream info */
unsigned short sensor_num_HDR_frame_skip; //unused
/* sensor pipeline delay */
unsigned int sensor_max_pipeline_frame_delay;
/* sensor lens info */
sensor_property_t sensor_property;
/* imaging pixel array size info */
sensor_imaging_pixel_array_size pixel_array_size_info;
/* Sensor color level information */
sensor_color_level_info color_level_info;
/* sensor port info that consists of cid mask and fourcc mapaping */
sensor_stream_info_array_t sensor_stream_info_array;
/* Sensor Settings */
struct camera_i2c_reg_setting start_settings;
struct camera_i2c_reg_setting stop_settings;
struct camera_i2c_reg_setting groupon_settings;
struct camera_i2c_reg_setting groupoff_settings;
struct camera_i2c_reg_setting embedded_data_enable_settings;
struct camera_i2c_reg_setting embedded_data_disable_settings;
struct camera_i2c_reg_setting aec_enable_settings;
struct camera_i2c_reg_setting aec_disable_settings;
/* sensor test pattern info */
sensor_test_info test_pattern_info;
/* sensor effects info */
struct sensor_effect_info effect_info;
/* Sensor Settings Array */
struct sensor_lib_reg_settings_array init_settings_array;
struct sensor_lib_reg_settings_array res_settings_array;
struct sensor_lib_out_info_array out_info_array;
struct sensor_csi_params csi_params;
struct sensor_csid_lut_params_array csid_lut_params_array;
struct sensor_lib_crop_params_array crop_params_array;
/* Exposure Info */
sensor_exposure_table_t exposure_func_table;
/* video_hdr mode info*/
struct sensor_lib_meta_data_info_array meta_data_out_info_array;
/* sensor_capability */
/* bit mask setting for sensor capability */
/* bit positions are provided in sensor_capability_t */
unsigned int sensor_capability;
/* sensor_awb_table_t */
sensor_awb_table_t awb_func_table;
/* Parse RDI stats callback function */
sensor_RDI_parser_stats_t parse_RDI_stats;
/* full size info */
sensor_rolloff_config rolloff_config;
/* analog-digital conversion time */
long long adc_readout_time;
/* number of frames to skip for fast AEC use case */
unsigned short sensor_num_fast_aec_frame_skip;
/* add soft delay for sensor settings like exposure, gain ...*/
unsigned char app_delay[SENSOR_DELAY_MAX];
/* for noise profile calculation
Tuning team must update with proper values. */
struct sensor_noise_coefficient_t noise_coeff;
/* Flag to be set if any external library are to be loaded */
unsigned char external_library;
/* custom func table ptr */
sensor_custom_func_table_t sensor_custom_func; //客制化API
boolean use_sensor_custom_func; //是否使用客制化接口
uint32 src_id_enable_mask;
uint32 input_id;
} sensor_lib_t;
ais调用sensor接口的时序图如下。
AIS使用了大量IOCTL和V4L2结合的实例,来完成ais_server和kernel的通信。以解串器的中断诊断为例。
/* ---------------------------------------------------------------------------
* FUNCTION - sensor_setup_gpio_interrupt -
*
* DESCRIPTION: Setup gpio id as interrupt
* ------------------------------------------------------------------------ */
CameraResult SensorPlatformLinux::SensorSetupGpioInterrupt(enum camera_gpio_type gpio_id,
sensor_intr_callback_t cb, void *data)
{
//1.获取中断配置信息
//....
//2.以GPIO号为id订阅V4L2消息
struct v4l2_event_subscription sub = {};
sub.type = AIS_SENSOR_EVENT_TYPE;
sub.id = m_probeCmd->gpio_intr_config[freeIdx].gpio_num;
rc = ioctl(m_sensorFd, VIDIOC_SUBSCRIBE_EVENT, &sub);
if(rc < 0)
{
AIS_LOG(SENSOR_PLATFORM, ERROR, "[%s] VIDIOC_SUBSCRIBE_EVENT [0x%x] failed %d",
m_pSensorLib->sensor_slave_info.sensor_name,
AIS_SENSOR_EVENT_TYPE, rc);
result = CAMERA_EFAILED;
}
//3.通过IOCTL初始化中断信息
if (CAMERA_SUCCESS == result)
{
cam_cmd.op_code = AIS_SENSOR_INTR_INIT;
cam_cmd.size = sizeof(struct ais_sensor_gpio_intr_config);
cam_cmd.handle_type = CAM_HANDLE_USER_POINTER;
cam_cmd.reserved = 0;
cam_cmd.handle = (uint64_t)m_probeCmd->gpio_intr_config;
rc = ioctl(m_sensorFd, VIDIOC_CAM_CONTROL, &cam_cmd, "intr_init");
if (rc < 0)
{
result = CAMERA_EFAILED;
}
}
//4.新建线程监听V4L2的消息并获取信息
if (CAMERA_SUCCESS == result)
{
pPlatformIntr->pCtxt = this;
pPlatformIntr->isUsed = TRUE;
pPlatformIntr->pCbFcn = cb;
pPlatformIntr->pData = data;
pPlatformIntr->gpio_id = gpio_id;
pPlatformIntr->gpio_num = pinInfo.pin_id;
snprintf(pPlatformIntr->tname, sizeof(pPlatformIntr->tname),
"sensor_platform_intr_poll_thread_%d", m_eCameraInterface);
result = CameraCreateThread(CAMERA_THREAD_PRIO_DEFAULT, 0,
SensorPlatformIntrPollThread,
(void*)(pPlatformIntr),
0,
pPlatformIntr->tname,
&pPlatformIntr->hThread);
(void)CameraSetThreadPriority(pPlatformIntr->hThread,
CAMERA_THREAD_PRIO_HIGH_REALTIME);
if (result != CAMERA_SUCCESS)
{
pPlatformIntr->isUsed = FALSE;
//@todo: any cleanup...
}
}
}
//kernel层对应的代码
int32_t cam_sensor_driver_cmd(struct cam_sensor_ctrl_t *s_ctrl,
void *arg)
{
switch (cmd->op_code):
case AIS_SENSOR_INTR_INIT: {
rc = cam_sensor_init_gpio_intr(
gpio_intr_cfg,
s_ctrl);
}
}
}
static int32_t cam_sensor_init_gpio_intr(
struct ais_sensor_gpio_intr_config *gpio_intr_info,
struct cam_sensor_ctrl_t *s_ctrl)
{
int32_t rc = 0;
int32_t gpio_num = 0;
int32_t gpio_cfg0 = 0;
int32_t idx = 0;
//kernel层支持多个INTR的注册,但HAL层获取INTR的代码只允许注册一个
for (idx = 0; idx < AIS_MAX_INTR_GPIO; idx++) {
if (!s_ctrl->s_intr[idx].work_inited &&
gpio_intr_info->gpio_num != -1) {
gpio_num = gpio_intr_info->gpio_num;
gpio_cfg0 = gpio_intr_info->gpio_cfg0;
s_ctrl->s_intr[idx].sctrl = s_ctrl;
s_ctrl->s_intr[idx].gpio_array[0].gpio = gpio_num;
//初始化工作队列
INIT_WORK(&s_ctrl->s_intr[idx].irq_work,
bridge_irq_work);
//申请一个GPIO口
rc = gpio_request_one(gpio_num,
GPIOF_DIR_IN, "camera_intr");
if (!rc) {
//用该GPIO口申请硬件中断,中断响应函数为bridge_irq
rc = request_irq(gpio_to_irq(gpio_num),
bridge_irq,
IRQF_ONESHOT | gpio_cfg0,
"qcom,ais",
&s_ctrl->s_intr[idx]);
if (rc < 0)
CAM_ERR(CAM_SENSOR,
"gpio %d request irq failed", gpio_num);
} else {
gpio_free(gpio_num);
CAM_ERR(CAM_SENSOR,
"gpio %d request failed", gpio_num);
}
if (!rc)
s_ctrl->s_intr[idx].work_inited = 1;
break;
}
}
return rc;
}
//在任务队列里生成一个id为GPIO口的V4L2事件并入队,因为native层订阅过该id的信息,所以native能触发该消息的处理函数
static void bridge_irq_work(struct work_struct *work)
{
struct cam_sensor_ctrl_t *s_ctrl;
struct cam_sensor_intr_t *s_intr;
struct v4l2_event event;
s_intr = container_of(work, struct cam_sensor_intr_t,
irq_work);
s_ctrl = s_intr->sctrl;
mutex_lock(&s_ctrl->cam_sensor_mutex);
/* Queue the event */
memset(&event, 0, sizeof(struct v4l2_event));
event.id = s_intr->gpio_array[0].gpio;
event.type = AIS_SENSOR_EVENT_TYPE;
v4l2_event_queue(s_ctrl->v4l2_dev_str.sd.devnode, &event);
mutex_unlock(&s_ctrl->cam_sensor_mutex);
}
//在中断响应函数里触发工作任务
static irqreturn_t bridge_irq(int irq_num, void *dev)
{
struct cam_sensor_intr_t *s_intr = dev;
//将任务提交到工作队列
schedule_work(&s_intr->irq_work);
return IRQ_HANDLED;
}
4.4 MAX9296A
有些项目使用的是美信的MAX9296A解串器,该解串器有GMSL1和GMSL2两种工作模式。默认工作模式由硬件电路决定:
MAX9296A 有两个输入 LINKA/B 和两个输出 Port A/B,内部有4个 Pipeline。同个 Port 口可以支持多路输出,通过 VC 来区分。
以下是当前GMSL2模式下默认数据流,配置0x51 -> 0x00,表示 streamId 为0的数据流会 remap 到 Pipeline Y 上,在 GMSL2 模式下,Pipeline 和 streamId 绑定,streamId 由出入信号的串行器设置,此时跟输入流接 LINKA 或 B 没有关系,但是硬件上输入流具体接入的是 LINKA 还是 LINKB,需要配置寄存器 0x0010 来选择。具体拓扑图就不贴上来了,需要可以自行联系美信对接人。
5. 调试
这个章节描述如何进行debug。
5.1 ais debug
ais的log由log等级控制,默认输出等级是
/**
* ais default configuration
*/
#if defined(__INTEGRITY)
#define AIS_LOG_DEFAULT_CONF AIS_LOG_CONF_MAKE(AIS_LOG_MODE_CONSOLE, AIS_LOG_LVL_WARN)
#elif defined(CAMERA_UNITTEST)
#define AIS_LOG_DEFAULT_CONF AIS_LOG_CONF_MAKE(AIS_LOG_MODE_CONSOLE, AIS_LOG_LVL_WARN)
#else
#define AIS_LOG_DEFAULT_CONF AIS_LOG_CONF_MAKE(AIS_LOG_MODE_OS, AIS_LOG_LVL_HIGH)
#endif
可以通过文件来修改输出等级。
//ais_server的Log配置文件: vendor/etc/camera/ais_log_ais_server.conf
//ais_client的Log配置文件: vendor/etc/camera/ais_log.conf
#
# AIS Log Configurations
#
# Copyright (c) 2018-2020 Qualcomm Technologies, Inc.
# All Rights Reserved.
# Confidential and Proprietary - Qualcomm Technologies, Inc.
#
#memory log size
#MEM_SIZE=1048576
#os log size
#OS_SIZE=1048576
#log file
#FILE_PATH=/tmp/ais_log.txt
#FILE_SIZE=104857600
#
# Module-Specified Log Configuration
# Log id:
##define AIS_MOD_ID_RSRV 0 //reserved
#define AIS_MOD_ID_CAMERA_PLATFORM 1
#define AIS_MOD_ID_CAMERAQ 2
# ...
#
# Log Level: 1 == AIS_LOG_LVL_FATAL
# 2 == AIS_LOG_LVL_CRIT
# 3 == AIS_LOG_LVL_ERR
# 4 == AIS_LOG_LVL_WARN
# 5 == AIS_LOG_LVL_HIGH
# 6 == AIS_LOG_LVL_MED
# 7 == AIS_LOG_LVL_LOW
# 8 == AIS_LOG_LVL_DBG
# 9 == AIS_LOG_LVL_DBG1
#
# Log Mode: M == log to memory
# S == log to OS logger
# F == log to file
# C == log to console
#
# Example:
# MODULE_xx=yMSFC
# xx means module ID, 0 is reserved, the maximum number is 255
# y means log level
# MSFC means combination of log modes, which means one message can be output to more than one destinations
#
#
# Conf file path in target
# Android: /vendor/etc/camera
GLOBAL=8S #global log level set to DBG and log to OS logger
MODULE_2=8SF #module CAMERAQ set log level to DBG and log to both os logger and file
5.2 Linux
5.2.1 Kernel log
Camera Kernel drivers模块的Log可通过文件节点设置。
echo 0x20 > /sys/module/cam_debug_util/parameters/debug_mdl #enable CAM_SENSOR debug log
#define CAM_CDM (1 << 0)
#define CAM_CORE (1 << 1)
#define CAM_CPAS (1 << 2)
#define CAM_ISP (1 << 3)
#define CAM_CRM (1 << 4)
#define CAM_SENSOR (1 << 5)
#define CAM_SMMU (1 << 6)
#define CAM_SYNC (1 << 7)
#define CAM_ICP (1 << 8)
#define CAM_JPEG (1 << 9)
#define CAM_FD (1 << 10)
#define CAM_LRME (1 << 11)
#define CAM_FLASH (1 << 12)
#define CAM_ACTUATOR (1 << 13)
#define CAM_CCI (1 << 14)
#define CAM_CSIPHY (1 << 15)
#define CAM_EEPROM (1 << 16)
#define CAM_UTIL (1 << 17)
#define CAM_HFI (1 << 18)
#define CAM_CTXT (1 << 19)
#define CAM_OIS (1 << 20)
#define CAM_RES (1 << 21)
#define CAM_MEM (1 << 22)
5.2.2 I2C tool
I2C tool是集成到toolbox里的,操作I2C设备的协助工具。
# 820项目,使用的是I2C类型
# 总线地址是由硬件配置决定的
i2cget -f -y 8 0x6c 0x30 # 读取总线地址是8,从地址是0x6c设备的0x30寄存器
5.2.3 ccidbg
ccidbg是用于读写CCI设备的工具,只有使用CCI的设备才能使用。
# 8155项目,使用的是CCI类型 i2ctype = CAMERA_I2C_TYPE_CCI,
# 总线地址是由硬件配置决定的
ccidbg -master=1 # master id 可以在dtsi的配置里确定 cci-master = <1>;
# CCI DBGR v1.1
# cci power up [ok]
# MAIN MENU
# [ 0 ] - cci_read
# [ 1 ] - cci_write
# [ 2 ] - cci_write_sync_array
# [ 3 ] - cci_update
# [ 4 ] - exit
3
# <slave addr>, <addr_type>, <data type>
0x90 2 1 #根据设备实际 addr_type 和 data_type 来设置
# MAIN MENU
# [ 0 ] - cci_read
# [ 1 ] - cci_write
# [ 2 ] - cci_write_sync_array
# [ 3 ] - cci_update
# [ 4 ] - exit
0
# <addr>
0x100
5.2.4 qcarcam_test
camera.provider里包含了ais_client的实现,而qcarcam_test也包含了ais_client的实现,所以qcarcam_test能直接跟ais_server通信实现native层的camera功能,通常该可执行文件用来测试ais_server和摄像头是否正常,因为它没有经过定制化的图像数据处理(裁剪、缩放等)。
qcarcam_test使用ais_server的标准流程如下。
qcarcam_test -config=/vendor/bin/qcarcam_config_singleid3.xml
xml配置文件的内容要跟项目匹配。
<!--
Copyright (c) 2017 Qualcomm Technologies, Inc.
All Rights Reserved.
Confidential and Proprietary - Qualcomm Technologies, Inc.
-->
<qcarcam_inputs>
<input_device>
<!-- CameraBoardType设定的devId匹配到CameraChannelInfoType里的aisInputId,写入input_id -->
<properties input_id="0" />
<!-- window_x 是camera图像显示到大屏的位置信息 -->
<display_setting display_id="0" nbufs="3"
window_width="0.5" window_height="0.5" window_pos_x="0" window_pos_y="0"
zorder="1"/>
<!-- 匹配项目实际的尺寸 -->
<output_setting width="1280" height="960" nbufs="5" />
</input_device>
</qcarcam_inputs>
5.3 代码调试
这里推荐使用 VS code来进行代码级别的在线调试。在线调试对于跟踪代码流程、debug问题有很大的帮助。
下面的在window PC上的设置步骤,在Ubuntu上会简单很多。
# 1. VS code 安装 C/C++ extension 插件;
# 2. 下载最新NDK,将NDK中的gdbserver push到手机
adb push .\android-arm64\gdbserver\gdbserver /data/local/
adb shell chmod 777 /data/local/gdbserver
# 3. VS code把ais的目录添加到工作区
# 4. 配置调试信息
# 4.1 运行 --> 添加配置
# 4.2 选择 C++(GDB/LLDB)
# 4.3 修改launch.json的配置信息
# 5. 用gdb启动设备端程序
# 5.1 把本地编译的ais_server和相关lib库push到设备上
# 5.2 ps -e |grep ais 查询获得ais_server的进行PID
# 5.3 gdb attach到ais_server
./data/local/gdbserver --remote-debug :5555 --attach $pid
# 5.3.1 如果不是常驻进程,需要gdb来启动的进程,如一个test可执行文件,则命令是
./data/local/gdbserver --remote-debug :5555 test [arg]
# 6. window端建立端口转发。PC和设备的socket可通过5555的端口相互转发,建立设备和gdb的联系
adb forward tcp:5555 tcp:5555
# 7. VS code 启动调试(F5),就可以在线调试了
其中,launch.json的配置信息参考如下。
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) 启动",
"type": "cppdbg",
"request": "launch",
//带符号的目标调试程序
"program": "Z:\\android\\out\\target\\product\\au8155\\symbols\\vendor\\bin\\ais_server",
"args": [],
"stopAtEntry": false,
//其他的动态库路径
"additionalSOLibSearchPath": "Z:\\android\\out\\target\\product\\au8155\\symbols\\vendor\\lib64",
//本地主机的socket地址,注意需要用adb forward 把设备的地址链接到本地主机上
"miDebuggerServerAddress": "localhost:5555",
//源码路径
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
//gdb路径
"miDebuggerPath": "D:\\Program Files\\android-ndk-r23b\\prebuilt\\windows-x86_64\\bin\\gdb.exe",
//gdb参数
"setupCommands": [
{
"description": "为 gdb 启用整齐打印",
"text": "-enable-pretty-printing",
"ignoreFailures": true
},
{
"text": "-environment-directory Z:\\android",
"description": "gdb command: dir",
"ignoreFailures": false
},
{
"text": "-gdb-set solib-absolute-prefix Z:\\android\\out\\target\\product\\au8155\\symbols",
"description": "gdb command: set solib-absolute-prefix",
"ignoreFailures": false
},
{
"text": "-gdb-set solib-search-path Z:\\android\\out\\target\\product\\au8155\\symbols\\vendor\\lib64;Z:\\android\\out\\target\\product\\au8155\\symbols\\vendor\\lib;",
"description": "gdb command: set solib-search-path",
"ignoreFailures": false
},
{
"description": "将反汇编风格设置为 Intel",
"text": "-gdb-set disassembly-flavor intel",
"ignoreFailures": true
},
]
}
]
}
需要特别注意的是,调试camera.provider的时候,会遇到PIPE信号导致gdb无法调试的问题,该问题是因为ais health thread心跳监测导致的,可以通过宏
#define AIS_DISABLE_HEALTH
来关闭心跳机制,或者在config配置项里配置忽略SIGPIPE信号。
setupCommands
{ "text": "handle SIGPIPE nostop noprint pass",
"description": "ignore SIGPIPE",
"ignoreFailures": true
},
5.4 编译调试
修改源码后,要编译模块,可以选择mmm命令或者make单编模块,熟悉编译流程的同学都知道这种编译命令会先收集遍历makefile,重新生成Ninja配置文件,然后再执行具体模块的编译,遇到服务器有其他编译任务的时候,单编也会消耗很长的时间(短则几分钟,长则几十分钟)。
其实在满足某些条件下,我们是可以跳过前面的步骤,直接用Ninja编译。
# xx是具体lunch项
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xx.ninja ais_server
需要满足条件:
- 当前out目录已经生成完整的Ninja配置文件,执行过全编或者用make进行过单编的情况下,就会生成完整的Ninja配置文件;
- 当前改动没有涉及到编译规则,即没有改动Android.mk和Android.bp以及相关的makefile。通俗的说只是C/C++/JAVA源码的改动,就满足该条件。
5.4 编译调试
修改源码后,要编译模块,可以选择mmm命令或者make单编模块,熟悉编译流程的同学都知道这种编译命令会先收集遍历makefile,重新生成Ninja配置文件,然后再执行具体模块的编译,遇到服务器有其他编译任务的时候,单编也会消耗很长的时间(短则几分钟,长则几十分钟)。
其实在满足某些条件下,我们是可以跳过前面的步骤,直接用Ninja编译。
# xx是具体lunch项
./prebuilts/build-tools/linux-x86/bin/ninja -f out/combined-xx.ninja ais_server
需要满足条件:
- 当前out目录已经生成完整的Ninja配置文件,执行过全编或者用make进行过单编的情况下,就会生成完整的Ninja配置文件;
- 当前改动没有涉及到编译规则,即没有改动Android.mk和Android.bp以及相关的makefile。通俗的说只是C/C++/JAVA源码的改动,就满足该条件。