交叉编译多平台 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. ECharts雷达图详细配置说明

    雷达图表配置说明: // 指定图表的配置项和数据 var option = { backgroundColor: 'rgba(204,204,204,0.7 )', // 背景色,默认无背景 rgba ...

  2. 【OGG 故障处理】OGG-01031

    故障原因 -------------------- 网络异常,导致DP进程异常中断   故障现象 -------------------- 源端DP 进程全部挂起,且启动失败 GGSCI 34> ...

  3. opengl 4.5 中文api 链接

    https://www.cnblogs.com/wiki3d/p/opengl_a.html

  4. php首页定向到内页代码

    php首页定向到内页代码,index.php头部加上以下代码, /afish-c-1/换成内页链接即可. if($_SERVER["REQUEST_URI"]=='/' || $_ ...

  5. oracle 给表字段把VARCHAR2 换成 CLOB

    select * from TableName -- 添加一个字段 alter table TableName add 字段2 clob; --复制数据到此字段update TableName set ...

  6. 使用selenium实现站长素材图片采集

    from selenium import webdriver import requests,os from lxml import etree from selenium.webdriver.chr ...

  7. 发现sqlite

    1.前言 本文使用"发现"二字,是表示我作为一个用过mysql oracle hive以及各种nosql数据库的男人,竟然发现有一个如此常识的数据库我竟然不知道. 在配置airfl ...

  8. VueCropper 图片裁剪

    基于vue的图片裁剪vue-cropper 简小咖 关注  0.2 2018.12.12 15:42 字数 164 阅读 3900评论 1喜欢 3 vue-cropper官网http://xyxiao ...

  9. java+批量下载文件到指定文件夹

    需求 导出文件后存留在了服务器中,需要提供下载按钮,点击后可下载到本地:(因为涉及多个文件,下载前先将文件进行压缩,提供下载压缩文件) 效果预览 代码 主要方法 /**     * 下载生成的所有在线 ...

  10. OSI七层协议模型

    OSI七层模型详解 TCP/IP协议 链接:https://www.nowcoder.com/questionTerminal/b2ccf60bbb13483b94b4bffe200b4f3c 来源: ...