FFmpeg-3.3.1移植到Android平台(Mac编译)

来源:互联网 发布:寻找客源的软件 编辑:程序博客网 时间:2024/06/05 20:22

转载地址:http://www.jianshu.com/p/da64059799d5


我们的目标是:

1.mac平台编译FFmpeg生成so动态库。2.Android通过cmake集成,使用so动态库。

环境

mac os: 10.12.6ndk: 15.1.4119039android studio: 2.3.3cmake: cmake_minimum_required(VERSION 3.4.1)

前置条件:

1:NDK环境配置

#编辑本地配置文件,没有自行创建vim .bash_profile

顺便把android环境也配置后,后面会用到


#重新加载配置文件source .bash_profile#验证是否配置正确ndk-build
image.png
image.png

出现上述结果说明已经能找到ndk

2.下载FFmpeg-3.3.1源码

image.png
image.png

这个地方点击Sourcecode,要吐槽一下他们的设计,开始以为要点上面的那个按钮,点了半天才反应过来要点下面的字。

image.png
image.png

解压之后放到一个目录:
这里目录名称可以随便,我统一都放在ffmpeg下
/Users/leon/Desktop/ffmpeg


image.png
image.png

2:Mac编译脚本编写(坑点颇多)

在目录下新建ffmpeg_build.sh(名称可修改)

image.png
image.png

此处我用Sublime Text编辑,最好不要用其他平台的文件,比如从window平台复制一份已经写好的脚本文件,可能出现字符编码问题.所以我为了避免麻烦直接在Mac平台重写.

#!/bin/bash# 因为我是在根目录,所以要先进入ffmpeg-3.3.1目录cd ffmpeg-3.3.1make clean# NDK的路径,根据自己的安装位置进行设置export NDK=/Users/leon/develop/android/android-sdk-macosx/ndk-bundleexport SYSROOT=$NDK/platforms/android-16/arch-arm/export TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64export CPU=armexport PREFIX=$(pwd)/android/$CPUexport ADDI_CFLAGS="-marm"function build_one{./configure \    --prefix=$PREFIX \    --target-os=linux \    --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \    --arch=arm \    --sysroot=$SYSROOT \    --extra-cflags="-Os -fpic $ADDI_CFLAGS" \    --extra-ldflags="$ADDI_LDFLAGS" \    --cc=$TOOLCHAIN/bin/arm-linux-androideabi-gcc \    --nm=$TOOLCHAIN/bin/arm-linux-androideabi-nm \    --enable-shared \    --enable-runtime-cpudetect \    --enable-gpl \    --enable-small \    --enable-cross-compile \    --disable-debug \    --disable-static \    --disable-doc \    --disable-asm \    --disable-ffmpeg \    --disable-ffplay \    --disable-ffprobe \    --disable-ffserver \    --enable-postproc \    --enable-avdevice \    --disable-symver \    --disable-stripping \$ADDITIONAL_CONFIGURE_FLAGsed -i '' 's/HAVE_LRINT 0/HAVE_LRINT 1/g' config.hsed -i '' 's/HAVE_LRINTF 0/HAVE_LRINTF 1/g' config.hsed -i '' 's/HAVE_ROUND 0/HAVE_ROUND 1/g' config.hsed -i '' 's/HAVE_ROUNDF 0/HAVE_ROUNDF 1/g' config.hsed -i '' 's/HAVE_TRUNC 0/HAVE_TRUNC 1/g' config.hsed -i '' 's/HAVE_TRUNCF 0/HAVE_TRUNCF 1/g' config.hsed -i '' 's/HAVE_CBRT 0/HAVE_CBRT 1/g' config.hsed -i '' 's/HAVE_RINT 0/HAVE_RINT 1/g' config.hmake clean# 这里是定义用几个CPU编译,我用4个,一般在5分钟之内编译完成make -j4make install}build_one

这个文件在编译时会出现各种奇怪问题,其中一个坑是在每行语句后面不能有空格.比如

image.png
image.png

3.修改configure文件(修改前最好backup一份)

image.png
image.png

修改如下所示:
大概在3300行,注释前四行掉,然后换成没有注释的

#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)'
image.png
image.png

