探讨如何在AS上构建webrtc(2)从sdk/android/Build.gn开始

全文七千多字,示例代码居多别担心,没有废话,不建议跳读。

零、梦开始的地方

要发美梦得先入睡,要入睡得找能躺平的地方。那么能躺平编译webrtc-android的地方在哪?在./src/sdk/android/Build.gn。Build.gn是Build.ninja的规则文件,类似build.cpp gcc -o后生build.o一样的意思,所以要搞清楚webrtc-android的细节脉络,就要从./src/sdk/android/Build.gn入手分析。

./src/sdk/android/Build.gn分三大部分,如下:

if (is_android) {import("//build/config/android/config.gni")import("//build/config/android/rules.gni")import("//third_party/jni_zero/jni_zero.gni")import("../../webrtc.gni")###################### Aggregate targets ######################
}if (current_os == "linux" || is_android) {################################# JNI targets for Java modules #################################... ...########################## Generated JNI targets ##########################... ...####################### Native API targets #######################... ...##################### Internal targets #####################
}if (is_android) {################# Test targets #################... ...
}

我们节省工程量,不要测试部分,所以排除第三个if(Test targets)

第二个if是核心组成部分,囊括了多个target。这几个target也说明了整个libwebrtc.aar的核心组成结构。包括:Java层对外暴露的API 和相关内部模块,JNI层,Native可暴露的API,以及跨平台的cpp内部模块。总结来说就分三个部分:Java部分、JNI部分、CXX部分。

接下来,我们按照这三个部分逐一分析。

一、第1层梦境——Java部分

上面Build.gn的片段节选中,我特意显示了开头几行的import gni。这个gni文件分别定义了整个Build.gn中使用的编译函数和相关的指令参数。可以说我所知道的内容都是从这几个gni以及其import的gni文件中慢慢看代码翻译出来的内容。精力有限,很多都是逆向查找,有什么不对的地方可以留言指正。

  dist_jar("libwebrtc") {_target_dir_name = get_label_info(":$target_name", "dir")output = "${root_out_dir}/lib.java${_target_dir_name}/${target_name}.jar"direct_deps_only = trueuse_unprocessed_jars = truerequires_android = trueno_build_hooks = truedeps = [":audio_api_java",":base_java",":builtin_audio_codecs_java",":camera_java",":default_video_codec_factory_java",":filevideo_java",":hwcodecs_java",":java_audio_device_module_java",":libaom_av1_encoder_java",":libjingle_peerconnection_java",":libjingle_peerconnection_metrics_default_java",":libvpx_vp8_java",":libvpx_vp9_java",":logging_java",":peerconnection_java",":screencapturer_java",":surfaceviewrenderer_java",":swcodecs_java",":video_api_java",":video_java","../../rtc_base:base_java",]}

先来看最顶层的目标dist_jar的定义在import 的rules.gni当中,其解释的意义如下:

  # Combines all dependent .jar files into a single .jar file.## Variables:#   output: Path to the output jar.#   use_interface_jars: Use all dependent interface .jars rather than#     implementation .jars.#   use_unprocessed_jars: Use unprocessed / undesugared .jars.#   direct_deps_only: Do not recurse on deps.#   jar_excluded_patterns (optional)#     List of globs for paths to exclude.## Example#   dist_jar("lib_fatjar") {#     deps = [ ":my_java_lib" ]#     output = "$root_build_dir/MyLibrary.jar"#   }

可以很清楚的看到其目的是将多个jar合并成一个jar,换句话说 dep 依赖列表中显示的节点,全都是.jar模块。

rtc_android_library("video_api_java") {visibility = [ "*" ]sources = ["api/org/webrtc/CapturerObserver.java","api/org/webrtc/EncodedImage.java","api/org/webrtc/VideoCodecInfo.java","api/org/webrtc/VideoCodecStatus.java","api/org/webrtc/VideoDecoder.java","api/org/webrtc/VideoDecoderFactory.java","api/org/webrtc/VideoEncoder.java","api/org/webrtc/VideoEncoderFactory.java","api/org/webrtc/VideoFrame.java","api/org/webrtc/VideoSink.java",]deps = [":base_java","//rtc_base:base_java","//third_party/androidx:androidx_annotation_annotation_java",]srcjar_deps = [ "//api/video:video_frame_enums" ]
}

