美文网首页Android FFMPEGFFmpeg音视频开发音视频
《Android音视频系列-3》Android项目中使用FFmp

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

作者: 蓝师傅_Android | 来源:发表于2019-06-20 00:30 被阅读224次

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

    相关文章

      网友评论

        本文标题:《Android音视频系列-3》Android项目中使用FFmp

        本文链接:https://www.haomeiwen.com/subject/ljhifctx.html