美文网首页
「ffmpeg」二 Android平台基于ffmpeg的Hell

「ffmpeg」二 Android平台基于ffmpeg的Hell

作者: 叨码 | 来源:发表于2019-08-08 14:46 被阅读0次
引言

本ffmpeg系列文章可以看作是基于雷神的博客的学习实践,由于雷神所使用的ffmpeg是老版本,一些地方我也会根据新版本的API做了一些更新,另外改为了Cmake构建方式,这也是区别于雷神博客的地方。

书接上回,经过一通操作,我们成功编译了ffmpeg源码并生成了so库文件。本文就讲解一下如何集成so库文件到Android项目并调用其API打印一些库的相关信息。

准备

创建一个Android DNK项目,项目建成后,会默认生成一个入口MainActivity以及cpp源码native-lib.c,这部分之前文章有讲过,不了解的可以移步CMake构建NDK项目生成so库
最终我们得到了如下

编译ffmpeg生成的so库以及include文件夹.png

之后我们就开始着手迁移和改造工作了。

迁移

1.之前我们在编译ffmpeg时指定的CPU=arm ,对应着abi就是armeabi-v7a ,将编译生成的so库扔到armeabi-v7a下,另外将include扔到libs下


so库文件及include.png

2.配置build.gradle,主要有三个关键点,如图所示


gradle.png

3.重头戏,配置CmakeList.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 最低版本为3.4.1,如果没指定,执行 cmake 命令时可能会出错
cmake_minimum_required(VERSION 3.4.1)

# 添加在native层log库
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 )

set(distribution_DIR ${CMAKE_SOURCE_DIR}/libs)
include_directories(libs/include)
#----------添加响应的库----------#
# avutil
add_library( avutil-55
        SHARED
        IMPORTED )
set_target_properties( avutil-55
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/armeabi-v7a/libavutil-55.so )

# avdevice
add_library( avdevice-57
        SHARED
        IMPORTED )
set_target_properties( avdevice-57
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/armeabi-v7a/libavdevice-57.so )
# swresample
add_library( swresample-2
        SHARED
        IMPORTED )
set_target_properties( swresample-2
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/armeabi-v7a/libswresample-2.so )

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

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

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

# avformat
add_library( avformat-57
        SHARED
        IMPORTED)
set_target_properties( avformat-57
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/armeabi-v7a/libavformat-57.so )
# postproc
add_library( postproc-54
        SHARED
        IMPORTED)
set_target_properties( postproc-54
        PROPERTIES IMPORTED_LOCATION
        ${distribution_DIR}/armeabi-v7a/libpostproc-54.so )

#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")


add_library( native-lib
        SHARED
        src/main/cpp/native-lib.c
        # TODO 我们之后自己编写的cpp文件都会添加在这里,比如
        # src/main/cpp/test1.cpp
        # src/main/cpp/test2.cpp
        )
