AndroidStudio 2.2 开始支持 CMake
编译,网上的文章清一色都是 ndk-build
,记录一下我用 CMake 搭建环境的过程。
基础环境
MacOS 10.12.3
AndroidStudio 2.3
NDK 13.1.*
FFmpeg 3.2.*
下载 FFmpeg 源码 1 git clone https://git.ffmpeg.org/ffmpeg.git
切换到 3.2 分支
1 git checkout -b 3.2 remotes/origin/release/3.2
当然你也可以去去官网下载压缩包。
编译 FFmpeg for Android 打开 configure 文件,找到:
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)'
编写 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 39 40 #!/bin/bash NDK=/Users/gavin/Develop/android-sdk/ndk-bundle SYSROOT=$NDK /platforms/android-15/arch-arm/ TOOLCHAIN=$NDK /toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64 CPU=arm PREFIX=$(pwd )/android/$CPU ADDI_CFLAGS="-marm" 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 } build_one
前三行自行更换成自己的 NDK 路径即可,PREFIX
就是最终编译输出地址,根据情况也可以修改。
保存文件到 FFmpeg
根目录为 build_android.sh
, chmod +x build_android.sh
赋予执行权限。
编译 执行脚本进行编译。
小憩一会儿,编译完成。
进入 PREFIX 目录:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ── include │ ├── libavcodec │ ├── libavfilter │ ├── libavformat │ ├── libavutil │ ├── libswresample │ └── libswscale └── lib ├── libavcodec-57.so ├── libavfilter-6.so ├── libavformat-57.so ├── libavutil-55.so ├── libswresample-2.so ├── libswscale-4.so └── pkgconfig
我们需要的 so 和头文件都有啦。
导入 Android 工程 对于如何开始一个 C++ 项目,官方已经有中文文档了:
https://developer.android.google.cn/studio/projects/add-native-code.html
官网有的东西不赘述,再次声明学什么东西优先看 官方文档,官方文档,官方文档 ,并且 Google dev 现在已经有很多中文资源了。
新建一个 C/C++ 工程
勾选 Include C++ Support 即可得到一个基于 CMake 的模板工程。
拷贝 so 和 incude 文件到 libs 因为我们只编译了 arm 版本的 so,所以需要把 so 拷到 armeabi-v7a 目录下,完整路径如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ├── build.gradle ├── libs │ ├── armeabi-v7a │ │ ├── libavcodec-57.so │ │ ├── libavfilter-6.so │ │ ├── libavformat-57.so │ │ ├── libavutil-55.so │ │ ├── libswresample-2.so │ │ └── libswscale-4.so │ └── include │ ├── libavcodec │ ├── libavfilter │ ├── libavformat │ ├── libavutil │ ├── libswresample │ └── libswscale ├── proguard-rules.pro └── src └── main ├── AndroidManifest.xml ├── cpp ├── java └── res
指定 jniLibs 路径 1 2 3 4 5 6 7 8 android { ... sourceSets { main { jniLibs.srcDirs = ['libs' ] } } }
指定 abiFilters 因为 so 只有 armeabi-v7a 的,所以我们还需要指定一下 abi 为 armeabi-v7a
1 2 3 4 5 6 7 8 9 android { ... defaultConfig { ... ndk { abiFilters 'armeabi-v7a' } } }
编写 CMakeLists 脚本怎么写,有什么讲究其实官方文档已经很清楚了:创建 CMake 构建脚本
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 cmake_minimum_required(VERSION 3.4.1) add_library( native-lib SHARED src/main/cpp/native-lib.cpp ) add_library( avcodec-57 SHARED IMPORTED ) set_target_properties( avcodec-57 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libavcodec-57.so ) add_library( avfilter-6 SHARED IMPORTED ) set_target_properties( avfilter-6 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libavfilter-6.so ) add_library( avformat-57 SHARED IMPORTED ) set_target_properties( avformat-57 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libavformat-57.so ) add_library( avutil-55 SHARED IMPORTED ) set_target_properties( avutil-55 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libavutil-55.so ) add_library( swresample-2 SHARED IMPORTED ) set_target_properties( swresample-2 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libswresample-2.so ) add_library( swscale-4 SHARED IMPORTED ) set_target_properties( swscale-4 PROPERTIES IMPORTED_LOCATION ../../../../libs/armeabi-v7a/libswscale-4.so ) include_directories( libs/include ) find_library( log-lib log ) target_link_libraries( native-lib avcodec-57 avfilter-6 avformat-57 avutil-55 swresample-2 swscale-4 ${log-lib} )
Java 引入 so 库 1 2 3 4 5 6 7 8 9 10 static { System.loadLibrary("native-lib" ); System.loadLibrary("avcodec-57" ); System.loadLibrary("avfilter-6" ); System.loadLibrary("avformat-57" ); System.loadLibrary("avutil-55" ); System.loadLibrary("swresample-2" ); System.loadLibrary("swscale-4" ); }
运行测试一下 如果成功运行不报错的话,就证明一切没有问题。
编写 FFmpeg 测试代码 UI 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 54 55 56 57 58 59 60 61 62 <?xml version="1.0" encoding="utf-8" ?> <LinearLayout 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:orientation ="vertical" tools:context ="cn.gavinliu.android.ffmpeg.MainActivity" > <LinearLayout android:layout_width ="match_parent" android:layout_height ="wrap_content" android:orientation ="horizontal" > <Button android:id ="@+id/button" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:onClick ="protocol" android:text ="Protocol" android:textAllCaps ="false" /> <Button android:id ="@+id/button2" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:onClick ="filter" android:text ="Filter" android:textAllCaps ="false" /> <Button android:id ="@+id/button3" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:onClick ="format" android:text ="Format" android:textAllCaps ="false" /> <Button android:id ="@+id/button4" android:layout_width ="0dp" android:layout_height ="wrap_content" android:layout_weight ="1" android:onClick ="codec" android:text ="Codec" android:textAllCaps ="false" /> </LinearLayout > <ScrollView android:layout_width ="match_parent" android:layout_height ="wrap_content" > <TextView android:id ="@+id/sample_text" android:layout_width ="wrap_content" android:layout_height ="wrap_content" android:scrollbars ="vertical" android:text ="Hello World!" /> </ScrollView > </LinearLayout >
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 54 55 56 57 58 59 60 61 62 63 64 package cn.gavinliu.android.ffmpeg;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.TextView;public class MainActivity extends AppCompatActivity { static { System.loadLibrary("native-lib" ); System.loadLibrary("avcodec-57" ); System.loadLibrary("avfilter-6" ); System.loadLibrary("avformat-57" ); System.loadLibrary("avutil-55" ); System.loadLibrary("swresample-2" ); System.loadLibrary("swscale-4" ); } private TextView tv; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } public void protocol (View view) { tv.setText(urlprotocolinfo()); } public void filter (View view) { tv.setText(avformatinfo()); } public void format (View view) { tv.setText(avfilterinfo()); } public void codec (View view) { tv.setText(avcodecinfo()); } public native String stringFromJNI () ; public native String avformatinfo () ; public native String urlprotocolinfo () ; public native String avcodecinfo () ; public native String avfilterinfo () ; }
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 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 #include <jni.h> #include <string> extern "C" {#include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavfilter/avfilter.h> JNIEXPORT jstring JNICALL Java_cn_gavinliu_android_ffmpeg_MainActivity_stringFromJNI ( JNIEnv *env, jobject ) { std ::string hello = "Hello from C++" ; return env->NewStringUTF(hello.c_str()); } JNIEXPORT jstring JNICALL Java_cn_gavinliu_android_ffmpeg_MainActivity_avformatinfo ( JNIEnv *env, jobject ) { char info[40000 ] = {0 }; av_register_all(); AVInputFormat *if_temp = av_iformat_next(NULL ); AVOutputFormat *of_temp = av_oformat_next(NULL ); while (if_temp != NULL ) { sprintf (info, "%sInput: %s\n" , info, if_temp->name); if_temp = if_temp->next; } while (of_temp != NULL ) { sprintf (info, "%sOutput: %s\n" , info, of_temp->name); of_temp = of_temp->next; } return env->NewStringUTF(info); } JNIEXPORT jstring JNICALL Java_cn_gavinliu_android_ffmpeg_MainActivity_urlprotocolinfo ( JNIEnv *env, jobject ) { char info[40000 ] = {0 }; av_register_all(); struct URLProtocol *pup = NULL ; struct URLProtocol **p_temp = &pup; avio_enum_protocols((void **) p_temp, 0 ); while ((*p_temp) != NULL ) { sprintf (info, "%sInput: %s\n" , info, avio_enum_protocols((void **) p_temp, 0 )); } pup = NULL ; avio_enum_protocols((void **) p_temp, 1 ); while ((*p_temp) != NULL ) { sprintf (info, "%sInput: %s\n" , info, avio_enum_protocols((void **) p_temp, 1 )); } return env->NewStringUTF(info); } JNIEXPORT jstring JNICALL Java_cn_gavinliu_android_ffmpeg_MainActivity_avcodecinfo ( JNIEnv *env, jobject ) { char info[40000 ] = {0 }; av_register_all(); AVCodec *c_temp = av_codec_next(NULL ); while (c_temp != NULL ) { if (c_temp->decode != NULL ) { sprintf (info, "%sdecode:" , info); } else { sprintf (info, "%sencode:" , info); } switch (c_temp->type) { case AVMEDIA_TYPE_VIDEO: sprintf (info, "%s(video):" , info); break ; case AVMEDIA_TYPE_AUDIO: sprintf (info, "%s(audio):" , info); break ; default : sprintf (info, "%s(other):" , info); break ; } sprintf (info, "%s[%10s]\n" , info, c_temp->name); c_temp = c_temp->next; } return env->NewStringUTF(info); } JNIEXPORT jstring JNICALL Java_cn_gavinliu_android_ffmpeg_MainActivity_avfilterinfo (JNIEnv *env, jobject ) { char info[40000 ] = {0 }; avfilter_register_all(); AVFilter *f_temp = (AVFilter *) avfilter_next(NULL ); while (f_temp != NULL ) { sprintf (info, "%s%s\n" , info, f_temp->name); f_temp = f_temp->next; } return env->NewStringUTF(info); } }
运行
更多