前言:百度说,FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件)。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的。

  说白了ffmpeg就是一个编解码库,我们可以使用android 提供的ndk编译其源码,网上很多资料,这里不是我们考虑的重点。我们的任务是将使用ndk编译好的so文件加入到我们的andriod apk中,让android的apk能够使用ffmpeg提供的音视频编解码功能。


闲话少说,我们开始吧。

一、在你的android项目src下创建包com.xx. 并在其中新建一个类叫做FFmpegOperater.java.  内容如下:

   

package com.xx;

public class FFmpegOperater {
	
	private static FFmpegOperater mFFmpegOperater;
	
	private FFmpegOperater(){
		
	}
	
	public static FFmpegOperater newInstance(){
		if(mFFmpegOperater == null){
			mFFmpegOperater = new FFmpegOperater();
		}
		return mFFmpegOperater;
	}
	
	public native int readVersion();//本地方法,在此使用native 关键字声明,在C中实现
	
}



二、生成头文件,此文件包含FFmpegOperater.java中 native 方法的声明(C中)

打开命令行,切换目录到项目的bin/classes/目录下。输入javah -classpath  .  -jni com.xx.FFmpegOperater 然后回车。会在当前目录下生成一个com_xx_FFmpegOperater.h文件。这个文件要copy到jni目录下,下文讲解。

android视频流解码 安卓视频解码包_FFmpeg


打开此文件是如下内容

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

#ifndef _Included_com_xx_FFmpegOperater
#define _Included_com_xx_FFmpegOperater
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xx_FFmpegOperater
 * Method:    readVersion
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_com_xx_FFmpegOperater_readVersion
  (JNIEnv *, jobject); //java 中声明的native方法。

#ifdef __cplusplus
}
#endif
#endif


三、创建jni(java native interface)

   在项目根目录下创建jni文件夹。此文件夹用于存放本地代码,和我们要加入的第三方库libffmpeg.so.将如下内容加入到jni目录下

com_xx_FFmpegOperater.c   -----------> 位于java 与C之间,功能是调用ffmpeg提供的函数,并将反回结果通过jni传给java

com_xx_FFmpegOperater.h    --------->上文中生成的com_xx_FFmpegOperater.h,将此文件放在jni目录下

         Android.mk            -----------> android 提供的makefile。定义编译规则,根据这个规则可以将libffmpeg.so编译到我们的.c 文件中,并生成我们自己的 .so文件供java调用。       

        libs 文件夹 -----------------> 下面放置libffmpeg.so 。

        include文件夹    -------------------->  此文件夹下面是ffmpeg的所有头文件,要调用libffmpeg.so中的函数,必须包含相应的头文件。去ffmpeg的官网可以下载到。

结构图:

android视频流解码 安卓视频解码包_FFmpeg_02


依次详细讲解这些内容:

com_xx_FFmpegOperater.c     承上启下的作用。


#include "com_xx_FFmpegOperater.h"
#include "libavutil/avutil.h"
#include <android/log.h>


#define LOG_TAG "ffmpeg"
#define LOGW(a)  __android_log_write(ANDROID_LOG_INFO,LOG_TAG,a)

JNIEXPORT jint JNICALL Java_com_xx_FFmpegOperater_readVersion (JNIEnv *env, jobject obj)
{
	LOGW(avutil_license());
	return (int)avutil_version();
}




Android.mk  定义ndk的编译规则,内容如下:


LOCAL_PATH := $(call my-dir) #表示将当前目录的文件路径,即此Android.mk所在的路径赋值给LOCAL_PATH变量

#编译libffmpeg.so 其实就是告诉ndk,它是一个第三方库
include $(CLEAR_VARS)  
LOCAL_SRC_FILES := libs/libffmpeg.so  #指定libffmpeg.so的路径
LOCAL_MODULE :=ffmpeg
include $(PREBUILT_SHARED_LIBRARY) 

#编译我们的C文件,并将libffmpeg链接过来
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE    := myffmpeg
LOCAL_SRC_FILES := com_xx_FFmpegOperater.c
LOCAL_C_INCLUDES := \           #这一行定义所有使用的头文件的路径
	$(JNI_H_INCLUDE) \
	$(LOCAL_PATH)/include/ 
LOCAL_LDLIBS +=-L$(LOCAL_PATH) -lm -llog 
#要使用的其他库,其中包括libffmpeg
LOCAL_SHARED_LIBRARIES := liblog libcutils libffmpeg 
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))




四、编译

打开终端,切换目录到jni下。输入 ndk-build,回车编译成功。前提你要配置好ndk. 具体配置,请自行百度

编译完成如果没有错误,在刷新一下项目,会自动生成两个文件夹,obj和libs。

obj目录存放的是在编译过程中生成的中间文件。libs文件夹下面是我们要在java里面lode的库文件,这些文件将会在apk被安装是放在系统路径的/data/data/com.xx/libs/目录下。java在载入时就会从这里找。


五、回到FFmpegOperater.java中,在此类中添加静态代码块来load   生成的那两个库文件,libffmpeg.so和libmyffmpeg.so

static {
		System.loadLibrary("ffmpeg");  //注意,此处要使用“ffmpeg”,而不是“libffmpeg”。这是规定。
		System.loadLibrary("myffmpeg");
	}




六、调用

 至此,就可以调用你的native 方法了,在java中。


package com.xx;

import android.app.Activity;
import android.os.Bundle;

public class FFmpegDemoActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        int v = FFmpegOperater.newInstance().readVersion();
        
        System.out.println(v);
    }
}





七、总结:

   我们的目的是在java中调用第三方C库里的函数,所以必须通过jni,将C库链接到我们自己的C代码中,在我自己的C代码中调用第三方C库提供的函数。然后通过jni,将我们的C代码与java代码串起来。


另外,libffmpeg.so是从源码中编译而来的,所以要使用与其相同版本的头文件。