一、移植到RK:
编译相关:
ndi本身没有什么好移植编译的,本身提供的就是so库。但是ndi依赖其它第三方开源库:
avahi; dbus; expat; libcap; libdameon。因为编译这些开源库的服务器也需要安装一些插件所以在自己虚拟机Ubuntu20.04上编译好so,再把so库打包到rootfs目录, 都是开源的读者可以自行下载对应版本就行编译。
目录结构:
xxx@xxx-VirtualBox:~/work/rk3588$ ls
avahi build_spi_nand.sh mkfirmware.sh rockdev
build docs NDI_Advanced_SDK_v5_Linux tinyalsa_new
build_all.sh_bk fdk-aac note.sh tools
build_avahi_all.sh IMAGE RK3588_LINUX_NVR_SDK_Release_V1.4.0_20221021.tgz u-boot
build_emmc.sh kernel rkbin
交叉编译遇到很多问题,这里提供一键编译脚步,编译结果cp到rootfs相应目录:
build_avahi_all.sh 编译脚本:
#!/bin/sh
usage()
{
echo "USAGE: [-A] [-D] [-E] [-C] [-L] [-N] [-R]"
echo "No ARGS means use default build option "
echo "WHERE: -A = build avahi "
echo " -D = build dbus "
echo " -E = build expat "
echo " -C = build libcap "
echo " -L = build libdaemon "
echo " -N = build ndi "
echo " -R = build libdaemon "
exit 1
}
CODE_PATH=$PWD
BUILD_AVAHI=false
BUILD_DBUS=false
BUILD_EXPAT=false
BUILD_CAP=false
BUILD_DAEMON=false
BUILD_NDI=false
BUILD_CLEAN_FLAG=false
BUILD_JOBS=8
# check pass argument
while getopts "ADECLNR" arg
do
case $arg in
A)
echo "will build avahi"
BUILD_AVAHI=true
;;
D)
echo "will dbus"
BUILD_DBUS=true
;;
E)
echo "will build expat"
BUILD_EXPAT=true
;;
C)
echo "will build libcap"
BUILD_CAP=true
;;
L)
echo "will build libdaemon"
BUILD_DAEMON=true
;;
N)
echo "will build ndi"
BUILD_NDI=true
;;
R)
echo "will clean"
BUILD_CLEAN_FLAG=true
;;
?)
usage ;;
esac
done
# set env
echo "start set env.................."
declare -x PATH=$CODE_PATH/build//toolchain/bin/:$PATH
export PATH
declare -x CROSS_COMPILE="aarch64-none-linux-gnu-"
declare -x ARCH="arm64"
${CROSS_COMPILE}gcc -v
echo "end set env.................."
# build libdaemon-0.14
if [ "$BUILD_DAEMON" = true ] ; then
echo "start build libdaemon-0.14................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build libdaemon-0.14"
cd avahi/libdaemon-0.14
mkdir _install
make clean
./configure \
CC=aarch64-none-linux-gnu-gcc \
CXX=aarch64-none-linux-gnu-g++ \
--host=arm-none-linux \
--prefix=$(pwd)/_install \
ac_cv_func_setpgrp_void=yes
make -j$BUILD_JOBS
make install
else
echo "start build libdaemon-0.14"
fi
if [ $? -eq 0 ]; then
echo "Build libdaemon-0.14 ok!"
else
echo "Build libdaemon-0.14 failed!"
exit 1
fi
cd -
fi
# build libcap
if [ "$BUILD_CAP" = true ] ; then
echo "start build libcap-2.25................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build libcap"
cd avahi/libcap-2.25
mkdir _install
make clean
make -j$BUILD_JOBS
make DESTDIR=$(pwd)/_install install
else
cd avahi/libcap-2.25
mkdir _install
make -j$BUILD_JOBS
make DESTDIR=$(pwd)/_install install
fi
if [ $? -eq 0 ]; then
echo "Build libcap-2.25 ok!"
else
echo "Build libcap-2.25 failed!"
exit 1
fi
cd -
fi
# build expat
if [ "$BUILD_EXPAT" = true ] ; then
echo "start build expat-2.4.8................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build expat-2.4.8"
cd avahi/expat-2.4.8
mkdir _install
./configure \
CC=aarch64-none-linux-gnu-gcc \
CXX=aarch64-none-linux-gnu-g++ \
--host=aarch64-none-linux \
--prefix=$(pwd)/_install \
ac_cv_func_setpgrp_void=yes\
--enable-shared=no\
--enable-static=yes
make clean
make -j$BUILD_JOBS
make install
else
cd avahi/expat-2.4.8
mkdir _install
./configure \
CC=aarch64-none-linux-gnu-gcc \
CXX=aarch64-none-linux-gnu-g++ \
--host=aarch64-none-linux \
--prefix=$(pwd)/_install \
ac_cv_func_setpgrp_void=yes\
--enable-shared=no\
--enable-static=yes
make -j$BUILD_JOBS
make install
fi
if [ $? -eq 0 ]; then
echo "Build expat-2.4.8 ok!"
else
echo "Build expat-2.4.8 failed!"
exit 1
fi
cd -
fi
# build dbus-1.12.16
if [ "$BUILD_DBUS" = true ] ; then
echo "start build dbus-1.12.16................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build dbus-1.12.16"
cd avahi/dbus-1.12.16
mkdir _install
make clean
./configure \
CC=aarch64-none-linux-gnu-gcc \
CXX=aarch64-none-linux-gnu-g++ \
--host=aarch64-linux \
--enable-systemd=no \
--enable-selinux=no \
--with-x=no \
--enable-tests=no \
EXPAT_CFLAGS="-I$CODE_PATH/avahi/expat-2.4.8/_install/include" \
EXPAT_LIBS="$CODE_PATH/avahi/expat-2.4.8/_install/lib/libexpat.a" \
--with-session-socket-dir=/var/run/dbus \
--with-system-socket=/var/run/dbus/system_bus_socket \
--with-system-pid-file=/var/run/dbus/pid \
--prefix=/usr/avahi/dbus-1.12.16/_install
make -j$BUILD_JOBS
make install
else
echo "start build dbus-1.12.16"
fi
if [ $? -eq 0 ]; then
echo "Build dbus-1.12.16 ok!"
else
echo "Build dbus-1.12.16 failed!"
exit 1
fi
cd -
fi
# build avahi
if [ "$BUILD_AVAHI" = true ] ; then
echo "start build avahi-0.8................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build avahi-0.8"
cd avahi/avahi-0.8
mkdir _install
make clean
./configure \
CC=aarch64-none-linux-gnu-gcc \
CXX=aarch64-none-linux-gnu-g++ \
--host=aarch64-none-linux-gnu \
--with-xml=expat \
--disable-qt3 \
--disable-qt4 \
--disable-qt5 \
--disable-mono \
--disable-libevent \
--with-distro=none \
--disable-glib \
--disable-gobject \
--disable-gtk \
--disable-gtk3 \
--disable-gdbm \
--disable-python \
--disable-python-dbus \
CFLAGS="-I$CODE_PATH/avahi/expat-2.4.8/_install/include -I/usr/avahi/dbus-1.12.16/_install/include -I$CODE_PATH/avahi/libcap-2.25/_install/usr/include" \
LDFLAGS="-L/usr/avahi/dbus-1.12.16/_install/lib -L$CODE_PATH/avahi/expat-2.4.8/_install/lib -L$CODE_PATH/avahi/libcap-2.25/_install/lib64" \
LIBDAEMON_CFLAGS="-I$CODE_PATH/avahi/libdaemon-0.14/_install/include" \
LIBDAEMON_LIBS="$CODE_PATH/avahi/libdaemon-0.14/_install/lib/libdaemon.a" \
ac_cv_header_sys_capability_h=yes \
--prefix=/usr/avahi/avahi-0.8/_install
make -j$BUILD_JOBS
make install
else
echo "start build avahi-0.8"
fi
if [ $? -eq 0 ]; then
echo "Build avahi-0.8ok!"
else
echo "Build avahi-0.8 failed!"
exit 1
fi
cd -
fi
#buid ndi examples
if [ "$BUILD_NDI" = true ] ; then
echo "start build ndi................."
if [ "$BUILD_CLEAN_FLAG" = true ] ; then
echo "start clean && build ndi"
cd NDI_Advanced_SDK_v5_Linux/examples/C++
make NDILIB=aarch64-rockchip-linux-gnu CC=aarch64-none-linux-gnu-gcc CXX=aarch64-none-linux-gnu-g++ -j$BUILD_JOBS
else
echo "build ndi"
fi
if [ $? -eq 0 ]; then
echo "Build ndi ok!"
else
echo "Build ndifailed!"
exit 1
fi
cd -
fi
sync
mkdir -p build/rootfs/oem/lib
mkdir -p build/rootfs/oem/bin
# cp libcap so
cp avahi/libcap-2.25/_install/lib64/libcap.so.2.25 build/rootfs/oem/lib
cd build/rootfs/oem/lib
ln -s libcap.so.2.25 libcap.so.2
ln -s libcap.so.2.25 libcap.so
cd -
# cp libdaemon so
cp avahi/libdaemon-0.14/_install/lib/libdaemon.so.0.5.0 build/rootfs/oem/lib
cd build/rootfs/oem/lib
ln -s libdaemon.so.0.5.0 libdaemon.so.0
ln -s libdaemon.so.0.5.0 libdaemon.so
cd -
# cp libdbus so
cp avahi/dbus-1.12.16/_install/lib/libdbus-1.so.3.19.11 build/rootfs/oem/lib
cd build/rootfs/oem/lib
ln -s libdbus-1.so.3.19.11 libdbus-1.so.3
ln -s libdbus-1.so.3.19.11 libdbus-1.so
cd -
cp /usr/avahi/dbus-1.12.16/_install/bin/dbus-daemon build/rootfs/oem/bin
# cp avahi so
cp avahi/avahi-0.8/_install/lib/libavahi-client.so.3.2.9 build/rootfs/oem/lib/
cp avahi/avahi-0.8/_install/lib/libavahi-common.so.3.5.3 build/rootfs/oem/lib/
cp avahi/avahi-0.8/_install/lib/libavahi-core.so.7.0.2 build/rootfs/oem/lib/
cd build/rootfs/oem/lib
ln -s libavahi-client.so.3.2.9 libavahi-client.so.3
ln -s libavahi-client.so.3.2.9 libavahi-client.so
ln -s libavahi-common.so.3.5.3 libavahi-common.so.3
ln -s libavahi-common.so.3.5.3 libavahi-common.so
ln -s libavahi-core.so.7.0.2 libavahi-core.so.7
ln -s libavahi-core.so.7.0.2 libavahi-core.so
cd -
cp /usr/avahi/avahi-0.8/_install/sbin/avahi-daemon build/rootfs/oem/bin
# cp ndi sdk so
cp NDI_Advanced_SDK_v5_Linux/lib/aarch64-rockchip-linux-gnu/libndi.so.5.5.2 build/rootfs/oem/lib/
cd build/rootfs/oem/lib
ln -s libndi.so.5.5.2 libndi.so.5
ln -s libndi.so.5.5.2 libndi.so
cd -
cp NDI_Advanced_SDK_v5_Linux/examples/C++/NDIlib_Recv/NDIlib_Recv build/rootfs/oem/bin/
cp NDI_Advanced_SDK_v5_Linux/examples/C++/NDIlib_Send_Video_and_Audio/NDIlib_Send_Video_and_Audio build/rootfs/oem/bin/
cp /usr/avahi/avahi-0.8/_install/etc/dbus-1/system.d/avahi-dbus.conf /usr/avahi/dbus-1.12.16/_install/etc/dbus-1/system.conf
cp -rf /usr/avahi/ build/rootfs/usr/
sync
二、板端配置相关:
/etc/init.d/目录增加启动脚本S100_avahi:
#!/bin/sh
source /etc/profile.d/RkEnv.sh
echo "start avahi...................................."
/oem/bin/busybox adduser avahi -D -H
/oem/bin/busybox adduser messagebus -D -H
sync
cat /etc/group | grep avahi
if [ $? -eq 0 ]; then
echo "Found!"
else
echo "avahi:x:1001:" >> /etc/group
fi
export
sync
mkdir /var/run/dbus/ -p
/oem/bin/dbus-daemon --system --fork
/oem/bin/avahi-daemon &
#sleep 20 wait for net ready and system time ready.
sleep 20 && /oem/bin/ndi_send_h264_aac /oem/test.h264 /oem/qhc_48.wav &
RK3588 sdk bin目录自带的busybox 不带adduser 命令,需要自己下载重新编译一个。否则avahi和dbus跑不起来。
sleep 20的作用是等待网络获取到ip,以及系统时钟,avahi初始化。如果太早起来会失败,还有系统时间问题导致ndi so检测时间出错立马达到30分钟停止工作,免费版本只能使用30分钟。
三、NDI Demo使用:
普通版sdk裸数据发送:
windows打开 NDI 软件 -》点击 Studio Monitor -》Ubuntu 运行example的NDIlib_Send_Video_and_Audio -》在 Studio Monitor界面选择设备 -》windows看到画面和听到声音。
windows打开 NDI 软件 -》点击 Test Patterns -》Ubuntu 运行example的NDIlib_Send_Audio -》看到打印信息。
四、高级版NDI Demo相关代码,开发测试程序:
背景:
高级sdk只有一个发送demo,是基于数组的。
我们最终是要发送h264和aac文件。需要编写一个读取aac,h264文件进行解码,解析,然后转换到ndi的数据结构再发送出去。
移植fdkaac开源库:
这个比较简单网上很多。
编写发送h264,aac文件测试程序代码:
代码路径:
Z:\rk3588_linux_nvr\anker_3rdparty\NDI_Advanced_SDK_for_Linux\examples\C++_HX2\NDIlib_Send_H264
编译:
#源码根目录 设置编译环境参数
CODE_PATH=$PWD
declare -x PATH=$CODE_PATH/build/toolchain/bin/:$PATH
export PATH
declare -x ARCH="arm64"
declare -x CROSS_COMPILE="aarch64-none-linux-gnu-"
#进入NDIlib_Send_H264目录编译
make -j8
得到ndi_send_h264_aac ,运行:/oem/bin/ndi_send_h264_aac /oem/test.h264 /oem/qhc_48.wav &
可以使用FFmpeg命令把mp4转成h264文件,如:
ffmpeg -i test.mp4 -codec copy -bsf: h264_mp4toannexb -f h264 test.h264
编写接收程序:
代码位置:anker_3rdparty\NDI_Advanced_SDK_for_Linux\examples\C++_HX2\NDIlib_Recv_H264_AAC。
高级sdk版本只有发送的demo,没有接收h264 aac的demo。只能自己看英文pdf和头文件去写 去试。
遇到的问题:
A. 接收的pcm数据保存成文件后,在audacity播放,声音不对,分析排查后发现ndi api接收到的audio frame的数据不是常用交错排列的pcm数据,而是先放channel_stride_in_bytes数据的通道1,再接着放channel_stride_in_bytes的通道2,以此类推,这是看头文件结构体猜,试出来的,英文pdf并没有介绍。
B. 在自己虚拟机可以的程序,编译到rk3588 板子无法接收到音频数据。 通过排查分析和猜:编译器,运行环境等。把同样的代码在服务器编译测试现象一样。最后发现是ndi的so库会用dlopen方式加载FFmpeg相关的库去解码aac得到pcm数据。因为rk3588没有FFmpeg库所以导致ndi 库内部无法解码aac而且不会打印任何日志,导致我写的接收程序一直无法接收到音频数据。
C. 因为存在B问题,而且我们不需要FFmpeg。接收端程序不是一定要接收pcm数据,可以接收aac数据然后用fdk-aac去解码得到pcm数据。查看pdf文档,可以设置参数直接接收aac数。但是接收到的数据保存文件无法在电脑上无法识别。通过分析发现接收到的数据没有adst头,手动添加adts头后电脑可以正确识别但是无法播放出声音。
D. 分析无法播放出声音的问题。 通过dump数据和发送端对比,发现接收端接收到的audio frame数据比发送端多了44个字节+2字节。44字节不知道是ndi发送api加的还是接收api加的,2字节是发送端的extra data。保存成aac文件的时候去掉前面44字节和后面2字节的extra data即可,这样的aac文件就可以在电脑上播放了。
结合rk mpi,获取camera数据进行发送。camera -> venc -> ndi send。
音频数据使用wav文件:
ndi_send_camera_aac 3840 2160 60 /dev/video11 /oem/qhc_48.wav
3840:x分辨率
2160:y分辨率
60: 帧率
/dev/video11:camera设备节点命令
/oem/qhc_48.wav: 要发送的wav文件
音频数据使用声卡:
ndi_send_camera_aac 3840 2160 60 /dev/video11
代码 rk3588_linux_nvr.git
六、测试:
ndi_send_camera_aac 程序发送4k 60帧 ,win10端接收预览,平均延时在150ms ~ 300ms。和画面变化有关和硬件编码器码率有关。
码率在1 ~ 6.5Mbps之间,和画面运动变化有关。
3. cpu使用率在1% ~5% 变化
内存:
开机后是82M,跑起ndi_send_camera_aac 后是338M, NDI库大概使用1M,大部分内存是RK MPI接口(camera,venc,isp)使用的。