android 移植 ffmpeg (二) 测试用例

在android 移植 ffmpeg (一)中已经对环境进行了设置。 这一章将重点讨论怎么在应用中加入ffmpeg组件。

所有测试都将在 Android Studio工具中进行。

测试例子源地址: https://github.com/roman10/android-ffmpeg-tutorial 

本例子是在android-ffmpeg-tutorial01 基础上进行了简单调整。调整后的源码页:http://download.csdn.net/detail/net_wolf_007/9620856


一. 工程目录结构

android 移植 ffmpeg (二) 测试用例_第1张图片

目录中主要有6个地方进行变动,所以逐一说明一下。

1 assets路径

测试视频存放地, 目前测试视频为1.mp4. 通过命令 ffmpeg -i 1.mp4 得到视频信息。
       视频信息为:(时长10.04秒, 比特率: 352Kb/s, 分辨率: 640*360)
android 移植 ffmpeg (二) 测试用例_第2张图片

2 Java 文件目录

  MainActivity.java 主Java程序, 后面会对程序进行说明

Utils.java 工具类。提供了文件拷贝功能,程序中主要用于把1.mp4导入到指定目录

3 NDK工作目录 jni文件夹

       tutorial01.c: 
          测试ffmpeg源文件,主要功能,调用ffmpeg sdk分析视频,并把指定的图片上传给Java代码。后面会进一步说明。
      Applicaton.mk文件: 
           定义了应用程序编译的基本信息, 非必要。主要告诉ndk要编译的指令集,及对应的Android平台。
            Android NDK编译系统支持3种API: armeabi, armeabi-v7a和x86,分别对应 ARMv5TE, ARMv7-A和X86指令集的CPU.默认支持armeabi.
       Android.mk
           NDK 编译命令文件,用来告诉NDK如何编译此项目的。
           常用的命令在上一篇( android 移植 ffmpeg (一))文章中已经有了说明,这里仅补充没有说明的。
           LOCAL_LDLIBS := -llog -ljnigraphics -lz
                        linker flags。 可以用它来添加系统库
          LOCAL_SHARED_LIBRARIES := libavformat libavcodec libswscale libswresample libavutil
                       要链接到本模块的动态库。
          $(call import-module,ffmpeg/android)
                 import-module:   允许寻找并inport其它modules到本Android.mk中来。 它会从NDK_MODULE_PATH寻找指定的模块名。 
                   使用方法:  $(call import-module,)
                这里就是把ffmpeg/android的模块导入进来,即把ffmpeg/android/下的Android.mk导入进本模块来。 
                这样上面 LOCAL_SHARED_LIBRARIES指字的库名才能找到。

4 NDK生成目录 libs目录

      一般NDK生成目录位于 ./libs/armeabi/ 目录下
    里面有目录 armeabi, 即生成支持ARMv5TE指令集的CPU的库。这些库可直接在ARMv5TE指令集的CPU上去行。
    如果要设置成支持多个指令集的话,可在 Application.mk中进行设置,则可生成多个文件夹,每个文件夹中的文件名一样。

5 build.gradle文件配置

     需要配置相应的NDK配置
      android 移植 ffmpeg (二) 测试用例_第3张图片

6 local.properties 文件匹配

    需要配置SDK,NDK的路径名。我的机器的配置是:
         android 移植 ffmpeg (二) 测试用例_第4张图片
     NDK的配置还可以通过如下方法进行设置: File->Project Structure… 快捷建(Command + ;)进行设置。 
     android 移植 ffmpeg (二) 测试用例_第5张图片


二. 生成代码文件

生成库文件:

需要通过命令行来生成 

       命令行运行到main目录下,调用ndk_build.

172-15-70-196:main jerome$ pwd
/Users/jerome/dev/ffmpeg/android-ffmpeg-tutorial01/app/src/main
172-15-70-196:main jerome$ /Users/jerome/dev/android-ndk-r12b/ndk-build 
[armeabi] Compile thumb  : tutorial01 <= tutorial01.c
jni/tutorial01.c: In function 'naMain':
jni/tutorial01.c:117:2: warning: 'codec' is deprecated (declared at /Users/jerome/dev/android-ndk-r12b/sources/ffmpeg/android/include/libavformat/avformat.h:881) [-Wdeprecated-declarations]
  pCodecCtx=pFormatCtx->streams[videoStream]->codec;
  ^
[armeabi] SharedLibrary  : libtutorial01.so
[armeabi] Install        : libtutorial01.so => libs/armeabi/libtutorial01.so
[armeabi] Install        : libavformat-57.so => libs/armeabi/libavformat-57.so
[armeabi] Install        : libavcodec-57.so => libs/armeabi/libavcodec-57.so
[armeabi] Install        : libswscale-4.so => libs/armeabi/libswscale-4.so
[armeabi] Install        : libswresample-2.so => libs/armeabi/libswresample-2.so
[armeabi] Install        : libavutil-55.so => libs/armeabi/libavutil-55.so
172-15-70-196:main jerome$ 

编译运行apk

直接运行调试。这过程中会碰到如下问题, 需要修改ffmpeg源码,再重新编译ffmpeg:
cannot locate symbol "log2f" referenced by "libavcodec-57.so"..
cannot locate symbol "log2" referenced by "libavcodec-57.so"..
原因: 这个跟ndk与android版本有关。
解决办法: 
修改 ./libavutil/libm.h里面的定义,不再判断是否已经存在函数。使用重新定义
//#if !HAVE_LOG2
//#undef log2
#define log2(x) (log(x) * 1.44269504088896340736)
//#endif /* HAVE_LOG2 */

//#if !HAVE_LOG2F
//#undef log2f
#define log2f(x) ((float)log2(x))
//#endif /* HAVE_LOG2F */

