Android 安卓使用C/C++静态库/动态库指南

         目前我在做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>


这个步骤完成之后工程的目录结构如下图:

Android 安卓使用C/C++静态库/动态库指南_第1张图片


三、JAVA调用

      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



四、修改Andoid.mk文件

        我的是这样子的:

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 += -fexceptions
JNI目录的结构是这样子的::

Android 安卓使用C/C++静态库/动态库指南_第2张图片

五、编译


      命令行下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目录的情况)

六、生成apk与测试

        用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];   // 只是举例说明,最后这里会出现动态时错误


你可能感兴趣的:(android,hm,HEVC)