本文只分析最核心的 AirLib 项目结构,以及其与 Unreal 项目的关系
假如已经根据 Airsim 主页,克隆了完整项目。
克隆源码
# go to the folder where you clone GitHub projects
git clone https://github.com/Microsoft/AirSim.git
cd AirSim
这步没有完整下载资源,还需要
./setup.sh
setup.sh 会下载一些依赖的第三方项目。
完整下载源码后,开始编译。build.sh 会自动检查依赖,设置编译环境,创建目录,编译项目。
./build.sh
分析 setup.sh 和 build.sh 两个脚本,对于理解项目结构是重要的。按照执行顺序,只关注 Ubuntu 系统(20以上)相关的部分,查看两个脚本完成了哪些任务。
先看 setup.sh,一开始安装了一些依赖
sudo apt-get update
sudo apt-get -y install --no-install-recommends \
lsb-release \
rsync \
software-properties-common \
wget \
libvulkan1 \
vulkan-utils
# install clang and build tools
sudo apt-get install -y clang-8 clang++-8 libc++-8-dev libc++abi-8-dev
# Install additional tools, CMake if required
sudo apt-get install -y build-essential
sudo apt-get install -y unzip=6.0-25ubuntu1.1
然后,检查和升级 cmake 版本,官方对 cmake 版本有要求。
Installing cmake on Linux – AirSim
if ! which cmake; then
# CMake not installed
cmake_ver=0
else
cmake_ver=$(cmake --version 2>&1 | head -n1 | cut -d ' ' -f3 | awk '{print $NF}')
fi
if version_less_than_equal_to "$cmake_ver" "$MIN_CMAKE_VERSION"; then
# Build CMake 3.10.2 from source
if [[ ! -d "cmake_build/bin" ]]; then
echo "Downloading cmake..."
wget https://cmake.org/files/v3.10/cmake-3.10.2.tar.gz \
-O cmake.tar.gz
tar -xzf cmake.tar.gz
rm cmake.tar.gz
rm -rf ./cmake_build
mv ./cmake-3.10.2 ./cmake_build
pushd cmake_build
./bootstrap
make
popd
fi
else
echo "Already have good version of cmake: $cmake_ver"
fi
设置串口权限,Linux 新电脑使用 USB 外设需要(比如 PX4 连接遥控器进行硬件在环仿真)。
# give user perms to access USB port - this is not needed if not using PX4 HIL
if [[ -n "${whoami}" ]]; then #this happens when running in travis
sudo /usr/sbin/useradd -G dialout "$USER"
sudo usermod -a -G dialout "$USER"
fi
然后是下载依赖的第三方库和虚幻资源。
-
rpclib-远程调用库(核心)
Airsim 使用 RPC 服务器,将虚幻仿真过程中的无人机位姿、点云和图像传输到其他进程。用户可以使用 Python,C++ 编写程序,用 API 获取数据,实现控制和 ROS 转发。
https://github.com/rpclib/rpclib
下载 2.3.0 版本,然后解压到 AirSim/external/rpclib,供之后 AirLib 编译链接。
# Download rpclib if [ ! -d "external/rpclib/rpclib-2.3.0" ]; then echo "*********************************************************************************************" echo "Downloading rpclib..." echo "*********************************************************************************************" wget https://github.com/rpclib/rpclib/archive/v2.3.0.zip # remove previous versions rm -rf "external/rpclib" mkdir -p "external/rpclib" unzip -q v2.3.0.zip -d external/rpclib rm v2.3.0.zip fi
-
虚幻资源
下载车辆模型后,存放在 AirSim/Unreal/Plugins/AirSim/Content/VehicleAdv。这个资源只会在编译 UE 项目时使用。
# Download high-polycount SUV model if $downloadHighPolySuv; then if [ ! -d "Unreal/Plugins/AirSim/Content/VehicleAdv" ]; then mkdir -p "Unreal/Plugins/AirSim/Content/VehicleAdv" fi if [ ! -d "Unreal/Plugins/AirSim/Content/VehicleAdv/SUV/v1.2.0" ]; then echo "*********************************************************************************************" echo "Downloading high-poly car assets.... The download is ~37MB and can take some time." echo "To install without this assets, re-run setup.sh with the argument --no-full-poly-car" echo "*********************************************************************************************" if [ -d "suv_download_tmp" ]; then rm -rf "suv_download_tmp" fi mkdir -p "suv_download_tmp" cd suv_download_tmp wget https://github.com/Microsoft/AirSim/releases/download/v1.2.0/car_assets.zip if [ -d "../Unreal/Plugins/AirSim/Content/VehicleAdv/SUV" ]; then rm -rf "../Unreal/Plugins/AirSim/Content/VehicleAdv/SUV" fi unzip -q car_assets.zip -d ../Unreal/Plugins/AirSim/Content/VehicleAdv cd .. rm -rf "suv_download_tmp" fi else echo "### Not downloading high-poly car asset (--no-full-poly-car). The default unreal vehicle will be used." fi
-
eigen3-数学运算库(核心)
下载 3.3.7 版本,存放在 AirLib/deps/eigen3。这是一个很常用的库,全是头文件,使用时直接 include 包含即可。
if [ ! -d "AirLib/deps/eigen3" ]; then echo "Downloading Eigen..." wget -O eigen3.zip https://gitlab.com/libeigen/eigen/-/archive/3.3.7/eigen-3.3.7.zip unzip -q eigen3.zip -d temp_eigen mkdir -p AirLib/deps/eigen3 mv temp_eigen/eigen*/Eigen AirLib/deps/eigen3 rm -rf temp_eigen rm eigen3.zip else echo "Eigen is already installed." fi
以上是 setup.sh 的主要内容, rpclib 和 eigen3 都是 AirLib 编译依赖,十分重要。另外 AirLib 还依赖仓库自带的 MavLink 通信库(PX4 无人机仿真需要),存放在 AirSim/MavLinkCom,除此以外,Airsim 主目录下其他文件都不是 AirLib 编译必需。
再来看 build.sh。默认编译 release 版本,使用编译工具如下:
debug=false
gcc=false
# Parse command line arguments
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--debug)
debug=true
shift # past argument
;;
--gcc)
gcc=true
shift # past argument
;;
esac
done
if $gcc; then
export CC="gcc-8"
export CXX="g++-8"
else
export CC="clang-8"
export CXX="clang++-8"
fi
构建和编译
# variable for build output
if $debug; then
build_dir=build_debug
else
build_dir=build_release
fi
pushd $build_dir >/dev/null
if $debug; then
folder_name="Debug"
"$CMAKE" ../cmake -DCMAKE_BUILD_TYPE=Debug $CMAKE_VARS \
|| (popd && rm -r $build_dir && exit 1)
else
folder_name="Release"
"$CMAKE" ../cmake -DCMAKE_BUILD_TYPE=Release $CMAKE_VARS \
|| (popd && rm -r $build_dir && exit 1)
fi
popd >/dev/null
pushd $build_dir >/dev/null
# final linking of the binaries can fail due to a missing libc++abi library
# (happens on Fedora, see https://bugzilla.redhat.com/show_bug.cgi?id=1332306).
# So we only build the libraries here for now
make -j"$(nproc)"
popd >/dev/null
构建成功后,生成文件默认位置在 build_release,如果构建失败自动删除该文件夹。之后启动 make 多线程编译。注意,$CMAKE 是前面设置的 cmake 可执行文件所在目录,总的 CMakeLists.txt 在 AirSim/cmake,内容如下:
cmake_minimum_required(VERSION 3.5.0)
project(AirSim)
add_subdirectory("rpclib_wrapper")
add_subdirectory("AirLib")
add_subdirectory("MavLinkCom")
add_subdirectory("AirLibUnitTests")
add_subdirectory("HelloDrone")
add_subdirectory("HelloSpawnedDrones")
add_subdirectory("HelloCar")
add_subdirectory("DroneShell")
add_subdirectory("DroneServer")
其中,前三个比较重要,是核心组件。可以查看子项目的 CMakeLists.txt 文件,例如 AirLib/cmake/AirLib/CMakeLists.txt
cmake_minimum_required(VERSION 3.5.0)
project(AirLib)
LIST(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake-modules")
INCLUDE("${CMAKE_CURRENT_LIST_DIR}/../cmake-modules/CommonSetup.cmake")
CommonSetup()
IncludeEigen()
include_directories(
${AIRSIM_ROOT}/AirLib/
${AIRSIM_ROOT}/AirLib/include
${AIRSIM_ROOT}/MavLinkCom/include
${RPC_LIB_INCLUDES}
)
file(GLOB_RECURSE ${PROJECT_NAME}_sources
${AIRSIM_ROOT}/${PROJECT_NAME}/src/api/*.cpp
${AIRSIM_ROOT}/${PROJECT_NAME}/src/common/common_utils/*.cpp
${AIRSIM_ROOT}/${PROJECT_NAME}/src/safety/*.cpp
${AIRSIM_ROOT}/${PROJECT_NAME}/src/vehicles/car/api/*.cpp
${AIRSIM_ROOT}/${PROJECT_NAME}/src/vehicles/multirotor/*.cpp
${AIRSIM_ROOT}/${PROJECT_NAME}/src/vehicles/multirotor/api/*.cpp
)
add_library(${PROJECT_NAME} STATIC ${${PROJECT_NAME}_sources})
CommonTargetLink()
target_link_libraries(${PROJECT_NAME} ${RPC_LIB})
target_link_libraries(${PROJECT_NAME} MavLinkCom)
#string(SUBSTRING ${CMAKE_STATIC_LINKER_FLAGS} 9 -1 "BUILD_PLATFORM")
#find_package(Threads REQUIRED)
#target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT})
#target_link_libraries(${PROJECT_NAME} AirLib)
#target_link_libraries(${PROJECT_NAME} MavLinkCom)
#target_link_libraries(${PROJECT_NAME} ../AirLib/deps/rpclib/lib/${BUILD_PLATFORM}/$(Configuration)/rpc)
回到 build.sh 脚本,因为最终目的是将编译好的 AirLib 静态库 libAirLib.a 提供给 UE 项目使用。所以之后的命令都是在重新组织文件结构。
mkdir -p AirLib/lib/x64/$folder_name
mkdir -p AirLib/deps/rpclib/lib
mkdir -p AirLib/deps/MavLinkCom/lib
cp $build_dir/output/lib/libAirLib.a AirLib/lib
cp $build_dir/output/lib/libMavLinkCom.a AirLib/deps/MavLinkCom/lib
cp $build_dir/output/lib/librpc.a AirLib/deps/rpclib/lib/librpc.a
# Update AirLib/lib, AirLib/deps, Plugins folders with new binaries
rsync -a --delete $build_dir/output/lib/ AirLib/lib/x64/$folder_name
rsync -a --delete external/rpclib/$RPC_VERSION_FOLDER/include AirLib/deps/rpclib
rsync -a --delete MavLinkCom/include AirLib/deps/MavLinkCom
rsync -a --delete AirLib Unreal/Plugins/AirSim/Source
rm -rf Unreal/Plugins/AirSim/Source/AirLib/src
# Update all environment projects
for d in Unreal/Environments/* ; do
[ -L "${d%/}" ] && continue
$d/clean.sh
mkdir -p $d/Plugins
rsync -a --delete Unreal/Plugins/AirSim $d/Plugins
done
- 将编译生成的静态库都拷贝到 AirLib 目录下,其中
libAirLib.a
拷贝到AirSim/AirLib/lib
libMavLinkCom.a
拷贝到AirSim/AirLib/deps/MavLinkCom/lib
librpc.a
拷贝到AirSim/AirLib/deps/rpclib/lib/librpc.a
再将所有库的头文件也拷贝到 AirLib 文件夹中。
- 将 AirLib 文件夹整个都拷贝到
AirSim/Unreal/Plugins/AirSim/Source
中,删除其中 src(已经不需要源码了)。--archive
选项,表示以归档模式进行同步。这会递归地复制目录并保持文件的所有属性(如权限、时间戳、符号链接等)。--delete
:删除目标目录中源目录中没有的文件。就是说,如果源目录中删除了某些文件,目标目录中对应的文件也会被删除。 - 再同步更新
AirSim/Unreal/Environments/Plugins
中 AirSim 的对应内容。
需要单独说明的是
- AirLib 与
AirSim/Unreal
下两个文件夹的关系是:- AirLib 都是一些基类,如传感器接口,四旋翼动力学与运动学模型,rpc 服务器等基础功能。所以 AirLib 是不依赖虚幻和其他引擎的。
AirSim/Unreal/Plugins
继承 AirLib 中的基类,通过C++编程,结合虚幻蓝图,实现了具体的功能,在虚幻引擎中使用。AirSim/Unreal/Environments
是完整的虚幻项目,通过加载AirSim/Unreal/Plugins
目录,使用插件中的功能。

- 注意
AirSim/Unreal/Plugins
中除了 AirLib,还有很多具体实现的源码。