这里要修改的目的是因为如果不修改生成so文件的名称大概是 xxxx.so.5.60 ,版本号跟在了.so后面,拿到android下无法正确读取,即使手动改变文件名称也不行.
修改后版本号是在.so前面,文件是以.so结尾.

*这里进入此目录,执行configure文件生成一些xx.mk配置文件,主要目的是读取本地编译环境信息并记录。

./configure --disable-yasm

4:编译

激动人心的时刻要到了,大部分时候都是要折腾很久才能爽这一下,不好意思,又要开车了。

# 命令行到脚本所在目录./ffmpeg_build.sh

此时正确的姿势是这样的


虽然其中会出现这种情况,但是不必管他

image.png
image.png

第一次折腾好时,一看表已经凌晨2点,说多了都是泪.

image.png
image.png

编译结束,在FFmpeg-3.3.1目录中会多一个android目录,这里面就是我们要用到的so

image.png
image.png

在开头我们定的目标一已经完成,来看一下我们目标二.

下面开始使用Android Studio创建App工程,并使用以上我们编译生成的动态库,编写一个简单的jni调用ffmpeg播放本地视频文件

5:创建Android项目

image.png
image.png
image.png
image.png

可以先运行一下项目,保证新创建的项目没有问题,这里模拟器不能使用x86架构的,但是arm架构的模拟器又巨慢,我用真机测试

  • main文件目录中新建 jniLibs文件件
  • 把本地编译的include文件夹和so赋值过来
image.png
image.png

6:编写CMakeLists

cmake_minimum_required(VERSION 3.4.1)add_library( native-ffmpeg             SHARED             src/main/cpp/native-ffmpeg.cpp )find_library( log-lib              log )find_library( android-lib              android )set(distribution_DIR ${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})add_library( avutil-55             SHARED             IMPORTED )set_target_properties( avutil-55                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libavutil-55.so)add_library( swresample-2             SHARED             IMPORTED )set_target_properties( swresample-2                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libswresample-2.so)add_library( avcodec-57             SHARED             IMPORTED )set_target_properties( avcodec-57                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libavcodec-57.so)add_library( avfilter-6             SHARED             IMPORTED )set_target_properties( avfilter-6                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libavfilter-6.so)add_library( swscale-4             SHARED             IMPORTED )set_target_properties( swscale-4                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libswscale-4.so)add_library( avdevice-57             SHARED             IMPORTED )set_target_properties( avdevice-57                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libavdevice-57.so)add_library( avformat-57             SHARED             IMPORTED )set_target_properties( avformat-57                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libavformat-57.so)add_library( postproc-54             SHARED             IMPORTED )set_target_properties( postproc-54                       PROPERTIES IMPORTED_LOCATION                       ${distribution_DIR}/libpostproc-54.so)set(CMAKE_VERBOSE_MAKEFILE on)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")include_directories(src/main/cpp)include_directories(src/main/jniLibs/include)target_link_libraries(native-ffmpeg                      avutil-55       #工具库(大部分需要)                      swresample-2    #音频采样数据格式转换                      avcodec-57      #编解码(重要)                      avfilter-6      #滤镜特效处理                      swscale-4       #视频像素数据格式转换                      avdevice-57     #各种设备的输入输出                      avformat-57     #封装格式处理                      postproc-54     #后加工                      ${log-lib}                      ${android-lib})

这里cmake需要看下官方文档了解下

7:编写JNI文件

image.png
image.png

1)java控制类:PlayerNative.java

package com.example.leon.ffmpegandroiddemo;/** * Created by leon */public class PlayerNative {    /**     * 这里加载有依赖关系     */    static {        System.loadLibrary("avutil-55");        System.loadLibrary("swresample-2");        System.loadLibrary("avcodec-57");        System.loadLibrary("avfilter-6");        System.loadLibrary("swscale-4");        System.loadLibrary("avdevice-57");        System.loadLibrary("avformat-57");        System.loadLibrary("postproc-54");        System.loadLibrary("native-ffmpeg");    }    /**     * 音视频解码播放     *     * @param path     * @param view     */    public native static void paly(String path, Object view);    /**     * 音视频解码停止     *     */    public native static void stop();}

2)Jni:native-ffmpeg.cpp(可以不用写.h文件)