拿其中一个依赖节点作为举例,比如说"video_api_java"。

  • rtc_android_library是构建Android的jar模块函数,visibility是可见性定义。
  • sources就是源文件列表,指定当前模块所包含的源文件。这个比较好理解。
  • deps就是依赖列表,指的是当前模块依赖了哪些模块。其中":base_java"是指当前build.gn文件中定义的模块。"//rtc_base:base_java" 是指根目录下的rtc_base工程目录下的build.gn里所定义的"base_java"模块,也可以用相对路径的写法"../../rtc_base:base_java"
  • "//third_party/androidx:androidx_annotation_annotation_java"这个依赖模块比较特殊,因为在AndroidStudio建立的工程中,是可以直接引用androidx相应的annotation包,所以我们要自行判断忽略没必要的模块引用。
  • srcjar_deps 是一个特殊的依赖参数,在rule.gni中可以找到相关说明,.srcjar是由CXX生成的java文件,以纯java文本的方式(并不是.class)打包成一个jar。我们可以在之前用脚本命令编译后的out路径下找到。然后直接用解压工具解压出所需要的java文件。

以上基本上描述了webrtc-android编译构建的Java部分,基本上都是利用 rtc_android_library 这个编译函数单独生成各个模块项,然后根据需要组合成最终形态。

找全所有模块的java文件后,按文件自带的package路径放好,基本结构如下:

在高版本的分支我发现有几个注解类,是在编译期根据cxx枚举生成的。为了在AS上不报错,我创建了 generatefromc并将对应的文件放在这里,这个目录不属于webrtc工程,请知悉。 

二、第2层梦境——JNI部分

第一层其实是# JNI targets for Java modules #这部分。总所周知Android系统源码,Java只是Android平台的API入口,真正的实现都是放在CXX中,那么就需要JNI做转接,对应的就是第二层# Generated JNI targets #。