cannot locate symbol "atof" referenced by "libavformat-57.so"...
原因:android的stdlib.h中atof是内联的, 外部模块不能直接使用。跟android版本有关。
解决办法:将所有的atof改成strtod


修改完之后:
1 重新生成ffmpeg(调用 ./build_andriod_mac.sh), 
2 调用ndk_build生成C库文件。
3. 运行android Java代码。

4. 点击界面的start按钮,生成图片。

至此,程序运行OK.

三. NDK开发运行流程

ndk-build 编译流程:

1. 查找环境变量 NDK_PROJECT_PATH,如果用户没有设置,就根据如下流程查找!
    /Users/jerome/dev/android-ndk-r12b/build/core/build-local.mk中的代码部分
ifndef NDK_PROJECT_PATH
    ifneq (,$(strip $(wildcard AndroidManifest.xml)))
        NDK_PROJECT_PATH := .
    else
        ifneq (,$(strip $(wildcard jni/Android.mk)))
            NDK_PROJECT_PATH := .
        endif
    endif
endif
ifndef NDK_PROJECT_PATH
    NDK_PROJECT_PATH := $(call find-project-dir,.,jni/Android.mk)
endif
ifndef NDK_PROJECT_PATH
    NDK_PROJECT_PATH := $(call find-project-dir,.,AndroidManifest.xml)
endif
说明:就是在当前目录及父目录中查找文件:AndroidManifest.xml 或 jni/Android.mk 文件,如果找到,就把找到的目录赋给NDK_PROJECT_PATH, 如果没找到,就直接报错。Android NDK: Could not find application project directory ! 


2. 查找环境变量APP_BUILD_SCRIPT: 如果没设置,就运行如下代码。
   在/Users/jerome/dev/android-ndk-r12b/build/core/add-application.mk 中
 _build_script := $(strip $(wildcard $(APP_PROJECT_PATH)/jni/Android.mk))
    ifndef _build_script
        $(call __ndk_info,There is no Android.mk under $(APP_PROJECT_PATH)/jni)
        $(call __ndk_info,If this is intentional, please define APP_BUILD_SCRIPT to point)
        $(call __ndk_info,to a valid NDK build script.)
        $(call __ndk_error,Aborting...)
    endif
    APP_BUILD_SCRIPT := $(_build_script)
说明: 如果没找到 APP_BUILD_SCRIPT的定义,就查找APP_PROJECT_PATH下的 jni/Android.mk, 如果找到就赋值给APP_BUILD_SCRIPT。如果没有找到,就报错。Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk 

这里查找的是 APP _PROJECT_PATH, 与1中的NDK_PROJECT_PATH的关系如下(core/add-application.mk 中)。
APP_PROJECT_PATH := $(strip $(APP_PROJECT_PATH))
ifndef APP_PROJECT_PATH
    APP_PROJECT_PATH := $(NDK_PROJECT_PATH)
endif
如果定义了环境变量 APP _PROJECT_PATH, 就使用定义的值,如果没有就使用 NDK_PROJECT_PATH的值。

3. 加载其它模块Android.mk文件
APP_BUILD_SCRIPT中查找是否有如下语句,有,则加载对应的库文件
$(call import-module,ffmpeg/android)
查找逻辑是通过查找环境变量:NDK_MODULE_PATH下的对应模块的Android.mk文件, 上例中就是查找 ffmpeg/android/Android.mk文件。如果找不到就报错。Android NDK: jni/Android.mk: Cannot find module with tag 'ffmpeg/android' in import path    
NDK_MODULE_PATH 是可以有多个值。看脚本(core/setup-imports.mk)
NDK_MODULE_PATH := $(strip $(NDK_MODULE_PATH))
ifdef NDK_MODULE_PATH
  ifneq ($(words $(NDK_MODULE_PATH)),1)
    $(call __ndk_info,ERROR: You NDK_MODULE_PATH variable contains spaces)
    $(call __ndk_info,Please fix the error and start again.)
    $(call __ndk_error,Aborting)
  endif
endif

$(call import-init)
$(foreach __path,$(subst $(HOST_DIRSEP),$(space),$(NDK_MODULE_PATH)),\
  $(call import-add-path,$(__path))\
)
$(call import-add-path-optional,$(NDK_ROOT)/sources)
$(call import-add-path-optional,$(NDK_ROOT)/../development/ndk/sources)

由上脚本可知, 如果设置了NDK_MODULE_PATH,则就在NDK_MODULE_PATH中查找,如果没有设置,那就是$(NDK_ROOT)/sources目录中查找(还记得嘛,这也是我们编译ffmpeg存放的目录!),还就有是在$(NDK_ROOT)/../development/ndk/sources目录中查找!
$(NDK_ROOT)目录就是ndk-build运行的目录, 如果用户不确定, 可直接设置环境变量:
1. vim ~/.bash_profile
2. 添加 export NDK_ROOT=/Users/jerome/dev/android-ndk-r12b
3. source .bash_profile


4. 编译合成后的Android.mk文件,生成指定的结果文件。


这样 ndk-build的运行逻辑就清晰了。通过设置主要环境变量, 就可以实现模块化编程。
NDK_ROOT: 代码根目录, 默认为ndk-build所在的目录。
NDK_PROJECT_PATH: 默认为ndk-build运行的目录。
APP_PROJECT_PATH: 保存脚本的路径
APP_BUILD_SCRIPT: 脚本路径
NDK_MODULE_PATH: 模块路径, 用户定义的路径
还有两个系统模块路径: $(NDK_ROOT)/sources, $(NDK_ROOT)/../development/ndk/sources。

Gradle打包流程就不分析了。


四. 后记


本篇主要让程序运行起来,并分析ndk-build的逻辑, 下一篇,将重点说明程序结构,及代码流程。





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