交叉编译多平台 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);

 

交叉编译多平台 FFmpeg 库并提取视频帧(转)的更多相关文章

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

    原文地址: 交叉编译多平台 FFmpeg 库并提取视频帧 交叉编译多平台 FFmpeg 库并提取视频帧 本文档适用于 x86 平台编译 armeabi.armeabi-v7a.arm64-v8a.x8 ...

  2. C++调用ffmpeg.exe提取视频帧

    有时候,我们获得一段视频,需要将其中的每一帧都提取出来,来进行一些相关的处理,这时候我们就可以需要用到ffmpeg.exe来进行视频帧的提取. ffmpeg简介:FFmpeg是一套可以用来记录.转换数 ...

  3. 基于C#利用ffmpeg提取视频帧

    利用ffmepg提取视频帧实际上是利用C#调用ffmepg命令行进行处理对应的视频,然后输出出视频帧 GetPicFromVideo("); static public string Get ...

  4. Windows下 ffmpeg + labelImg 提取视频帧 得到图片集 并 标注图片 来 构造数据集

    构造数据集的流程 视频文件  >>  ffmpeg处理  >>  图片集  >>  labelImg进行标注  >>  标注好的数据集 准备ffmpeg ...

  5. ffmpeg-python 任意提取视频帧

    ▶ 环境准备 1.安装 FFmpeg 音/视频工具 FFmpeg 简易安装文档 2.安装 ffmpeg-python pip3 install ffmpeg-python 3.[可选]安装 openc ...

  6. FFmpeg进行视频帧提取&音频重采样-Process.waitFor()引发的阻塞超时

    由于产品需要对视频做一系列的解析操作,利用FFmpeg命令来完成视频的音频提取.第一帧提取作为封面图片.音频重采样.字幕压缩等功能: 前一篇文章已经记录了FFmpeg在JAVA中的使用-音频提取&am ...

  7. 实战FFmpeg--编译iOS平台使用的FFmpeg库(支持arm64的FFmpeg2.6.2)

    编译环境:Mac OS X 10.10.2 ,Xcode 6.3  iOS SDK 8.3        FFmpeg库的下载地址是 http://www.ffmpeg.org/releases/ . ...

  8. FFmpeg编译:mac下编译iOS平台的FFmpeg库(支持armv7, arm64, i386, x86_64)

    环境:FFmpeg 3.4.6Xcode 10.3macOS 10.14.6iOS SDK 12.4 一.准备工作 1. 下载FFmpeg我这里使用的是3.4.6版本的FFmpeg,可以从FFmpeg ...

  9. libavcodec是一款LGPL自由软件编解码库,用于视频和音频数据的编解码工作

    http://zh.wikipedia.org/zh-cn/Libavcodec http://baike.baidu.com/view/856526.htm libavcodec是一款LGPL自由软 ...

随机推荐

  1. Oracle dump函数的用法

    一.函数标准格式: DUMP(expr[,return_fmt[,start_position][,length]]) 基本参数时4个,最少可以填的参数是0个.当完全没有参数时,直接返回null.另外 ...

  2. Spark学习笔记2——RDD(上)

    目录 Spark学习笔记2--RDD(上) RDD是什么? 例子 创建 RDD 并行化方式 读取外部数据集方式 RDD 操作 转化操作 行动操作 惰性求值 Spark学习笔记2--RDD(上) 笔记摘 ...

  3. Netty UDP 使用采坑

    使用Netty搭建UDP服务收集日志,使用过程中发现,部分日志接收不到,排查发现,都是大日志记录不到,后查询相关文档进行如下修改 EventLoopGroup workerGroup = new Ni ...

  4. FreeRTOS编程风格

    数据类型 基本使用的是标准C里面的数据类型,但是针对不同的处理器,对标准C的数据类型又进行了重定义: 在FreeRTOS中详细的数据类型重定义在portmacro.h这个文件中,具体如下: /* Ty ...

  5. jade总结

    随着时间的迁移,要跟官方api相匹配   jade的缺点 1.可移植性差 2.调试困难 3.性能不是非常出色(不是为性能设计,可以使用dot, http://olado.github.io/) 选择的 ...

  6. 通过字节码分析this关键字以及异常表的重要作用

    在之前的字节码分析中缺少对异常的介绍,这次主要来对字节码异常表相关的东东进行一个学习,下面先来编写一个相关异常的小程序: 接着编译来看用javap -verbose来查看一下它的字节码信息: xion ...

  7. java中的io流总结(一)

    知识点:基于抽象基类字节流(InputStream和OutputStream).字符流(Reader和Writer)的特性,处理纯文本文件,优先考虑使用字符流BufferedReader/Buffer ...

  8. SPFA的优化

    [为什么要优化] 关于SPFA,他死了(懂的都懂)   进入正题... 一般来说,我们有三种优化方法. SLF优化: SLF优化,即 Small Label First  策略,使用 双端队列 进行优 ...

  9. nginx配置跨域之后每次访问会发送两次请求

    公司项目从前后端不分离转到前后端分离 首先遇到的问题就是前后端分离的时候跨域的问题 但是当跨域成功配置并且能访问成功的时候发现 每次客户端的请求都会发送两次 第一次是OPTIONS的请求,然后才是正常 ...

  10. TCP/IP分层图解

    网络协议通常分不同层次进行开发,每一层分别负责不同的通信功能.一个协议族,比如 T C P / I P,是一组不同层次上的多个协议的组合. T C P / I P通常被认为是一个四层协议系统,如图1 ...