编译可执行命令的FFmpeg

2024-09-01 19:04
文章标签 编译 ffmpeg 执行命令

本文主要是介绍编译可执行命令的FFmpeg,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇讲到了使用FFmpeg生成视频封面图,其实也可以直接使用FFmpeg相关命令截取一帧的图像数据保存到本地,然后加载到ImageView上,有时候使用命令确实比写代码更加简单和使人轻松一点,所以这一篇是讲解如何导入FFmpeg相关源码 然后如何执行命令行工具的博客,但是其实这只是个Demo而已,因为有很多细节需要处理,推荐直接使用开源库。

导入源码

从FFmpeg源码中导入cmdutils.ccmdutils.hconfig.hffmpeg.cffmpeg.h

ffmpeg_filter.cffmpeg_hw.cffmpeg_opt.c这几个源码,一般存放在fftools目录下。config.h如果编译生成目录下没有,就可以直接使用ffmpeg根目录下的config.h

编写CmakeList

# 设置构建本机库文件所需的 CMake的最小版本
cmake_minimum_required(VERSION 3.4.1)#添加头文件的搜索路径
include_directories(src/main/cpp/include)#设置查找动态库位置
set(LINK_DIR ${CMAKE_SOURCE_DIR}/libs/${CMAKE_ANDROID_ARCH_ABI})
link_directories(${LINK_DIR})
#找到所有的so库,存放在全局变量SO_DIR中
file(GLOB SO_DIR ${LINK_DIR}/*.so)#找到所有的源文件,存放在全局变量中
#file(GLOB FFMPEG_DIR src/main/cpp/ffmpeg/*.c)
#message("FFMPEG_DIR == ${FFMPEG_DIR}")file(GLOB CPP_DIR src/main/cpp/*.cpp)
file(GLOB FFMPEG_DIR src/main/cpp/include/*.c)# 添加自己写的 C/C++源文件
add_library(utils #so名称SHARED #动态库${CPP_DIR}${FFMPEG_DIR})#  依赖 NDK中自带的log库
find_library(log-lib log)#  链接库
target_link_libraries(utils${SO_DIR}jnigraphics${log-lib})

我是将ffmpeg的源码和之前生成的ffmpeg头文件都放在了cpp/include目录下,这样在CmakeList中使用include_directories就可以直接找到所有的头文件,然后将ffmpeg的源码和自己写的工具类源码关联起来就行了。

修改FFmpeg的源码

修改ffmpeg.cmain方法名称为exe_cmd,并在ffmpeg.h头文件加上同样名称的方法声明。

//ffmpeg.c
int exe_cmd(int argc, char **argv) {...
}
//ffmpeg.h
int exe_cmd(int argc, char **argv);

原生命令行工具在执行完FFmpeg命令后都会退出程序,但是在Android里面可不能这样,所以我们要修改FFmpeg结束程序的函数。

修改cmdutils.ccmdutils.h,注释掉退出程序的代码,并且增加一个int的返回值。

//cmdutils.c
int exit_program(int ret)
{
//    if (program_exit)
//        program_exit(ret);//    exit(ret);return ret;
}//cmdutils.h
int exit_program(int ret);

并且在Android里面我们肯定是执行完一条命令,接着还会继续执行其他命令,所以我们需要重新初始化一些关键变量的值。

找到ffmpeg.c中的ffmpeg_cleanup函数,在末尾将一些关键变量重新初始化。

//ffmpeg.c
static void ffmpeg_cleanup(int ret) {...nb_filtergraphs = 0;nb_output_files = 0;nb_output_streams = 0;nb_input_files = 0;nb_input_streams = 0;
}

最后在main函数末尾调用ffmpeg_cleanup函数

int exe_cmd(int argc, char **argv) {...//    exit_program(received_nb_signals ? 255 : main_return_code);ffmpeg_cleanup(0);
}

增加FFmpeg日志输出

ffmpeg.c中找到log_callback_null的函数,添加如下代码,原代码块是空实现。

#include "android/log.h"
#define logDebug(...) __android_log_print(ANDROID_LOG_DEBUG,"MainActivity",__VA_ARGS__)static void log_callback_null(void *ptr, int level, const char *fmt, va_list vl) {static int print_prefix = 1;static int count;static char prev[1024];char line[1024];static int is_atty;av_log_format_line(ptr, level, fmt, vl, line, sizeof(line), &print_prefix);strcpy(prev, line);logDebug("ffmpeg log ----- %s", line);
}

main函数中调用log_callback_null函数

int exe_cmd(int argc, char **argv) {av_log_set_callback(log_callback_null);int i, ret;...
}

编写工具类方法

MainActivity中增加exeCmd(String[] cmd)方法

public static native int exeCmd(String[] cmd);

ffmpeg_utils.cpp增加jni方法

JNIEXPORT jint JNICALL
Java_demo_simple_example_1ffmpeg_MainActivity_exeCmd(JNIEnv *env, jclass clazz, jobjectArray cmd) {int argc = env->GetArrayLength(cmd);logDebug("argc == %d", argc);char *argv[argc];for (int i = 0; i < argc; ++i) {jstring str = (jstring) env->GetObjectArrayElement(cmd, i);argv[i] = (char *) env->GetStringUTFChars(str, JNI_FALSE);logDebug("%s ", argv[i]);}return exe_cmd(argc, argv);
//    return 1;
}

执行命令

private void exeCmd() {String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator+ "get_cover1.mp4";String outPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator+ "video.flv";File outFile = new File(outPath);if (outFile.exists()) {outFile.delete();}//裁剪个1s视频String cmd = "ffmpeg -ss 00:00:00 -t 00:00:10 -i " + path + " -vcodec copy -acodec copy " + outPath;String[] cmdArr = cmd.split(" ");int result = exeCmd(cmdArr);Log.d(TAG, "exe cmd result == " + result);}

查看日志输出

demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log -----   Total: 331 packets (1993540 bytes) demuxed
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- Output file #0 (/storage/emulated/0/video.flv):
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log -----   Output stream #0:0 (video): 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 129 packets muxed (1822580 bytes); 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log -----   Output stream #0:1 (audio): 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 202 packets muxed (170960 bytes); 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log -----   Total: 331 packets (1993540 bytes) muxed
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 0 frames successfully decoded, 0 decoding errors
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- [AVIOContext @ 0xda2ea480] Statistics: 2 seeks, 10 writeouts
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- [AVIOContext @ 0xda2ea3c0] Statistics: 2161643 bytes read, 1 seeks
demo.simple.example_ffmpeg D/MainActivity: ffmpeg log ----- 
demo.simple.example_ffmpeg D/MainActivity: exe cmd result == 0

执行命令的返回值==0,并且也看到确实文件已经生成出来了,我们adb pull把文件导出到桌面用ffprobeffplay看看。

ffprobe video.flv
ffprobe version 4.0.4 Copyright (c) 2007-2019 the FFmpeg developers
built with Apple LLVM version 10.0.0 (clang-1000.10.44.4)
configuration:
libavutil      56. 14.100 / 56. 14.100
libavcodec     58. 18.100 / 58. 18.100
libavformat    58. 12.100 / 58. 12.100
libavdevice    58.  3.100 / 58.  3.100
libavfilter     7. 16.100 /  7. 16.100
libswscale      5.  1.100 /  5.  1.100
libswresample   3.  1.100 /  3.  1.100
Input #0, flv, from 'video.flv':
Metadata:major_brand     : mp42minor_version   : 0compatible_brands: mp42mp41encoder         : Lavf58.45.100
Duration: 00:00:01.07, start: 0.033000, bitrate: 3130 kb/sStream #0:0: Video: h264 (Main), yuv420p(tv, bt709, progressive), 1080x1920, 3390 kb/s, 30 fps, 30 tbr, 1k tbn, 60 tbcStream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp, 317 kb/s

可以看到确实裁剪生成了一个1秒的视频,虽然后缀名我们用的.flv,但是其实我们是拷贝的视频编码,所以还是mp4的封装格式。

源码

使用已有的轮子

上面的例子并不是一个完善的工具类,比如缺少Native层的线程支持,出现错误就会直接闪退,缺少进度回调等,所以还是直接使用现成的轮子比较靠谱,只是我们需要知道轮子大概是怎么造出来的就行了。

这里我推荐使用mobile-ffmpeg这个开源库,1.8k的star足以证明其品质还行,直接导入编译好的aar就可以执行命令行工具链,而且可以自行编译链接很多有用的第三方library,比如x264libwebp 等。

动手能力强或有特殊需求的可以使用android.sh自行编译出FFmpeg头文件和动态库,以及Android工具链的aar。

比如说我现在只需要一个支持arm64-v8aapi16及以上的动态库,那么我就自己新建了一个shell脚本文件:

#!/bin/bashexport ANDROID_HOME="/Users/chenpeng/Library/Android/sdk/"
export ANDROID_NDK_ROOT="/Users/chenpeng/Desktop/work_space/ndk/android-ndk-r21b/"build() {./android.sh \--lts \--disable-arm-v7a \--disable-arm-v7a-neon \--disable-x86 \--disable-x86-64
}build

执行完这个shell后,就会在prebuilt目录下生产对应的头文件,动态库,以及aar文件,直接拿来用就可以了。

这篇关于编译可执行命令的FFmpeg的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1127781

相关文章

maven 编译构建可以执行的jar包

💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」👈,「stormsha的知识库」👈持续学习,不断总结,共同进步,为了踏实,做好当下事儿~ 专栏导航 Python系列: Python面试题合集,剑指大厂Git系列: Git操作技巧GO

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础:WAV专题(6)——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道,通过FFprobe命令可以打印WAV音频文件每个packet(也称为数据包或多媒体包)的信息,这些信息包含该packet的pts、dts: 打印出来的“pts”实际是AVPacket结构体中的成员变量pts,是以AVStream->time_base为单位的显

Windows环境利用VS2022编译 libvpx 源码教程

libvpx libvpx 是一个开源的视频编码库,由 WebM 项目开发和维护,专门用于 VP8 和 VP9 视频编码格式的编解码处理。它支持高质量的视频压缩,广泛应用于视频会议、在线教育、视频直播服务等多种场景中。libvpx 的特点包括跨平台兼容性、硬件加速支持以及灵活的接口设计,使其可以轻松集成到各种应用程序中。 libvpx 的安装和配置过程相对简单,用户可以从官方网站下载源代码

ffmpeg面向对象-待定

1.常用对象 rtsp拉流第一步都是avformat_open_input,其入参可以看下怎么用: AVFormatContext *fmt_ctx = NULL;result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); 其中fmt_ctx 如何分配内存的?如下 int avformat_open_input(

Golang test编译使用

创建文件my_test.go package testsimport "testing"func TestMy(t *testing.T) {t.Log("TestMy")} 通常用法: $ go test -v -run TestMy my_test.go=== RUN TestMyTestMy: my_test.go:6: TestMy--- PASS: TestMy (0.

C++/《C/C++程序编译流程》

程序的基本流程如图:   1.预处理        预处理相当于根据预处理指令组装新的C/C++程序。经过预处理,会产生一个没有宏定义,没有条件编译指令,没有特殊符号的输出文件,这个文件的含义同原本的文件无异,只是内容上有所不同。 读取C/C++源程序,对其中的伪指令(以#开头的指令)进行处理将所有的“#define”删除,并且展开所有的宏定义处理所有的条件编译指令,如:“#if”、“

编译linux内核出现 arm-eabi-gcc: error: : No such file or directory

external/e2fsprogs/lib/ext2fs/tdb.c:673:29: warning: comparison between : In function 'max2165_set_params': -。。。。。。。。。。。。。。。。。。 。。。。。。。。。。。。。 。。。。。。。。 host asm: libdvm <= dalvik/vm/mterp/out/Inte

FFmpeg系列-视频解码后保存帧图片为ppm

在正常开发中遇到花屏时怎么处理呢?可以把解码后的数据直接保存成帧图片保存起来,然后直接看图片有没有花屏来排除是否是显示的问题,如果花屏,则代表显示无问题,如果图片中没有花屏,则可以往显示的方向去排查了。 void saveFrame(AVFrame* pFrame, int width, int height, int iFrame){FILE *pFile;char szFilename[

QT 编译报错:C3861: ‘tr‘ identifier not found

问题: QT 编译报错:C3861: ‘tr’ identifier not found 原因 使用tr的地方所在的类没有继承自 QObject 类 或者在不在某一类中, 解决方案 就直接用类名引用 :QObject::tr( )

hector_quadrotor编译总结 | ubuntu 16.04 ros-kinetic版本

hector_quadrotor编译总结 | ubuntu 16.04 ros-kinetic版本 基于Ubuntu 16.04 LTS系统所用ROS版本为 Kinetic hector_quadrotor ROS包主要用于四旋翼无人机的建模、控制和仿真。 1.安装依赖库 所需系统及依赖库 Ubuntu 16.04|ros-kinetic|Gazebo|gazebo_ros_pkgs|ge