Android項目總結覆盤5

文章將會同步到我的微信公衆號:Android部落格html

一、項目需求

項目總體的需求是Android盒子支持上下左右控制雲臺攝像頭,還要能相對和絕對控制攝像頭的位置。相對控制,意思就是按着左方向鍵不放,攝像頭一直往左邊轉,到最大值爲止,反之亦然;絕對控制,意思是每次按一下方向鍵,就轉一個角度就停下來。linux

二、需求實現

最終選擇經過定製Android kernel層的uvc代碼,編譯kernel,打包固件,刷機,編寫上層App,從上到下打通控制流程。android

三、靈感來源

驗證Android盒子是否支持控制雲臺攝像頭,只須要將攝像頭鏈接到ubuntu虛擬機,經過ubuntu上面的工具便可以控制攝像頭旋轉,也就能夠經過改造Android的kernel支持對應的功能。git

以前的文章裏面有提到過,使用uvcdynctrl工具,輸入對應的指令就行,這裏看看他的源碼是怎麼實現的:github

github.com/llmike/v4l2…web

struct v4l2_control v4l2_ctrl = {
    .id		= control->v4l2_control,
    .value	= value->value
};
if(ioctl(v4l2_dev, VIDIOC_S_CTRL, &v4l2_ctrl)) {
    ret = C_V4L2_ERROR;
    set_last_error(hDevice, errno);
}

struct v4l2_control v4l2_ctrl = { .id = control->v4l2_control };
if(ioctl(v4l2_dev, VIDIOC_G_CTRL, &v4l2_ctrl)) {
	ret = C_V4L2_ERROR;
	set_last_error(hDevice, errno);
	goto done;
}
value->value	= v4l2_ctrl.value;
複製代碼

VIDIOC_S_CTRLVIDIOC_G_CTRL的解釋詳見以下連接,一個用於設置參數,一個用於獲取參數。ubuntu

www.linuxtv.org/downloads/l…api

v4l2_control是兩個操做中間的媒介,新的數據能夠經過VIDIOC_S_CTRL傳遞這個結構體;當設置id以後,經過VIDIOC_G_CTRL,能夠返回須要的數據。微信

這個id填什麼參數呢?是struct uvc_control_mapping uvc_ctrl_mappings中對應的id,這個id就是具體攝像頭支持的參數id,在設置以前先要查詢攝像頭支持的參數,只有它支持以後才能設置。markdown

3.1 上層JNI代碼編寫

獲取攝像頭支持的控制參數:

jboolean queryControls(){
	jint canControl = 0;
	__android_log_print(ANDROID_LOG_ERROR , TAG , "設備號=%d" , fd);

	struct v4l2_queryctrl qctrl;
	qctrl.id = V4L2_CTRL_CLASS_CAMERA | V4L2_CTRL_FLAG_NEXT_CTRL;
	int i = ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);
	while (0 == i){
		__android_log_print(ANDROID_LOG_ERROR , TAG , "開始查找");
		if (V4L2_CTRL_ID2CLASS(qctrl.id) != V4L2_CTRL_CLASS_CAMERA)
			continue;

		if(strcmp(qctrl.name , CONTROL_FLAG_PAN) == 0 || strcmp(qctrl.name , CONTROL_FLAG_TILT) == 0
							|| strcmp(qctrl.name , CONTROL_FLAG_ZOOM) == 0){
			++canControl;
		}

		__android_log_print(ANDROID_LOG_ERROR , TAG , "找到的控制函數是%s" , qctrl.name);
		__android_log_print(ANDROID_LOG_ERROR , TAG , "繼續查找");
		__android_log_print(ANDROID_LOG_ERROR , TAG , "id = %d" , qctrl.id);
		__android_log_print(ANDROID_LOG_ERROR , TAG , "Next_Ctrl = %x" , V4L2_CTRL_FLAG_NEXT_CTRL);
		__android_log_print(ANDROID_LOG_ERROR , TAG , "Camera_Class = %x" , V4L2_CTRL_CLASS_CAMERA);

		qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;

		__android_log_print(ANDROID_LOG_ERROR , TAG , "id+ = %x" , qctrl.id);

		i = ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);
		if(i != 0){
			__android_log_print(ANDROID_LOG_ERROR, TAG,"uvcioc ctrl add error: errno=%d (reason=%s)\n", errno,strerror(errno));
		}
	}
	//若是存在ptz控制的話,應該會有Pan,Tilt,Zoom字符串,變量自加三次
	return canControl == 3;
}
複製代碼

獲取某個id當前對應的值:

int getControlValue(int controlId){
	//an array of v4l2_ext_control
	struct v4l2_ext_control clist[1];
	struct v4l2_ext_controls ctrls;

	memset(&clist, 0, sizeof(clist));
	memset(&ctrls, 0, sizeof(ctrls));

	clist[0].id    = controlId;
	clist[0].value = 0;

	//v4l2_ext_controls with list of v4l2_ext_control
	ctrls.ctrl_class = V4L2_CTRL_CLASS_CAMERA;
	ctrls.count = 1;
	ctrls.controls = clist;

	//read back the value
	if (-1 == xioctl (fd, VIDIOC_G_EXT_CTRLS, &ctrls))
	{
		__android_log_print(ANDROID_LOG_ERROR,TAG,"get current value failed fd = %d,reason=%s" , fd,strerror(errno));
		return -1;
	}
	__android_log_print(ANDROID_LOG_ERROR,TAG,"get before value success , %d" , clist[0].value);
	return clist[0].value;
}
複製代碼

