怎么让 FFmpeg 运行命令呢?很

上一篇文章实现了 FFmpeg 编译及 Android 端的简单调用,成功获取了 FFmpeg 支持的编解码信息,而在实际使用时,需要调用 FFmpeg 内部函数,或通过命令行方式调用,但后者简单很多。

怎么让 FFmpeg 运行命令呢?很简单,调用 FFmpeg 中执行命令的函数即可,这个函数位于源码的 ffmpeg.c 文件中:

int main(int argc, char **argv)
1
1
我们的目的很简单:将 FFmpeg 命令传递给 main 函数并执行。而这个传递过程需要编写底层代码实现,在这个底层接口代码中,接收上层传递过来的 FFmpeg 命令 ,然后调用 ffmpeg.c 中的 main 函数执行该命令。

开始集成之前,首先回顾一下 JNI 标准接入步骤:

编写带有 native 方法的 Java 类
生成该类扩展名为 .h 的头文件
创建该头文件的 C/C++ 文件,实现 native 方法
将该 C/C++ 文件编译成动态链接库
在Java 程序中加载该动态链接库
接下来按照此步骤开始集成,实现 android 端以命令方式调用 FFmpeg ,这里假设你已经编译过 FFmpeg 源码,具体编译方法可查看本系列第一篇。如果你是新手或对 Android 端集成底层库不太熟悉,强烈建议先阅读本系列第一篇 Android 集成 FFmpeg (一)基础知识及简单调用 。

首先新建一个文件夹 ndkBuild 作为工作空间,在 ndkBuild 目录下新建 jni 文件夹, 作为编译工作目录。

1. 编写带有 native 方法的 Java 类

package com.jni;

