首先上一个图交代一下我的文件目录 :
需要说明的几点:
1.首先在Androidstudio上创建一个支持c++的项目。
2.我用的ffmpeg编译是那种编译成一个so文件的方式,不是那种对各个模块分别生成一个so的方式。
3.libffmpeg.so放在libs armeabi-v7a目录下,这是在app 的gradle里配置的。涉及到这两个配置
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
ndk{
abiFilters "armeabi-v7a"
}
这个libffmpeg只能放在armeabi-v7a目录下,不能再建子目录(我之前踩坑了,创建了一个子目录,老是提示找不到so文件)
4. .so包里并不包含头文件,有头文件的需要单独拿出来(include里放的就是ffmpeg的头文件)。我之前把include放在armeabi-v7a目录下,一直提示找不到头文件。后面发现要放你写的jni的cpp文件同一个目录下。然后还要加一些配置。
#包含头文件
target_include_directories(ffmpegPlayer PRIVATE ${CMAKE_SOURCE_DIR}/src/main/cpp/include)
5.重点就是CmakeList.txt的配置,如下:
cmake_minimum_required(VERSION 3.4.1)
#判断编译器类型,如果是gcc编译器,则在编译选项中加入c++11支持
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")
message(STATUS "optional:-std=c++11")
endif(CMAKE_COMPILER_IS_GNUCXX)
#添加jni的so
add_library( ffmpegPlayer
SHARED
src/main/cpp/ffmpegPlayer.cpp )
#查找log-lib的so,系统的库可以通过find来添加
find_library(log-lib log )
#声明libffmpeg
add_library(ffmpeg SHARED IMPORTED)
#给ffmpeg添加具体的so,在build.gradle中ndk模块中添加下面的代码:abiFilters 'armeabi-v7a'
#注意armeabi-v7a下面不能再添加目录,so直接放在里面
set_target_properties(ffmpeg
PROPERTIES IMPORTED_LOCATION
#CMAKE_CURRENT_SOURCE_DIR 这个变量是系统自定义的,表示CMakeLists.txt文件的绝对路径
${CMAKE_CURRENT_SOURCE_DIR}/libs/armeabi-v7a/libffmpeg.so
)
#包含头文件
target_include_directories(ffmpegPlayer PRIVATE ${CMAKE_SOURCE_DIR}/src/main/cpp/include)
#最后将上面准备好的so链接到一起去
target_link_libraries( ffmpegPlayer
ffmpeg
${log-lib} )
5.需要交代的是,我们可以直接通过CmakeList 的这个配置
add_library( ffmpegPlayer
SHARED
src/main/cpp/ffmpegPlayer.cpp )
直接运行,就能生成libffmpegPlayer.so 文件。我一直有个疑问,为什么不能直接这样编译出ffmepg的so文件呢?我不知道确实原因,我想应该是ffmpeg项目里的代码比较多,而且有封复杂的依赖关系,不适合通过AndroidStudio直接编译。
下面看看我的jni文件 ffmpegPlayer.cpp 这就是一个中介,java通过这个入口文件来调用ffmpeg里编码、编码、封包、解包、解码、YUV to RGBA的转换的功能,当然这是后话,我现在仅仅通过这个入口文件调用一个ffmpeg各个模块的最基础的方法,只是为了证明确实调到了ffmpeg。
代码如下:
#include <jni.h>
#include <string>
#include <stdio.h>
#include <time.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavutil/log.h"
#ifdef __cplusplus
};
#endif
#ifdef ANDROID
#include <android/log.h>
#define LOG_TAG "FFmpegHello"
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, format, ##__VA_ARGS__)
#else
#define LOGE(format, ...) printf(LOG_TAG format "\n", ##__VA_ARGS__)
#define LOGI(format, ...) printf(LOG_TAG format "\n", ##__VA_ARGS__)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
JNIEXPORT jstring JNICALL
Java_com_example_ffmpegplayerone_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
// 被extern “C”修饰的函数或者变量是按照C语言方式编译和链接的,所以可以用一句话来概括extern “C”的真实目的是实现C++与C的混合编程。
JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_MainActivity_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(info);
}
JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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(info);
}
JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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;
}
LOGE("%s", info);
return env->NewStringUTF(info);
}
JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_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[%s]\n", info, f_temp->name);
}
LOGE("%s", info);
return env->NewStringUTF(info);
}
JNIEXPORT jstring JNICALL Java_com_example_ffmpegplayerone_MainActivity_configurationinfo
(JNIEnv * env, jobject obj){
char info[10000] = { 0 };
av_register_all();
sprintf(info, "%s\n", avcodec_configuration());
LOGE("%s", info);
return env->NewStringUTF(info);
}
#ifdef __cplusplus
}
#endif
java调调用的代码如下:
package com.example.ffmpegplayerone;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("ffmpegPlayer");
}
private TextView tv_protocol_info;
private TextView tv_format_info;
private TextView tv_codec_info;
private TextView tv_filter_info;
private TextView tv_configuration_info;
private TextView tv_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
findViewById(R.id.tv_protocol_info).setOnClickListener(this);
findViewById(R.id.tv_format_info).setOnClickListener(this);
findViewById(R.id.tv_codec_info).setOnClickListener(this);
findViewById(R.id.tv_filter_info).setOnClickListener(this);
findViewById(R.id.tv_configuration_info).setOnClickListener(this);
tv_content = findViewById(R.id.tv_content);
}
/**
* 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 urlprotocolinfo();
public native String avformatinfo();
public native String avcodecinfo();
public native String avfilterinfo();
public native String configurationinfo();
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.tv_protocol_info:
tv_content.setText(urlprotocolinfo());
break;
case R.id.tv_codec_info:
tv_content.setText(avcodecinfo());
break;
case R.id.tv_filter_info:
tv_content.setText(avfilterinfo());
break;
case R.id.tv_format_info:
tv_content.setText(avformatinfo());
break;
case R.id.tv_configuration_info:
tv_content.setText(configurationinfo());
break;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/sample_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello World!" />
<TextView
android:id="@+id/tv_protocol_info"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_margin="5dip"
android:background="#ff4163"
android:gravity="center"
android:text="Protocol信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_format_info"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_margin="5dip"
android:background="#ff4163"
android:gravity="center"
android:text="format信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_codec_info"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_margin="5dip"
android:background="#ff4163"
android:gravity="center"
android:text="codec信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_filter_info"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_margin="5dip"
android:background="#ff4163"
android:gravity="center"
android:text="filter信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_configuration_info"
android:layout_width="match_parent"
android:layout_height="40dip"
android:layout_margin="5dip"
android:background="#ff4163"
android:gravity="center"
android:text="Configuration信息"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
运行效果如下图:
其中filter那个jni方法报错,没查出来什么原因。
下一步通过jni调用一下ffmpeg音视频相关的方法。