關(guān)于海思HI3559AV100及SDK的一些使用經(jīng)驗(yàn)

[TOC]

概述

我們絕對(duì)不會(huì)從0開(kāi)始粱侣,所以,如果你想從0開(kāi)始使用海思平----無(wú)論是做視頻/音頻采集還是做人工智能項(xiàng)目----肌稻,那得考慮一下

  • 你是否能得到海思的技術(shù)支持
  • 你是否熟練掌握各種規(guī)范
  • 你是否有足夠多的時(shí)間去閱讀手冊(cè)
  • 你是否足夠聰明去猜測(cè)海思開(kāi)發(fā)人員的思路

如果你的答案都是否维苔,那么還是放棄吧宵荒。因?yàn)槭褂煤K计脚_(tái)徘意,你需要從頭至尾了解所有的硬件苔悦,對(duì)于linux,你反倒不用太在乎椎咧;正所謂海思不在乎玖详。

海思從來(lái)不在乎小生意,F(xiàn)AE據(jù)說(shuō)都在大客戶(hù)那里勤讽,這種有錢(qián)就是爹的尿性正是太令人喜歡了蟋座。相反,nvidia跟你隔著N個(gè)時(shí)區(qū)地技,你在論壇上提的問(wèn)題都有人詳細(xì)跟蹤蜈七,你只需要注冊(cè)一個(gè)賬號(hào)就行了秒拔。

Video

如果是從各路IMX傳感器來(lái)莫矗,那么請(qǐng)使用海思提供的sample,如果是采集YUV視頻砂缩,那么請(qǐng)繼續(xù)往下讀作谚。
海思提供了不少的樣例,每一個(gè)樣例的友好度無(wú)限接近于0庵芭∶美粒看著海思自以為聰明的命名方式,自以為聰明的代碼排版双吆,自以為聰明的結(jié)構(gòu)設(shè)計(jì)眨唬,你無(wú)從下手。所以從現(xiàn)在開(kāi)始好乐,忘記你在使用linux匾竿,調(diào)整到面向海思SDK編程模式。

MIPI-Rx

如果你采用2-lane的方式蔚万,那么我勸你放棄岭妖。原因如下:

  • 不穩(wěn)定,總是莫名其妙的錯(cuò)誤,手動(dòng)調(diào)整DESCREW也沒(méi)用
  • 幾乎不可能靠自己的力量解決問(wèn)題昵慌,然而小公司不可能得到技術(shù)支持假夺,各路代理商屬于你問(wèn)100個(gè)問(wèn)題,他只回答一個(gè)的那種斋攀,而且他經(jīng)骋丫恚回答“我們也沒(méi)用過(guò)”、“我們也不知道”淳蔼、“你可以試試”悼尾、“沒(méi)有”。
  • 海思提供的寄存器不足以讓你找到問(wèn)題肖方,比如:MIPI(N)_PKT_INTR_ST闺魏、MIPI(N)_PKT_INTR2_STMIPI(N)_FRAME_INTR_ST會(huì)告訴你發(fā)生了錯(cuò)誤俯画,但你難以進(jìn)一步跟蹤析桥,比如此時(shí)你想捕獲一個(gè)包頭看看,一個(gè)包頭正好是4字節(jié)艰垂,海思沒(méi)有提供寄存器泡仗,所幸,可以讀PHY\_PH\_MIPI\_LINK(P)猜憎,因?yàn)槟阒挥?-lane娩怎,所以你只能遺憾的得到頭2個(gè)字節(jié),總之如果你的CSI不穩(wěn)定胰柑,你很難從硬件上找到問(wèn)題并解決截亦。而如果選用4-lane,那么你正好可以從PHY_PH_MIPI_LINK(P)讀出包頭柬讨。

為了調(diào)試2-lane崩瓤,我廢了很多工夫,海思提供的himm讀寫(xiě)不好用踩官,我這里提供一個(gè)還不錯(cuò)的却桶。

//
// (c) 2020 chiv
//
#include "utils/flags.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string>

qDefineFlag(uint32, phy, 0, "phy link, [0..3]");
qDefineFlag(uint32, chn, 0, "mipi rx chn, [0..7]");
qDefineFlag(bool, wr, true, "read");
qDefineFlag(bool, dump, true, "dump status register only");
qDefineFlag(uint32, addr, 0, "register offset");
qDefineFlag(uint32, value, 0, "if write mode was enabled, this is the value to write");
qDefineFlag(bool, relative, true, "use offset");

uint32_t regaddr(uint32_t pa_offset, uint32_t addr, uint32_t reg_addr)
{
    return addr - pa_offset + reg_addr;
}

void dump_mipi_rx_regs(int mem_fd, int phy, int chn)
{
    size_t len = 0x10000;
    uint32_t offset = 0x11a40000;
    uint32_t pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
    printf("mipi rx, pa_offset = %08x\n", pa_offset);
    uint32_t* base = (uint32_t*)mmap(NULL, len + offset - pa_offset, PROT_READ | PROT_WRITE,
        MAP_SHARED, mem_fd, pa_offset);
    if (base) {
        uint32_t pkt_intr_st = 0x1060 + 0x1000 * chn;
        uint32_t pkt_intr_st2 = 0x1070 + 0x1000 * chn;
        uint32_t frm_intr_st = 0x1080 + 0x1000 * chn;
        printf("PKT_INTR_ST :\033[31m%08x\033[0m\n", *(base + regaddr(pa_offset, offset, pkt_intr_st) / 4));
        printf("PKT_INTR_ST2:\033[31m%08x\033[0m\n", *(base + regaddr(pa_offset, offset, pkt_intr_st2) / 4));
        printf("FRM_INTR_ST :\033[31m%08x\033[0m\n", *(base + regaddr(pa_offset, offset, frm_intr_st) / 4));
        //
        munmap((void*)base, len + offset - pa_offset);
    }
}

