FFmpeg4Android:FFmpeg在Android端的使用

3 FFmpeg4Android:FFmpeg在Android端的使用

3.1 編寫Java端代碼

創(chuàng)建HelloFFmpeg項目裳凸,修改MainActivity代碼惨恭,準備調(diào)用C語言函數(shù)闲昭。使用JNI調(diào)用C語言代碼有兩點需要做的步驟:
1)聲明C語言函數(shù)對應(yīng)的Java函數(shù)决采;
2)聲明要加載的類庫栖榨。
需要注意昆汹,C語言函數(shù)的聲明要加上“native”關(guān)鍵字;加載類庫的時候需要使用“System.loadLibrary()”方法婴栽。
例如MainActivity源代碼如下所示:

package com.lzp.helloffmpeg;

import android.app.Activity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends Activity {

    private HelloJNI helloJNI = new HelloJNI();

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

        final TextView libinfoText = findViewById(R.id.text_libinfo);
        libinfoText.setMovementMethod(ScrollingMovementMethod.getInstance());

        libinfoText.setText(helloJNI.configurationinfo());

        Button configurationButton = findViewById(R.id.button_configuration);
        Button urlprotocolButton = findViewById(R.id.button_urlprotocol);
        Button avformatButton = findViewById(R.id.button_avformat);
        Button avcodecButton = findViewById(R.id.button_avcodec);
        Button avfilterButton = findViewById(R.id.button_avfilter);

        urlprotocolButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(helloJNI.urlprotocolinfo());
            }
        });

        avformatButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(helloJNI.avformatinfo());
            }
        });

        avcodecButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(helloJNI.avcodecinfo());
            }
        });

        avfilterButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(helloJNI.avfilterinfo());
            }
        });

        configurationButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View arg0){
                libinfoText.setText(helloJNI.configurationinfo());
            }
        });

    }

    static{
        // ffmpeg的.so庫
        System.loadLibrary("avutil-54");
        System.loadLibrary("swresample-1");
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avformat-56");
        System.loadLibrary("swscale-3");
        System.loadLibrary("postproc-53");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avdevice-56");
        // 自己的.so庫
        System.loadLibrary("helloffmpeg");
    }

}

JNI所在類HelloJNI代碼:

package com.lzp.helloffmpeg;

public class HelloJNI {
    //JNI
    public native String urlprotocolinfo();
    public native String avformatinfo();
    public native String avcodecinfo();
    public native String avfilterinfo();
    public native String configurationinfo();
}

3.2 編寫C語言端代碼

step 1:獲取C語言的接口函數(shù)聲明
根據(jù)Java對于C語言接口的定義满粗,生成相應(yīng)的接口函數(shù)聲明。這一步需要用到JDK中的“javah”命令愚争。首先切換到...\HelloFFmpeg\app\src\main\java文件夾下映皆,輸入如下命令:

javah com.lzp.helloffmpeg.HelloJNI

就可以在當前目錄下生成一個頭文件“com_lzp_helloffmpeg_HelloJNI.h”,該頭文件內(nèi)容如下所示:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lzp_helloffmpeg_HelloJNI */

#ifndef _Included_com_lzp_helloffmpeg_HelloJNI
#define _Included_com_lzp_helloffmpeg_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_lzp_helloffmpeg_HelloJNI
 * Method:    urlprotocolinfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_urlprotocolinfo
  (JNIEnv *, jobject);

/*
 * Class:     com_lzp_helloffmpeg_HelloJNI
 * Method:    avformatinfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_avformatinfo
  (JNIEnv *, jobject);

/*
 * Class:     com_lzp_helloffmpeg_HelloJNI
 * Method:    avcodecinfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_avcodecinfo
  (JNIEnv *, jobject);

/*
 * Class:     com_lzp_helloffmpeg_HelloJNI
 * Method:    avfilterinfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_avfilterinfo
  (JNIEnv *, jobject);

/*
 * Class:     com_lzp_helloffmpeg_HelloJNI
 * Method:    configurationinfo
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_configurationinfo
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

從源代碼可以看出轰枝,JNI調(diào)用的C語言函數(shù)是有固定格式的捅彻,即:
Java_{包名}{包名}…{類名}(JNIEnv *,…)
對于HelloJNI類中的configurationinfo方法,其C語言版本的函數(shù)聲明為:

JNIEXPORT jstring JNICALL Java_com_lzp_helloffmpeg_HelloJNI_configurationinfo
  (JNIEnv *, jobject);

PS:這個頭文件只是一個參考鞍陨,對于JNI來說并不是必須的步淹。也可以根據(jù)命名規(guī)則直接編寫C語言函數(shù)。
實現(xiàn)后的com_lzp_helloffmpeg_HelloJNI.c代碼如不下:

#include <stdio.h>

#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"

//Log
#ifdef ANDROID
#include <jni.h>
#include <android/log.h>
#define LOGE(format, ...)  __android_log_print(ANDROID_LOG_ERROR, "ffmpeg", format, ##__VA_ARGS__)
#else
#define LOGE(format, ...)  printf("ffmpeg" format "\n", ##__VA_ARGS__)
#endif

//FIX
struct URLProtocol;
/**
 * com.lzp.helloffmpeg.HelloJNI.urlprotocolinfo()
 * Protocol Support Information
 */
