Linux下ndk编译移植FFmpeg到Android平台

字数 767阅读 2262

简介

  • linux下编译FFmpeg
  • Android项目使用FFmpeg
  • 编译运行
  • 常见问题

linux下编译FFmpeg

开发环境配置
  • FFmpeg编译环境 centos6.8 64位
[root@iZ94g6hanmqZ include]# lsb_release -a
LSB Version:    :base-4.0-amd64:base-4.0-noarch:core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID: CentOS
Description:    CentOS release 6.8 (Final)
Release:    6.8
Codename:   Final
  • linux下安装sdkmanager以及ndk
    参考Linux下Android构建环境,可以sdk加入的环境变量中,方便执行sdkmanager命令。
export ANDROID_HOME=/usr/local/android
export PATH=${PATH}:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools/

查看可下载的内容sdkmanager --list

6.png

然后下载ndk。执行命令

sdkmanager 'ndk-bundle'

然后等待下载完成。

7.png
  • 配置ndk环境变量
export NDK_HOME=/usr/local/android/ndk-bundle
export PATH=${PATH}:$NDK_HOME
下载编译FFmpeg

这里我们选择3.2.4版本(注意:这里使用的3.2.4版本,如果用最新的版本,编译可能出现问题,为了想让大家上手,建议版本先保持一致)。直接github上选择下载解压即可。为了方便编译,我们在解压后的目录中写一个shell脚本来进行配置。build_ffmpeg.sh

#!/bin/bash
NDK=/usr/local/android/ndk-bundle
SYSROOT=$NDK/platforms/android-16/arch-arm/
TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64

function build_one
{
    ./configure \
        --prefix=$PREFIX \
        --enable-shared \
        --disable-static \
        --disable-doc \
        --disable-ffserver \
        --enable-cross-compile \
        --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \
        --target-os=linux \
        --arch=arm \
        --sysroot=$SYSROOT \
        --extra-cflags="-Os -fpic $ADDI_CFLAGS" \
        --extra-ldflags="$ADDI_LDFLAGS" \
        $ADDITIONAL_CONFIGURE_FLAG
}
CPU=arm
PREFIX=$(pwd)/android/$CPU
ADDI_CFLAGS="-marm"
build_one

注意:这里的platforms使用的android-16,如果使用更高,可能在app运行时会出现atof相关错误,同样至于为什么,现在先不做解释

NDK替换成自己的路径即可。接下来给脚本添加运行属性chmod +x build_ffmpeg.sh。现在就可以开始运行build_ffmpeg.sh来生成Makefile。./build_ffmpeg.sh

1.png

执行完可能会有一个警告如下:

WARNING: /usr/local/android/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-pkg-config not found, library detection may fail.

这里不会有影响,直接忽略。接下来就开始编译

make -j4

编译完成后,安装

make install

在当前目录下会生成一个android目录。里面有个arm目录

[root@iZ94g6hanmqZ android]# ll
total 4
drwxr-xr-x 6 root root 4096 Oct 31 07:12 arm
[root@iZ94g6hanmqZ android]# cd arm/
[root@iZ94g6hanmqZ arm]# ll
total 16
drwxr-xr-x 2 root root 4096 Oct 31 07:12 bin
drwxr-xr-x 9 root root 4096 Oct 31 07:12 include
drwxr-xr-x 3 root root 4096 Oct 31 07:12 lib
drwxr-xr-x 3 root root 4096 Oct 31 07:12 share

我们看到有bin、include、lib、share
lib包含了生成的动态库文件

-rwxr-xr-x 1 root root 11414264 Oct 31 07:12 libavcodec-57.so
lrwxrwxrwx 1 root root       16 Oct 31 07:12 libavcodec.so -> libavcodec-57.so
-rwxr-xr-x 1 root root    59384 Oct 31 07:12 libavdevice-57.so
lrwxrwxrwx 1 root root       17 Oct 31 07:12 libavdevice.so -> libavdevice-57.so
-rwxr-xr-x 1 root root  1638076 Oct 31 07:12 libavfilter-6.so
lrwxrwxrwx 1 root root       16 Oct 31 07:12 libavfilter.so -> libavfilter-6.so
-rwxr-xr-x 1 root root  1952368 Oct 31 07:12 libavformat-57.so
lrwxrwxrwx 1 root root       17 Oct 31 07:12 libavformat.so -> libavformat-57.so
-rwxr-xr-x 1 root root   443860 Oct 31 07:12 libavutil-55.so
lrwxrwxrwx 1 root root       15 Oct 31 07:12 libavutil.so -> libavutil-55.so
-rwxr-xr-x 1 root root    91532 Oct 31 07:12 libswresample-2.so
lrwxrwxrwx 1 root root       18 Oct 31 07:12 libswresample.so -> libswresample-2.so
-rwxr-xr-x 1 root root   406924 Oct 31 07:12 libswscale-4.so
lrwxrwxrwx 1 root root       15 Oct 31 07:12 libswscale.so -> libswscale-4.so
drwxr-xr-x 2 root root     4096 Oct 31 07:12 pkgconfig

