引言
本ffmpeg系列文章可以看作是基于雷神的博客的学习实践,由于雷神所使用的ffmpeg是老版本,一些地方我也会根据新版本的API做了一些更新,另外改为了Cmake构建方式,这也是区别于雷神博客的地方。
书接上回,经过一通操作,我们成功编译了ffmpeg源码并生成了so库文件。本文就讲解一下如何集成so库文件到Android项目并调用其API打印一些库的相关信息。
准备
创建一个Android DNK项目,项目建成后,会默认生成一个入口MainActivity以及cpp源码native-lib.c,这部分之前文章有讲过,不了解的可以移步CMake构建NDK项目生成so库
最终我们得到了如下
之后我们就开始着手迁移和改造工作了。
迁移
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的过程,其间肯定会有很多沟沟坎坎在等着,会有很多很多南墙等着,学习这回事,不怕晚也不怕笨。
网友评论