From ee655750f9966b9ec5d818d9e798a8f66f326620 Mon Sep 17 00:00:00 2001 From: thead_admin Date: Thu, 5 Jan 2023 17:50:07 +0800 Subject: [PATCH] Linux_SDK_V1.0.3 --- .gitignore | 27 - Makefile | 139 +-- README.md | 65 +- doc/pics/comm.PNG | Bin 0 -> 50893 bytes doc/pics/packet.PNG | Bin 0 -> 11834 bytes doc/pics/plink-sw-stack.PNG | Bin 0 -> 10931 bytes doc/pics/thead-logo-eng-white-wide.png | Bin 0 -> 6220 bytes doc/plink-api.md | 1141 ++++++++++++++++++++++++ inc/process_linker.h | 225 +++++ inc/process_linker_types.h | 196 ++++ kernel_mode/Makefile | 6 - kernel_mode/isp_venc_shake_driver.c | 439 --------- kernel_mode/isp_venc_shake_driver.h | 71 -- src/process_linker.c | 552 ++++++++++++ test/Makefile | 73 -- test/isp_venc_shake.c | 76 -- test/plink_client.c | 203 +++++ test/plink_server.c | 432 +++++++++ test/plink_stitcher.c | 766 ++++++++++++++++ user_mode/Makefile | 75 -- user_mode/isp_venc_shake_hal.c | 160 ---- user_mode/isp_venc_shake_hal.h | 75 -- 22 files changed, 3603 insertions(+), 1118 deletions(-) create mode 100644 doc/pics/comm.PNG create mode 100644 doc/pics/packet.PNG create mode 100644 doc/pics/plink-sw-stack.PNG create mode 100644 doc/pics/thead-logo-eng-white-wide.png create mode 100644 doc/plink-api.md create mode 100644 inc/process_linker.h create mode 100644 inc/process_linker_types.h delete mode 100644 kernel_mode/Makefile delete mode 100644 kernel_mode/isp_venc_shake_driver.c delete mode 100644 kernel_mode/isp_venc_shake_driver.h create mode 100644 src/process_linker.c delete mode 100755 test/Makefile delete mode 100644 test/isp_venc_shake.c create mode 100644 test/plink_client.c create mode 100644 test/plink_server.c create mode 100644 test/plink_stitcher.c delete mode 100755 user_mode/Makefile delete mode 100644 user_mode/isp_venc_shake_hal.c delete mode 100644 user_mode/isp_venc_shake_hal.h diff --git a/.gitignore b/.gitignore index e0c3d40..d34d9fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,29 +1,2 @@ -/.auto.deps -/.config.cmd -/.config.old -/..config.tmp -/.config /.vscode -*.tmp -*.depend -*.o -*.a -*.o.d -*.o.cmd -*.a.cmd -*.ko.cmd -*.ko -*.mod -*.mod.c -*.mod.cmd -*.order -*.orig -*.symvers.cmd -*.order.cmd -test/ivs_test -user_mode/libivs.so -Module.symvers -out/ output/ -obj/ -dependencies/ diff --git a/Makefile b/Makefile index ce40b43..c199387 100644 --- a/Makefile +++ b/Makefile @@ -1,120 +1,47 @@ -## - # Copyright (C) 2020 Alibaba Group Holding Limited -## ifneq ($(wildcard ../.param),) - include ../.param + include ../.param endif -#CONFIG_DEBUG_MODE=1 -CONFIG_OUT_ENV=hwlinux +INC_PATH ?= /usr/include +LIB_PATH ?= /usr/lib -CONFIG_BUILD_DRV_EXTRA_PARAM:="" -CONFIG_BUILD_LIB_EXTRA_PARAM:="" -CONFIG_BUILD_TST_EXTRA_PARAM:="" +OUTPUTDIR = ./output +LIBNAME = $(OUTPUTDIR)/libplink.so +server_NAME = $(OUTPUTDIR)/plinkserver +client_NAME = $(OUTPUTDIR)/plinkclient +stitcher_NAME = $(OUTPUTDIR)/plinkstitcher -DIR_TARGET_BASE=bsp/ivs -DIR_TARGET_LIB =bsp/ivs/lib -DIR_TARGET_KO =bsp/ivs/ko -DIR_TARGET_TEST=bsp/ivs/test +INCS = ./inc +LIBSRCS = ./src/process_linker.c +LIBOBJS = $(LIBSRCS:.c=.o) +server_SRCS = ./test/plink_server.c +server_OBJS = $(server_SRCS:.c=.o) +client_SRCS = ./test/plink_client.c +client_OBJS = $(client_SRCS:.c=.o) +stitcher_SRCS = ./test/plink_stitcher.c +stitcher_OBJS = $(stitcher_SRCS:.c=.o) -MODULE_NAME=IVS -BUILD_LOG_START="\033[47;30m>>> $(MODULE_NAME) $@ begin\033[0m" -BUILD_LOG_END ="\033[47;30m<<< $(MODULE_NAME) $@ end\033[0m" +CFLAGS = -I$(INCS) -I$(INC_PATH)/vidmem +CFLAGS += -pthread -fPIC -O -# -# Do a parallel build with multiple jobs, based on the number of CPUs online -# in this system: 'make -j8' on a 8-CPU system, etc. -# -# (To override it, run 'make JOBS=1' and similar.) -# +$(shell if [ ! -e $(OUTPUTDIR) ];then mkdir -p $(OUTPUTDIR); fi) -ifeq ($(JOBS),) - JOBS := $(shell grep -c ^processor /proc/cpuinfo 2>/dev/null) - ifeq ($(JOBS),) - JOBS := 1 - endif -endif +all: lib server client stitcher -all: info driver lib test install_local_output install_rootfs -.PHONY: info driver lib test install_local_output install_rootfs \ - install_prepare install_addons clean_driver clean_lib clean_test clean_output clean +lib: + $(CC) $(LIBSRCS) $(CFLAGS) -shared -o $(LIBNAME) -info: - @echo $(BUILD_LOG_START) - @echo " ====== Build Info from repo project ======" - @echo " BUILDROOT_DIR="$(BUILDROOT_DIR) - @echo " CROSS_COMPILE="$(CROSS_COMPILE) - @echo " LINUX_DIR="$(LINUX_DIR) - @echo " ARCH="$(ARCH) - @echo " BOARD_NAME="$(BOARD_NAME) - @echo " KERNEL_ID="$(KERNELVERSION) - @echo " KERNEL_DIR="$(LINUX_DIR) - @echo " INSTALL_DIR_ROOTFS="$(INSTALL_DIR_ROOTFS) - @echo " INSTALL_DIR_SDK="$(INSTALL_DIR_SDK) - @echo " ====== Build configuration by settings ======" -# @echo " CONFIG_DEBUG_MODE="$(CONFIG_DEBUG_MODE) - @echo " CONFIG_OUT_ENV="$(CONFIG_OUT_ENV) - @echo " JOBS="$(JOBS) - @echo $(BUILD_LOG_END) +server: lib + $(CC) $(server_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(server_NAME) -driver: - @echo $(BUILD_LOG_START) - make -C $(LINUX_DIR) M=$(PWD)/kernel_mode ARCH=$(ARCH) modules - @echo $(BUILD_LOG_END) +client: lib + $(CC) $(client_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(client_NAME) -clean_driver: - @echo $(BUILD_LOG_START) - make -C kernel_mode KDIR=$(LINUX_DIR) clean - @echo $(BUILD_LOG_END) +stitcher: lib + $(CC) $(stitcher_SRCS) $(CFLAGS) -L$(OUTPUTDIR) -L$(LIB_PATH)/vidmem -lplink -lvmem -ldl -pthread -o $(stitcher_NAME) -lib: - @echo $(BUILD_LOG_START) - make -w -C user_mode hwlinux - @echo $(BUILD_LOG_END) - -clean_lib: - @echo $(BUILD_LOG_START) - make clean -C user_mode - @echo $(BUILD_LOG_END) - -test: lib driver - @echo $(BUILD_LOG_START) - make -w -C test hwlinux - @echo $(BUILD_LOG_END) - -clean_test: - @echo $(BUILD_LOG_START) - make clean -C test - @echo $(BUILD_LOG_END) - -install_prepare: - mkdir -p ./output/rootfs/$(DIR_TARGET_KO) - mkdir -p ./output/rootfs/$(DIR_TARGET_LIB) - mkdir -p ./output/rootfs/$(DIR_TARGET_TEST) - -install_addons: install_prepare - @echo $(BUILD_LOG_START) - @echo $(BUILD_LOG_END) - -install_local_output: driver lib test install_addons - @echo $(BUILD_LOG_START) - find ./kernel_mode -name "*.ko" | xargs -i cp -f {} ./output/rootfs/$(DIR_TARGET_KO) - find ./user_mode -name "*.so" | xargs -i cp -f {} ./output/rootfs/$(DIR_TARGET_LIB) - find ./user_mode -name "*.a" | xargs -i cp -f {} ./output/rootfs/$(DIR_TARGET_LIB) - cp -f ./test/ivs_test ./output/rootfs/$(DIR_TARGET_TEST) - @if [ `command -v tree` != "" ]; then \ - tree ./output/rootfs; \ - fi - @echo $(BUILD_LOG_END) - -install_rootfs: install_local_output - @echo $(BUILD_LOG_START) - @echo $(BUILD_LOG_END) - -clean_output: - @echo $(BUILD_LOG_START) - rm -rf ./output - @echo $(BUILD_LOG_END) - -clean: clean_output clean_driver clean_lib clean_test +clean: + rm -rf $(OUTPUTDIR) +%.o : %.c + $(CC) $(CFLAGS) -c $< -o $@ diff --git a/README.md b/README.md index 812cb57..f23815f 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,57 @@ -# How to build -- Just run 'make' to build kernel mode driver, user mode driver and test application. +# Process Linker Repository +This git module contains a linker library which can be used to connect two process for data sharing and passing file descriptor. -Find all the binaries in following folders: -- output/rootfs/bsp/ivs/ko: kernel mode driver -- output/rootfs/bsp/ivs/lib: user mode driver static linked library and dynamic linked library -- output/rootfs/bsp/ivs/test: test application +- **src**: c source code of process linker library. +- **inc**: public header files of process linker library. User should include these files to use process linker. +- **test**: sample applications. Two sample applications, server and client, are implimented for test and reference purpose. -# Description of each directories -- kernel_mode: kernel mode driver source -- user_mode: user mode driver source -- test: test application source +## How to build +Just run `make` and binaries will be generated in **output** folder. + +## How to use +- **libplink.so**: shared library of process link. See API doc for more details of usage. +- **plinkserver**: sample server application +```shell +usage: ./plinkserver [options] + + Available options: + -l plink file name (default: /tmp/plink.test) + -i input YUV file name (mandatory) + -f input color format (default: 3) + 2 - I420 + 3 - NV12 + 4 - P010 + 14 - Bayer Raw 10bit + 15 - Bayer Raw 12bit + -w video width (mandatory) + -h video height (mandatory) + -s video buffer stride in bytes (default: video width) + -n number of frames to send (default: 10) +``` +- **plinkclient**: sample client application +```shell +./plinkclient [frames] [plink server name] [dump file name] +``` + +- **plinkstitcher**: sample implementation of stitching filter, which can stitch up to 4 source videos (NV12 or RAW) into one as NV12 output + +``` +usage: ./plinkstitcher [options] + + Stitch multiple pictures to one. Maximum # of pictures to be stitched is 4 + Available options: + -i plink file name of input port #n (default: /tmp/plink.stitch.in). n is 0 based. + -o plink file name of output port (default: /tmp/plink.stitch.out) + -l layout (default: 0) + 0 - vertical + 1 - horizontal + 2 - matrix + -f output color format (default: 3) + 3 - NV12 + -w output video width (default: 800) + -h output video height (default: 1280) + -s output video buffer stride (default: video width) + --help print this message +``` + +Please note the sample applications have dependency on **video-memory** module for memory allocating and dma-buf operations. diff --git a/doc/pics/comm.PNG b/doc/pics/comm.PNG new file mode 100644 index 0000000000000000000000000000000000000000..75ae4e996c3aa4daa3b69b8fa611e9b9614be985 GIT binary patch literal 50893 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 literal 0 HcmV?d00001 diff --git a/doc/pics/packet.PNG b/doc/pics/packet.PNG new file mode 100644 index 0000000000000000000000000000000000000000..db98711e5ad89dc96ce27cb6a5c065a0f14a26b0 GIT binary patch literal 11834 zcmd6NcT`jBwr^BGX`&z^T}41zKoCJ`cBKe{KoCNcCOv>i3(2M{QWS(xgjfhDErJL^ zAQ%v`Aru8^(h_>_os_#k_dffcbMJe5ocHb+_YVdmYkli0v;5}#&G|jPt$%})O@Ivq z0&!~JylMmj?E`~AdoCSd0iHaI+vWuR?eQ_XaT!$DA~Xs7U~;;2>kc`dcL&>f#h1XuU<0tx0>w_YO?pU)LP&r90E7qI-%_UM928`(?gFB z7aa@RPL9kCnXV)YePNJ)?$ym*zHwRdfXwk183B$~*pbBk^FCvJnVT(}m1rMFKV4vR zDPE!MAdtc`6CVg>~j+j6a@M$RT2lhRx*I?0f8Jd$$LSdCUpXEoRon7^9P>I z1P4;GoX)$nk4~+b_9GzB`@_?0;GB$yX+|!WcNS>A3xR!$A=uZmfiC(m@!fx1v={VM zh`?-qFCPRtdjl4dd`t!ybr||me0PWdC%8Xpf30M)C%fnXUWo3vUHG*JuLP&gWY})U zd2DP*wsK~)mqoSkquDY(XbQL7#b1DydK)b1q-X(qz32l>fJAQ6-%m%@Ql|E;M+(m2 zB>Vgq3SrByDr#n<=2^>v7j-Gg`}PNY9FMD|v;cd4TeGTt8eA9Sl~d_4!b`AM;Sm&bEGgOJDBcp=`+`&Oa#i3&Ms4oB||x zHitr9>N$;rE5pUuzy{X{qgruv>y0sn5W9h>+R@C|8q$Yv>IT>LjJYy`)ZvIxeL_UJ z#O{YZWbYQO2-o#Ne*EHhCCdFH&^XOysA}MhzyH=G;~+79eP2_efd+4!`sRZT>OgHM zyU#5^Tle+f5Jv5K{lW#;L^-(BxBR<*s<`Ayujt7)1&gpYZG9GZg((k7Fa|MQO&-i#`|Y+ zFIFdoZRbP6D3lv`kz|xSXTOKvZ9XUrp~_HFtJ&nUI_B}iPXX%PA@@NLy5{R`n`f30 z+yK{*dBtX7y$nKz7qY05ot$bGpi;|gw{-?5-8kY$VIZx8ddi=wEaSv*ZN~ly# zETodlRqI^33DxH?V4P{+?!z{}^o&Oedi03m>D@e(BBBVFwS^@ogFf+w@+wM%Ena=e z#(#mbzoy^1O1=-VAwxl9`;?XmnB2S8{U5&vc^op1COBlc>_;^QZ1ui((3RY>s7>Pl zPjmRDCjr*}^~Lot(v8*)twAujzB_@=#HtgM}A*0d| zvQg#I;r(GHI+twpF1~$kRjw_~Dzqsh+o0~ss$pnir6Jv zxMzd81x%_xJITxwHYxh#Y?eJLT~Dwq<~d+6pD(b2O|pfdZA6njuS{Y5%AVZD_4#G; zJlDMm;tA?4k-3J0Ia0Uy0P{7IBw!jjm{0^c^Dr5HS5~m|LIbu;%e^hI?enwB(qqt* zqYkKjh9;linRlx1fN`1_&NwxK%-z6{v2tYx7hSroEp4U0S`A8OiofJgm|z?k`+y!T zb2)YNdAzk_rt%EYk#|04$BH@rdszJ&$on^jbn-$=JqJbB=KAxV3*6a-+fE^<)%vHd zRRKlJ_l*mq&+pWvI%THD2!TN7YIylD=DGg)xS^fB4SM}E)c=hI|D7Q@b>w313*Rai zsTQTO5p&P%y$Mr=n4;ENNAX>x{YY-(I+bx6A0vrV!0sYp$x*0jsXMDh>BCx=CI2IY zh=89XBghF=*JR(h*G_}0E-LvK@_YgSQ5q4yYxt^i9QT^=`kp|fT)w%3 z$Sn=*VC|ceGrA&@ebV)n+&e1uX$vL&E~)+;jpk zznA9=aTj%Wx3m$*UUK!1*`Bq{-mB{UGUN;_abx$PHYd0U;jb;jCR+il0pDGt-5eHj z&FR06s{f80|1~!Mubw3bv#WNvZiQ)WW2szK*Ge9bMq&{bh&^omVhzKY9bfwenkzmE z(h9I;F%yf8g_x$|gs#=(XZpR|7q+*sAtKJEmeN7~}M(CHP|5A684NMy^Du#RCu$wcbdcd@==#1%7Y4SttU^m3> zblF1CliJ-A5lJJ;buUli-t+Gcb(X?`{%&sdf6<@L_Rw(9)VmyMj7xIsDH|o8ot$jv zp`hq^F3T;cBzd&)SkCX}`vvyIZBpBt4>w)^)oKWm9t-rt%p?&hMi>EB~~A42&HtA^e5I%WjTn89o$s#PvQ{m#f$a5XW00YzY%Na@(S6|{Z(pE#HNF7K)GN?9cAdr8*{6J zQFB46&;Zg2AQx43&Ih@%gtC`>F>@Zwa-3N?jmsBO7xJi_EnR4YROPC87Fp4kAmMXM z)#3eJgJ7IO*+0oH<8LR40G7J`XOVSFwrHyK!ASIlRbu9Wc#SpxjUQ7k8|ybjIk_}e zZu^fs+g}^TrFCeIJ{3$^tF>ljz(+F^xdHG_!ftWo@t7uT2Cn(OcGi6rWdCK@-1M@f z`C40H$B0)F;QQtuHE05iACq_cKOS+nrlfQn7A3m7B093q{o~AcD^zdi*8%5#;{=zU z!HT50i-+jGd{LiENe%I8<|5miBuntngz`M!WIOWXNIJgm1`{yAH0*Kt1fmisuVu71DcDq5!9+&!4#dkMGV zQx=(Yg>kgdqFfB>Hyr}&mn(~MUC$49U7Lxe_X*OfTL3@SL$X=b%gz!X5Hd~iZtJ(9 z@dposf?BUl&mGhh+n#QSAAFz%+3HOL%ak<_m#Xv-o0jWlg+2e_i<4*Kbz$cNhVsBYcC>+Yyr2DV zso!HP|21zdjpYP4IQ84S=wbtZTT--hTo@36n_@VFAEthD^@a#ZFr%Vp^|eq+q)BWA zwH&?OCp~v3t9s@eR&d72BfWic)JqL&-GLBq_a^CBrDw>W*Y9x`{d31JD#^u%1)&3E z`#WAnB9Rdk)&PZqSBdSoRi{`h&oz}0PT_Wb<8@W#ci$COjg>D3hq)htny>Z`()t^` z2DmfVE5}#q`D_a5Mf}s%REcU$CRoV77hUc=Vu0hfZ*LAPPrx@XgIo7w`WXTK0sS_wxqo#31Zcg zUb-2o+dNj*{k_X2>q+d^0_}xqc{y4Ly3}iF<%nPEH5M4ewB}Hq3=LNiM|0N8-cg@K z-@DDfh;tcq6`pUt1lu!4Fb*S;?M>A~MzXKvCYQ0$5^ojv9Zu4ChAMOQPblO7r}7NI zEb9_2k$5l2-p1)>5;&&}-$+jOm^P31$v(`lGWduVmsBdB;BFSVoqiY6QACm}bE!~V zHWeO!+Zfwp&e2s4ux`LRa=L6hv!nObM`^CSTHr%2@=;ppGp&J?VFUVzL5(PtAR4}2 zH6L6Ryfr&WpIw;qt5&1dds45@>#pobPH12;&M&;Inl?)rN6d}aSPX7;F0^aW+9}PR z2p|9U^noJ-3y~_}UDfne$_>QGjT$c6SFY92uj}yE_3%6T=fx|xIt-AB23m(!410~$MH=w-ng)TyESEuMzTZ+6Eg(=u$%GfCVY&gSqt~_0FJ3d@g?jJF zYC+VS6k$hK$2gM{U)T`myB3(Nrd7K%>ffxX8TH}2e-F+*U}=KI9&{?Av}RSJ1>I#E zN%{p2!i(^<@YToFq&BUVj@334{w#b%Ow_9mvyKotbB+ydUAykRxY->}PKc?UNfGUs ziBHosHiIms8d237=3IAF4w;2yl~BE5lkz##9tibr?2kAglLEB#-Y^ zrG@kci9lL+;^bGGbT&!4{}1wric7k5Hdm+h+o8vwDhDbF%MFN_3lE(g;wfK-)fJB) z;aI~~(oBc}Bbp}p*s0Yw$cNg?hc!@hwF|1AB}K>Rl|J*eznga+Zq3m)LvQ5jcXT9g zlz!Z>7Vf1EdcE5ekz-048m4a-;qve88$`Ycmd}|VHd3-)<96DB*mKifm@O~0+ z*D&3PHt1<>qiPz@J9!1xjX$_h@ZP#=bu`8t%~&L#dtoip z39LnqS9;B1{Q1@Q0-kvh92TGtgH0p{eMQIh_9diZkEqvEWpm?}VHF>?)9ykX2O81s zHo=AtrLGF|-NThuwX-}>eLR}hgT58FIPp25E#}O3(K3@)2kb`eAH^3KlGzSTE>vjY(~ zm_t?6nKmeO?|Ybplo13fE9VL}{OgEv^m~j^ly#<_uw2HVmxORxsID?VMshL(UFMI; zU?d>wR-JZRWzn43PSU*(B$t|}$GE`XzF(TSX;3npPW`rR7HC2Ydw0vuz+PDKT z7UuxIm}HICkO=F_l5mIE7if)tV?sGUu7*`7+hz0H-GuQI&77_K9@X)R+F`y==nxwN zOL(Bi4G)H-hmec!ej#>wMK%Bo$vuld>>5D)KAa1JX-{V`<}=EPp(MAjF_$y|R>j2k z_6E$>kriC8lYZ9jc6?ErCV_eCFw};HUuL%bFJnUZ?ej|vlvq}e$pGSgDMDa&dqWE2M2^{GqEJmiW)wEk^D1HV-Dw<^_>I9n zNJZOGChdqd9}N8i&ZUjNJN+3xi;RzI#r8?<7Q9m~{Xu29;N*Rj{0}WgIa|$ zy1M_J+WW3v942-jPqzRs>0Ax2#i61LcY^Ei%igEBie*kfZYG*dy-C(^b_ zW{#rj5zkkomJb8u1eU_A!nYLy+~<%B6Q9iq=RXCwoQ&mS_;22#)W~?m z(Gj;)83}o{TBm#mu0g_u4Oi(R9p$7h{dHu^;8$K$E>N-WOT7q`sLm~Jk`p46;RjNW zK)1?@>P|?dJ_Sn(KGNAX?L(V+q1zmstWmORoQJ1b!IxMrvp;o+#>GwWpO5sM(@>b<$VW*5ak~^-$q=d_!%YYLB%ge^> zRIrj(^Maa~P|-v{e3QeMZ}JO5r9-bU*z^MEhV37gAfDI5qwF)PhD*lCMw%?aG+$Qm z8&=?BU!%05k#HVtPBEKgCcQy9x5;xWH@l-lD;TS9JM$fz3sO=6x)VT)jNN&PfO&lw%XVngxwfk=M3RyIg&LSEutK5M2He<>>Yn(l zU`Bs5ZdVsDluLJ?foV98qRIxyMrIx*@uLABh-a0mZQ8|fZhC9Ey3n6gkv@s0JNpkk zmJD1z0KOeh-Yc8sydULuMQbjagd-bS)^6jTaBjgeHfQ-op^B{#mY0_ulArYx;kMD*n4Q?{UGLkRZcl~B0MYUd}ghKeW=lzlMz3(c>AiXu*|a6;8ww) zfYn9~UnB~@f}!wY{jd42=2;8xRB<9jH$p@YV1xRnYo@IkTMP9n_%oH8&7O12v1L`W z?vzy=g%B?Iaq1?T5#SlvaiKl> zO4EAq;5ujFXHdruo;6>FNztz(3kc$jEy{`!tikk}>Rne@>I1#DS?Oj-In_3W9~og+ zK)_r$RTsUjw3WiGOKfzk7+Q*uho6A@0=?^KnZg4ot3YSm#Oo(D%zV&84`;d$x11k; z0qeZ$6rt3jDnsVCN}f75PxtU& zuNV|Y(7KQs)J7~-$DgP+a2P=!M`~8C7I<=Mt=`e%9aza$i9l~WMjrsa)Dl1}>_;lV z0c!23y3@?^LyY@HqN-*bb_p`T;jO+RTW`I!rgd1~io0<88i1olecfMTFH@b|V-ZA* z8*p^d6c-j$)ABoQAc2OrO>l|Czja&Iav7kgzgf$<567L3diE{kChTsyZ!E93=F+qH zl|i3~K*0CAwwIN$0k`~X1qYrsamJk7ah7kPx_wXi_T9{Nsk$->y6r)AZR~3~S1Il~ z3xVd2<^9Mu+*StTVz>;RXv2C^Hb|R`es=@s%d^_%E54K^)wO#5%tC>gUI7-)5)6g$ ztE2j(^g#TwIVD&zkB(f`$XZ`}ytPr9=PbcNL9Sx9r+1wRWzK|(m7@Fn?u7{v7sp-H z*g;@@9i?+V1o7S`t}8VPsxgIdv){uL?wLeBk8W&1aq`sVcF%tzh;OGgazkAXLp$ZS z4Ng>V)XC5FE1Iq{!*|o$-y%xh62t{Bw1E^faWQmAsScJD`OMhl^t&|dNsiHdfFJ7S zAF_JwiyG&J*6&P$FJ9Uq>tg$KbzmWZe+Es5;GvhrKI1Lsj4wsxy{mu86%b=ii0pMn z9em}DsEJPOJO3BJ%bIUL6A1*Bh*XD@%HX;irJmd8FyB+68LW$|^mfW$VuS~8SYV$t zi|nbNNw&dX*Tn>fvVRu?G^733l-%s*&kPRzpSQA`E{F<&(j^ln-u zTFl|&7Hagkp?{vQ!?k`2zKEczPx7X?9iKEHv83V=pC_;ChW7vswLfR=jajYzg1dM| z0w**XiD~mOfp~FO<$*^p?rR4w*!(ff;*L^sU%hg^+arEnXhJW+d(ic<3EW8n63YHz z$0dKIHf3{;vgOf=9P%avaZX7Sh;zcYP6&%*A`l;b$=Oq^*^I6xJxVsBDaF2?863EYUD;Yv+}ah>9qpYV|BH z@i;5OZrrvOnERK#W%V2Y8vQnM?k=*!VhZ*LiSP<+E|Zg-=BWV2Oe zr>Jcrl-_nuoYaUfe9$3vSAVpm$xFuyLXThRTFNQwdNg`m5b?T6SV&Zk?CsfUNRYzsiwdKET4Uh_N z^QM_ACs^$Ap1=nDx^Th&o?IrSCAN{EC141_(>mJEY8adsTfjR>3&ul z=KO~nrX>NH;g6@Rr;?Jm_-S2^(jP20FB-ec5inF*>QGV25oo>8lOF_!FYC67#EXxd zGs(YL_-_$s1qql9>f1cQ#MRn*SZ~@v@CTqSCVi0;b4lOdIr$$0A9$w~u>u(lzi3Ba zyxZ1(wrPPyVU*iVGhLVz^J8Bm5hIkn6(VLixD`hF-QD5JUk>#Q?&2grrUH^{Gt=wwQn^22ZwI&3!H@Nfo;qunSCJgZ zK)38bWIMotw5oVahxXt2WGj_c{lQcG0UK6crb+D78N1yNbNgd>vA9#O?47$@6i*yQ za!K&$B?Jr|Z|mtopFME8XCa{lm3mJ47@_0 zv6VXe(t%8UXGdIMnv8f7;3uYnIp&1$GgLSDjTE+ASV#^ z0fC&_jjO=&KQRrM@qgnG!+u9Mpw!V_i~;)8#3Z0k9Z(wHf*(24boi(A+hURqfSb*g>nONKbGAq@XX^*-d|7u_h^s89BzJAI^9g#YZJfH=j}AjqZtQBHKn8c} zgacr2^W}TWqR$X_?a&+on`D08)RAn=eV>VM3=kY3BY@G;sv%6MiJmxe&5NBIMA`Q> z2z9)CM}NI%1LKEl_Wpcf38b>BK^+)mdJ4H__exXt{VIZZCpwv~1va$l88tmKhWe>Y z=X#rG|D$SgRw{LB6#+80-L0MW_)_ZD^ZI$NA#i?>LK$%2|36oo|GT@;!gKnyb^Ai}E%sHiBThysEp zAVL^S2u|Tu0TDugkOV{tB!mPZ1QG&$K)>(a+r7TKZugH~x7X?)ti4Y5p>|b0b@qO$ zYF~GCwqL(iWi0>z>yIBhatZ*%h5-PwPI?Xa%e5NuFmQmtPuU*=(4A@%;Dbbft&=SP z)aJ-8o|golWg?E9g#&=x+m$~E5mtE-0RA|3{D|%8SYI~Pt9_?El{5Vytx{jg<@m14 zyQGvhl|kB6jnXAJbM6=4$^L!K|44_Kzg)3V7ZY&Ek(WxCB-Mf35+R<;GzqI2^t zxbV(hZ2F?eWZA8?$-q_kr?7(UOc7gAtZ5B*FXr{j=BZ{@;H#qzh+X*N<(a>$;srCW ze1e#wLJO3eb_=Oj*d)1PP?X#-VZ#fHY2M<+F+#ei{soXtt$EMS{bkOBEf6(bG1GL= z9Z`yel(?{s1cqE_KU5-O9cbK-?I`h??WnS&08v5A)OEj@=>bfZn5m}i0O+fK{lffE z%6efYkiAVJ;*mF~gRexyE9qfn&~G1)0#al3Cjo}`ZqT9s$qOYGHRq9v1pPEX?rtU^ z_ZgJ78KQPrdiY=yh}UcB;lO`48Bzvx1`uLEtp52zqxgUF{(lfAV^!zTx+b77veA5@ z&=-3YiTuW8dM1#bU4CZ1KqJ-W7&*+G5;kCh3W(?M&&6VWa;XVq%){Ho7H8FceK<6O z_&DQm^{^K7ka@9fG-UB~JvLrX-e#Il_-R^^{eo7(FZRYgRtAF}G~T7+;axfM`JG2i zi#C>*M;!8=0O85qAv^5VkY(O!jdD2*XkK46x%h-hEh*P6)z1;2Yf5mN6bM!lTrl*y zj#(Y7{JSOOt@=@UzfQQ_`n&P2h!RSDn9JmnjnppLjgs3?72UmPG{e;|h-{MUMpgKiE#^uD6=k6BXCXbjXMBk=w))D43$`egn`D>w-ECanBR%|yu_euE%PoV4!fTYR@y~ZU zy)v$S5OA7<9BWh15Q`OaK6oZGnRGlORRq`tGm@W&SJzWhS+x7Xp?zPy&@s6ldT|D z>K5v)ReH#9R=XRo!g0u9g(_sSBH%9N6{eO}8{rPLz$S?`+wQAYDo8=0=jj!L;eIYwSD%jo)H5Vr% zp8u_=)!HM7I{aj6Gp7kqA(b9f7=?^aY_?o2akSTNS z>Ry>$GX9#pm~t?ZuO9Ctj1l{I5TK*mmRNIHwC{eAg2!YJL%yeoEfB%EuV1+$JuOff z=ucGV@fo&pnGRRv@licuK}6!@xtb)Um{k*>$p@UZHfif`-hici`6|}%>s_VF%e^TEYbW!_;f$zMMo|D*uf2 zoU-T~82nJpLG&5uTVKSfu+ctxd%nuhvbprItjCs2R|TsS8RsV!4xuIAw5oEnHy2ZG z8*vt?a&+CM*Jo$ae@kBI_sJI+i7fzbA9&>d5TDv%7Ao`->VCOvLfL*nQZ^R+rY#oXi zQ{{;ALWoMw!)7e8*&ljqT=hU>8nd%>_oIluHZi`4`}X+zyyR=-KI?|+Wm*VtdXj&H zTwDW7D7?q&`yz}e_P_b;*@y^}0^qNNXDCSAFq~H_^|NXVbDVB|&ydfqzs(p!SeKUZ zGkt%fvBk9OkRkzFv9(?M>MJY#iB^tPDS2WSe^ZiF1+}wiBaxI;=QQuheiCLyv$TKk zsMi=lSJjXm++{uGn=w?87g`2bvjPaC_22PhqWtMR)p)rBoD#D$dX4F0GI;}?h6xFv zDJ1S}^H)}{^;i}5sU(+#K+t_&ipDOUgjk+(=zl4>CgQvuF?kz`L0EjpNRNQ?59Z%IoHN0j@)0Au0?;6_ z_ z#fosoKyWP~F!d$XZvDsZgTcwW$2l0}YxiT^U8%PrLxZhb4aa1MZ`32HCkmf~A$AC$ z%lt2lr-Y@w+cRBKGGZS{hIfOk>%R%}8iLsWYo>M-_^(ZxNpWLdkE$CEZl~Bm7MGK6 zH|!1bf_R&6*6jk>_Q$&+0J1BAb{OQiPHTskIeU-3S{Ai;km{C4ji+miLf}#n6Ur8S zr^6N_+_k^@TMRUyKz0hUW3_{{-c~;ucx(|ctE(xnRv$e`5}%d;LvV*}acLkISj{A!*sajz`LTaIMt2yIPh+Xk z1-@c}Yz?b(KG6f0;$_@^XCTM2R(LSC17zPT3CaKb$VPxngm*h9@d&tU3R^FcU)w!;)Ih zDzrf}~RtwJSx@Klo_3Vb4L|A-JL6v;sus9f*!g->N zFHS%L|K(T;Ah*K~=%V|?P~Cmve%yW3RW^-Tal@#jESaziusjVy1&DG-BI135VUnG8Agub_I*VC<975}9Kf z36r(VLU?EQzpdY0W?y|(N}dV&u(!3pGRGNmhp35s-5GZ*)_9On8c@@am$Mg8=gw2>Tk1!oYq!3!-M;Kku}11I@T*+3@mx30MRKV+ANil{V2cn zx8f4Sf5l5pBGab-*>a%NG5kysr`HVJE=AS8SHpf|#2>yYeN@BG&Jeuog+|Z-HAw0YhjNX3^kSrXC)Q-%l}f0|6e;`8Qi;H+(#wCn&YZ98rZ<#|SR2z2RZ z9#7?w3w7a)(c`H!?e$`&x0E54$dbkHI@KQdpDPG9AVn-FPYsO^mb+Eim0YcIvP9Be z<#)VTizZw=FtP9Vf?*M=_nLdiDr4n!atO+r$JVK`>&2F8{Zr_|=YXob*2N#7P-7mo zGgxl94~u_F29nC{dig2;T%0MqOXB|_ZZ=P$FP-Tcp)bv0S9BDuadg*Cblju<4;_U= zj4IjvjR!&J%zK)uXpRj!uO{rbmKyQnrOd^dF0upRrD!?_`JBP! zAeV*l>xV5Eh43n(MuMZ>`F|q0$bCzSV-!{7p)Lu*kJYPd#75AaGpj(g zA!j;9hfep){Q}n}&nNn<)tchCv4^}8rgQh%fFjZwSkHsX8v19d6N$Fa8PnIs%&=KJ zk6RSdm+`*I{1!SjysW`Ku2+334>x%UvHn4%Q_^#1BRAEmXSMPa@Z4Papon-`^kAm% z>|@%`fq;?@Dr{eRb7m0plxShhq_t^jCJdI~C906|X71np@o92NN|ks|uahKc5&`O_ z@k=)srt>Ws!&Cl`-<+y!8Hx_ecfFK~4H^>dFKCuUTaSj3)2O}vi_0iBSVLObv%b?L zv8v0CnNLrau%pGXK}@ORQ^|+7s*rotqeA6>c~g(4uQajWG^VxIaLOm5AN_k1zcaNtlCSE35b?+fB@^yh76mm{3*U2b0#~EAt+zroe+e?-hbw|oPKVc= za-xdHI(K<9GmM1V4vVsX&;1e4znyA*^U&^7trd9MHQ%Xl*i1l1zOK?R%V>By*ZU$M zlNDr8zl=P$<dn#P`gDVt;`SX(=zpp1ST=-UVNefLsQ^#sB6mB4p@L)Wj zRMD+2XBxKvOWdl=Wk#9dIbSD|E+kR6R;z63@U9v|3GQED(rlKx?$*abM1dtr>EbOV zX7rsqSJ(YK3gl>^%C3OBgCa9}gP3X5k^le0e>;EN5y}0zT92I1sLt)0*mQ^Cd?v$# z5j%gq)q`Y^FwL%ZBhi9|2H8blKT*iu_KOe7&|6MQaHYjePk?l7Lut6qz_ON-{3oYC zs#lmpyW1QyoP_%mzFk2PH;K%Q{lX!xTnP}(-`$W zO@&WK0H~W!Kyd0E%06DnZ8u^s!d@^?X_1PLr*}tcX-jaA-&eSp&RXP<{&uhQ(R1WL z0h@()RW>(7Cwzz_!JG%QYyht)7ApZsv@=-4ja0%Y5!5BPkD-pU z(?AK>9k`@udpR%}PL!CCauy}l`xw|2-W6{YiYGr>YOx?SXG#AI1CY)gMTZDy7>$E2 z*dkxd`|KbA>lrON-lh|=KC2{530i<7$tzH!6PByvd~o_PA1wde1hGs7thL2r?}MvG z=})` zf-dNg-qRYA8G#Sw{PIOgRSJXx=bAxh=7GNC*AV-}&p#w00=^iie_0ATI|#*5nTyU1 znB10}sU{lhlxEMu^~J47zhF~Fi-H8#Xu?{d>Qi~^!<5#zE`D*Kz|KeeO&Y}*b9o~c zwl?<$MenKgRJiu+bfo`L=yJSxA$~yDf;{Gp#Bpe5S16gIj(#>B(?iKsnhw_i>%T9g z-t;0Go%iO1=}5h6xKBBU$TPCSGzjIX<5f9k-ef16pw3U96R2j-WHS?jtjX!eG%WwzT+XIM zb@q%g5iSUdT7gyRVxd{U(R>{NZ_1`(WLD3oY^ia37$Zk#Wb{ftoYeJ#xVgfG=|9_` zbl*bcKPL=1oQPI!B>MLBvxh_+C9&9d;Zj_qU9PYTQ99EH8EA_%jl}1VVdh1xNtN0y zxs8d==#fsd*nJ`NpV29-O;U2DWwI=yk6Szl)iv78CO1CS-$^|#>42fFsqlVC`9MHj z2Y1OyWX$-HmR20;f9)N+WqSqeBc>E7N9D0tS8_(Z13pog2?_T%LK5%nlzf;BT|2AV zLOX2Ke2R>XL!-O>dMMY%j0`p(BkBvf!aAv;n9ldJ@_glvml4j*1HuU6-s!}mY1pjM z0H*q_b#z^8`-8gx#PR}}gNjkN-#9)vL1p@hWD?=1Np6?}_!ng7?nVTEI4}4{O<2x{# znqgI8hmDjq_g-|O|K%7x#k)b~Kw$=QlkN$@xCq}4Goml?-Y~0388qprqzdf zq`=7-W>RR@8f<2lemL@39z`>v#d=@(7S`zAudj9IPMWyf#FdwlE0aRsH9nMHu|Pbw zBR{d=@|Fm<;ZwD9<~}|{Uys11vq!@*7U^|PL7PF0JJ!Ik~@J@Zf_ikN|%jFGwZY!`tc_lRrU`g-(rVe}vF55MA zS9&;i-`@5s5Is{D96eFNn zpY=PI*o=p9%}7V2#l(Z#OI62(KgYW7og{CZAdc}eadOWT2-+WIhxf*F54_hyM6|*O z+6Wh7c3|CPt~&SFF3R-y=pinaz@42L8!eV@w7{kd3(4&V)L;A9m}))HG@WD4T;bPm zWc9rmT>7{d^Bl}Iw&BVT12OCQsxVxy2`<@>dBQ!^Z@G=vaJe9spou>dw;0hVIx=Z8 zEkm`qf8aKGi1%%J4g$u=56P|6m_rCXQUUrnLLFfd{gp}FOl1siejwH1eQ_`X3OfdW z+>1O6uSjs@4?UeeG34=xZim^^mveZOX}t|)KN$`1bqseJUVopwfloFQZZUUl)5riO}ZXz=03w=D`Q}AFMOWEv<^oP(DFSop|GH{5+;C zLie{RAkFHlxtokuBxpx3%QY6??0rHq4ti;5(=8uX_gIJBAOoq_bo`vm7A-KoU)#EO zG98((u0)`6Abg7oWx^(wT7G#$H`yO;F*s7h-V#_DP9i#jz2XbFHBN3_w-pFJ&ROP; zvBplc&e?4}Y3C5ar%F#Y@ja_}nDdjK-DHX3g9eO|IZ(Bm(!+bl%qPhoTa2--M}}2bD0hldPd=S{%cDC1J-ErR6*V#49PF zQ`!|*$U^qlYs|T6q9&%(W_eMd-*TM390n46NH2SbM8x;MR%ny`L^|^fm3=0~%PM@y ztz6)e54OAg0EC4MM9pkF%3gtRvRNNdz2;5<4Btd^HsYy)6Uqmv&Zk0ebwmACmwc47 z#%x3d%Uqkq7O7iuE6Wz&;i@t%AFkJMSdi3I^{fadG2W7pgv3u?w8!LGuzv5j{HADx zbB`?gym;uwI(#Nxc@;_4=L`w%Q>~Rn-L|9jy6-l-cb&YNnF-)8m5DW81@LOCuSsx! z`F{`C2@LFP-id;3g;>H?r{2z#E-K#51v5=Vb%6=Tn8JS1;6|*(1|hQ&W;}&MH2?K8 z4r9*w^g~VgQp-YQ)fHF7jLXrlRF?BBko+zRRj=Tr)7nyJ9qR_>b2qU@ zIrVUS_x7#WyXohL;I{NM;yp(&?&hB8Y3+*6N%i+0Eq* zwnvtlOke~b`K-SON@7`1bt_Z3$=%>aC&3*l{wm){eNC})XZ%V^L!XK>!D2wi-I)QkYz)&yrp*xV&~wfF2kp^!=ecj+HC0T9Tn||ORZlS^7lBc2CuT%t%nr0IT_A<~(N(i#PuUEn zSQ~6z3E#$^81+*Czhrc7;7j!8q(t5zxr0eBeKa-48IG1*1zvpi;*fF7<9eObPstNv zl;`caO-}M_iF5hcdZBxz0^e_S6;r}lY8~skmzoU^OY#4n1{B}+^BF8uA>~#s7e~+v2Oe zF^>*rFeSyQDc77mVg?lXbc`Pf>omLx6LcJT~&XZV*0FPMc`qb)#x>1!h! zDF~!uo~qRAe7xtX3;T2bZk#4i=zl*Qu$F-&Hntvv3?Z{A_CMI)k+?pVy<|iP#h0!p zzl!a6=ft(q+e%@2%4s^I^Hj{zqc7{$h+Gf!PA7<^Tu1~o6`vEsv3?TVo3nzmfAuV< z<~}ljWY)hPX>_gAprkClR9p=t?#*Z5Dy&SJsg@+y6-gGp>XYw$+g!p?_X;yGrE`o| z?Ngq5)qTBBX3bBR?$XXMxRm6`>q9ATd^8_Sf=Y2L*9C~=B-Vo96(;Y33jzZ-#DsV} zzf`)s$Db(KG23Kiv|plOkJt)?v9TXczXR~W;r&xxxSwh|)Y;%ZUWtt|p(9pnvK0($ zX|cxh<~OOO&CVN(0¾<;WkJ}30t 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

