Mac OS下为Android Studio编译FFmpeg解码库的详细教程

时间:2022-06-15 07:53:19

ndk部分
1、下载ndk

这里就一笔带过了。

2、解压ndk
不要解压,文件权限会出错。执行之,会自动解压,然后mv到想放的地方。我放到了”/usr/local/bin/android-ndk-r10d”(此目录之后用$ndk_dir指代)。

3、下载ffmpeg
我下的是2.5.3版本。

4、解压ffmpeg
解压ffmpeg到$ndk_dir/sources/ffmpeg-2.5.3。

5、修改ffmpeg编译配置
在ffmpeg-2.5.3目录下把configure文件中的这几行,目的是去掉默认生成的库名字libavcodec.so.55最后那个”55″的版本号。

?
1
2
3
4
slibname_with_major='$(slibname).$(libmajor)'
lib_install_extra_cmd='$$(ranlib) "$(libdir)/$(libname)"'
slib_install_name='$(slibname_with_version)'
slib_install_links='$(slibname_with_major) $(slibname)'
?
1
2
3
4
slibname_with_major='$(slibpref)$(fullname)-$(libmajor)$(slibsuf)'
lib_install_extra_cmd='$$(ranlib) "$(libdir)/$(libname)"'
slib_install_name='$(slibname_with_major)'
slib_install_links='$(slibname)'


6、编译ffmpeg
在ffmpeg-2.5.3目录下创建文件build_android.sh。
注意前三行要按照自己的路径正确配置。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash
ndk=/usr/local/android-ndk-r10d
sysroot=$ndk/platforms/android-15/arch-arm/
toolchain=$ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64
function build_one
{
./configure \
 --prefix=$prefix \
 --enable-shared \
 --disable-static \
 --disable-doc \
 --disable-ffmpeg \
 --disable-ffplay \
 --disable-ffprobe \
 --disable-ffserver \
 --disable-avdevice \
 --disable-doc \
 --disable-symver \
 --cross-prefix=$toolchain/bin/arm-linux-androideabi- \
 --target-os=linux \
 --arch=arm \
 --enable-cross-compile \
 --sysroot=$sysroot \
 --extra-cflags="-os -fpic $addi_cflags" \
 --extra-ldflags="$addi_ldflags" \
 $additional_configure_flag
make clean
make
make install
}
cpu=arm
prefix=$(pwd)/android/$cpu
addi_cflags="-marm"
build_one

保存后执行

?
1
2
sudo chmod +x build_android.sh
./build_android.sh

编译会花上一段时间。

7、查看编译结果

编译完成后$ndk_dir/sources/ffmpeg-2.5.3下面会多出一个android目录,里面就是我们想要的编译好的库。

?
1
[cg@local]# ls -r android/
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
arm
 
android//arm:
android.mk include lib
 
android//arm/include:
libavcodec libavfilter libavformat libavutil  libswresample libswscale
 
android//arm/include/libavcodec:
avcodec.h  avfft.h   dv_profile.h dxva2.h   old_codec_ids.h vaapi.h   vda.h   vdpau.h   version.h  vorbis_parser.h xvmc.h
 
android//arm/include/libavfilter:
asrc_abuffer.h avcodec.h  avfilter.h  avfiltergraph.h buffersink.h buffersrc.h  version.h
 
android//arm/include/libavformat:
avformat.h avio.h  version.h
 
android//arm/include/libavutil:
adler32.h  avstring.h  cast5.h   downmix_info.h hash.h   macros.h   opt.h   replaygain.h  time.h
aes.h   avutil.h   channel_layout.h error.h   hmac.h   mathematics.h parseutils.h  ripemd.h   timecode.h
attributes.h  base64.h   common.h   eval.h   imgutils.h  md5.h   pixdesc.h  samplefmt.h  timestamp.h
audio_fifo.h  blowfish.h  cpu.h   ffversion.h  intfloat.h  mem.h   pixelutils.h  sha.h   version.h
audioconvert.h bprint.h   crc.h   fifo.h   intreadwrite.h motion_vector.h pixfmt.h   sha512.h   xtea.h
avassert.h  bswap.h   dict.h   file.h   lfg.h   murmur3.h  random_seed.h stereo3d.h
avconfig.h  buffer.h   display.h  frame.h   log.h   old_pix_fmts.h rational.h  threadmessage.h
 
android//arm/include/libswresample:
swresample.h version.h
 
android//arm/include/libswscale:
swscale.h version.h
 
android//arm/lib:
libavcodec-56.so libavfilter-5.so libavformat-56.so libavutil-54.so libswresample-1.so libswscale-3.so pkgconfig
libavcodec.so  libavfilter.so  libavformat.so  libavutil.so  libswresample.so libswscale.so
 
