AndroidStudio中使用FFMPEG入门

1.简介


但是我们想要在Android开发时使用FFMPEG的功能,不仅仅把.so库文件和头文件夹移到工程下面就可以直接调用,

还需要我们进行一些配置才能在代码中使用FFMPEG的功能。

网上搜了一大堆博客教程,讲如何在AndroidStudio中配置FFMPEG的.so库和头文件,都是使用一个文件叫 Android.mk,奈何我没有成功过一次。

经过摸打滚爬,我终于汲取了一些经验。找到了一种我更好看懂及理解的方法,那就是不要Android.mk文件了,而是另外一个东西叫

CMakeLists.txt。自我感觉用 CMakeLists.txt简单多了。下面就讲讲我是如何在AndroidStudio中使用FFMPEG的。

2.具体步骤


1.在androidstudio中新建一个工程

我的工程名是  Useffmpeg

包名是  com.liuyan.myapplication

记得勾选 include c++ support ,然后一直next就行了

创建工程后,如果提示没有配置ndk,就自己配置好ndk路径。

ndk配置一定要慎重,不要从androidstudio直接下载,我说过很多遍了。

不知道ndk配置的去这篇文章    android开发-NDK-JNI入门教程   找对应的内容。

创建工程完成后你会发现多出来两个东西,稍后会用到。

2.在main目录下面新建一个包 jniLibs 

由于这个包下面的东西是会被打包进APK的,但是我们简简单单创建了没有用,还需要在 build.gradle中配置这个包。

apply plugin: 'com.android.application'

android {
    ...
    defaultConfig {
        ...
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        //自动生成的,不用管
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
//配置我们的 jniLibs
    sourceSets.main {
        jniLibs.srcDirs = ['src/main/jniLibs']
    }

    buildTypes {
        ...
    }
//自动生成的不用管
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

dependencies {
    ...
}

然后sync 一下 project。

把我们之前编译好的 include头文件夹 和.so文件准备好。

在 jniLibs 下创建一个 armeabi 文件夹。把 include 头文件夹移到 jniLibs下面,把.so文件移到armeabi文件内。如下:

3.编写我们的 CMakeLists.txt 文件

cmake_minimum_required(VERSION 3.4.1)

find_library( log-lib
              log )

set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})

add_library( avutil-55
             SHARED
             IMPORTED )
set_target_properties( avutil-55
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libavutil-55.so)

add_library( swresample-2
             SHARED
             IMPORTED )
set_target_properties( swresample-2
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libswresample-2.so)

add_library( avcodec-57
             SHARED
             IMPORTED )
set_target_properties( avcodec-57
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libavcodec-57.so)

add_library( avfilter-6
             SHARED
             IMPORTED )
set_target_properties( avfilter-6
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libavfilter-6.so)

add_library( swscale-4
             SHARED
             IMPORTED )
set_target_properties( swscale-4
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libswscale-4.so)

add_library( avdevice-57
             SHARED
             IMPORTED)
set_target_properties( avdevice-57
                       PROPERTIES IMPORTED_LOCATION
                       ${distribution_DIR}/libavdevice-57.so )

add_library( avformat-57
             SHARED  
             IMPORTED )
set_target_properties( avformat-57  
                       PROPERTIES IMPORTED_LOCATION  
                       ${distribution_DIR}/libavformat-57.so)  
  
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp)

include_directories(src/main/jniLibs/include)

target_link_libraries(native-lib swresample-2 avcodec-57 avfilter-6 swscale-4 avdevice-57 avformat-57 avutil-55
                                             ${log-lib})

至于为什么是上面那样配置,我也说不上来,自己理解就好,或者自己百度有专门解释 CMakeLists.txt 的内容。

编写完成之后, sync 一下 project。

4.编写 cpp 目录下面的 native-lib.cpp 文件

这个文件中已经有一个示例代码了,MainActivity中也调用了。我们不管这个,继续往下:

