Android编译调用FFmpeg API,自己写方法,编译so库

t0124cf926ea9205e45

作者:谭东

时间:2017年9月19日

环境:Windows 8.1专业版

NDK版本:android-ndk-r14b

FFmpeg版本:FFmpeg 3.0.2 “Einstein”


我这里使用的Android Studio为2.3.3版本。

大名鼎鼎的FFmpeg可以说全球出名,基本上视频处理都是它,但是使用FFmpeg的技术门槛和难度也相对要求高一些。

那么我就先给大家简单引用别人的描述简单介绍下FFmpeg吧:

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。

FFmpeg被许多开源项目采用,比如ffmpeg2theora,VLC, MPlayer, HandBrake, Blender, Google Chrome等。还有DirectShow/VFW的ffdshow(external project)和QuickTime的Perian (external project)也采用了FFmpeg。
由于FFmpeg是在LGPL/GPL协议下发布的(如果使用了其中一些使用GPL协议发布的模块则必须使用GPL协议),任何人都可以自由使用,但必须严格遵守LGPL/GPL协议。有很多播放软件都使用了FFmpeg的代码,但它们并没有遵守LGPL/GPL协议,没有公开任何源代码。我们应该对这种侵权行为表示耻辱。
2009年加入FFmpeg耻辱黑名单的播放软件:暴风影音、QQ影音、KMP、GOM Player、PotPlayer(2010)都在其列。
2009年2月,韩国名软KMPlayer被FFmpeg开源项目发现使用了它们的代码和二进制文件,但是没有按照规定/惯例开放相应说明/源码。因此被人举报,进入了FFmpeg官网上的耻辱黑名单。

2009年11月,网友roo_zhou向FFmpeg举报,指出QQ影音的credit只给出了修改的FFmpeg源码下载,声称是LGPL许可证。但实际是修改过的ffdshow,采用的是GPL许可证,之后QQ影音被正式加入到FFmpeg耻辱名单之列。
Libav项目启动之后,FFmpeg官方版本也仍然在一直维护中。FFmpeg与libav属于独立的两个项目。

-------------------------------------------------------------------------------------------------------------------------

就先介绍到这里。

FFmpeg大致有两种使用方式,第一种是命令行,这个最简单也最直接。不过这个比较适合Linux下,移动平台和Windows下还是需要单独编译源码,自己写C方法调用FFmpeg的API,然后编译成库,例如Android平台要编译成so库。这样难度和门槛就加大了,你要读懂和了解FFmpeg的C方法,C源码和API文档。其实很多用法可以看下官方英文文档,官方也有很多参考资料。我这里以Android平台为例,但是大同小异,毕竟方法都是C++的方法,IOS、Linux、Windows都通用的。

Android编译调用FFmpeg API,自己写方法,编译so库_第1张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第2张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第3张图片


好了。接下来给大家讲解下编译和调用FFmpeg的C的API。


首先肯定是先要去FFmpeg官网下载最新的源码,然后在Linux下进行编译了。Android平台需要编译so库。

官网源码下载地址:http://ffmpeg.org/download.html

我之前写过一篇文章,讲解如何编译FFmpeg的。

Ubuntu下编译Android版本的ffmepg so库及源码
以及
Ubuntu编译调用FFmpeg so库Api方法例子
具体如何编译我就不说了,编译好后,进行下面的步骤。

1、Android Studio新建支持C++的项目。勾选Include C++ support。

Android编译调用FFmpeg API,自己写方法,编译so库_第4张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第5张图片


2、新建好的项目大致结构这样。主要关注下图里红色的几个部分。

Android编译调用FFmpeg API,自己写方法,编译so库_第6张图片


3.、接下来在项目的src下的main目录新建jniLibs目录,再新建对应的架构平台的文件夹名,然后把so库放进去。我这里只编译了armeabi平台的so库,所以结构是下图这样的。

Android编译调用FFmpeg API,自己写方法,编译so库_第7张图片


4、把FFmpeg编译后的源码.h头文件拷贝到项目的libs目录里。

Android编译调用FFmpeg API,自己写方法,编译so库_第8张图片