void process_read(int mem_fd, uint32_t addr)
{
    if (qFlag(relative)) {
        addr += 0x11a40000;
    }
    size_t len = 4;
    uint32_t offset = addr;
    uint32_t pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
    printf("pa_offset = %08x\n", pa_offset);
    uint32_t* base = (uint32_t*)mmap(NULL, len + offset - pa_offset, PROT_READ | PROT_WRITE,
        MAP_SHARED, mem_fd, pa_offset);
    if (base) {
        printf("Reg:%08x, Value:%08x\n", addr, *(base + regaddr(pa_offset, addr, 0) / 4));
        //
        munmap((void*)base, len + offset - pa_offset);
    }
}

void process_write(int mem_fd, uint32_t addr, uint32_t value)
{
    if (qFlag(relative)) {
        addr += 0x11a40000;
    }
    size_t len = 4;
    uint32_t offset = addr;
    uint32_t pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);
    printf("pa_offset = %08x\n", pa_offset);
    uint32_t* base = (uint32_t*)mmap(NULL, len + offset - pa_offset, PROT_READ | PROT_WRITE,
        MAP_SHARED, mem_fd, pa_offset);
    if (base) {
        *(base + regaddr(pa_offset, offset, 0) / 4) = value;
        //
        munmap((void*)base, len + offset - pa_offset);
    }
}

int main(int argc, char** argv)
{
    // usage
    std::string usage = std::string() + "\n" +
        argv[0] + "--dump=1, dump mipi_rx status registers\n" +
        argv[0] + "--dump=0 --wr=1 --addr=0x1060, read the mipi_rx register\n" +
        argv[0] + "--dump=0 --wr=1 --addr=0x11a41060 --relative=false, read any register\n" +
        argv[0] + "--dump=0 --wr=0 --addr=0x1064 --value=0x10081008, write the mipi_rx register\n" +
        argv[0] + "--dump=0 --wr=0 --addr=0x10601064 --value=0 --relative=false, write any register\n";
    //printf("%s\n", usage.c_str());
    google::SetUsageMessage(usage);
    qInitializeFlags(&argc, &argv);
    
    int mem_fd = open("/dev/mem", O_RDWR, 0666);
    if (mem_fd < 0) {
        printf("can't open device:/dev/mem\n");
        return -1;
    }
    if (qFlag(dump)) {
        dump_mipi_rx_regs(mem_fd, qFlag(phy), qFlag(chn));
    } else {
        if (qFlag(wr)) { // read mode
            process_read(mem_fd, qFlag(addr));
        } else {
            process_write(mem_fd, qFlag(addr), qFlag(value));
        }
    }
    //
    close(mem_fd);

    return 0;
}

為了使用時(shí)方便,通痴崮担可以放入腳本中執(zhí)行

#!/bin/sh

# read mipi register
reg_addr=0x20
if [ $1 != "" ]; then
    reg_addr=$1
    echo read $reg_addr
fi

./dump_mipi_rx_regs --dump=0 --wr=1 --addr=$reg_addr
#!/bin/sh

