《Android音视频系列-3》Android项目中使用FFmp
上一篇已经编译出FFmpeg动态库,这一篇就来介绍如何在Android项目中使用动态库。
新建一个项目,在Android Studio 3.4.1中发现新建项目发生了一些改变
如果是要support c++,那么应该选Native c++,然后一路next,发现AS帮我们把cmake配置好了,cmake是c++代码构建工具,以前的老项目可能还是用mk的构建方式,我们要与时俱进,用cmake准没错。
我电脑已经装好cmake工具了,如果没有装的打开setting->sdk 进行打钩下载
LLDB是c++调试需要的
CMake 不要用最新版,会报错
然后这样就可以编译跑到手机上了,这一步就不演示了。
直接进入正题
一、拷贝动态库
怎么把FFmpeg的几个so搞进去
1、新建jniLibs目录,把上一篇编译成功的FFmpeg 的8个so放到armeabi目录,把头文件include目录拷贝到jniLibs目录下
然后需要修改CMakeLists.txt,为了看起来清晰点,先把CMakeLists.txt移动到app目录下,(默认是在app/src/main/cpp),然后修改一下app下的build.gradle
android {
compileSdkVersion 28
buildToolsVersion = '28.0.3'
defaultConfig {
applicationId "com.lanshifu.ffmpegdemo"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-fexceptions" //使用的c++标准
abiFilters "armeabi"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt" //CMakeLists 配置文件路径
}
}
}
就是修改 externalNativeBuild ,有注释的两个地方
进入下一步
二、修改MakeLists.txt
直接加注释贴上来吧
# cmake参考 https://www.jianshu.com/p/528eeb266f83
cmake_minimum_required(VERSION 3.4.1)
# 需要引入我们头文件,以这个配置的目录为基准
include_directories(src/main/jniLibs/include)
# 添加共享库(so)搜索路径
LINK_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi)
# 指定源文件目录,把当前工程目录下的 src/main/cpp 目录的下的所有 .cpp 和 .c 文件赋值给 SRC_LIST
AUX_SOURCE_DIRECTORY(${CMAKE_SOURCE_DIR}/src/main/cpp SRC_LIST)
#这个一般不用怎么改,最终把cpp编译成共享库 native-lib
add_library(
# 编译生成的库的名称叫native-lib,对应System.loadLibrary("native-lib");
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${SRC_LIST}
)
#为native-lib 链接额外的库,例如ffmpeg、ndk库
target_link_libraries(
native-lib
# 编解码(最重要的库)
avcodec-57
# 设备信息
avdevice-57
# 滤镜特效处理库
avfilter-6
# 封装格式处理库
avformat-57
# 工具库(大部分库都需要这个库的支持)
avutil-55
# 后期处理
postproc-54
# 音频采样数据格式转换库
swresample-2
# 视频像素数据格式转换
swscale-4
# 链接 android ndk 自带的一些库
android
# Links the target library to the log library
# included in the NDK.
log)
当然,这些是最简单的,实际项目应该会很复杂。
然后直接run,如果没有报错,说明FFmpeg动态库已经集成到Android项目里了
验证一下:
使用 APK Analyzer查看。
选择 Build > Analyze APK > 选择apk打开
发现里so已经打包进去了
然后就可以通过JNI来调用FFmpeg了
三、来个简单的例子
一个mp3文件,使用FFmpeg来获取音频信息
1、在sd卡中放一个 input1.mp3
2、Activity中处理,读取sd卡动态权限就不贴了
File mMusicFile = new File(Environment.getExternalStorageDirectory(), "input1.mp3");
//onCreate调用
printAudioInfo(mMusicFile.getAbsolutePath());
public native void printAudioInfo(String url);
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
printAudioInfo 是 native方法,按option+enter,自动生成对应c++方法
extern "C"
JNIEXPORT void JNICALL
Java_com_lanshifu_ffmpegdemo_MainActivity_printAudioInfo(JNIEnv *env,jobject instance,jstring url_) {
}
用cmake 的好处之一就是c++方法可以自动生成,不用什么javah获取头文件,然后复制啥的...
上代码吧
#include <jni.h>
#include <string>
#include <android/log.h>
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
extern "C"
JNIEXPORT void JNICALL
Java_com_lanshifu_ffmpegdemo_MainActivity_printAudioInfo(JNIEnv *env, jobject instance,
jstring url_) {
const char *url = env->GetStringUTFChars(url_, 0);
//1、初始化所有组件,只有调用了该函数,才能使用复用器和编解码器(源码)
av_register_all();
AVFormatContext *avFormatContext = NULL;
int audio_stream_idx;
AVStream *audio_stream;
//读文件头,对 mp4 文件而言,它会解析所有的 box。但它知识把读到的结果保存在对应的数据结构下。
// 这个时候,AVStream 中的很多字段都是空白的。
int open_res = avformat_open_input(&avFormatContext, url, NULL, NULL);
if (open_res != 0) {
LOGE("lxb->Can't open file: %s", av_err2str(open_res));
return ;
}
//获取文件信息
//读取一部分视音频数据并且获得一些相关的信息,会检测一些重要字段,如果是空白的,就设法填充它们。
// 因为我们解析文件头的时候,已经掌握了大量的信息,avformat_find_stream_info 就是通过这些信息来填充自己的成员,
// 当重要的成员都填充完毕后,该函数就返回了。这中情况下,该函数效率很高。但对于某些文件,单纯的从文件头中获取信息是不够的,
// 比如 video 的 pix_fmt 是需要调用 h264_decode_frame 才可以获取其pix_fmt的。
int find_stream_info_res = avformat_find_stream_info(avFormatContext, NULL);
if (find_stream_info_res < 0) {
LOGE("lxb->Find stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
// 获取采样率和通道
//av_find_best_stream:获取音视频及字幕的 stream_index , 以前没有这个函数时,我们一般都是写的 for 循环。
audio_stream_idx = av_find_best_stream(avFormatContext, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx < 0) {
LOGE("lxb->Find audio stream info error: %s", av_err2str(find_stream_info_res));
goto __avformat_close;
}
audio_stream = avFormatContext->streams[audio_stream_idx];
LOGE("采样率:%d", audio_stream->codecpar->sample_rate);
LOGE("通道数: %d", audio_stream->codecpar->channels);
LOGE("format: %d", audio_stream->codecpar->format);
LOGE("extradata_size: %d", audio_stream->codecpar->extradata_size);
__avformat_close:
avformat_close_input(&avFormatContext);
}
解码流程参考
解码流程
FFmpeg API说明:
1:av_register_all() 初始化所有组件,只有调用了该函数,才能使用复用器和编解码器
2:avformat_open_input:读文件头,对 mp4 文件而言,它会解析所有的 box。但它只是把读到的结果保存在对应的数据结构下
3:avformat_find_stream_info:读取一部分视音频数据并且获得一些相关的信息。因为我们解析文件头的时候,已经掌握了大量的信息,avformat_find_stream_info 就是通过这些信息来填充自己的成员
4:av_find_best_stream:获取音视频及字幕的 stream_index , 以前没有这个函数时,我们一般都是写的 for 循环
5:AVStream中保存了音视频文件的一堆信息,在内部AVCodecParameters 结构体中
然后run一下,可以看到打印
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: 采样率:44100
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: 通道数: 2
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: format: 6
06-19 21:36:57.732 29875-29875/? E/JNI_TAG: extradata_size: 0
也就说明已经成功在项目中使用FFmpeg了。