include 包含了头文件

[root@iZ94g6hanmqZ arm]# cd include/
[root@iZ94g6hanmqZ include]# ll
total 28
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libavcodec
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libavdevice
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libavfilter
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libavformat
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libavutil
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libswresample
drwxr-xr-x 2 root root 4096 Oct 31 07:12 libswscale

到此为止,FFmpeg的动态库就编译完成了。

Android项目使用FFmpeg

首先android studio及其ndk需要下载配置好.红色都是需要安装的。

3.png

新建工程,注意勾选include C++ support(这里我们不再使用老的模式进行nkd开发,我们使用android studio新支持的模式)


2.png

看下项目工程目录:


4.png

将FFmpeg生成的include和so库放入到图中对应的位置中。
下面看下gradle文件:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.wangheart.ffmpegdemo"
        minSdkVersion 16
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters "armeabi"
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.0.0-beta1'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:0.5'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
}

主要增加abiFilters "armeabi",因为我们刚才编译的FFmpeg是arm平台。所以这里就选择arm平台。

重点看到CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)


#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

#引用的头文件
include_directories(
                    src/main/cpp/include
                    )

#添加库 动态库为SHARED  静态库就是STATIC
add_library(avcodec SHARED IMPORTED)
set_target_properties(avcodec
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so)

add_library(avdevice SHARED IMPORTED)
set_target_properties(avdevice
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavdevice-57.so)

add_library(avfilter SHARED IMPORTED)
set_target_properties(avfilter
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavfilter-6.so)

add_library(avformat SHARED IMPORTED)
set_target_properties(avformat
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavformat-57.so)

add_library(avutil SHARED IMPORTED)
set_target_properties(avutil
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavutil-55.so)

add_library(swresample SHARED IMPORTED)
set_target_properties(swresample
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswresample-2.so)

add_library(swscale SHARED IMPORTED)
set_target_properties(swscale
  PROPERTIES IMPORTED_LOCATION
  ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswscale-4.so)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
# 链接库
target_link_libraries( # Specifies the target library.
                       native-lib

                       avcodec
                       avdevice
                       avfilter
                       avformat
                       avutil
                       swresample
                       swscale
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

add_library表示添加库,可以添加动态库或静态库。如:
add_library(avcodec SHARED IMPORTED)如果是静态库就是STATIC。下面就是设置动态库的路径:

set_target_properties(avcodec
  PROPERTIES IMPORTED_LOCATION
 ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so)

然后添加链接库,在target_link_libraries中加入我们需要连接的库。到这里基本CMakeLists.txt就配置完成了。

编辑native-lib.cpp文件

这里就是我们需要开发的c++文件,需要做什么功能都可以在这里编辑,本节只是做个演示,我就只是获取FFmpeg的一些信息并返回。

#include <jni.h>
#include <string>

//这里很重要,FFmpeg是C语言写的,如果不使用extern "C"则
//会出现链接出错
extern "C" {
#include "libavcodec/avcodec.h"
}

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_wangheart_ffmpegdemo_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */thiz) {
    char info[10000] = {0};
//    avcodec_version();
    sprintf(info, "%s\n", avcodec_configuration());
    return env->NewStringUTF(info);
}
MainActivity
package com.wangheart.ffmpegdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("avutil-55");
        System.loadLibrary("swresample-2");
        System.loadLibrary("avcodec-57");
        System.loadLibrary("avformat-57");
        System.loadLibrary("swscale-4");
        System.loadLibrary("avfilter-6");
        System.loadLibrary("avdevice-57");
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());



    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

这里注意要把其他所有的so都要加入。

8.png

Github示例项目源码注意版本

10.png

常见问题

jni编译链接失败

5.png

很多朋友在网上看到代码,在引入头文件时候直接引用比如:#include "libavcodec/avcodec.h"
如果是C语言则没什么问题。如果是C++,则需要使用extern "C"

extern "C" {
#include "libavcodec/avcodec.h"
}
atof问题
cannot locate symbol "atof" referenced by "libavformat-57.so"...

这里有个解决办法就是在编译FFmpeg的时候使用android-16。也就是build_ffmpeg.sh的时候修改为:

SYSROOT=$NDK/platforms/android-16/arch-arm/

推荐阅读更多精彩内容