这一层需要关注Build.gn的两个方法。generate_jar_jni 、generate_jni。举例解读如下:

  generate_jar_jni("generated_external_classes_jni") {classes = ["java/lang/Integer.class","java/lang/Double.class","java/lang/Long.class","java/lang/Iterable.class","java/util/Iterator.class","java/lang/Boolean.class","java/math/BigInteger.class","java/util/Map.class","java/util/LinkedHashMap.class","java/util/ArrayList.class","java/lang/Enum.class",]jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"}generate_jni("generated_audio_device_module_base_jni") {sources = [ "src/java/org/webrtc/audio/WebRtcAudioManager.java" ]namespace = "webrtc::jni"jni_generator_include = "//sdk/android/src/jni/jni_generator_helper.h"}

generate_jni很好理解,就是根据自定义的java源文件生成对应的jni 接口文件,内容也比较简单没啥好说的。

generate_jar_jni 是根据系统定义的包文件生成jni export接口文件,看上去可能有点难理解。为啥普通类也需要 jni 桥接?其实仔细想想用包装类&容器 作为参数输入输出也是很常有的事。

如上图所示,webrtc/src/api/ 对应 main/cpp/api/,webrtc/src/rtc_base/ 对应 main/cpp/rtc_base/,webrtc/src/third_party/ 对应 main/cpp/third_party/,webrtc/src/sdk/android 对应 main/cpp/sdk/android,如此类推,但内容不能全部无脑copy,往下会分析原因。

将由命令编译generate_jar_jni、generate_jni方法生成的 jni 产物文件(全局搜索generate_方法输入的模块名称)整理统一放到目录,cpp\sdk\android 下。收集好所需的jni 接口文件后,可以知道其目录下的jni文件依赖繁多,就拿generate_jni("generated_audio_device_module_base_jni")做举例说明,其接口文件WebRtcAudioManager_jni.h,其依赖关系树如下所示。

所有脚本生成的jni接口文件基本都包含了 third_party/jni_zero/jni_export.h 和 sdk/android/src/jni/jni_generator_helper.h 这两个文件。到这里我强烈建议手动找到WebRtcAudioManager_jni.h其依赖树的所有子项文件,这样有助于了解整个WebRTC工程的源码结构组成。

这里再多唠叨几句,说一些关键目录地址及其含义:

  • webrtc/src/api : 跨平台cxx实现部分,也可以说是webrtc的内核所在,其直接or间接依赖很多一级目录下的模块目录,譬如webrtc/src/rtc_base、webrtc/src/pc、webrtc/src/p2p、webrtc/src/call、还有最繁杂的webrtc/src/third_party 等等。
  • webrtc/src/sdk : 移动端的入口所在地,包含android和objc。webrtc/src/sdk/android下的 api/ & native_api/ 可以理解为对使用者暴露的 java api 和 native api。
  • 而 webrtc/src/sdk/android下的 src 是 java api 和 native api 的内部实现文件,所以我们会看到 webrtc/src/sdk/android/src下,又分了java目录和jni目录。

操作到这里脉络算是清晰起来了,但其实接下来才是重头戏。

三、第3层梦境——CXX & CMake

在处理移植时候,大家应该接触到 third_party这个目录,这里存放的都是网络上成熟开源的第三方工具库。上面我强烈建议手动处理 WebRtcAudioManager_jni.h 的依赖,会处遇到Google的一个知名轮子库 abseil.io (Abseil · GitHub)。以这个库为例思考分析:偌大的一个WebRTC工程,如何在AS上顺利进行编译构建。

如上图示:absl/是源码目录;ci/是不同平台的构建入口;CMake/是cmake构建方式的扩展目录。还看到三种不同的跨平台 bazel / gn / cmake 构建方式的入口文件。这里选用CMake

首先进入abseil-cpp/absl/目录,看到很多子模块,(以base为例)每个子模块也是会看到 bazel、gn、cmake 的构建脚本,如上图所示。而且我们还看到了很多_test.cc 和 _benchmark.cc的文件,它们都是 实现测试文件和性能测试文件,为了工程简洁 和 不额外引用googletest,我们直接舍弃掉这些文件。

把子模块所需要的文件 和 abseil-cpp/absl/CMakeLists.txt拷贝到 main/cpp/third_party/abseil-cpp/ 目录下。 目录结构如下组成。

然后翻阅子模块的CMakeLists.txt 和 abseil-cpp/absl/CMakeLists.txt可以发现,需要引入abseil-cpp/CMake/目录下的几个.cmake扩展文件,这样编译脚本才能正常工作起来。

万事具备了,那么东风在哪?我找到两个借东风的方法:

  • 翻阅官方网站的教程(Abseil CMake Build Instructions)
  • 翻阅\third_party\abseil-cpp\ci\对应平台的编译触发脚本

我翻看了\third_party\abseil-cpp\ci\linux_gcc-latest_libstdcxx_cmake.sh脚本,我们需要做的是:在AS项目工程上引入abseil-cpp模块的CMake编译。

add_subdirectory(third_party/abseil-cpp)

然后我们在abseil-cpp/CMakeLists.txt中,增加需要的编译参数,如上图所示。这里做些简单解读:

  1. 两个 if 折叠项都是可省略的测试项;
  2. 其他设置项都是通过\third_party\abseil-cpp\ci\linux_gcc-latest_libstdcxx_cmake.sh脚本翻译过来的;
  3. 至于ABSL_DEFAULT_LINKOPTS和 ABSL_DEFAULT_COPTS是absl的子项里需要到的编译参数。具体赋值是在\third_party\abseil-cpp\absl\copts\AbseilConfigureCopts.cmake,但测试发现ABSL_DEFAULT_LINKOPTS在Android平台上并没有具体值,但在链接的过程中又需要用到android原生的日志模块,如果需要生成独立动态库,则需要在这里手动添加。
  4. ABSL_BUILD_DLL这个标识,是abseil-cpp\absl\CMakeLists.txt文件中,用于触发生成独立动态库的方法absl_make_dll(),其详细实现参考third_party\abseil-cpp\CMake\AbseilDll.cmake

然后,就可以build看看了,我这里是成功的生成了产物。

Last but not least

自此整个AS工程的经络算是打通了。经过这么一折腾我是对整个webrtc的整体有了全新的认识。但兄弟们别忘了,这只是其中的一个第三方模块而已啊~后续还需要不断的填肉,顺着工程量的增加肯定会有更多问题出现,甚至整个工程结构都可能发生改变,到时候再出相关文章填补了。

还没说完,可能有大佬会说,用不着这样折腾。AndroidStudio原本就支持ninja。直接在webrtc\src目录创建一个AS工程目录,引用一下Build.gn就可以完美触发编译。这确实是一条容易走的路,但这个解决不了在AS上集成调试webrtc模块。这个和用命令行编译没什么区别。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/69355.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux firewalld开启日志审计功能(2)

在Firewalld防火墙中启用和配置logdenied选项,记录被拒绝的数据包(等同于开启日志功能) 效果展示: 1.开启日志记录功能 firewall-cmd --set-log-deniedunicast #重新加载生效配置 firewall-cmd --reload 2.配置rsyslog捕获日志…

Spring Web MVC项目的创建及使用

一、什么是Spring Web MVC? Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中,通常被称为Spring MVC。 1.1 MVC的定义 MVC 是 Model View Controller 的缩写,它是软件工程中的一种软件架构…

oracle:索引(B树索引,位图索引,分区索引,主键索引,唯一索引,联合索引/组合索引,函数索引)

索引通过存储列的排序值来加快对表中数据的访问速度,帮助数据库系统快速定位到所需数据,避免全表扫描 B树索引(B-Tree Index) B树索引是一种平衡树结构,适合处理范围查询和精确查找。它的设计目标是保持数据有序,并支持高效的插入…

android 适配 api 35(android 15) 遇到的问题

首先升级 targetSdkVersion 和 compileSdkVersion 到 35,升级后发生的报错 一、 解决方案: 升级 gradle 和 gradle 插件版本 com.android.tools.build:gradle -> 8.3.0-alpha02 gradle-wrapper.properties : distributionUrl -> gradle-8.6-bin.zip htt…

@Value属性读取系统变量错误

Value属性读取配置属性错误 场景 在测试Value读取yml配置文件属性时,发现系统配置属性优先级高于配置文件,导致注入异常值: 配置文件: user:name: yanxin测试类: RestController RequestMapping("/books") public class BookC…

BFS算法——广度优先搜索,探索未知的旅程(下)

文章目录 前言一. N叉树的层序遍历1.1 题目链接:https://leetcode.cn/problems/n-ary-tree-level-order-traversal/description/1.2 题目分析:1.3 思路讲解:1.4 代码实现: 二. 二叉树的锯齿形层序遍历2.1 题目链接:htt…

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题

【Ubuntu】ARM交叉编译开发环境解决“没有那个文件或目录”问题 零、起因 最近在使用Ubuntu虚拟机编译ARM程序,解压ARM的GCC后想要启动,报“没有那个文件或目录”,但是文件确实存在,环境配置也检查过了没问题,本文记…

清理服务器/docker容器

清理服务器 服务器或docker容器清理空间。 清理conda环境 删除不用的conda虚拟环境: conda env remove --name python38 conda env remove --name python310清理临时目录:/tmp du -sh /tmp # 查看/tmp目录的大小/tmp 目录下的文件通常是可以直接删除…

康谋方案 | BEV感知技术:多相机数据采集与高精度时间同步方案

随着自动驾驶技术的快速发展,车辆准确感知周围环境的能力变得至关重要。BEV(Birds-Eye-View,鸟瞰图)感知技术,以其独特的视角和强大的数据处理能力,正成为自动驾驶领域的一大研究热点。 一、BEV感知技术概…

HarmonyOS 5.0应用开发——ContentSlot的使用

【高心星出品】 文章目录 ContentSlot的使用使用方法案例运行结果 完整代码 ContentSlot的使用 用于渲染并管理Native层使用C-API创建的组件同时也支持ArkTS创建的NodeContent对象。 支持混合模式开发,当容器是ArkTS组件,子组件在Native侧创建时&#…

脚本一键生成管理下游k8s集群的kubeconfig

一、场景 1.1 需要管理下游k8s集群的场景。 1.2 不希望使用默认的cluster-admin权限的config. 二、脚本 **重点参数: 2.1 配置变量。 1、有单独namespace的权限和集群只读权限。 2、自签名的CA证书位置要正确。 2.2 如果配置错误,需要重新…

windows安装linux子系统【ubuntu】操作步骤

1.在windows系统中开启【适用于Linux的Windows子系统】 控制面板—程序—程序和功能—启用或关闭Windows功能—勾选适用于Linux的Windows子系统–确定 2.下载安装Linux Ubuntu 22.04.5 LTS系统 Ununtu下载链接 3.安装完Ununtu系统后更新系统 sudo apt update4.进入/usr/l…

LabVIEW自定义测量参数怎么设置?

以下通过一个温度采集案例,说明在 LabVIEW 中设置自定义测量参数的具体方法: 案例背景 ​ 假设使用 NI USB-6009 数据采集卡 和 热电偶传感器 监测温度,需自定义以下参数: 采样率:1 kHz 输入量程:0~10 V&a…

老游戏回顾:G2

一个老的RPG游戏。 剧情有独到之处。 ------- 遥远的过去,古拉纳斯将希望之光给予人们,人类令希望之光不断扩大,将繁荣握在手中。 但是,暗之恶魔巴鲁玛将光从人类身上夺走。古拉纳斯为了守护人类与其展开了一场激战&#xff0c…

DeepSeek R1 Distill Llama 70B(免费版)API使用详解

DeepSeek R1 Distill Llama 70B(免费版)API使用详解 在人工智能领域,随着技术的不断进步,各种新的模型和应用如雨后春笋般涌现。今天,我们要为大家介绍的是OpenRouter平台上提供的DeepSeek R1 Distill Llama 70B&…

【LeetCode: 887. 鸡蛋掉落 + 递归 + 二分 + dp】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

学习 PostgreSQL 流复制

PostgreSQL 流复制 PostgreSQL数据库异常中止后,数据库刚重启时,会重放停机前最后一个checkpoint点之后的 WAL日志,在把数据库恢复到停机的状态后,自动进入正常的状态,可以接收其他用户的查询和修改。 想象另一个场景…

LabVIEW位移测量系统

本文介绍了一种基于LabVIEW的位移测量系统,结合先进的硬件设备与LabVIEW平台的强大功能,能够实现对位移的精确测量和高效数据处理。该系统具备高精度、实时性和良好的可扩展性,适用于工程与科学实验中对位移测量的多种需求。 项目背景 位移…

Qt+海康虚拟相机的调试

做机器视觉项目的时候,在没有相机或需要把现场采集的图片在本地跑一下做测试时,可以使用海康的虚拟相机调试。以下是设置步骤: 1.安装好海康MVS软件,在菜单栏->工具选择虚拟相机工具,如下图: 2.打开虚拟…

数据结构【链式队列】

基于链式存储结构的队列实现与分析 一、引言 队列作为一种重要的数据结构,在计算机科学的众多领域有着广泛应用,如操作系统中的任务调度、网络通信中的数据缓冲等。本文通过C 代码实现了一个基于链式存储结构的队列,并对其进行详细解析。 …