k{eRrCXsrbpW$N~Bop!$Xzf;=yc=zsQ? zJ69|AP$2b=2_W`@h`E|(e3yD7uz=^xDW16Hd$VHwx-&fM6QpXYvE^?8q}9U@%uq|j zT#W#U`MvC5%S&o3%TyFweI;-cHadGJXQKS11e6v+5%*S2Qaio$*8tI^mY(GB^b_Sv zs!-SCGon78s$5xtwqPrO@Vl4|e5z}X zWQ{znWz0Ws=YsL}XT4HT%V7K__S=Og)QBYt_*RuQ$19|-;@AH(qBp*@4&y_v#rztF zM9W^QOzd+DqpkB@D~MV#bl{U;Bv@Ew!9t@aCc%pKQ&aUMQ*R`xdBcA0tp!`{fXpJG zq+QK6WsINIviI`j=a5(!)Gp{78t4S@-s%QFeSi6nv)=d=>5Ija+2XYLU${et8B%#DP&=#j_&ci zX2TP3NE@|Yomi;eZ>Cz|oGAgC>)hJ*j7M30>*HGT) z+-NfatYw!sxj7qJ6-<3RdrTzir{kw{;{S@2-#xVR0& zs`C~-`D!#9zk5!t>L}K|)GxumdOwDwIB7jI1JI*kVH-B^rD*tyoldSi;sx}#y;(7b zV9bgA+Aw z9Z9owgl|*5+IMv-VhEJw(c3^WX762KBJPgKrb!qwtpr8PaaDA)d(aW zb=$n*oIIEfYXLLnn>O?H8lZlq-Inj^_FJ-9KdL1<0P=+E5*xSy^RU&zVZDj8$KW|v)NTiWa0gz!q zmg-??e*@+WZU2WXjlTl1SZSHMd0@l5#vUtY4g^>GKviSl z`DK7Vz+OTe{rB<&Kqex;J{2$5k{MfzfN&aD!P6V z0XloNMjn5~?U|(6M!p*%Vc&P8CUD%}e5>FjkZIJrH(osLdP!q(cmzcJ2{RcKhxj`383W=|tKi}G1Y3d-mKDamLF-0U<|*_1e0a^^Tq zqXhN*rYM*+dm_}{$0{gIifw>XJ8=HRv@U^lr~XHU$PdU zU|L?fyMl+%TnUQ6DJm{dg@O)8Bht+}?pg}`O zQL=C$uQVZ*?r@o%9Ox4;xAPkI4`y+yxLKdq%8~Wlrs1B)cH|UvTu;kkNOjPHRgqhg zR-0_}E-e+;mZK@c2MXAYwl6Wt(rM;#u6gNgN9&Iy^+?d<@jr_bc1NZ|roN6#50Z!O0Sn7SeQTUm)-{t&WLG_HJyTth&Yk%G11=I47ytkO literal 0 HcmV?d00001 diff --git a/doc/plink-api.md b/doc/plink-api.md new file mode 100644 index 0000000..ca09d4e --- /dev/null +++ b/doc/plink-api.md @@ -0,0 +1,1141 @@ +--- + +title: 视语融合Process Linker软件用户手册 +author: 楼展 +creator: 平头哥半导体有限公司 +subject: API document +keywords: [Light, API, Process Linker] +--- + + + + + +# 视语融合Process Linker软件用户手册 + +版本 0.1.1 + +发布日期 2022-5-10 + +

+ +**Copyright © 2021-2022 T-HEAD Semiconductor Co.,Ltd. All rights reserved.** + +This document is the property of T-HEAD Semiconductor Co.,Ltd. This document may only be distributed to: (i) a T-HEAD party having a legitimate business need for the information contained herein, or (ii) a non-T-HEAD party having a legitimate business need for the information contained herein. No license, expressed or implied, under any patent, copyright or trade secret right is granted or implied by the conveyance of this document. No part of this document may be reproduced, transmitted, transcribed, stored in a retrieval system, translated into any language or computer language, in any form or by any means, electronic, mechanical, magnetic, optical, chemical, manual, or otherwise without the prior written permission of T-HEAD Semiconductor Co.,Ltd. + +**Trademarks and Permissions** + +The T-HEAD Logo and all other trademarks indicated as such herein are trademarks of T-HEAD Semiconductor Co.,Ltd. All other products or service names are the property of their respective owners. + +**Notice** + +The purchased products, services and features are stipulated by the contract made between T-HEAD and the customer. All or part of the products, services and features described in this document may not be within the purchase scope or the usage scope. Unless otherwise specified in the contract, all statements, information, and recommendations in this document are provided "AS IS" without warranties, guarantees or representations of any kind, either express or implied. + +The information in this document is subject to change without notice. Every effort has been made in the preparation of this document to ensure accuracy of the contents, but all statements, information, and recommendations in this document do not constitute a warranty of any kind, express or implied. + +**Copyright © 2021-2022 平头哥半导体有限公司,保留所有权利.** + +本文档的所有权及知识产权归属于平头哥半导体有限公司及其关联公司(下称“平头哥”)。本文档仅能分派给:(i)拥有合法雇佣关系,并需要本文档的信息的平头哥员工,或(ii)非平头哥组织但拥有合法合作关系,并且其需要本文档的信息的合作方。对于本文档,未经平头哥半导体有限公司明示同意,则不能使用该文档。在未经平头哥半导体有限公司的书面许可的情形下,不得复制本文档的任何部分,传播、转录、储存在检索系统中或翻译成任何语言或计算机语言。 + +**商标申明** + +平头哥的LOGO和其它所有商标归平头哥半导体有限公司及其关联公司所有,未经平头哥半导体有限公司的书面同意,任何法律实体不得使用平头哥的商标或者商业标识。 + +**注意** + +您购买的产品、服务或特性等应受平头哥商业合同和条款的约束,本文档中描述的全部或部分产品、服务或特性可能不在您的购买或使用范围之内。除非合同另有约定,平头哥对本文档内容不做任何明示或默示的声明或保证。 + +由于产品版本升级或其他原因,本文档内容会不定期进行更新。除非另有约定,本文档仅作为使用指导,本文档中的所有陈述、信息和建议不构成任何明示或暗示的担保。平头哥半导体有限公司不对任何第三方使用本文档产生的损失承担任何法律责任。 + +**平头哥半导体有限公司 T-HEAD Semiconductor Co.,LTD** + +地址: 杭州市西湖区西斗门路3号天堂软件园A座15楼 邮编: 310012 +网址: www.t-head.cn + +
+ +# 修订历史 + +| 版本号 | 版本说明 | 修订人 | 日期 | +| ------ | ----------------------------- | ---- | --------- | +| 0.0.1 | 初始版本 | 楼展 | 2021-9-15 | +| 0.1.1 | 添加Bayer RAW相关数据类型
添加connect和recv超时接口:PLINK_recv_ex(),PLINK_connect_ex() | 楼展 | 2022-5-10 | +| | | | | + +
+ +# 目录 + +[TOC] + +
+ +# 概述 + +本文档对视语融合项目Process Linker模块做详细介绍和说明。文档内容包括系统架构简述,Process Linker功能描述,编程指南以及接口函数、数据结构和枚举的详细介绍。 + +
+ +# 约定 + +| 格式 | 说明 | +| ------------- | -------------------------- | +| 黑体 | 正文采用黑体表示 | +| **粗体** | | +| *斜体* | | +| 下划线 | | +| `Source Code` | 正文中的代码用等宽字体表示 | +| “双引号” | | + +
+ +# 缩略语及术语 + +| 缩略语/术语 | 全名/释义 | +| ----------- | --------------------------------- | +| **API** | Application Programming Interface | +| **plink** | Process Linker | +| | | +| | | + +
+ +# 参考文档 + +N/A + +
+ +# 1 系统架构 + +视语融合项目各IP底软目前分别提供测试应用。这些测试应用在作为独立进程运行。为实现各测试应用的互联互通,从而进行各种组合的链路测试及demo,测试应用可使用Process Linker库。Process Linker可被集成到任意user mode应用或库中,多个模块进程间的连接通信,包括buffer共享,dma-buf file descriptor传递,信息同步等。 + + + +
Figure 1: Process Linker Software Stack
+ +# 2 功能描述 + +## 2.1 连接方式 + +Process Linker的基于socket接口实现,在进程间提供**串行的**、可靠的、基于连接的**双向传输**通道。 + +Process Linker可创建为两种实例:Server和Client。Server需先于Client创建并等待连接请求,且允许接收多个连接请求(如下图所示);Client使用与Server相同的文件名创建并发送连接请求。一旦建立连接通道,进程间可开始数据包发送接收,且所有数据包按发送顺序到达。 + + + +
Figure 2: Process Linker Connection Examples
+ +## 2.2 传输协议 + +Process Linker传输的基本数据结构定义为[PlinkPacket](#PlinkPacket)。该数据结构包含描述一个buffer所需的信息,包括dma-buf file descriptor以及描述该buffer的数据结构list。该列表可包含以下数据结构实例: + +- [PlinkBufferInfo](#PlinkBufferInfo):描述1D buffer地址,数据的offset和size +- [PlinkYuvInfo](#PlinkYuvInfo):描述YUV buffer的color format、地址、图像宽高、数据的offset和stride等 +- [PlinkRGBInfo](#PlinkRGBInfo):描述RGB buffer的color format、地址、图像宽高、数据的offset和stride等 +- [PlinkRawInfo](#PlinkRawInfo):描述Bayer raw buffer的color format、地址、图像宽高,数据的offset和stride等 +- [PlinkFaceInfo](#PlinkFaceInfo):描述NPU人脸/物体识别的结果 +- [PlinkMsg](#PlinkMsg):包含进程间需要传递的同步信号或指令等 +- 更多……可根据需要添加扩展 + +以上数据结构的基本形式为[PlinkDescHdr](#PlinkDescHdr) + 描述内容。 + + + +以YUV buffer传递为例,传输及同步的协议定义如下: + +Src: + +1) 连接成功后,根据需要发送的YUV buffer创建PlinkPacket实例**sendpkt**,并设置fd和[PlinkDescriptor](PlinkDescriptor) list。list应至少包含结构体[PlinkYuvInfo](#PlinkYuvInfo),并填写相应的buffer的id、图像color format、宽高、offset和stride。id应为区别于其他buffer的唯一标识符。 +2) 调用[PLINK_send](#PLINK_send)()发送**sendpkt** +3) 重复1~2发送所有可用的YUV buffer。 +4) 当没有更多的YUV buffer可用时,通过调用[PLINK_wait](#PLINK_wait)()及[PLINK_recv](#PLINK_recv)()等待接收来自sink的[PlinkMsg](#PlinkMsg)数据包。 + 4.1 如果PlinkMsg.msg为有效的buffer id,代表sink已使用完毕并释放该buffer,src可继续使用此buffer并发送。 + 4.2 如果PlinkMsg.msg为PLINK_EXIT_CODE (-1),src可在适当时间断开连接。 +5) 当所有数据处理完毕后需主动断开连接时,应发送PlinkMsg通知sink,并且PlinkMsg.msg设置为PLINK_EXIT_CODE (-1)。最后断开连接。 + +Sink: + +1) 连接成功后,通过调用[PLINK_recv](PLINK_recv)()等待来自src的数据包。 + 1.1 如果收到的数据包为[PlinkMsg](#PlinkMsg),且PlinkMsg.msg为PLINK_EXIT_CODE (-1),sink在处理完所有buffer并返回后,可在适当时间断开连接。 + 1.2 如果收到的数据包为[PlinkYuvInfo](#PlinkYuvInfo),解析结构体,并通过PlinkDescriptor.fd获取buffer并使用。使用完毕后应发送[PlinkMsg](#PlinkMsg),且PlinkMsg.msg设置为相应的buffer id。 +2) 当所有数据处理完毕后需主动断开连接时,应发送[PlinkMsg](#PlinkMsg)通知src,并且PlinkMsg.msg设置为PLINK_EXIT_CODE (-1)。最后断开连接。 + +
+ +# 3 编程指南 + +Process Linker提供以下两个头文件,定义接口函数及数据。 + +- **process_linker.h**:包含接口函数定义及相应的主要结构体/枚举定义。 +- **process_linker_types.h**: 包含各种预定义的结构体/枚举用来描述buffer或数据。可根据需要在此添加扩展所需的数据描述。也可以将所需的扩展定义在独立头文件中,并同时在server和client端引用。 + +使用Process Linker的程序在链接时应添加**libplink.so**。 + +Process Linker同时提供两个sample application,**plinkserver**和**plinkclient**,分别用于server和client的编程参考和连接测试。 + +通常,Process Linker应被集成到应用程序。例如,一个测试应用程序通过读文件获取YUV数据并写入一块buffer作为IP模块的输入。这里可以用Process Linker获取输入buffer,从而代替读文件和对本地输入buffer的操作。 + +## 3.1 Debug信息打印 + +可通过设置环境变量PLINK_LOG_LEVEL来控制信息打印级别。以下是不同打印级别的含义描述。 + +| 级别 | 名称 | 描述 | +| ---- | ------- | ------------------------------------------------------------ | +| 0 | QUIET | 不打印任何debug信息。 | +| 1 | ERROR | 打印可能导致程序中断的严重错误信息。 | +| 2 | WARNING | 打印所有警告。警告并不会导致程序中断,但可能引起不可预期的错误。 | +| 3 | INFO | 打印所有的模块行为信息。 | +| 4 | DEBUG | 打印所有调试信息。 | +| 5 | TRACE | 打印所有跟踪调试信息。 | + +PLINK_LOG_LEVEL设置为高级别时,所有低于该级别的信息都将被打印到屏幕。例如,设置以下环境变量后,Process Linker将打印所有ERROR, WARNING和INFO信息。默认情况下,只打印ERROR信息。 + +```bash +export PLINK_LOG_LEVEL=3 +``` + +
+ +# 4 接口函数 + + + +| 函数名 | 说明 | +| ------------------------------------- | ------------------------------------------------- | +| [PLINK_create](#PLINK_create) | 创建Process Linker实例 | +| [PLINK_connect](#PLINK_connect) | 建立连接 | +| [PLINK_connect_ex](#PLINK_connect_ex) | 建立连接(带超时) | +| [PLINK_send](#PLINK_send) | 向指定连接发送一个PlinkPacket | +| [PLINK_wait](#PLINK_wait) | 从指定连接等待数据到达 | +| [PLINK_recv](#PLINK_recv) | 从指定连接等待数据并接收一个PlinkPacket | +| [PLINK_recv_ex](#PLINK_recv_ex) | 从指定连接等待数据并接收一个PlinkPacket(带超时) | +| [PLINK_close](#PLINK_close) | 断开连接并销毁Process Linker实例 | + +## PLINK_create + +**语法** + +```c +PlinkStatus PLINK_create( + PlinkHandle *plink, + const char *name, + PlinkMode mode); +``` + +**描述** + +创建一个Process Linker实例并初始化。执行成功后,将返回一个指向该实例的指针。 + +实例可创建为server或client,通过`mode`参数设置。 + +参数`name`指定实例名,必须为文件路径。server实例将创建一个相应的文件。只有相同名称server和client才能建立连接。 + +**参数说明** + +| 成员名称 | 描述 | +| -------- | ----------------------------------------------------------- | +| plink | 指向Process Linker实例指针 | +| name | 实例名,必须时文件路径 | +| mode | 指定Process Linker实例工作模式。参考[PlinkMode](#PlinkMode) | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 创建实例并初始化成功 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_NO_MEMORY | 内存不足 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +## PLINK_connect + +**语法** + +```c +PlinkStatus PLINK_connect( + PlinkHandle plink, + PlinkChannelID *channel); +``` + +**描述** + +创建连接。server实例将等待来自client的连接请求,并在连接成功后返回channel ID `channel`。client实例将连接同名的server。 + +**参数说明** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------------ | +| plink | Process Linker实例指针 | +| channel | 指向连接的channel ID。仅对server实例有效,client应设置为NULL。 | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 连接成功 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +## PLINK_connect_ex + +**语法** + +```c +PlinkStatus PLINK_connect_ex( + PlinkHandle plink, + PlinkChannelID *channel, + int timeout_ms); +``` + +**描述** + +创建连接。server实例将等待来自client的连接请求,并在连接成功后返回channel ID `channel`。client实例将连接同名的server。若等待超时,则返回PLINK_STATUS_TIMEOUT。 + +**参数说明** + +| 成员名称 | 描述 | +| ---------- | ------------------------------------------------------------ | +| plink | Process Linker实例指针 | +| channel | 指向连接的channel ID。仅对server实例有效,client应设置为NULL。 | +| timeout_ms | 等待连接超时,单位毫秒 | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 连接成功 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | +| PLINK_STATUS_TIMEOUT | 等待超时 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +## PLINK_send + +**语法** + +```c +PlinkStatus PLINK_send( + PlinkHandle plink, + PlinkChannelID channel, + PlinkPacket *pkt); +``` + +**描述** + +发送一个[PlinkPacket](#PlinkPacket)到指定channel。该接口成功返回只保证packet已成功发送到channel,但不代表packet已被对方收到。 + +**参数说明** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------- | +| plink | Process Linker实例指针 | +| channel | 连接的channel ID。仅对server实例有效,client应设置为0。 | +| pkt | 指向要发送的[PlinkPacket](#PlinkPacket) | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 接收数据成功 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +## PLINK_wait + +**语法** + +```c +PlinkStatus PLINK_wait( + PlinkHandle plink, + PlinkChannelID channel, + int timeout_ms); +``` + +**描述** + +等待数据到达。可通过`timeout_ms`参数指定等待超时时间。如设置`timeout_ms`为0,该接口在检查是否有数据后立即返回。 + +**参数说明** + +| 成员名称 | 描述 | +| ---------- | ------------------------------------------------------- | +| plink | Process Linker实例指针 | +| channel | 连接的channel ID。仅对server实例有效,client应设置为0。 | +| timeout_ms | 等待时间,单位毫秒 | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | -------------------------- | +| PLINK_STATUS_OK | 检测到数据到达 | +| PLINK_STATUS_TIMEOUT | 等待超时,没有可接收的数据 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +## PLINK_recv + +**语法** + +```c +PlinkStatus PLINK_recv( + PlinkHandle plink, + PlinkChannelID channel, + PlinkPacket *pkt); +``` + +**描述** + +从指定channel接收一个[PlinkPacket](#PlinkPacket)。接收到的数据被存放在实例内部buffer。当内部buffer不足以接收整个PlinkPacket的数据时,函数返回PLINK_STATUS_MORE_DATA。这时应继续调用此函数来接收剩余数据。 + +**参数说明** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------- | +| plink | Process Linker实例指针 | +| channel | 连接的channel ID。仅对server实例有效,client应设置为0。 | +| pkt | 指向接收到的[PlinkPacket](#PlinkPacket) | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 接收数据成功 | +| PLINK_STATUS_MORE_DATA | 有更多可接收数据 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +**注意** + +plink实例只保留最近一次PLINK_recv接收到的数据。因此,需及时使用或备份PlinkPacket内的数据。 + +## PLINK_recv_ex + +**语法** + +```c +PlinkStatus PLINK_recv_ex( + PlinkHandle plink, + PlinkChannelID channel, + PlinkPacket *pkt, + int timeout_ms); +``` + +**描述** + +从指定channel接收一个[PlinkPacket](#PlinkPacket)。接收到的数据被存放在实例内部buffer。当内部buffer不足以接收整个PlinkPacket的数据时,函数返回PLINK_STATUS_MORE_DATA。这时应继续调用此函数来接收剩余数据。若等待超时,则返回PLINK_STATUS_TIMEOUT。 + +**参数说明** + +| 成员名称 | 描述 | +| ---------- | ------------------------------------------------------- | +| plink | Process Linker实例指针 | +| channel | 连接的channel ID。仅对server实例有效,client应设置为0。 | +| pkt | 指向接收到的[PlinkPacket](#PlinkPacket) | +| timeout_ms | 等待接收数据超时,单位毫秒 | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 接收数据成功 | +| PLINK_STATUS_MORE_DATA | 有更多可接收数据 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | +| PLINK_STATUS_TIMEOUT | 等待超时 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +**注意** + +plink实例只保留最近一次PLINK_recv接收到的数据。因此,需及时使用或备份PlinkPacket内的数据。 + +## PLINK_close + +**语法** + +```c +PlinkStatus PLINK_close( + PlinkHandle plink, + PlinkChannelID channel); +``` + +**描述** + +关闭`channel`指定的连接。实例将在最后一个连接关闭后被销毁。对于server实例,若指定`channel`为`PLINK_CLOSE_ALL`,则关闭所有有效连接。 + +**参数说明** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------- | +| plink | Process Linker实例指针 | +| channel | 连接的channel ID。仅对server实例有效,client应设置为0。 | + +**返回值** + +| 返回值 | 描述 | +| ------------------------- | ---------------------- | +| PLINK_STATUS_OK | 执行成功 | +| PLINK_STATUS_WRONG_PARAMS | 参数错误 | +| PLINK_STATUS_ERROR | 其他导致程序中断的错误 | + +**依赖** + +头文件:process_linker.h + +库文件:libplink.so + +
+ +# 5 数据结构 + +## PlinkPacket + +**定义** + +```c +typedef struct _PlinkPacket +{ + int fd; /* file descriptor. If PLINK_INVALID_FD, it's invalid */ + unsigned int timestamp; /* timestamp of this packet, the time for rendering */ + int num; /* number of valid data descriptor entries in list[] */ + PlinkDescriptor *list[PLINK_MAX_DATA_DESCS]; /* list of pointers which point to data descriptor. */ +} PlinkPacket; +``` + +**描述** + +Process Linker发送接收的基本数据单位。包含dma-buf `fd`,`timestamp`和一个数据描述结构体列表。列表的最大元素个数为`PLINK_MAX_DATA_DESCS`。有效元素的个数由`num`指定。每个元素指向一个数据描述结构体实例,如[PlinkBufferInfo](#PlinkBufferInfo)、[PlinkYuvInfo](#PlinkYuvInfo)、[PlinkMsg](#PlinkMsg)等。 + +**成员** + +| 成员名称 | 描述 | +| --------- | ---------------------------------- | +| fd | 发送/接收buffer的dma-buf fd | +| timestamp | packet时间戳 | +| num | list中有效数据描述结构体实例个数 | +| list | 指向数据描述符结构体实例的指针列表 | + +**依赖** + +头文件:process_linker.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkDescHdr + +**定义** + +```c +typedef struct _PlinkDescHdr +{ + unsigned int size; /* data size, excluding this header */ + int type; /* type of this data descriptor */ + int id; /* buffer id if it's buffer descriptor. Only values greater than 0 are valid */ +} PlinkDescHdr; +``` + +**描述** + +定义数据描述结构体的头数据,包含描述数据大小(单位字节),类型及相应buffer的ID。 + +**成员** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------ | +| size | 数据描述结构体内描述数据的大小(不包含此头结构体大小) | +| type | 描述结构体类型,参考[PlinkDescType](#PlinkDescType) | +| id | 对应buffer的ID。有效的ID应大于0。 | + +**依赖** + +头文件:process_linker.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkBufferInfo + +**定义** + +```c +typedef struct _PlinkBufferInfo +{ + PlinkDescHdr header; + unsigned long long bus_address; + unsigned int offset; + unsigned int size; +} PlinkBufferInfo; +``` + +**描述** + +描述一个一维线性buffer。`header.type`应为`PLINK_TYPE_1D_BUFFER`。 + +**成员** + +| 成员名称 | 描述 | +| ----------- | ------------------------------------ | +| header | 数据描述结构体头 | +| bus_address | buffer的物理地址 | +| offset | 有效数据在buffer中的偏移,单位:字节 | +| size | 有效数据大小,单位:字节 | + +**依赖** + +头文件:process_linker_types.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkYuvInfo + +**定义** + +```c +typedef struct _PlinkYuvInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + unsigned long long bus_address_y; + unsigned long long bus_address_u; + unsigned long long bus_address_v; + unsigned int offset_y; + unsigned int offset_u; + unsigned int offset_v; + unsigned int pic_width; + unsigned int pic_height; + unsigned int stride_y; + unsigned int stride_u; + unsigned int stride_v; +} PlinkYuvInfo; +``` + +**描述** + +描述一个二维YUV图像buffer。`header.type`应为`PLINK_TYPE_2D_YUV`。 + +**成员** + +| 成员名称 | 描述 | +| --------------------------------------------------- | ------------------------------------------------------ | +| header | 数据描述结构体头 | +| format | YUV数据格式,参考[PlinkColorFormat](#PlinkColorFormat) | +| bus_address_y
bus_address_u
bus_address_v | YUV各通道的物理地址 | +| offset_y
offset_u
offset_v | 各通道有效数据在基于相应起始地址的偏移,单位:字节 | +| pic_width | 图像宽度,单位:像素 | +| pic_height | 图像高度,单位:像素 | +| stride_y
stride_u
stride_v | 各通道buffer stride,单位:字节 | + +**依赖** + +头文件:process_linker_types.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkRGBInfo + +**定义** + +```c +typedef struct _PlinkRGBInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + unsigned long long bus_address_r; + unsigned long long bus_address_g; + unsigned long long bus_address_b; + unsigned long long bus_address_a; + unsigned int offset_r; + unsigned int offset_g; + unsigned int offset_b; + unsigned int offset_a; + unsigned int img_width; + unsigned int img_height; + unsigned int stride_r; + unsigned int stride_g; + unsigned int stride_b; + unsigned int stride_a; +} PlinkRGBInfo; +``` + +**描述** + +描述一个二维RGB图像buffer。`header.type`应为`PLINK_TYPE_2D_RGB`。 + +**成员** + +| 成员名称 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------- | +| header | 数据描述结构体头 | +| format | RGBA数据格式,参考[PlinkColorFormat](#PlinkColorFormat) | +| bus_address_r
bus_address_g
bus_address_b
bus_address_a | RGBA各通道的物理地址 | +| offset_r
offset_g
offset_b
offset_a | 各通道有效数据在基于相应起始地址的偏移,单位:字节 | +| img_width | 图像宽度,单位:像素 | +| img_height | 图像高度,单位:像素 | +| stride_r
stride_g
stride_b
stride_a | 各通道buffer stride,单位:字节 | + +**依赖** + +头文件:process_linker_types.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkRawInfo + +**定义** + +```c +typedef struct _PlinkRawInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + PlinkBayerPattern pattern; + unsigned long long bus_address; + unsigned int offset; + unsigned int img_width; + unsigned int img_height; + unsigned int stride; +} PlinkRawInfo; +``` + +**描述** + +描述一个二维Bayer raw图像buffer。`header.type`应为`PLINK_TYPE_2D_RAW`。 + +**成员** + +| 成员名称 | 描述 | +| ----------- | ------------------------------------------------------ | +| header | 数据描述结构体头 | +| format | RAW数据格式,参考[PlinkColorFormat](#PlinkColorFormat) | +| bus_address | buffer物理地址 | +| offset | 有效数据基于起始地址的偏移,单位:字节 | +| img_width | 图像宽度,单位:像素 | +| img_height | 图像高度,单位:像素 | +| stride | buffer stride,单位:字节 | + +**依赖** + +头文件:process_linker_types.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +## PlinkMsg + +**定义** + +```c +typedef struct _PlinkMsg +{ + PlinkDescHdr header; + int msg; /* When greater than 0, it means the id of buffer which can be released */ + /* When set to 0, it means a buffer can be released, but id is unknown */ + /* When set to PLINK_EXIT_CODE, it means to close connection */ + /* Other values are reserved */ +} PlinkMsg; +``` + +**描述** + +描述传输message内容。message可包括buffer id,exit code等。`header.type`应为`PLINK_TYPE_MESSAGE`。 + +**成员** + +| 成员名称 | 描述 | +| -------- | ------------------------------------------------------------ | +| header | 数据描述结构体头 | +| msg | 传输的message。
当大于0时,表示一个可归还的buffer的id
当等于0时,表示归还一个buffer,但buffer id未知
当等于PLINK_EXIT_CODE时,表示连接可断开
其他为保留值 | + +**依赖** + +头文件:process_linker_types.h + +**注意** + +无 + +**相关接口函数或结构体** + +[PLINK_send](#PLINK_send) + +[PLINK_recv](#PLINK_recv) + +[PlinkBufferInfo](#PlinkBufferInfo) + +[PlinkYuvInfo](#PlinkYuvInfo) + +[PlinkRGBInfo](#PlinkRGBInfo) + +[PlinkObjectInfo](#PlinkObjectInfo) + +[PlinkMsg](#PlinkMsg) + +
+ +# 6 枚举 + +## PlinkStatus + +**定义** + +```c +typedef enum _PlinkStatus +{ + PLINK_STATUS_OK = 0, + PLINK_STATUS_MORE_DATA = 1, /* have more data to parse in the receive buffer */ + PLINK_STATUS_TIMEOUT = 2, /* wait timeout, which means no data received within the time */ + PLINK_STATUS_ERROR = -1, /* general error */ + PLINK_STATUS_WRONG_PARAMS = -2, /* wrong parameters */ + PLINK_STATUS_NO_MEMORY = -3, /* not enough memory */ +} PlinkStatus; +``` + +**描述** + +接口函数返回的状态码。 + +**成员** + +| 成员名称 | 描述 | +| ------------------------- | ---------------------------------------- | +| PLINK_STATUS_OK | 函数执行成功,并无任何异常 | +| PLINK_STATUS_MORE_DATA | 函数执行过程中重现警告:有更多数据可接收 | +| PLINK_STATUS_TIMEOUT | 函数执行过程中重现警告:等到数据到达超时 | +| PLINK_STATUS_ERROR | 函数执行错误 | +| PLINK_STATUS_WRONG_PARAMS | 函数执行错误:参数错误 | +| PLINK_STATUS_NO_MEMORY | 函数执行错误:内存不足 | + +**需求** + +头文件:process_linker.h + +**注意** + +无 + +## PlinkMode + +**定义** + +```c +typedef enum _PlinkMode +{ + PLINK_MODE_SERVER = 0, /* run plink as server; server should be launched before client */ + PLINK_MODE_CLIENT, /* run plink as client which can connect to server */ + PLINK_MODE_MAX +} PlinkMode; +``` + +**描述** + +plink运行模式。在实例创建时作为[PLINK_create](#PLINK_create)输入参数。 + +**成员** + +| 成员名称 | 描述 | +| ----------------- | ------------------------------------------ | +| PLINK_MODE_SERVER | server模式,需先于client创建 | +| PLINK_MODE_CLIENT | client模式,需已创建相应的plink server实例 | + +**需求** + +头文件:process_linker.h + +**注意** + +无 + +## PlinkDescType + +**定义** + +```c +typedef enum _PlinkDescType +{ + PLINK_TYPE_1D_BUFFER = 0, /* PlinkBufferInfo */ + PLINK_TYPE_2D_YUV, /* PlinkYuvInfo */ + PLINK_TYPE_2D_RGB, /* PlinkRGBInfo */ + PLINK_TYPE_OBJECT, /* PlinkObjectInfo */ + PLINK_TYPE_MESSAGE, /* PlinkMsg */ + PLINK_TYPE_2D_RAW, /* PlinkRawInfo */ + PLINK_TYPE_MAX +} PlinkDescType; +``` + +**描述** + +数据描述结构体类型。 + +**成员** + +| 成员名称 | 描述 | +| -------------------- | ---------------------------------------------------------- | +| PLINK_TYPE_1D_BUFFER | 一维buffer,对应[PlinkBufferInfo](#PlinkBufferInfo) | +| PLINK_TYPE_2D_YUV | 二维YUV图像buffer,对应[PlinkYuvInfo](#PlinkYuvInfo) | +| PLINK_TYPE_2D_RGB | 二维RGB图像buffer,对应[PlinkRGBInfo](#PlinkRGBInfo) | +| PLINK_TYPE_OBJECT | 物体检测结果,对应[PlinkObjectInfo](#PlinkObjectInfo) | +| PLINK_TYPE_MESSAGE | message,对应[PlinkMsg](#PlinkMsg) | +| PLINK_TYPE_2D_RAW | 二维Bayer raw图像buffer,对应[PlinkRawInfo](#PlinkRawInfo) | + +**需求** + +头文件:process_linker_types.h + +**注意** + +无 + +## PlinkColorFormat + +**定义** + +```c +typedef enum _PlinkColorFormat +{ + PLINK_COLOR_FormatUnused, + PLINK_COLOR_FormatMonochrome, + PLINK_COLOR_FormatYUV420Planar, + PLINK_COLOR_FormatYUV420SemiPlanar, + PLINK_COLOR_FormatYUV420SemiPlanarP010, + PLINK_COLOR_FormatYUV422Planar, + PLINK_COLOR_FormatYUV422SemiPlanar, + PLINK_COLOR_Format32bitBGRA8888, + PLINK_COLOR_Format32bitARGB8888, + PLINK_COLOR_Format24BitRGB888, + PLINK_COLOR_Format24BitRGB888Planar, + PLINK_COLOR_Format24BitBGR888, + PLINK_COLOR_Format24BitBGR888Planar, + PLINK_COLOR_FormatRawBayer8bit, + PLINK_COLOR_FormatRawBayer10bit, + PLINK_COLOR_FormatRawBayer12bit, + PLINK_COLOR_FormatMax +} PlinkColorFormat; +``` + +**描述** + +YUV或RGB色彩空间数据格式。 + +**成员** + +| 成员名称 | 描述 | +| -------------------------------------- | ------------------------------- | +| PLINK_COLOR_FormatUnused | 无效格式 | +| PLINK_COLOR_FormatMonochrome | 8-bit monochrome | +| PLINK_COLOR_FormatYUV420Planar | 8-bit I420 | +| PLINK_COLOR_FormatYUV420SemiPlanar | 8-bit NV12 | +| PLINK_COLOR_FormatYUV420SemiPlanarP010 | 10-bit P010 | +| PLINK_COLOR_FormatYUV422Planar | 8-bit YUV422 planar | +| PLINK_COLOR_FormatYUV422SemiPlanar | 8-bit YUV422 semi-planar (NV16) | +| PLINK_COLOR_Format32bitBGRA8888 | 32-bit BGRA 8:8:8:8 interleaved | +| PLINK_COLOR_Format32bitARGB8888 | 32-bit ARGB 8:8:8:8 interleaved | +| PLINK_COLOR_Format24BitRGB888 | 24-bit RGB 8:8:8 interleaved | +| PLINK_COLOR_Format24BitRGB888Planar | 24-bit RGB 8:8:8 planar | +| PLINK_COLOR_Format24BitBGR888 | 24-bit BGR 8:8:8 interleaved | +| PLINK_COLOR_Format24BitBGR888Planar | 24-bit BGR 8:8:8 planar | +| PLINK_COLOR_FormatRawBayer8bit | 8-bit raw | +| PLINK_COLOR_FormatRawBayer10bit | 10-bit raw | +| PLINK_COLOR_FormatRawBayer12bit | 12-bit raw | + +**需求** + +头文件:process_linker_types.h + +**注意** + +无 + +## PlinkBayerPattern + +**定义** + +```c +typedef enum _PlinkBayerPattern +{ + PLINK_BAYER_PATTERN_RGGB, + PLINK_BAYER_PATTERN_BGGR, + PLINK_BAYER_PATTERN_GRBG, + PLINK_BAYER_PATTERN_GBRG, + PLINK_BAYER_PATTERN_MAX +} PlinkBayerPattern; +``` + +**描述** + +Bayer raw格式pattern。 + +**成员** + +| 成员名称 | 描述 | +| ------------------------ | ---------- | +| PLINK_BAYER_PATTERN_RGGB | RG
GB | +| PLINK_BAYER_PATTERN_BGGR | BG
GR | +| PLINK_BAYER_PATTERN_GRBG | GR
BG | +| PLINK_BAYER_PATTERN_GBRG | GB
RG | + +**需求** + +头文件:process_linker_types.h + +**注意** + +无 + +
+ +# 7 附录 + +TBD + +
+ + + + + diff --git a/inc/process_linker.h b/inc/process_linker.h new file mode 100644 index 0000000..e29baee --- /dev/null +++ b/inc/process_linker.h @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef _PROCESS_LINKER_H_ +#define _PROCESS_LINKER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define PLINK_VERSION_MAJOR 0 +#define PLINK_VERSION_MINOR 1 +#define PLINK_VERSION_REVISION 1 + +/* Maximum data descriptors in one packet */ +#define PLINK_MAX_DATA_DESCS 10 + +/* Close all the connections from client. */ +/* Can be used as the second parameter of PLINK_close when the instance is created as SERVER */ +#define PLINK_CLOSE_ALL -1 + +/* invalid file descriptor */ +#define PLINK_INVALID_FD -1 + +#define DATA_HEADER_SIZE (sizeof(PlinkDescHdr)) +#define DATA_SIZE(type) (sizeof(type) - DATA_HEADER_SIZE) + +typedef void *PlinkHandle; +typedef void PlinkDescriptor; +typedef int PlinkChannelID; + +typedef enum _PlinkStatus +{ + PLINK_STATUS_OK = 0, + PLINK_STATUS_MORE_DATA = 1, /* have more data to parse in the receive buffer */ + PLINK_STATUS_TIMEOUT = 2, /* wait timeout, which means no data received within the time */ + PLINK_STATUS_NO_DATA = 3, /* no data recieved */ + PLINK_STATUS_ERROR = -1, /* general error */ + PLINK_STATUS_WRONG_PARAMS = -2, /* wrong parameters */ + PLINK_STATUS_NO_MEMORY = -3, /* not enough memory */ +} PlinkStatus; + +/* plink mode */ +typedef enum _PlinkMode +{ + PLINK_MODE_SERVER = 0, /* run plink as server; server should be launched before client */ + PLINK_MODE_CLIENT, /* run plink as client which can connect to server */ + PLINK_MODE_MAX +} PlinkMode; + +typedef union _PlinkVersion +{ + struct process_linker + { + unsigned char major; + unsigned char minor; + unsigned char revision; + unsigned char step; + } v; + unsigned int version; +} PlinkVersion; + +typedef struct _PlinkDescHdr +{ + unsigned int size; /* data size, excluding this header */ + int type; /* type of this data descriptor */ + int id; /* buffer id if it's buffer descriptor. Only values greater than 0 are valid */ +} PlinkDescHdr; + +/* data packet can be sent/received in one send/recv call */ +typedef struct _PlinkPacket +{ + int fd; /* file descriptor. If PLINK_INVALID_FD, it's invalid */ + unsigned int timestamp; /* timestamp of this packet, the time for rendering */ + int num; /* number of valid data descriptor entries in list[] */ + PlinkDescriptor *list[PLINK_MAX_DATA_DESCS]; /* list of pointers which point to data descriptor. */ +} PlinkPacket; + +/** + * \brief Create a plink instance. + * + * Create a plink object with the specified name as server or client. + * When mode is PLINK_MODE_SERVER, a file of the specified name will be created. + * + * \param plink Point to the pointer of plink instance. + * \param name Socket file name. + * \param mode plink mode, server or client. + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_getVersion(PlinkVersion *version); + +/** + * \brief Create a plink instance. + * + * Create a plink object with the specified name as server or client. + * When mode is PLINK_MODE_SERVER, a file of the specified name will be created. + * + * \param plink Point to the pointer of plink instance. + * \param name Socket file name. + * \param mode plink mode, server or client. + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_create(PlinkHandle *plink, const char *name, PlinkMode mode); + +/** + * \brief Create a connection between server and client + * + * Server calls this function to wait for connection and accept. + * Client calls this function to connect to server. + * + * \param plink Pointer of plink instance. + * \param channel id of the new connection. Valid for server only. Should be 0 for client + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_connect(PlinkHandle plink, PlinkChannelID *channel); + +/** + * \brief Create a connection between server and client with timeout + * + * Server calls this function to wait for connection and accept with timeout. + * Client calls this function to connect to server with timeout. + * + * \param plink Pointer of plink instance. + * \param channel id of the new connection. Valid for server only. Should be 0 for client + * \param timeout_ms timeout in unit of milliseconds. + * \return PLINK_STATUS_OK successful, + * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, + * \return other unsuccessful. + */ +PlinkStatus PLINK_connect_ex(PlinkHandle plink, PlinkChannelID *channel, int timeout_ms); + +/** + * \brief Send a packet + * + * Send a packet through the channel. + * + * \param plink Pointer of plink instance. + * \param channel The channel to send this packet. Valid for server only. Should be 0 for client + * \param pkt Point to the packet to be sent. + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_send(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt); + +/** + * \brief Wait for data from channel + * + * This function returns once there is data received from the channel. + * + * \param plink Pointer of plink instance. + * \param channel The channel to receive data. Valid for server only. Should be 0 for client + * \param timeout_ms timeout in unit of milliseconds. + * \return PLINK_STATUS_OK successful, + * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, + * \return other unsuccessful. + */ +PlinkStatus PLINK_wait(PlinkHandle plink, PlinkChannelID channel, int timeout_ms); + +/** + * \brief Receive data + * + * Receive data from the channel. + * Data descriptors of the packet are stored in the internal buffer, + * and may be overwritten in the next PLINK_recv call. + * + * \param plink Pointer of plink instance. + * \param channel The channel to receive data. Valid for server only. Should be 0 for client + * \param pkt Point to the received packet. + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_recv(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt); + +/** + * \brief Receive data with timeout + * + * Receive data from the channel with timeout. + * Data descriptors of the packet are stored in the internal buffer, + * and may be overwritten in the next PLINK_recv call. + * + * \param plink Pointer of plink instance. + * \param channel The channel to receive data. Valid for server only. Should be 0 for client + * \param pkt Point to the received packet. + * \param timeout_ms timeout in unit of milliseconds. + * \return PLINK_STATUS_OK successful, + * \return PLINK_STATUS_TIMEOUT if no data received within timeout_ms, + * \return other unsuccessful. + */ +PlinkStatus PLINK_recv_ex(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt, int timeout_ms); + +/** + * \brief Close connections + * + * Close connections. Server can set channel to PLINK_CLOSE_ALL to close all connections. + * + * \param plink Pointer of plink instance. + * \param channel The connection to be closed. Valid for server only. Should be 0 for client + * \return PLINK_STATUS_OK successful, + * \return other unsuccessful. + */ +PlinkStatus PLINK_close(PlinkHandle plink, PlinkChannelID channel); + +#ifdef __cplusplus +} +#endif + +#endif /* !_PROCESS_LINKER_H_ */ diff --git a/inc/process_linker_types.h b/inc/process_linker_types.h new file mode 100644 index 0000000..8fe4070 --- /dev/null +++ b/inc/process_linker_types.h @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#ifndef _PROCESS_LINKER_TYPES_H_ +#define _PROCESS_LINKER_TYPES_H_ + +#include "process_linker.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* When set PlinkMsg.msg to this exit code, it means to close the connection */ +#define PLINK_EXIT_CODE -1 + +/* image/video color format */ +typedef enum _PlinkColorFormat +{ + PLINK_COLOR_FormatUnused, + PLINK_COLOR_FormatMonochrome, + PLINK_COLOR_FormatYUV420Planar, + PLINK_COLOR_FormatYUV420SemiPlanar, + PLINK_COLOR_FormatYUV420SemiPlanarP010, + PLINK_COLOR_FormatYUV422Planar, + PLINK_COLOR_FormatYUV422SemiPlanar, + PLINK_COLOR_Format32bitBGRA8888, + PLINK_COLOR_Format32bitARGB8888, + PLINK_COLOR_Format24BitRGB888, + PLINK_COLOR_Format24BitRGB888Planar, + PLINK_COLOR_Format24BitBGR888, + PLINK_COLOR_Format24BitBGR888Planar, + PLINK_COLOR_FormatRawBayer8bit, + PLINK_COLOR_FormatRawBayer10bit, + PLINK_COLOR_FormatRawBayer12bit, + PLINK_COLOR_FormatMax +} PlinkColorFormat; + +typedef enum _PlinkBayerPattern +{ + PLINK_BAYER_PATTERN_RGGB, + PLINK_BAYER_PATTERN_BGGR, + PLINK_BAYER_PATTERN_GRBG, + PLINK_BAYER_PATTERN_GBRG, + PLINK_BAYER_PATTERN_MAX +} PlinkBayerPattern; + +/* Data descriptor type */ +typedef enum _PlinkDescType +{ + PLINK_TYPE_1D_BUFFER = 0, /* PlinkBufferInfo */ + PLINK_TYPE_2D_YUV, /* PlinkYuvInfo */ + PLINK_TYPE_2D_RGB, /* PlinkRGBInfo */ + PLINK_TYPE_OBJECT, /* PlinkObjectInfo */ + PLINK_TYPE_MESSAGE, /* PlinkMsg */ + PLINK_TYPE_TIME, /* PlinkTimeInfo */ + PLINK_TYPE_2D_RAW, /* PlinkRawInfo */ + PLINK_TYPE_MAX +} PlinkDescType; + +/* time type */ +typedef enum _PlinkTimeType +{ + PLINK_TIME_START = 0, /* start time */ + PLINK_TIME_CALIBRATION, /* time delta for calibration */ + PLINK_TIME_MAX +} PlinkTimeType; + +/* 1D buffer */ +typedef struct _PlinkBufferInfo +{ + PlinkDescHdr header; + unsigned long long bus_address; + unsigned int offset; + unsigned int size; +} PlinkBufferInfo; + +/* 2D YUV surface */ +typedef struct _PlinkYuvInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + unsigned long long bus_address_y; + unsigned long long bus_address_u; + unsigned long long bus_address_v; + unsigned int offset_y; + unsigned int offset_u; + unsigned int offset_v; + unsigned int pic_width; + unsigned int pic_height; + unsigned int stride_y; + unsigned int stride_u; + unsigned int stride_v; +} PlinkYuvInfo; + +/* 2D RGB surface */ +typedef struct _PlinkRGBInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + unsigned long long bus_address_r; + unsigned long long bus_address_g; + unsigned long long bus_address_b; + unsigned long long bus_address_a; + unsigned int offset_r; + unsigned int offset_g; + unsigned int offset_b; + unsigned int offset_a; + unsigned int img_width; + unsigned int img_height; + unsigned int stride_r; + unsigned int stride_g; + unsigned int stride_b; + unsigned int stride_a; +} PlinkRGBInfo; + +/* 2D Bayer Raw surface */ +typedef struct _PlinkRawInfo +{ + PlinkDescHdr header; + PlinkColorFormat format; + PlinkBayerPattern pattern; + unsigned long long bus_address; + unsigned int offset; + unsigned int img_width; + unsigned int img_height; + unsigned int stride; +} PlinkRawInfo; + +/* Feature map buffer after NPU inference */ +typedef struct _PlinkBox +{ + float x1; + float y1; + float x2; + float y2; +} PlinkBox; + +typedef struct _PlinkLandmark +{ + float x[5]; + float y[5]; +} PlinkLandmark; + +typedef struct _PlinkObjectDetect +{ + float score; + PlinkBox box; + PlinkLandmark landmark; +} PlinkObjectDetect; + +typedef struct _PlinkObjectInfo +{ + PlinkDescHdr header; + unsigned long long bus_address; + unsigned int object_cnt; +} PlinkObjectInfo; + +/* Used to send message */ +typedef struct _PlinkMsg +{ + PlinkDescHdr header; + int msg; /* When greater than 0, it means the id of buffer which can be released */ + /* When set to 0, it means a buffer can be released, but id is unknown */ + /* When set to PLINK_EXIT_CODE, it means to close connection */ + /* Other values are reserved */ +} PlinkMsg; + +/* time information */ +typedef struct _PlinkTimeInfo +{ + PlinkDescHdr header; + PlinkTimeType type; + long long seconds; + long long useconds; +} PlinkTimeInfo; + +#ifdef __cplusplus +} +#endif + +#endif /* !_PROCESS_LINKER_TYPES_H_ */ diff --git a/kernel_mode/Makefile b/kernel_mode/Makefile deleted file mode 100644 index b683436..0000000 --- a/kernel_mode/Makefile +++ /dev/null @@ -1,6 +0,0 @@ -shake-objs := isp_venc_shake_driver.o -obj-m += shake.o - -clean: - rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions *.ur-safe .cache.mk - rm -rf modules.order Module.symvers diff --git a/kernel_mode/isp_venc_shake_driver.c b/kernel_mode/isp_venc_shake_driver.c deleted file mode 100644 index e1289b3..0000000 --- a/kernel_mode/isp_venc_shake_driver.c +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright (C) 2021 - 2022 Alibaba Group. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "isp_venc_shake_driver.h" - -#define IVS_SWREG_AMOUNT 27 - -typedef struct _theadivs_dev -{ - struct cdev cdev; - dev_t devt; - struct class *class; - unsigned long base_addr; - u32 iosize; - volatile u8 *hwregs; - int irq; - int state; -} theadivs_dev; - -static int theadivs_major = 0; /* dynamic */ -static int theadivs_minor = 0; -static theadivs_dev* theadivs_data = NULL; -static unsigned int device_register_index = 0; -struct class *theadivs_class; - -static irqreturn_t theadivs_isr(int irq, void *dev_id); - -static void print_registers(void) -{ - printk("pic_width = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x00))); - printk("pic_height = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x04))); - printk("encode_width = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x08))); - printk("encode_height = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x0c))); - printk("wid_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x10))); - printk("wid_uv = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x14))); - printk("sram_size = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x18))); - printk("encode_n = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x1c))); - printk("stride_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x20))); - printk("stride_uv = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x24))); - printk("encode_x = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x2c))); - printk("encode_y = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x30))); - printk("int_state = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x38))); - printk("int_mask = %d\n", ioread32((void*)(theadivs_data->hwregs + 0x40))); -} - -static int config_ivs(struct file *filp, struct ivs_parameter *params) -{ - iowrite32(params->pic_width, (void*)(theadivs_data->hwregs + 0x00)); - iowrite32(params->pic_height, (void*)(theadivs_data->hwregs + 0x04)); - iowrite32(params->encode_width, (void*)(theadivs_data->hwregs + 0x08)); - iowrite32(params->encode_height, (void*)(theadivs_data->hwregs + 0x0c)); - iowrite32(params->wid_y, (void*)(theadivs_data->hwregs + 0x10)); - iowrite32(params->wid_uv, (void*)(theadivs_data->hwregs + 0x14)); - iowrite32(params->sram_size, (void*)(theadivs_data->hwregs + 0x18)); - iowrite32(params->encode_n, (void*)(theadivs_data->hwregs + 0x1c)); - iowrite32(params->stride_y, (void*)(theadivs_data->hwregs + 0x20)); - iowrite32(params->stride_uv, (void*)(theadivs_data->hwregs + 0x24)); - iowrite32(1, (void*)(theadivs_data->hwregs + 0x28)); // CLEAR - iowrite32(params->encode_x, (void*)(theadivs_data->hwregs + 0x2c)); - iowrite32(params->encode_y, (void*)(theadivs_data->hwregs + 0x30)); - iowrite32(0, (void*)(theadivs_data->hwregs + 0x34)); // START - iowrite32(0, (void*)(theadivs_data->hwregs + 0x3c)); // INT_CLEAN - iowrite32(params->int_mask, (void*)(theadivs_data->hwregs + 0x40)); - - return 0; -} - -static long theadivs_ioctl(struct file *filp, - unsigned int cmd, unsigned long arg) -{ - int err = 0; - - if (_IOC_TYPE(cmd) != THEAD_IOC_MAGIC) - return -ENOTTY; - else if (_IOC_NR(cmd) > THEAD_IOC_MAXNR) - return -ENOTTY; - - if (_IOC_DIR(cmd) & _IOC_READ) - err = !access_ok((void *) arg, _IOC_SIZE(cmd)); - else if (_IOC_DIR(cmd) & _IOC_WRITE) - err = !access_ok((void *) arg, _IOC_SIZE(cmd)); - if (err) - return -EFAULT; - - switch (cmd) - { - case THEAD_IOCH_CONFIG_IVS: - { - struct ivs_parameter params; - printk("%s: THEAD_IOCH_CONFIG_IVS\n", __func__); - err = copy_from_user(¶ms, (struct ivs_parameter*)arg, sizeof(struct ivs_parameter)); - config_ivs(filp, ¶ms); - print_registers(); - theadivs_data->state = THEADIVS_READY; - break; - } - case THEAD_IOCH_START_IVS: - { - printk("%s: THEAD_IOCH_START_IVS\n", __func__); - iowrite32(1, theadivs_data->hwregs + 0x34); // START - print_registers(); - theadivs_data->state = THEADIVS_RUNNING; - break; - } - case THEAD_IOCH_RESET_IVS: - { - printk("%s: THEAD_IOCH_RESET_IVS\n", __func__); - iowrite32(1, theadivs_data->hwregs + 0x40); // INT_MASK - iowrite32(1, theadivs_data->hwregs + 0x28); // CLEAR - iowrite32(0, theadivs_data->hwregs + 0x40); // INT_MASK - print_registers(); - theadivs_data->state = THEADIVS_IDLE; - break; - } - case THEAD_IOCH_GET_STATE: - { - printk("%s: THEAD_IOCH_GET_STATE: %d\n", __func__, theadivs_data->state); - err = copy_to_user((int *)arg, &theadivs_data->state, sizeof(int)); - break; - } - default: - { - printk("%s: undefined command: 0x%x\n", __func__, cmd); - } - } - return 0; -} - -static int theadivs_open(struct inode *inode, struct file *filp) -{ - int result = 0; - //theadivs_dev *dev = theadivs_data; - //filp->private_data = (void *) dev; - - return result; -} -static int theadivs_release(struct inode *inode, struct file *filp) -{ - //theadivs_dev *dev = (theadivs_dev *) filp->private_data; - - return 0; -} - -static struct file_operations theadivs_fops = { - .owner= THIS_MODULE, - .open = theadivs_open, - .release = theadivs_release, - .unlocked_ioctl = theadivs_ioctl, - .fasync = NULL, -}; - -static const struct of_device_id thead_of_match[] = { - { .compatible = "thead,light-ivs", }, - { /* sentinel */ }, -}; - -static int theadivs_reserve_IO(void) -{ - if(!request_mem_region - (theadivs_data->base_addr, theadivs_data->iosize, "shake")) - { - printk(KERN_INFO "theadivs: failed to reserve HW regs\n"); - printk(KERN_INFO "theadivs: base_addr = 0x%08lx, iosize = %d\n", - theadivs_data->base_addr, - theadivs_data->iosize); - return -1; - } - -#if (LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)) - theadivs_data->hwregs = - (volatile u8 *) ioremap_nocache(theadivs_data->base_addr, - theadivs_data->iosize); -#else - theadivs_data->hwregs = - (volatile u8 *) ioremap(theadivs_data->base_addr, - theadivs_data->iosize); -#endif - - if (theadivs_data->hwregs == NULL) - { - printk(KERN_INFO "theadivs: failed to ioremap HW regs\n"); - release_mem_region(theadivs_data->base_addr, theadivs_data->iosize); - return -1; - } - - printk("theadivs: mapped from 0x%lx to %p with size %d\n", - theadivs_data->base_addr, theadivs_data->hwregs, theadivs_data->iosize); - - return 0; -} - -static void theadivs_release_IO(void) -{ - if(theadivs_data->hwregs) - { - iounmap((void *) theadivs_data->hwregs); - release_mem_region(theadivs_data->base_addr, theadivs_data->iosize); - theadivs_data->hwregs = NULL; - } -} - -int __init theadivs_probe(struct platform_device *pdev) -{ - int result = -1; - struct resource *mem; - printk("enter %s\n",__func__); - - theadivs_data = (theadivs_dev *)vmalloc(sizeof(theadivs_dev)); - if (theadivs_data == NULL) - return result; - memset(theadivs_data, 0, sizeof(theadivs_dev)); - - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if(mem->start) - theadivs_data->base_addr = mem->start; - theadivs_data->irq = platform_get_irq(pdev, 0); - printk("%s:get irq %d\n", __func__, theadivs_data->irq); - theadivs_data->iosize = IVS_SWREG_AMOUNT * 4; -#if 1 - if (device_register_index == 0) - { - if (theadivs_major == 0) - { - result = alloc_chrdev_region(&theadivs_data->devt, 0, 1, "shake"); - if (result != 0) - { - printk("%s:alloc_chrdev_region error\n", __func__); - goto err1; - } - theadivs_major = MAJOR(theadivs_data->devt); - theadivs_minor = MINOR(theadivs_data->devt); - } - else - { - theadivs_data->devt = MKDEV(theadivs_major, theadivs_minor); - result = register_chrdev_region(theadivs_data->devt, 1, "shake"); - if (result) - { - printk("%s:register_chrdev_region error\n", __func__); - goto err1; - } - } - - theadivs_class = class_create(THIS_MODULE, "shake"); - if (IS_ERR(theadivs_class)) - { - printk("%s[%d]:class_create error!\n", __func__, __LINE__); - goto err; - } - } - theadivs_data->devt = MKDEV(theadivs_major, theadivs_minor + pdev->id); - - cdev_init(&theadivs_data->cdev, &theadivs_fops); - result = cdev_add(&theadivs_data->cdev, theadivs_data->devt, 1); - if ( result ) - { - printk("%s[%d]:cdev_add error!\n", __func__, __LINE__); - goto err; - } - theadivs_data->class = theadivs_class; - device_create(theadivs_data->class, NULL, theadivs_data->devt, - theadivs_data, "shake"); - - device_register_index++; -#else - result = register_chrdev(theadivs_major, "shake", &theadivs_fops); - if (result < 0) - { - printk(KERN_INFO "theadivs_driver: unable to get major <%d>\n", - theadivs_major); - goto err1; - } - else if (result != 0) /* this is for dynamic major */ - { - theadivs_major = result; - } -#endif - - theadivs_reserve_IO(); - - /* get the IRQ line */ - if (theadivs_data->irq!= -1) - { - result = request_irq(theadivs_data->irq, theadivs_isr, - IRQF_SHARED, - "shake", (void *)theadivs_data); - if (result == -EINVAL) - { - printk(KERN_ERR "theadivs_driver: Bad irq number or handler.\n"); - theadivs_release_IO(); - goto err; - } - else if (result == -EBUSY) - { - printk(KERN_ERR "theadivs_driver: IRQ <%d> busy, change your config.\n", - theadivs_data->irq); - theadivs_release_IO(); - goto err; - } - } - else - { - printk(KERN_INFO "theadivs_driver: IRQ not in use!\n"); - } - - printk(KERN_INFO "theadivs_driver: module inserted. Major <%d>\n", theadivs_major); - - return 0; -err: - //unregister_chrdev(theadivs_major, "shake"); - unregister_chrdev_region(theadivs_data->devt, 1); -err1: - if (theadivs_data != NULL) - vfree(theadivs_data); - printk(KERN_INFO "theadivs_driver: module not inserted\n"); - return result; -} - -static int theadivs_remove(struct platform_device *pdev) -{ - free_irq(theadivs_data->irq, theadivs_data); - theadivs_release_IO(); - //unregister_chrdev(theadivs_major, "shake"); - device_register_index--; - cdev_del(&theadivs_data->cdev); - device_destroy(theadivs_data->class, theadivs_data->devt); - unregister_chrdev_region(theadivs_data->devt, 1); - if (device_register_index == 0) - { - class_destroy(theadivs_data->class); - } - if (theadivs_data != NULL) - vfree(theadivs_data); - - printk(KERN_INFO "theadivs_driver: module removed\n"); - return 0; -} - - -static struct platform_driver theadivs_driver = { - .probe = theadivs_probe, - .remove = theadivs_remove, - .driver = { - .name = "shake", - .owner = THIS_MODULE, - .of_match_table = of_match_ptr(thead_of_match), - } -}; - - -int __init theadivs_init(void) -{ - int ret = 0; - printk("enter %s\n",__func__); -#if 1 - ret = platform_driver_register(&theadivs_driver); - if (ret) - { - pr_err("register platform driver failed!\n"); - } -#endif - return ret; -} - -void __exit theadivs_cleanup(void) -{ - printk("enter %s\n",__func__); - platform_driver_unregister(&theadivs_driver); - return; -} - -static irqreturn_t theadivs_isr(int irq, void *dev_id) -{ - theadivs_dev *dev = (theadivs_dev *) dev_id; - u32 irq_status = 0; - - printk( "theadivs_isr: received IRQ!\n"); - irq_status = (u32)ioread32((void*)dev->hwregs + 0x38); // INT_STATE - printk( "INT_STATE of is: 0x%x\n", irq_status); - theadivs_data->state = THEADIVS_ERROR; - - iowrite32(1, theadivs_data->hwregs + 0x40); // INT_MASK - iowrite32(1, theadivs_data->hwregs + 0x3c); // INT_CLEAN - iowrite32(1, theadivs_data->hwregs + 0x28); // CLEAR - iowrite32(0, theadivs_data->hwregs + 0x40); // INT_MASK - - return IRQ_HANDLED; -} - - -module_init(theadivs_init); -module_exit(theadivs_cleanup); - -MODULE_LICENSE("GPL"); -MODULE_AUTHOR("T-HEAD"); -MODULE_DESCRIPTION("T-HEAD ISP-VENC-SHAKE driver"); - diff --git a/kernel_mode/isp_venc_shake_driver.h b/kernel_mode/isp_venc_shake_driver.h deleted file mode 100644 index aa91a16..0000000 --- a/kernel_mode/isp_venc_shake_driver.h +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2021 - 2022 Alibaba Group. All rights reserved. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#ifndef _ISP_VENC_SHAKE_DRIVER_H_ -#define _ISP_VENC_SHAKE_DRIVER_H_ -#include - -#define THEAD_IOC_MAGIC 't' - -#define THEAD_IOCH_CONFIG_IVS _IOR(THEAD_IOC_MAGIC, 3, unsigned long *) -#define THEAD_IOCH_START_IVS _IOR(THEAD_IOC_MAGIC, 4, unsigned long *) -#define THEAD_IOCH_RESET_IVS _IOR(THEAD_IOC_MAGIC, 5, unsigned long *) -#define THEAD_IOCH_GET_STATE _IOR(THEAD_IOC_MAGIC, 6, int *) - -#define THEAD_IOC_MAXNR 6 - -typedef int8_t i8; -typedef uint8_t u8; -typedef int16_t i16; -typedef uint16_t u16; -typedef int32_t i32; -typedef uint32_t u32; -typedef int64_t i64; -typedef uint64_t u64; - -typedef enum _theadivs_state -{ - THEADIVS_IDLE, - THEADIVS_READY, - THEADIVS_RUNNING, - THEADIVS_ERROR -} theadivs_state; - -struct ivs_parameter -{ - u32 pic_width; - u32 pic_height; - u32 encode_width; - u32 encode_height; - u32 wid_y; - u32 wid_uv; - u32 sram_size; - u32 encode_n; - u32 stride_y; - u32 stride_uv; - //u32 clear; - u32 encode_x; - u32 encode_y; - //u32 start; - //u32 int_state; - //u32 int_clean; - u32 int_mask; - //u32 signal_resv; -}; - -#endif /* !_ISP_VENC_SHAKE_DRIVER_H_ */ diff --git a/src/process_linker.c b/src/process_linker.c new file mode 100644 index 0000000..19f2401 --- /dev/null +++ b/src/process_linker.c @@ -0,0 +1,552 @@ +/* + * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "process_linker.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define MAX_CONNECTIONS 3 +#define MAX_BUFFER_SIZE (4 * 1024 * 1024) + +#define PLINK_PRINT(level, ...) \ + { \ + if (log_level >= PLINK_LOG_##level) \ + { \ + struct timeval ts; \ + gettimeofday(&ts, 0); \ + printf("PLINK[%d][%ld.%06ld] %s: ", pid, ts.tv_sec, ts.tv_usec, #level); \ + printf(__VA_ARGS__); \ + } \ + } + +#define PLINK_PRINT_RETURN(retcode, level, ...) \ + { \ + PLINK_PRINT(level, __VA_ARGS__) \ + return retcode; \ + } + +typedef enum _PlinkLogLevel +{ + PLINK_LOG_QUIET = 0, + PLINK_LOG_ERROR, + PLINK_LOG_WARNING, + PLINK_LOG_INFO, + PLINK_LOG_DEBUG, + PLINK_LOG_TRACE, + PLINK_LOG_MAX +} PlinkLogLevel; + +typedef struct _PlinkContext +{ + PlinkMode mode; + struct sockaddr_un addr; + struct iovec ioIn[PLINK_MAX_DATA_DESCS]; + struct iovec ioOut[PLINK_MAX_DATA_DESCS]; + int sockfd; + int cfd[MAX_CONNECTIONS]; + int connect[MAX_CONNECTIONS]; + int count; // connected client number + char *buffer; + int offset; + int pid; +} PlinkContext; + +int log_level = PLINK_LOG_ERROR; +int pid = 0; + +static PlinkStatus parseData(PlinkContext *ctx, PlinkPacket *pkt, int total); +static PlinkStatus wait(int sockfd, int timeout_ms); +static int getLogLevel(); + +PlinkStatus +PLINK_getVersion(PlinkVersion *version) +{ + if (version != NULL) + { + version->v.major = PLINK_VERSION_MAJOR; + version->v.minor = PLINK_VERSION_MINOR; + version->v.revision = PLINK_VERSION_REVISION; + version->v.step = 0; + return PLINK_STATUS_OK; + } + else + return PLINK_STATUS_ERROR; +} + +PlinkStatus +PLINK_create(PlinkHandle *plink, const char *name, PlinkMode mode) +{ + PlinkContext *ctx = NULL; + int sockfd; + + log_level = getLogLevel(); + pid = getpid(); + + if (plink == NULL || name == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p, name = %s\n", plink, name); + + ctx = (PlinkContext *)malloc(sizeof(*ctx)); + if (NULL == ctx) + PLINK_PRINT_RETURN(PLINK_STATUS_NO_MEMORY, ERROR, + "Failed to allocate memory for plink\n"); + memset(ctx, 0, sizeof(*ctx)); + *plink = (PlinkHandle)ctx; + + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (-1 == sockfd) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to create socket as AF_UNIX, SOCK_STREAM\n"); + + ctx->sockfd = sockfd; + ctx->mode = mode; + ctx->addr.sun_family = AF_UNIX; + strncpy(ctx->addr.sun_path, name, sizeof(ctx->addr.sun_path) - 1); + + if (mode == PLINK_MODE_SERVER) + { + if (unlink (name) == -1 && errno != ENOENT) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to unlink %s\n", name); + + if (bind(sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to bind to AF_UNIX address: %s\n", ctx->addr.sun_path); + + if (listen(sockfd, MAX_CONNECTIONS) == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to listen for connection request\n"); + } + + ctx->buffer = malloc(MAX_BUFFER_SIZE); + if (ctx->buffer == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_NO_MEMORY, ERROR, + "Failed to allocate memory for internal buffer\n"); + + ctx->pid = getpid(); + + return PLINK_STATUS_OK; +} + +PlinkStatus +PLINK_connect(PlinkHandle plink, PlinkChannelID *channel) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p\n", plink); + + if (ctx->mode == PLINK_MODE_SERVER) + { + if (channel == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: channel = %p\n", channel); + + if (ctx->count >= MAX_CONNECTIONS) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Too many connections %d while it is limited to %d\n", ctx->count, MAX_CONNECTIONS); + + // find available slot + int i; + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (ctx->connect[i] == 0) + { + ctx->connect[i] = 1; + *channel = i; + break; + } + } + + // wait for connection from client + PLINK_PRINT(INFO, "Waiting for connection...\n"); + int fd = accept(ctx->sockfd, NULL, NULL); + if (fd == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to accept connection\n"); + + ctx->cfd[i] = fd; + ctx->count++; + PLINK_PRINT(INFO, "Accepted connection request from client %d (%d/%d): %d\n", + i, ctx->count, MAX_CONNECTIONS, fd); + } + else + { + if (connect(ctx->sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to connect to server %s: %s\n", ctx->addr.sun_path, strerror(errno)); + + PLINK_PRINT(INFO, "Connected to server: %d\n", ctx->sockfd); + } + + return PLINK_STATUS_OK; +} + +PlinkStatus +PLINK_send(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL || pkt == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p, pkt = %p\n", plink, pkt); + + if (pkt->num > PLINK_MAX_DATA_DESCS) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Too many data nodes to send: %d\n", pkt->num); + + char buf[CMSG_SPACE(sizeof(int))]; + memset(buf, 0, sizeof(buf)); + for (int i = 0; i < pkt->num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]); + ctx->ioOut[i].iov_base = pkt->list[i]; + ctx->ioOut[i].iov_len = hdr->size + DATA_HEADER_SIZE; + PLINK_PRINT(INFO, "Sending Out %ld bytes\n", ctx->ioOut[i].iov_len); + } + + struct msghdr msg = {0}; + msg.msg_iov = ctx->ioOut; + msg.msg_iovlen = pkt->num; + + if (pkt->fd > PLINK_INVALID_FD) + { + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + struct cmsghdr *cmsg; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + *((int *)CMSG_DATA(cmsg)) = pkt->fd; + PLINK_PRINT(INFO, "Sent fd %d\n", pkt->fd); + } + + int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd; + if (sendmsg(sockfd, &msg, 0) == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "sendmsg() failed: %s\n", strerror(errno)); + PLINK_PRINT(INFO, "Sent data to %d\n", sockfd); + + return PLINK_STATUS_OK; +} + +PlinkStatus +PLINK_recv(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL || pkt == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p, pkt = %p\n", plink, pkt); + + pkt->num = 0; + + char buf[CMSG_SPACE(sizeof(int))]; + memset(buf, 0, sizeof(buf)); + ctx->ioIn[0].iov_base = ctx->buffer + ctx->offset; + ctx->ioIn[0].iov_len = MAX_BUFFER_SIZE - ctx->offset; + + struct msghdr msg = {0}; + msg.msg_iov = ctx->ioIn; + msg.msg_iovlen = 1; + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + + int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd; + PLINK_PRINT(INFO, "Receiving data from %d\n", sockfd); + int total = recvmsg (sockfd, &msg, 0); + if (total > 0) + PLINK_PRINT(INFO, "Received %d bytes\n", total) + else if (total == 0) + PLINK_PRINT_RETURN(PLINK_STATUS_NO_DATA, WARNING, + "recvmsg() returns %d\n", total) + else + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to recieve data from %d: %s\n", sockfd, strerror(errno)) + + if (msg.msg_controllen >= CMSG_SPACE(sizeof(int))) + { + struct cmsghdr *cmsg; + cmsg = CMSG_FIRSTHDR(&msg); + pkt->fd = *((int *)CMSG_DATA(cmsg)); + PLINK_PRINT(INFO, "Received fd %d\n", pkt->fd); + } + else + pkt->fd = PLINK_INVALID_FD; + + return parseData(ctx, pkt, ctx->offset + total); +} + +PlinkStatus +PLINK_wait(PlinkHandle plink, PlinkChannelID channel, int timeout_ms) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p\n", plink); + + int sockfd = ctx->mode == PLINK_MODE_SERVER ? ctx->cfd[channel] : ctx->sockfd; + return wait(sockfd, timeout_ms); +} + +PlinkStatus +PLINK_close(PlinkHandle plink, PlinkChannelID channel) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p\n", plink); + + if (ctx->mode == PLINK_MODE_SERVER) + { + if (channel == PLINK_CLOSE_ALL) + { + // close all connections + int i; + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (ctx->connect[i] != 0) + { + ctx->connect[i] = 0; + close(ctx->cfd[i]); + ctx->count--; + PLINK_PRINT(INFO, "Closed channel %d\n", i); + } + } + + unlink(ctx->addr.sun_path); + close(ctx->sockfd); + if (ctx->buffer != NULL) + free(ctx->buffer); + + free(ctx); + } + else if (channel < MAX_CONNECTIONS && ctx->connect[channel] != 0) + { + close(ctx->cfd[channel]); + ctx->count--; + PLINK_PRINT(INFO, "Closed channel %d\n", channel); + } + else + { + PLINK_PRINT(ERROR, "Invalid channel: %d\n", channel); + } + + unlink(ctx->addr.sun_path); + } + else + { + close(ctx->sockfd); + if (ctx->buffer != NULL) + free(ctx->buffer); + + free(ctx); + } + + return PLINK_STATUS_OK; +} + +PlinkStatus +PLINK_connect_ex(PlinkHandle plink, PlinkChannelID *channel, int timeout_ms) +{ + PlinkContext *ctx = (PlinkContext *)plink; + + if (ctx == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: plink = %p\n", plink); + + int i = 0; + if (ctx->mode == PLINK_MODE_SERVER) + { + if (channel == NULL) + PLINK_PRINT_RETURN(PLINK_STATUS_WRONG_PARAMS, ERROR, + "Wrong parameters: channel = %p\n", channel); + + if (ctx->count >= MAX_CONNECTIONS) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Too many connections %d while it is limited to %d\n", ctx->count, MAX_CONNECTIONS); + + // find available slot + for (i = 0; i < MAX_CONNECTIONS; i++) + { + if (ctx->connect[i] == 0) + { + ctx->connect[i] = 1; + *channel = i; + break; + } + } + + // wait for connection from client + PLINK_PRINT(INFO, "Waiting for connection...\n"); + if (wait(ctx->sockfd, timeout_ms) == PLINK_STATUS_OK) + { + int fd = accept(ctx->sockfd, NULL, NULL); + if (fd == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to accept connection\n"); + + ctx->cfd[i] = fd; + ctx->count++; + PLINK_PRINT(INFO, "Accepted connection request from client %d (%d/%d): %d\n", + i, ctx->count, MAX_CONNECTIONS, fd); + } + else + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "No connection request within %dms\n", timeout_ms); + } + else + { + int max_tries = (timeout_ms + 1000 - 1) / 1000; + for (i = 0; i < max_tries; i++) + { + if (connect(ctx->sockfd, (struct sockaddr *)&ctx->addr, sizeof(struct sockaddr_un)) == -1) + sleep(1); + else + break; + } + + if (i >= max_tries) + PLINK_PRINT_RETURN(PLINK_STATUS_TIMEOUT, WARNING, + "Failed to connect to server %s\n", ctx->addr.sun_path); + + PLINK_PRINT(INFO, "Connected to server: %d\n", ctx->sockfd); + } + + return PLINK_STATUS_OK; +} + +PlinkStatus +PLINK_recv_ex(PlinkHandle plink, PlinkChannelID channel, PlinkPacket *pkt, int timeout_ms) +{ + int ret = PLINK_wait(plink, channel, timeout_ms); + if (ret == PLINK_STATUS_OK) + { + return PLINK_recv(plink, channel, pkt); + } + + return ret; +} + + +static PlinkStatus +parseData(PlinkContext *ctx, PlinkPacket *pkt, int remaining) +{ + PlinkStatus sts = PLINK_STATUS_OK; + + if (ctx == NULL) + return PLINK_STATUS_ERROR; + + int index = 0; + void *buffer = ctx->buffer; + while (remaining >= DATA_HEADER_SIZE) + { + if (index >= PLINK_MAX_DATA_DESCS) + { + // not enough buffer to store received data, need another recv call + sts = PLINK_STATUS_MORE_DATA; + PLINK_PRINT(ERROR, "sts:%d Received %d bytes,index exceed max:%d!\n", + sts, remaining, PLINK_MAX_DATA_DESCS); + break; + } + + PlinkDescHdr *hdr = (PlinkDescHdr *)buffer; + if (remaining < (unsigned int)hdr->size) + { + PLINK_PRINT(WARNING, "Not enough data received. Expect %d while only %d available\n", hdr->size, remaining); + // return to get more data in next recvmsg call + break; + } + + pkt->list[index] = buffer; + + buffer += DATA_HEADER_SIZE + hdr->size; + remaining -= DATA_HEADER_SIZE + hdr->size; + index++; + } + + // move the remaining data to the beginning of the buffer + if (remaining > 0) + { + memmove(ctx->buffer, buffer, remaining); + ctx->offset = remaining; + } + + pkt->num = sts == PLINK_STATUS_MORE_DATA ? index-1 : index; + + return sts; +} + +static int getLogLevel() +{ + char *env = getenv("PLINK_LOG_LEVEL"); + if (env == NULL) + return PLINK_LOG_ERROR; + else + { + int level = atoi(env); + if (level >= PLINK_LOG_MAX || level < PLINK_LOG_QUIET) + return PLINK_LOG_ERROR; + else + return level; + } +} + +static PlinkStatus +wait(int sockfd, int timeout_ms) +{ + fd_set rfds; + struct timeval tv; + int ret; + + FD_ZERO(&rfds); + FD_SET(sockfd, &rfds); + + tv.tv_sec = timeout_ms / 1000; + tv.tv_usec = (timeout_ms % 1000) * 1000; + PLINK_PRINT(INFO, "Waiting for data from socket %d, timeout %dms\n", sockfd, timeout_ms); + ret = select(sockfd+1, &rfds, NULL, NULL, &tv); + if (ret == -1) + PLINK_PRINT_RETURN(PLINK_STATUS_ERROR, ERROR, + "Failed to wait for data\n") + else if (ret) + return PLINK_STATUS_OK; + else if (timeout_ms > 0) + PLINK_PRINT_RETURN(PLINK_STATUS_TIMEOUT, WARNING, + "Wait timeout\n"); + + return PLINK_STATUS_TIMEOUT; +} diff --git a/test/Makefile b/test/Makefile deleted file mode 100755 index 2c20831..0000000 --- a/test/Makefile +++ /dev/null @@ -1,73 +0,0 @@ - -CFLAGS = -Wall -D_GNU_SOURCE -D_REENTRANT -D_THREAD_SAFE -O2 -Werror -Wno-unused -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-strict-overflow -Wno-array-bounds -Wno-shift-negative-value -Wempty-body -Wtype-limits -Wno-unused-result -fPIC -Wmissing-field-initializers -std=gnu99 - -INCLUDE += -I../user_mode - -SRCS = isp_venc_shake.c -OBJS = $(SRCS:.c=.o) - -#source search path -vpath %.c - -# name of the outputfile (library) -IVSLIB = ../user_mode/libivs.a -IVSSLIB = ../user_mode/libivs.so -TARGET = ivs_test - -#Here are rules for building codes and generating object library. -all: tags - @echo --------------------------------------- - @echo "Usage: make [ system | testdata | versatile | integrator | android]" - @echo "system - PC system model (== pclinux)" - @echo "testdata - PC system model for test data creation" - @echo "eval - PC system model for evaluation with frame limit" - @echo "versatile - ARM versatile with FPGA HW" - @echo "integrator - ARM integrator with FPGA HW" - @echo "android - ARM android" - @echo "NOTE! Make sure to do 'make clean'" - @echo "between compiling to different targets!" - @echo --------------------------------------- - -.PHONY: system_lib system testdata clean depend - -evaluation: eval -eval: system - -system_static: testdata -system: testdata - -# for libva -system_lib: system - -testdata: .depend $(IVSLIB) - - -.PHONY: hwlinux -hwlinux: -hwlinux: .depend $(TARGET) - -$(TARGET): $(OBJS) - $(CC) $(CFLAGS) $(OBJS) $(IVSLIB) -lm -lpthread -o $(TARGET) - - - -%.o: %.c - $(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@ - -clean: - $(RM) $(TARGET) - $(RM) .depend - $(RM) *.o *.gcno *.gcda - -tags: - ctags ../kernel_mode/*h ./*[ch] - -depend .depend: $(SRCS) - @echo "[CC] $@" - @$(CC) -M $(DEBFLAGS) $(INCLUDE) $^ > .depend - -ifneq (clean, $(findstring clean, $(MAKECMDGOALS))) -ifeq (.depend, $(wildcard .depend)) -include .depend -endif -endif diff --git a/test/isp_venc_shake.c b/test/isp_venc_shake.c deleted file mode 100644 index 6f4c126..0000000 --- a/test/isp_venc_shake.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. - * License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "isp_venc_shake_hal.h" - -#ifndef NULL -#define NULL ((void *)0) -#endif - - -int main(void) -{ - void *ivs = NULL; - IvsConfig config; - IvsStatus status = IVS_STATUS_OK; - - do - { - status = createIspVencShake(&ivs); - if (status != IVS_STATUS_OK) - break; - - status = resetIspVencShake(ivs); - - config.mode = IVS_BUFFER_MODE_FRAME; - config.encode = IVS_ENCODER_FORMAT_H264; - config.pic_width = 640; - config.pic_height = 480; - config.encode_width = 640; - config.encode_height = 480; - config.buffer_height = 240; - config.stride_y = 640; - config.stride_uv = 640; - config.encode_x = 0; - config.encode_y = 0; - status = configIspVencShake(ivs, &config); - if (status != IVS_STATUS_OK) - break; - - status = startIspVencShake(ivs); - if (status != IVS_STATUS_OK) - break; - - sleep(90); - - status = resetIspVencShake(ivs); - if (status != IVS_STATUS_OK) - break; - } while (0); - - destroyIspVencShake(ivs); - - if (status != IVS_STATUS_OK) - printf("ERROR: ivs_test exit with error!\n"); - else - printf("ivs_test done!\n"); - - return (status == IVS_STATUS_OK ? 0 : 1); -} - diff --git a/test/plink_client.c b/test/plink_client.c new file mode 100644 index 0000000..773927f --- /dev/null +++ b/test/plink_client.c @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2021 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "process_linker_types.h" +#include "video_mem.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define NUM_OF_BUFFERS 5 +#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ + } while (0) + +int main(int argc, char **argv) { + PlinkStatus sts = PLINK_STATUS_OK; + PlinkPacket sendpkt, recvpkt; + PlinkMsg msg; + PlinkHandle plink = NULL; + VmemParams params; + void *vmem = NULL; + FILE *fp = NULL; + int exitcode = 0; + + int frames = argc > 1 ? atoi(argv[1]) : 1000; + char *plinkname = argc > 2 ? argv[2] : "/tmp/plink.test"; + char *dumpname = argc > 3 ? argv[3] : NULL; + + if (dumpname != NULL) + { + fp = fopen(dumpname, "wb"); + if (fp == NULL) + errExit("fopen"); + } + + if (VMEM_create(&vmem) != VMEM_STATUS_OK) + errExit("Failed to create VMEM."); + + if (PLINK_create(&plink, plinkname, PLINK_MODE_CLIENT) != PLINK_STATUS_OK) + errExit("Failed to create PLINK."); + + if (PLINK_connect(plink, NULL) != PLINK_STATUS_OK) + errExit("Failed to connect to server."); + + int frmcnt = 0; + do { + sts = PLINK_recv(plink, 0, &recvpkt); + memset(¶ms, 0, sizeof(params)); + if (recvpkt.fd != PLINK_INVALID_FD) + { + params.fd = recvpkt.fd; + if (VMEM_import(vmem, ¶ms) != VMEM_STATUS_OK) + errExit("Failed to import fd."); + if (VMEM_mmap(vmem, ¶ms) != VMEM_STATUS_OK) + errExit("Failed to mmap buffer."); + } + + for (int i = 0; i < recvpkt.num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(recvpkt.list[i]); + if (hdr->type == PLINK_TYPE_2D_YUV) + { + PlinkYuvInfo *pic = (PlinkYuvInfo *)(recvpkt.list[i]); + printf("[CLIENT] Received YUV frame %d 0x%010llx: fd %d, %dx%d, stride = luma %d, chroma %d\n", + pic->header.id, pic->bus_address_y, recvpkt.fd, + pic->pic_width, pic->pic_height, + pic->stride_y, pic->stride_u); + + // Save YUV data to file + if (fp != NULL && params.vir_address != NULL) + { + void *buffer = params.vir_address; + for (int i = 0; i < pic->pic_height * 3 / 2; i++) + { + fwrite(buffer, pic->pic_width, 1, fp); + buffer += pic->stride_y; + } + } + + // return the buffer to source + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = hdr->id; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = PLINK_INVALID_FD; + if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR) + errExit("Failed to send data."); + } + if (hdr->type == PLINK_TYPE_2D_RGB) + { + PlinkRGBInfo *pic = (PlinkRGBInfo *)(recvpkt.list[i]); + printf("[CLIENT] Received RGB picture %d 0x%010llx: fd %d, %dx%d, stride %d/%d/%d/%d\n", + pic->header.id, pic->bus_address_r, recvpkt.fd, + pic->img_width, pic->img_height, + pic->stride_r, pic->stride_g, pic->stride_b, pic->stride_a); + + // Save RGB data to file + if (fp != NULL && params.vir_address != NULL && pic->format == PLINK_COLOR_Format24BitBGR888Planar) + { + void *buffer = params.vir_address; + for (int i = 0; i < pic->img_height * 3; i++) + { + fwrite(buffer, pic->img_width, 1, fp); + buffer += pic->stride_r; + } + } + + // return the buffer to source + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = hdr->id; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = PLINK_INVALID_FD; + if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR) + errExit("Failed to send data."); + } + if (hdr->type == PLINK_TYPE_2D_RAW) + { + PlinkRawInfo *pic = (PlinkRawInfo *)(recvpkt.list[i]); + printf("[CLIENT] Received RAW picture %d 0x%010llx: fd %d, %dx%d, stride %d\n", + pic->header.id, pic->bus_address, recvpkt.fd, + pic->img_width, pic->img_height, pic->stride); + + // Save RAW data to file + if (fp != NULL && params.vir_address != NULL) + fwrite(params.vir_address, pic->stride * pic->img_height, 1, fp); + + // return the buffer to source + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = hdr->id; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = PLINK_INVALID_FD; + if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR) + errExit("Failed to send data."); + } + else if (hdr->type == PLINK_TYPE_MESSAGE) + { + PlinkMsg *msg = (PlinkMsg *)(recvpkt.list[i]); + if (msg->msg == PLINK_EXIT_CODE) + { + exitcode = 1; + printf("Exit\n"); + break; + } + } + } + + if (VMEM_release(vmem, ¶ms) != VMEM_STATUS_OK) + errExit("Failed to release buffer."); + + if (recvpkt.fd != PLINK_INVALID_FD) + close(recvpkt.fd); + + frmcnt++; + + if (frmcnt >= frames) + { + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = PLINK_EXIT_CODE; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = PLINK_INVALID_FD; + if (PLINK_send(plink, 0, &sendpkt) == PLINK_STATUS_ERROR) + errExit("Failed to send data."); + break; + } + } while (exitcode == 0); + +cleanup: + sleep(1); // Sleep one second to make sure server is ready for exit + PLINK_close(plink, 0); + VMEM_destroy(vmem); + if (fp != NULL) + fclose(fp); + exit(EXIT_SUCCESS); +} diff --git a/test/plink_server.c b/test/plink_server.c new file mode 100644 index 0000000..4a57584 --- /dev/null +++ b/test/plink_server.c @@ -0,0 +1,432 @@ +/* + * Copyright (c) 2021 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "process_linker_types.h" +#include "video_mem.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define NUM_OF_BUFFERS 5 +#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ + } while (0) + +typedef struct _ServerParams +{ + char *plinkname; + char *inputfile; + PlinkColorFormat format; + int width; + int height; + int stride; + int frames; +} ServerParams; + +typedef struct _PlinkChannel +{ + PlinkChannelID id; + PlinkHandle plink; + PlinkPacket pkt; + int sendid; + int backid; + int exit; + int available_bufs; +} PlinkChannel; + +typedef struct _PictureBuffer +{ + unsigned int bus_address; + void *virtual_address; + unsigned int size; + int fd; +} PictureBuffer; + +void printUsage(char *name) +{ + printf("usage: %s [options]\n" + "\n" + " Available options:\n" + " -l plink file name (default: /tmp/plink.test)\n" + " -i input YUV file name (mandatory)\n" + " -f input color format (default: 3)\n" + " 2 - I420\n" + " 3 - NV12\n" + " 4 - P010\n" + " 14 - Bayer Raw 10bit\n" + " 15 - Bayer Raw 12bit\n" + " -w video width (mandatory)\n" + " -h video height (mandatory)\n" + " -s video buffer stride in bytes (default: video width)\n" + " -n number of frames to send (default: 10)\n" + "\n", name); +} + +void parseParams(int argc, char **argv, ServerParams *params) +{ + int i = 1; + memset(params, 0, sizeof(*params)); + params->plinkname = "/tmp/plink.test"; + params->format = PLINK_COLOR_FormatYUV420SemiPlanar; + params->frames = 10; + while (i < argc) + { + if (argv[i][0] != '-' || strlen(argv[i]) < 2) + { + i++; + continue; + } + + if (argv[i][1] == 'l') + { + if (++i < argc) + { + params->plinkname = argv[i++]; + } + } + else if (argv[i][1] == 'i') + { + if (++i < argc) + { + params->inputfile = argv[i++]; + } + } + else if (argv[i][1] == 'f') + { + if (++i < argc) + { + params->format = atoi(argv[i++]); + } + } + else if (argv[i][1] == 'w') + { + if (++i < argc) + { + params->width = atoi(argv[i++]); + } + } + else if (argv[i][1] == 'h') + { + if (++i < argc) + { + params->height = atoi(argv[i++]); + } + } + else if (argv[i][1] == 's') + { + if (++i < argc) + { + params->stride = atoi(argv[i++]); + } + } + else if (argv[i][1] == 'n') + { + if (++i < argc) + { + params->frames = atoi(argv[i++]); + } + } + } + + if ((params->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 || + params->format == PLINK_COLOR_FormatRawBayer10bit || + params->format == PLINK_COLOR_FormatRawBayer12bit) && + params->stride < params->width*2) + { + params->stride = params->width*2; + } + else if (params->stride < params->width) + { + params->stride = params->width; + } +} + +int checkParams(ServerParams *params) +{ + if (params->plinkname == NULL || + params->inputfile == NULL || + params->format == PLINK_COLOR_FormatUnused || + params->width == 0 || + params->height == 0 || + params->stride == 0) + return -1; + return 0; +} + +int getBufferSize(ServerParams *params) +{ + int size = 0; + switch (params->format) + { + case PLINK_COLOR_FormatYUV420Planar: + case PLINK_COLOR_FormatYUV420SemiPlanar: + size = params->stride * params->height * 3 / 2; + break; + case PLINK_COLOR_FormatYUV420SemiPlanarP010: + size = params->stride * params->height * 3; + break; + case PLINK_COLOR_FormatRawBayer10bit: + case PLINK_COLOR_FormatRawBayer12bit: + size = params->stride * params->height; + break; + default: + size = 0; + } + return size; +} + +void AllocateBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], unsigned int size, void *vmem) +{ + unsigned int buffer_size = (size + 0xFFF) & ~0xFFF; + VmemParams params; + params.size = buffer_size; + params.flags = VMEM_FLAG_CONTIGUOUS | VMEM_FLAG_4GB_ADDR; + for (int i = 0; i < NUM_OF_BUFFERS; i++) + { + params.fd = 0; + VMEM_allocate(vmem, ¶ms); + VMEM_mmap(vmem, ¶ms); + VMEM_export(vmem, ¶ms); + printf("[SERVER] mmap %p from %x with size %d, dma-buf fd %d\n", + params.vir_address, params.phy_address, params.size, params.fd); + picbuffers[i].virtual_address = params.vir_address; + picbuffers[i].bus_address = params.phy_address; + picbuffers[i].size = buffer_size; + picbuffers[i].fd = params.fd; + } +} + +void FreeBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], void *vmem) +{ + VmemParams params; + memset(¶ms, 0, sizeof(params)); + for (int i = 0; i < NUM_OF_BUFFERS; i++) + { + close(picbuffers[i].fd); + params.size = picbuffers[i].size; + params.vir_address = picbuffers[i].virtual_address; + params.phy_address = picbuffers[i].bus_address; + VMEM_free(vmem, ¶ms); + } +} + +void ProcessOneFrame(void *virtual_address, FILE *fp, int size) +{ + if(virtual_address != MAP_FAILED && fp != NULL) + { + if (fread(virtual_address, size, 1, fp) < 1) + fprintf(stderr, "ERROR: Failed to read %d bytes from file (eof %d): %s\n", + size, feof(fp), strerror(errno)); + } +} + +void constructYuvInfo(PlinkYuvInfo *info, ServerParams *params, unsigned int bus_address, int id) +{ + int size_y = params->width * params->stride; + int size_uv = size_y / 2; + int size_yuv = size_y + size_uv; + + info->header.type = PLINK_TYPE_2D_YUV; + info->header.size = DATA_SIZE(*info); + info->header.id = id + 1; + + info->format = params->format; + info->bus_address_y = bus_address; + info->bus_address_u = info->bus_address_y + size_y; + info->bus_address_v = info->bus_address_u + (size_uv / 2); + info->pic_width = params->width; + info->pic_height = params->height; + info->stride_y = params->stride; + info->stride_u = + params->format == PLINK_COLOR_FormatYUV420Planar ? + params->stride/2 : params->stride; + info->stride_v = info->stride_u; +} + +void constructRawInfo(PlinkRawInfo *info, ServerParams *params, unsigned int bus_address, int id) +{ + int size_y = params->width * params->stride; + int size_uv = size_y / 2; + int size_yuv = size_y + size_uv; + + info->header.type = PLINK_TYPE_2D_RAW; + info->header.size = DATA_SIZE(*info); + info->header.id = id + 1; + + info->format = params->format; + info->bus_address = bus_address; + info->img_width = params->width; + info->img_height = params->height; + info->stride = params->stride; +} + +int getBufferCount(PlinkPacket *pkt) +{ + int ret = 0; + for (int i = 0; i < pkt->num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]); + if (hdr->type == PLINK_TYPE_MESSAGE) + { + int *data = (int *)(pkt->list[i] + DATA_HEADER_SIZE); + if (*data == PLINK_EXIT_CODE) + { + ret |= 0x80000000; // set bit 31 to 1 to indicate 'exit' + } + else if (*data >= 0) + ret++; + } + } + + return ret; +} + +void retreiveSentBuffers(PlinkHandle plink, PlinkChannel *channel) +{ + PlinkStatus sts = PLINK_STATUS_OK; + while (channel->available_bufs < NUM_OF_BUFFERS && sts == PLINK_STATUS_OK) + { + do + { + sts = PLINK_recv(plink, channel->id, &channel->pkt); + int count = getBufferCount(&channel->pkt); + if (count > 0) + { + channel->available_bufs += count; + } + } while (sts == PLINK_STATUS_MORE_DATA); + } +} + + +int main(int argc, char **argv) { + PlinkStatus sts = PLINK_STATUS_OK; + ServerParams params; + PlinkChannel channel[2]; + PlinkHandle plink = NULL; + PlinkYuvInfo pic = {0}; + PlinkRawInfo img = {0}; + PlinkMsg msg; + + parseParams(argc, argv, ¶ms); + if (checkParams(¶ms) != 0) + { + printUsage(argv[0]); + return 0; + } + + FILE *fp = fopen(params.inputfile, "rb"); + if (fp == NULL) + errExit("fopen"); + + void *vmem = NULL; + if (VMEM_create(&vmem) != VMEM_STATUS_OK) + errExit("Failed to create VMEM."); + + int width = params.width; + int height = params.height; + int stride = params.stride; + int frames = params.frames; + int size = getBufferSize(¶ms); + if (size == 0) + errExit("Wrong format or wrong resolution."); + PictureBuffer picbuffers[NUM_OF_BUFFERS]; + AllocateBuffers(picbuffers, size, vmem); + + sts = PLINK_create(&plink, params.plinkname, PLINK_MODE_SERVER); + + memset(&channel[0], 0, sizeof(channel[0])); + channel[0].available_bufs = NUM_OF_BUFFERS; + sts = PLINK_connect(plink, &channel[0].id); + + int frmcnt = 0; + do { + int sendid = channel[0].sendid; + ProcessOneFrame(picbuffers[sendid].virtual_address, fp, size); + if (params.format == PLINK_COLOR_FormatRawBayer8bit || + params.format == PLINK_COLOR_FormatRawBayer10bit || + params.format == PLINK_COLOR_FormatRawBayer12bit) + { + constructRawInfo(&img, ¶ms, picbuffers[sendid].bus_address, sendid); + printf("[SERVER] Processed frame %d 0x%010llx: %dx%d, stride %d\n", + sendid, img.bus_address, img.img_width, img.img_height, img.stride); + channel[0].pkt.list[0] = &img; + } + else // YUV + { + constructYuvInfo(&pic, ¶ms, picbuffers[sendid].bus_address, sendid); + printf("[SERVER] Processed frame %d 0x%010llx: %dx%d, stride = luma %d, chroma %d\n", + sendid, pic.bus_address_y, + pic.pic_width, pic.pic_height, + pic.stride_y, pic.stride_u); + channel[0].pkt.list[0] = &pic; + } + + channel[0].pkt.num = 1; + channel[0].pkt.fd = picbuffers[sendid].fd; + sts = PLINK_send(plink, channel[0].id, &channel[0].pkt); + channel[0].sendid = (channel[0].sendid + 1) % NUM_OF_BUFFERS; + channel[0].available_bufs -= 1; + + int timeout = 0; + if (channel[0].available_bufs == 0) + timeout = 60000; // wait up to 60 seconds if buffers are used up + + if (PLINK_wait(plink, channel[0].id, timeout) == PLINK_STATUS_OK) + { + do + { + sts = PLINK_recv(plink, channel[0].id, &channel[0].pkt); + int count = getBufferCount(&channel[0].pkt); + if (count < 0) + channel[0].exit = 1; + channel[0].available_bufs += count; + } while (sts == PLINK_STATUS_MORE_DATA); + } + + frmcnt++; + } while (channel[0].exit == 0 && frmcnt < frames); + +cleanup: + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = PLINK_EXIT_CODE; + channel[0].pkt.list[0] = &msg; + channel[0].pkt.num = 1; + channel[0].pkt.fd = PLINK_INVALID_FD; + sts = PLINK_send(plink, channel[0].id, &channel[0].pkt); + retreiveSentBuffers(plink, &channel[0]); + PLINK_recv_ex(plink, channel[0].id, &channel[0].pkt, 1000); + //sleep(1); // Sleep one second to make sure client is ready for exit + if (vmem) + FreeBuffers(picbuffers, vmem); + PLINK_close(plink, PLINK_CLOSE_ALL); + VMEM_destroy(vmem); + if (fp != NULL) + fclose(fp); + exit(EXIT_SUCCESS); +} + diff --git a/test/plink_stitcher.c b/test/plink_stitcher.c new file mode 100644 index 0000000..1bdbe92 --- /dev/null +++ b/test/plink_stitcher.c @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2022 Alibaba Group. All rights reserved. + * License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "process_linker_types.h" +#include "video_mem.h" + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#define MAX_NUM_OF_INPUTS 4 +#define NUM_OF_BUFFERS 5 +#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \ + } while (0) +#define STITCHER_MIN(a, b) ((a) < (b) ? (a) : (b)) + + +typedef enum _StitchLayout +{ + STITCH_LAYOUT_Vertical = 0, + STITCH_LAYOUT_Horizontal, + STITCH_LAYOUT_Matrix, + STITCH_LAYOUT_Max +} StitchLayout; + +typedef struct _StitherRegion +{ + int width; + int height; + int offset_uv; + int offset_y; +} StitcherRegion; + +typedef struct _StitcherParams +{ + char *in_name[MAX_NUM_OF_INPUTS]; + char *out_name; + StitchLayout layout; + PlinkColorFormat format; + int width; + int height; + int stride; +} StitcherParams; + +typedef struct _StitcherPort +{ + char *name; + int index; + PlinkChannelID id; + PlinkHandle plink; + int sendid; + int backid; + int *exit; + int available_bufs; + void *vmem; + pthread_mutex_t pic_mutex; + pthread_mutex_t *count_mutex; + sem_t *sem_ready; + sem_t *sem_done; + StitchLayout layout; + PlinkColorFormat format; + void *buffer; + int width; + int height; + int stride; + int offset; + int offset_uv; + int *in_count; +} StitcherPort; + +typedef struct _StitcherContext +{ + StitcherPort in[MAX_NUM_OF_INPUTS]; + StitcherPort out; + pthread_mutex_t count_mutex; + sem_t sem_ready; + sem_t sem_done; + int in_count; + int exitcode; +} StitcherContext; + +typedef struct _PictureBuffer +{ + unsigned int bus_address; + void *virtual_address; + unsigned int size; + int fd; +} PictureBuffer; + +static void printUsage(char *name) +{ + printf("usage: %s [options]\n" + "\n" + " Stitch multiple pictures to one. Maximum # of pictures to be stitched is %d\n" + " Available options:\n" + " -i plink file name of input port #n (default: /tmp/plink.stitch.in). n is 0 based.\n" + " -o plink file name of output port (default: /tmp/plink.stitch.out)\n" + " -l layout (default: 0)\n" + " 0 - vertical\n" + " 1 - horizontal\n" + " 2 - matrix\n" + " -f output color format (default: 3)\n" + " 3 - NV12\n" + " -w output video width (default: 800)\n" + " -h output video height (default: 1280)\n" + " -s output video buffer stride (default: video width)\n" + " --help print this message\n" + "\n", name, MAX_NUM_OF_INPUTS); +} + +static void parseParams(int argc, char **argv, StitcherParams *params) +{ + int i = 1; + memset(params, 0, sizeof(*params)); + params->in_name[0] = "/tmp/plink.stitch.in0"; + params->in_name[1] = "/tmp/plink.stitch.in1"; + params->in_name[2] = "/tmp/plink.stitch.in2"; + params->in_name[3] = "/tmp/plink.stitch.in3"; + params->out_name = "/tmp/plink.stitch.out"; + params->layout = STITCH_LAYOUT_Vertical; + params->format = PLINK_COLOR_FormatYUV420SemiPlanar; + params->width = 800; + params->height = 1280; + while (i < argc) + { + if (argv[i][0] != '-' || strlen(argv[i]) < 2) + { + i++; + continue; + } + + if (argv[i][1] == 'i') + { + if (++i < argc) + { + if (strlen(argv[i-1]) > 2) // input name + { + int id = atoi(argv[i-1]+2); + if (id < MAX_NUM_OF_INPUTS) + params->in_name[id] = argv[i++]; + } + else + params->in_name[0] = argv[i++]; + } + } + else if (argv[i][1] == 'o') + { + if (++i < argc) + params->out_name = argv[i++]; + } + else if (argv[i][1] == 'l') + { + if (++i < argc) + params->layout = atoi(argv[i++]); + } + else if (argv[i][1] == 'f') + { + if (++i < argc) + params->format = atoi(argv[i++]); + } + else if (argv[i][1] == 'w') + { + if (++i < argc) + params->width = atoi(argv[i++]); + } + else if (argv[i][1] == 'h') + { + if (++i < argc) + params->height = atoi(argv[i++]); + } + else if (argv[i][1] == 's') + { + if (++i < argc) + params->stride = atoi(argv[i++]); + } + else if (strcmp(argv[i], "--help") == 0) + { + params->layout = STITCH_LAYOUT_Max; + i++; + } + } + + if (params->stride == 0) + params->stride = params->width; + + printf("[STITCHER] Input 0 Name : %s\n", params->in_name[0]); + printf("[STITCHER] Input 1 Name : %s\n", params->in_name[1]); + printf("[STITCHER] Input 2 Name : %s\n", params->in_name[2]); + printf("[STITCHER] Input 3 Name : %s\n", params->in_name[3]); + printf("[STITCHER] Output Name : %s\n", params->out_name); + printf("[STITCHER] Output Layout : %d\n", params->layout); + printf("[STITCHER] Output Format : %d\n", params->format); + printf("[STITCHER] Output Resolution : %dx%d\n", params->width, params->height); + printf("[STITCHER] Output Stride : %d\n", params->stride); +} + +static int checkParams(StitcherParams *params) +{ + if (params->format != PLINK_COLOR_FormatYUV420SemiPlanar || + params->layout >= STITCH_LAYOUT_Max) + return -1; + return 0; +} + +static int getBufferSize(StitcherParams *params) +{ + int size = 0; + switch (params->format) + { + case PLINK_COLOR_FormatYUV420Planar: + case PLINK_COLOR_FormatYUV420SemiPlanar: + size = params->stride * params->height * 3 / 2; + break; + default: + size = 0; + } + return size; +} + +static void AllocateBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], unsigned int size, void *vmem) +{ + unsigned int buffer_size = (size + 0xFFF) & ~0xFFF; + VmemParams params; + params.size = buffer_size; + params.flags = VMEM_FLAG_CONTIGUOUS | VMEM_FLAG_4GB_ADDR; + for (int i = 0; i < NUM_OF_BUFFERS; i++) + { + params.fd = 0; + VMEM_allocate(vmem, ¶ms); + VMEM_mmap(vmem, ¶ms); + VMEM_export(vmem, ¶ms); + printf("[STITCHER] mmap %p from %x with size %d, dma-buf fd %d\n", + params.vir_address, params.phy_address, params.size, params.fd); + picbuffers[i].virtual_address = params.vir_address; + picbuffers[i].bus_address = params.phy_address; + picbuffers[i].size = buffer_size; + picbuffers[i].fd = params.fd; + } +} + +static void FreeBuffers(PictureBuffer picbuffers[NUM_OF_BUFFERS], void *vmem) +{ + VmemParams params; + memset(¶ms, 0, sizeof(params)); + for (int i = 0; i < NUM_OF_BUFFERS; i++) + { + close(picbuffers[i].fd); + params.size = picbuffers[i].size; + params.vir_address = picbuffers[i].virtual_address; + params.phy_address = picbuffers[i].bus_address; + VMEM_free(vmem, ¶ms); + } +} + +static void getRegion(StitcherPort *out, int index, int in_count, StitcherRegion *region) +{ + region->offset_uv = 0; //out->stride * out->height; + if (out->layout == STITCH_LAYOUT_Horizontal) + { + int width = (out->width / in_count + 1) & ~1; + region->offset_y = width * index; + region->offset_uv += width * index; + region->width = width; + region->height = out->height; + } + else if (out->layout == STITCH_LAYOUT_Matrix && in_count >= 3) + { + int y = index >> 1; + int x = index & 1; + int width = (out->width / 2 + 1) & ~1; + int height = (out->height / 2 + 1) & ~1; + region->offset_y = out->stride * (height * y); + region->offset_y += width * x; + region->offset_uv += out->stride * (height * y / 2); + region->offset_uv += width * x; + region->width = width; + region->height = height; + } + else // (out->layout == STITCH_LAYOUT_Vertical) + { + int height = (out->height / in_count + 1) & ~1; + region->offset_y = out->stride * (height * index); + region->offset_uv += out->stride * (height * index / 2); + region->width = out->width; + region->height = height; + } +} + +static int stitchOneFrame(StitcherContext *ctx) +{ + StitcherPort *in = NULL; + StitcherPort *out = &ctx->out; + int in_count = 0; + do + { + pthread_mutex_lock(out->count_mutex); + in_count = ctx->in_count; + pthread_mutex_unlock(out->count_mutex); + if (in_count > 0) + break; + else + sleep(1); + } while (1); + + for (int i = 0; i < in_count; i++) + { + in = &ctx->in[i]; + if (in->index == 0) + { + sem_wait(&ctx->sem_ready); // wait for picture from input port0. + if (ctx->exitcode == 1) + return 1; + } + StitcherRegion region; + getRegion(out, in->index, in_count, ®ion); + pthread_mutex_lock(&in->pic_mutex); + int width = STITCHER_MIN(region.width, in->width); + int height = STITCHER_MIN(region.height, in->height); + void *dst = out->buffer + out->offset + region.offset_y; + void *src = in->buffer + in->offset; + //fprintf(stderr, "%d: %p, %d, %d, %p, %d\n", in->index, out->buffer, out->offset, region.offset_y, in->buffer, in->offset); + //fprintf(stderr, "%d: %dx%d, %d, %d\n", in->index, width, height, in->stride, out->stride); + if (in->available_bufs > 0) + { + if (in->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 || + in->format == PLINK_COLOR_FormatRawBayer10bit || + in->format == PLINK_COLOR_FormatRawBayer12bit) + { + int shift = in->format == PLINK_COLOR_FormatYUV420SemiPlanarP010 ? 2 : 4; + unsigned int temp[4]; + for (int h = 0; h < height; h++) + { + unsigned int *dst32 = (unsigned int *)dst; + unsigned short *src16 = (unsigned short *)src; + for (int w = 0; w < width; w+=4) + { + temp[0] = ((unsigned int)(src16[w+0] >> shift) & 0xFF); + temp[1] = ((unsigned int)(src16[w+1] >> shift) & 0xFF) << 8; + temp[2] = ((unsigned int)(src16[w+2] >> shift) & 0xFF) << 16; + temp[3] = ((unsigned int)(src16[w+3] >> shift) & 0xFF) << 24; + dst32[w>>2] = temp[0] | temp[1] | temp[2] | temp[3]; + } + dst += out->stride; + src += in->stride; + } + } + else + { + for (int h = 0; h < height; h++) + { + memcpy(dst, src, width); + dst += out->stride; + src += in->stride; + } + } + } + else + { + for (int h = 0; h < height; h++) + { + memset(dst, 0x00, width); + dst += out->stride; + } + } + dst = out->buffer + out->offset_uv + region.offset_uv; + src = in->buffer + in->offset_uv; + //fprintf(stderr, "%d: %p, %d, %d, %p, %d\n", in->index, out->buffer, out->offset_uv, region.offset_uv, in->buffer, in->offset_uv); + if (in->format == PLINK_COLOR_FormatYUV420SemiPlanar && + in->available_bufs > 0) + { + for (int h = 0; h < height/2; h++) + { + memcpy(dst, src, width); + dst += out->stride; + src += in->stride; + } + } + else // treat all the other formats as monochrome + { + for (int h = 0; h < height/2; h++) + { + memset(dst, 0x80, width); + dst += out->stride; + } + } + pthread_mutex_unlock(&in->pic_mutex); + if (in->index == 0) + sem_post(&ctx->sem_done); // Resume input port0. + // !Assume output thread is fast enough, so input port0 won't be blocked for long time. + } + + return 0; +} + +static void constructYuvInfo(PlinkYuvInfo *info, StitcherParams *params, unsigned int bus_address, int id) +{ + int size_y = params->width * params->stride; + int size_uv = size_y / 2; + int size_yuv = size_y + size_uv; + + info->header.type = PLINK_TYPE_2D_YUV; + info->header.size = DATA_SIZE(*info); + info->header.id = id + 1; + + info->format = params->format; + info->bus_address_y = bus_address; + info->bus_address_u = info->bus_address_y + size_y; + info->bus_address_v = info->bus_address_u + (size_uv / 2); + info->pic_width = params->width; + info->pic_height = params->height; + info->stride_y = params->stride; + info->stride_u = + params->format == PLINK_COLOR_FormatYUV420Planar ? + params->stride/2 : params->stride; + info->stride_v = info->stride_u; +} + +int getBufferCount(PlinkPacket *pkt) +{ + int ret = 0; + for (int i = 0; i < pkt->num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(pkt->list[i]); + if (hdr->type == PLINK_TYPE_MESSAGE) + { + int *data = (int *)(pkt->list[i] + DATA_HEADER_SIZE); + if (*data == PLINK_EXIT_CODE) + { + ret |= 0x80000000; // set bit 31 to 1 to indicate 'exit' + } + else if (*data >= 0) + ret++; + } + } + + return ret; +} + +static void retreiveSentBuffers(PlinkHandle plink, StitcherPort *port) +{ + PlinkStatus sts = PLINK_STATUS_OK; + PlinkPacket pkt = {0}; + while (port->available_bufs < NUM_OF_BUFFERS && sts == PLINK_STATUS_OK) + { + do + { + sts = PLINK_recv(plink, port->id, &pkt); + int count = getBufferCount(&pkt); + if (count > 0) + { + port->available_bufs += count; + } + } while (sts == PLINK_STATUS_MORE_DATA); + } +} + +static void *input_thread(void *args) +{ + StitcherPort *port = (StitcherPort *)args; + PlinkHandle plink = NULL; + PlinkStatus sts = PLINK_STATUS_OK; + void *vmem = port->vmem; + + pthread_mutex_init(&port->pic_mutex, NULL); + + if (PLINK_create(&plink, port->name, PLINK_MODE_CLIENT) != PLINK_STATUS_OK) + return NULL; + + do + { + sts = PLINK_connect_ex(plink, NULL, 1000); + } while (sts == PLINK_STATUS_TIMEOUT && *port->exit == 0); + if (sts != PLINK_STATUS_OK) + { + PLINK_close(plink, 0); + return NULL; + } + + pthread_mutex_lock(port->count_mutex); + port->index = *(port->in_count); + *(port->in_count) = *(port->in_count) + 1; + pthread_mutex_unlock(port->count_mutex); + + PlinkPacket sendpkt = {0}; + PlinkPacket recvpkt = {0}; + PlinkMsg msg = {0}; + VmemParams params; + int exitcode = 0; + do { + sts = PLINK_recv(plink, 0, &recvpkt); + if (sts == PLINK_STATUS_ERROR) + break; + + for (int i = 0; i < recvpkt.num; i++) + { + PlinkDescHdr *hdr = (PlinkDescHdr *)(recvpkt.list[i]); + if (hdr->type == PLINK_TYPE_2D_YUV || + hdr->type == PLINK_TYPE_2D_RAW) + { + // return previous buffer to source + if (sendpkt.num > 0) + { + if (port->index == 0) + sem_wait(port->sem_done); + pthread_mutex_lock(&port->pic_mutex); + if (VMEM_release(vmem, ¶ms) != VMEM_STATUS_OK) + fprintf(stderr, "[STITCHER] ERROR: Failed to release buffer.\n"); + sts = PLINK_send(plink, 0, &sendpkt); + if (sts == PLINK_STATUS_ERROR) + break; + if (sendpkt.fd != PLINK_INVALID_FD) + close(sendpkt.fd); + port->available_bufs--; + sendpkt.num = 0; + } + else + pthread_mutex_lock(&port->pic_mutex); + + memset(¶ms, 0, sizeof(params)); + if (recvpkt.fd != PLINK_INVALID_FD) + { + params.fd = recvpkt.fd; + if (VMEM_import(vmem, ¶ms) != VMEM_STATUS_OK) + break; + if (VMEM_mmap(vmem, ¶ms) != VMEM_STATUS_OK) + break; + } + + if (port->index == 0) + sem_post(port->sem_ready); // signal output thread that one picture is ready + } + + if (hdr->type == PLINK_TYPE_2D_YUV) + { + PlinkYuvInfo *pic = (PlinkYuvInfo *)(recvpkt.list[i]); + printf("[STITCHER] Input%d: Received YUV frame %d 0x%010llx from %s: fd %d, %dx%d, stride = luma %d, chroma %d\n", + port->index, pic->header.id, pic->bus_address_y, port->name, recvpkt.fd, + pic->pic_width, pic->pic_height, + pic->stride_y, pic->stride_u); + + port->buffer = params.vir_address; + port->format = pic->format; + port->width = pic->pic_width; + port->height = pic->pic_height; + port->stride = pic->stride_y; + port->offset = pic->offset_y; + port->offset_uv = pic->offset_u > 0 ? pic->offset_u : (pic->offset_y + pic->pic_height*pic->stride_y); + } + else if (hdr->type == PLINK_TYPE_2D_RAW) + { + PlinkRawInfo *img = (PlinkRawInfo *)(recvpkt.list[i]); + printf("[STITCHER] Input%d: Received RAW frame %d 0x%010llx from %s: fd %d, %dx%d, stride %d\n", + port->index, img->header.id, img->bus_address, port->name, recvpkt.fd, + img->img_width, img->img_height, img->stride); + + port->buffer = params.vir_address; + port->format = img->format; + port->width = img->img_width; + port->height = img->img_height; + port->stride = img->stride; + port->offset = img->offset; + } + else if (hdr->type == PLINK_TYPE_MESSAGE) + { + PlinkMsg *msg = (PlinkMsg *)(recvpkt.list[i]); + if (msg->msg == PLINK_EXIT_CODE) + { + exitcode = 1; + printf("[STITCHER] Input %d: Exit\n", port->index); + break; + } + } + + if (hdr->type == PLINK_TYPE_2D_YUV || + hdr->type == PLINK_TYPE_2D_RAW) + { + port->available_bufs++; + pthread_mutex_unlock(&port->pic_mutex); + + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = hdr->id; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = recvpkt.fd; + } + } + } while (exitcode == 0 && *port->exit == 0 && sts != PLINK_STATUS_ERROR); + + if (sendpkt.num > 0) + { + pthread_mutex_lock(&port->pic_mutex); + if (VMEM_release(vmem, ¶ms) != VMEM_STATUS_OK) + fprintf(stderr, "[STITCHER] ERROR: Failed to release buffer.\n"); + sts = PLINK_send(plink, 0, &sendpkt); + port->available_bufs--; + pthread_mutex_unlock(&port->pic_mutex); + } + + if (port->index == 0) + { + *port->exit = 1; + sem_post(port->sem_ready); + } + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = PLINK_EXIT_CODE; + sendpkt.list[0] = &msg; + sendpkt.num = 1; + sendpkt.fd = PLINK_INVALID_FD; + sts = PLINK_send(plink, 0, &sendpkt); + PLINK_close(plink, 0); + pthread_mutex_destroy(&port->pic_mutex); + return NULL; +} + +int main(int argc, char **argv) { + PlinkStatus sts = PLINK_STATUS_OK; + StitcherParams params; + StitcherContext ctx; + PlinkHandle plink = NULL; + PlinkPacket pkt = {0}; + PlinkYuvInfo pic; + PlinkMsg msg; + + parseParams(argc, argv, ¶ms); + if (checkParams(¶ms) != 0) + { + printUsage(argv[0]); + return 0; + } + + memset(&ctx, 0, sizeof(ctx)); + + void *vmem = NULL; + if (VMEM_create(&vmem) != VMEM_STATUS_OK) + errExit("Failed to create VMEM."); + + int width = params.width; + int height = params.height; + int stride = params.stride; + StitchLayout layout = params.layout; + int size = getBufferSize(¶ms); + if (size == 0) + errExit("Wrong format or wrong resolution."); + PictureBuffer picbuffers[NUM_OF_BUFFERS]; + AllocateBuffers(picbuffers, size, vmem); + + sem_init(&ctx.sem_ready, 0, 0); + sem_init(&ctx.sem_done, 0, 0); + pthread_mutex_init(&ctx.count_mutex, NULL); + + pthread_t thread_in[MAX_NUM_OF_INPUTS]; + pthread_attr_t attr; + pthread_attr_init(&attr); + for (int i = 0; i < MAX_NUM_OF_INPUTS; i++) + { + ctx.in[i].name = params.in_name[i]; + ctx.in[i].count_mutex = &ctx.count_mutex; + ctx.in[i].sem_ready = &ctx.sem_ready; + ctx.in[i].sem_done = &ctx.sem_done; + ctx.in[i].in_count = &ctx.in_count; + ctx.in[i].exit = &ctx.exitcode; + ctx.in[i].vmem = vmem; + if (pthread_create(&thread_in[i], &attr, input_thread, &ctx.in[i]) != 0) + fprintf(stderr, "[STITCHER] ERROR: Failed to create thread for input %d\n", i); + } + + sts = PLINK_create(&plink, params.out_name, PLINK_MODE_SERVER); + + StitcherPort *out = &ctx.out; + out->available_bufs = NUM_OF_BUFFERS; + out->count_mutex = &ctx.count_mutex; + out->sem_ready = &ctx.sem_ready; + out->sem_done = &ctx.sem_done; + out->format = params.format; + out->layout = params.layout; + out->width = params.width; + out->height = params.height; + out->stride = params.stride; + out->offset = 0; + out->offset_uv = params.height * params.stride; + out->exit = &ctx.exitcode; + sts = PLINK_connect(plink, &out->id); + + int exitcode = 0; + do { + int sendid = out->sendid; + out->buffer = picbuffers[sendid].virtual_address; + if (stitchOneFrame(&ctx) != 0) + break; + constructYuvInfo(&pic, ¶ms, picbuffers[sendid].bus_address, sendid); + printf("[STITCHER] Processed frame %d 0x%010llx: %dx%d, stride = luma %d, chroma %d\n", + sendid, pic.bus_address_y, + pic.pic_width, pic.pic_height, + pic.stride_y, pic.stride_u); + + pkt.list[0] = &pic; + pkt.num = 1; + pkt.fd = picbuffers[sendid].fd; + sts = PLINK_send(plink, out->id, &pkt); + out->sendid = (out->sendid + 1) % NUM_OF_BUFFERS; + out->available_bufs -= 1; + + int timeout = out->available_bufs == 0 ? 100 : 0; + if (PLINK_wait(plink, out->id, timeout) == PLINK_STATUS_OK) + { + do + { + sts = PLINK_recv(plink, out->id, &pkt); + int count = getBufferCount(&pkt); + if (count < 0) + exitcode = 1; + out->available_bufs += count; + } while (sts == PLINK_STATUS_MORE_DATA); + } + } while (exitcode == 0); + +cleanup: + ctx.exitcode = 1; + sem_post(&ctx.sem_done); + msg.header.type = PLINK_TYPE_MESSAGE; + msg.header.size = DATA_SIZE(PlinkMsg); + msg.msg = PLINK_EXIT_CODE; + pkt.list[0] = &msg; + pkt.num = 1; + pkt.fd = PLINK_INVALID_FD; + sts = PLINK_send(plink, out->id, &pkt); + retreiveSentBuffers(plink, out); + PLINK_recv_ex(plink, out->id, &pkt, 1000); + for (int i = 0; i < MAX_NUM_OF_INPUTS; i++) + pthread_join(thread_in[i], NULL); + //sleep(1); // Sleep one second to make sure client is ready for exit + if (vmem) + FreeBuffers(picbuffers, vmem); + PLINK_close(plink, PLINK_CLOSE_ALL); + VMEM_destroy(vmem); + pthread_mutex_destroy(&ctx.count_mutex); + sem_destroy(&ctx.sem_ready); + sem_destroy(&ctx.sem_done); + exit(EXIT_SUCCESS); +} + diff --git a/user_mode/Makefile b/user_mode/Makefile deleted file mode 100755 index fcd939f..0000000 --- a/user_mode/Makefile +++ /dev/null @@ -1,75 +0,0 @@ - -CFLAGS = -Wall -D_GNU_SOURCE -D_REENTRANT -D_THREAD_SAFE -O2 -Werror -Wno-unused -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-strict-overflow -Wno-array-bounds -Wno-shift-negative-value -Wempty-body -Wtype-limits -Wno-unused-result -fPIC -Wmissing-field-initializers -std=gnu99 - -INCLUDE += -I../kernel_mode - -SRCS = isp_venc_shake_hal.c -OBJS = $(SRCS:.c=.o) - -#source search path -vpath %.c - -# name of the outputfile (library) -IVSLIB = libivs.a -IVSSLIB = libivs.so - -#Here are rules for building codes and generating object library. -all: tags - @echo --------------------------------------- - @echo "Usage: make [ system | testdata | versatile | integrator | android]" - @echo "system - PC system model (== pclinux)" - @echo "testdata - PC system model for test data creation" - @echo "eval - PC system model for evaluation with frame limit" - @echo "versatile - ARM versatile with FPGA HW" - @echo "integrator - ARM integrator with FPGA HW" - @echo "android - ARM android" - @echo "NOTE! Make sure to do 'make clean'" - @echo "between compiling to different targets!" - @echo --------------------------------------- - -.PHONY: system_lib system testdata clean depend - -evaluation: eval -eval: system - -system_static: testdata -system: testdata - -# for libva -system_lib: system - -testdata: .depend $(IVSLIB) - - -.PHONY: hwlinux -hwlinux: -hwlinux: .depend $(IVSLIB) $(IVSSLIB) - -$(IVSLIB): $(OBJS) - @echo "[AR] $@" - $(AR) rcs $(IVSLIB) $(OBJS) - -$(IVSSLIB): $(OBJS) - $(CC) -fPIC -Wl,-no-undefined -shared $(CFLAGS) $(OBJS) -lm -lpthread -o $(IVSSLIB) - - -%.o: %.c - $(CC) -c $(CFLAGS) $(INCLUDE) $< -o $@ - -clean: - $(RM) $(IVSLIB) $(IVSSLIB) - $(RM) .depend - $(RM) *.o *.gcno *.gcda - -tags: - ctags ../kernel_mode/*h ./*[ch] - -depend .depend: $(SRCS) - @echo "[CC] $@" - @$(CC) -M $(DEBFLAGS) $(INCLUDE) $^ > .depend - -ifneq (clean, $(findstring clean, $(MAKECMDGOALS))) -ifeq (.depend, $(wildcard .depend)) -include .depend -endif -endif diff --git a/user_mode/isp_venc_shake_hal.c b/user_mode/isp_venc_shake_hal.c deleted file mode 100644 index 676f5f5..0000000 --- a/user_mode/isp_venc_shake_hal.c +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. - * License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "isp_venc_shake_driver.h" -#include "isp_venc_shake_hal.h" -#include - -#ifndef NULL -#define NULL ((void *)0) -#endif - - -#ifndef IVS_MODULE_PATH -#define IVS_MODULE_PATH "/dev/shake" -#endif - -typedef struct _IspVencShake -{ - unsigned int fd; - struct ivs_parameter params; -} IspVencShake; - -IvsStatus createIspVencShake(void **ivs) -{ - IvsStatus status = IVS_STATUS_OK; - IspVencShake *shake = NULL; - unsigned int fd; - - shake = (IspVencShake *)malloc(sizeof(IspVencShake)); - if (NULL == shake) - return IVS_STATUS_ERROR; - - fd = open("/dev/shake", O_RDONLY); - if (fd == -1) - { - free(shake); - return IVS_STATUS_ERROR; - } - - shake->fd = fd; - *ivs = shake; - - return status; -} - -IvsStatus configIspVencShake(void *ivs, IvsConfig *config) -{ - IvsStatus status = IVS_STATUS_OK; - IspVencShake *shake = (IspVencShake *)ivs; - struct ivs_parameter *params = NULL; - unsigned int buffer_height; - - if (ivs == NULL || config == NULL) - return IVS_STATUS_ERROR; - - params = &shake->params; - buffer_height = config->mode = IVS_BUFFER_MODE_SLICE ? - config->buffer_height : config->pic_height * 3 / 2; - - params->pic_width = config->pic_width; - params->pic_height = config->pic_height; - params->encode_width = config->encode_width; - params->encode_height = config->encode_height; - params->wid_y = 1; - params->wid_uv = 2; - params->sram_size = config->pic_width * buffer_height; - params->encode_n = config->encode == IVS_ENCODER_FORMAT_H264 ? 16 : 64; - params->stride_y = config->stride_y; - params->stride_uv = config->stride_uv; - params->encode_x = config->encode_x; - params->encode_y = config->encode_y; - params->int_mask = 0; - - if (ioctl(shake->fd, THEAD_IOCH_CONFIG_IVS, params) == -1) - { - status = IVS_STATUS_ERROR; - } - - return status; -} - -IvsStatus startIspVencShake(void *ivs) -{ - IvsStatus status = IVS_STATUS_OK; - IspVencShake *shake = (IspVencShake *)ivs; - - if (ivs == NULL) - return IVS_STATUS_ERROR; - - if (ioctl(shake->fd, THEAD_IOCH_START_IVS, NULL) == -1) - { - status = IVS_STATUS_ERROR; - } - - return status; -} - -IvsStatus resetIspVencShake(void *ivs) -{ - IvsStatus status = IVS_STATUS_OK; - IspVencShake *shake = (IspVencShake *)ivs; - - if (ivs == NULL) - return IVS_STATUS_ERROR; - - if (ioctl(shake->fd, THEAD_IOCH_RESET_IVS, NULL) == -1) - { - status = IVS_STATUS_ERROR; - } - - return status; -} - -IvsStatus getIspVencShakeState(void *ivs, int *state) -{ - IvsStatus status = IVS_STATUS_OK; - IspVencShake *shake = (IspVencShake *)ivs; - - if (ivs == NULL || state == NULL) - return IVS_STATUS_ERROR; - - if (ioctl(shake->fd, THEAD_IOCH_GET_STATE, state) == -1) - { - status = IVS_STATUS_ERROR; - } - - return status; -} - -void destroyIspVencShake(void *ivs) -{ - IspVencShake *shake = (IspVencShake *)ivs; - - if (NULL == ivs) - return; - - if (shake->fd != -1) - close(shake->fd); - - free(ivs); -} - diff --git a/user_mode/isp_venc_shake_hal.h b/user_mode/isp_venc_shake_hal.h deleted file mode 100644 index 8d0b8cd..0000000 --- a/user_mode/isp_venc_shake_hal.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021-2022 Alibaba Group. All rights reserved. - * License-Identifier: Apache-2.0 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _ISP_VENC_SHAKE_HAL_H_ -#define _ISP_VENC_SHAKE_HAL_H_ - -#ifndef IVS_MODULE_PATH -#define IVS_MODULE_PATH "/tmp/dev/theadivs" -#endif - -typedef enum _IvsStatus -{ - IVS_STATUS_OK = 0, - IVS_STATUS_ERROR = -1, -} IvsStatus; - -typedef enum _IvsState -{ - IVS_STATE_IDLE, - IVS_STATE_READY, - IVS_STATE_RUNNING, - IVS_STATE_ERROR -} IvsState; - -typedef enum _IvsBufferMode -{ - IVS_BUFFER_MODE_FRAME = 0, - IVS_BUFFER_MODE_SLICE, - IVS_BUFFER_MODE_MAX -} IvsBufferMode; - -typedef enum _IvsEncoderFormat -{ - IVS_ENCODER_FORMAT_H264 = 0, - IVS_ENCODER_FORMAT_H265, - IVS_ENCODER_FORMAT_MAX -} IvsEncoderFormat; - -typedef struct _IvsConfig -{ - IvsBufferMode mode; - IvsEncoderFormat encode; - unsigned int pic_width; - unsigned int pic_height; - unsigned int encode_width; - unsigned int encode_height; - unsigned int buffer_height; // valid in slice buffer mode only - unsigned int stride_y; - unsigned int stride_uv; - unsigned int encode_x; - unsigned int encode_y; -} IvsConfig; - -IvsStatus createIspVencShake(void **ivs); -IvsStatus configIspVencShake(void *ivs, IvsConfig *config); -IvsStatus startIspVencShake(void *ivs); -IvsStatus resetIspVencShake(void *ivs); -IvsStatus getIspVencShakeState(void *ivs, int *state); -void destroyIspVencShake(void *ivs); - -#endif /* !_ISP_VENC_SHAKE_HAL_H_ */