5、可以大致看下里面的,h里的方法和定义。

Android编译调用FFmpeg API,自己写方法,编译so库_第9张图片


6、接下来就是编写配置我们的CMakeLists.txt了。下面是我写好的配置,大家可以参考。

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

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)

add_library( avutil-55
             SHARED
             IMPORTED )
set_target_properties( avutil-55
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libavutil-55.so )

add_library( swresample-2
             SHARED
             IMPORTED )
set_target_properties( swresample-2
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libswresample-2.so )
add_library( avcodec-57
             SHARED
             IMPORTED )
set_target_properties( avcodec-57
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libavcodec-57.so )
add_library( avfilter-6
             SHARED
             IMPORTED)
set_target_properties( avfilter-6
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libavfilter-6.so )
add_library( swscale-4
             SHARED
             IMPORTED)
set_target_properties( swscale-4
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libswscale-4.so )
add_library( avdevice-57
             SHARED
             IMPORTED)
set_target_properties( avdevice-57
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/libavdevice-57.so )
add_library( avformat-57
             SHARED
             IMPORTED)
set_target_properties( avformat-57
                       PROPERTIES IMPORTED_LOCATION
                       ../../../../src/main/jniLibs/armeabi/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(libs/include)

#target_include_directories(native-lib PRIVATE libs/include)

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


7、接下来我们看下默认生成的cpp文件夹里的native-lib.cpp的里面的C方法例子规范。

#include 
#include 

extern "C" 

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

很简单对不对。我们可以按照这个规范写我们的其他测试和调用FFmpeg的API的C++方法了。

#include 
#include 

extern "C" {
#include 
#include 
#include 

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

JNIEXPORT jstring JNICALL
Java_com_tandong_testffmpeg_MainActivity_stringFromJNI2(
        JNIEnv *env,
        jobject /* this */) {
    return env->NewStringUTF("测试");
}

/**
 * 获取FFmpeg支持的协议格式
 */
JNIEXPORT jstring JNICALL
Java_com_tandong_testffmpeg_MainActivity_getUrlProtocolInfo(
        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);
}

/**
 * 获取avcodec的库的版本号
 */
JNIEXPORT jstring JNICALL
Java_com_tandong_testffmpeg_MainActivity_getAvCodecVersion(
        JNIEnv *env, jobject) {
    char version[50];
    avcodec_register_all();
    sprintf(version, "%d", avcodec_version());
    return env->NewStringUTF(version);
}

/**
 * 获取avformat的库的协议
 */
JNIEXPORT jstring JNICALL
Java_com_tandong_testffmpeg_MainActivity_getAvFormatLicense(
        JNIEnv *env, jobject) {
    std::string license = avformat_license();
    return env->NewStringUTF(license.c_str());
}

}

这些是我写的几个调用API的方法,这些都是很简单的,后面有很多复杂的功能,写法就会更加的复杂,难度也会更大。

我们可以根据它的.h头文件或者官方源码文档,查看这个方法传入的参数含义和类型以及返回的类型及作用。

Android编译调用FFmpeg API,自己写方法,编译so库_第10张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第11张图片

下面是官方文档查看及源码。

Android编译调用FFmpeg API,自己写方法,编译so库_第12张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第13张图片


Android编译调用FFmpeg API,自己写方法,编译so库_第14张图片


8、紧接着,我们编写我们的JAVA来调用JNI的方法。

package com.tandong.testffmpeg;

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

public class MainActivity extends AppCompatActivity {

    @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(getUrlProtocolInfo());
    }

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

    public native String stringFromJNI2();

    public native String getUrlProtocolInfo();

    public native String getAvCodecVersion();

    public native String getAvFormatLicense();

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
}

这个就很简单了,就不给大家解释了。

9、最后就是编译打包运行了,编译运行后,就会生成我们的so库。

Android编译调用FFmpeg API,自己写方法,编译so库_第15张图片

Android编译调用FFmpeg API,自己写方法,编译so库_第16张图片


调用编译就先给大家讲解到这里,请继续关注


版权所有,尊重版权。
微信公众号:


你可能感兴趣的:(Android)