Android Studio中使用FFMPEG(CMake)

来源:互联网 发布:java线程池使用实例 编辑:程序博客网 时间:2024/05/11 04:26

本文是转载的,不过根据我当前的例子做了下调整

1.下载 FFmpeg 源码
git clone https://git.ffmpeg.org/ffmpeg.git
这一步可能会花比较长的时间,我是在Mac电脑下编译的,Windows下编译我不是很清楚。

2.编译 FFmpeg for Android
2.1.修改 FFmpeg 的 configure
由于FFMPEG默认编译出来的动态库文件名的版本号在.so之后(例如“libavcodec.so.5.100.1”),但是android平台不能识别这样文件名,所以我们需要修改FFMPEG生成的动态库的文件名。

打开 configure 文件,找到:

SLIBNAME_WITH_MAJOR='$(SLIBNAME).$(LIBMAJOR)'  LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'  SLIB_INSTALL_NAME='$(SLIBNAME_WITH_VERSION)'  SLIB_INSTALL_LINKS='$(SLIBNAME_WITH_MAJOR)$(SLIBNAME)'

修改为

SLIBNAME_WITH_MAJOR='$(SLIBPREF)$(FULLNAME)-$(LIBMAJOR)$(SLIBSUF)'  LIB_INSTALL_EXTRA_CMD='$$(RANLIB)"$(LIBDIR)/$(LIBNAME)"'  SLIB_INSTALL_NAME='$(SLIBNAME_WITH_MAJOR)'  SLIB_INSTALL_LINKS='$(SLIBNAME)'

2.2.编写 Android 编译脚本

  • 注意这个脚本网上非常多,而且ffmpeg的版本也不一样(我使用3.3版本就编译出来的不是.so文件),所以你会发现也不一样,这个脚本也不是很懂照葫芦画瓢而已,有问题的话还是学学脚本吧。具体的编译文档请参考文章后面的连接。
#!/bin/shNDK=/Users/Shared/Development/android_sdk/ndk-bundleSYSROOT=$NDK/platforms/android-21/arch-armTOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64function build_one{./configure \--prefix=$PREFIX \--enable-shared \--disable-static \--disable-doc \--disable-ffmpeg \--disable-ffplay \--disable-ffprobe \--disable-ffserver \--disable-avdevice \--disable-doc \--disable-symver \--cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \--target-os=linux \--arch=arm \--enable-cross-compile \--sysroot=$SYSROOT \--extra-cflags="-Os -fpic $ADDI_CFLAGS" \--extra-ldflags="$ADDI_LDFLAGS" \$ADDITIONAL_CONFIGURE_FLAGmake cleanmakemake install}CPU=armPREFIX=$(pwd)/android/$CPUADDI_CFLAGS="-marm"build_one

2.3.编译
执行上面的脚本编译出我们需要的动态库

./build_android.sh
进入android/$CPU目录可以看到生成的动态库和我们需要的头文件

复制代码
.
└── arm
├── include
│ ├── libavcodec
│ ├── libavfilter
│ ├── libavformat
│ ├── libavutil
│ ├── libswresample
│ └── libswscale
└── lib
├── libavcodec-57.so
├── libavcodec.so -> libavcodec-57.so
├── libavfilter-6.so
├── libavfilter.so -> libavfilter-6.so
├── libavformat-57.so
├── libavformat.so -> libavformat-57.so
├── libavutil-55.so
├── libavutil.so -> libavutil-55.so
├── libswresample-2.so
├── libswresample.so -> libswresample-2.so
├── libswscale-4.so
├── libswscale.so -> libswscale-4.so
└── pkgconfig
复制代码
3.将上一步生成的头文件和库文件导入到Android Studio工程中
首先新建一个工程,并且勾选 Include C++ Support 即可得到一个基于CMake的模板工程。目录结构如下所示