JNIEXPORT jstring Java_com_lzp_helloffmpeg_HelloJNI_urlprotocolinfo(JNIEnv *env, jobject obj){
    
    char info[40000]={0};
    av_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;
    //Output
    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));
    }

    //LOGE("%s", info);
    return (*env)->NewStringUTF(env, info);
}

/**
 * com.lzp.helloffmpeg.HelloJNI.avformatinfo()
 * AVFormat Support Information
 */
JNIEXPORT jstring Java_com_lzp_helloffmpeg_HelloJNI_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);
}

/**
 * com.lzp.helloffmpeg.HelloJNI.avcodecinfo()
 * AVCodec Support Information
 */
JNIEXPORT jstring Java_com_lzp_helloffmpeg_HelloJNI_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;
    }
    //LOGE("%s", info);

    return (*env)->NewStringUTF(env, info);
}

/**
 * com.lzp.helloffmpeg.HelloJNI.avfilterinfo()
 * AVFilter Support Information
 */
JNIEXPORT jstring Java_com_lzp_helloffmpeg_HelloJNI_avfilterinfo(JNIEnv *env, jobject obj)
{
    char info[40000] = { 0 };
    av_register_all();
    AVFilter *f_temp = (AVFilter *)avfilter_next(NULL);
    while (f_temp != NULL){
        sprintf(info, "%s[%10s]\n", info, f_temp->name);
    }
    //LOGE("%s", info);

    return (*env)->NewStringUTF(env, info);
}

/**
 * com.lzp.helloffmpeg.HelloJNI.urlprotocolinfo()
 * Protocol Support Information
 */
JNIEXPORT jstring Java_com_lzp_helloffmpeg_HelloJNI_configurationinfo(JNIEnv *env, jobject obj)
{
    char info[10000] = { 0 };
    av_register_all();

    sprintf(info, "%s\n", avcodec_configuration());

    //LOGE("%s", info);
    return (*env)->NewStringUTF(env, info);
}

3.3 修改Android.mk诚撵、Application.mk缭裆、build.gradle、local.properties

a) Android.mk:

LOCAL_PATH := $(call my-dir)

# FFmpeg library
include $(CLEAR_VARS)
LOCAL_MODULE := avcodec
LOCAL_SRC_FILES := libavcodec-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avdevice
LOCAL_SRC_FILES := libavdevice-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avfilter
LOCAL_SRC_FILES := libavfilter-5.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avformat
LOCAL_SRC_FILES := libavformat-56.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := avutil
LOCAL_SRC_FILES := libavutil-54.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := postproc
LOCAL_SRC_FILES := libpostproc-53.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swresample
LOCAL_SRC_FILES := libswresample-1.so
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := swscale
LOCAL_SRC_FILES := libswscale-3.so
include $(PREBUILT_SHARED_LIBRARY)

# Program
include $(CLEAR_VARS)
LOCAL_MODULE := helloffmpeg
LOCAL_SRC_FILES :=com_lzp_helloffmpeg_HelloJNI.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
LOCAL_LDLIBS := -llog -lz
LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil postproc swresample swscale
include $(BUILD_SHARED_LIBRARY)

LOCAL_MODULE指定的是所生成的庫寿烟,被引用的名稱澈驼,并非文件名。指定的LOCAL_MODULE對于編譯后的文件名分兩種情況:
1).so生成.so筛武,文件名不變:如ffmpeg中的libavcodec-56.so指定LOCAL_MODULE為avcodec后生成的文件名依然為libavcodec-56.so缝其;
2).c生成.so购桑,文件變?yōu)閘ib+LOCAL_MODULE+.so:如com_lzp_helloffmpeg_HelloJNI.c指定LOCAL_MODULE為helloffmpeg后,生成的文件名為libhelloffmpeg.so氏淑。
但是上述兩種情況的引用名都是一樣的勃蜘,都為其LOCAL_MODULE名。

