[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_ST
、MIPI(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)不行嗎?