交叉编译多平台 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. vagrant 搭建开发环境

    虚拟机盒子地址 https://app.vagrantup.com/boxes/search vagrant init hirocom/centos7.2vagrant up 修改配置 config. ...

  2. 【Distributed】分布式任务调度平台

    一.概述 什么是定时任务 二.Java实现定时任务方式 2.1 Thread 2.2 TimerTask 2.3 ScheduledExecutorService 2.4 Quartz 引入maven ...

  3. SPI学习笔记1

    SPI 简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口.是 Motorola首先在其 MC68HCXX 系列处理器上定义的. SPI ...

  4. Vue 循环 [Vue warn]: Avoid using non-primitive value as key

    页面中不添加  :key 索引的时候,会不停的提示虚线,但不影响使用 后来加了一个索引,加成了:key= "content" 从后台取出来的contents是一个list,里面有多 ...

  5. 文件格式 rdp

    auto connect:i:1full address:s:公网访问IP地址username:s:Administrator

  6. 深入理解flex布局的flex-grow、flex-shrink、flex-basis

    flex-basis用来设置初始的宽度(或者高度),优先级高于width flex-grow用来设置flex容器内 当还有剩余宽度(或高度)时, 子元素的缩放比例. 同理 flex-shrink 用来 ...

  7. Nginx命令和Nginx命令

    Nginx命令 h 查看帮助选项 V 查看版本和配置选项t 测试nginx语法错误c filename 指定配置⽂文件(default:/etc/nginx/nginx.conf)s signal 发 ...

  8. unittest简单使用的介绍

    无论是paython+request接口测试.ui自动化测试等,都常会用到unittest的框架,简单的介绍如下:

  9. 甘特图、Data Editors控件新玩法—DevExpress WPF v19.2

    通过DevExpress WPF Controls,你能创建有着强大互动功能的XAML基础应用程序,这些应用程序专注于当代客户的需求和构建未来新一代支持触摸的解决方案. 无论是Office办公软件的衍 ...

  10. 父元素设置固定宽度并设置overflow:scroll,如何让子元素撑开父元素

    <div class="a"> <div class="b"> <div class="c">内容内容, ...