【Airsim】项目结构分析

本文只分析最核心的 AirLib 项目结构,以及其与 Unreal 项目的关系

假如已经根据 Airsim 主页,克隆了完整项目。

Build on Linux – 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.shbuild.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

然后是下载依赖的第三方库和虚幻资源。

  1. 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
    
  2. 虚幻资源

    下载车辆模型后,存放在 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
    
  3. 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
  1. 将编译生成的静态库都拷贝到 AirLib 目录下,其中
    • libAirLib.a拷贝到 AirSim/AirLib/lib
    • libMavLinkCom.a 拷贝到 AirSim/AirLib/deps/MavLinkCom/lib
    • librpc.a 拷贝到 AirSim/AirLib/deps/rpclib/lib/librpc.a 再将所有库的头文件也拷贝到 AirLib 文件夹中。
  2. 将 AirLib 文件夹整个都拷贝到 AirSim/Unreal/Plugins/AirSim/Source 中,删除其中 src(已经不需要源码了)。--archive 选项,表示以归档模式进行同步。这会递归地复制目录并保持文件的所有属性(如权限、时间戳、符号链接等)。--delete:删除目标目录中源目录中没有的文件。就是说,如果源目录中删除了某些文件,目标目录中对应的文件也会被删除。
  3. 再同步更新 AirSim/Unreal/Environments/Plugins 中 AirSim 的对应内容。

需要单独说明的是

  1. AirLib 与 AirSim/Unreal 下两个文件夹的关系是:
    • AirLib 都是一些基类,如传感器接口,四旋翼动力学与运动学模型,rpc 服务器等基础功能。所以 AirLib 是不依赖虚幻和其他引擎的。
    • AirSim/Unreal/Plugins 继承 AirLib 中的基类,通过C++编程,结合虚幻蓝图,实现了具体的功能,在虚幻引擎中使用。
    • AirSim/Unreal/Environments 是完整的虚幻项目,通过加载 AirSim/Unreal/Plugins 目录,使用插件中的功能。

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

上一篇
下一篇