## C++(CPP)在Android开发中的应用 ##
#### 使用纯Java开发App的缺点 ###
- 在某些场合下,使用纯Java开发Android应用程序不完美,比如:
- 有高性能算法,Java语言无法满足
- 有跨平台需求,希望将App移植到iOS(现在很多设计都是C语言和C++来写底层,Android来写界面)
- 已有代码的重用
#### 为什么使用NDK ####
1. 代码的保护。由于apk 的java 层代码很容易被反编译,而C/C++库反编译难度较大。
2. 可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
3. 提高程序的执行效率。将要求高性能的应用逻辑使用C 开发,从而提高应用程序的执行效率。
4. 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
### 引入NDK ####
- 早在Android 1.6(2009年)时,google就提供了NDK(native development kit),NDK包括了一套Android的交叉编译环境和开发库,利用它可以编写C/C++程序,并编译成Android环境下使用的动态库,Java代码通过Jni规范,调用C/C++写的动态库。
- NDK
- NDK是一系列工具的集合。
- 它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
- NDK 提供了一份稳定、功能有限的API 头文件声
- Google 明确声明该API 是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API 支持的功能非常有限,包含有:C 标准库(libc)、标准数学库(libm)、压缩库(libz)、Log 库(liblog)。
- JNI
- JavaNative Interface (JNI)标准是java平台的一部分,JNI是Java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以通过JNI调用java代码。JNI 是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。
- 总结
- NDK就是为我们生成了c/c++的动态链接库而已,JNI只不过是java和c沟通的工具,两者与Android没有半毛钱关系,只因为安卓是java程序开发然后jni又能与c沟通,所以使“Java+C”的开发方式终于转正。
- Android是JVM架设在Linux之上的架构。所以无论如何,在Linux OS层面,都应该可以跑C/C++程序。
- Android Native C就是使用C/C++程序直接跑到Linux OS层面上的程序。与其它平台类似,只需要交叉编译后。并得到Linux OS root权限,就可以直接跑起来了。
- 官方定义:Android NDK 是一套允许您使用原生代码语言(例如 C 和 C++)实现部分应用的工具集。在开发某些类型应用时,这有助于您重复使用以这些语言编写的代码库。
- 目前最新的Android Studio 2.2中,集成了C/C++开发环境,开发人员在使用C/C++更加简单了。(以前,写Android的jni库时,需要用到Android.mk,Application.mk这些配置,来交叉编译)
- Android.mk,负责配置如下内容:
- (1) 模块名(LOCAL_MODULE)
- (2) 需要编译的源文件(LOCAL_SRC_FILES)
- (3) 依赖的第三方库(LOCAL_STATIC_LIBRARIES,LOCAL_SHARED_LIBRARIES)
- (4) 编译/链接选项(LOCAL_LDLIBS、LOCAL_CFLAGS)
- Application.mk,负责配置如下内容:
- (1) 目标平台的ABI类型(默认值:armeabi)(APP_ABI)
- ![](https://i.imgur.com/KZFxWSR.png)
- (2) Toolchains(默认值:GCC 4.8)
- (3) C++标准库类型(默认值:system)(APP_STL)
- (4) release/debug模式(默认值:release)
- [NDK中文官方开发技术文档 ](https://developer.android.google.cn/ndk/index.html)
- NDK下载地址
- [NDK 下载 ](https://developer.android.google.cn/ndk/downloads/index.html)
- [AndroidDevTools](http://www.androiddevtools.cn/)(随时更新官网最新版本)
- [官方下载](https://developer.android.com/ndk/downloads/index.html)(需要翻墙,而且翻墙后网站的NDK版本都不是最新版本)
- 网友找到的一些版本----[NDK各个版本链接](http://blog.csdn.net/shuzfan/article/details/52690554)
- 也可以打开---AndroidDevTools网站的上各个大学的镜像网站,找到Android,点击repository。可以看到最新最全的各个版本(包括sdk、sdktools、platform、platform-tools、Android源码等等官网上所持有的所有的资源),如下图是打开的大连东软信息学院镜像服务器中的Android资源
- 注意:NDK的r10版本以上,就已经集成了Cygwin的UNIX虚拟机的功能,不需要安装Cygwin的UNIX环境
- ![](https://i.imgur.com/KDcSXSQ.png)
-
- 下载完成,解压到自己认为合适的目录,**注意:目录路径中不能有中文和空格**
###创建NDK项目(支持 C/C++ 的新项目)(project)
- 创建项目过程中,遇到问题,建议参考官网文章----[向您的项目添加 C 和 C++ 代码](https://developer.android.com/studio/projects/add-native-code.html)(貌似需要翻墙,没法翻墙的可以查看[在 Android Studio 2.2 中愉快地使用 C/C++ ](http://blog.csdn.net/wl9739/article/details/52607010)),而且这篇文章提到以下几点
- Android Studio 用于构建原生库的默认工具是 CMake。由于很多现有项目都使用构建工具包编译其原生代码,Android Studio 还支持 [ndk-build](https://developer.android.com/ndk/guides/ndk-build.html)。如果您想要将现有的 ndk-build 库导入到您的 Android Studio 项目中,请参阅介绍如何配置 Gradle 以[关联到您的原生库](https://developer.android.com/studio/projects/add-native-code.html#link-gradle)的部分。不过,如果您在创建新的原生库,则应使用 CMake。
- 下载 NDK 和构建工具
要为您的应用编译和调试原生代码,您需要以下组件:
- [Android 原生开发工具包 (NDK)](https://developer.android.com/ndk/index.html):这套工具集允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库,让您可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。
- [CMake](https://cmake.org/):一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。[使用CMake变量](https://developer.android.com/ndk/guides/cmake.html#variables)
- [LLDB](http://lldb.llvm.org/):一种调试程序,Android Studio 使用它来[调试原生代码](https://developer.android.com/studio/debug/index.html)
- 您可以使用 SDK 管理器安装这些组件:
- 在打开的项目中,从菜单栏选择 Tools > Android > SDK Manager。
- 点击 SDK Tools 标签。
- 选中 LLDB、CMake 和 NDK 旁的复选框,如图所示。
- ![](https://i.imgur.com/eTN2W2Z.png)
#### 创建NDK项目(支持 C/C++ 的新项目)(project)或者在一个已存在的项目中创建NDK module ####
- 创建一个NDK项目
- 创建支持原生代码的项目与创建任何其他 Android Studio 项目类似,不过还需要额外几个步骤:把 Include C++ support的勾打上
![](https://i.imgur.com/KLACA7L.png)
- 一直next,直到出一个左边一大白色C++的方形框时,表示这是一个C++的项目,右边选择C++标准,可以选择默认的Toolchain Default,同时可以选择C++11,因为C++11有更多的新特性和功能。,一般选择C++11。
![](https://i.imgur.com/24eT4pk.png)
- 点击Finish后,进入工程目录,如图所示,除了java文件夹外多了一个cpp文件夹,cpp就是存放c和c++代码的文件夹,如下图所示
![](https://i.imgur.com/oA2Zpsi.png)
- 同时,下面为CMakeLists.txt和build.gradle中的内容
- CMakeLists.txt(注释部分已删除)
- ![](https://i.imgur.com/h3a4aqk.png)
- build.gradle中的内容
- ![](https://i.imgur.com/4lDDu2j.gif)
- PS:如果在创建project时,没有勾选,就只是一个简单的AS的Android项目,这时如果想要创建C++项目,可以参考下面的在module中
- 如何在一个module中添加CPP组件,创建原生源文件
- 一般,我们的项目创建时并不是一个C++项目,而是一个Android普通的项目,如果需要用到NDK,JNI方面的,这时就需要添加CPP组件,原生源文件,之前都是用ndk-build命令来进行NDK开发,所以正如上面这篇文章(向您的项目添加 C 和 C++ 代码)所述---->由于很多现有项目都使用构建工具包编译其原生代码,Android Studio 还支持 ndk-build(已非主流,过时)。如果您想要将现有的 ndk-build 库导入到您的 Android Studio 项目中,请参阅介绍如何配置 Gradle 以关联到您的原生库的部分。不过,如果您在创建新的原生库,则应使用 CMake。但是还是推荐使用CMake,因为在Android2.2以后的版本,进行JNI开发,更方便
- 同样,我们需要认真阅读[《向您的项目添加 C 和 C++ 代码》](https://developer.android.com/studio/projects/add-native-code.html)的---->”向现有项目添加 C/C++ 代码“这一小章节,注意CMake构建的脚本 CMakeLists.txt中的内容和build.gradle中的注意细节,一定要认真阅读[《向您的项目添加 C 和 C++ 代码》](https://developer.android.com/studio/projects/add-native-code.html),或者可以参考上面创建C++项目的build.gradle和CMakeLists.txt中的内容。
#### 添加 NDK API ####
- Android NDK 提供了一套实用的原生 API 和库。通过将 [NDK 库](https://developer.android.com/ndk/guides/stable_apis.html)包含到项目的 CMakeLists.txt 脚本文件中,您可以使用这些 API 中的任意一种。
- [NDK 库(Android NDK 原生 API)](https://developer.android.com/ndk/guides/stable_apis.html)
- [概览](https://developer.android.com/ndk/guides/stable_apis.html#purpose)
- [主要的原生 API 更新](https://developer.android.com/ndk/guides/stable_apis.html#mnu)([Android平台版本对应的API等级](https://developer.android.com/guide/topics/manifest/uses-sdk-element.html#ApiLevels))
- [Android API 级别 3](https://developer.android.com/ndk/guides/stable_apis.html#a3)
- [Android API 级别 4](https://developer.android.com/ndk/guides/stable_apis.html#a4)
- [Android API 级别 5](https://developer.android.com/ndk/guides/stable_apis.html#a5)
- [Android API 级别 8](https://developer.android.com/ndk/guides/stable_apis.html#a8)
- [Android API 级别 9](https://developer.android.com/ndk/guides/stable_apis.html#a9)
- 从 API 级别 9 开始,您可以使用原生代码编写整个 Android 应用,无需使用任何 Java。注:在原生代码中编写您的应用本身并不能让您的应用在 VM 中运行。 此外,您的应用仍必须通过 JNI 访问 Android 平台的大部分功能
- [Android API 级别 14](https://developer.android.com/ndk/guides/stable_apis.html#a14)
- [Android API 级别 18 ](https://developer.android.com/ndk/guides/stable_apis.html#a18)
#### JNI规范 ####
- 在进行NDK开发时,阅读官网的《[向您的项目添加 C 和 C++ 代码](https://developer.android.com/studio/projects/add-native-code.html)》涉及到的[Android 的 JNI 提示](https://developer.android.com/training/articles/perf-jni.html),而在[Android 的 JNI 提示](https://developer.android.com/training/articles/perf-jni.html)中又涉及到了[JNI规范](http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html),来了解JNI如何工作,以及JNI的特点。
- 早已有大神将官方的文档翻译(可以说是摘抄)到自己的博客了
- [《Java 本地接口规范》](http://blog.csdn.net/zhanghw0917/article/details/7000377)
- [《Java 本地接口规范》- 简介 ](http://blog.csdn.net/snowdream86/article/details/6886954)
- [已整理的目录篇](http://116.196.111.149:8080/jni.htm)
#### APK分析仪 ####
- 如果您想要验证 Gradle 是否已将原生库打包到 APK 中,可以使用[APK分析器 ](https://developer.android.com/studio/build/apk-analyzer.html)
- 步骤
- 选择 Build > Analyze APK。
- 从 app/build/outputs/apk/ 目录中选择 APK 并点击 OK。
- 如下图所示,您会在 APK 分析器窗口的 lib/<ABI>/ 下看到 编译好的动态库`libnative-lib.so`。
- ![](https://i.imgur.com/G3HpvJz.png)
- 提示:如果您想要试验使用原生代码的其他 Android应用,请点击 File>New>Import Sample 并从Ndk列表中选择示例项目。
#### 指定 ABI ####
- APP_ABI
- ABI全称是:Application binary interface,即:应用程序二进制接口,它定义了一套规则,允许编译好的二进制目标代码在所有兼容该ABI的操作系统和硬件平台中无需改动就能运行。(具体的定义请参考 [百度百科](https://baike.baidu.com/item/ABI/10912305#viewPageContent) 或者 维基百科 )
- 由上述定义可以判断,ABI定义了规则,而具体的实现则是由编译器、CPU、操作系统共同来完成的。不同的CPU芯片(如:ARM、Intel x86、MIPS)支持不同的ABI架构,常见的ABI类型包括:armeabi,armeabi-v7a,x86,x86_64,mips,mips64,arm64-v8a等。
- 这就是为什么我们编译出来的可以运行于Windows的二进制程序不能运行于Mac OS/Linux/Android平台了,因为CPU芯片和操作系统均不相同,支持的ABI类型也不一样,因此无法识别对方的二进制程序。
- 而我们所说的“**交叉编译**(在一个平台上去编译另一个平台上可以执行的本地代码)”的核心原理也跟这些密切相关,交叉编译,就是使用交叉编译工具,在一个平台上编译生成另一个平台上的二进制可执行程序,为什么可以做到?因为交叉编译工具实现了另一个平台所定义的ABI规则。我们在Windows/Linux平台使用Android NDK交叉编译工具来编译出Android平台的库也是这个道理。
- 支持的 ABI(每个 ABI 支持一个或多个指令集。下图提供每个 ABI 支持的指令集概览。)
- ![](https://i.imgur.com/DzKrRs9.png)
- 默认情况下,Gradle 会针对 [NDK 支持的 ABI](https://developer.android.com/ndk/guides/abis.html#sa) 将您的原生库构建到单独的 .so 文件中,并将其全部打包到您的 APK 中。如果您希望 Gradle 仅构建和打包原生库的特定 ABI 配置,您可以在模块级 build.gradle 文件中defaultConfig节点内使用 ndk.abiFilters 标志指定这些配置,如下所示:
![](https://i.imgur.com/9Ot21no.png)
- 在大多数情况下,您只需要在 ndk {} 块中指定 abiFilters(如上所示),因为它会指示 Gradle 构建和打包原生库的这些版本。不过,如果您希望控制 Gradle 应当构建的配置,并独立于您希望其打包到 APK 中的配置,请在 defaultConfig.externalNativeBuild.cmake {} 块(或 defaultConfig.externalNativeBuild.ndkBuild {} 块中)配置另一个 abiFilters 标志。Gradle 会构建这些 ABI 配置,不过仅会打包您在 defaultConfig.ndk{} 块中指定的配置。
- 为了进一步降低 APK 的大小,请考虑[配置 ABI APK 拆分](https://developer.android.com/studio/build/configure-apk-splits.html#configure-abi-split),而不是创建一个包含原生库所有版本的大型 APK,Gradle 会为您想要支持的每个 ABI 创建单独的 APK,并且仅打包每个 ABI 需要的文件。如果您配置 ABI 拆分,但没有像上面的代码示例一样指定 abiFilters 标志,Gradle 会构建原生库的所有受支持 ABI 版本,不过仅会打包您在 ABI 拆分配置中指定的版本。为了避免构建您不想要的原生库版本,请为 abiFilters 标志和 ABI 拆分配置提供相同的 ABI 列表。
#### 从 ndkCompile 迁移 ####
- 如果您使用已过时的ndkCompile ,你应该迁移---使用 CMake 或 ndk-build 生成。因为ndkCompile生成的中间文件Android.mk 给你,迁移到ndk-build 可能是一个简单的选择。
- 从ndkCompile迁移到ndk-build,进行如下操作:
1. 使用ndkCompile,至少一次,通过Build > Make Project, 来编译项目,这将为您生成Android.mk文件。
2. 通过 `project-root/module-root/build/intermediates/ndk/debug/Android.mk. `到找到的自动生成的Android.mk 文件。
3. 迁移Android.mk 文件到其他的目录,比如和module下的build.gradle同一等级的目录,这将确保 clean时,Gradle 并不会删除该脚本文件,
4. 打开Android.mk文件并编辑脚本中的任何路径,这样,他们是相对于当前脚本文件的位置。
5. [Link Gradle to the Android.mk file. ](https://developer.android.com/studio/projects/add-native-code.html#link-gradle)
6. 禁用ndkCompile 通过打开build.properties文件并删除以下行:`android.useDeprecatedNdk = true`
7. 通过单击工具栏中的Sync Project ![](https://i.imgur.com/dISdrfs.png),应用这些改变。
#### 需要注意的几点 ####
- CMakeLists.txt脚本文件,`Android.mk`,`Application.mk`,这2个文件全是通过CMakeLists.txt来写;如果项目目录的CPP文件夹下有N个原生源文件,那么CMakeLists.txt中的add_library中就要对应有N个路径,类似于native-lib.cpp ,对应的add_library就对应的`src/main/cpp/native-lib.cpp`,如果CPP有test.cpp,则add_library对应有`src/main/cpp/test.cpp`;这就是编译的配置,而且相互之间空格隔开;
- 这配置和module目录下的build.gradle的`android--->externalNativeBuild--->cmake--->path "CMakeLists.txt",`编译build时,通过这个路径path,找到CMakeLists.txt里面的构建命令,这是相互对应的。CMake是Unix自动化固件,跨平台
- find_library是链接静态库,如果有其他的.so库,就写在find_library节点内。
- 在使用Cmake插件之前,是在module目录下的build.gradle中配置NDK选项,以后就不需要在module目录下的build.gradle中NDK,因为这些操作,都在CMakeLists.txt中配置好了
#### 具体的创建工程 ####
- JNI知识总结
- cpp中原生源文件(.c、.cpp)方法名的命名规则
- 这里的命名规则只适用于跟java 文件中的native 方法相对应C 语言函数,而C 语言中的其他函数命名只要符合C 语言规则就行。java 文件中的native 方法有N个,对应的源文件中就有N个函数,来实现native方法。
- C++调用Java
#### Java和C++字符串转换 ####
- JNI 基本类型和本地等效类型的对应表格如下:
![](https://i.imgur.com/oBMdtIY.png)
- 引用类型,JNI 还包含了很对对应于不同Java 对象的引用类型,JNI 引用类型的组织层次如下图
![](https://i.imgur.com/DfLShli.png)
- Java中任何一种类型,在Jni中都有一种对应的类型,比如:String--->jstring,但是Java中String是双字节的,在C++中不是双字节的,这就涉及到一个字符串转换,编码的转换
- string转换为jstring
~~~
jstring c2j(JNIEnv* env, string cstr)
{
return env->NewStringUTF(cstr.c_str());
}
~~~
- jstring转换为string
~~~
string j2c(JNIEnv* env, jstring jstr)
{
string ret;
jclass stringClass = env->FindClass("java/lang/String");
jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
// 把参数用到的字符串转化成java的字符
jstring arg = c2j(env, "utf-8");
jbyteArray jbytes = (jbyteArray)env->CallObjectMethod(jstr, getBytes, arg);
// 从jbytes中,提取UTF8格式的内容
jsize byteLen = env->GetArrayLength(jbytes);
jbyte* JBuffer = env->GetByteArrayElements(jbytes, JNI_FALSE);
// 将内容拷贝到C++内存中
if(byteLen > 0)
{
char* buf = (char*)JBuffer;
std::copy(buf, buf+byteLen, back_inserter(ret));
}
// 释放
env->ReleaseByteArrayElements(jbytes, JBuffer, 0);
return ret;
}
~~~
- jstring转换成c语言的char* 类型
![](https://i.imgur.com/5jKKxay.png)
#### javah和javap
- javah
- 开发时,Java代码中native声明了本地方法,由于本地方法没有方法体,需要到cpp文件中完善对应的本地方法,但是cpp中原生源文件(.c、.cpp)对应本地方法的方法名的命名很麻烦,如果一个一个来写,可能会出错,这时,可以使用javah来生成一个头文件,该头文件中直接写好了Java代码的本地方法对应的cpp中方法命名,可以直接复制到cpp文件中,这样便捷高效。
- javah用于生成native接口定义,比如`javah com.wsc.wangsc.ndktest.Jni `
- ![](https://i.imgur.com/RgmTGee.png)
- ![](https://i.imgur.com/OBXEJaw.png)
- jni类中native方法和.h头文件中对应的方法如下图红色方框
- ![](https://i.imgur.com/o7n4Sex.png)
- ![](https://i.imgur.com/oZyD6XB.png)
- 这样可以直接复制.h头文件中方法命名到cpp文件中
- javap
- javap作用
1. 反编译
2. 生成函数签名
- javap用于生成java函数的签名,比如 `javap -s Jni`
- 前言
- JNI基础知识
- C语言知识点总结
- ①基本语法
- ②数据类型
- 枚举类型
- 自定义类型(类型定义)
- ③格式化输入输出
- printf函数
- scanf函数
- 编程规范
- ④变量和常量
- 局部变量和外部变量
- ⑤类型转换
- ⑥运算符
- ⑦结构语句
- 1、分支结构(选择语句)
- 2、循环结构
- 退出循环
- break语句
- continue语句
- goto语句
- ⑧函数
- 函数的定义和调用
- 参数
- 函数的返回值
- 递归函数
- 零起点学通C语言摘要
- 内部函数和外部函数
- 变量存储类别
- ⑨数组
- 指针
- 结构体
- 联合体(共用体)
- 预处理器
- 预处理器的工作原理
- 预处理指令
- 宏定义
- 简单的宏
- 带参数的宏
- 预定义宏
- 文件包含
- 条件编译
- 内存中的数据
- C语言读文件和写文件
- JNI知识点总结
- 前情回顾
- JNI规范
- jni开发
- jni开发中常见的错误
- JNI实战演练
- C++(CPP)在Android开发中的应用
- 掘金网友总结的音视频开发知识
- 音视频学习一、C 语言入门
- 1.程序结构
- 2. 基本语法
- 3. 数据类型
- 4. 变量
- 5. 常量
- 6. 存储类型关键字
- 7. 运算符
- 8. 判断
- 9. 循环
- 10. 函数
- 11. 作用域规则
- 12. 数组
- 13. 枚举
- 14. 指针
- 15. 函数指针与回调函数
- 16. 字符串
- 17. 结构体
- 18. 共用体
- 19. typedef
- 20. 输入 & 输出
- 21.文件读写
- 22. 预处理器
- 23.头文件
- 24. 强制类型转换
- 25. 错误处理
- 26. 递归
- 27. 可变参数
- 28. 内存管理
- 29. 命令行参数
- 总结
- 音视频学习二 、C++ 语言入门
- 1. 基本语法
- 2. C++ 关键字
- 3. 数据类型
- 4. 变量类型
- 5. 变量作用域
- 6. 常量
- 7. 修饰符类型
- 8. 存储类
- 9. 运算符
- 10. 循环
- 11. 判断
- 12. 函数
- 13. 数学运算
- 14. 数组
- 15. 字符串
- 16. 指针
- 17. 引用
- 18. 日期 & 时间
- 19. 输入输出
- 20. 数据结构
- 21. 类 & 对象
- 22. 继承
- 23. 重载运算符和重载函数
- 24. 多态
- 25. 数据封装
- 26. 接口(抽象类)
- 27. 文件和流
- 28. 异常处理
- 29. 动态内存
- 30. 命名空间
- 31. 预处理器
- 32. 多线程
- 总结
- 音视频学习 (三) JNI 从入门到掌握
- 音视频学习 (四) 交叉编译动态库、静态库的入门学习
- 音视频学习 (五) Shell 脚本入门
- 音视频学习 (六) 一键编译 32/64 位 FFmpeg 4.2.2
- 音视频学习 (七) 掌握音频基础知识并使用 AudioTrack、OpenSL ES 渲染 PCM 数据
- 音视频学习 (八) 掌握视频基础知识并使用 OpenGL ES 2.0 渲染 YUV 数据
- 音视频学习 (九) 从 0 ~ 1 开发一款 Android 端播放器(支持多协议网络拉流/本地文件)
- 音视频学习 (十) 基于 Nginx 搭建(rtmp、http)直播服务器
- 音视频学习 (十一) Android 端实现 rtmp 推流
- 音视频学习 (十二) 基于 FFmpeg + OpenSLES 实现音频万能播放器
- 音视频学习 (十三) Android 中通过 FFmpeg 命令对音视频编辑处理(已开源)