目前我在做HM(HEVC测试软件)移植到Android平台,在移植过程中出现了一些问题,顺便记录下来,供大家分享。也让大家在出现问题是能够有一个参照。这些天忙着调试程序中出现的BUG,没有来得及更新。
移植HM详细请看另外一篇日志:http://blog.csdn.net/luofl1992/article/details/8736149
开始之前请确认你有这样几个工具 :
Java JDK(Java Development Toolkit )
Android SDK (简称ADK )可以直接下载Eclipse+ADT二合一版本
Eclipse (需要集成 CDT,直接使用上面的ADK开发的无视这个)
NDK (Android NDK)
这些工具没有的安装,可以去官方网站下载。各个工具的安装教程网络上很多,自己找一找,这里就不再列出了。
1、使用IDE工具
用Eclipse建立一个工程,配置相关选项,建立工程完毕,我们可以进入工程目录(即 PROJECT_DIR )。
然后建立一个新的文件夹,叫做 jni 的目录。
自己在里面添加两个文件 Application.mk 和 Android.mk (我没有装NDK的时候说一直再找哪里有这两个文件)
然后,修改一下这两个文件的内容,具体说明请看:
Android-ndk/docs/Android-mk.html 等等一系列参考文档,说明了参数的意义之类的。
2、使用命令行
cd到任意目录下,首先执行这条命令查看一下Android对应的版本号(下面会用到)
android list targets然后执行这样的指令:
android create project \ --target <target-id> \ --name MyFirstApp \ --path <path-to-workspace>/MyFirstApp \ --activity MainActivity 、 --package com.example.myfirstapp这里 <target-id>是上面一条指令列出的版本号,数字,比如我执行上面一条指令的结果是9表示 Android 2.3.3,是我需要生成的目标平台版本号,
--name 后面为项目名称,比如我的是 --name NcHevcPlayer
--path 后面跟的是项目的路径,为文件夹的名字,比如我要在当前目录下建立 NcHevcPlayer 的文件夹作为工程目录,就是 --path NcHevcPlayer
--activity 后面跟的是主Activity的类名字,随意指定。
--package 后面跟着包名字,可以像这样 --package com.NcHevc
(先卖个关子,包的名字会对后面使用NDK、JNI造成影响)
那么整个命令就像这样子:
android create project \ --target 9 \ -name NcHevcPlayer \ --path NcHevcPlayer \ --activity NcHevcPlayerActivity \ --package com.NcHevc
更加详细的说明可以参考NDK的网站,或者在本机NDK目录下的docs里面阅读相关说明。
http://developer.android.com/tools/projects/projects-cmdline.html
如果没有错误,那么在当前目录下会出现一个NcHevcPlayer目录,由上面的--path指定。
或者可以对已有Android项目更新,其语法如下:
android update project \ --name <project_name> \ --target <target_ID> \ --path <path_to_your_project>
这个步骤完成之后工程的目录结构如下图:
JAVA调用C/C++的函数需要使用JNI,需要使用的指令有 javac 和 javah
详细的介绍请参考这篇文章:http://www.cnblogs.com/hibraincol/archive/2011/05/30/2063847.html
首先在某个类(比如Activity类)增加native函数,然后运行javac指令编译该类的java文件,
生成一个class文件,再用javah命令,生成对应的头文件。
建议cd到src目录,不要再进去了,执行
javac com/NcHevc/NcHevcDecoder.java
javah com.NcHevc.NcHevcDecoder
就可以得到一个头文件了,见下面JNI目录的图中NcHevcDecoder.h。
其内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_NcHevc_NcHevcDecoder */ #ifndef _Included_com_NcHevc_NcHevcDecoder #define _Included_com_NcHevc_NcHevcDecoder // 这三行是额外添加的 #include "TLibCommon/CommonDef.h" typedef unsigned char byte; // typedefine type as unsigned char // 以下为JAVAH生成的函数原型 #ifdef __cplusplus extern "C" { #endif /* * Class: com_NcHevc_NcHevcDecoder * Method: Open * Signature: (Ljava/lang/String;)Z */ JNIEXPORT jboolean JNICALL Java_com_NcHevc_NcHevcDecoder_Open (JNIEnv *, jobject, jstring); /* * Class: com_NcHevc_NcHevcDecoder * Method: GetPixelsBuffer * Signature: ([I)V */ JNIEXPORT void JNICALL Java_com_NcHevc_NcHevcDecoder_GetPixelsBuffer (JNIEnv *, jobject, jintArray); /* * Class: com_NcHevc_NcHevcDecoder * Method: DecodeFrame * Signature: ()Z */ JNIEXPORT jboolean JNICALL Java_com_NcHevc_NcHevcDecoder_DecodeFrame (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
我的是这样子的:
LOCAL_PATH := $(call my-dir) # 是否采用改进了的 HM 库 USING_IMPROVED_HM := true # 一些路径的设置,相对路径和绝对路径 ifeq ($(USING_IMPROVED_HM),true) # 优化的版本,加快运行效率 PATH_HM := libHM else # 标准版本(只修改错误使其能够正常在Android平台运行) PATH_HM := libHMStandard endif PATH_HM_ABSOLUTE := $(LOCAL_PATH)/$(PATH_HM) #include $(CLEAR_VARS) #LOCAL_MODULE := HmDecoder # name it whatever #LOCAL_SRC_FILES := libHM.a #include $(PREBUILT_STATIC_LIBRARY) # Here we give our module name and source file(s) include $(CLEAR_VARS) LOCAL_MODULE := NcHevcDecoder LOCAL_CPP_EXTENTION := .cpp LOCAL_CXXFLAGS := -D__cplusplus -g # LOCAL_CPPFLAGS += -fexceptions #设置系统默认包含路径,避免编译时出现找不到头文件的情况 LOCAL_C_INCLUDES := ~/android-ndk/sources/cxx-stl/stlport/stlport:$(LOCAL_PATH) LOCAL_C_INCLUDES += $(PATH_HM_ABSOLUTE) LOCAL_CPP_INCLUDES := ~/android-ndk/sources/cxx-stl/stlport/stlport:$(LOCAL_PATH) LOCAL_CPP_INCLUDES += $(PATH_HM_ABSOLUTE) # find . -name '*.c*' 编码器相关的库实际上没有使用到,可以不予添加 LOCAL_SRC_FILES := NcHevcDecoder.cpp TAppDecTop.cpp JNI_OnLoad.cpp \ $(PATH_HM)/libmd5/libmd5.c \ $(PATH_HM)/TLibEncoder/TEncEntropy.cpp \ $(PATH_HM)/TLibEncoder/TEncBinCoderCABAC.cpp \ $(PATH_HM)/TLibEncoder/TEncSbac.cpp \ $(PATH_HM)/TLibEncoder/SyntaxElementWriter.cpp \ $(PATH_HM)/TLibEncoder/TEncGOP.cpp \ $(PATH_HM)/TLibEncoder/TEncSearch.cpp \ $(PATH_HM)/TLibEncoder/TEncPic.cpp \ $(PATH_HM)/TLibEncoder/NALwrite.cpp \ $(PATH_HM)/TLibEncoder/TEncCavlc.cpp \ $(PATH_HM)/TLibEncoder/TEncPreanalyzer.cpp \ $(PATH_HM)/TLibEncoder/TEncBinCoderCABACCounter.cpp \ $(PATH_HM)/TLibEncoder/WeightPredAnalysis.cpp \ $(PATH_HM)/TLibEncoder/TEncAnalyze.cpp \ $(PATH_HM)/TLibEncoder/TEncRateCtrl.cpp \ $(PATH_HM)/TLibEncoder/SEIwrite.cpp \ $(PATH_HM)/TLibEncoder/TEncSlice.cpp \ $(PATH_HM)/TLibEncoder/TEncSampleAdaptiveOffset.cpp \ $(PATH_HM)/TLibEncoder/TEncCu.cpp \ $(PATH_HM)/TLibEncoder/TEncTop.cpp \ $(PATH_HM)/TLibDecoder/SyntaxElementParser.cpp \ $(PATH_HM)/TLibDecoder/TDecGop.cpp \ $(PATH_HM)/TLibDecoder/TDecTop.cpp \ $(PATH_HM)/TLibDecoder/TDecEntropy.cpp \ $(PATH_HM)/TLibDecoder/TDecCAVLC.cpp \ $(PATH_HM)/TLibDecoder/TDecCu.cpp \ $(PATH_HM)/TLibDecoder/NALread.cpp \ $(PATH_HM)/TLibDecoder/TDecSbac.cpp \ $(PATH_HM)/TLibDecoder/SEIread.cpp \ $(PATH_HM)/TLibDecoder/TDecSlice.cpp \ $(PATH_HM)/TLibDecoder/AnnexBread.cpp \ $(PATH_HM)/TLibDecoder/TDecBinCoderCABAC.cpp \ $(PATH_HM)/TAppCommon/program_options_lite.cpp \ $(PATH_HM)/TLibVideoIO/TVideoIOYuv.cpp \ $(PATH_HM)/TLibCommon/TComRdCostWeightPrediction.cpp \ $(PATH_HM)/TLibCommon/TComCABACTables.cpp \ $(PATH_HM)/TLibCommon/TComPic.cpp \ $(PATH_HM)/TLibCommon/TComSampleAdaptiveOffset.cpp \ $(PATH_HM)/TLibCommon/TComPattern.cpp \ $(PATH_HM)/TLibCommon/TComLoopFilter.cpp \ $(PATH_HM)/TLibCommon/TComPrediction.cpp \ $(PATH_HM)/TLibCommon/SEI.cpp \ $(PATH_HM)/TLibCommon/TComPicYuvMD5.cpp \ $(PATH_HM)/TLibCommon/TComRdCost.cpp \ $(PATH_HM)/TLibCommon/TComBitStream.cpp \ $(PATH_HM)/TLibCommon/TComTrQuant.cpp \ $(PATH_HM)/TLibCommon/TComSlice.cpp \ $(PATH_HM)/TLibCommon/TComPicYuv.cpp \ $(PATH_HM)/TLibCommon/TComDataCU.cpp \ $(PATH_HM)/TLibCommon/TComWeightPrediction.cpp \ $(PATH_HM)/TLibCommon/TComInterpolationFilter.cpp \ $(PATH_HM)/TLibCommon/ContextModel.cpp \ $(PATH_HM)/TLibCommon/TComRom.cpp \ $(PATH_HM)/TLibCommon/TComMotionInfo.cpp \ $(PATH_HM)/TLibCommon/ContextModel3DBuffer.cpp \ $(PATH_HM)/TLibCommon/TComYuv.cpp \ $(PATH_HM)/TLibCommon/TComPicSym.cpp LOCAL_STATIC_LIBRARIES := ~/android-ndk/sources/cxx-stl/stlport/libs/armeabi/libstlport_static.a LOCAL_LDLIBS := -llog # 导出动态库 include $(BUILD_SHARED_LIBRARY)
同时可以在JNI目录下建立一个Application.mk文件,内容如下:
APP_PROJECT_PATH := $(call my-dir)/.. APP_PLATFORM := android-10 APP_STL := stlport_static APP_ABI := armeabi-v7a APP_CPPFLAGS += -fexceptionsJNI目录的结构是这样子的::
命令行下cd到工程文件目录,即 PROJECT_DIR ,然后执行 ndk-build 指令即可。
不知到在什么情况下需要JNI_OnLoad,想知道怎么弄可以看这里:http://blog.csdn.net/luhuajcdd/article/details/7750146
完毕后在Eclipse下执行build,生成并且 Run 就会生成apk文件
命令行模式下需要安装ant工具,首先执行 sudo apt-get install ant
安装完毕后创建一个签名文件,详细请看http://blog.csdn.net/aeolus1019/article/details/8121031
为了使ant编译工程时能够自动打包签名文件,在工程目录的ant.properties文件的末尾增加这样几行:
key.alias=mNcDecoder.keystore key.store=mNcDecoder.keystore key.store.password=这里该成密码 key.alias.password=这里改成密码上述操作都完成后最后执行:
ant release
(如果编译出错,请查看local.properties中的 SDK 路径是否正确,这只针对自己出于某些需要移动了SDK目录的情况)
用Eclipse选择 Run as Android app,然后会自动启动模拟器或者连接Android设备,
大家可以用自己的手机进行测试,模拟器太慢了啊。
怎么创建模拟器这里不讨论了。
命令行模式下,需要用adb指令。可以这么弄:
abd install -r bin/NcHevcPlayer-release.apk
其中-r参数表示重新安装,可以在此之前先卸载 adb uninstall com.NcHevc
最后为了使整个过程简单完成,可以写这样的一个shell脚本,保存成run.sh文件放到工程目录下,并且附加执行权限:
echo -e "\033[41;32;1m 清除 bin 文件目录下的内容 \033[0m" rm -r bin echo -e "\033[41;32;1m 建立共享库 NcHevcDecoder \033[0m" ndk-build echo -e "\033[41;32;1m 编译生成 APK \033[0m" ant release echo -e "\033[41;32;1m 清除原先安装版本 \033[0m" adb devices adb uninstall com.NcHevc echo -e "\033[41;32;1m 安装最新版到手机或模拟设备 \033[0m" adb install -r bin/NcHevcPlayer-release.apk echo -e "\033[41;32;1m 正在清除系统日志 \033[0m" adb logcat -c echo -e "\033[41;32;1m 操作完成,请进行手机测试 \033[0m" echo -e "\033[41;32;1m 以下为调试信息,结束查看日志按Ctrl+C即可 \033[0m" # adb logcat -v time *:I > logcat.txt adb logcat -v time NcHevcPlayerActivity:I *:S
以后修改了源代码只要运行这个脚本,就可以了,
停止该脚本的方法是按住 左Ctrl + C 两个键,就会自动终止。
最后的几条语句是使用logcat输出调试信息,具体可以查看:http://blog.csdn.net/czbever/article/details/5910640
或者:http://blog.chinaunix.net/uid-23173926-id-109068.html
JAVA的对象其实是C++的指针,所以使用之前必须要赋值。否则会出现 NullPointerException,自己还不知道怎么回事。
特别注意:在ARM平台(Android开发)下,char类型默认为 unsigned char,而GCC和MSVC默认为 signed char.
使用不慎会导致严重BUG。
例如这样的程序:
char k = -1; int i = k + 1; // 值应该为0吧。(但是最终结果是256,瞎了把) int a[20]; int j; j = a[i]; // 只是举例说明,最后这里会出现动态时错误