探讨如何在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中,增加需要的编译参数,如上图所示。这里做些简单解读:
- 两个 if 折叠项都是可省略的测试项;
- 其他设置项都是通过\third_party\abseil-cpp\ci\linux_gcc-latest_libstdcxx_cmake.sh脚本翻译过来的;
- 至于ABSL_DEFAULT_LINKOPTS和 ABSL_DEFAULT_COPTS是absl的子项里需要到的编译参数。具体赋值是在\third_party\abseil-cpp\absl\copts\AbseilConfigureCopts.cmake,但测试发现ABSL_DEFAULT_LINKOPTS在Android平台上并没有具体值,但在链接的过程中又需要用到android原生的日志模块,如果需要生成独立动态库,则需要在这里手动添加。
- 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模块。这个和用命令行编译没什么区别。
相关文章:
探讨如何在AS上构建webrtc(2)从sdk/android/Build.gn开始
全文七千多字,示例代码居多别担心,没有废话,不建议跳读。 零、梦开始的地方 要发美梦得先入睡,要入睡得找能躺平的地方。那么能躺平编译webrtc-android的地方在哪?在./src/sdk/android/Build.gn。Build.gn是Build.nin…...
C语言时间相关宏定义
在C语言中,预处理器提供了一些与时间相关的宏定义,用于在编译时获取日期、时间等信息。除了 __TIMESTAMP__ 和 __DATE__,还有以下相关的宏定义: __DATE__ 当前编译日期的字符串,格式为 "Mmm dd yyyy"&#x…...
【Linux基础】Linux下常用的系统命令
一、前言 本文主要总结了工作中常用的linux指令,有遇到新的命令会不定期更新。 二、系统监控和进程管理指令 2.1 ps命令 作用:查看当前进程信息。 常用选项: -e: 显示所有进程,包括其他用户的进程。-f: 显示更详细的进程信息…...
Spring Boot 线程池自定义拒绝策略:解决任务堆积与丢失问题
如何通过自定义线程池提升系统稳定性 背景 在高并发系统中,线程池管理至关重要。默认线程池可能导致: 资源浪费(创建过多线程导致 OOM)任务堆积(队列满后任务被拒绝)任务丢失(默认拒绝策略丢…...
人工智能D* Lite 算法-动态障碍物处理、多步预测和启发式函数优化
在智能驾驶领域,D* Lite 算法是一种高效的动态路径规划算法,适用于处理环境变化时的路径重规划问题。以下将为你展示 D* Lite 算法的高级用法,包含动态障碍物处理、多步预测和启发式函数优化等方面的代码实现。 代码实现 import heapq impo…...
C#常用集合优缺点对比
先上结论: 在C#中,链表、一维数组、字典、List<T>和ArrayList是常见的数据集合类型,它们各有优缺点,适用于不同的场景。以下是它们的比较: 1. 一维数组 (T[]) 优点: 性能高:数组在内存中…...
多线程下jdk1.7的头插法导致的死循环问题
20250208 多线程下jdk1.7的头插法导致的死循环问题 多线程下jdk1.7的头插法导致的死循环问题 【新版Java面试专题视频教程,java八股文面试全套真题深度详解(含大厂高频面试真题)】 jdk1.7在hashmap扩容时使用的是头插法,所以扩容…...
MySQL的深度分页如何优化?
大家好,我是锋哥。今天分享关于【MySQL的深度分页如何优化?】面试题。希望对大家有帮助; MySQL的深度分页如何优化? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 MySQL在处理深度分页(即查询页数较大时,通常是查询…...
uniapp中使用uCharts折线图X轴数据间隔显示
1、先看官网 https://www.ucharts.cn/ 2、设置代码 "xAxisDemo3":function(val, index, opts){if(index % 2 0){return val}else {return }}, 再在数据中引入设置好样式...
VMware下Linux和macOS安装VSCode一些总结
本文介绍VMware下Linux和macOS安装VSCode的一些内容,包括VSCode编译器显示中文以及安装.NET环境和Python环境。 VSCode下载地址:Download Visual Studio Code - Mac, Linux, Windows 一.Linux系统下 1.安装中文包 按 Ctrl Shift P 打开命令面板。输…...
公司配置内网穿透方法笔记
一、目的 公司内部有局域网,局域网上有ftp服务器,有windows桌面服务器; 在内网环境下,是可以访问ftp服务器以及用远程桌面登录windows桌面服务器的; 现在想居家办公时,也能访问到公司内网的ftp服务器和win…...
【Windows/C++/yolo开发部署02:正确方法】将自定义实例分割模型导出为 ONNX 格式
【完整项目下载地址】: 【TensorRT部署YOLO项目:实例分割+目标检测】+【C++和python两种方式】+【支持linux和windows】资源-CSDN文库 目录 写在前面 环境准备 安装必要的库 下载模型并开始转换 解决依赖问题 安装 ONNX 降级 Protobuf 最终转换 总结 写在前面 在…...
国产编辑器EverEdit - 编辑辅助功能介绍
1 编辑辅助功能 1.1 各编辑辅助选项说明 1.1.1 行号 打开该选项时,在编辑器主窗口左侧显示行号,如下图所示: 1.1.2 文档地图 打开该选项时,在编辑器主窗口右侧靠近垂直滚动条的地方显示代码的缩略图,如下图所示&…...
Jupyter Notebook自动保存失败等问题的解决
一、未生成配置文件 需要在命令行中,执行下面的命令自动生成配置文件 jupyter notebook --generate-config 执行后会在 C:\Users\用户名\.jupyter目录中生成文件 jupyter_notebook_config.py 二、在网页端打开Jupyter Notebook后文件保存失败;运行代码…...
Shapefile格式文件解析和显示
Java实现GIS SHP文件格式的解析和显示,JDK19下编译,awt图形系统显示。 SHP文件对应的属性存储在DBF格式数据库中,解析见:DBASE DBF数据库文件解析_数据库文件在线解析-CSDN博客 解析SHP文件代码: public static Shap…...
大语言模型需要的可观测性数据的关联方式
可观测性数据的关联方式及其优缺点 随着现代分布式架构和微服务的普及,可观测性(Observability)已经成为确保系统健康、排查故障、优化性能的重要组成部分。有效的可观测性数据关联方式不仅能够帮助我们实时监控系统的运行状态,还…...
wordpressAI工具,已接入Deepseek 支持自动生成文章、生成图片、生成长尾关键词、前端AI窗口互动、批量采集等
基于关键词或现有内容生成SEO优化的文章,支持多种AI服务(如OpenAI、百度文心一言、智谱AI等),并提供定时任务、内容采集、关键词生成等功能。 核心功能 文章生成 关键词生成:根据输入的关键词生成高质量文章。 内容…...
解构赋值在 TypeScript 中的妙用:以 Babylon.js 的 loadModel 函数为例
在现代 JavaScript 和 TypeScript 开发中,解构赋值(Destructuring Assignment)是一种非常实用的特性,它能够让代码更加简洁、易读且高效。今天,我们就通过一个实际的例子——在 Babylon.js 中加载 3D 模型的 loadMod…...
mysql8安装时提示-缺少Microsoft Visual C++ 2019 x64 redistributable
MySQL8.0安装包mysql-8.0.1-winx64进行安装,提示:This application requires Visual Studio 2019 x64Redistributable, Please install the Redistributable then runthis installer again。出现这个错误是因为我们电脑缺少Microsoft Visual C 这个程序&…...
物品匹配问题-25寒假牛客C
登录—专业IT笔试面试备考平台_牛客网 这道题看似是在考察位运算,实则考察的是n个物品,每个物品有ai个,最多能够得到多少个物品的配对.观察题目可以得到,只有100,111,010,001(第一位是ci,第二位是ai,第三位是bi)需要进行操作,其它都是已经满足条件的对,可以假设对其中两个不同…...
学习数据结构(6)单链表OJ上
1.移除链表元素 解法一:(我的做法)在遍历的同时移除,代码写法比较复杂 解法二:创建新的链表,遍历原链表,将非val的节点尾插到新链表,注意,如果原链表结尾是val节点需要将…...
03/29 使用 海康SDK 对接时使用的 MysqlUtils
前言 最近朋友的需求, 是需要使用 海康sdk 连接海康设备, 进行数据的获取, 比如 进出车辆, 进出人员 这一部分是 资源比较贫瘠时的一个 Mysql 工具类 测试用例 public class MysqlUtils {public static String MYSQL_HOST "192.168.31.9";public static int MY…...
全志A133 android10 thermal温控策略配置调试
一,功能介绍 Thermal简称热控制系统,其功能是通过temperature sensor(温度传感器)测量当前CPU、GPU等设备的温度值,然后根据此温度值,影响CPU、GPU等设备的调频策略,对CPU、GPU等设备的最大频率…...
知识图谱智能应用系统:数据存储架构与流程解析
在当今数字化时代,知识图谱作为一种强大的知识表示和管理工具,正逐渐成为企业、科研机构以及各类智能应用的核心技术。知识图谱通过将数据转化为结构化的知识网络,不仅能够高效地存储和管理海量信息,还能通过复杂的查询和推理,为用户提供深度的知识洞察。然而,构建一个高…...
mac下生成.icns图标
笔记原因: 今日需要在mac下开发涉及图标文件的使用及icons文件的生成,所以记录一下。 网络上都是一堆命令行需要打印太麻烦了,写一个一键脚本。 步骤一 将需要生成的png格式文件重命名为“pic.png” mv xxxx.png pic.png 步骤二 下载我…...
Dev-cpp C语言编写和调用dll
Dev-cpp新建DLL项目。 dllmain.cpp #include "dll.h" #include <windows.h>int add(int a, int b) { return a b; } dll.h #ifndef _DLL_H_ #define _DLL_H_extern "C" __declspec(dllexport) int add(int a, int b);#endif 调用DLL&#…...
IDEA编写SpringBoot项目时使用Lombok报错“找不到符号”的原因和解决
目录 概述|背景 报错解析 解决方法 IDEA配置解决 Pom配置插件解决 概述|背景 报错发生背景:在SpringBoot项目中引入Lombok依赖并使用后出现"找不到符号"的问题。 本文讨论在上述背景下发生的报错原因和解决办法,如果仅为了解决BUG不论原…...
差速驱动机器人MPC算法实现-C++
差速驱动机器人,其运动学模型需要考虑线速度和角速度。MPC(模型预测控制)需要建立预测模型,并在每个控制周期内求解优化问题。 差速驱动机器人的运动学方程通常包括位置(x, y)和航向角θ,线速度…...
将仓库A分支同步到仓库B分支,并且同步commit提交
一、 问题 有一仓库A 和 一仓库B, 需要将仓库A分支a1所有提交同步推送到仓库B分支b1上 二、 解决 2.1、 首先需要仓库A、仓库B的权限, 2.2、将仓库A clone到本地, 进入A目录,并且切换到a1分支 cd A ## A 为A仓库clone到本地代…...
kafka生产者之发送模式与ACK
文章目录 Kafka的发送模式Kafka的ack机制发送模式与ack的关联重试次数总结 在Kafka中,发送模式与ack机制紧密相关,它们共同影响着消息发送的可靠性和性能。 Kafka的发送模式 发后即忘(Fire and Forget):生产者发送消息…...