.
├── app
│ ├── app.iml
│ ├── build
│ │ ├── generated
│ │ │ ├── res
│ │ │ └── source
│ │ ├── intermediates
│ │ │ ├── blame
│ │ │ ├── incremental
│ │ │ ├── manifest
│ │ │ ├── manifests
│ │ │ ├── res
│ │ │ ├── rs
│ │ │ └── symbols
│ │ └── outputs
│ │ └── logs
│ ├── build.gradle
│ ├── CMakeLists.txt
│ ├── CMakeLists.txt~
│ ├── libs
│ │ ├── armeabi
│ │ │ ├── libavcodec-57.so
│ │ │ ├── libavfilter-6.so
│ │ │ ├── libavformat-57.so
│ │ │ ├── libavutil-55.so
│ │ │ ├── libswresample-2.so
│ │ │ └── libswscale-4.so
│ │ └── include
│ │ ├── libavcodec
│ │ ├── libavfilter
│ │ ├── libavformat
│ │ ├── libavutil
│ │ ├── libswresample
│ │ └── libswscale
│ ├── proguard-rules.pro
│ └── src
│ ├── androidTest
│ │ └── java
│ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── cpp
│ │ ├── java
│ │ └── res
│ └── test
│ └── java
├── build
│ ├── android-profile
│ │ └── profile-2017-03-31-23-04-31-347.rawproto
│ └── generated
│ └── mockable-android-25.jar
├── build.gradle
├── FFMPEGTest.iml
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── local.properties
└── settings.gradle

然后将上面编译FFMPEG生成的头文件和动态库拷贝到app/libs目录下,拷贝完后的目录结构如下所示

├── app
│ ├── libs
│ │ ├── armeabi
│ │ │ ├── libavcodec-57.so
│ │ │ ├── libavfilter-6.so
│ │ │ ├── libavformat-57.so
│ │ │ ├── libavutil-55.so
│ │ │ ├── libswresample-2.so
│ │ │ └── libswscale-4.so
│ │ └── include
│ │ ├── libavcodec
│ │ ├── libavfilter
│ │ ├── libavformat
│ │ ├── libavutil
│ │ ├── libswresample
│ │ └── libswscale
│ ├── proguard-rules.pro
│ └── src
这样还没完,我当时就是这样直接去编译,然后就踩了一个大坑,APP启动之后一直crash,原因就是没有找到我们在java文件里load的动态库。为什么呢?原因是在编译的时候,我们根本没有将我们的动态库打包到APP中,我们还需要修改app/build.gradle将我们放在libs目录下的动态库打包到APP中去

apply plugin: 'com.android.application'android {    compileSdkVersion 25    buildToolsVersion "25.0.2"    defaultConfig {        applicationId "com.arshowbaby.ffmpeg"        minSdkVersion 21        targetSdkVersion 25        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        externalNativeBuild {            cmake {                cppFlags "-frtti -fexceptions"            }            ndk {                abiFilters "armeabi"            }        }    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }    externalNativeBuild {        cmake {            path "CMakeLists.txt"        }    }    sourceSets {        main {            jniLibs.srcDirs = ["libs"]        }    }}dependencies {    compile fileTree(dir: 'libs', include: ['*.jar'])    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {        exclude group: 'com.android.support', module: 'support-annotations'    })    compile 'com.android.support:appcompat-v7:25.3.1'    compile 'com.android.support.constraint:constraint-layout:1.0.2'    compile 'com.android.support:design:25.3.1'    testCompile 'junit:junit:4.12'}

紧接着就是来编写我们的CMakeLists.txt文件来编译我们的动态库和native源文件了

# 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_minimum_required(VERSION 3.4.1)# Creates and names a library, sets it as either STATIC# or SHARED, and provides the relative paths to its source code.# You can define multiple libraries, and CMake builds them for you.# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.             native-lib             # Sets the library as a shared library.             SHARED             # Provides a relative path to your source file(s).             src/main/cpp/native-lib.cpp )# Searches for a specified prebuilt library and stores the path as a# variable. Because CMake includes system libraries in the search path by# default, you only need to specify the name of the public NDK library# you want to add. CMake verifies that the library exists before# completing its build.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 )# Specifies libraries CMake should link to your target library. You# can link multiple libraries, such as libraries you define in this# build script, prebuilt third-party libraries, or system libraries.set(distribution_DIR ../../../../libs)add_library( avcodec-57             SHARED             IMPORTED)set_target_properties( avcodec-57                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libavcodec-57.so)add_library( avfilter-6             SHARED             IMPORTED)set_target_properties( avfilter-6                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libavfilter-6.so)add_library( avformat-57             SHARED             IMPORTED)set_target_properties( avformat-57                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libavformat-57.so)add_library( avutil-55             SHARED             IMPORTED)set_target_properties( avutil-55                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libavutil-55.so)add_library( swresample-2             SHARED             IMPORTED)set_target_properties( swresample-2                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libswresample-2.so)add_library( swscale-4             SHARED             IMPORTED)set_target_properties( swscale-4                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/armeabi/libswscale-4.so)include_directories(libs/include)target_link_libraries( # Specifies the target library.                       native-lib                       avcodec-57                       avfilter-6                       avformat-57                       avutil-55                       swresample-2                       swscale-4                       # Links the target library to the log library                       # included in the NDK.                       ${log-lib}                       )

