Android項(xiàng)目總結(jié)5

個(gè)人主頁:https://chengang.plus/

文章將會(huì)同步到個(gè)人微信公眾號(hào):Android部落格

1富岳、項(xiàng)目需求

項(xiàng)目整體的需求是Android盒子支持上下左右控制云臺(tái)攝像頭抢韭,還要能相對(duì)和絕對(duì)控制攝像頭的位置。相對(duì)控制堕仔,意思就是按著左方向鍵不放英融,攝像頭一直往左邊轉(zhuǎn)趴俘,到最大值為止,反之亦然掌眠;絕對(duì)控制蕾盯,意思是每次按一下方向鍵,就轉(zhuǎn)一個(gè)角度就停下來蓝丙。

2级遭、需求實(shí)現(xiàn)

最終選擇通過定制Android kernel層的uvc代碼望拖,編譯kernel,打包固件挫鸽,刷機(jī)说敏,編寫上層App,從上到下打通控制流程丢郊。

3盔沫、靈感來源

驗(yàn)證Android盒子是否支持控制云臺(tái)攝像頭,只需要將攝像頭連接到ubuntu虛擬機(jī)枫匾,通過ubuntu上面的工具即可以控制攝像頭旋轉(zhuǎn)架诞,也就可以通過改造Android的kernel支持對(duì)應(yīng)的功能。

之前的文章里面有提到過干茉,使用uvcdynctrl工具谴忧,輸入對(duì)應(yīng)的指令就行,這里看看他的源碼是怎么實(shí)現(xiàn)的:

https://github.com/llmike/v4l2-tools/blob/master/libwebcam-src-0.2.4/libwebcam/libwebcam.c

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的解釋詳見如下鏈接角虫,一個(gè)用于設(shè)置參數(shù)沾谓,一個(gè)用于獲取參數(shù)。

https://www.linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/spec-single/v4l2.html#vidioc-g-ctrl

v4l2_control是兩個(gè)操作中間的媒介上遥,新的數(shù)據(jù)可以通過VIDIOC_S_CTRL傳遞這個(gè)結(jié)構(gòu)體搏屑;當(dāng)設(shè)置id之后,通過VIDIOC_G_CTRL粉楚,可以返回需要的數(shù)據(jù)辣恋。

這個(gè)id填什么參數(shù)呢?是struct uvc_control_mapping uvc_ctrl_mappings中對(duì)應(yīng)的id模软,這個(gè)id就是具體攝像頭支持的參數(shù)id伟骨,在設(shè)置之前先要查詢攝像頭支持的參數(shù),只有它支持之后才能設(shè)置燃异。

3.1 上層JNI代碼編寫

獲取攝像頭支持的控制參數(shù):

jboolean queryControls(){
    jint canControl = 0;
    __android_log_print(ANDROID_LOG_ERROR , TAG , "設(shè)備號(hào)=%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 , "找到的控制函數(shù)是%s" , qctrl.name);
        __android_log_print(ANDROID_LOG_ERROR , TAG , "繼續(xù)查找");
        __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控制的話携狭,應(yīng)該會(huì)有Pan,Tilt,Zoom字符串,變量自加三次
    return canControl == 3;
}

獲取某個(gè)id當(dāng)前對(duì)應(yīng)的值:

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;
}

設(shè)置某個(gè)參數(shù)的值回俐,也就是開始控制攝像頭左右上下轉(zhuǎn)動(dòng)了:

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對(duì)應(yīng)的value值應(yīng)該按照協(xié)議文檔中對(duì)值的定義來傳逛腿,比如左右絕對(duì)控制的值對(duì)應(yīng)的是轉(zhuǎn)動(dòng)的角度;左右相對(duì)控制分為四位仅颇,每一個(gè)位表示不同的控制方式单默,需要按照不同的id傳遞不同的值。

v4l2_ext_controls可以在一個(gè)id下同時(shí)要控制多個(gè)參數(shù)忘瓦,具體詳見:
https://www.linuxtv.org/downloads/legacy/video4linux/API/V4L2_API/spec-single/v4l2.html#v4l2-ext-control搁廓。

4、底層定制

上邊的代碼寫好了之后,可以先選取某個(gè)非控制左右上下轉(zhuǎn)動(dòng)的id試一下境蜕,看看能否正確控制蝙场,然后再去調(diào)試pan,tilt功能粱年。

一般情況下要和攝像頭廠商配合聯(lián)調(diào)售滤,因?yàn)閿z像頭廠商的固件也要適配UVC協(xié)議。其中UVC協(xié)議版本是個(gè)大問題逼泣,在Android kernel中查看UVC版本的地方在:

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

#define DRIVER_VERSION      "1.1.1"

如果kernel中UVC版本與攝像頭固件UVC版本不一致趴泌,會(huì)導(dǎo)致控制位不匹配,導(dǎo)致控制返回失敗拉庶。

到這里可以知道攝像頭的固件版本嗜憔,支持的控制參數(shù),從而可以知道盒子Android底層kernel的定制方向了氏仗。當(dāng)前項(xiàng)目的定制方向是添加pan和tilt的相對(duì)控制能力吉捶。定制流程如下:

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

在這個(gè)文件中相對(duì)控制的速度:

#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

文件中添加相對(duì)控制速度的描述:

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

這個(gè)文件是核心的控制文件,里面包含了設(shè)置和獲取的方法皆尔,最終都到這個(gè)文件中實(shí)現(xiàn)呐舔,在這里我們需要添加相對(duì)控制的方法:

#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);
}

在映射集合里面添加相對(duì)控制參數(shù),還要添加控制和獲取速度的方法慷蠕。

到這里上層編寫和底層定制基本完成珊拼。

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市流炕,隨后出現(xiàn)的幾起案子澎现,更是在濱河造成了極大的恐慌,老刑警劉巖每辟,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剑辫,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渠欺,警方通過查閱死者的電腦和手機(jī)妹蔽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挠将,“玉大人胳岂,你說我怎么就攤上這事√蛳。” “怎么了旦万?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長镶蹋。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么贺归? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任淆两,我火速辦了婚禮,結(jié)果婚禮上拂酣,老公的妹妹穿的比我還像新娘秋冰。我一直安慰自己,他們只是感情好婶熬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布剑勾。 她就那樣靜靜地躺著,像睡著了一般赵颅。 火紅的嫁衣襯著肌膚如雪虽另。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天饺谬,我揣著相機(jī)與錄音捂刺,去河邊找鬼。 笑死募寨,一個(gè)胖子當(dāng)著我的面吹牛族展,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播拔鹰,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼仪缸,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了列肢?” 一聲冷哼從身側(cè)響起恰画,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎例书,沒想到半個(gè)月后锣尉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡决采,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年自沧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片树瞭。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拇厢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晒喷,到底是詐尸還是另有隱情孝偎,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布凉敲,位于F島的核電站衣盾,受9級(jí)特大地震影響寺旺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜势决,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一阻塑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧果复,春花似錦陈莽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迈窟,卻和暖如春私植,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菠隆。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國打工兵琳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骇径。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓躯肌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親破衔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子清女,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容