android//arm/lib/pkgconfig:
libavcodec.pc libavfilter.pc libavformat.pc libavutil.pc  libswresample.pc libswscale.pc
其中libavcodec.so、libavfilter.so、libavformat.so、libavutil.so、libswresample.so、libswscale.so都是软链,没有用,可以删掉。

8、给ffmpeg库写android.mk使其可用
创建$ndk_dir/sources/ffmpeg-2.5.3/android/arm/android.mk文件,内容如下:
要注意其中.so前面的数字应该改成你的ffmpeg版本编译出来的数字。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
local_path:= $(call my-dir)
 
 include $(clear_vars)
local_module:= libavcodec
local_src_files:= lib/libavcodec-56.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)
 
 include $(clear_vars)
local_module:= libavformat
local_src_files:= lib/libavformat-56.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)
 
 include $(clear_vars)
local_module:= libswscale
local_src_files:= lib/libswscale-3.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)
 
 include $(clear_vars)
local_module:= libavutil
local_src_files:= lib/libavutil-54.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)
 
 include $(clear_vars)
local_module:= libavfilter
local_src_files:= lib/libavfilter-5.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)
 
 include $(clear_vars)
local_module:= libswresample
local_src_files:= lib/libswresample-1.so
local_export_c_includes := $(local_path)/include
include $(prebuilt_shared_library)

至此ndk配置完毕,后面是配置android studio的部分。

android studio部分
android studio和eclipse不太一样,它有一定的自动生成android.mk并自动搞定jni的能力。
但目前还并不足以让我们使用起来ffmpeg库。
因此我们的思路是禁用掉android studio自动ndk-build的功能,手动编译我们的c代码达到目的。

首先当然要新建一个android studio项目。
我们使用$root_dir指代项目根目录。

1、android studio配置ndk路径
$root_dir/local.properties原先只配置了sdk。

?
1
sdk.dir=/usr/local/bin/android-sdk-macosx

给它增加一行ndk的配置

?
1
2
sdk.dir=/usr/local/bin/android-sdk-macosx
ndk.dir=/usr/local/bin/android-ndk-r10d

2、配置build.gradle
项目里面有两个build.gradle,一个在根目录下,一个在$root_dir/app/src下,我们要修改的是后者。
a>添加这一段以禁用自动ndk-build。

?
1
sourcesets.main.jni.srcdirs = []

b>添加这一段让它知道用库

?
1
2
3
4
5
ndk {
 abifilter "armeabi"
 modulename "ovsplayer"
 ldlibs "log", "z", "m", "jnigraphics", "android"
}

修改后的build.gradle是这样的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
android {
 compilesdkversion 21
 buildtoolsversion "21.1.1"
 
 sourcesets.main.jni.srcdirs = [] // 禁用自动执行ndk-build
 defaultconfig {
  applicationid "com.example.chengang.myapplication"
  minsdkversion 15
  targetsdkversion 21
  versioncode 1
  versionname "1.0"
  ndk {
   abifilter "armeabi"
   modulename "ovsplayer" // 这个是c文件的名字
   ldlibs "log", "z", "m", "jnigraphics", "android"
  }
 }
 buildtypes {
  release {
   minifyenabled false
   proguardfiles getdefaultproguardfile('proguard-android.txt'), 'proguard-rules.pro'
  }
 }
}

3、生成头文件
执行命令,注意路径要根据自己的情况更改。

 

复制代码 代码如下:

 

javah -d jni -classpath ..\..\build\intermediates\classes\debug com.example.nativeapp.app.mainactivity

 


会生成这个文件$root_dir/app/src/main/jni/com_example_chengang_myapplication_mainactivity.h

 

4、编写c文件
$root_dir/app/src/main/jni/ovsplayer.c内容如下:


 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
 
#include "com_example_chengang_myapplication_mainactivity.h"
#include "libavutil/avutil.h"
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
 
 
jniexport jstring jnicall java_com_example_chengang_myapplication_mainactivity_getstringfromnative
 (jnienv * env , jobject obj)
 {
  const char *url = "/mnt/sdcard/1.mp4";
  av_register_all();
 
  avformatcontext *pformatctx = null;
  int ret = avformat_open_input(&pformatctx, url, null, null);
 
  ret = avformat_find_stream_info(input_context, null);
  int streamnum = input_context->nb_streams;
 
  char wd[512];
  sprintf(wd, "avcodec version %u\n, streamnum[%d]"
    , avcodec_version()
    , streamnum
    );
  return (*env)->newstringutf(env, wd);
 }

