安卓 x86 教程(三)
十、x86 NDK 和 C/C++ 优化
技术进步只是为我们提供了更有效的倒退手段。
Aldous Leonard 赫胥黎
在前一章中,我们介绍了性能优化的基本原则、优化方法和 Android 应用开发的相关工具。由于 Java 是 Android 开发人员的主要应用开发语言,前一章介绍的优化工具主要是针对 Java 的。我们知道 Java 应用是在虚拟机中运行的,速度天生就比 C/C++ 应用慢,C/c++ 应用是直接编译运行在硬件指令上的。此外,由于 C/C++ 的底层和基础性质,C/C++ 应用的开发人员已经创建了更多的优化工具。
矢量化
英特尔编译器支持高级代码生成,包括自动矢量化。对于英特尔 C/C++ 编译器,矢量化是指通过同时对几个元素执行单指令、多数据(SIMD)指令的生成来展开循环。开发者可以手动展开循环,并插入对应于 SIMD 指令的适当的函数调用。这种方法不能向前扩展,并且会导致高开发成本。当具有高级指令支持的新微处理器发布时,这项工作必须重做。例如,早期的英特尔凌动微处理器无法从处理双精度浮点的循环向量化中受益,而单精度则由 SIMD 指令有效处理。
自动向量化 简化了编程任务,因为程序员不必学习每个特定微处理器的指令集。例如,英特尔编译器始终支持最新一代的英特尔微处理器。
-vec
选项为支持 IA32 架构的微处理器(包括英特尔和非英特尔)开启默认优化级别的矢量化功能。为了提高矢量化的质量,您需要指定代码将在其上执行的目标微处理器。为了在基于英特尔架构的 Android 智能手机上获得最佳性能,最好使用–xSSSE3_ATOM
选项。英特尔 C++ 编译器以-O2
及更高的优化级别支持矢量化。
许多循环是自动矢量化的,大多数时候编译器会自己生成最佳代码。然而,有时可能需要程序员的指导。高效矢量化的最大问题是让编译器尽可能精确地估计数据依赖关系。
为了充分利用英特尔编译器矢量化功能,以下技术非常有用:
- 生成并理解矢量化报告
- 通过指针歧义消除提高性能
- 使用过程间优化提高性能
- 使用编译器编译指示
矢量化报告
本节从内存复制的实现开始。循环采用 Android 源代码中常用的结构:
清单 10-1 。内存复制实现
// It is assumed that the memory pointed to by dst
// does not intersect with the memory pointed to by src
void copy_int(int* dst, int* src, int num)
{
int left = num;
if ( left <= 0 ) return;
do {
left--;
*dst++ = *src++;
} while ( left > 0 );
}
对于矢量化实验,您将重用hello-jni
项目。为此,将函数添加到名为jni/copy_cpp.cpp
的新文件中。将该文件添加到jni/Android.mk
的源文件列表中,如下所示:
清单 10-2 。矢量化失败
LOCAL_SRC_FILES := hello-jni.c copy_int.cpp
要启用详细的矢量化报告,请将–vec-report3
选项添加到jni/Application.mk
中的APP_CFLAGS
变量:
APP_CFLAGS := -O3 -xSSSE3_ATOM -vec-report3
如果您重新构建libhello-jni.so
,您会注意到生成了几条注释:
jni/copy_int.cpp(6): (col. 5) remark: loop was not vectorized: existence of vector dependence.
jni/copy_int.cpp(9): (col. 10) remark: vector dependence: assumed ANTI dependence between src line 9 and dst line 9.
jni/copy_int.cpp(9): (col. 10) remark: vector dependence: assumed FLOW dependence between dst line 9 and src line 9.
...
不幸的是,自动矢量化失败了,因为编译器可用的信息太少。如果矢量化成功,赋值将被替换如下:
*dst++ = *src++;
//The previous statement would be replaced with
*dst = *src;
*(dst + 1) = *(src + 1);
*(dst + 2) = *(src + 2);
*(dst + 3) = *(src + 3);
dst += 4; src += 4;
前四个任务将由 SIMD 指令并行执行。但是如果在赋值的左边访问的存储器也在赋值的右边访问,那么赋值的并行执行是无效的。例如,考虑当dst+1
等于src+2
时的情况。在这种情况下,dst+2
的最终值将是不正确的。
备注指出编译器保守假设哪些类型的依赖关系会阻止向量化:
- 流依赖性是来自同一存储器位置的较早存储和较晚加载之间的依赖性。
- 反依赖性是对同一存储器位置的较早加载和较晚存储之间的依赖性。
- 输出依赖于两次存储到同一个内存位置。
从代码注释中,可以安全地假设作者要求由dst
和src
指向的内存不重叠。为了向编译器传递信息,只需向dst
和src
参数添加限制限定符:
void copy_int(int * __restrict__ dst, int * __restrict__ src, int num)
restrict 限定符被添加到 1999 年发布的 C 标准中。要启用对 C99 的支持,您需要将–std=c99
添加到选项中。或者,您可以使用–restrict
选项为 C++ 和其他 C 语言启用它。在前面的代码中,__restrict__
关键字已经被插入,并且总是被认为是restrict
关键字的同义词。
如果您再次重建库,您会注意到循环被矢量化了:
jni/copy_int.cpp(6): (col. 5) remark: LOOP WAS VECTORIZED.
在本例中,由于编译器保守分析,矢量化失败。还有其他循环未矢量化的情况,包括:
- 指令集不允许有效的向量化。以下说明指出了这种类型的问题:
- "使用了非单位步幅"
- "混合数据类型"
- "运算符不适合矢量化"
- "第 XX 行包含不可计算的语句"
- “条件可以保护异常”
- 编译器试探法会阻止矢量化。矢量化是可能的,但实际上可能会导致速度变慢。如果是这种情况,诊断将包含:
- “矢量化是可能的,但似乎效率不高”
- "低行程计数"
- “非内部循环”
- 矢量器的缺点:
- "条件太复杂"
- "下标太复杂"
- "不支持的循环结构"
矢量器产生的信息量由–vec-reportN
控制。您可以在编译器文档中找到更多详细信息。
杂注
如您所见,您可以使用restrict
指针限定符来避免对数据依赖性的保守假设。但是有时候插入restrict
关键词很棘手。如果在循环中访问许多数组,注释所有指针也可能太费力。为了在这些情况下简化矢量化,您可以使用英特尔特有的编译指令simd
。假设迭代之间没有依赖关系,您可以使用它来矢量化内部循环。
Pragma simd
仅适用于在本机整数和浮点类型上操作的for
循环:
for
循环应该是可计数的,在循环开始之前知道迭代的次数。- 循环应该在最里面。
- 循环中的所有内存引用都不应该出错(这对屏蔽的间接引用很重要)。
要用 pragma 对循环进行矢量化,需要将代码重写为一个for
循环,如清单 10-3 所示。
清单 10-3 。可以向量化的内存复制实现
void copy_int(int* dst, int* src, int num)
{
#pragma simd
for ( int i = 0; i < num; i++ ) {
*dst++ = *src++;
}
}
重新构建示例,注意循环是矢量化的。简单的循环重组pragma simd
的和在 Android OS 源代码中插入#pragma simd
允许您在不修改基准本身的情况下将 Softweg 基准的性能提高 1.4 倍。
自动矢量化和限制
前面几节中的例子是基于这样的假设,即在开始优化工作之前,您已经很好地理解了代码。如果您不熟悉代码,可以通过扩展分析范围来帮助编译器分析它。在复制的例子中,编译器应该做出保守的假设,因为它对copy_int
例程的参数一无所知。如果调用点可用于分析,编译器可以尝试证明参数对于矢量化是安全的。
为了扩展分析的范围,您需要启用过程间优化 。在单个文件编译期间,默认情况下会启用其中的一些优化。过程间优化将在单独的章节中介绍。
矢量化不能用来加速 Linux 内核代码,因为在内核模式下使用–mno-sse
选项禁用了 SIMD 指令。这是内核开发人员有意为之的。
过程间优化
如果编译器可以跨函数边界进行优化,它可以执行额外的优化。例如,如果编译器知道某个函数调用参数是常量,那么它可以创建一个专门为该常量参数定制的特殊版本的函数。这个特殊版本以后可以利用参数值的知识进行优化。
要在单个文件中启用优化,请指定–ip
选项。当指定此选项时,编译器会生成一个可由系统链接器处理的最终目标文件。生成目标文件的缺点是几乎完全丢失信息;编译器甚至不会尝试从目标文件中提取信息。
由于信息丢失,单个文件范围可能不足以进行分析。在这种情况下,需要添加–ipo
选项。使用此选项时,编译器会将文件编译成中间表示形式,稍后由特殊的英特尔工具 xiar 和 xild 进行处理。
您使用 xiar 工具而不是 GNU archiver ar 来创建静态库,并且使用 xild 而不是 GNU linker ld。只有在直接调用链接器和归档器时才需要它。更好的方法是使用编译器驱动程序icc
或icpc
进行最终链接。扩展范围的缺点是失去了单独编译的优势——每次修改源代码都需要重新链接,而重新链接会导致完全重新编译。
从全局分析中受益的高级优化技术有很多。 第九章:x86 平台上 Android 应用的性能优化介绍了其中一些技术,其他的将在本章后面讨论。请注意,有些优化是英特尔特有的,并通过–x*
选项启用。
不幸的是,就共享库而言,Android 中的事情稍微复杂一些。默认情况下,所有全局符号都是可抢占的。可抢占性很容易用例子来解释。考虑以下库链接到同一个可执行文件的情况:
清单 10-4 。libone.so ,一个链接库示例
int id(void) {
return 1;
}
清单 10-4 是链接的第一个库,第二个在清单 10-5 中有描述。
清单 10-5 。libtwo.so ,第二个链接库示例
int id( void ) {
return 2;
}
int foo( void ) {
return id();
}
假设这些库是通过执行icc –fpic –shared –o <libname>.so <libname>.c
创建的。只给出严格要求的选项–fpic
和–shared
。
如果系统动态链接器在库libtwo.so
之前加载库libone.so
,那么函数foo()
对函数id()
的调用在libone.so
库中解析。
当编译器优化函数foo()
时,它不能使用来自libtwo.so
库的关于id()
的知识。例如,它不能内联id()
函数。如果编译器内联了id()
函数,就会破坏涉及libone.so
和libtwo.so
的场景。
因此,当您编写共享库时,您应该仔细指定哪些函数可以被抢占。默认情况下,所有全局函数和变量在共享库之外都是可见的,并且可以被抢占。当您实现几个本机方法时,默认设置并不方便。在这种情况下,您只需要导出由 Dalvik Java 虚拟机直接调用的符号。
符号的可见性属性指定一个符号在模块外是否可见,以及它是否可以被抢占:
- “默认”可见性使全局符号在共享库之外可见,并且能够被抢占。
- “受保护的”可见性使符号在共享库之外可见,但该符号不能被抢占。
- “隐藏”可见性使全局符号仅在共享库中可见,并禁止抢占。
回到hello-jni
应用,有必要指定默认可见性是隐藏的,并且为 JVM 导出的函数具有受保护的可见性。
要将默认可见性设置为隐藏,将-fvisibility=hidden
添加到jni/Application.mk
中的APP_CFLAGS
变量:
APP_CFLAGS := -O3 -xSSSE3_ATOM -vec-report3 -fvisibility=hidden -ipo
要覆盖Java_com_example_hellojni_HelloJni_stringFromJNI
的可见性,将属性添加到函数定义中:
Jstring __attribute__((visibility("protected")))
Java_com_example_hellojni_HelloJni_stringFromJNI(JNIEnv* env, jobject thiz)
设置此标志后,默认可见性被隐藏。这是英特尔 NDK 应用的过程间优化程度。
利用 IPP 进行优化
从第七章你知道,Android 应用可以绕过 NDK 开发工具,使用第三方开发的现有.so
共享库。在本章中,我们以英特尔 IPP 函数库为例。使用该库的典型应用包括多媒体和流应用,但是任何时间性能都是问题的应用都将从该工具中受益。
英特尔 IPP (集成性能基元)是英特尔提供的高性能库之一。它是英特尔处理器和芯片组的强大函数库,涵盖数学、信号处理、多媒体、图像和图形处理、矢量计算和其他领域。英特尔 IPP 的一个显著特点是其代码已经基于任何英特尔处理器的特性使用多种方法进行了广泛的优化。可以说是一个与英特尔处理器关联的高度优化的高性能服务库。英特尔 IPP 具有跨平台特性;它提供了一组跨平台和操作系统的通用 API,可用于 Windows、Linux 和其他操作系统,并支持嵌入式、桌面、服务器和其他处理器规模的系统。
事实上,IPP 是一组函数库,每个函数库在相应的函数库中都有不同的功能区域,并且英特尔 IPP 中的函数库在不同处理器架构支持的功能数量上略有不同。例如,英特尔 IPP 5。x 图像处理功能在英特尔架构中可支持 2570 个功能,而在 IXP 处理器架构中仅支持 1574 个功能。
包括英特尔 IPP 在内的各种高性能图书馆提供的服务是多方面和多层次的。应用可以直接或间接使用 IPP。IPP 为应用、其它组件和库提供支持。
使用 IPP 的应用可以分为两个层次——直接使用英特尔 IPP 函数接口,或者使用样本代码间接使用英特尔 IPP。此外,使用 OpenCV 库(一个跨平台开源计算机视觉库)相当于间接使用英特尔 IPP 库。英特尔 IPP 和英特尔 MKL 函数库最终都可以在各种架构的高性能英特尔处理器上运行。
考虑到 IPP 的强大功能,并根据英特尔处理器优化特性的特点,您可以使用英特尔 IPP 库来替换一些经常运行且耗费大量时间的关键源代码。您可以获得比一般代码更高的性能加速。这简直就是一个“站在巨人肩膀上”的实用优化方法。用户无需在关键区域手动编写代码即可实现优化。
英特尔最近发布了名为 Beacon Mountain 的英特尔 Android 开发环境代码。它为 Android 应用开发人员提供了 IPP 和英特尔线程构建模块(TBB)。普通用户可以轻松使用英特尔 IPP、英特尔 TBB、英特尔 GPA 和其他工具进行 Android 应用开发。英特尔 IPP 的示例可在http://software.intel.com/en-us/articles/intel-integrated-performance-primitives-intel-ipp-intel-ipp-sample-code
找到。
NDK 集成优化示例
我们已经介绍了基于 NDK 优化的知识和基本理论的优化。本节使用一个案例研究来演示通过将 NDK 与 C/C++ 相集成的综合优化技术。
本案分为两步。第一步演示了一种在从 C/C++ 代码编译的本地函数上使用的技术,以加速传统的基于 Java 的程序中的计算任务。第二步演示了使用 NDK 编译器优化来实现 C/C++ 优化任务本身。我们在这一章的以下两个部分中介绍每个步骤,这两个部分是紧密联系的。
C/C++:原始应用加速
在前一章中,我们介绍了使用 Java 代码示例(SerialPi)来计算π。在这一节中,我们将把计算任务从 Java 改为 C 代码,使用 NDK 把它变成一个本地库。然后,我们将它与原始的 Java 代码任务进行比较,您将获得一些使用 C/C++ 本地库函数实现传统的基于 Java 的任务加速的第一手经验。
用于本案例研究的应用名为 NDKExp ,我们使用联想 K800 作为目标手机,它运行图 10-1 所示的界面。
图 10-1 。NDKExp 运行界面的原始版本
图 10-1 (a)显示了应用的主界面,包括三个按钮——启动 Java 任务、启动 C 任务、退出 App。点击启动 Java 任务按钮会启动一个传统的 Java 任务(如 Java 写的 SerialPi 源代码所示,它计算π)。当任务完成后,计算的结果将显示在按钮下方,同时显示花费的时间,如图图 10-1 (b)所示。点击启动 C 任务按钮将启动一个用 C 编写的计算任务,使用相同的数学公式计算π。当任务完成后,计算的结果将显示在按钮下方,同时显示花费的时间,如图图 10-1 (c)所示。
如图图 10-1 所示,同样的任务,用传统 Java 编写的应用需要 12.565 秒才能完成;用 C 语言编写并由 NDK 开发工具编译的应用需要 6.378 秒才能完成。这个例子可以让您直观地体验到使用 NDK 来实现性能优化的强大功能。
该示例实现如下。
步骤 1 :创建一个新的 Android 应用项目
-
在 Eclipse 中生成项目,将项目命名为
NDKExp
,并选择 Build SDK 选项以支持 x86 版本的 API(在本例中为 Android 4.0.3)。其他人使用默认值。完成所有这些步骤后,生成项目。 -
Modify the main layout file. Put three text views and three buttons in the layout, set their Text and ID attributes, and adjust their size and position, as shown in Figure 10-2.
图 10-2 。原始版本 NDKExp 的布局
-
Modify the main layout of the class source code file
MainActivity.java
. It reads as follows:1. package com.example.ndkexp; 2. import android.os.Bundle; 3. import android.app.Activity; 4. import android.view.Menu; 5. import android.widget.Button; 6. import android.view.View; 7. import android.view.View.OnClickListener; 8. import android.os.Process; 9. import android.widget.TextView; 10\. import android.os.Handler; 11\. import android.os.Message; 12. 13\. public class MainActivity extends Activity { 14. private JavaTaskThread javaTaskThread = null; 15. private CCodeTaskThread cCodeTaskThread = null; 16. private TextView tv_JavaTaskOuputInfo; 17. private TextView tv_CCodeTaskOuputInfo; 18. private Handler mHandler;; 19. private long end_time; 20. private long time; 21. private long start_time; 22. @Override 23. public void onCreate(Bundle savedInstanceState) { 24. super.onCreate(savedInstanceState); 25. setContentView(R.layout.activity_main); 26. tv_JavaTaskOuputInfo = (TextView)findViewById (R.id.javaTaskOuputInfo); 27. tv_JavaTaskOuputInfo.setText("Java the task is not started "); 28. tv_CCodeTaskOuputInfo = (TextView)findViewById (R.id.cCodeTaskOuputInfo); 29. tv_CCodeTaskOuputInfo.setText("C code task is not start "); 30. final Button btn_ExitApp = (Button) findViewById (R.id.exitApp); 31. btn_ExitApp.setOnClickListener(new /*View.*/ OnClickListener(){ 32. public void onClick(View v) { 33. exitApp(); 34. } 35. }); 36. final Button btn_StartJavaTask = (Button) findViewById(R.id.startJavaTask); 37. final Button btn_StartCCodeTask = (Button) findViewById(R.id.startCCodeTask); 38. btn_StartJavaTask.setOnClickListener(new /*View.*/ OnClickListener(){ 39. public void onClick(View v) { 40. btn_StartJavaTask.setEnabled(false); 41. btn_StartCCodeTask.setEnabled(false); 42. btn_ExitApp.setEnabled(false); 43. startJavaTask(); 44. } 45. }); 46. btn_StartCCodeTask.setOnClickListener(new /*View.*/ OnClickListener(){ 47. public void onClick(View v) { 48. btn_StartJavaTask.setEnabled(false); 49. btn_StartCCodeTask.setEnabled(false); 50. btn_ExitApp.setEnabled(false); 51. startCCodeTask(); 52. } 53. }); 54. mHandler = new Handler() { 55. public void handleMessage(Message msg) { 56. String s; 57. switch (msg.what) 58. { 59. case JavaTaskThread.MSG_FINISHED: 60. end_time = System.currentTimeMillis(); 61. time = end_time - start_time; 62. s = " The return value of the Java task "+ (Double)(msg.obj) +" Time consumed:" 63. + JavaTaskThread.msTimeToDatetime(time); 64. tv_JavaTaskOuputInfo.setText(s); 65. btn_StartCCodeTask.setEnabled(true); 66. btn_ExitApp.setEnabled(true); 67. break; 68. case CCodeTaskThread.MSG_FINISHED: 69. end_time = System.currentTimeMillis(); 70. time = end_time - start_time; 71. s = " The return value of the C code task"+ (Double)(msg.obj) +" time consumed:" 72. + JavaTaskThread.msTimeToDatetime(time); 73. tv_CCodeTaskOuputInfo.setText(s); 74. btn_StartJavaTask.setEnabled(true); 75. btn_ExitApp.setEnabled(true); 76. break; 77. default: 78. break; 79. } 80. } 81. }; 82. } 83. 84. @Override 85. public boolean onCreateOptionsMenu(Menu menu) { 86. getMenuInflater().inflate(R.menu.activity_main, menu); 87. return true; 88. } 89. 90. private void startJavaTask() { 91. if (javaTaskThread == null) 92. javaTaskThread = new JavaTaskThread(mHandler); 93. if (! javaTaskThread.isAlive()) 94. { 95. start_time = System.currentTimeMillis(); 96. javaTaskThread.start(); 97. tv_JavaTaskOuputInfo.setText("The Java task is running..."); 98. } 99. } 100. 101. private void startCCodeTask() { 102. if (cCodeTaskThread == null) 103. cCodeTaskThread = new CCodeTaskThread(mHandler); 104. if (! cCodeTaskThread.isAlive()) 105. { 106. start_time = System.currentTimeMillis(); 107. cCodeTaskThread.start(); 108. tv_CCodeTaskOuputInfo.setText("C codes task is running..."); 109. } 110. } 111. private void exitApp() { 112. try { 113. if (javaTaskThread !=null) 114. { 115. javaTaskThread.join(); 116. javaTaskThread = null; 117. } 118. } catch (InterruptedException e) { 119. } 120. try { 121. if (cCodeTaskThread !=null) 122. { 123. cCodeTaskThread.join(); 124. cCodeTaskThread = null; 125. } 126. } catch (InterruptedException e) { 127. } 128. finish(); 129. Process.killProcess(Process.myPid()); 130. } 131. 132. static { 133. System.loadLibrary("ndkexp_extern_lib"); 134. } 135. }
前面的代码与 SerialPi 的示例代码基本相同。第 123 到 134 行的代码是唯一的新部分。这段代码要求在应用运行之前加载
libndkexp_extern_lib.so
共享库文件。应用需要使用这个库中的本地函数。 -
项目中的新线程任务类
JavaTaskThread
用于计算π。代码类似于 SerialPi 示例中的MyTaskThread
类代码,此处省略。 -
The thread task class
CCodeTaskThread
in the new project calls the local function to calculate π; its source code filesCCodeTaskThread.java
read as follows:1. package com.example.ndkexp; 2. import android.os.Handler; 3. import android.os.Message; 4. public class CCodeTaskThread extends Thread { 5. private Handler mainHandler; 6. public static final int MSG_FINISHED = 2; // The message after the end of the task 7. private native double cCodeTask(); // Calling external C functions to accomplish computing tasks 8. static String msTimeToDatetime(long msnum){ 9. long hh,mm,ss,ms, tt= msnum; 10. ms = tt % 1000; tt = tt / 1000; 11. ss = tt % 60; tt = tt / 60; 12. mm = tt % 60; tt = tt / 60; 13. hh = tt % 60; 14. String s = "" + hh +" Hour "+mm+" Minute "+ss + " Second " + ms +" Millisecond "; 15. return s; 16. } 17. @Override 18. public void run() 19. { 20. double pi = cCodeTask(); // Calling external C function to complete the calculation 21. Message msg = new Message(); 22. msg.what = MSG_FINISHED; 23. Double dPi = Double.valueOf(pi); 24. msg.obj = dPi; 25. mainHandler.sendMessage(msg); 26. } 27. public CCodeTaskThread(Handler mh) 28. { 29. super(); 30. mainHandler = mh; 31. } 32. }
前面的代码类似于 SerialPi 示例的
MyTaskThread
类的代码框架。主要区别在第 20 行。原来计算π的 Java 代码被替换为调用一个本地函数cCodeTask
。要声明cCodeTask
函数是一个局部函数,您必须在第 7 行添加该函数的局部声明。 -
在 Eclipse 中构建项目。同样,这里我们只是构建,而不是运行。
-
在项目根目录下创建
jni
子目录。
第二步:编写 cCodeTask 函数的 C 实现代码
根据第七章:创建和移植基于 NDK 的 Android 应用****的 NDK 示例部分描述的方法,你需要将文件编译成一个.so
库文件。主要步骤如下:
-
Create a C interface file. Since the case is a
CCodeTaskThread
class using a local function, you need to generate the class header file according to the class file of this class. At the command line, go to the project directory and then run the following command:E:\temp\Android Dev\workspace\NdkExp> javah -classpath "D:\Android\android-sdk\platforms\android-15\android.jar";bin/classes com.example.ndkexp.CCodeTaskThread
该命令将在项目目录中生成一个名为
com_example_ndkexp_CCodeTaskThread.h
的文件。文件的主要内容如下:... 23. JNIEXPORT jdouble JNICALL Java_com_example_ndkexp_ CCodeTaskThread_cCodeTask 24. (JNIEnv *, jobject); ...
在第 23–24 行,定义了本地函数
cCodeTask
的原型。 -
Based on the previous header files, you create corresponding C code files in the
jni
directory of the project. In this case, we named itmycomputetask.c
, which reads as follows:1. #include <jni.h> 2. jdouble Java_com_example_ndkexp_CCodeTaskThread_cCodeTask (JNIEnv* env, jobject thiz ) 3. { 4. const long num_steps = 100000000; // The total step length 5. const double step = 1.0 / num_steps; 6. double x, sum = 0.0; 7. long i; 8. double pi = 0; 9. 10. for (i=0; i< num_steps; i++){ 11. x = (i+0.5)*step; 12. sum = sum + 4.0/(1.0 + x*x); 13. } 14. pi = step * sum; 15. 16. return (pi); 17. }
第 4 行到第 16 行是函数的主体——计算π的代码,这是对应于 SerialPi 示例中的
MyTaskThread
类的代码。注意,在第 4 行,变量num_steps
(总步长)的值必须与JavaTaskThread
类表示的相同步长的值相同。否则,比较没有意义。每个
jni
文件的第一行必须包含标题。第 2 行是cCodeTask
函数原型,基于上一步获得的稍微修改过的头文件。第 16 行显示了返回结果。使用 Java 的
double
类型,对应于 C 的jdouble
类型,C 可以有一个直接返回给它的double
类型的pi
变量。这是我们在本章介绍中讨论过的内容。 -
In the project
jni
directory, you must create theAndroid.mk
andApplication.mk
files. The content ofAndroid.mk
reads as follows:1. LOCAL_PATH := $(call my-dir) 2. include $(CLEAR_VARS) 3. LOCAL_MODULE := ndkexp_extern_lib 4. LOCAL_SRC_FILES := mycomputetask.c 5. include $(BUILD_SHARED_LIBRARY)
第 4 行指定了案例文件中的 C 代码。第 3 行表示生成的库的文件名,其名称必须与项目文件
MainActivity.java
的第 133 行System.loadLibrary
函数的参数一致。 -
按照 第七章 关于 NDK 实例一节中描述的方法,将 C 代码编译到项目
lib
目录下的.so
库文件中。 -
部署:运行项目。
应用运行界面如图 10-3 所示。
图 10-3 。扩展版 NDKExp 运行界面
编译器优化扩展应用
在前面的示例中,您见证了 NDK 在应用加速方面的能力。然而,这个应用只实现了一个局部函数,不能为您提供比较编译器优化效果的信息。为此,您需要重新构建应用,并使用它来试验编译器优化的效果。
运行该界面的应用如图 10-3 所示。
该应用有四个按钮。当您单击 Start Java Task 按钮时,响应代码不会改变。
当你点击 Start C Task 或 Start Other C Task 按钮时,应用将启动一个本地函数来运行。
两个函数的代码(函数体)是一样的。它计算π的值,但名称不同。第一个调用cCodeTask
函数,第二个调用anotherCCodeTask
函数。这两个函数位于mycomputetask.c
和anothertask.c
文件中,编译后分别对应库文件libndkexp_extern_lib.so
和libndkexp_another_lib.so
。在这种情况下,使用-O0
选项编译libndkexp_extern_lib.so
,使用-O3
选项编译libndkexp_another_lib.so
,因此一个是非优化编译,另一个是优化编译。
因此,点击启动 C 任务将运行 C 函数的未优化版本,如图图 10-3 (b)所示,点击启动其他 C 任务将运行 C 函数的优化版本,如图图 10-3 (c)所示。任务执行后,系统显示时间消耗的计算结果。
从图中可以看出,无论是否使用编译器优化,本地函数的运行时间总是比 Java 函数的运行时间(12.522 秒)短。相对而言,-O3
优化函数的执行时间(5.632 秒)比未优化(-O0
编译器选项)函数的执行时间(7.321 秒)要短。
从这个比较中,您可以看到使用编译器优化实际上减少了应用的执行时间。不仅如此,它甚至比原来的应用运行时间(6.378 秒)还要短。这是因为没有编译器选项的原始应用默认为优化的-O1
级别,而-O3
优化级别甚至比原始应用更高,因此它的运行时间最短也就不足为奇了。
下面的应用是原始应用 NDKExp 的修改和扩展版本。步骤如下。
步骤 1:修改应用的 Android 部分
-
Modify the main layout file. Add a text view and a button in a layout. Set their Text and ID properties, and adjust their size and position, as shown in Figure 10-4.
图 10-4 。扩展版本 NDKExp 布局
-
Modify the class source code file
MainActivity.java
of the main layout. The main changes are as follows:... 13. public class MainActivity extends Activity { 14. private JavaTaskThread javaTaskThread = null; 15. private CCodeTaskThread cCodeTaskThread = null; 16. private AnotherCCodeTaskThread anotherCCodeTaskThread = null; 17. private TextView tv_JavaTaskOuputInfo; 18. private TextView tv_CCodeTaskOuputInfo; 19. private TextView tv_AnotherCCodeTaskOuputInfo; ... 182. static { 183. System.loadLibrary("ndkexp_extern_lib"); 184. System.loadLibrary("ndkexp_another_lib"); 185. } 186\. }
在第 16 行和第 19 行,为新的 Start Other C Task 按钮添加必需的变量。
关键的变化在第 184 行。除了加载原始共享库文件之外,这些文件还会被添加到另一个库文件中。
-
In the project, add a thread task class with the name
AnotherCCodeTaskThread
that calls a local function to calculate π. Its source code fileAnotherCCodeTaskThread.java
reads as follows:1. package com.example.ndkexp; 2. import android.os.Handler; 3. import android.os.Message; 4. public class AnotherCCodeTaskThread extends Thread { 5. private Handler mainHandler; 6. public static final int MSG_FINISHED = 3; // The message after the end of the task 7. private native double anotherCCodeTask(); // Calling external C functions to complete computing tasks 8. static String msTimeToDatetime(long msnum){ 9. long hh,mm,ss,ms, tt= msnum; 10. ms = tt % 1000; tt = tt / 1000; 11. ss = tt % 60; tt = tt / 60; 12. mm = tt % 60; tt = tt / 60; 13. hh = tt % 60; 14. String s = "" + hh +"Hour "+mm+"Minute "+ss + "Second " + ms +"Millisecond"; 15. return s; 16. } 17. @Override 18. public void run() 19. { 20. double pi = anotherCCodeTask(); // Calling external C function to complete the calculation 21. Message msg = new Message(); 22. msg.what = MSG_FINISHED; 23. Double dPi = Double.valueOf(pi); 24. msg.obj = dPi; 25. mainHandler.sendMessage(msg); 26. } 27. public CCodeTaskThread(Handler mh) 28. { 29. super(); 30. mainHandler = mh; 31. } 32. }
前面的代码几乎是抄录了
CCodeTaskThread
类的代码。它只通过调用另一个名为anotherCCodeTask
的外部 C 函数来完成第 20 行的计算任务,做了一点处理。在第 7 行中,它为本地函数提供了适当的指令,并在第 6 行中更改了消息类型的值。这样,它通过前面的 C 函数将自己与完整的消息区分开来。第 4 行显示了从thread
类继承而来的task
类。 -
在 Eclipse 中构建项目。类似地,在这里,你只有一个构建,而不是运行。
第二步:修改 mycomputetask.c 的 Makefile 文件,重新构建库文件
-
Modify the
Android.mk
file under thejni
directory of the project, which reads as follows:1. LOCAL_PATH := $(call my-dir) 2. include $(CLEAR_VARS) 3. LOCAL_MODULE := ndkexp_extern_lib 4. LOCAL_SRC_FILES := mycomputetask.c 5. LOCAL_CFLAGS := -O0 6. include $(BUILD_SHARED_LIBRARY)
与原来的应用不同,在第 5 行中,您添加了传递给
gcc
的命令LOCAL_CFLAGS
的参数。值-O0
表示没有优化。 -
将 C 代码文件编译成项目的
lib
目录下的.so
库文件。 -
将项目的
lib
目录中的.so
库文件(在本例中,该文件是libndkexp_extern_lib.so
)保存到磁盘中的某个其他目录中。以下操作将删除这个.so
库文件。
步骤 2:为 anotherCCodeTask 函数编写 C 实现代码
从上一节复制cCodeTask
功能的处理步骤。然后将文件编译成.so
库文件。主要步骤如下:
-
Create a C interface file. At the command line, go to the project directory, and then run the following command:
E:\temp\Android Dev\workspace\NdkExp> javah -classpath "D:\Android\android-sdk\platforms\android-15\android.jar";bin/classes com.example.ndkexp.AnotherCCodeTaskThread
该命令将生成一个名为
project com_example_ndkexp_AnotherCCodeTaskThread.h
的目录文件。该文件的主要内容有:... 23. JNIEXPORT jdouble JNICALL Java_com_example_ndkexp_AnotherCCodeTaskThread_anotherCCodeTask 24. (JNIEnv *, jobject); ...
第 23–24 行定义了本地函数
anotherCCodeTask
原型。 -
According to the previously mentioned header files in the project
jni
directory, establish corresponding C code files, in this case namedanothertask.c
, the content of which is based on themycomputetask.c
modification. The modification as follows:1. #include <jni.h> 2. jdouble Java_com_example_ndkexp_AnotherCCodeTaskThread_anotherCCodeTask (JNIEnv* env, jobject thiz ) 3. { ... 17\. }
mycomputetask.c
的第二行被替换为anotherCCodeTask
函数的原型。这是从关于.h
文件的函数原型的描述中复制的同一个函数原型,它是在前面的步骤中创建的,只做了微小的修改。最终的形式可以在代码行 2 中看到。 -
Modify the
Android.mk
file under thejni
directory in the project, as follows:1. LOCAL_PATH := $(call my-dir) 2. include $(CLEAR_VARS) 3. LOCAL_MODULE := ndkexp_another_lib 4. LOCAL_SRC_FILES := anothertask.c 5. LOCAL_CFLAGS := -O3 6. include $(BUILD_SHARED_LIBRARY)
在第 4 行,该值被替换为新的 C 代码文件
anothertask.c
。在第 3 行,该值被替换为新的库文件名,这与System.loadLibrary
函数的参数一致(在项目中的MainActivity.java
文件的第 184 行)。在第 5 行,用于传递的gcc
命令的LOCAL_CFLAGS
参数的值被替换为-O3
,这表示最高级别的优化。 -
将 C 代码文件编译成项目的
lib
目录下的.so
库文件。然后你可以看到项目中lib
目录下的libndkexp_extern_lib.so
文件消失了,取而代之的是一个新生成的libndkexp_another_lib.so
文件。保存库文件是非常重要的。 -
Put the previously saved
libndkexp_extern_lib.so
library file back into thelibs
directory in the project.现在目录中有两个文件。您可以使用
dir
命令来验证:E:\temp\Android Dev\workspace\NdkExp>dir libs\x86 2013-02-28 00:31 5,208 libndkexp_another_lib.so 2013-02-28 00:23 5,208 libndkexp_extern_lib.so
-
您重新部署并运行项目。
运行界面的应用如本章前面的图 10-3 所示。
编译器优化扩展的多种情况比较
通过本章中的案例研究,您将获得关于编译器优化效果的第一手经验。任务执行时间从优化前的 7.321 秒缩短到优化后的 5.632 秒。我们只比较了gcc -O3
和-O0
命令的区别。您可以通过在编译mycomputetask.c
和anothertask.c
两个文件时修改Android.mk
文件来扩展这种配置,然后继续比较使用不同编译器命令选项时优化效果的差异。要修改Android.mk
文件,只需要修改LOCAL_CFLAGS
项的值即可。您可以选择gcc
命令的多个选项进行比较。这里有几个例子来说明这个过程。
示例:使用 SSE 指令比较优化结果
可以让启动 C 任务按钮对应mycomputetask.c
编译的Android.mk
文件:
LOCAL_CFLAGS := -mno-sse
并使启动其他 C 任务按钮对应于anothertask.c
编译的Android.mk
文件:
LOCAL_CFLAGS := -msse3
前者告诉编译器不要编译 SSE 指令;后者允许编译器编程为 SSE3 指令。选择 SSE3 指令的原因是 SSE3 是英特尔凌动处理器支持的最高级别的指令。
运行应用的结果如图 10-5 所示。
图 10-5 。应用 NDKExp 的编译器 SSE 指令优化比较
从图 10-5 可以看出,同样的任务使用 SSE 指令执行时间比不使用 SSE 指令要短。执行时间从原来的 6.759 秒缩短到 5.703 秒。
需要注意的是,在这个例子中,我们完成了对Android.mk
文件的修改,并重新运行ndk-build
来生成.so
库文件。我们立即部署并运行了 NDKExp 项目,但发现我们无法达到预期的效果。原因是因为只有.so
库文件被更新。Eclipse 的项目经理没有意识到项目需要重新构建。结果,apk
没有得到更新,目标机器码上的 NDKExp 应用也不会更新原代码。考虑到这种情况,您可以使用以下方法来避免此问题:
- 从手机上卸载应用。
- 从宿主项目目录的
bin
子目录中删除classes.dex
、jarlist.cache
和NdkExp.apk
文档。 - 在 Eclipse 中删除项目。
- 在 Eclipse 中,重新导入项目。
- 最后,重新部署并运行项目,这样您就可以获得想要的效果。
此示例仅比较 SSE 指令的效果。有兴趣的读者可以尝试其他的gcc
编译器选项,比较它们的运行结果。
此外,在前面的例子中,我们只关心 NDK 效应,所以 C 函数仍然使用单线程代码。有兴趣的读者可以将本章学到的 NDK 优化知识与上一章的多线程优化结合起来,将 C 函数改为多线程,与编译器优化一起实现。在各种应用中编写这样一套优化技术肯定会让应用运行得更快。
概观
与第九章类似,本章主要关注基于英特尔 x86 架构的 Android NDK 的代码和技术方面。我们创建了一个简单的 Android NDK 应用,展示了所有这些部分是如何连接并在 x86 模拟器上运行的。本章还提供了 Android NDK 编译器可以为其开发者提供的优化的高级视图。然后我们看了英特尔的集成性能原语库(IPP),这是一个提供给 x86 开发人员的高性能库。最后,我们用一些如何使用所有讨论的工具和技巧的例子来结束这一章。
十一、在 Windows、Mac OS 和 Linux 上使用英特尔硬件加速执行管理器来加速 x86 仿真上的 Android
我不害怕电脑。我担心他们的缺乏。
艾萨克·阿西莫夫
一旦安装了 Android SDK,运行了 Android 模拟器,并且您的开发环境设置符合您的喜好,前面还有一个挫折:Android 模拟器可能非常慢。尤其是在测试和调试较大的应用时,模拟器的速度是开发中一个明显的瓶颈。最佳解决方案是采用英特尔虚拟化技术(英特尔 VT)的英特尔硬件加速执行管理器(英特尔 HAXM) 。如果您的开发系统使用受支持的英特尔处理器之一,这种硬件辅助虚拟化引擎或虚拟机管理程序将支持闪电般的 Android 仿真。
介绍
该软件:
- 使用英特尔 VT,在特定的英特尔处理器上提供。
- 提供英特尔 x86 Android 虚拟设备的硬件加速仿真。
- 与 Android SDK 集成。
- 英特尔 HAXM 要求安装 Android SDK(版本 17 或更高)。为了获得最佳性能,建议使用 SDK 2.0 版或更高版本。
- 最新的 Windows 或 Mac OS X (32/64 位)。
重要的是,英特尔 HAXM 不能在没有英特尔处理器的系统上使用,也不能在没有所需硬件特性的英特尔处理器上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。此外,英特尔 HAXM 只能为仿真器 x86 加速 Android x86 系统映像。HAXM 已通过英特尔在http://www.intel.com/software/android
提供的 x86 系统映像的验证。
下载英特尔 HAXM
英特尔 HAXM 可以通过 Android SDK 管理器(推荐)安装,也可以通过从英特尔网站下载安装程序来手动安装。
注意英特尔 HAXM 不会自动检查更新。要获得最新版本,请使用 Android SDK 管理器(推荐)或从英特尔软件网络 Android 开发人员网站下载英特尔 HAXM 软件包。
通过 Android SDK 管理器下载
-
启动 Android SDK 管理器。
-
Under Extras, check the box next to Intel x86 Emulator Accelerator (HAXM), as seen in Figure 11-1.
图 11-1 。下载英特尔 x86 模拟器加速器(HAXM)
-
单击安装软件包按钮。
-
查看英特尔公司许可协议。如果您接受这些条款,请选择接受并单击安装。
-
SDK 管理器会将安装程序下载到主 SDK 目录下的 Tools 目录中。
-
提取工具目录中的安装程序,并按照您的平台的安装说明进行操作。
手动下载
- 转到
http://www.intel.com/software/android
。 - 为您的平台选择英特尔 HAXM 安装程序包。
- 提取安装程序,并按照您的平台的安装说明进行操作。
在 Windows 上安装英特尔 HAXM
警告如果您的系统不符合系统要求,包括对英特尔处理器特性的支持,如英特尔虚拟化技术(英特尔 VT),英特尔 HAXM 安装将会失败。
-
从
http://www.intel.com/software/android
或使用 SDK 管理器下载安装包。 -
运行安装程序(如果适用,接受 UAC 提示)。
-
If an older version Intel HAXM is installed, you will see something like Figure 11-2.
图 11-2 。通知对话框
-
单击是升级英特尔 HAXM,或单击否退出安装并保留当前安装的英特尔 HAXM 版本。
-
You will see a screen like Figure 11-3.
图 11-3 。HAXM 安装屏幕
注您可以通过点击英特尔 HAXM 文档随时访问文档。
-
单击下一步。
-
阅读英特尔 HAXM 最终用户许可协议(EULA ),如果您同意,接受 EULA 并继续安装英特尔 HAXM。
-
You will be prompted to adjust the amount of RAM allocated to Intel HAXM, as shown in Figure 11-4.
图 11-4 。HAXM RAM 调整屏幕
注意安装程序也可以作为英特尔 HAXM 的配置工具。要更改内存设置,请再次运行安装程序。
-
Figure 11-5 confirms your Intel HAXM memory allocation settings.
图 11-5 。英特尔 HAXM 准备安装
-
英特尔 HAXM 安装完成后,单击完成退出安装程序。
英特尔 HAXM 现已安装完毕,可以使用了。要验证英特尔 HAXM 是否正在运行,请打开命令提示符窗口并执行以下命令:
sc query
intelhaxm
如果英特尔 HAXM 正在工作,该命令将显示一条状态消息,指示状态为4 RUNNING
。
要停止英特尔 HAXM,请使用以下命令:
sc stop
intelhaxm
要启动英特尔 HAXM,请使用以下命令:
sc start
intelhaxm
调整英特尔 HAXM 内存分配
要更改分配给英特尔 HAXM 的内存量,请再次运行安装程序。
注意对英特尔 HAXM 内存设置的更改将在英特尔 HAXM 重启后生效。当前运行的模拟器将继续使用以前的内存设置。
英特尔虚拟化技术(英特尔 VT-x)能力
安装英特尔 HAXM 时,您可能会遇到有关英特尔 VT-x 支持的错误。以下情况可能会触发此错误消息:
- 您的计算机处理器不支持英特尔 VT-x。
- 英特尔 VT-x 未启用。
不支持英特尔 VT-x
英特尔 HAXM 需要具有英特尔 VT-x 功能的英特尔处理器,并且不能在缺乏该硬件特性的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。
英特尔 VT-x 未启用
在某些情况下,英特尔 VT-x 可能在系统 BIOS 中被禁用,必须在 BIOS 设置工具中启用。要访问 BIOS 设置工具,您需要在计算机启动过程中按某个键。该键取决于使用的 BIOS,但通常是 F2、Delete 或 Esc 键。在 BIOS 设置工具中,英特尔虚拟化可通过术语“虚拟化”、“虚拟化技术”或“虚拟化-d”来识别。请确保启用所有虚拟化功能。有关进入 BIOS 设置和启用英特尔 VT 的具体信息,请联系您的硬件制造商。
小窍门
下面的列表包含了一些使用英特尔 HAXM 驱动程序从 Android 模拟器获得最佳体验的建议:
- 在 AVD 管理器中为您的映像启用 GPU 加速。HAXM 驱动程序通过处理器中的英特尔虚拟化技术本机执行大多数 CPU 指令,GPU 加速将 OpenGL 调用卸载到主机 GPU。截至 SDK release 19,GPU 加速被谷歌认为是“实验性的”。
- 从命令行启动模拟器以获得更详细的输出。
- 使用以下命令启动模拟器:
emulator-x86 –avd <avd name> -partition-size 1024 –gpu on –verbose
- 1024 的分区大小允许安装 1GB 的应用。这与 AVD 管理器中的 SDCard 大小选项不同,后者指定在仿真器内部分配多少存储空间来存储媒体文件。将 GPU 设置为
on
将提供更好的图形性能。 - 确保在控制面板
系统
高级系统设置
环境变量中设置了 GPU 仿真库的
Path
环境变量。您也可以在每次启动新的命令提示符时手动设置它。如果您使用多个 SDK 安装,建议手动设置它。以下<sdk install location>
通常是指:
"c:\Users\<your username>\android-sdk" set PATH=%PATH%;<sdk install location>\tools\lib
- 安装英特尔 HAXM 时,将驱动程序设置为使用系统中一半的可用内存。例如,如果您的系统安装了 6GB 的内存,则使用 3GB 用于英特尔 HAXM 驱动程序。与系统内存相比,这为 HAXM 驱动程序提供了良好的内存平衡。
- 创建映像时,不要将设备 RAM 大小选项设置为大于分配给英特尔 HAXM 驱动程序的 RAM 数量。在前面的示例中,设备 RAM 大小不应大于 3GB,因为只有 3GB 分配给了英特尔 HAXM。
- 32 位系统可选择的英特尔 HAXM 驱动程序的最大内存为 1.6 GB。对于 64 位系统,最大值为 8GB。
有时,当第一次启动一个映像时,它会出现在启动屏幕上。引导过程已完成,但主屏幕没有出现。点击模拟器上的主页按钮,显示主页屏幕。
Mac OS 系统
-
从
http://www.intel.com/software/android
或使用 SDK 管理器下载安装包。 -
打开 DMG 文件,然后运行里面的安装程序。
-
如果安装了旧版本的英特尔 HAXM,您会看到一个通知对话框。单击“确定”关闭对话框。然后,您可以退出安装程序以保留当前版本的英特尔 HAXM,或者继续安装并升级您的英特尔 HAXM 版本。
-
You will see a welcome screen, like Figure 11-6.
图 11-6 。Mac OS 上的英特尔 HAXM 欢迎屏幕
-
单击继续。
-
阅读英特尔 HAXM 最终用户许可协议(EULA ),如果您同意,接受 EULA 并继续安装英特尔 HAXM。
-
You will be prompted to adjust the amount of RAM that will be allocated to Intel HAXM, as shown in Figure 11-7.
图 11-7 。Mac OS 上的英特尔 HAXM RAM 调整屏幕
-
Figure 11-8 confirms your Intel HAXM memory allocation settings.
图 11-8 。Mac OS 上的英特尔 HAXM 完成屏幕
-
选择将安装英特尔 HAXM 的驱动器,然后单击继续。
-
安装英特尔 HAXM 后,单击关闭退出安装程序。
-
英特尔 AXM 现已安装完毕,可以使用了。
要验证英特尔 HAXM 是否正在运行,请打开终端窗口并执行以下命令:
kextstat | grep
intel
如果英特尔 HAXM 运行正常,该命令将显示一条状态消息,指示名为com.intel.kext.intelhaxm
的内核扩展已加载。
要停止英特尔 HAXM,请使用以下命令:
sudo kextunload -b
com.intel.kext.intelhaxm
要启动英特尔 HAXM,请使用以下命令:
sudo kextload -b
com.intel.kext.intelhaxm
调整英特尔 HAXM 内存分配
要更改分配给英特尔 HAXM 的内存量,请再次运行安装程序。
注意对英特尔 HAXM 内存设置的更改将在英特尔 HAXM 重启后生效。当前运行的模拟器将继续使用以前的内存设置。
移除英特尔 HAXM
要卸载英特尔 HAXM,请打开终端窗口并执行以下命令:
sudo
/System/Library/Extensions/intelhaxm.kext/Contents/Resources/uninstall.sh
系统将提示您输入当前用户密码。按照卸载程序的提示删除英特尔 HAXM。
重要提示移除英特尔 HAXM 将禁用所有英特尔 x86 Android 仿真器的加速。现有的 Android 虚拟设备将继续运行,但不再加速。再次安装英特尔 HAXM 将重新启用 Android 模拟器加速。
故障排除
英特尔 HAXM 需要英特尔提供的 Android x86 系统映像。您可以通过 Android SDK 管理器或从英特尔开发人员专区网站手动下载这些图像。
英特尔执行禁用(XD)位功能错误
安装英特尔 HAXM 时,您可能会遇到有关英特尔 XD 支持的错误。
以下情况可能会触发此错误消息:
- 您的计算机处理器不支持英特尔 XD。
- 英特尔 XD 未启用。
不支持英特尔 XD
英特尔 HAXM 需要具有执行禁用(XD)位功能的英特尔处理器,不能在缺少此硬件功能的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。
英特尔 XD 未启用
注意如果处理器支持,苹果电脑会永久启用英特尔 XD。
如果您收到一条错误消息,指出英特尔 XD 未启用,则您的计算机不符合使用英特尔 HAXM 的最低系统要求。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。
英特尔虚拟化技术(VT-x)能力
安装英特尔 HAXM 时,您可能会遇到有关英特尔 VT-x 支持的错误。
以下情况可能会触发此错误消息:
- 您的计算机处理器不支持英特尔 VT-x。
- 英特尔 VT-x 未启用。
不支持英特尔 VT-x
英特尔 HAXM 需要具有英特尔 VT-x 功能的英特尔处理器,并且不能在缺乏该硬件特性的系统上使用。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。
英特尔 VT-x 未启用
注意如果处理器支持,苹果电脑会永久启用英特尔 VT-x。
如果您收到一条错误消息,指出英特尔 VT 未启用,则您的计算机不符合使用英特尔 HAXM 的最低系统要求。要确定您的英特尔处理器的性能,请访问http://ark.intel.com/
。
小窍门
以下列表包含使用英特尔 HAXM 驱动程序从 Android 模拟器获得最佳体验的建议:
- 在 AVD 管理器中为您的映像启用 GPU 加速。英特尔 HAXM 驱动程序通过处理器中的英特尔虚拟化技术本机执行大多数 CPU 指令,GPU 加速将 OpenGL 调用卸载到主机 GPU。
- 在终端中使用以下命令启动模拟器:
./emulator-x86 –avd <avd name> -partition-size 1024 –gpu on
- 1024 的分区大小允许安装 1GB 的应用。这与 AVD 管理器中的 SDCard 大小选项不同,后者指定在仿真器内部分配多少存储空间来存储媒体文件。将 GPU 设置为
on
将提供更好的图形性能。 - 确保 GL 库的环境变量设置正确。在终端中使用以下命令设置
LD_LIBRARY_PATH
变量。修改命令以指向您的 SDK 安装。
export LD_LIBRARY_PATH=<sdk install location>/tools/lib
- 要在新终端启动时自动运行该命令,您可以将该命令添加到您的
∼/.bash_profile
脚本中。 - 安装英特尔 HAXM 时,将驱动程序设置为使用系统中一半的可用内存。例如,如果您的系统安装了 6GB 的内存,则使用 3GB 用于英特尔 HAXM 驱动程序。与系统内存相比,这使得英特尔 HAXM 驱动程序的内存达到了良好的平衡。
- 创建映像时,不要将设备 RAM 大小选项设置为大于分配给英特尔 HAXM 驱动程序的 RAM 数量。在前面的示例中,设备 RAM 大小不应大于 3GB,因为只有 3GB 分配给了英特尔 HAXM。
- 在 32 位系统上,英特尔 HAXM 驱动程序的最大内存为 1.6GB。对于 64 位系统,最大内存为 8GB。
- 有时,当第一次启动一个映像时,它会出现在启动屏幕上。启动过程已完成,但主屏幕没有出现。点击模拟器上的主页按钮,显示主页屏幕。
Linux〔??〕
由于 Google 主要支持 Linux 平台上的 Android 构建,并且许多 Android 开发人员正在 Linux 系统托管的 Eclipse 上使用 AVD,因此 Android 开发人员利用面向 Linux 的英特尔硬件辅助 KVM 虚拟化非常重要,就像面向 Windows 和 IOS 的英特尔 HAXM 一样。要在 Ubuntu 主机平台上启用 KVM,并开始使用支持英特尔硬件辅助虚拟化(hypervisor)的英特尔 Android x86 仿真器,请执行以下步骤。
KVM 安装
第一步是按照 Ubuntu 社区页面的说明(https://help.ubuntu.com/community/KVM/Installation
)安装所需的 KVM。要检查您系统的处理器是否支持硬件虚拟化,请使用以下命令:
$ egrep -c '(vmx|svm)' /proc/cpuinfo
如果输出是0
,说明你的 CPU 不支持硬件虚拟化。
下一步是安装 CPU 检查器:
$ sudo apt-get install cpu-checker
现在,您可以通过发出以下命令来检查您的 CPU 是否支持 KVM:
$kvm-ok
如果您看到此消息:
"INFO: Your CPU supports KVM extensions
INFO: /dev/kvm exists
KVM acceleration can be used"
这意味着您可以使用 KVM 扩展更快地运行虚拟机。
但是,如果你看到这个:
"INFO: KVM is disabled by your BIOS
HINT: Enter your BIOS setup and enable Virtualization Technology (VT),
and then hard poweroff/poweron your system
KVM acceleration can NOT be used"
您需要进入 BIOS 设置并启用英特尔 VT。
安装 KVM
对于 Ubuntu Lucid (10.04)或更高版本,请使用以下命令:
$ sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
接下来,将您的<username>
帐户添加到kvm
和libvirtd
组:
$ sudo adduser your_user_name kvm
$ sudo adduser your_user_name libvirtd
安装完成后,您需要再次登录,以便您的用户帐户成为kvm
和libvirtd
用户组的有效成员。这些组的成员可以运行虚拟机。您可以使用以下命令验证安装是否成功:
$ sudo virsh -c qemu:///system list
如果安装成功,您的屏幕将显示以下内容:
Id Name State
从终端直接从 Android SDK 启动 AVD
现在使用以下命令启动 Android for x86 英特尔仿真器,如图 11-9 所示:
$ <SDK directory>/tools/emulator-x86 -avd Your_AVD_Name -qemu -m 2047 -enable-kvm
图 11-9 。Linux 上的英特尔 HAXM
只有 64 位的 Ubuntu 才允许你运行 2GB 或更大的内存。我的 64 位 Ubuntu 有 6GB 内存,所以我用了三分之一用于 Android AVD。我的名为Intel_Atom_gingerbread_2.3\. '-qemu'
的 AVD 提供了到qemu
的选项,-m
指定了仿真 Android(也就是 guest)的内存量。如果使用的值太小,可能会因为频繁的交换活动而影响性能。添加-show-kernel
查看来自内核的消息。
在 Eclipse 中通过 AVD 管理器启动 AVD
以下是谷歌推荐的程序。如果您从 Eclipse 运行模拟器,请使用基于 x86 的 AVD 运行您的 Android 应用,并包含 KVM 选项:
-
在 Eclipse 中,单击您的 Android 项目文件夹,然后选择 Run
Run Configurations。
-
在运行配置对话框的左侧面板中,选择您的 Android 项目来运行配置或创建新的配置。
-
单击目标选项卡。
-
选择您之前创建的基于 x86 的 AVD。
-
在附加仿真器命令行选项字段中,输入:
-qemu -m 2047 -enable-kvm
-
使用这个运行配置运行您的 Android 项目。
概观
本章介绍了采用英特尔虚拟化技术(英特尔 VT)的英特尔硬件加速执行管理器(英特尔 HAXM)的安装。作为 Android x86 开发人员,这些工具为您提供了最快、最高效的全方位体验。本章包括特定于每个主要操作系统的部分——Windows、Mac OS 和 Linux。这些部分不仅强调了安装过程,还强调了排除一些常见问题的提示和技巧。
十二、通过平台调整执行性能测试和分析应用
我没有失败。我刚刚发现了一万种行不通的方法。
—托马斯·爱迪生
在计算中,硬件加速涉及使用计算机硬件来执行功能,其速度比运行在通用 CPU 上的软件更快。通常,处理器是顺序的,指令是一个接一个执行的。各种各样的技术被用来提高处理性能,硬件加速,正如在 第十一章 中所讨论的,就是其中之一。硬件和软件优化之间的主要区别可以说是抽象层次。由于硬件优化的性质,它们可能比软件优化提供更大的速度提升。硬件加速器是为计算密集型软件代码设计的。
越来越多的开发者使用 FFmpeg 开发 Android 视频应用和 OpenCV 开发图像处理软件,这些都有 NDK 的改编。多媒体应用通常有高性能要求,本章介绍一些 x86 Android 上常见的优化技术。
从您的第一台 x86 全格式视频播放器开始
Android 上内置的编解码程序非常有限,所以开发者使用 FFmpeg 免费开源媒体框架支持全格式解码。FFmpeg 项目包括音频/视频编解码器库和一个用于转换多媒体文件代码的命令行程序,并根据您选择的组件,使用 LGPL 或 GPL 许可证支持跨平台的音频和视频流。它提供录制、转换和流式音频和视频功能。FFmpeg 是用于多媒体 Android 开发的最流行的开源框架;这是研究英特尔架构性能软件调整的良好起点。有关 FFmpeg 项目的更多信息,请访问http://www.ffmpeg.org/
。
我们将从制作一款全新的全格式 x86 播放器开始。本项目推荐使用开源的tewilove_faplayer
。它基于 VLC 播放器,但是tewilove_faplayer
包含了所有需要的组件,而 VLC 播放器必须首先做一个引导来下载所有的组件。VLC 玩家的编译脚本也比tewilove_faplayer
复杂。项目 URL 为https://github.com/shaobin0604/faplayer
,在这里可以阅读项目笔记,下载 ZIP 文件。tewilove_faplayer
可以在 ARM 平台上轻松使用;对于 x86,有一些必要的修改:
-
修改
vlc_fixups.h
。删除__cplusplus
,否则会导致编译问题。 -
修改
\jni\vlc\config.h
。添加此处定义的宏:#define CAN_COMPILE_MMX 1 #define CAN_COMPILE_MMXEXT 1 #define CAN_COMPILE_SSE 1 #define CAN_COMPILE_SSE2 1 #define asm __asm__ #define MODULE_NAME_IS_i422_yuy2_sse2 #define MODULE_NAME_IS_i420_yuy2_sse2 #define MODULE_NAME_IS_i420_rgb_sse2
-
修改
libvlcjni.h
。删除yuv2rgb
;是 ARM NEON 代码,不是 x86 代码。 -
修改
Application.mk
如下:APP_ABI := x86 BUILD_WITH_NEON := 0 OPT_CPPFLAGS += -frtti –fexceptions
-
删除
ext\ffmpeg
中的Android.mk
;您必须将其替换为 x86 FFmpeg 版本。
编译 x86 FFmpeg:交叉编译
一般来说,开源程序通常支持交叉编译。FFmpeg 也不例外。这里有一个脚本文件,可以用来在 Linux 和 Android 上构建 FFmpeg:
#!/bin/bash
NDK=$ANDROID_NDK_ROOT #your ndk root path
PLATFORM=$NDK/platforms/android-14/arch-x86
#PREBUILT=$NDK/toolchains/arm-linux-androideabi-4.4.3/prebuilt/linux-x86
PREBUILT=$NDK/toolchains/x86-4.4.3/prebuilt/linux-x86
function build_one
{
./configure --target-os=linux \
--prefix=$PREFIX \
--enable-cross-compile \
--extra-libs="-lgcc" \
--arch=x86 \
--cc=$PREBUILT/bin/i686-android-linux-gcc \
--cross-prefix=$PREBUILT/bin/i686-android-linux- \
--nm=$PREBUILT/bin/i686-android-linux-nm \
--sysroot=$PLATFORM \
--extra-cflags=" -O3 -fpic -DANDROID -DHAVE_SYS_UIO_H=1 -Dipv6mr_interface=ipv6mr_ifindex -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 $OPTIMIZE_CFLAGS " \
--disable-shared --enable-static \
--extra-ldflags="-Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm" \
--disable-ffplay --disable-avfilter --disable-avdevice --disable-ffprobe \
--disable-yasm \
$ADDITIONAL_CONFIGURE_FLAG
make clean
make -j4 install
}
#x86
CPU=x86
OPTIMIZE_CFLAGS="-march=atom -ffast-math -msse3 -mfpmath=sse"
PREFIX=./android/$CPU
ADDITIONAL_CONFIGURE_FLAG=
build_one
运行这个脚本后,可以使用libavcode.a
、libavformat.a
、libavutil.a
、libswscale.a
;将这些库作为预链接静态库链接到你的项目。
x86 FFmpeg 编译:Android.mk
最好交叉编译 FFmpeg 简单快捷。但是如果需要 FFmpeg 来编译Android.mk
,这个还是可以的。 Havlenapetr FFmpeg 可以用来构建这个脚本。
Havlenapetr 是早期的 Android FFmpeg 项目,因此具有相对简单的音频和视频同步功能。适合初学者学习如何在 Android 上移植 FFmpeg。它的项目 URL 是https://github.com/havlenapetr
。
当你下载了所有的工具,准备工作完成后,就该制作 faplayer x86 了,这是一款全格式的 x86 播放器。你必须首先在设备上播放 1080p MP4 这将使用 Android 默认播放器。软件调谐时,修改PlayerActivity.java
中的selectMediaPlayer
功能,将useDefault
设置为false
。安卓 2.3 再玩一次 1080p MP4。正如预期的那样,性能是次优的。幸运的是,通过软件调优,您可以提高性能。(如果您无法在 Android 4.0 上显示图像,请参阅本章后面标题为如何使用 Android 4.0 NDK 显示图像的章节。)
如何确定 CPU 使用率并找到热点
强烈建议将英特尔的图形性能分析器(GPA) 和 VTune amplifier 作为调整工具,但是如果 GPU 的使用不是目标,还有其他调整工具选项,例如 OProfile(这将需要构建系统映像)。
在屏幕上动态显示 CPU 使用情况
CPU 使用情况可以通过/proc/stat
查询,在 Linux 上使用命令cat /proc/stat
(因此在 Android 上也是如此)。该命令将生成如下所示的字符串:
cpu 4884 440 2841 75755 1681 320 121 0 0 0
cpu02211 212 1639 38296 462 223 90 0 0 0
cpu12673 228 1202 37459 1219 97 31 0 0 0
下面是一些用 Java 写的 CPU 使用函数。您可以使用它们在屏幕上显示 CPU 使用情况:
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq
/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq
/proc/stat
public static long getCurCpuFreq() {
String result = "N/A";
try {
FileReader fr = new FileReader(
/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
BufferedReader br = new BufferedReader(fr);
String text = br.readLine();
br.close();
fr.close();
result = text.trim();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return Long.parseLong(result)/1000;
}
public static long getCurUsage() {
try
{
BufferedReader reader = new BufferedReader( new InputStreamReader( new FileInputStream( "/proc/stat" ) ), 1000);
String load = reader.readLine();
reader.close();
String[] toks = load.split(" ");
long currTotal = Long.parseLong(toks[2]) + Long.parseLong(toks[3]) + Long.parseLong(toks[4])+Long.parseLong(toks[6])
+Long.parseLong(toks[7])+Long.parseLong(toks[8]);
long currIdle =
Long.parseLong(toks[5]);
usage =(long) ((currTotal - total) * 100.0f / (currTotal - total + currIdle - idle));
total = currTotal;
idle = currIdle;
}
catch( IOException ex )
{
ex.printStackTrace();
}
return usage;
}
获取函数运行时间
-
使用
function clock()
(必须包含time.h
)。 -
使用
register rdtsc
。static inline uint64_t read_time(void) { uint32_t a, d; __asm__ volatile("rdtsc" : "=a" (a), "=d" (d)); return ((uint64_t)d << 32) + a; }
这两个函数返回函数的运行时。
使用 Yasm 获得性能最佳的 x86 库
Yasm 是一个 x86 ASM 汇编程序。在 FFmpeg 移植的情况下,通常建议您在编译 ARM 版本时添加-disable-yasm
选项。但是如果你正在编译一个 x86 版本,disable-yasm
将会丢弃大量的优化代码,这会显著降低性能。
Yasm 是 NASM 汇编器在“新”BSD 许可下的完全重写(一些部分在其他许可下;详见http://yasm.tortall.net/
的 Yasm 网站)。Yasm 目前支持 x86 和 AMD64 指令集;接受 NASM 和 GAS 汇编程序语法;输出二进制、ELF32、ELF64、32 位和 64 位 Mach-O、RDOFF2、COFF、Win32 和 Win64 对象格式。并生成 STABS、DWARF 2 和 CodeView 8 格式的源代码调试信息。
如何使用 Yasm
如果 Yasm 是交叉编译的,那就相当简单了。在 Linux 上下载了 Yasm 源码,安装了 make 之后,运行configure
、make
、make install
就这么简单了。
./configure --enable-shared --prefix=/usr/local
make
make install
然后运行delete -disable-yasm
并再次运行构建脚本。但是如果你直接使用Android.mk
,注意谷歌 NDK 构建脚本不支持.asm
。以下链接是对谷歌 NDK 构建脚本的改进。更多详情可以访问http://software.intel.com/en-us/articles/using-yasm-compiler-on-android-ndkbuild
。
使用 Yasm 的结果
对于 1080p mp4 软件解码,图 12-1 比较了三种配置。
图 12-1 。YASM 比较,单位为纳秒
YASM: -enable yasm –enable asm
NO-YASM: -disable yasm -enable asm
NO-SIMD: -disable yasm -disable asm
启用 Yasm 可以显著提高性能。在前面的例子中,仅通过启用 Yasm,平均时间就减少了 57.6%。(同时启用 Yasm 和 asm 时,平均时间下降了 150%;有关 SIMD 和 ASM 的更多信息,请参见本章的下一节。)下载并安装 Yasm 对于许多种类的开源优化项目来说都是很有价值的一步,包括那些基于 x264、Vp8 和 manifestly x86 的项目。
使用 SSE(英特尔的流式 SIMD 扩展)优化色彩空间转换
图像的颜色空间 以机器可读的格式表示颜色。正如不同的人可能会将文森特·梵高的虹膜描述为“紫色”或“靛蓝”,等离子电视以 RGB 格式呈现颜色 1 而这幅画的海报的打印文件使用 CMYK 代码。 2 要在这些不同的色彩空间格式之间转换图像,必须进行色彩空间转换。视频一般是 YUV 格式;液晶屏是 RGB 格式;而相机输出一般是 nv21 格式。FFmpeg 提供了swscale
函数来执行这种转换。对于大型图像文件,色彩空间转换将消耗更多的 CPU 能力,正如您在表 12-1 中看到的。使用英特尔的 SIMD 流扩展(SSE)指令集可以产生 6-16 倍的性能提升。
表 12-1 。SSE 优化
|
3040×1824 NV21-RGB888
|
SWS _ 双线性
|
SWS _ FAST _ 双线性
| | --- | --- | --- | | 不使用 Yasm | 425 毫秒 | 158 毫秒 | | 使用 Yasm | 179 毫秒 | 155 毫秒 | | 使用 SSE NV21-RGB888 | 27 毫秒 | 27 毫秒 |
SSE (SIMD 技术)是 x86 Android (ARM 有 NEON——也是 SIMD 技术)上最重要的优化技术,尤其是对于多媒体 app。这是因为它提供了全面的性能优化。
什么是 SIMD?
单指令多数据(SIMD) 设备具有多个处理元件,可同时对多个数据点执行相同的操作。大多数现代 CPU 设计包括 SIMD 指令,以提高多媒体性能。英特尔的 Medfield CPUs 支持 MMX、MMX2、SSE、SSE2、SSE3 和 SSSE3,但不支持 SSE4 和 AVX。SIMD 支持也可以在运行时动态检查(参见 FFmpeg 1.0 上的cpuid
—cpu.c
上的函数ff_get_cpu_flags_x86
)。
有三种方法可以实现 SIMD 码。
- C/C++ 语言级的内在函数,用
emmintrin.h
定义。直到现在,除了 WEBP,很少有开源库使用它。如果 SIMD 代码以这种方式实现,它很容易适应所有的硬件平台。(例如,它可以与 ARM 平台的 NEON 代码互换。) - 内嵌汇编程序(使用最广泛)。它不需要单独的组装和链接步骤,比单独的组装器更方便。
- 单独的汇编程序。它有多种风格(NASM、TASM、MASM 等),文件扩展名为
.s
、.asm
。(扩展名为.asm
的汇编程序,安卓 NDK 无法正常编译;您必须使用上一节“如何使用 Yasm”中提供的补丁程序)
SIMD 是如何运作的
要添加两个 8 位整数数组,通用 C 代码如下所示:
Int s[16];
for(int i=0;i<16;i++){
S[i]=data1[i]+data2[i]; //ensure s[i] is 0∼255
}
但是如果你使用 SSE,你只需要:
movups data1,xmm1
movups data2,xmm2
paddusb xmm1,xmm2
movntq xmm2,S
使用一条指令paddusb
,可以同时执行 16 个add
操作。这听起来很棒,但它实际上有局限性。所有的数据必须组织良好,算法可以矢量化。然而,这确实提高了性能,尤其是对于多媒体应用。
SIMD 有五种类型的指令:
- 数据移动,如
movd
、movq
、movups
- 布尔逻辑:
psllw
和psrlw
- 数学:
paddb
和pmulhw
- 比较 :
pcmpeqb
- 数据打包:
packssdw
、punpcklbw
、pshufb
数据打包(见图 12-2 和 12-3 )对 SIMD 来说是最困难的部分。在下面两张图中,您可以看到两种不同的操作,它们的组织和结构,以及它们如何执行数据打包过程。
图 12-2 。使用 Punpcklbw 进行数据打包
图 12-3 。用 Packssdw 打包数据
压缩混洗字节(pshufb
)取寄存器中的字节 R =【R0 R1 R2...R15]和 M = [M0 M1 M2...M15]并用 RM0RM1RM2代替 R...rM15;除了如果 M i 的最高位被置位,它用 0 替换第 I 个条目。这显示在以下代码中:
![image
R0 := (mask0 & 0x80) ? 0 : SELECT(a, mask0 & 0x07)
R1 := (mask1 & 0x80) ? 0 : SELECT(a, mask1 & 0x07)
...
R15 := (mask15 & 0x80) ? 0 : SELECT(a, mask15 & 0x0f)
pshufb
指令可以根据 128 位掩码将任意 8 位数据放入任意位置。
实施 NV21-RGB SSE 代码
FFmpeg yuv2rgb
是 MMX2 码,所以必须先修改成 SSE 码,因为 MMX 是 8 位对齐,SSE 是 16 位对齐。您必须将数据放大到 16 位:
-
Modify
swscale_internal.h
andyuv2rgb_mmx.c
:DECLARE_ALIGNED(8, uint64_t, redDither); ==> DECLARE_ALIGNED(16, uint64_t, redDither); DECLARE_ALIGNED(8, uint64_t, redDither1); DECLARE_ASM_CONST(16, uint64_t, mmx_redmask) = 0xf8f8f8f8f8f8f8f8ULL; ==> DECLARE_ASM_CONST(8, uint64_t, mmx_redmask1) = 0xf8f8f8f8f8f8f8f8ULL; DECLARE_ASM_CONST(16, uint64_t, mmx_redmask) = 0xf8f8f8f8f8f8f8f8ULL;
现在
redDither
和mmx_redmask
可以作为 8 位数据或者 16 位数据。 -
Change the
mov
andmm
instructions:#if HAVE_SSE2 #define MM1 "%xmm" #define MM "%%xmm" #define MOVD "movq" #define MOVQ "movups" #define MOVNTQ "movntps" #define SFENCE "sfence" #define SIMD8 "16" #else #if HAVE_MMX2 #define MM1 "%mm" #define MM "%%mm" #define MOVD "movd" #define MOVQ "movq" #define MOVNTQ "movntq" #define SFENCE "sfence" #define SIMD8 "8" #endif
MMX 使用一个
mm
寄存器,而 SSE 使用一个xmm
寄存器。因为 SSE 有 128 位数据长度(16 字节),所以使用 SSE 时数据偏移量是 16(SIMD 8 是 16)。 -
RGB_PACK24
must be rewritten due to fact that the data length of MMX and SSE are different.DECLARE_ASM_CONST(16, uint8_t, rmask1[16]) = {0x00,0x80,0x80,0x01,0x80,0x80,0x02,0x80,0x80,0x03,0x80,0x80,0x04,0x80,0x80,0x05}; ... MOVQ" "MM""red", "MM"5 \n"\ "pshufb "MANGLE(rmask1)", "MM"5 \n"\ MOVNTQ" "MM"5, (%1) \n"\
这里用的是
pshufb
。关键思想是用pshufb
把每个 R,G,B 值放到正确的位置,用它得到RGB888
数据。以下代码显示了每个 RGB 段由什么组成。例如,RGB0 是 R0、G0 和 B0。 -
添加
ff_nv2rgb_init_mmx
,在ff_get_unscaled_swscale
:/* yuv2bgr */ if ((srcFormat==PIX_FMT_YUV420P || srcFormat==PIX_FMT_YUV422P || srcFormat==PIX_FMT_YUVA420P) && isAnyRGB(dstFormat) && !(flags & SWS_ACCURATE_RND) && !(dstH&1)) { c->swScale= ff_yuv2rgb_get_func_ptr(c); } /* nv2bgr */ if (srcFormat==PIX_FMT_NV21 && isAnyRGB(dstFormat)&& !(flags & SWS_ACCURATE_RND) && !(dstH&1)) { c->swScale= ff_nv2rgb_get_func_ptr(c); }
函数中添加
ff_nv2rgb_get_func_ptr
如何使用 Android 4.0 NDK 显示图像
Surface_lock
和Surface_unlockAndPost
可用于 NDK 层,在设备上显示图像。这两个功能在安卓 4.0 上从libsurfaceflinger_client.so
移到libgui.so
,又移到libui.so
。
使用nm
(Windows 为nm.exe
)可以对libui.so
进行分析。比如在 Windows 上,可以用nm.exe libui.so >>1.txt
,用open 1.txt
找Surface_lock
。可以找到字符串"_ZN7android7Surface4lockEPNS0_11SurfaceInfoEPNS_6RegionE"
,配合dlsym
使用,然后从libui.so
获取函数句柄。
clz = (*env)->GetObjectClass(env, s);
f_Surface_mSurface = (*env)->GetFieldID(env, clz, "mSurface", "I");
if (f_Surface_mSurface == 0)
{
jthrowable e = (*env)->ExceptionOccurred(env);
if (e)
{
(*env)->DeleteLocalRef(env, e);
(*env)->ExceptionClear(env);
}
f_Surface_mSurface = (*env)->GetFieldID(env, clz, "mNativeSurface", "I");
}
(*env)->DeleteLocalRef(env, clz);
surface = (*env)->GetIntField(env, s, f_Surface_mSurface);
有了这个,就可以添加jni
函数上面的代码;它接收 Java 层表面对象,并从该对象获取表面句柄。f_Surface_mSurface
和 Surface_lock 和Surface_unlockAndPost
函数可用于显示 NDK 图层上的图像。
通用交叉编译脚本
开源配置文件一般可以用于 Android 的交叉编译。以下是 x86 平台的通用脚本。它使用 SSE 来优化 JPEG 编码和解码。
#!/bin/bash
HOSTCONF=x86
BUILDCONF=i686-pc-linux-gnu
NDK=$ANDROID_NDK_ROOT
TOOLCHAIN=$NDK/toolchains/x86-4.4.3/prebuilt/linux-x86
PLATFORM=$NDK/platforms/android-14/arch-x86
PREFIX=/home/lym/libjpeg-turbo-1.2.1/android/x86
ELF=$TOOLCHAIN/i686-android-linux/lib/ldscripts/elf_i386.x
export ARCH=x86
export SYSROOT=$PLATFORM
export PATH=$PATH:$TOOLCHAIN/bin:$SYSROOT
export CROSS_COMPILE=i686-android-linux
export CC=${CROSS_COMPILE}-gcc
export CXX=${CROSS_COMPILE}-g++
export AR=${CROSS_COMPILE}-ar
export AS=${CROSS_COMPILE}-as
export LD=${CROSS_COMPILE}-ld
export RANLIB=${CROSS_COMPILE}-ranlib
export NM=${CROSS_COMPILE}-nm
export STRIP=${CROSS_COMPILE}-strip
export CFLAGS="-I$PLATFORM/usr/include -O3 -nostdlib -fpic -DANDROID -fasm -Wno-psabi -fno-short-enums -fno-strict-aliasing -finline-limit=300 -fomit-frame-pointer -march=i686 -msse3 -mfpmath=sse"
export CXXFLAGS=$CFLAGS
export LDFLAGS="-Wl,-T,$ELF -Wl,-rpath-link=$PLATFORM/usr/lib -L$PLATFORM/usr/lib -nostdlib -lc -lm"
./configure --enable-shared --host=$HOSTCONF --build=$BUILDCONF --with-sysroot=$SYSROOT --prefix=$PREFIX
make clean
make -j4 install
受益于 x86 平台的悠久历史,几乎所有开源项目都在 x86 平台上进行了优化,尤其是在多媒体领域。大量的算术函数被写入 SIMD 代码
(FFmpeg)、vp8、x264 和 OpenCV)。通常,您需要选择正确的源代码并使用正确的编译脚本。必要时,可以在 Linux 上调试 NDK 汇编代码。
使用硬件加速进行测试和分析
在 Android 4.0 NDK 中,视频和音频解码基于 OpenMAX AL 1.0.1,允许软件和硬件解码。由 Khronos 集团开发的 OpenMAX(开放媒体加速)是一个免版税的跨平台 API。它的 C 语言编程接口集为音频、视频和静态图像提供了对象抽象。它允许这些资源在各种平台上轻松移植。它面向处理或消费大量多媒体数据的设备,尤其是嵌入式和移动设备,如智能手机和平板电脑。OpenMAX 有三层接口,如图图 12-4 所示——应用层(AL)、集成层(IL)、开发层(DL)。
图 12-4 。OpenMAX 图层
使用集成层(IL) 进行硬件编码
在 Android 4.1 之前,谷歌没有向开发者公开任何硬件编解码接口,所以大量多媒体 app 不得不使用 FFmpeg、x264、VP8 作为视频编解码。尤其是视频编码时,软件编码会占用大部分 CPU 资源(640×480 H.264 编码对于一个 ARM v9 1.2-GHz 双核会占用近 90%的 CPU 资源)。这对 Android 设备的性能是一个巨大的消耗。所以直到 4.1 版本,还没有为 Android 开发出精彩的视频录制应用。开发者和用户的兴趣当然是存在的,但是这样的应用是不可行的。现在,开发人员可以使用 OpenMAX 集成层(IL)来获得硬件编码器接口。(澄清一下,ARM 和 Intel 架构都可以使用这种方法进行硬件编码,但是 ARM 架构会遇到兼容性问题。)OpenMAX IL 由不同的供应商实现,谷歌不保证它的兼容性,因此不能保证它在所有支持 Android 的硬件系统上都能很好地工作。但是对于基于英特尔架构的 Android,兼容性问题已经得到解决。
如何在面向英特尔架构的 Android 上获得 OMX-伊尔界面
Libwrs_omxil_core_pvwrapped.so
是 Medfield 英特尔架构平台上的 OMX-IL 接口层。开发人员可以如下加载它来访问 OMX-伊尔接口。
pf_init = dlsym( dll_handle, "OMX_Init" );
pf_deinit = dlsym( dll_handle, "OMX_Deinit" );
pf_get_handle = dlsym( dll_handle, "OMX_GetHandle" );
pf_free_handle = dlsym( dll_handle, "OMX_FreeHandle" );
pf_component_enum = dlsym( dll_handle, "OMX_ComponentNameEnum" );
pf_get_roles_of_component = dlsym( dll_handle, "OMX_GetRolesOfComponent" );
得到这些句柄后,就可以调用pf_component_enum
和pf_get_roles_of_component
来得到正确的硬件编码接口。这里列出了所有的视频编解码接口:
component OMX.Intel.VideoDecoder.AVC
- role: video_decoder.avc
component OMX.Intel.VideoDecoder.H263
- role: video_decoder.h263
component OMX.Intel.VideoDecoder.WMV
- role: video_decoder.wmv
component OMX.Intel.VideoDecoder.MPEG4
- role: video_decoder.mpeg4
component OMX.Intel.VideoDecoder.PAVC
- role: video_decoder.pavc
component OMX.Intel.VideoDecoder.AVC.secure
- role: video_decoder.avc
component OMX.Intel.VideoEncoder.AVC
- role: video_encoder.avc
component OMX.Intel.VideoEncoder.H263
- role: video_encoder.h263
component OMX.Intel.VideoEncoder.MPEG4
- role: video_encoder.mpeg4
可以根据自己的需求选择合适的组件。比如要做 MP4 编码,可以选择OMX.Intel.VideoEncoder.MPEG4
,调用pf_get_handle
获取硬件 MP4 编码句柄。
OMX-IL 是如何工作的?
为了创建或配置和连接 OpenMAX 组件,应用被编写为集成层(IL)客户端。该 IL 客户端用于调用不同组件的 OpenMAX APIs,如图 12-5 所示。在这个应用中,组件分配视频缓冲区以响应 IL 客户端上的 OMX API。IL 客户端负责从一个组件获取缓冲区,并将它们传递给其他组件。功能OMX_GetParameter
和OMX_SetParameter
用作参数/配置set
,而get. OMX_SendCommand
用于向组件发送命令,包括启用/禁用端口命令和状态改变命令。OMX_EmptyThisBuffer
和OMX_FillThisBuffer
将缓冲区传递给组件。调用pf_get_handle
时必须注册OmxEventHandler
、OmxEmptyBufferDone
和OmxFillBufferDone (OMX_CALLBACKTYPE)
。
图 12-5 。OpenMAX 组件和集成层客户端
分配 OMX 缓冲区并调用OMX_SendCommand
设置OMX_StateExecuting
状态后,就可以使用FillThisBuffer
和EmptyThisBuffer
及其回调函数做硬件编码了。图 12-6 显示了调用顺序。
图 12-6 。OMX-伊尔渲染管道
调用FillThisBuffer
将一张 raw 图片填充到 OMX 本地缓冲区,调用EmptyThisBuffer
让 OMX 组件进行硬件编码;当你完成编码或者当本地输出缓冲区已满时,OMX 组件将调用OnEmptyThisBufferDone
来告诉客户端再次执行EmptyThisBuffer
。所以一个FillThisBuffer
可能会产生几个OnEmptyThisBufferDone
的实例。如果 OMX 组件发现输入缓冲区是空的,它将调用OnFillThisBufferDone
来告诉客户端填充更多的缓冲区空间。
演示:特效录像机
在本节中,您将使用硬件视频编码器来实现特殊效果视频录像机。这个想法是从相机获取数据,并在相机预览图像中添加一个标记。重新编码时,应将标记记录到视频文件中。这听起来是一个简单的设计,但在 Android 4.1 之前,唯一的选择是软件编码器(FFmpeg 或 x264),这会浪费大量的 CPU 资源。从 Android 4.1 开始,引入了新的类MediaCodec
;它与 OMX-IL 相同,但由谷歌实现,谷歌保证其兼容性。
表 12-2 展示了三台录像机的 CPU 使用情况。录像机文件格式为. MP4,使用 MPEG-4 编码器。一般来说,VP8 视频编码对 CPU 的占用最少,其次是 MPEG-4 编码,而 H.264 编码占用的 CPU 资源最多)。
表 12-2 。硬件与软件编码器
如果您使用硬件编码器,所需的总 CPU 资源仅为 3.7%(硬件编码–预览),而软件编码器将需要 46.9%。本例中使用的分辨率为 1024×576。如果想要 1080P 的视频录制,软件解决方案是不可能的!
封装一个硬件视频编码器库
下面的代码展示了一个名为libomx.so
的动态库。它提供了三个简单的功能— EncInit
、EncVideo
和EncRelease
。用法也很简单——你调用EncInit
初始化硬件编码器,调用EncVideo
做硬件编码,调用EncRelease
释放硬件编码器。两个主要结构是stEncConfig
和stEncPic
:
– stEncConfig (use in EncInit)
– stcfg.id = ENC_MPEG4; //choose the encoder
– stcfg.type = ENC_DEFAULT; //for feature use, now must this value
– stcfg.w=1080; //encoding size
– stcfg.h=1920;
– stcfg.framerate = 15; //encoding framerate
– stcfg.controlrate = enum OMX_VIDEO_CONTROLRATETYPE;
– stcfg.bitrate = xxxx; //your bitrate
– stEncPic(use in EncVideo)
– pic.w=1080; //picture size
– pic.h=1920;
– pic.stride = 1080; //picture stride
– pic.pbuf[0]=pmem; //yuv420 image data
– pic.pbuf[1]=pmem+1920*1080;
– pic.pbuf[2]=pmem+1920*1080/4;
实现相机预览
因为不能使用通用的 Google Android API 来开发这个演示,所以必须实现摄像头预览。我推荐用setPreviewCallbackWithBuffer
来获取相机预览数据。虽然setPreviewCallbackWithBuffer
和setPreviewCallback
都可以获得预览数据,但是前者更高效,并且会避免 Java 垃圾收集。
准则:为频繁使用保留足够的内存。这样做可以避免伪 Java 垃圾收集。
在预览回调,你应该传递图像数据到 NDK 层;不要添加不必要的代码。我添加了camera.getParameters().getPreviewSize().width
作为例子来说明它消耗了大量的 CPU 资源。
准则:尽可能少地调用任何对象函数。将其值赋给一个变量,并使用该变量。
在 NDK 层,将预览数据从 NV21 转换为 RGB565(或 RGB88,根据您的屏幕配置),然后将数据显示到屏幕上(使用Surface_lock
和Surface_unlockAndPost
)。
用 Traceview 分析 Java 代码
您可以使用 Traceview 来分析 Java 代码的性能。Traceview 已经通过一个新的插件集成到 Eclipse 中。该插件与 DDMS 插件集成在一起,因此使用开始/停止概要分析按钮将直接在 Eclipse 中打开跟踪,而不是启动独立的工具。此外,如果您按住 Ctrl 键单击(在 Mac 上按住 Command 键单击)某个方法,它将在编辑器中打开源文件(您必须添加android:debuggable="true"
)。
选择正确的包名com.Filters
,按开始方法剖析,如图 12-7 所示。等待一会儿,然后停止剖析:你可以在图 12-8 的中看到跟踪结果。
图 12-7 。在 Eclipse 中使用过滤器
图 12-8 。剖析相机应用
从结果中,很容易看出Camera.getParameters
将耗费大部分 CPU 资源(97%)。那就是PreviewCallback
里的camera.getParameters().getPreviewSize().width
。Traceview 只能分析 Java 代码。如果您想评测 NDK 代码,可以使用英特尔工具 VTune,它可以从http://software.intel.com/en-us/intel-vtune-amplifier-xe
获得。
启动记录线程
要在 FFmpeg 中引用来自-example.c
的输出,您可以使用它作为基础版本。剩下的工作是确定如何在记录线程中获取视频和声音数据。总体思路是,一旦获得一帧视频(或音频),就可以锁定缓冲区并调用一个记录线程开始工作。然而,这是非常低效的。相机预览线程、音频线程和硬件记录线程的并行性已经被破坏,大多数时候,CPU 只是等待。这就是CircleBuffer
类的用武之地;生产者(相机预览线程、音频线程)会谨慎地将缓冲区填充到CircleBuffer
(如果缓冲区已满,只需重叠最后一个缓冲区,这样即使记录线程很慢,数据也会一直刷新),消费者会谨慎地从中获取缓冲区(如果缓冲区为空,可以选择获取前一个缓冲区或等待)。
准则:尽可能保持所有线程的并行性。 CircleBuffer
一般是不错的选择。
添加特殊效果
现在,视频原始数据已经在 NDK 图层上处理过了,所以你可以很容易地添加特殊效果,比如视频标记。只需将您的标记图像(Y 数据,丢弃 UV 数据)阿尔法混合到视频原始数据中。这项工作可以由 CPU 完成,也可以由 GPU 完成。事实上,谷歌视频录制也支持有限(非常有限)的视频效果。它使用 GPU 而不是 CPU 来降低 CPU 负载。GPU 渲染(OpenGL-ES)非常复杂,很难理解。即使代码很简单,开发人员也应该对 OpenGL 有深入的了解。这里我只是解释一下 GPU 渲染的一个基本工作流程。
-
获取并初始化默认的 EGL 显示。
EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); eglInitialize(mEglDisplay, &majorVersion, &minorVersion);
-
创建一个只有一个元素的整数数组。它将保存返回值,该值指示与由
configAttributes
数组指定的属性相匹配的 EGL 配置的数量。创建一个包含一个元素的EGLConfig
数组来存储匹配属性的第一个 EGL 配置。调用eglChooseConfig()
并作为参数提供您在步骤 1 中初始化的EGLDisplay
对象、指定要匹配的配置属性的数组、第一个匹配的EGLConfig
对象的占位符、EGLConfig
占位符的大小以及存储匹配的配置数量的num_configs
数组。将来自eglConfigs
数组的单个配置存储在EGLConfig
变量eglConfig
中。EGLint configAttribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_NONE }; eglChooseConfig(mEglDisplay, configAttribs, &config, 1, &numConfigs);
-
调用
eglCreateWindowSurface()
创建一个 EGL 表面,并提供eglDisplay
和eglConfig
作为参数,它们是您在步骤 1 和 2 中设置的EGLDisplay
和EGLConfig
的实例。在下面的代码示例中,从一个从Screen
类派生的类中调用eglCreateWindowSurface()
,该参数将EGLSurface
对象绑定到当前屏幕。eglCreateWindowSurface(mEglDisplay, config,mNativeWindow.get(), NULL);
-
调用
eglCreateContext()
创建一个 EGL 上下文。eglCreateContext(mEglDisplay, config, EGL_NO_CONTEXT,contextAttribs);
-
调用
eglMakeCurrent()
将 EGL 上下文绑定到 EGL 表面和 EGL 显示。eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext);
-
创建程序和加载着色器到 EGL。你的特效函数需要实现为一个着色器。
loadShader(GL_FRAGMENT_SHADER, fSrc[i], &fShader); createProgram(vShader, fShader, &mProgram[i]);
-
做渲染。
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mFrameWidth, mFrameHeight, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, pixels); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_BYTE, 0);
-
如果你想看 EGL 的图像,你可以打电话给
glReadPixels
。
其实,你也可以用GLSurfaceView
**。**一个GLSurfaceView
提供以下功能:
- 管理一个 surface,它是一块特殊的内存,可以合成到 Android view 系统中。
- 管理 EGL 显示,这使 OpenGL 能够呈现到图面中。
- 接受用户提供的进行实际渲染的渲染器对象。
- 在专用线程上呈现,以将呈现性能与 UI 线程分离。
- 支持按需渲染和连续渲染。
- 可选地包装、跟踪和/或错误检查渲染器的 OpenGL 调用。
有关GLSurfaceView
的更多信息,请查看http://developer.android.com/reference/android/opengl/GLSurfaceView.html
。你可以在http://www.learnopengles.com/how-to-use-opengl-es-2-in-an-android-live-wallpaper
找到它的样品。
**在 Android 4.0 上使用 OpenMAX AL
OpenMAX AL API 通过为系统的媒体回放和录制功能提供通用抽象,为应用级多媒体解决方案提供跨一系列平台的可移植性。API 围绕一组高级对象组织这种抽象。应用从一个“引擎”对象获取所有对象,该对象封装了一个 OpenMAX AL 会话,并作为所有其他对象的保护伞。
使用原生多媒体 API (OpenMAX AL)的优势
从 Android 4.0 开始,谷歌包括了基于 Khronos group OpenMAX AL 1.0.1 标准的 Android 原生多媒体 API,截至 Android API level 14 (Android 平台版本 4.0)及更高版本。它为低级流式多媒体提供了直接、高效的路径。对于需要在将媒体数据传递给平台进行呈现之前保持对媒体数据的完全控制的应用来说,新路径是理想的。
例如,媒体应用现在可以从任何来源检索数据,应用专有的加密/解密,然后将数据发送到平台进行显示。应用现在还可以将处理后的数据作为 MPEG-2 传输流格式的音频/视频内容的多路复用流发送到平台。该平台对内容进行解复用、解码和呈现。音频轨道被渲染到活动的音频设备,而视频轨道被渲染到Surface
或SurfaceTexture
。当渲染到一个SurfaceTexture
流格式时,应用可以使用 OpenGL 对每一帧应用后续的图形效果。
注意虽然它基于 OpenMAX AL,但 Android 原生多媒体 API 并不是 OpenMAX AL 1.0.1 配置文件(媒体播放器或媒体播放器/记录器)的符合实现。这是因为 Android 没有实现任何一个配置文件所需的所有特性。在接下来的“Android 扩展”一节中描述了任何已知的 Android 行为与规范不同的情况。Android OpenMAX AL 实现具有有限的功能,主要用于某些对性能敏感的本地流媒体应用,如视频播放器。表 12-3 表示 Android 的 OpenMAX AL 实现支持的对象和接口。阴影单元格表示该功能受支持。
表 12-3 。Android 的 OpenMAX AL 实现支持的对象和接口
演示:流媒体播放器
Google 提供了一个流媒体播放器的样本。您可以在您的android-sdk
文件夹中查看samples\native-media\jni \native-media-jni.c
中的样本。函数Java_com_example_nativemedia_NativeMedia_createStreamingMediaPlayer
将创建一个本地流媒体播放器。
用法很简单,但是你要注意两点:
- 视频源必须是 NativeWindow。可以调用
ANativeWindow_fromSurface
从Surface
获取原生窗口,它是从 Java 层传过来的。而Surface
必须是GLSurfaceView
才能保证硬件渲染。 - 用于填充流缓冲区的寄存器
XA_ANDROIDBUFFERQUEUEEVENT_PROCESSED
回调。这个演示是从一个文件中读取缓冲区,但是你也可以从网上读取缓冲区,这样你就可以实现一个 P2P 流媒体播放器。
使用强大的媒体 API:Android 4.1 上的 MediaCodec
Android 有一个很棒的媒体库,允许各种强大的动作。直到最近,还没有编码和解码音频/视频的方法,这使得开发人员几乎可以做任何事情。幸运的是,Jelly Bean 版本引入了android.media.MediaCodec
API。它的设计遵循与 OpenMAX(媒体行业的知名标准)相同的原则和架构,从纯粹的高级媒体播放器过渡到编码器/解码器级别。
示例代码:音频解码器
此示例代码显示了如何实现解码器。它使用两个类— MediaCodec
和MediaExtractor. MediaExtractor
来促进从数据源中提取解复用的(通常是编码的)媒体数据。MediaCodec
当然是用作低级编解码器。
首先你应该使用MediaExtractor
来获取媒体格式:
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(sampleFD.getFileDescriptor(),sampleFD.getStartOffset(), sampleFD.getLength());
MediaFormat format = extractor.getTrackFormat(0);
其次,可以创建MediaCodec
并进行配置。
MediaCodec codec;
ByteBuffer[] codecInputBuffers;
ByteBuffer[] codecOutputBuffers;
MediaCodec codec = MediaCodec.createByCodecName(name);
codec.configure(format, null,null,0); //no display, so surface is null
codec.start();
最后,你做解码。像 OMX-伊尔一样,它有两个港口。你要调用dequeueInputBuffer
发送解码缓冲到MediaCodec
,调用dequeueOutputBuffer
?? 接收外部缓冲。
int inputBufIndex = codec.dequeueInputBuffer(TIMEOUT_US);
if (inputBufIndex >= 0) {
ByteBuffer dstBuf = codecInputBuffers[inputBufIndex];
int sampleSize = extractor.readSampleData(dstBuf, 0);
long presentationTimeUs = 0;
if (sampleSize < 0) {
sawInputEOS = true; sampleSize = 0;
} else {
presentationTimeUs = extractor.getSampleTime();
}
codec.queueInputBuffer(inputBufIndex, 0, sampleSize,
presentationTimeUs,
sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
if(!sawInputEOS){
extractor.advance();
}
}
final int res = codec.dequeueOutputBuffer(info, TIMEOUT_US);
if(res >= 0){
int outputBufIndex = res;
ByteBuffer buf = codecOutputBuffers[outputBufIndex];
final byte[] chunk = new byte[info.size];
buf.get(chunk);
buf.clear();
if(chunk.length > 0){
audioTrack.write(chunk,0,chunk.length);
}
codec.releaseOutputBuffer(outputBufIndex, false);
if((info.flags && MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0){
sawOutputEOS = true;
}
} else if(res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){
codecOutputBuffers = codec.getOutputBuffers();
} else if(res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){
final MediaFormat offormat = codec.getOutputFormat();
mAudioTrack.setPlaybackRate(oformat.getInteger(MediaFormat.KEY_SAMPLE_RATE));
}
在 NDK 使用 media codec
MediaCodec
是一个 Java 层类,但是你必须用 C 代码进行解码(或编码),这是在 NDK 层。因此,在 NDK 中调用 Java 类是很重要的。这可以通过使用jni
功能FindClass
来完成。
这里显示了FindClass
的示例代码:
jclass audio_record_class = jni_env->FindClass("android/media/AudioRecord");
int size = jni_env->CallStaticIntMethod(audio_record_class
,jni_env->GetStaticMethodID(audio_record_class,"getMinBufferSize", "(III)I")
,prefered_rate
,2/*CHANNEL_CONFIGURATION_MONO*/
,2/* ENCODING_PCM_16BIT */);
用 surface 配置MediaCodec
将提供最佳性能,因为它可以使用硬件渲染。您可以引用本地媒体(例如,OpenMAX AL)来添加表面(或 GLSurfaceView)。如果你使用的是普通的 surface,你可以使用 textview 作为字幕,imageview 作为播放栏。如果您使用的是 GLSurfaceView,可以扩展这个类并实现自己的渲染器。
概述
目前只有两种硬件加速技术可以用在 Android 应用层:OpenGL 和 OpenMAX。OpenGL 包括 NDK 中的 GLSurfaceView 和 OpenGL-ES 1.0 和 2.0,它通常用作渲染器或用于多媒体效果处理。OpenMAX 包括 NDK 的 OpenMAX AL、MediaCodec 和 OMX-IL(不是 Google 代码,但必须由作者实现)。每种技术都有使用场景和适用的 Android 版本。到目前为止,流行的 Android 版本是 2.3 和 4.0,所以本章只介绍了从 Android 2.3 到 Android 4.1 的版本。表 12-4 表示哪些硬件加速器可以与哪个版本的 Android 配合使用。
表 12-4 。硬件加速器与安卓版本的兼容性
OpenGL 比较复杂但是使用场景相对固定(视频效果和图像处理),所以表格只列出了 OpenMAX 的用法。
Android 4.1 MediaCodec
是多媒体应用的重要更新。它让应用能够在编码前或解码后处理图像。即使在 Android 4.1 之前,在英特尔架构上使用 Android 也可以获得这种能力,在 4.1 之后,硬件加速仍然可以提供越来越好的效果。
1 红绿蓝(RGB)码是基于光的三原色的加色模型。虹膜的颜色表示为 69、102、137。
2 虹膜的 CMYK 值为 50、26、0、46。**
十三、附录 A:参考
第一章:Android 操作系统的历史和发展
起源
http://www.webcitation.org/5wk7sIvVb
http://en.wikipedia.org/wiki/Andy_Rubin
2007 年首次发布 Android 版本
http://www.openhandsetalliance.com/press_110507.html
http://www.cbsnews.com/2100-500395_162-6209772.html
http://oxrep.oxfordjournals.org/content/17/2/248.short
http://www.google.com/intl/en/policies/terms/
安卓是什么?
http://en.wikipedia.org/wiki/Android_(operating_system)#cite_note-AndroidInc-6
http://books.google.com/books?hl=en&lr=&id=h0lltXyJ8aIC&oi=fnd&pg=PT7&dq=linux&ots=gN3lF-b7OW&sig=ticDFAx0zLF3ocyAyAZUtk8oink#v=onepage&q=linux&f=false
http://developer.android.com/guide/basics/what-is-android.html
http://kebomix.wordpress.com/2010/08/17/android-system-architecture/
http://os.ibds.kit.edu/downloads/sa_2010_braehler-stefan_android-architecture.pdf
http://www.scandevconf.se/db/Marakana-Android-Internals.pdf
http://en.wikipedia.org/wiki/WebKit
http://developer.android.com/about/versions/index.html
开放手机联盟(OHA)
http://en.wikipedia.org/wiki/Open_Handset_Alliance
http://www.openhandsetalliance.com/oha_faq.html
http://www.openhandsetalliance.com/oha_overview.html
http://www.openhandsetalliance.com/oha_members.html
http://www.acronyms.net/terms/o/Open-Handset-Alliance/index.pdf
Android 开源项目(AOSP)
http://www.springerlink.com/content/m0136q318k2p4361/
http://source.android.com/about/index.html
http://source.android.com/about/philosophy.html
http://developer.android.com/about/dashboards/index.html
Android 版本
http://en.wikipedia.org/wiki/Astro_(operating_system)#Android_1.0
http://news.cnet.com/8301-30686_3-10385806-266.html
http://reviews.cnet.com/8301-19736_7-20016542-251/a-brief-history-of-android-phones/
http://developer.android.com/about/dashboards/index.html
第二章:移动设备和操作系统的前景
移动领域的竞争
http://www.mobiledevicemanager.com/mobile-device-statistics/250-million-android-devices-in-use/
http://www.engadget.com/2012/07/02/comscore-may-2012-smartphone/
ios
http://en.wikipedia.org/wiki/IOS
http://downloadsquad.switched.com/2010/08/12/camera-out-of-app-store-after-revealing-volume-button-snap-tric
http://www.iphonehacks.com/2010/12/quick-snap-camera-plus-app-pulled-from-app-store-for-using-volume-buttons-to-activate-camera-shutter.html
http://en.wikipedia.org/wiki/IPod_Touch
http://en.wikipedia.org/wiki/IPhone
http://en.wikipedia.org/wiki/IPad
米戈
http://en.wikipedia.org/wiki/MeeGo
http://en.wikipedia.org/wiki/Tizen
黑莓
http://en.wikipedia.org/wiki/BlackBerry
windows 手机
http://en.wikipedia.org/wiki/Windows_Phone
http://en.wikipedia.org/wiki/Windows_Mobile
智能移动终端操作系统
http://en.wikipedia.org/wiki/Symbian
安卓之前
http://www.hongkiat.com/blog/evolution-of-mobile-phones/
http://en.wikipedia.org/wiki/Smartphone
http://en.wikipedia.org/wiki/IBM_Simon
http://en.wikipedia.org/wiki/Nokia_9000
http://en.wikipedia.org/wiki/Kyocera_6035
http://www.bitrebels.com/technology/the-evolution-of-smartphones-infographic/
http://researchinmotion.wikia.com/wiki/BlackBerry_5810
移动市场
http://www.eweek.com/c/a/Mobile-and-Wireless/10-Smartphones-That-Failed-to-Inspire-Buyers-In-2010-696525/
http://blog.pricesbolo.com/index.php/tag/10-smartphones-that-failed-in-2010/
http://timesofindia.indiatimes.com/tech/itslideshow/7010501.cms
http://timesofindia.indiatimes.com/tech/itslideshow/7010503.cms
http://timesofindia.indiatimes.com/tech/itslideshow/6519089.cms
http://en.wikipedia.org/wiki/List_of_best-selling_mobile_phones#2010
摩托罗拉 i1
http://www.intomobile.com/2010/08/02/review-boost-mobile-motorola-i1-does-prepaid-android-work-well/
http://en.wikipedia.org/wiki/Motorola_i1
http://reviews.cnet.com/smartphones/motorola-i1-boost-mobile/4505-6452_7-34117412-2.html
http://www.slashgear.com/motorola-i1-set-to-crash-onto-sprint-on-july-25th-1994629/
http://www.engadget.com/2010/01/20/motorola-launching-20-30-android-phones-in-2010/
机器人 X
http://sparxoo.com/2010/06/21/motorola-droid-x-heats-up-competition-with-apple/
http://en.wikipedia.org/wiki/Droid_X
http://www.pcworld.com/article/201259/Droid_X_Sells_Out_Despite_Verizon_Preparation.html
http://www.androidcentral.com/verizon-stores-selling-out-droid-xs
黑莓火炬
http://dvice.com/archives/2011/01/why-the-blackbe.php
http://gizmodo.com/5614843/the-blackberry-torchs-biggest-failure-rims-ridiculous-expectations
http://www.ixibo.com/balckberry-torch-a-failure-against-iphone-and-android/
http://www.zdnet.com/blog/btl/blackberry-torch-best-blackberry-ever-fails-to-generate-buzz/37573
http://business.financialpost.com/2010/08/17/rim-slides-on-sluggish-blackberry-torch-sales/
http://www.thestar.com/business/companies/rim/article/862297--torch-ignites-research-in-motion
苹果手机
http://www.slideshare.net/bkiprin/apples-iphone-launch-marketing-strategy-analysis-2858373
http://www.scribd.com/doc/21275028/Apple-iPhone-Marketing-Plan
http://ezinearticles.com/?iPhone-Marketing-Strategy&id=4718557
http://en.wikipedia.org/wiki/IPhone
http://www.techiewww.com/marketing/apple-iphone-marketing-strategy
移动市场:趋势
http://socialmediatoday.com/gonzogonzo/495583/great-trends-mobile-infographic
http://smallbiztrends.com/2012/05/mobile-trend-key-things.html
http://mashable.com/2012/04/30/mobile-trends-brands-marketing/
http://mashable.com/2012/08/22/mobile-trends-ecommerce/
http://www.clickz.com/clickz/column/2168103/mobile-trends-watch
http://memeburn.com/2011/12/five-mobile-trends-to-look-out-for-in-2012/
位置
http://blogs.jpost.com/content/2012-mobile-trends-commerce-product-ecosystems-location-integration
https://discussions.apple.com/thread/3374979?start=0&tstart=0
http://www.itbusinessedge.com/slideshows/show.aspx?c=87261&slide=2
平均使用
http://thenextweb.com/us/2010/05/04/twitter-facebook-soar-myspace-sags-market-share/
贸易
http://mashable.com/2012/08/22/mobile-trends-ecommerce/
http://www.itbusinessedge.com/slideshows/show.aspx?c=87261&slide=5
http://www.oracle.com/us/products/applications/web-commerce/ecommerce-trends-2012-1504949.pdf
http://www.fortune3.com/blog/2012/01/us-m-commerce-sales-2010-2015-statistics/
第三章:超越移动应用——技术基础
连接的设备
http://www.intel.com/p/en_US/embedded/innovation/connectivity/johnson-article-connectivity
http://www.intel.com/content/www/us/en/home-users/get-more-from-your-devices-with-connecting-apps-from-intel.html
http://newsroom.intel.com/community/intel_newsroom/blog/2012/03/06/new-intel-server-technology-powering-the-cloud-to-handle-15-billion-connected-devices
http://venturebeat.com/2011/10/17/intel-execs-predicts-15b-devices-will-be-connected-to-the-internet/
家庭计算
http://www.apartmenttherapy.com/how-many-americans-have-multip-143096
汽车的
http://www.wired.com/autopia/2012/06/gps-devices-are-dead/
http://lifehacker.com/5626711/the-best-android-apps-for-your-car
特殊要求
加固
http://en.wikipedia.org/wiki/Rugged_computer
http://en.wikipedia.org/wiki/IP_Code
医学的
http://medicalconnectivity.com/2011/04/03/emr-integration-for-medical-devices-the-basics/
http://en.wikipedia.org/wiki/Electronic_medical_record
安全通信
http://en.wikipedia.org/wiki/Type_1_product
http://en.wikipedia.org/wiki/Federal_Information_Processing_Standard
我们互联世界的网络纤维
蜂窝网络
http://en.wikipedia.org/wiki/Cellular_network
http://en.wikipedia.org/wiki/GSM
http://en.wikipedia.org/wiki/Code_division_multiple_access
http://en.wikipedia.org/wiki/Multimedia_Messaging_Service
http://en.wikipedia.org/wiki/Short_Message_Service
开放移动联盟
http://en.wikipedia.org/wiki/Open_Mobile_Alliance
无线电
http://en.wikipedia.org/wiki/Wi-Fi
http://en.wikipedia.org/wiki/Bluetooth#Pairing_and_bonding
移动界面
触摸屏
http://www.knowyourmobile.com/features/392511/touchscreen_lowdown_capacitive_vs_resistive.html
http://www.goodgearguide.com.au/article/355922/capacitive_vs_resistive_touchscreens/
http://www.knowyourcell.com/features/687370/touchscreen_lowdown_capacitive_vs_resistive.html
http://en.wikipedia.org/wiki/Touchscreen
电阻的
http://en.wikipedia.org/wiki/Resistive_touchscreen
振动传感器(触觉)
http://en.wikipedia.org/wiki/Haptic_technology
http://en.wikipedia.org/wiki/Vibrating_alert
加速计
http://en.wikipedia.org/wiki/Accelerometer
倾斜传感器
http://en.wikipedia.org/wiki/Tilt_sensor
硬件按钮
第四章:Android 开发——业务概述和考虑
市场占有率
http://techcrunch.com/2012/11/02/idc-android-market-share-reached-75-worldwide-in-q3-2012/
http://www.businessinsider.com/android-market-share-2012-11
http://www.huffingtonpost.com/2012/09/18/android-market-share-q3-2012_n_1893292.html
http://www.huffingtonpost.com/2012/11/02/android-market-share_n_2066986.html
http://www.t-gaap.com/2012/5/24/can-google-make-money-with-android?site_locale=en
http://www.wired.com/business/2012/10/profit-or-no-profit/?pid=707
http://venturebeat.com/2012/11/01/as-android-grabs-75-market-share-can-anyone-tell-me-why-this-is-not-mac-vs-pc-all-over-again/
http://film.wapka.mobi/site_342.xhtml
http://en.wikipedia.org/wiki/International_Data_Corporation
http://androidandme.com/2012/04/opinions/the-future-of-android-in-2012/
http://www.tapscape.com/smartphone-war-android-market-share-hits-75-share/
http://openceo.blogspot.com/2012/11/android-75-market-share-future-is-open.html
http://www.forbes.com/sites/darcytravlos/2012/08/22/five-reasons-why-google-android-versus-apple-ios-market-share-numbers-dont-matter/
http://techpinions.com/android-v-ios-part-6-the-future/9687
http://www.wired.com/business/2012/10/google-ad-prices/
http://www.splatf.com/2011/10/google-revenue/
http://hellboundbloggers.com/2012/04/28/why-android-is-popular/
http://www.nascentstuff.com/why-android-os-is-getting-so-popular/
http://forum.xda-developers.com/showthread.php?t=865371
http://artinandroid.blogspot.com/2011/11/why-android-is-so-successful.html
http://techcrunch.com/2012/05/15/3997-models-android-fragmentation-as-seen-by-the-developers-of-opensignalmaps/
https://play.google.com/store/apps
http://authors.library.caltech.edu/11284/1/MCAaer04.pdf
http://nick.typepad.com/blog/2012/01/androids-legacy-nonsense.html
http://www.phonearena.com/news/The-Update-Battle-Innovation-vs-legacy-support_id23282
安全
http://www.cs.rice.edu/∼sc40/pubs/enck-sec11.pdf
批准
http://pandodaily.com/2012/01/28/how-google-can-save-android-close-it-license-it-swim-in-the-profits/
http://www.unwiredview.com/2011/07/13/the-real-cost-of-android-potentially-60-per-device-in-patent-fees/
http://www.quora.com/Mobile-Software-Development/What-is-the-licensing-royalty-cost-of-Android-OS
http://www.techrepublic.com/blog/app-builder/app-store-fees-percentages-and-payouts-what-developers-need-to-know/1205
http://developer.android.com/distribute/googleplay/publish/register.html
第五章:英特尔移动式处理器
移动巨头的冲突:ARM 对 Intel
http://beta.fool.com/iamgreatness/2012/07/25/mobile-vs-desktop-intel-ready-crush-arm/7757/
http://seekingalpha.com/article/874181-arm-s-david-vs-intel-s-goliath-outcome-uncertain
http://techland.time.com/2012/07/16/arm-vs-intel-how-the-processor-wars-will-benefit-consumers-most/
http://en.wikipedia.org/wiki/Acorn_Computers
http://www.ot1.com/arm/armchap1.html
http://www.techulator.com/resources/7489-The-All-time-Processor-War-ARM-Intel.aspx
http://en.wikipedia.org/wiki/ARM_architecture
http://media.corporate-ir.net/media_files/irol/19/197211/reports/ar06.pdf
" ARM 控股有限公司报告了截至 2001 年 6 月 30 日的第二季度和半年的业绩."http://www.arm.com/about/newsroom/arm-holdings-plc-reports-results-for-the-second-quarter-and-half-year-ended-30-june-2013.php
http://it.bentley.edu/dommara_prud/arm/index.html
http://www.zdnet.com/amd-arms-power-advantages-could-wane-in-the-coming-years-7000006597/
美国英特尔公司(财富 500 强公司之一ˌ以生产 CPU 芯片著称)
http://www.intel.com/content/www/us/en/history/historic-timeline.html
http://en.wikipedia.org/wiki/Semiconductor_sales_leaders_by_year#Ranking_for_year_2011
http://www.intel.com/support/motherboards/desktop/sb/CS-033869.htm
http://en.wikipedia.org/wiki/Handheld_game_console
http://www.extremetech.com/wp-content/uploads/2011/11/Chart_USportableGameRevenue_MarketShare_2009-2011-resized-600.png
http://appleinsider.com/articles/10/03/22/iphone_ipod_touch_carve_19_gaming_share_from_sony_nintendo
Android Atom 平台
http://www.pcworld.com/article/259737/intel_porting_android_41_to_work_on_atom_tablets_smartphones.html
http://www.phonearena.com/news/Android-Intel-Atom-powered-Phones-Hands-on-Reviews-Lenovo-K800-Orange-Santa-Clara-Lava-Xolo-X900_id27528
http://www.tomshardware.com/news/intel-tablet-medfield-soc-cpu,14389.html
第六章:为英特尔应用开发安装 Android SDK
安装和设置
Java 开发工具包
http://www.oracle.com/technetwork/java/javase/downloads/index.html
黯然失色
http://www.eclipse.org/downloads/
阿帕奇人 Ant
http://ant.apache.org/
软件开发工具包
http://developer.android.com/sdk/index.html
竞争
http://developer.android.com/guide/developing/devices/index.html
http://developer.android.com/tools/devices/emulator.html
冰淇淋三明治模拟
http://software.intel.com/en-us/articles/android-43-jelly-bean-x86-emulator-system-image
http://www.computerworld.com/s/article/9230152/Android_4.0_The_ultimate_guide_plus_cheat_sheet_
http://source.android.com/source/initializing.html
姜饼仿真
http://blogs.computerworld.com/17479/android_gingerbread_faq
千伏计(kilovoltmeter 的缩写)
https://help.ubuntu.com/community/KVM/Installation
https://help.ubuntu.com/community/KVM
http://android-er.blogspot.com/2010/09/how-to-set-battery-status-of-android.html
英特尔工具
http://www.intel.com/software/android
http://int-software.intel.com/en-us/android
http://software.intel.com/en-us/articles/android-virtual-device-emulation-for-intel-architecture
第七章:创建和移植基于 NDK 的 Android 应用
http://www.cygwin.com/
http://developer.android.com/sdk/ndk/index.html
http://www.eclipse.org/cdt/downloads.php
http://download.eclipse.org/tools/cdt/releases/galileo/
第八章:调试 Android
http://www.intel.com/software/android
http://developer.android.com/sdk/installing.html
http://developer.android.com/guide/developing/device.html
http://developer.android.com/tools/extras/oem-usb.html
http://developer.android.com/tools/device.html#VendorIds
。
http://developer.android.com/guide/developing/tools/adb.html
http://developer.android.com/sdk/eclipse-adt.html#installing
http://source.android.com/source/downloading.html
http://www.windriver.com/products/JTAG-debugging/
http://www.lauterbach.com
http://software.intel.com/en-us/articles/embedded-using-intel-tools
http://developer.android.com/guide/developing/device.html
http://www.eclipse.org/downloads/
http://developer.android.com/sdk/index.html
约翰逊,兰迪和斯图尔特·克里斯蒂。" JTAG 101: IEEE 1149.x 和软件调试."http://www.intel.com/content/www/us/en/intelligent-systems/jtag-101-ieee-1149x-paper.html
第九章:x86 平台上 Android 应用的性能优化
http://intel.com/software/gpa
http://software.intel.com/en-us/vcsource/tools/intel-gpa
第十章:x86 NDK 和 C/C++ 优化
http://software.intel.com/en-us/articles/intel-integrated-performance-primitives-intel-ipp-intel-ipp-sample-code
http://www.princeton.edu/∼achaney/tmve/wiki100k/docs/Locality_of_reference.html
第十一章:在 Windows、Mac OS 和 Linux 上使用英特尔硬件加速执行管理器加速 x86 仿真上的 Android
“英特尔硬件加速执行管理器- Microsoft Windows 的安装说明。”http://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-windows
“英特尔硬件加速执行管理器- Mac OS X 的安装说明”。http://software.intel.com/en-us/articles/installation-instructions-for-intel-hardware-accelerated-execution-manager-macosx
“如何在 Linux 上启动英特尔硬件辅助虚拟化(hypervisor)来加速英特尔 Android x86 姜饼仿真器。”http://software.intel.com/en-us/blogs/2012/03/12/how-to-start-intel-hardware-assisted-virtualization-hypervisor-on-linux-to-speed-up-intel-android-x86-gingerbread-emulator
第十二章:通过平台调整进行性能测试和分析应用
http://elinux.oimg/e0/The_OpenMAX_Integration_Layer_standard.pdf
http://www.ffmpeg.org/
。
https://github.com/shaobin0604/faplayer
https://github.com/havlenapetr
http://yasm.tortall.net/
http://software.intel.com/en-us/articles/using-yasm-compiler-on-android-ndkbuild
http://software.intel.com/en-us/intel-vtune-amplifier-xe
http://developer.android.com/reference/android/opengl/GLSurfaceView.html
http://www.learnopengles.com/how-to-use-opengl-es-2-in-an-android-live-wallpaper