#-------指定目标链接库-------#
target_link_libraries( # Specifies the target library.
        native-lib
        # 这里需要注意下,下面这些ffmpeg的so库编译是有先后顺序的
        # 下面的顺序是没问题的,如果想验证编译顺序,可以自己变换顺序试试.
        avcodec-57
        avutil-55
        avdevice-57
        swresample-2
        avfilter-6
        swscale-4
        avformat-57
        postproc-54
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

PS:需要注意的是,target_link_libraries中库的顺序,刚开始随意写的顺序,但编译报错,一直查原因没有查到,后来调整了下顺序之后就可以了,原因不明。。。我这里的顺序是正确的,你可以去尝试其他的或者知道原因的话,还望不吝指教,谢谢。
到此,其实项目正常是可以运行起来了,但应该只是一个空白页面而已。
接下来我们做一些改造,最终实现调用ffmpeg的api方法展示一些相关库信息

改造

1.native-lib.c文件修改实现c语言端的改造

#include <jni.h>
#include <string.h>
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
/**
* 本程序是移植FFmpeg到安卓平台的最简单程序。它可以打印出FFmpeg类库    *的下列信息:
 *  Protocol:  FFmpeg类库支持的协议
 *  AVFormat:  FFmpeg类库支持的封装格式
 *  AVCodec:   FFmpeg类库支持的编解码器
 *  AVFilter:  FFmpeg类库支持的滤镜
**/

//
//jstring Java_com_ing_ffmpeg_MainActivity_stringFromJNI(
//        JNIEnv *env,
//        jobject thiz) {
//    char info[10000] = {0};
//    sprintf(info, "%s\n", avcodec_configuration());
//    return (*env)->NewStringUTF(env, info);
//}

struct URLProtocol;
/**
 * 利用avio_enum_protocols迭代libavformat/protocol_list.c中的url_protocols数组,输出ffmpeg支持的IO协议
 */
JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_urlprotocolinfo(JNIEnv *env, jobject obj) {
    char info[40000] = {0}; //全部初始化为0
    avcodec_register_all();//注册所有的编解码器
    struct URLProtocol *pup = NULL;
    //Input
    struct URLProtocol **p_temp = &pup;
    avio_enum_protocols((void **) p_temp, 0);
    while ((*p_temp) != NULL) {
        sprintf(info, "%s[In ][%10s\n]", info, avio_enum_protocols((void **) p_temp, 0));
    }
    pup = NULL;
    avio_enum_protocols((void **) p_temp, 1);
    while ((*p_temp) != NULL) {
        sprintf(info, "%s[Out][%10s]\n", info, avio_enum_protocols((void **) p_temp, 1));
    }
    return (*env)->NewStringUTF(env, info);
}


/**
 * 输出ffmpeg支持的格式
 */
JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_avformatinfo(JNIEnv *env, jobject obj) {
    char info[40000] = {0};
    av_register_all();
    AVInputFormat *if_temp = av_iformat_next(NULL);
    AVOutputFormat *of_temp = av_oformat_next(NULL);
    //Input
    while (if_temp != NULL) {
        sprintf(info, "%s[In ][%10s]\n", info, if_temp->name);
        if_temp = if_temp->next;
    }
    //Output
    while (of_temp != NULL) {
        sprintf(info, "%s[Out ][%10s]\n", info, of_temp->name);
        of_temp = of_temp->next;
    }
    //LOGE("%s",info);
    return (*env)->NewStringUTF(env, info);
}

/**
 * 支持的编码器信息
 */
JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_avcodecinfo(JNIEnv *env, jobject obj) {
    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, "%s[Dec]", info);
        } else {
            sprintf(info, "%s[Enc]", 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(env, info);
}
/**
 * 支持的滤波器
 */
JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_avfilterinfo(JNIEnv *env, jobject obj) {
    char info[40000] = {0};
    avfilter_register_all();
    AVFilter *f_temp = (AVFilter *) avfilter_next(NULL);
    while (f_temp != NULL) {
        sprintf(info, "%s[%10s]\n", info, f_temp->name);
        f_temp = f_temp->next;
    }
    return (*env)->NewStringUTF(env, info);
}
/**
 * configure信息
 */
JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_configurationinfo(JNIEnv *env, jobject obj) {
    char info[40000] = {0};
    av_register_all();
    sprintf(info, "%s\n", avcodec_configuration());
    return (*env)->NewStringUTF(env, info);
}

通过代码可以看出,JNI调用的c语言函数是有固定格式的
例如

JNIEXPORT jstring Java_com_ing_ffmpeg_MainActivity_configurationinfo(JNIEnv *env, jobject obj)

Java_{包名}_{包名}…_{类名}(JNIEnv *,…)

前面的JNIEXPORT jstring 中 jstring代表的是函数返回值类型,对应着java代码就是返回String字符串类型。

2.Java代码MainActivity的修改

package com.ing.ffmpeg;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button show_protocol, show_format, show_decodec, show_confuretion, show_filter,decode;
    TextView tv;

    //引入ffmpeg相关库
    static {
        System.loadLibrary("native-lib");
        System.loadLibrary("avcodec-57");
        System.loadLibrary("avfilter-6");
        System.loadLibrary("avdevice-57");
        System.loadLibrary("avutil-55");
        System.loadLibrary("swresample-2");
        System.loadLibrary("avdevice-57");
        System.loadLibrary("swscale-4");

    }
    //c语言函数对应的 java函数
    public native String urlprotocolinfo();

    public native String avformatinfo();

    public native String avcodecinfo();

    public native String avfilterinfo();

    public native String configurationinfo();

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

        // Example of a call to a native method
        tv = (TextView) findViewById(R.id.sample_text);
        show_confuretion = (Button) findViewById(R.id.show_configuration);
        show_confuretion.setOnClickListener(this);
        show_decodec = (Button) findViewById(R.id.show_avcodec);
        show_decodec.setOnClickListener(this);
        show_filter = (Button) findViewById(R.id.show_filter);
        show_filter.setOnClickListener(
                this
        );
        show_format = (Button) findViewById(R.id.show_avformat);
        show_format.setOnClickListener(
                this
        );
        show_protocol = (Button) findViewById(R.id.show_protocol);
        show_protocol.setOnClickListener(this);
        decode=findViewById(R.id.decode);
        decode.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.show_avcodec:
                tv.setText(avcodecinfo());
                break;
            case R.id.show_avformat:
                tv.setText(avformatinfo());
                break;
            case R.id.show_configuration:
                tv.setText(configurationinfo());
                break;
            case R.id.show_filter:
                tv.setText(avfilterinfo());
                break;
            case R.id.show_protocol:
                tv.setText(urlprotocolinfo());
                break;
           // case R.id.decode:
           //  startActivity(new Intent(this,DecodecActivity.class));
        }
    }
}

至此,就可以运行查看效果了


效果图.png

现在我们就算临门一脚,后续才是真正的开始学习ffmpeg的过程,其间肯定会有很多沟沟坎坎在等着,会有很多很多南墙等着,学习这回事,不怕晚也不怕笨。

要么学!要么不学!学和不学之间没有中间值, 不学就放弃,学就要去认真的学! -- 致选择

相关文章

网友评论

      本文标题:「ffmpeg」二 Android平台基于ffmpeg的Hell

      本文链接:https://www.haomeiwen.com/subject/zywzdctx.html