public class FFmpegJni {

public static native int run(String[] commands);
2. 生成该类扩展名为 .h 的头文件

在 Android Studio 的 Terminal 中 切换到 Java 目录下,运行 javah 命令生成头文件:

这里写图片描述

可以看到在 java 目录下生成了头文件:

这里写图片描述

然后将此头文件剪切到 jni 目录下。

3. 创建该头文件的 C/C++ 文件,实现 native 方法

在 jni 目录下创建对应的 C 文件 com_jni_FFmpegJni.c :

#include "android_log.h"
#include "com_jni_FFmpegJni.h"
#include "ffmpeg.h"

JNIEXPORT jint JNICALL Java_com_jni_FFmpegJni_run(JNIEnv *env, jclass obj, jobjectArray commands) {
int argc = (*env)->GetArrayLength(env, commands);
char *argv[argc];
int i;
for (i = 0; i < argc; i++) {
jstring js = (jstring) (*env)->GetObjectArrayElement(env, commands, i);
argv[i] = (char*) (*env)->GetStringUTFChars(env, js, 0);
}
LOGD("----------begin---------");
return main(argc, argv);
函数的主要作用就是将 Java 端传递过来的 jobjectArray 类型的 FFmpeg 命令,转换为 main 函数所需要的参数 argc 和 argv ,然后调用之。为了将日志输出函数简化为简洁的 “LOGD”、 “LOGE”,需要在同级目录下新建 android_log.h 文件:

#ifdef ANDROID
#include <android/log.h>
#ifndef LOG_TAG
#define MY_TAG "MYTAG"
#define AV_TAG "AVLOG"
#endif
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, MY_TAG, format, ##__VA_ARGS__)
#define LOGD(format, ...) __android_log_print(ANDROID_ www.yigozongdai2.cn LOG_DEBUG, MY_TAG, format, ##__VA_ARGS__)
#define XLOGD(...) __android_log_print(ANDROID_LOG_INFO,AV_TAG,__VA_ARGS__)
#define XLOGE(...) __android_log_print(ANDROID_LOG_ERROR,AV_TAG,__VA_ARGS__)
#else
#define LOGE(format, ...) printf(www.xyseo.net MY_TAG format "\n", ##__VA_ARGS__)
#define LOGD(format, ...) printf(MY_TAG format "\n", ##__VA_ARGS__)
其中 XLOGD 和 XLOGE 方法是为了将 FFmpeg 内部日志信息自动输出到 logcat,后面会用到。除 android_log.h 之外,很显然,还需要添加 ffmpeg.c 、ffmpeg.h 文件,实际上 ffmpeg.c 的 main 函数中还会调用到其他文件,所以需要从源码中拷贝 ffmpeg.h、ffmpeg.c、ffmpeg_opt.c、ffmpeg_filter.c、cmdutils.c、cmdutils.h 以及 cmdutils_common_opts.h 共 7 个文件到 jni 目录下。

此时 jni 目录下应该有以下 10 个文件:

这里写图片描述

接下来还要修改 ffmpeg.c 、cmdutils.c 以及 cmdutils.h 三个文件使其适用于 Android 端调用,按功能分为以下三点:

1.日志输出到 logcat (修改 ffmpeg.c)

在执行命令过程中,FFmpeg 内部的日志系统会输出很多有用的信息,但是在 Android 的 logcat 中是看不到的,所以需要修改源码将 FFmpeg 内部日志输出 logcat 中,方便调试,其实这是十分必要的。修改方法很简单,只需修改 ffmpeg.c 文件三处:

引入 android_log.h 头文件:

#include "android_log.h"
1
1
修改 log_callback_null 方法为下:(原方法为空)

static void log_callback_null(void www.zenmebanw.com *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);
if (level <= AV_LOG_WARNING){
XLOGE("%s", line);
}else{
XLOGD("%s", line);
修改为:

int exit_program(int ret)
{
return ret;
此处修改了方法的返回值类型,所以还需要修改对应头文件中的方法声明,即将 cmdutils.h 中的:

void exit_program(int ret) av_noreturn;
1
1
修改为:

int exit_program(int ret);
1
1
到这里需要修改项都已修改完毕,网上教程实现 FFmpeg 内部日志输出到 logcat 的并不多,但这一步是十分有必要的。很多教程中需要将 ffmpeg 中的 main 方法名字修改为 “run” 、”exec” 等等,其实完全没必要,为什么要对方法名这么在意,乃至不惜徒增新手学习的复杂度呢? 我不知道修改的原因和意义所在。 有些教程中需要把 config.h 文件也拷贝到 jni 目录下,而我并没有拷贝,那么到底需不需要呢?FFmpeg 的命令数不胜数,我只能说我执行过的命令都不需要拷贝 /www.wmyl11.com config.h ,尽管源码 ffmpeg.c 中就声明了引入 config.h 文件。

4. 将该 C/C++ 文件编译成动态链接库

在 jni 目录下创建 Android.mk 文件 :

LOCAL_PATH:= $(call my-dir)

#编译好的 FFmpeg 头文件目录
INCLUDE_PATH:=/home/yhao/sf/ffmpeg-3.3.3/Android/arm/include

#编译好的 FFmpeg 动态库目录
FFMPEG_LIB_PATH:=/home/yhao/sf/ffmpeg-3.3.3/Android/arm/lib

include $(CLEAR_VARS)
LOCAL_MODULE:= libavcodec
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavcodec-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavformat
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavformat-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libswscale
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswscale-4.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_/www.wmyl88.com LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavutil
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavutil-55.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavfilter
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libavfilter-6.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libswresample
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libswresample-2.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libpostproc
LOCAL_SRC_FILES:= $(FFMPEG_LIB_PATH)/libpostproc-54.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE:= libavdevice
LOCAL_SRC_FILES:= $(FFMPEG_ http://blog.sina.com.cn/u/5080116125 LIB_PATH)/libavdevice-57.so
LOCAL_EXPORT_C_INCLUDES := $(INCLUDE_PATH)
include $(PREBUILT_SHARED_www.tips139.com/ LIBRARY)

include $(CLEAR_VARS)
LOCAL_MODULE := ffmpeg
LOCAL_SRC_FILES := com_jni_FFmpegJni.c \
cmdutils.c \
ffmpeg.c \
ffmpeg_opt.c \
ffmpeg_filter.c
LOCAL_C_INCLUDES := /home/yhao/sf/ffmpeg-3.3.3
LOCAL_LDLIBS := -lm -llog
LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice
include $(BUILD_SHARED_LIBRARY)
此处使用的 FFmpeg 为本系列上篇文章中的编译配置,其中引入了 libmp3lame 库以支持 mp3 格式编码,与上篇文章不同的是最后对 ffmpeg 动态库的编译,加入了 ffmpeg.c 、www.wmyl130.com cmdutils.c 等文件。

然后在 jni 目录下创建 Application.mk 文件:

APP_ABI := armeabi-v7a
APP_PLATFORM=android-14
NDK_TOOLCHAIN_VERSION=4.9
这时 jni 目录应该有以下 12 个文件:

这里写图片描述

一切准备就绪,在 jni 目录下运行 ndk 编译命令:

ndk-build
1
1
然后就可以在 ndkBuild 目录下看到生成的 libs 和 obj 文件夹了。

5.在Java 程序中加载该动态链接库

将 libs 目录下生成的 armeabi-v7a 动态库拷贝到 Android 工程中,此时工程应该是这样的:

这里写图片描述

在 FFmpegJni.java 中加载动态库:

package com.jni;

public class FFmpegJni {
static {
System.loadLibrary("avutil-55");
System.loadLibrary("avcodec-57");
System.loadLibrary("avformat-57");
System.loadLibrary("avdevice-57");
System.loadLibrary("swresample-2");
System.loadLibrary("swscale-4");
System.loadLibrary("postproc-54");
System.loadLibrary("avfilter-6");
System.loadLibrary("ffmpeg");
}
public static native int run(String[] commands);
记得在应用的 build.gradle 文件中 android 节点下添加动态库加载路径:

sourceSets {
main {
jniLibs.srcDirs = [‘libs‘]
OK,至此集成工作就全部完成了,在程序中调用 run 方法,就能以命令方式调用 FFmpeg 。

接下来以剪切 mp3 文件为例,验证是否集成成功,首先需要准备一个 mp3 文件,这里我提供两首歌:泡沫、童话镇,下载解压后直接将其放到手机根目录下即可。

直接给出 MainActivity 代码:

import com.jni.FFmpegJni;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if ( ActivityCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE }, 1);
}
}

public void run(View view) {
String dir = Environment.getExternalStorageDirectory().getPath() + "/ffmpegTest/";

//ffmpeg -i source_mp3.mp3 -ss 00:01:12 -t 00:01:42 -acodec copy output_mp3.mp3
String[] commands = new String[10];
commands[0] = "ffmpeg";
commands[1] = "-i";
commands[2] = dir+"paomo.mp3";
commands[3] = "-ss";
commands[4] = "00:01:00";
commands[5] = "-t";
commands[6] = "00:01:00";
commands[7] = "-acodec";
commands[8] = "copy";
commands[9] = dir+"paomo_cut_mp3.mp3";

int result = FFmpegJni.run(commands);
Toast.makeText(MainActivity.this, "命令行执行完成 result="+result, Toast.LENGTH_SHORT).show();
记得在 AndroidManifest.xml 中声明权限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
1
2
1
2
运行之后,播放生成的 paomo_cut_mp3 试试,剪切成功~ 但是一个例子不够过瘾,再来一个延时播放的命令:

String[] commands = new String[12];
commands[0] = "ffmpeg";
commands[1] = "-i";
commands[2] = dir+"paomo.mp3";
commands[3] = "-filter_complex";
commands[4] = "adelay=5000|5000";
commands[5] = "-ac"; //声道数
commands[6] = "1";
commands[7] = "-ar"; //采样率
commands[8] = "24k";
commands[9] = "-ab"; //比特率
commands[10] = "32k";
commands[11] = dir+"adelay_output.mp3";
更简单的方法实现

本文的核心工作是 ffmpeg.c 等文件的修改及编译,其实这些文件的修改是一劳永逸的。无论你的 FFmpeg 如何配置编译选项,不管是支持 mp3 编码还是支持 h264 编码,对 ffmpeg.c 等文件的修改内容都是固定的,所以我把这个工作目录上传到了 github ,方便大家直接拿来使用。

github 地址 : https://github.com/yhaolpz/ffmpeg-command-ndkBuild

接下来演示一下如何使用,首先要将 ffmpeg-command-ndkBuild 克隆到你的电脑上,注意这里默认你已经编译过 FFmpeg,编译方法见本系列第一章。

1. 编写带有 native 方法的 Java 类

package com.jni;

public class FFmpegJni {

public static native int run(String[] commands);
如果你编写的 Java 类跟上面这个类的包名、类名和方法都相同,那就直接跳到第 4 步,因为你可以直接使用 jni 目录中的 com_jni_FFmpegJni.c 和 com_jni_FFmpegJni.h 。

2. 生成该类扩展名为 .h 的头文件

在 Android Studio 的 Terminal 中 切换到 Java 目录下,运行 javah 命令生成头文件:

javah -classpath . com.包名.类名
1
1
将该头文件拷贝到 jni 目录下,并且删除 com_jni_FFmpegJni.h 文件。

3. 创建该头文件的 C/C++ 文件,实现 native 方法

这里不需要重新创建 C 文件,直接在 com_jni_FFmpegJni.c 基础上修改即可。

修改第2行 com_jni_FFmpegJni.h 为你自己的头文件名
修改 Java_com_jni_FFmpegJni_run 方法名为你自己的头文件中的方法名
修改 com_jni_FFmpegJni.c 文件名为你自己的头文件名
修改 Android.mk 中文件最后的 com_jni_FFmpegJni.c 为你自己的 C 文件名
4. 将该 C/C++ 文件编译成动态链接库

修改 Android.mk 中的 INCLUDE_PATH 、FFMPEG_LIB_PATH 为你自己编译好的 FFmpeg 动态库路径
修改 Android.mk 倒数第四行的 LOCAL_C_INCLUDES 为你自己的 FFmpeg 源码路径。
OK~ 直接运行 ndk-build 命令编译吧,最后一步 “在Java 程序中加载该动态链接库” 以及调用案例在上文中已经描述的很详细了,就不再赘述了。

总结

本文延续第一篇的规则,将编译工作置于 Android 工程之外进行,直到生成最后可用的 so 库再移植到 Android 工程中,我相信这样对 jni 的理解会更清晰一些。

本文中还有两个问题待解决,在 Application.mk 文件中通过 NDK_TOOLCHAIN_VERSION=4.9 指定编译器为 4.9 版本的 gcc,若不指定,将默认使用 clang 编译器,这时编译会报错,提示缺少一些文件。第二个问题就是 Application.mk 中通过 APP_ABI := armeabi-v7a 指定生成 armv7 架构动态库,若改成 armeabi 架构编译则会报错,提示不支持该模式,后续尽快解决这两个问题。

时间: 2025-01-03 15:47:35

怎么让 FFmpeg 运行命令呢?很的相关文章

nobup 与 后台运行命令

1. Linux进程状态:R (TASK_RUNNING),可执行状态&运行状态(在run_queue队列里的状态) 2. Linux进程状态:S (TASK_INTERRUPTIBLE),可中断的睡眠状态, 可处理signal 3. Linux进程状态:D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态, 可处理signal, 有延迟 4. Linux进程状态:T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态, 不可处理signal, 因为根

windows运行命令大全

winver 检查Windows版本 wmimgmt.msc 打开Windows管理体系结构(wmi) wupdmgr Windows更新程序 w脚本 Windows脚本宿主设置 write 写字板 winmsd 系统信息 wiaacmgr 扫描仪和照相机向导 winchat xp自带局域网聊天 mem.exe 显示内存使用情况 msconfig.exe 系统配置实用程序 mplayer2 简易widnows media player mspaint 画图板 mstsc 远程桌面连接 mplay

断开ssh链接在后台继续运行命令

转载:http://blog.csdn.net/v1v1wang/article/details/6855552 对于linux运维,我们都是使用ssh登录到服务器,如果我们运行的任务需要很长时间或不间断运行,在我们直接关闭终端窗口或网络不稳定的情况下,任务就会中断,当然这只对于普通程序,不包括如mysqld,httpd这样的守护进程.原因分析: [[email protected] ~]# ping 51osos.com > /dev/null & [1] 13678 [[email pr

Win8系统108个运行命令 你能记住多少?(转)

 Win8运行命令:程序和功能 取消了开始菜单的Win8让人感觉很不习惯,这才发现原来开始菜单可以做这么多事.不过Win8中的一些快捷键还沿用了Windows一直以来的习惯,比如按下Windows + R打开"运行"对话框.在这里我们可以通过命令来打开各种应用程序或系统设置,不过这需要你有超强的记忆力,能把Win8中所有的运行命令都记住. 以下这108条运行命令都是大家经常会用到的,看看你能记住多少? Win8系统108个运行命令 你能记住多少? 1.appwiz.cpl:程序和功

Windows XP运行命令

运行程序&运行命令辅助功效选项access.cpl添加硬件向导hdwwiz.cpl添加或者删除程序appwiz.cpl管理工具control admintools自动更新w pl.cpl Bltooth文件传送向导fsquirt计算器calc证书管理节制台certmgr.msc字符照射表charmap磁盘查抄工具chkdsk剪贴簿查看器clipbrd命令行提示符cmd组件服务dcomcnfg计算机管理compmgmt.msc日期和时间属性timedate.cpl DDE同享ddeshare装备管

高级电脑操作就是拒绝不确定性--window运行命令大全

记得很久之前看人家在命令行里对电脑进行操作,觉得好厉害的样子.其实,类似的操作是因为,他们使用了一种对他们而言更加快捷(相对熟悉来说)的方法来执行操作.而对于高手,都是拒绝不确定性的.也就是编程中的不二性--我的一个语句说什么就是执行什么,不由得你有歧义的理解.比如在命令行中打开个程序,你去寻找一个快捷图标,还不确定那个图标有没改变位置,需要去寻找.而不管你在哪个机子上,只要你记得对应打开该程序的命令行命令,就可以闭着眼睛直接操作了.这像极了五笔的盲打跟在自己机子上用拼音智能输入法"盲打&quo

Linux批量运行命令

需求: 要在Linux下面执行很多条命令,并且每条命令执行的时间会很长. 解决办法: 编写一个sh脚本,将多条命令放入到此脚本中,执行执行脚本就可以了. 例: test.sh Linux代码   #!/bin/sh java -classpath :/home/javaliujie/mysql-connector-java-3.1.12-bin.jar:/home/javaliujie/myLib.jar: cn.com.TestMain 20090201 java -classpath :/h

Windows7下的Run运行命令一览表

按住Windows键(就是左边Ctrl和Alt之间那个印windows徽标的键,简称Win键)+R,即可弹出运行对话框,在里面输入黑体字符即可运行相应程序.相比XP这次新增了不少新东西. 添加/删除程序 = appwiz.cpl管理工具 = control admintools (注:其实是打开一堆管理工具快捷方式)授权管理器 = azman.msc 新增 计算器 = calc证书管理器 = certmgr.msc字符映射表 = charmap磁盘检查工具 = chkdsk控制面板 = cont

nohup 后台运行命令

在Linux上部署zipkin,在SSH客户端执行java -jar zipkin-server-1.21.0-exec.jar,启动成功,在关闭SSH客户端后,运行的程序也同时终止了,怎样才能保证在推出SSH客户端后程序能一直执行呢?通过网上查找资料,发现需要使用nohup命令. 完美解决方案:nohup java -jar zipkin-server-1.21.0-exec.jar >output 2>&1 & 现对上面的命令进行下解释 用途:不挂断地运行命令.语法:noh