这样基本上就大功告成了。

4.使用FFMPEG
下面我们将通过一个小例子来看一下怎样使用FFMPEG。使用FFMPEG进行视频解码(音频和视频很相似)的一般流程如下图所示

首先需要在JAVA文件中加载我们需要的动态库

//MainActivity.javapublic class MainActivity extends AppCompatActivity{    // Used to load the 'native-lib' library on application startup.    static    {        System.loadLibrary("native-lib");        System.loadLibrary("avcodec-57");        System.loadLibrary("avfilter-6");        System.loadLibrary("avformat-57");        System.loadLibrary("avutil-55");        System.loadLibrary("swresample-2");        System.loadLibrary("swscale-4");    }    @Override    protected void onCreate(Bundle savedInstanceState)    {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);        setSupportActionBar(toolbar);        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);        fab.setOnClickListener(new View.OnClickListener()        {            @Override            public void onClick(View view)            {                File inputFile = new File(Environment.getExternalStorageDirectory(), "mv.mp4");                if (!inputFile.exists())                {                    Snackbar.make(view, "文件不存在", Snackbar.LENGTH_LONG).setAction("Action", null)                            .show();                    return;                }                File outputFile = new File(Environment.getExternalStorageDirectory(),                        "output_mv.yuv");                decode(inputFile.getAbsolutePath(), outputFile.getAbsolutePath());            }        });        // Example of a call to a native method        TextView tv = (TextView) findViewById(R.id.sample_text);        tv.setText(stringFromJNI());    }    @Override    public boolean onCreateOptionsMenu(Menu menu)    {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.menu_main, menu);        return true;    }    @Override    public boolean onOptionsItemSelected(MenuItem item)    {        // Handle action bar item clicks here. The action bar will        // automatically handle clicks on the Home/Up button, so long        // as you specify a parent activity in AndroidManifest.xml.        int id = item.getItemId();        //noinspection SimplifiableIfStatement        if (id == R.id.action_settings)        {            return true;        }        return super.onOptionsItemSelected(item);    }    /**     * A native method that is implemented by the 'native-lib' native library,     * which is packaged with this application.     */    public native String stringFromJNI();    public native void decode(String input, String output);}

然后在native代码中实现主要逻辑