b) Application.mk:
Application.mk中的APP_ABI設(shè)定了編譯后庫文件支持的指令集假残,默認使用“armeabi”缭贡。在本例子中,APP_ABI取值為“all”辉懒。由于我們編譯的FFmpeg并不在像x86這樣的平臺下運行阳惹,所以不需要“all”,把它修改為“armeabi”或者刪除就可以了(對于本例子眶俩,不做這一步的話會在編譯x86平臺類庫的時候報錯莹汤,但并不影響后面的測試運行)。

#APP_ABI := all
#APP_ABI := armeabi armeabi-v7a x86

APP_ABI :=armeabi

c) build.gradle:

import org.apache.tools.ant.taskdefs.condition.Os

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.1"
    defaultConfig {
        applicationId "com.lzp.helloffmpeg"
        minSdkVersion 15
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    //指定動態(tài)庫路徑
    sourceSets{
        main{
            jni.srcDirs = []    // disable automatic ndk-build call, which ignore our Android.mk
            jniLibs.srcDir 'src/main/libs'
        }
    }

    // call regular ndk-build(.cmd) script from app directory
    task ndkBuild(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd()
        //commandLine 'D:/ndk/android-ndk-r10e/ndk-build.cmd'   //也可以直接使用絕對路徑
    }

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }

    task cleanNative(type: Exec) {
        workingDir file('src/main')
        commandLine getNdkBuildCmd(), 'clean'
    }

    clean.dependsOn cleanNative
}

//獲取NDK目錄路徑
def getNdkDir() {
    if (System.env.ANDROID_NDK_ROOT != null)
        return System.env.ANDROID_NDK_ROOT

    Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    def ndkdir = properties.getProperty('ndk.dir', null)
    if (ndkdir == null)
        throw new GradleException("NDK location not found. Define location with ndk.dir in the local.properties file or with an ANDROID_NDK_ROOT environment variable.")

    return ndkdir
}

//根據(jù)不同系統(tǒng)獲取ndk-build腳本
def getNdkBuildCmd() {
    def ndkbuild = getNdkDir() + "/ndk-build"
    if (Os.isFamily(Os.FAMILY_WINDOWS))
        ndkbuild += ".cmd"

    return ndkbuild
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:26.+'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'
}

d) local.properties:由于我們用的是ffmpeg2.x颠印,要使用低版本NDK纲岭,這里用的是r10e。

#ndk.dir=C\:\\Users\\lizhiping03\\AppData\\Local\\Android\\Sdk\\ndk-bundle
ndk.dir=D\:\\ndk\\android-ndk-r10e

3.4 編譯线罕、運行

點擊Build->Make Project(Ctrl + F9)后止潮,會在根目錄下的“l(fā)ibs/armeabi”目錄中生成相關(guān)的庫文件。本例子中钞楼,會生成以下庫文件:


image.png

運行后的效果如下:


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喇闸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子询件,更是在濱河造成了極大的恐慌燃乍,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宛琅,死亡現(xiàn)場離奇詭異刻蟹,居然都是意外死亡,警方通過查閱死者的電腦和手機夯秃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門座咆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人仓洼,你說我怎么就攤上這事介陶。” “怎么了色建?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵哺呜,是天一觀的道長。 經(jīng)常有香客問我箕戳,道長某残,這世上最難降的妖魔是什么国撵? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮玻墅,結(jié)果婚禮上介牙,老公的妹妹穿的比我還像新娘。我一直安慰自己澳厢,他們只是感情好环础,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剩拢,像睡著了一般线得。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上徐伐,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天贯钩,我揣著相機與錄音,去河邊找鬼办素。 笑死角雷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的摸屠。 我是一名探鬼主播谓罗,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼季二!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起揭措,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤胯舷,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后绊含,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桑嘶,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年躬充,在試婚紗的時候發(fā)現(xiàn)自己被綠了逃顶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡充甚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馅扣,我是刑警寧澤兄纺,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站技矮,受9級特大地震影響抖誉,放射性物質(zhì)發(fā)生泄漏殊轴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一袒炉、第九天 我趴在偏房一處隱蔽的房頂上張望旁理。 院中可真熱鬧,春花似錦我磁、人聲如沸孽文。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叛溢。三九已至,卻和暖如春劲适,著一層夾襖步出監(jiān)牢的瞬間楷掉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工霞势, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留烹植,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓愕贡,卻偏偏與公主長得像草雕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子固以,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容