5、编写java文件
$root_dir/app/src/main/java/com/example/chengang/myapplication/mainactivity.java内容如下。
其中getstringfromnative()方法是我们实现的,打印了ffmpeg库的版本号(我编译的这个是3673444)和视频文件的信息出来。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.example.chengang.myapplication;
 
import android.support.v7.app.actionbaractivity;
import android.os.bundle;
import android.view.menu;
import android.view.menuitem;
import android.widget.textview;
 
 
public class mainactivity extends actionbaractivity {
 
 @override
 protected void oncreate(bundle savedinstancestate) {
  super.oncreate(savedinstancestate);
  setcontentview(r.layout.activity_main);
 
  textview view = (textview) findviewbyid(r.id.mytext);
  view.settext(this.getstringfromnative());
 }
 
 
 @override
 public boolean oncreateoptionsmenu(menu menu) {
  // inflate the menu; this adds items to the action bar if it is present.
  getmenuinflater().inflate(r.menu.menu_main, menu);
  return true;
 }
 
 @override
 public boolean onoptionsitemselected(menuitem item) {
  // handle action bar item clicks here. the action bar will
  // automatically handle clicks on the home/up button, so long
  // as you specify a parent activity in androidmanifest.xml.
  int id = item.getitemid();
 
  //noinspection simplifiableifstatement
  if (id == r.id.action_settings) {
   return true;
  }
 
  return super.onoptionsitemselected(item);
 }
 
 public native string getstringfromnative();
 static {
  system.loadlibrary("swresample-1");
  system.loadlibrary("avutil-54");
  system.loadlibrary("avformat-56");
  system.loadlibrary("avcodec-56");
  system.loadlibrary("swscale-3");
  system.loadlibrary("ovsplayer");
 }
}

附上布局文件是这样的。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
 android:layout_height="match_parent" android:paddingleft="@dimen/activity_horizontal_margin"
 android:paddingright="@dimen/activity_horizontal_margin"
 android:paddingtop="@dimen/activity_vertical_margin"
 android:paddingbottom="@dimen/activity_vertical_margin" tools:context=".mainactivity">
 
 <textview
  android:id="@+id/mytext"
  android:text="@string/hello_world" android:layout_width="wrap_content"
  android:layout_height="wrap_content" />
 
</relativelayout>

6、编写项目android.mk
android.mk放到$root_dir/app/build/intermediates/ndk/debug/下。
注意其中的绝对路径”/users/chengang/code/android/myapplication4″是咱们的$root_dir。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
local_path := $(call my-dir)
include $(clear_vars)
 
local_module := ovsplayer
local_ldlibs := \
 -lm \
 -ljnigraphics \
 -landroid \
 -llog \
 -lz \
 
local_shared_libraries := libavformat libavcodec libswscale libavutil
 
local_src_files := \
 /users/chengang/code/android/myapplication4/app/src/main/jni/ovsplayer.c \
 
local_c_includes += /users/chengang/code/android/myapplication4/app/src/main/jni
local_c_includes += /users/chengang/code/android/myapplication4/app/src/debug/jni
 
include $(build_shared_library)
$(call import-module,ffmpeg-2.5.3/android/arm)

简单说下local_ldlibs和local_shared_libraries的区别,前者链接系统库,后者链接第三方库。
并不能换着用。

7、编译c代码
在终端下执行这个命令编译c代码。

 

复制代码 代码如下:

 

/usr/local/bin/android-ndk-r10d/ndk-build ndk_project_path=null app_build_script=/users/chengang/code/android/myapplication4/app/build/intermediates/ndk/debug/android.mk app_platform=android-21 ndk_out=/users/chengang/code/android/myapplication4/app/build/intermediates/ndk/debug/obj ndk_libs_out=/users/chengang/code/android/myapplication4/app/build/intermediates/ndk/debug/lib app_abi=armeabi

 


8、运行项目
回到android studio中ctrl+r运行项目。
会看到手机上打印出ffmpeg库的版本号(我编译的这个是3673444)和视频文件的信息出来。
ffmpeg库已经可以调用了,然后继续写自己的逻辑就好了。

 

9、另外
另外说两个c代码中可能遇见的错误:
a>如果你通过标准库想得到文件大小,又写错了文件名。安卓上,在不存在的文件的句柄上执行fseek会报如下错误:”could not disable core file generation.”;
b>如果avformat_open_input()返回-1330794744了。那有两种可能,一是你忘记调用av_register_all()了,二是编译ffmpeg的时候没有编译进对应的demuxer或者指定了disable-everything。