設置某個參數的值,也就是開始控制攝像頭左右上下轉動了:

int startControl(int controlId , int value){
    //an array of v4l2_ext_control
    struct v4l2_ext_control clist[1];
    struct v4l2_ext_controls ctrls;
    CLEAR(clist);
    CLEAR(ctrls);
    
    clist[0].id    = controlId;
    clist[0].value = value;
    
    //v4l2_ext_controls with list of v4l2_ext_control
    ctrls.ctrl_class = V4L2_CTRL_CLASS_CAMERA;
    ctrls.count = 1;
    ctrls.controls = clist;
    
    int result = xioctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls);
}
複製代碼

v4l2_ext_control對應的value值應該按照協議文檔中對值的定義來傳,好比左右絕對控制的值對應的是轉動的角度;左右相對控制分爲四位,每個位表示不一樣的控制方式,須要按照不一樣的id傳遞不一樣的值。

v4l2_ext_controls能夠在一個id下同時要控制多個參數,具體詳見: www.linuxtv.org/downloads/l…

四、底層定製

上邊的代碼寫好了以後,能夠先選取某個非控制左右上下轉動的id試一下,看看可否正確控制,而後再去調試pan,tilt功能。

通常狀況下要和攝像頭廠商配合聯調,由於攝像頭廠商的固件也要適配UVC協議。其中UVC協議版本是個大問題,在Android kernel中查看UVC版本的地方在:

goldfish\drivers\media\usb\uvc\uvcvideo.h

#define DRIVER_VERSION "1.1.1"
複製代碼

若是kernel中UVC版本與攝像頭固件UVC版本不一致,會致使控制位不匹配,致使控制返回失敗。

到這裏能夠知道攝像頭的固件版本,支持的控制參數,從而能夠知道盒子Android底層kernel的定製方向了。當前項目的定製方向是添加pan和tilt的相對控制能力。定製流程以下:

goldfish\include\uapi\linux\v4l2-controls.h

在這個文件中相對控制的速度:

#define V4L2_CID_PAN_SPEED (V4L2_CID_CAMERA_CLASS_BASE+32)
#define V4L2_CID_TILT_SPEED (V4L2_CID_CAMERA_CLASS_BASE+33)
複製代碼

goldfish\drivers\media\v4l2-core\v4l2-ctrls.c

文件中添加相對控制速度的描述:

const char *v4l2_ctrl_get_name(u32 id) {
    case V4L2_CID_PAN_SPEED: return "Pan, Speed";
    case V4L2_CID_TILT_SPEED: return "Tilt, Speed";
}
複製代碼

goldfish\drivers\media\usb\uvc\uvc_ctrl.c

這個文件是核心的控制文件,裏面包含了設置和獲取的方法,最終都到這個文件中實現,在這裏咱們須要添加相對控制的方法:

#define UVC_CTRL_RELATIVE_PAN 10094852
#define UVC_CTRL_RELATIVE_TILT 10094853
#define UVC_CTRL_RELATIVE_ZOOM 10094863

static struct uvc_control_info uvc_ctrls[] = {
    static struct uvc_control_mapping uvc_ctrl_mappings[] = {
    {
		 .id = V4L2_CID_PAN_RELATIVE,
		 .name = "Pan (Relative)",
		 .entity = UVC_GUID_UVC_CAMERA,
		 .selector = UVC_CT_PANTILT_RELATIVE_CONTROL,
		 .size = 16,
		 .offset = 0,
		 .v4l2_type = V4L2_CTRL_TYPE_INTEGER,
		 .data_type = UVC_CTRL_DATA_TYPE_SIGNED,
		 .get = uvc_ctrl_get_rel_speed,
		 .set = uvc_ctrl_set_rel_speed,
	 },
	 {
 		.id = V4L2_CID_TILT_RELATIVE,
	 	.name = "Tilt (Relative)",
	 	.entity = UVC_GUID_UVC_CAMERA,
		.selector = UVC_CT_PANTILT_RELATIVE_CONTROL,
		.size = 16,
		.offset = 16,
		.v4l2_type = V4L2_CTRL_TYPE_INTEGER,
		.data_type = UVC_CTRL_DATA_TYPE_SIGNED,
		.get = uvc_ctrl_get_rel_speed,
		.set = uvc_ctrl_set_rel_speed,
	 },
    }
}

static __s32 uvc_ctrl_get_rel_speed(struct uvc_control_mapping *mapping,
 __u8 query, const __u8 *data)
{
    int first = mapping->offset / 8;
    __s8 rel = (__s8)data[first];

    switch (query) {
     case UVC_GET_CUR:
     return (rel == 0) ? 0 : (rel > 0 ? data[first+1]
     : -data[first+1]);
     case UVC_GET_MIN:
     return -data[first+1];
     case UVC_GET_MAX:
     case UVC_GET_RES:
     case UVC_GET_DEF:
     default:
     return data[first+1];
    }
}

static void uvc_ctrl_set_rel_speed(struct uvc_control_mapping *mapping, __s32 value, __u8 *data) {
    int first = mapping->offset / 8;

    data[first] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
    data[first+1] = min_t(int, abs(value), 0xff);
}
複製代碼

在映射集合裏面添加相對控制參數,還要添加控制和獲取速度的方法。

到這裏上層編寫和底層定製基本完成。

相關文章
相關標籤/搜索