#include <jni.h>#include <string>#include <android/log.h>extern "C" {//编码#include "libavcodec/avcodec.h"//封装格式处理#include "libavformat/avformat.h"//像素处理#include "libswscale/swscale.h"}#define FFLOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,"ffmpeg",FORMAT,##__VA_ARGS__);#define FFLOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,"ffmpeg",FORMAT,##__VA_ARGS__);extern "C"JNIEXPORT jstring JNICALLJava_com_arshowbaby_ffmpeg_MainActivity_stringFromJNI(        JNIEnv *env,        jobject /* this */) {    std::string hello = "Hello from C++";    return env->NewStringUTF(hello.c_str());}extern "C"JNIEXPORT void JNICALLJava_com_arshowbaby_ffmpeg_MainActivity_decode(JNIEnv *env, jclass type, jstring input_,                                                     jstring output_) {    //获取输入输出文件名    const char *input = env->GetStringUTFChars(input_, 0);    const char *output = env->GetStringUTFChars(output_, 0);    //1.注册所有组件    av_register_all();    //封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息    AVFormatContext *pFormatCtx = avformat_alloc_context();    //2.打开输入视频文件    if (avformat_open_input(&pFormatCtx, input, NULL, NULL) != 0)    {        FFLOGE("%s","无法打开输入视频文件");        return;    }    //3.获取视频文件信息    if (avformat_find_stream_info(pFormatCtx,NULL) < 0)    {        FFLOGE("%s","无法获取视频文件信息");        return;    }    //获取视频流的索引位置    //遍历所有类型的流(音频流、视频流、字幕流),找到视频流    int v_stream_idx = -1;    int i = 0;    //number of streams    for (; i < pFormatCtx->nb_streams; i++)    {        //流的类型        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)        {            v_stream_idx = i;            break;        }    }    if (v_stream_idx == -1)    {        FFLOGE("%s","找不到视频流\n");        return;    }    //只有知道视频的编码方式,才能够根据编码方式去找到解码器    //获取视频流中的编解码上下文    AVCodecContext *pCodecCtx = pFormatCtx->streams[v_stream_idx]->codec;    //4.根据编解码上下文中的编码id查找对应的解码    AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);    if (pCodec == NULL)    {        FFLOGE("%s","找不到解码器\n");        return;    }    //5.打开解码器    if (avcodec_open2(pCodecCtx,pCodec,NULL)<0)    {        FFLOGE("%s","解码器无法打开\n");        return;    }    //输出视频信息    FFLOGI("视频的文件格式:%s",pFormatCtx->iformat->name);    FFLOGI("视频时长:%d", (pFormatCtx->duration)/1000000);    FFLOGI("视频的宽高:%d,%d",pCodecCtx->width,pCodecCtx->height);    FFLOGI("解码器的名称:%s",pCodec->name);    //准备读取    //AVPacket用于存储一帧一帧的压缩数据(H264)    //缓冲区,开辟空间    AVPacket *packet = (AVPacket*)av_malloc(sizeof(AVPacket));    //AVFrame用于存储解码后的像素数据(YUV)    //内存分配    AVFrame *pFrame = av_frame_alloc();    //YUV420    AVFrame *pFrameYUV = av_frame_alloc();    //只有指定了AVFrame的像素格式、画面大小才能真正分配内存    //缓冲区分配内存    uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));    //初始化缓冲区    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等    struct SwsContext *sws_ctx = sws_getContext(pCodecCtx->width,pCodecCtx->height,pCodecCtx->pix_fmt,                                                pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P,                                                SWS_BICUBIC, NULL, NULL, NULL);    int got_picture, ret;    FILE *fp_yuv = fopen(output, "wb+");    int frame_count = 0;    //6.一帧一帧的读取压缩数据    while (av_read_frame(pFormatCtx, packet) >= 0)    {        //只要视频压缩数据(根据流的索引位置判断)        if (packet->stream_index == v_stream_idx)        {            //7.解码一帧视频压缩数据,得到视频像素数据            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);            if (ret < 0)            {                FFLOGE("%s","解码错误");                return;            }            //为0说明解码完成,非0正在解码            if (got_picture)            {                //AVFrame转为像素格式YUV420,宽高                //2 6输入、输出数据                //3 7输入、输出画面一行的数据的大小 AVFrame 转换是一行一行转换的                //4 输入数据第一列要转码的位置 从0开始                //5 输入画面的高度                sws_scale(sws_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height,                          pFrameYUV->data, pFrameYUV->linesize);                //输出到YUV文件                //AVFrame像素帧写入文件                //data解码后的图像像素数据(音频采样数据)                //Y 亮度 UV 色度(压缩了) 人对亮度更加敏感                //U V 个数是Y的1/4                int y_size = pCodecCtx->width * pCodecCtx->height;                fwrite(pFrameYUV->data[0], 1, y_size, fp_yuv);                fwrite(pFrameYUV->data[1], 1, y_size / 4, fp_yuv);                fwrite(pFrameYUV->data[2], 1, y_size / 4, fp_yuv);                frame_count++;                FFLOGI("解码第%d帧",frame_count);            }        }        //释放资源        av_free_packet(packet);    }    fclose(fp_yuv);    av_frame_free(&pFrame);    avcodec_close(pCodecCtx);    avformat_free_context(pFormatCtx);    env->ReleaseStringUTFChars(input_, input);    env->ReleaseStringUTFChars(output_, output);}

记得在Manifest文件中添加需要的权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

但是,我使用我的例子运行后无法打开视频,不知道为什么打不开。最后简单了解一下FFMPEG中使用的几个主要数据结构的作用:

这里写图片描述

最后感谢无私的分享,有你们我们才能进步。
AndroidStudio 中使用FFMPEG:http://www.cnblogs.com/CoderTian/p/6651343.html#undefined
手把手图文并茂教你用Android Studio编译FFmpeg库并移植:http://blog.csdn.net/hejjunlin/article/details/52661331

附上示例地址:https://github.com/Xanthuim/FFmpegSample

0 0
原创粉丝点击