# write value to mipi register
if [ $# != 2 ]; then
    echo "Usage:$0 <mipi_rx_reg_addr> <value>"

    exit
fi

./dump_mipi_rx_regs --dump=0 --wr=0 --addr=$1 --value=$2

例如颖系,讀取link0寄存器PHY_PH_MIPI_LINK(P)

./mipir 0x1c

程序中內(nèi)置了mipi-rx的寄存器基地址,默認(rèn)情況下輸入都被當(dāng)做相對(duì)地址辩越,使用--relative=0可啟用絕對(duì)地址嘁扼。最常用的地址就是0x1c[PHY_PH_MIPI_LINK(P)],可以從中讀出MIPI的包頭区匣,即Type-ID,Word-Count,ECC偷拔。不帶參數(shù)執(zhí)行程序蒋院,可以直接輸出MIPI_Rx的幾個(gè)狀態(tài)寄存器,無(wú)錯(cuò)誤時(shí)都為0莲绰。如果這些都沒(méi)有錯(cuò)誤欺旧,那么cat /proc/umap/hi_mipi可以獲得可閱讀的更多信息,否則還是拿上你的數(shù)據(jù)手冊(cè)打印寄存器的值看看吧蛤签。

YUV

我們只關(guān)注YUV辞友,也即sensor輸出的數(shù)據(jù)為YUV格式。海思提供有限支持震肮,在文檔《HiMPP V4.0 媒體處理軟件 FAQ》的3.3.4節(jié)有介紹称龙,這也是唯一的配置方法,我也是從這里抄的戳晌。海思的各個(gè)硬件之間可以互相倒騰數(shù)據(jù)鲫尊,這個(gè)工作可以由SDK完成。使用YUV時(shí)要旁路ISP沦偎,那么此時(shí)我們可以粗略的得到一個(gè)工作流程圖疫向。

graph TB
    id1[MIPI_Rx]-->id2(VI_CAP)
    subgraph Video input #0
        id2==>id3[PIPE0, YUV422]
        id2-.->id4[PIPE2-4]
        id3==>id5[Channel #0, YUV420 SP]
        id5-.->id6[Ext channel#0-8]
        id5==>id7(Encoder)
    end
    subgraph Video Encoder #0
        id7==>id8[Channel #0, H265]
        id8==>id9[Query frame]
    end
    subgraph live555
        id9==>id10[Send to network]
    end

對(duì)于HI3559AV100而言,Video和Mipi的對(duì)應(yīng)關(guān)系不需要設(shè)置豪嚎,天然對(duì)應(yīng)搔驼;PIPE可動(dòng)態(tài)綁定,每個(gè)PIPE有至少1個(gè)物理通道和幾個(gè)擴(kuò)展通道侈询,它們可以完成一些諸如裁切舌涨、縮放、旋轉(zhuǎn)之類(lèi)的工作扔字。理清了視頻的流向囊嘉,那么程序也就有了思路,不要看海思的樣例啦租,按照我們自己的思路走哗伯。

第一個(gè)YUV采集程序

硬件連接

4-lane不像2-lane一樣荒揣,在設(shè)計(jì)原理圖時(shí)不容易迷惑篷角。HI3559AV100有16個(gè)lane,4個(gè)link系任,4-lane的連接方式可以接入4路設(shè)備恳蹲。

graph LR
    id1["Sensor0"]==>id2["Lane0~3"]
    id3["Sensor1"]==>id4["Lane4~7"]
    id5["Sensor2"]==>id6["Lane8~Lane11"]
    id7["Sensor3"]==>id9["Lane12~Lane15"]

海思SDK中的資源描述

海思的SDK中把路視頻輸抽象為VI(Video input),并給它們分配不同的設(shè)備號(hào)俩滥,HI3559AV100最多可接入8路視頻嘉蕾,因此VI的設(shè)備號(hào)范圍為0~7。當(dāng)采用2-lane的方式時(shí)霜旧,可接入8路Sensor错忱,VI設(shè)備號(hào)對(duì)應(yīng)0~7,但是當(dāng)采用4-lane的連接時(shí),設(shè)備號(hào)只能由4個(gè)以清,但范圍并不是0~3儿普,而是0,2,4,6。對(duì)于MIPI掷倔,海思SDK也采用了類(lèi)似的抽象方式眉孩,其設(shè)備號(hào)范圍為對(duì)應(yīng)Vi的設(shè)備號(hào)。

開(kāi)始編寫(xiě)程序

首先勒葱,使用海思的SDK就不要想著多進(jìn)程了浪汪。其次,使用相應(yīng)的功能之前需要初始化凛虽。

第一步死遭,建立硬件描述

硬件的連接已經(jīng)非常清楚了,我們專(zhuān)門(mén)建立模塊來(lái)描述它們凯旋。

//
// (c) 2020 chiv
//
#pragma once

#include "mpi_vi.h"
#include "hi_mipi.h"

// all definitions of Hisilicon modules
namespace lowlevel {
namespace desc {
//---------------------------------------------------------------------------------------
//                                             Audio
//---------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------
//                                             Video
//---------------------------------------------------------------------------------------
// VI_DEV_ATTR_S
// VI_PIPE_ATTR_S
// VI_PIPE_CHN_ATTR_S
struct ViAttrs {
    VI_DEV_ATTR_S DevAttr;
    VI_PIPE_ATTR_S PipeAttr;
    VI_CHN_ATTR_S ChnAttr;
};
ViAttrs MakeMipiYuvAttrs_YUV422_8bits_UYVY(int width, int height);
//
// 4 input signals connected to mipi rx
combo_dev_attr_t MakeMipiAttrs_Input_0(int width, int height);
combo_dev_attr_t MakeMipiAttrs_Input_1(int width, int height);
combo_dev_attr_t MakeMipiAttrs_Input_2(int width, int height);
combo_dev_attr_t MakeMipiAttrs_Input_3(int width, int height);
//
static const int kNumberOfInputs = 4;
//
combo_dev_t MakeComboDev(int idx);
VI_DEV MakeViDev(int idx);
VI_PIPE MakeViPipe(int idx);
//
static inline combo_dev_attr_t MakeMipiAttrs(int idx, int cx, int cy)
{
    switch (idx) {
    case 0: return MakeMipiAttrs_Input_0(cx, cy);
    case 1: return MakeMipiAttrs_Input_1(cx, cy);
    case 2: return MakeMipiAttrs_Input_2(cx, cy);
    case 3: return MakeMipiAttrs_Input_3(cx, cy);
    default:printf("\033[31mMipiDevNo[%d] is not valid\033[0m", MakeComboDev(idx)); abort();break;
    }
}
}
}
#include "HardwareDesc.h"

namespace lowlevel {

//---------------------------------------------------------------------------------------
//                               Audio
//---------------------------------------------------------------------------------------

//---------------------------------------------------------------------------------------
//                               Video
//---------------------------------------------------------------------------------------
static combo_dev_attr_t MIPI_YUV422_ATTR_BASE = {
    .devno = 0,
    .input_mode = INPUT_MODE_MIPI,
    .data_rate = MIPI_DATA_RATE_X1,
    .img_rect = { 0, 0, 1920, 1080 },
    { .mipi_attr = {
          DATA_TYPE_YUV422_8BIT,
          HI_MIPI_WDR_MODE_NONE,
          { -1, -1, -1, -1, -1, -1, -1, -1 } } }
};

static VI_DEV_ATTR_S DEV_MIPI_YUV422_ATTR_BASE = {
    VI_MODE_MIPI_YUV422,
    VI_WORK_MODE_1Multiplex,
    { 0xFF000000, 0x00FF0000 },
    VI_SCAN_PROGRESSIVE,
    { -1, -1, -1, -1 },
    VI_DATA_SEQ_UVUV,
    { VI_VSYNC_PULSE, VI_VSYNC_NEG_LOW, VI_HSYNC_VALID_SINGNAL,
        VI_HSYNC_NEG_HIGH, VI_VSYNC_VALID_SINGAL,
        VI_VSYNC_VALID_NEG_HIGH,
        { 0, 1920, 0,
            0, 1080, 0,
            0, 0, 0 } },
    VI_DATA_TYPE_YUV,
    HI_FALSE,
    { 1920, 1080 },
    { {
          { 1920, 1080 },
      },
        { VI_REPHASE_MODE_NONE,
            VI_REPHASE_MODE_NONE } },
    { WDR_MODE_NONE,
        1080 },
    DATA_RATE_X1
};
static VI_PIPE_ATTR_S PIPE_YUV422_ATTR_BASE = {
    VI_PIPE_BYPASS_NONE, HI_FALSE, HI_TRUE,
    1920, 1080,
    PIXEL_FORMAT_YVU_SEMIPLANAR_422,
    COMPRESS_MODE_NONE,
    DATA_BITWIDTH_8,
    HI_FALSE,
    { PIXEL_FORMAT_YVU_SEMIPLANAR_422,
        DATA_BITWIDTH_8,
        VI_NR_REF_FROM_RFR,
        COMPRESS_MODE_NONE },
    HI_FALSE,
    { -1, -1 },
    HI_FALSE
};
static VI_CHN_ATTR_S CHN_YUV422_ATTR_BASE {
    { 1920, 1080 },
    PIXEL_FORMAT_YVU_SEMIPLANAR_420, // CHN ---> VENC, VENC need YVU420 semiplanar
    DYNAMIC_RANGE_SDR8,
    VIDEO_FORMAT_LINEAR,
    COMPRESS_MODE_NONE,
    HI_FALSE, HI_FALSE,
    0,
    { -1, -1 }
};
namespace desc {
    //
    combo_dev_t MakeComboDev(int idx)
    {
        return idx * 2;
    }
    VI_DEV MakeViDev(int idx)
    {
        return idx * 2;
    }
    VI_PIPE MakeViPipe(int idx)
    {
        return idx * 2;
    }
    // YUV 422 8bit
    ViAttrs MakeMipiYuvAttrs_YUV422_8bits_UYVY(int width, int height)
    {
        ViAttrs attrs = {
            .DevAttr = DEV_MIPI_YUV422_ATTR_BASE,
            .PipeAttr = PIPE_YUV422_ATTR_BASE,
            .ChnAttr = CHN_YUV422_ATTR_BASE
        };
        // No need for YUV
        attrs.DevAttr.stSynCfg.stTimingBlank = VI_TIMING_BLANK_S {
            0, (HI_U32)width, 0,
            0, (HI_U32)height, 0,
            0, 0, 0
        };
        attrs.DevAttr.stBasAttr.stSacleAttr.stBasSize = SIZE_S { (HI_U32)width, (HI_U32)height };
        attrs.DevAttr.stSize = SIZE_S { (HI_U32)width, (HI_U32)height };
        attrs.DevAttr.stWDRAttr.u32CacheLine = height;
        attrs.PipeAttr.u32MaxW = width;
        attrs.PipeAttr.u32MaxH = height;
        attrs.ChnAttr.stSize = SIZE_S { (HI_U32)width, (HI_U32)height };
        return attrs;
    }

    combo_dev_attr_t MakeMipiAttrs_Input_0(int width, int height)
    {
        combo_dev_attr_t attr = MIPI_YUV422_ATTR_BASE;
        attr.devno = 0;
        attr.img_rect = img_rect_t { 0, 0, (HI_U32)width, (HI_U32)height };
        attr.mipi_attr.lane_id[0] = 0;
        attr.mipi_attr.lane_id[1] = 1;
        attr.mipi_attr.lane_id[2] = 2;
        attr.mipi_attr.lane_id[3] = 3;
        attr.mipi_attr.lane_id[4] = -1;
        attr.mipi_attr.lane_id[5] = -1;
        attr.mipi_attr.lane_id[6] = -1;
        attr.mipi_attr.lane_id[7] = -1;
        return attr;
    }
    combo_dev_attr_t MakeMipiAttrs_Input_1(int width, int height)
    {
        combo_dev_attr_t attr = MIPI_YUV422_ATTR_BASE;
        attr.devno = 2;
        attr.img_rect = img_rect_t { 0, 0, (HI_U32)width, (HI_U32)height };
        attr.mipi_attr.lane_id[0] = 4;
        attr.mipi_attr.lane_id[1] = 5;
        attr.mipi_attr.lane_id[2] = 6;
        attr.mipi_attr.lane_id[3] = 7;
        attr.mipi_attr.lane_id[4] = -1;
        attr.mipi_attr.lane_id[5] = -1;
        attr.mipi_attr.lane_id[6] = -1;
        attr.mipi_attr.lane_id[7] = -1;
        return attr;
    }
    combo_dev_attr_t MakeMipiAttrs_Input_2(int width, int height)
    {
        combo_dev_attr_t attr = MIPI_YUV422_ATTR_BASE;
        attr.devno = 4;
        attr.img_rect = img_rect_t { 0, 0, (HI_U32)width, (HI_U32)height };
        attr.mipi_attr.lane_id[0] = 8;
        attr.mipi_attr.lane_id[1] = 9;
        attr.mipi_attr.lane_id[2] = 10;
        attr.mipi_attr.lane_id[3] = 11;
        attr.mipi_attr.lane_id[4] = -1;
        attr.mipi_attr.lane_id[5] = -1;
        attr.mipi_attr.lane_id[6] = -1;
        attr.mipi_attr.lane_id[7] = -1;
        return attr;
    }
    combo_dev_attr_t MakeMipiAttrs_Input_3(int width, int height)
    {
        combo_dev_attr_t attr = MIPI_YUV422_ATTR_BASE;
        attr.devno = 6;
        attr.img_rect = img_rect_t { 0, 0, (HI_U32)width, (HI_U32)height };
        attr.mipi_attr.lane_id[0] = 12;
        attr.mipi_attr.lane_id[1] = 13;
        attr.mipi_attr.lane_id[2] = 14;
        attr.mipi_attr.lane_id[3] = 15;
        attr.mipi_attr.lane_id[4] = -1;
        attr.mipi_attr.lane_id[5] = -1;
        attr.mipi_attr.lane_id[6] = -1;
        attr.mipi_attr.lane_id[7] = -1;
        return attr;
    }

} // namespace desc
} // namespace lowlevel

這部分代碼的目的是用一個(gè)連續(xù)的數(shù)字代替視頻輸入殃姓,例如:0代表第一路輸入,1代表第二路輸入瓦阐,等等蜗侈,硬件如何實(shí)現(xiàn)不重要,重要的是要把硬件的輸入與idx對(duì)應(yīng)起來(lái)睡蟋,故而對(duì)用戶(hù)而言踏幻,只存在video0、video1這樣的設(shè)備戳杀,不存在理解上的偏差该面。

第二步,初始化系統(tǒng)

使用視頻采集需要VB(Video buffer)模塊信卡,Sys是必須的隔缀,無(wú)用贅言。

// 初始化Video buffer
static bool _InitVb()
{
    struct {
        int Width;
        int Height;
        PIXEL_FORMAT_E PixFmt;
        DATA_BITWIDTH_E BitWidth;
        COMPRESS_MODE_E CompressMode;
        HI_U32 Align;
    } fmts[] = {
        { 1920, 1080, PIXEL_FORMAT_YVU_SEMIPLANAR_422, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN },
        { 1280, 720, PIXEL_FORMAT_YVU_SEMIPLANAR_422, DATA_BITWIDTH_8, COMPRESS_MODE_NONE, DEFAULT_ALIGN }
    };
    const int kFmtsCnt = sizeof(fmts) / sizeof(fmts[0]);
    //-------------------------------------------------------------------
    VB_CONFIG_S vb_conf;
    bzero(&vb_conf, sizeof(vb_conf));
    vb_conf.u32MaxPoolCnt = kFmtsCnt;
    for (int k = 0; k < kFmtsCnt; ++k) {
        HI_U32 frmsz = COMMON_GetPicBufferSize(fmts[k].Width, fmts[k].Height,
            fmts[k].PixFmt, fmts[k].BitWidth, fmts[k].CompressMode, fmts[k].Align);
        vb_conf.astCommPool[k].u32BlkCnt = 8;
        vb_conf.astCommPool[k].u64BlkSize = frmsz;
    }
    MPI_CHECK(HI_MPI_VB_SetConfig(&vb_conf), return false);
    MPI_CHECK(HI_MPI_VB_Init(), return false);

    return true;
}
// 初始化System
static bool _InitSys()
{
    MPI_CHECK(HI_MPI_SYS_Init(), return false);
    return true;
}

我們把這個(gè)過(guò)程總結(jié)為一個(gè)函數(shù)

bool Initialize()
{
    bool ok = _InitVb() && _InitSys();
    return ok;
}

void Shutdown()
{
    HI_MPI_SYS_Exit();
    HI_MPI_VB_Exit();
}
第三步傍菇,啟動(dòng)MIPI
// 模式7, 4-lane

void RunOnce()
{
    lane_divide_mode_t ld_mode = LANE_DIVIDE_MODE_7;
    Mipi::Inst()->IoControl(HI_MIPI_SET_HS_MODE, &ld_mode);
}
// idx 可取0,1,2,3
bool StartMipi(int idx, int cx, int cy)
{
    bool ok = true;
    combo_dev_t devno = desc::MakeComboDev(idx);
    ok = ok && Mipi::Inst()->IoControl(HI_MIPI_ENABLE_MIPI_CLOCK, &devno);
    ok = ok && Mipi::Inst()->IoControl(HI_MIPI_RESET_MIPI, &devno);
    auto dev_attr = desc::MakeMipiAttrs(idx, cx, cy);
    ok = ok && Mipi::Inst()->IoControl(HI_MIPI_SET_DEV_ATTR, &dev_attr);
    ok = ok && Mipi::Inst()->IoControl(HI_MIPI_UNRESET_MIPI, &devno);
    //
    return ok;
}
第四步猾瘸,創(chuàng)建VI

創(chuàng)建VI包括啟用VI_DEV,綁定PIPE丢习,設(shè)置CHANNEL等工作牵触。

// idx 可取 0,1,2,3
bool StartVi(int idx, int cx, int cy)
{
    VI_DEV devno = desc::MakeViDev(idx);
    VI_PIPE pipeno = desc::MakeViPipe(idx);
    // step 1. enable vi dev
    auto attrs = desc::MakeMipiYuvAttrs_YUV422_8bits_UYVY(cx, cy);
    MPI_CHECK(HI_MPI_VI_SetDevAttr(devno, &attrs.DevAttr), return false);
    MPI_CHECK(HI_MPI_VI_EnableDev(devno), return false);
    // step 2. start pipe
    VI_DEV_BIND_PIPE_S binded_pipe;
    bzero(&binded_pipe, sizeof(binded_pipe));
    binded_pipe.PipeId[0] = pipeno;
    binded_pipe.u32Num = 1;
    MPI_CHECK(HI_MPI_VI_SetDevBindPipe(devno, &binded_pipe), return false);
    MPI_CHECK(HI_MPI_VI_CreatePipe(pipeno, &attrs.PipeAttr), return false);
    MPI_CHECK(HI_MPI_VI_StartPipe(pipeno), return false);
    // step 3. start chn
    // Pipe has 2 channel, we use channel 0.
    VI_CHN chnno = 0;
    MPI_CHECK(HI_MPI_VI_SetChnAttr(pipeno, chnno, &attrs.ChnAttr), return false);
    MPI_CHECK(HI_MPI_VI_EnableChn(pipeno, chnno), return false);
    //
    return true;
}
第五步,創(chuàng)建編碼通道
static HI_S32 CreateH265Channel(VENC_CHN chnno, int cx, int cy, int fps)
{
    VENC_CHN_ATTR_S chn_attr;
    bzero(&chn_attr, sizeof(chn_attr));
    // 這個(gè)函數(shù)從SDK的sample中抄的.
    SAMPLE_COMM_VENC_GetGopAttr(VENC_GOPMODE_NORMALP, &chn_attr.stGopAttr);
    // rc attr , variable bitrate
    HI_U32 stat_time = 1, gop = 30;
    if (chn_attr.stGopAttr.enGopMode == VENC_GOPMODE_ADVSMARTP) {
        stat_time = chn_attr.stGopAttr.stSmartP.u32BgInterval / gop;
    } else if (chn_attr.stGopAttr.enGopMode == VENC_GOPMODE_SMARTP) {
        stat_time = chn_attr.stGopAttr.stSmartP.u32BgInterval / gop;
    }
    // vbr
#if 0
    chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H265VBR;
    chn_attr.stRcAttr.stH265Vbr.u32Gop = gop;
    chn_attr.stRcAttr.stH265Vbr.u32StatTime = stat_time;
    chn_attr.stRcAttr.stH265Vbr.u32SrcFrameRate = fps;
    chn_attr.stRcAttr.stH265Vbr.fr32DstFrameRate = std::min(25, fps);
    chn_attr.stRcAttr.stH265Vbr.u32MaxBitRate = 900; // 900k
#endif
#if 1 // 選用固定碼率
    chn_attr.stRcAttr.enRcMode = VENC_RC_MODE_H265CBR;
    chn_attr.stRcAttr.stH265Cbr.u32Gop = gop;
    chn_attr.stRcAttr.stH265Cbr.u32StatTime = stat_time;
    chn_attr.stRcAttr.stH265Cbr.u32SrcFrameRate = fps;
    chn_attr.stRcAttr.stH265Cbr.fr32DstFrameRate = std::min(25, fps);
    chn_attr.stRcAttr.stH265Cbr.u32BitRate = 500;//1024*2 + 2048*fps/gop;
#endif
    // enc attr
    chn_attr.stVencAttr.enType = PT_H265;
    chn_attr.stVencAttr.u32MaxPicWidth = cx;
    chn_attr.stVencAttr.u32MaxPicHeight = cy;
    chn_attr.stVencAttr.u32PicWidth = cx;
    chn_attr.stVencAttr.u32PicHeight = cy;
    chn_attr.stVencAttr.u32BufSize = cx * cy * 2;
    chn_attr.stVencAttr.u32Profile = 0; // H265 mainprofile
    chn_attr.stVencAttr.bByFrame = HI_TRUE;
    chn_attr.stVencAttr.stAttrH265e.bRcnRefShareBuf = HI_FALSE;
    // create channel
    MPI_CHECK(HI_MPI_VENC_CreateChn(chnno, &chn_attr), return HI_FAILURE);
    //
    VENC_RECV_PIC_PARAM_S recv_param;
    recv_param.s32RecvPicNum = -1;
    MPI_CHECK(HI_MPI_VENC_StartRecvFrame(chnno, &recv_param), return HI_FAILURE);
    //
    return HI_SUCCESS;
}
第六步咐低,建立視頻到編碼器的管道
void BuildPipeline(int idx)
{
    MPP_CHN_S src;
    src.enModId = HI_ID_VI;
    src.s32DevId = desc::MakeViDev(idx);
    src.s32ChnId = 0;
    MPP_CHN_S dst;
    dst.enModId = HI_ID_VENC;
    dst.s32DevId = 0;
    dst.s32ChnId = idx;
    MPI_CHECK(HI_MPI_SYS_Bind(&src, &dst), return -1);
}
所以完整的流程是

因?yàn)槭怯涗涍^(guò)程揽思,因此失敗后的處理代碼不贅述了。

int main()
{
    // 初始化必要的模塊
    Initialize();
    // 只需要執(zhí)行一次的內(nèi)容, 這里是選擇MIPI的工作模式
    RunOnce();
    // 創(chuàng)建4個(gè)采集通道
    for (int i = 0; i < 4; ++i) {
        StartMipi(i, 1920, 1080);
        StartVi(i, 1920, 1080);
        CreateH265Channel(i, 1920, 1080, 60);
        BuildPipeline(i);
    }
    // 取編碼數(shù)據(jù)
    while (1) {
        for (int i = 0; i < 4; ++i) {
            auto out = QueryFrame(i, 100);
            if (!out.empty()) {
                Dump(i, out);
            }
        }
    }
    // release your resources here
}
// 從編碼器里取數(shù)據(jù)
std::vector<char> QueryFrame(int idx, int timeout)
{
    std::vector<char> out; if (idx < 0) return out; // return empty for invalid idx.
    //
    VENC_CHN_STATUS_S chn_status;
    MPI_CHECK(HI_MPI_VENC_QueryStatus(idx, &chn_status), return out);
    if (chn_status.u32CurPacks > 0) {
        VENC_STREAM_S stream;
        bzero(&stream, sizeof(stream));
        stream.pstPack = new VENC_PACK_S[chn_status.u32CurPacks];
        stream.u32PackCount = chn_status.u32CurPacks;
        MPI_CHECK(HI_MPI_VENC_GetStream(idx, &stream, timeout),
                  delete[] stream.pstPack;
                  return out);
        HI_U32 len = 0;
        for (HI_U32 k = 0; k < stream.u32PackCount; ++k) {
            len += stream.pstPack[k].u32Len - stream.pstPack[k].u32Offset;
        }
        out = std::vector<char>(len);
        len = 0;
        for (HI_U32 k = 0; k < stream.u32PackCount; ++k) {
            memcpy(out.data() + len, stream.pstPack[k].pu8Addr + stream.pstPack[k].u32Offset,
                stream.pstPack[k].u32Len - stream.pstPack[k].u32Offset);
            len += stream.pstPack[k].u32Len - stream.pstPack[k].u32Offset;
        }
        //
        MPI_CHECK(HI_MPI_VENC_ReleaseStream(idx, &stream), );
        delete[] stream.pstPack;
    }
    return out;
}

吐槽時(shí)間

  • 視頻采集见擦,linux有v4l2系統(tǒng)钉汗,為什么不用羹令?
  • 音頻采集,linux有alsa损痰,為什么不用特恬?
  • 以及我什么也沒(méi)說(shuō)

虛擬通道的處理

如果采用了虛擬通道,即在4-lane上傳輸多路視頻徐钠,那么需要綁定對(duì)應(yīng)數(shù)目的PIPE癌刽,并使用HI_MPI_VI_SetPipeVCNumber為綁定的PIPE指定虛擬通道號(hào),在使能PIPE的物理通道時(shí)尝丐,要設(shè)置物理通道的隊(duì)列深度大于0显拜,這樣之后可以使用HI_MPI_VI_GetChnFrame從物理通道里獲取YUV420 SP格式的數(shù)據(jù),并調(diào)用HI_MPI_VENC_SendFrame把數(shù)據(jù)送入編碼器編碼爹袁。

Video output

海思SDK用VO來(lái)抽象視頻輸出远荠。HI3559AV100有三種物理輸出,HDMI失息、BT1120和MIPI-Tx譬淳。VO的目的用途是產(chǎn)生視頻幀、產(chǎn)生正確的時(shí)序盹兢,驅(qū)動(dòng)物理接口輸出邻梆。

graph LR
subgraph "HI3559AV100 VO"
    id1["DHD0"]---id2["VHD0, Super HD"]
    id1["DHD0"]---id3["VHD2, HD"]
    id1["DHD0"]---id6["G0,G3"]
    id4["DHD1"]---id5["VHD1, HD"]
    id4["DHD1"]---id7["G1,G3"]
end

DHD0支持超高清輸出,DHD1支持高清輸出绎秒。VHD浦妄、G可以疊加,DHD0和DHD1都可以與G3疊加见芹,故而G3可以用來(lái)實(shí)現(xiàn)鼠標(biāo)層剂娄。

海思SDK中內(nèi)置了一些常用的分辨率,使用它們時(shí)不需要自己設(shè)置時(shí)序玄呛,否則就需要自定義時(shí)序阅懦。自定義時(shí)序時(shí),請(qǐng)餐卡vesa標(biāo)準(zhǔn)徘铝,linux下有一些工具方便進(jìn)行計(jì)算耳胎,例如gtf、cvt庭砍,nvidia的顯卡驅(qū)動(dòng)中也帶了計(jì)算工具场晶,vesa官方提供了一個(gè)excel表格,也可以進(jìn)行計(jì)算怠缸,總之還是不難的。

程序流程

// 初始化模塊
bool Initialize()
{
    Shutdown();
    //
    bool ok = _InitVb() && _InitSys() && Hdmi::Initialize();
    // should be ok
    return ok;
}
// 初始化輸出物理設(shè)備
bool Hdmi::Open(int cx, int cy, int fps)
{
    // init, register callback
    auto devid = _FromIdx(mIdx);
    MPI_CHECK(HI_MPI_HDMI_Open(devid), return false);
    // register callback
    MPI_CHECK(HI_MPI_HDMI_RegCallbackFunc(devid, mRc->HdmiCb()), );
    //
    HI_HDMI_ATTR_S attr;
    MPI_CHECK(HI_MPI_HDMI_GetAttr(devid, &attr), return false);
    attr.bEnableHdmi = HI_TRUE;
    attr.bEnableVideo = HI_TRUE;
    attr.enVideoFmt = _FindVideoFmt(cx, cy, fps);
    if (attr.enVideoFmt == HI_HDMI_VIDEO_FMT_VESA_CUSTOMER_DEFINE) {
        qLog(ERROR) << "Dose not support user defined timing yet" << std::endl;
        return false;
    }
    attr.enVidInMode = HI_HDMI_VIDEO_MODE_YCBCR444;
    attr.enVidOutMode = HI_HDMI_VIDEO_MODE_YCBCR444;
    attr.enDeepColorMode = HI_HDMI_DEEP_COLOR_24BIT;
    attr.bxvYCCMode = HI_FALSE;
    attr.enOutCscQuantization = HDMI_QUANTIZATION_LIMITED_RANGE;
    attr.bEnableAudio = HI_FALSE;
    attr.enSoundIntf = HI_HDMI_SND_INTERFACE_I2S;
    attr.bIsMultiChannel = HI_FALSE;
    attr.enBitDepth = HI_HDMI_BIT_DEPTH_16;
    attr.bEnableAviInfoFrame = HI_TRUE;
    attr.bEnableAudInfoFrame = HI_TRUE;
    attr.bEnableSpdInfoFrame = HI_FALSE;
    attr.bEnableMpegInfoFrame = HI_FALSE;
    attr.bDebugFlag = HI_FALSE;
    attr.bHDCPEnable = HI_FALSE;
    attr.b3DEnable = HI_FALSE;
    attr.enDefaultMode = HI_HDMI_FORCE_HDMI;
    MPI_CHECK(HI_MPI_HDMI_SetAttr(devid, &attr), return false);
    MPI_CHECK(HI_MPI_HDMI_Start(devid), return false);
    //
    return true;
}
// 初始化輸出
bool InitVO()
{
    MPI_CHECK(HI_MPI_VO_Disable(vodev), );
    MPI_CHECK(HI_MPI_VO_SetPubAttr(vodev, &pub_attr), return false);
    MPI_CHECK(HI_MPI_VO_Enable(vodev), return false);
    // 啟用層
    VO_VIDEO_LAYER_ATTR_S vlayer_attr;
    vlayer_attr.bClusterMode = HI_FALSE;
    vlayer_attr.bDoubleFrame = HI_FALSE;
    vlayer_attr.enDstDynamicRange = DYNAMIC_RANGE_SDR8;
    vlayer_attr.enPixFormat = PIXEL_FORMAT_YVU_SEMIPLANAR_422;
    vlayer_attr.stImageSize = SIZE_S { (HI_U32)cx, (HI_U32)cy };
    vlayer_attr.stDispRect = RECT_S { 0, 0, (HI_U32)cx, (HI_U32)cy };
    vlayer_attr.u32DispFrmRt = fps;
    MPI_CHECK(HI_MPI_VO_SetDisplayBufLen(mVoLayer, 3), return false);
    MPI_CHECK(HI_MPI_VO_SetVideoLayerAttr(mVoLayer, &vlayer_attr), return false);
    MPI_CHECK(HI_MPI_VO_EnableVideoLayer(mVoLayer), return false);
}

海思的SDK是通用的钳宪,所以每一個(gè)用戶(hù)都需要根據(jù)硬件的情況設(shè)計(jì)應(yīng)用揭北。哎扳炬,帶一個(gè)圖形界面多好,這部分工作交給小廠(chǎng)家去做很費(fèi)勁的搔体。如果僅僅基于framebuffer做一個(gè)GUI恨樟,那效率堪憂(yōu)。瑞芯微3399上有一個(gè)linux發(fā)行版疚俱,用起來(lái)就是卡卡的劝术,看著性能與nvidia的jetson不差,但是jetson的GUI那是可以辦公用的呆奕。

Audio

To be continued

GPU

按照手冊(cè)測(cè)試养晋,有的不能運(yùn)行,能運(yùn)行的結(jié)果不對(duì)梁钾。哎绳泉,好好的,你整個(gè)X windows實(shí)現(xiàn)不行嗎?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末姆泻,一起剝皮案震驚了整個(gè)濱河市零酪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拇勃,老刑警劉巖四苇,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異方咆,居然都是意外死亡蛔琅,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)峻呛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)罗售,“玉大人,你說(shuō)我怎么就攤上這事钩述≌辏” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵牙勘,是天一觀的道長(zhǎng)职恳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)方面,這世上最難降的妖魔是什么放钦? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮恭金,結(jié)果婚禮上操禀,老公的妹妹穿的比我還像新娘。我一直安慰自己横腿,他們只是感情好颓屑,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布斤寂。 她就那樣靜靜地躺著,像睡著了一般揪惦。 火紅的嫁衣襯著肌膚如雪遍搞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天器腋,我揣著相機(jī)與錄音溪猿,去河邊找鬼。 笑死纫塌,一個(gè)胖子當(dāng)著我的面吹牛诊县,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播护戳,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼翎冲,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了媳荒?” 一聲冷哼從身側(cè)響起抗悍,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钳枕,沒(méi)想到半個(gè)月后缴渊,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鱼炒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年衔沼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昔瞧。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡指蚁,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出自晰,到底是詐尸還是另有隱情凝化,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布酬荞,位于F島的核電站搓劫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏混巧。R本人自食惡果不足惜枪向,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咧党。 院中可真熱鬧秘蛔,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)聪舒。三九已至辨液,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間箱残,已是汗流浹背滔迈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留被辑,地道東北人燎悍。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像盼理,于是被迫代替她去往敵國(guó)和親谈山。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349