《Android音视频系列-3》Android项目中使用FFmpeg

上一篇已经编译出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了。

参考:https://www.jianshu.com/p/d8300535bbf0

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,511评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,495评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,595评论 0 225
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,558评论 0 190
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,715评论 3 270
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,672评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,112评论 2 291
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,837评论 0 181
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,417评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,928评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,316评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,773评论 2 234
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,253评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,827评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,440评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,523评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,583评论 2 249

推荐阅读更多精彩内容