zoukankan      html  css  js  c++  java
  • 交叉编译多平台 FFmpeg 库并提取视频帧(转)

    交叉编译多平台 FFmpeg 库并提取视频帧

    转  https://www.cnblogs.com/leviatan/p/11142579.html

    本文档适用于 x86 平台编译 armeabi、armeabi-v7a、arm64-v8a、x86、x86_64 平台的 ffmpeg 运行库

    开发环境

    编译环境: Ubuntu 1810 x64

    开发环境: Windows 10

    IDE: Android Studio 3.4.1

    Android: 7.1

    FFmpeg: 3.4.6

    编译流程

    下载 FFmpeg 源码: Download FFmpeg

    解压后进入源码包,创建 build.sh 文件,并赋予执行权限

    tar zxvf ffmpeg-3.4.6.tar.gz
    cd ffmpeg-3.4.6
    touch build.sh
    chmod +x build.sh

    将以下脚本写入 build.sh

    NDK_PATH 建议下载 Revision 15C 版本

    根据实际情况修改 NDK_PATH,TOOLCHAIN_VERSION 及 ANDROID_VERSION

    #!/bin/sh
    
    MY_LIBS_NAME=ffmpeg-3.4.6
    
    # 编译产生的中间件目录
    MY_BUILD_DIR=binary
    
    # NDK 目录
    NDK_PATH=/usr/android-sdk-linux/android-ndk-r15c
    # 编译平台
    BUILD_PLATFORM=linux-x86_64
    # NDK 中交叉编译工具版本
    TOOLCHAIN_VERSION=4.9
    # Android API Level
    ANDROID_VERSION=26
    
    ANDROID_ARMV5_CFLAGS="-march=armv5te"
    ANDROID_ARMV7_CFLAGS="-march=armv7-a -mfloat-abi=softfp -mfpu=neon"
    ANDROID_ARMV8_CFLAGS="-march=armv8-a"
    ANDROID_X86_CFLAGS="-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32"
    ANDROID_X86_64_CFLAGS="-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel"
    
    
    # params($1: arch, $2: arch_abi, $3: host, $4: cross_prefix, $5: cflags)
    build_bin() {
    
        echo "------------------- Start build $2 -------------------------"
    
        ARCH=$1         # arm arm64 x86 x86_64
        ANDROID_ARCH_ABI=$2     # armeabi armeabi-v7a x86 mips
    
        PREFIX=$(pwd)/dist/${MY_LIBS_NAME}/${ANDROID_ARCH_ABI}/
    
        HOST=$3
        SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ARCH}
    
        CFALGS=$5
    
    
        TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}
        CROSS_PREFIX=${TOOLCHAIN}/bin/$4-
    
        # build 中间件
        mkdir -p ${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}
        BUILD_DIR=./${MY_BUILD_DIR}/${ANDROID_ARCH_ABI}
    
        echo "pwd==$(pwd)"
        echo "ARCH==${ARCH}"
        echo "PREFIX==${PREFIX}"
        echo "HOST==${HOST}"
        echo "SYSROOT=${SYSROOT}"
        echo "CFALGS=$5"
        echo "CFALGS=${CFALGS}"
        echo "TOOLCHAIN==${TOOLCHAIN}"
        echo "CROSS_PREFIX=${CROSS_PREFIX}"
    
        mkdir -p ${BUILD_DIR}
        cd ${BUILD_DIR}
    
    
        sh ../../configure 
            --prefix=${PREFIX} 
            --target-os=linux 
            --arch=${ARCH} 
            --sysroot=$SYSROOT 
            --enable-cross-compile 
            --cross-prefix=${CROSS_PREFIX} 
            --extra-cflags="$CFALGS -Os -fPIC -DANDROID -Wfatal-errors -Wno-deprecated" 
            --extra-cxxflags="-D__thumb__ -fexceptions -frtti" 
            --extra-ldflags="-L${SYSROOT}/usr/lib" 
            --enable-shared 
            --enable-asm 
            --enable-neon 
            --disable-encoders 
            --enable-encoder=aac 
            --enable-encoder=mjpeg 
            --enable-encoder=png 
            --disable-decoders 
            --enable-decoder=aac 
            --enable-decoder=aac_latm 
            --enable-decoder=h264 
            --enable-decoder=mpeg4 
            --enable-decoder=mjpeg 
            --enable-decoder=png 
            --disable-demuxers 
            --enable-demuxer=image2 
            --enable-demuxer=h264 
            --enable-demuxer=aac 
            --disable-parsers 
            --enable-parser=aac 
            --enable-parser=ac3 
            --enable-parser=h264 
            --enable-gpl 
            --disable-doc 
            --disable-ffmpeg 
            --disable-ffplay 
            --disable-ffprobe 
            --disable-symver 
            --disable-debug 
            --enable-small
    
    
        make clean
        make
        make install
    
        cd ../../
    
        echo "------------------- $2 Build finish -------------------------"
    }
    
    # build for armeabi
    #build_bin arm armeabi arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV5_CFLAGS"
    
    # build for armeabi-v7a
    #build_bin arm armeabi-v7a arm-linux-androideabi arm-linux-androideabi "$ANDROID_ARMV7_CFLAGS"
    
    # build for arm64-v8a
    build_bin arm64 arm64-v8a aarch64-linux-android aarch64-linux-android "$ANDROID_ARMV8_CFLAGS"
    
    # build for x86
    #build_bin x86 x86 x86 i686-linux-android "$ANDROID_X86_CFLAGS"
    
    # build for x86_64
    #build_bin x86_64 x86_64 x86_64 x86_64-linux-android "$ANDROID_X86_64_CFLAGS"

    根据需要选择脚本最后的编译命令,直接运行脚本即可自动编译

    编译成功后目录结构

    ffmpeg-3.4.6
    ├─ binary   # 编译产生的中间件
    ├─ build.sh # 编译脚本
    ├─ Changelog
    ├─ compat
    ├─ configure
    ├─ CONTRIBUTING.md
    ├─ COPYING.GPLv2
    ├─ COPYING.GPLv3
    ├─ COPYING.LGPLv2.1
    ├─ COPYING.LGPLv3
    ├─ CREDITS
    ├─ dist     # 编译输出的库和头文件目录
    │   └─ ffmpeg-3.4.6 # 该文件夹名由 MY_LIBS_NAME 指定
    │       └─ arm64-v8a    # 与编译的目标平台 ABI 名称相同
    │           ├─ bin
    │           ├─ include  # 头文件目录
    │           │   ├─ libavcodec
    │           │   ├─ libavdevice
    │           │   ├─ libavfilter
    │           │   ├─ libavformat
    │           │   ├─ libavutil
    │           │   ├─ libpostproc
    │           │   ├─ libswresample
    │           │   └─ libswscale
    │           ├─ lib      # 库目录,包含动态库和静态库
    │           │   ├─ libavcodec-57.so
    │           │   ├─ libavcodec.a
    │           │   ├─ libavcodec.so -> libavcodec-57.so
    │           │   ├─ libavdevice-57.so
    │           │   ├─ libavdevice.a
    │           │   ├─ libavdevice.so -> libavdevice-57.so
    │           │   ├─ libavfilter-6.so
    │           │   ├─ libavfilter.a
    │           │   ├─ libavfilter.so -> libavfilter-6.so
    │           │   ├─ libavformat-57.so
    │           │   ├─ libavformat.a
    │           │   ├─ libavformat.so -> libavformat-57.so
    │           │   ├─ libavutil-55.so
    │           │   ├─ libavutil.a
    │           │   ├─ libavutil.so -> libavutil-55.so
    │           │   ├─ libpostproc-54.so
    │           │   ├─ libpostproc.a
    │           │   ├─ libpostproc.so -> libpostproc-54.so
    │           │   ├─ libswresample-2.so
    │           │   ├─ libswresample.a
    │           │   ├─ libswresample.so -> libswresample-2.so
    │           │   ├─ libswscale-4.so
    │           │   ├─ libswscale.a
    │           │   ├─ libswscale.so -> libswscale-4.so
    │           │   └─ pkgconfig
    │           └─ share
    ├─ doc
    ├─ ffbuild
    ├─ fftools
    ├─ INSTALL.md
    ├─ libavcodec
    ├─ libavdevice
    ├─ libavfilter
    ├─ libavformat
    ├─ libavresample
    ├─ libavutil
    ├─ libpostproc
    ├─ libswresample
    ├─ libswscale
    ├─ LICENSE.md
    ├─ MAINTAINERS
    ├─ Makefile
    ├─ presets
    ├─ README.md
    ├─ RELEASE
    ├─ RELEASE_NOTES
    ├─ tests
    ├─ tools
    └─ VERSION

    将运行库导入到项目中

    目录结构

    ffmpegtest
    ├─ app
    │  ├─ build
    │  ├─ libs
    │  └─ src
    │      ├─ androidTest
    │      ├─ main
    │      │  ├─ java
    │      │  │  └─ com
    │      │  │      └─ example
    │      │  │          └─ ffmpegtest
    │      │  │                  MainActivity.java
    │      │  ├─ jni    # C/C++ 源码目录
    │      │  │  └─ include     # 需要导入的头文件
    │      │  │      ├─ libavcodec
    │      │  │      ├─ libavdevice
    │      │  │      ├─ libavfilter
    │      │  │      ├─ libavformat
    │      │  │      ├─ libavutil
    │      │  │      ├─ libpostproc
    │      │  │      ├─ libswresample
    │      │  │      └─ libswscale
    │      │  ├─ jniLibs    # JNI 需要调用的运行库
    │      │  │  └─ arm64-v8a   # 对应 ABI 版本建立文件夹
    │      │  │      ├─ libavcodec-57.so
    │      │  │      ├─ libavdevice-57.so
    │      │  │      ├─ libavfilter-6.so
    │      │  │      ├─ libavformat-57.so
    │      │  │      ├─ libavutil-55.so
    │      │  │      ├─ libpostproc-54.so
    │      │  │      ├─ libswresample-2.so
    │      │  │      ├─ libswscale-4.so
    │      │  │      └─ libswscale-4.so
    │      │  └─ res
    │      └─ test
    └─ gradle

    CMakeLists.txt 添加以下配置

    include_directories(${PROJECT_SOURCE_DIR}/src/main/jni/include)
    
    add_library(ffmpegTest
            SHARED
            src/main/jni/ffmpegTest.cpp )
    
    add_library(avcodec-57 SHARED IMPORTED)
    set_target_properties(avcodec-57
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavcodec-57.so )
    
    add_library(avfilter-6 SHARED IMPORTED)
    set_target_properties(avfilter-6
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavfilter-6.so )
    
    add_library(avformat-57 SHARED IMPORTED)
    set_target_properties(avformat-57
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavformat-57.so )
    
    add_library(avutil-55 SHARED IMPORTED)
    set_target_properties(avutil-55
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavutil-55.so )
    
    add_library(postproc-54 SHARED IMPORTED)
    set_target_properties(postproc-54
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libpostproc-54.so )
    
    add_library(avdevice-57 SHARED IMPORTED)
    set_target_properties(avdevice-57
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libavdevice-57.so )
    
    add_library(swscale-4 SHARED IMPORTED)
    set_target_properties(swscale-4
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswscale-4.so )
    
    add_library(swresample-2 SHARED IMPORTED)
    set_target_properties(swresample-2
            PROPERTIES
            IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libswresample-2.so )
    
    target_link_libraries(ffmpegTest
            ${log-lib}
            avcodec-57 avfilter-6 avformat-57 avutil-55 postproc-54 avdevice-57 swscale-4 swresample-2)

    提取视频帧并保存为图片

    #include <jni.h>
    #include <android/log.h>
    
    extern "C" {
    #include <libavformat/avformat.h>
    }
    
    #define DEBUG
    
    #ifdef DEBUG
    #define LOG    "ffmpegLOG"
    #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG, __VA_ARGS__)
    #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG, __VA_ARGS__)
    #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN, LOG, __VA_ARGS__)
    #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG, __VA_ARGS__)
    #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL, LOG, __VA_ARGS__)
    #else
    #define LOG
    #define LOGD(...)
    #define LOGI(...)
    #define LOGW(...)
    #define LOGE(...)
    #define LOGF(...)
    #endif
    
    int writeJPEG(AVFrame* frame, int width, int height, char* output_ath, int image_index);
    
    extern "C"
    JNIEXPORT jint JNICALL
    Java_com_example_ffmpegtest_MainActivity_videoFrame(JNIEnv *env, jobject instance,
                                                        jstring filePath_, jstring outputPath_) {
        const char *filePath = env->GetStringUTFChars(filePath_, 0);
        const char *outputPath = env->GetStringUTFChars(outputPath_, 0);
    
        LOGE("======================= ffmpeg start =======================");
    
        clock_t time_start, time_finish;
        double total_time;
        time_start = clock();
    
        // 注册所有模块
        av_register_all();
    
        AVFormatContext *formatContext = nullptr;
        int ret = 0;
    
        LOGD("Video path: [%s]", filePath);
        // 打开媒体
        ret = avformat_open_input(&formatContext, filePath, nullptr, nullptr);
        if (ret < 0) {
            LOGE("Cannot open file, error code: [%d]", ret);
            return -1;
        }
    
        // 获取媒体信息
        ret = avformat_find_stream_info(formatContext, nullptr);
        if (ret < 0) {
            LOGE("Cannot find stream, error code: [%d]", ret);
            return -1;
        }
    
        int video_index = -1;
        // 遍历媒体流
        for (int i = 0; i < formatContext->nb_streams; i++) {
            if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
                video_index = i;
                break;
            }
        }
    
        if (video_index == -1) {
            LOGE("Cannot find video stream");
            return -1;
        }
    
        // 找出一个有效码流的 AVCodecID,根据标准寻找对应的解码器
        AVCodecContext *codecContext = formatContext->streams[video_index]->codec;
        enum AVCodecID codecId = codecContext->codec_id;
        AVCodec *codec = avcodec_find_decoder(codecId);
        if(!codec){
            LOGE("Cannot find decoder");
            return -1;
        }
    
        // 初始化解码器
        ret = avcodec_open2(codecContext, codec, nullptr);
        if (ret < 0) {
            LOGE("Cannot open decoder, error code: [%d]", ret);
            return -1;
        }
    
        // 分配内存
        AVPacket *packet = av_packet_alloc();
        AVFrame *frame = av_frame_alloc();
        int image_index = 0;
    
        // 当剩余帧数大于 0 时
        while (av_read_frame(formatContext, packet) >= 0) {
            if (packet && packet->stream_index == video_index) {
                int gotFrame = 0;
                // 将 AVPacket 中的数据解码为原始数据(YUV、RGB 以及 PCM),存储在 AVFrame 上
                avcodec_decode_video2(codecContext, frame, &gotFrame, packet);
                if (gotFrame) {
                    image_index++;
                    // 将视频帧保存在本地
                    ret = writeJPEG(frame, codecContext->width, codecContext->height, (char*)outputPath, image_index);
                    if(ret == 0){
                        LOGI("Save frame in %s and rename to video_frame_%d.jpg", outputPath, image_index);
                    }
                }
            }
        }
        time_finish = clock();
        total_time = (double)(time_finish - time_start);
        LOGE("Total time: [%f]ms --- ffmpeg", total_time);
        LOGE("======================= ffmpeg finish =======================");
    
        env->ReleaseStringUTFChars(filePath_, filePath);
        env->ReleaseStringUTFChars(outputPath_, outputPath);
    
        av_frame_free(&frame);
        avcodec_close(codecContext);
        avformat_free_context(formatContext);
    
        return 0;
    }
    
    int writeJPEG(AVFrame *frame, int width, int height,char* output_path, int image_index) {
        char *out_file;
        sprintf(out_file, "%s/video_frame_%d.jpg", output_path, image_index);
        // 分配内存空间
        AVFormatContext *formatContext = avformat_alloc_context();
        // 初始化 AVFormatContext 结构体
        avformat_alloc_output_context2(&formatContext, nullptr, "singlejpeg", out_file);
    
        // 指定图片格式
        formatContext->oformat = av_guess_format("mjpeg", nullptr, nullptr);
        // 打开(创建?)要写入的文件
        if (avio_open(&formatContext->pb, out_file, AVIO_FLAG_READ_WRITE) < 0) {
            LOGE("Open file failed---write JPEG");
            return -1;
        }
    
        // 创建流通道,例如 Video - H.264, Audio - AAC
        AVStream *stream = avformat_new_stream(formatContext, nullptr);
        if (stream == nullptr) {
            LOGE("Create stream failed---write JPEG");
            return -1;
        }
    
        AVCodecContext *codecContext = stream->codec;
    
        // 保存文件头信息(帧信息)
        codecContext->codec_id = formatContext->oformat->video_codec;
        codecContext->codec_type = AVMEDIA_TYPE_VIDEO;
        codecContext->pix_fmt = AV_PIX_FMT_YUVJ420P;
        codecContext->height = height;
        codecContext->width = width;
        codecContext->time_base.num = 1;
        codecContext->time_base.den = 25;
    
        // 寻找解码器
        AVCodec* codec = avcodec_find_encoder(codecContext->codec_id);
        if (!codec) {
            LOGE("Cannot find encoder---write JPEG");
            return -1;
        }
    
        // 初始化解码器
        if (avcodec_open2(codecContext, codec, nullptr) < 0) {
            LOGE("Cannot open encoder---write JPEG");
            return -1;
        }
    
        // 将文件头保存到 codecpar 中
        avcodec_parameters_from_context(stream->codecpar, codecContext);
    
        // 写入头数据
        avformat_write_header(formatContext, nullptr);
    
        // 创建并初始化 ACPacket 内存空间
        int size = codecContext->width * codecContext->height;
        AVPacket *writePacket = av_packet_alloc();
        av_new_packet(writePacket, size * 3);
    
        int got_image = 0;
        // 调用编码器,编码为指定格式
        int result = avcodec_encode_video2(codecContext, writePacket, frame, &got_image);
        if (result < 0) {
            LOGE("Encode failed---write JPEG");
            return -1;
        }
        if (got_image == 1) {
            // 输出一帧数据
            av_write_frame(formatContext, writePacket);
        }
        // 释放内存
        av_free_packet(writePacket);
        // 写文件尾
        av_write_trailer(formatContext);
        // 将 AVFrame 归零
        if (frame) {
            av_frame_unref(frame);
        }
        // 关闭文件
        avio_close(formatContext->pb);
        // 释放内存
        avformat_free_context(formatContext);
        return 0;
    }

    在 Activity 中调用

    public class MainActivity extends AppCompatActivity {
        // 导入运行库
        static {
            System.loadLibrary("ffmpegTest");
        }
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            // 获取存储设备路径
            String storagePath = Environment.getExternalStorageDirectory().getPath();
            File videoPath = new File(storagePath + "/Download/testVideo.mp4");
            videoFrame(videoPath.toString(), storagePath + "/Download/video_frames/");
        }
    }
    
    // 实例化运行库中的方法
    public native int videoFrame(String filePath, String outputPath);

     

  • 相关阅读:
    (转载)C++ string中find() ,rfind() 等函数 用法总结及示例
    UVA 230 Borrowers (STL 行读入的处理 重载小于号)
    UVA 12100 打印队列(STL deque)
    uva 12096 The SetStack Computer(STL set的各种库函数 交集 并集 插入迭代器)
    uva 1592 Database (STL)
    HDU 1087 Super Jumping! Jumping! Jumping!
    hdu 1176 免费馅饼
    HDU 1003 Max Sum
    转战HDU
    hust 1227 Join Together
  • 原文地址:https://www.cnblogs.com/it-tsz/p/11142602.html
Copyright © 2011-2022 走看看