#include <jni.h>#include <string>#include "native-ffmpeg.h"#include <android/native_window.h>#include <android/native_window_jni.h>#include <unistd.h>#include <android/log.h>#define LOGI(FORMAT, ...) __android_log_print(ANDROID_LOG_INFO,"TAG",FORMAT,##__VA_ARGS__);#define LOGE(FORMAT, ...) __android_log_print(ANDROID_LOG_ERROR,"TAG",FORMAT,##__VA_ARGS__);extern "C" {#include "libavformat/avformat.h"#include "libswscale/swscale.h"#include <libavfilter/avfiltergraph.h>#include "libavfilter/avfilter.h"#include "libavutil/imgutils.h"#include "libavutil/avutil.h"#include "libavfilter/buffersink.h"#include "libavfilter/buffersrc.h"#include "libavcodec/avcodec.h"}extern "C"/** * 播放 */JNIEXPORT void JNICALL Java_com_example_leon_ffmpegandroiddemo_PlayerNative_paly        (JNIEnv *env, jclass cls, jstring path_, jobject view) {    LOGE("%s", "play()");    const char *path = env->GetStringUTFChars(path_, 0);    //注册所有的编解码器    av_register_all();    int ret;    //封装格式上线文    AVFormatContext *fmt_ctx = avformat_alloc_context();    //打开输入流并读取头文件。此时编解码器还没有打开    if (avformat_open_input(&fmt_ctx, path, NULL, NULL) < 0) {        return;    }    //获取信息    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {        return;    }    //获取视频流的索引位置    int video_stream_index = -1;    for (int i = 0; i < fmt_ctx->nb_streams; i++) {        if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {            video_stream_index = i;            LOGE("找到视频流索引位置video_stream_index=%d", video_stream_index);            break;        }    }    if (video_stream_index == -1) {        LOGE("未找到视频流索引");    }    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, view);    if (nativeWindow == NULL) {        LOGE("ANativeWindow_fromSurface error");        return;    }    //绘制时候的缓冲区    ANativeWindow_Buffer outBuffer;    //获取视频流解码器    AVCodecContext *codec_ctx = avcodec_alloc_context3(NULL);    avcodec_parameters_to_context(codec_ctx, fmt_ctx->streams[video_stream_index]->codecpar);    AVCodec *avCodec = avcodec_find_decoder(codec_ctx->codec_id);    //打开解码器    if ((ret = avcodec_open2(codec_ctx, avCodec, NULL)) < 0) {        ret = -3;        return;    }    //循环从文件读取一帧压缩数据    //开始读取视频    int y_size = codec_ctx->width * codec_ctx->height;    AVPacket *pkt = (AVPacket *) malloc(sizeof(AVPacket));//分配一个packet    av_new_packet(pkt, y_size);//分配packet的数据    AVFrame *yuvFrame = av_frame_alloc();    AVFrame *rgbFrame = av_frame_alloc();    // 颜色转换器    SwsContext *m_swsCtx = sws_getContext(codec_ctx->width, codec_ctx->height, codec_ctx->pix_fmt,                                          codec_ctx->width,                                          codec_ctx->height, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL,                                          NULL, NULL);    LOGE("开始解码");    int index = 0;    while (1) {        if (av_read_frame(fmt_ctx, pkt) < 0) {            //这里就认为视频读完了            break;        }        if (pkt->stream_index == video_stream_index) {            //视频解码            ret = avcodec_send_packet(codec_ctx, pkt);            if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF) {                LOGE("avcodec_send_packet ret=%d", ret);                av_packet_unref(pkt);                continue;            }            //从解码器返回解码输出数据            ret = avcodec_receive_frame(codec_ctx, yuvFrame);            if (ret < 0 && ret != AVERROR_EOF) {                LOGE("avcodec_receive_frame ret=%d", ret);                av_packet_unref(pkt);                continue;            }            //avcodec_decode_video2(codec_ctx,yuvFrame,&got_pictue,&pkt);            sws_scale(m_swsCtx, (const uint8_t *const *) yuvFrame->data, yuvFrame->linesize, 0,                      codec_ctx->height, rgbFrame->data, rgbFrame->linesize);            //设置缓冲区的属性            ANativeWindow_setBuffersGeometry(nativeWindow, codec_ctx->width, codec_ctx->height,                                             WINDOW_FORMAT_RGBA_8888);            ret = ANativeWindow_lock(nativeWindow, &outBuffer, NULL);            if (ret != 0) {                LOGE("ANativeWindow_lock error");                return;            }            av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize,                                 (const uint8_t *) outBuffer.bits, AV_PIX_FMT_RGBA,                                 codec_ctx->width, codec_ctx->height, 1);            //fill_ANativeWindow(&outBuffer,outBuffer.bits,rgbFrame);            //将缓冲区数据显示到surfaceView            ret = ANativeWindow_unlockAndPost(nativeWindow);            if (ret != 0) {                LOGE("ANativeWindow_unlockAndPost error");                return;            }            LOGE("成功显示到缓冲区%d次", ++index);        }        av_packet_unref(pkt);        usleep(1000 * 8);    }    av_frame_free(&rgbFrame);    avcodec_close(codec_ctx);    sws_freeContext(m_swsCtx);    avformat_close_input(&fmt_ctx);    ANativeWindow_release(nativeWindow);    env->ReleaseStringUTFChars(path_, path);    LOGI("解析完成");}/** * 暂停 */JNIEXPORT void JNICALL Java_com_example_leon_ffmpegandroiddemo_PlayerNative_stop        (JNIEnv *env, jclass cls) {    LOGE("%s", "stop()");}

3)其他文件
MainActivity.java

package com.example.leon.ffmpegandroiddemo;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.TextView;import java.io.File;public class MainActivity extends AppCompatActivity {    private VideoView mVideo;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mVideo = (VideoView) findViewById(R.id.video);    }    public void paly(View view) {        final String dir = Environment.getExternalStorageDirectory() + File.separator + "Download";        final String path = dir + File.separator + "1.mp4";        final VideoView mVideo = (VideoView) findViewById(R.id.video);        if (new File(path).exists()) {            runOnUiThread(new Runnable() {                @Override                public void run() {                    PlayerNative.paly(path, mVideo.getHolder().getSurface());                }            });        } else {            System.out.println("文件不存在");        }    }    public void stop(View view) {        PlayerNative.stop();    }}

VideoView.java

package com.example.leon.ffmpegandroiddemo;import android.content.Context;import android.graphics.PixelFormat;import android.util.AttributeSet;import android.view.SurfaceHolder;import android.view.SurfaceView;/** * * Created by leon */public class VideoView extends SurfaceView {    public VideoView(Context context) {        super(context);        init();    }    public VideoView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public VideoView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        SurfaceHolder holder = getHolder();        holder.setFormat(PixelFormat.RGBA_8888);    }}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@android:color/white"    android:orientation="vertical">    <LinearLayout        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="horizontal">        <Button            android:id="@+id/btn"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:onClick="paly"            android:text="开始" />        <Button            android:id="@+id/btn"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:onClick="stop"            android:text="结束" />    </LinearLayout>    <com.example.leon.ffmpegandroiddemo.VideoView        android:id="@+id/video"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginTop="10dp" /></LinearLayout>

到此我们定的两个目标已经完成,以后会再继续写FFmpeg使用教程。

最后总结一下各种问题:
1:cannot locate symbol "atof" referenced by "libavformat-57.so"...的问题

原因:Android的stdlib.h中atof是内联的
解决方法:将所有的atof改成strtod
示例代码:
char *strpi = "3.1415";
double dpi;
dpi = atof(strpi); 修改为: dpi = strtod(strpi, NULL);

或者脚本文件中定义platforms版本使用低版本,比如
export SYSROOT=$NDK/platforms/android-16/arch-arm/

2.cannot locate symbol "log2f" referenced by "libavcodec-57.so"..
原因: 这个跟ndk与android版本有关。如果使用高版本ndk编译,so会判断系统版本并使用系统log,但实际手机系统版本较低,并没有此方法,所以会报找不到此方法
解决办法:
1),使用platforms 低版本
2),修改 ./libavutil/libm.h里面的定义,不再判断是否已经存在函数。使用重新定义

//#if !HAVE_LOG2  //#undef log2  #define log2(x) (log(x) * 1.44269504088896340736)  //#endif /* HAVE_LOG2 */    //#if !HAVE_LOG2F  //#undef log2f  #define log2f(x) ((float)log2(x))  //#endif /* HAVE_LOG2F */  
FFmpeg


原创粉丝点击