我们这一步主要就是用 c代码调用 ffmpeg的功能。

但是为什么我们要到这一步才开始编写 c代码调用 ffmpeg的功能呢?

  • 答案当然是我们之前没有配置 头文件及库文件啊,只有配置好了,我们才能在 native-lib.cpp 文件先引入 ffmpeg相关的头文件,才能编写相关代码使用 ffmpeg的功能。

native-lib.cpp 文件编写如下,只是用来测试 ffmpeg能否使用,至少走的通。以后才能使用更强大的ffmpeg的功能。

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

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>

jstring
Java_com_liuyan_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

jstring
Java_com_liuyan_myapplication_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);
}

jstring
Java_com_liuyan_myapplication_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);
}

jstring
Java_com_liuyan_myapplication_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);
}

jstring
Java_com_liuyan_myapplication_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);
}


}

这里我编写了4个函数跟 ffmpeg有关,都是返回string,用来测试是否能够走通流程。

加上本来有的一个函数,一共5个函数。

你自己想要编写更多的 函数,可以自己编写函数名,函数命名的规则及参数,可以在上面找到规律,或者自己百度。

但是注意的是:

由于 native-lib.cpp 是 c++编写的,而我们的代码是 c语言编写的,所以,

所有的头文件引入 和 函数编写必须在 extern "C" { } 括号里面:

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

//一定要在括号里面
extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>

jstring
Java_com_liuyan_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

jstring
Java_com_liuyan_myapplication_MainActivity_urlprotocolinfo(
        JNIEnv *env, jobject) {
    ...
}

...


}

至此,我们的本地方法已经编写好了,所有的准备工作都已经做好了。终于歇了一口气!

5.在 MainActivity.java 中开始使用

首先当然是加载我们的库。

static {
        System.loadLibrary("native-lib");
    }

然后编写4个 native方法,名字要与之前在 native-lib.cpp文件中编写的函数名对应起来。加上本来有的一个native方法,一共5个。

而且编写完 native方法之后会有一个标记,点击可以跳到对应的 c函数。

使用如下:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    TextView tv;
    Button protocol;
    Button format;
    Button codec;
    Button filter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        protocol = (Button) findViewById(R.id.btn_protocol);
        format = (Button) findViewById(R.id.btn_format);
        codec = (Button) findViewById(R.id.btn_codec);
        filter = (Button) findViewById(R.id.btn_filter);
        protocol.setOnClickListener(this);
        format.setOnClickListener(this);
        codec.setOnClickListener(this);
        filter.setOnClickListener(this);
        tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());

    }
    public native String stringFromJNI();

    public native String urlprotocolinfo();
    public native String avformatinfo();
    public native String avcodecinfo();
    public native String avfilterinfo();

    static {
        System.loadLibrary("native-lib");
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_protocol:
                tv.setText(urlprotocolinfo());
                break;
            case R.id.btn_format:
                tv.setText(avformatinfo());
                break;
            case R.id.btn_codec:
                tv.setText(avcodecinfo());
                break;
            case R.id.btn_filter:
                tv.setText(avfilterinfo());
                break;
            default:
                break;
        }
    }
}

然后运行程序到手机上。

这里产生了一个错误:

Error:error: '../../../../src/main/jniLibs/mips64/libswresample-2.so', needed by '../obj/mips64/libnative-lib.so', missing and no known rule to make it

原因是打包过程中,找不到对应的系统架构mips64。因为我们之前只给出了一个系统架构armeabi , 我们可以在 build.gradle中配置一下解决这个问题。

apply plugin: 'com.android.application'

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            //我们只有armeabi就只配置这个就行了
            abiFilters  'armeabi'
        }
    }

    sourceSets.main {
        jniLibs.srcDirs = ['src/main/jniLibs']
    }

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

dependencies {
    ...
}

重新运行程序。点击其中format按钮,效果如下:

猜你喜欢

转载自blog.csdn.net/qq_38261174/article/details/83273409