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)的庫文件。本例子中钞楼,會生成以下庫文件:
運行后的效果如下: