FFmpeg For Android (三) 移植main函数到安卓上执行ffmpeg命令
2016-11-25 17:26
489 查看
根据上一篇文章《FFmpeg For Android (二) Ubuntu下编译FFmpeg源码》我们熟悉了如何在Ubuntu下编译FFmpeg, 但我们却不知道有何用…
本篇将讲解 移植ffmpeg 的main函数到安卓上 直接执行ffmpeg命令
现在你将可以做很多有趣的app 比如 支持几乎全格式的视频、音频转换、剪切、合并、水印、混合、视频转gif 获取某一帧图片(则获取支持全格式视频的缩略图)等。。。
有木有开始激动了???
如下(注意把路径换成你系统的路径 并检查路径是否存在 (已经在上一篇说了 这篇不再详细讲解)
然后保存并开始编译FFmpeg
编译好了 得到的so文件变多了, 如图
/home/ubuntu/桌面/Android/FFmpeg/ffmpeg-3.2/android/arm下
win下也需要安装NDK环境 教程太多太简单 这里不再讲解 请自行解决(能看到这个文章的人一般都装好了 win版的NDK )…
我们回到Windows 打开Android Studio新建一个工程MyFFmpeg
开启Android Studio 对C/C++支持
编辑local.properties文件,添加ndk路径(请按你windows下的ndk实际路径 不要照抄)
编辑gradle.properties文件
末尾添加
然后rebuild project一下
在MyFFmpeg\app\新建一个该路径的文件夹 jnicode\jni
在jni文件夹新建 prebuilt 文件夹
回到Ubuntu系统 进入
/home/ubuntu/桌面/Android/FFmpeg/ffmpeg-3.2
把已解压并且已编译好的FFmpeg so库一起压缩 , 就是直接压缩ffmpeg-3.2 文件夹
压缩格式选择rar 避免win下不能解压..
呐…压缩好了 如图
直接拖动到win下 (如果你的不支持 请想其他办法)
回到Win下 解压ffmpeg-3.2.rar文件 到我们刚才那个工程的目录里
D:\Android\AndroidStudioProjects\MyFFmpeg\ffmpeg-3.2\
进入复制 fmpeg-3.2\android\arm\lib下的所有so文件到
MyFFmpeg\app\jnicode\jni\prebuilt下
复制整个ffmpeg-3.2\android\arm\include文件夹 到MyFFmpeg\app\jnicode\jni\下
以上两次复制 项目结构变成了这样
编写native方法 , 新建类FFmpegCmd .java 简单的添加一个exec()方法(等会还要改)
然后编译为class文件 你可以直接使用android studio的make功能
点击菜单项里的>Build>Make Module app
然后class文件生成到 MyFFmpeg\app\build\intermediates\classes\debug\下
然后使用javah命令去生成头文件
打开win命令行
cd 进入到 刚才那个debug文件夹下执行 javah -jni 包名.类名,如:
如图
然后看到debug目录下生成了头文件
com_toshiba_ffmpeg_FFmpegCmd.h
把刚才生成的头文件复制到MyFFmpeg\app\jnicode\jni\下
com_toshiba_ffmpeg_FFmpegCmd.h的内容为:
然后, 我们新建一个相同名称的文件 并修改后缀为.c 并导入相关头文件实现其对应函数
com_toshiba_ffmpeg_FFmpegCmd.c的内容为(等会还要改)
在 MyFFmpeg\app\jnicode\jni\下 创建Android.mk Application.mk 空文件
Android.mk内容为
如果你不会这里这些语法 那么你肯定是跳级 直接来学ffmpeg了 这些配置的意义和注意事项应该是在你学完jni基础再来看这篇文章才能懂的 建议先去学一遍jni (我等后面有空再写这方面的文章)
先注意两个地方:
LOCAL_SRC_FILES 这里指定so文件或c/cpp等待编译文件的路径
LOCAL_C_INCLUDES 这里是重点 导入ffmpeg的源码 这样方便打包时不会提示缺少xxx文件 当然不会把ffmpeg全部打包进去
LOCAL_SHARED_LIBRARIES 里的名称要和LOCAL_MODULE 里的名称一致
Application.mk内容为
APP_ABI 这里打包只是为了支持ARMv7的设备 其他的请改成 armeabi armeabi-v7a x86以便支持更多设备
APP_PLATFORM 指定哪个版本没太多要求
最后经过上面的一番折腾
项目结构应该是这样子的 (注意观察你的是否跟我的结构一致)
复制以下文件
复制到我们的项目 MyFFmpeg\app\jnicode\jni\下
编辑ffmpeg.c 查找main(int argc, char **argv) 函数
并修改名称为ffmpeg_exec 如下:
来到ffmpeg.h头文件
末尾 添加函数声明如下 (#endif之前)
然后到ffmpeg.c 找到
static void ffmpeg_cleanup(int ret) 函数
在函数末尾添加以下内容 清除计数(实测 如果不这样做 第二次执行命令时会导致闪退 可能是ffmpeg的bug )
为了防止ffmpeg_exec执行完成回调和错误时及时返回
我们需要使用子线程来调用ffmpeg_exec 以便某些时刻及时中断停止线程
于是 我们新建一个工具类ffmpeg_thread 目的是子线程执行ffmpeg命令 和 完成回调给com_toshiba_ffmpeg_FFmpegCmd.c
ffmpeg_thread.h内容为
对应的ffmpeg_thread.c内容为:
来到cmdutils.c文件
导入头文件
为了防止指令调用错误或完成导致程序终止 我们修改exit_program函数 屏蔽 exit函数换成我们的停止线程函数
则:
修改为
这时 我们要在java层实现相关回调, 此时重新回到我们的FFmpegCmd.java
并完善所有功能
重新回到我们的com_toshiba_ffmpeg_FFmpegCmd.c文件
导入相关头文件并且通过jni调用该ffmpeg_thread_run_cmd函数 ,如下,
代码注释清晰明了:
回到Android.mk文件 编辑
找到LOCAL_SRC_FILES :=com_toshiba_ffmpeg_FFmpegCmd.c这行
我们继续添加一些需要编译的文件
算了 直接贴 完整Android.mk文件内容 ,给你们参考:
我们打开win下的命令行
cd 到 MyFFmpeg\app\jnicode\jni下
然后直接执行
不出意外 so文件正常编译输出…如图:
可能会有一些 warning: ‘xxxx ’ 警告 正常,请无视
但如果遇到error的话 那请检查代码是否有错误
文件输出在 MyFFmpeg\app\jnicode\libs下
指定lib库的位置
如果手机是6.0的 请把build.gradle修改目标sdk如果大于23请改成小于23 如: targetSdkVersion 22
否则你将要自己申请权限 这是测试例子没必要搞的那么麻烦
然后
回到我们的java代码
新建一个简单的MainActivity
这里演示几个比较简单的
1.音频剪切 剪切一首歌曲从00:01:40 到00:02:30 这段音频
2.任意格式的视频转换为任意格式的视频 支持分辨率 比特率 音频数据流 帧数等相关设置
3.其他的请自己去测试…
布局:
MainActivity :
最后别忘了添加权限
打包运行apk
先在手机sd卡根目录放一个test.mp3文件
然后回到我们的app 点击’剪切音频’按钮
结果是输出Toast 执行完成=0,回到sd卡下 我们看到test_out.mp3正常输出,笔者试听了下 并注意观察剪切的时间段 答案肯定是正确的—歌曲被剪切了,测试成功
(Ps:第二次点执行前要把sd卡的test_out.mp3删掉,否则执行失败, 这不属于程序的问题)
然后再尝试视频格式转换 为了方便测试 我是在sd卡根目录放了个很小的MP4视频文件
然后回到app转换 成功 视频越大转换时间越久 (用过电脑的格式工厂吧? )
然后回到sd卡 发现test_out.flv已经生成 点击正常播放 功能没问题…
然后 你可以做很多有趣的app了
本Demo完整源码在教程最后 …
下面的命令部分内容转来自
http://blog.csdn.net/weiyuefei/article/details/51678582
http://www.cnblogs.com/wainiwann/p/4128154.html
命令格式:
ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件]
ffmpeg [[options][`-i’ input_file]]… {[options] output_file}…
1、参数选项:
(1) -an: 去掉音频
(2) -acodec: 音频选项, 一般后面加copy表示拷贝
(3) -vcodec:视频选项,一般后面加copy表示拷贝
2、格式:
(1) h264: 表示输出的是h264的视频裸流
(2) mp4: 表示输出的是mp4的视频
(3)mpegts: 表示ts视频流
如果没有输入文件,那么视音频捕捉(只在Linux下有效,因为Linux下把音视频设备当作文件句柄来处理)就会起作用。作为通用的规则,选项一般用于下一个特定的文件。如果你给 –b 64选项,改选会设置下一个视频速率。对于原始输入文件,格式选项可能是需要的。缺省情况下,ffmpeg试图尽可能的无损转换,采用与输入同样的音频视频参数来输出。(by ternence.hsu)
安卓高级开发者交流群欢迎您加入
521231038
本篇将讲解 移植ffmpeg 的main函数到安卓上 直接执行ffmpeg命令
现在你将可以做很多有趣的app 比如 支持几乎全格式的视频、音频转换、剪切、合并、水印、混合、视频转gif 获取某一帧图片(则获取支持全格式视频的缩略图)等。。。
有木有开始激动了???
1.重新编译FFmpeg, 开启configure相关参数
回到build_andorid.sh文件 编辑–enable一些相关配置如下(注意把路径换成你系统的路径 并检查路径是否存在 (已经在上一篇说了 这篇不再详细讲解)
NDK=/home/ubuntu/桌面/Android/NDK/android-ndk-r10e SYSROOT=$NDK/platforms/android-9/arch-arm/ TOOLCHAIN=$NDK/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64 function build_android { ./configure \ --prefix=/home/ubuntu/桌面/Android/FFmpeg/ffmpeg-3.2/android/arm \ --enable-neon \ --enable-hwaccel=h264_vaapi \ --enable-hwaccel=h264_vaapi \ --enable-hwaccel=h264_dxva2 \ --enable-hwaccel=mpeg4_vaapi \ --enable-hwaccels \ --enable-shared \ --enable-jni \ --enable-mediacodec \ --disable-static \ --disable-doc \ --enable-ffmpeg \ --disable-ffplay \ --disable-ffprobe \ --disable-ffserver \ --enable-avdevice \ --disable-doc \ --disable-symver \ --cross-prefix=$TOOLCHAIN/bin/arm-linux-androideabi- \ --target-os=linux \ --arch=arm \ --enable-cross-compile \ --sysroot=$SYSROOT \ --extra-cflags="-Os -fpic $ADDI_CFLAGS" \ --extra-ldflags="$ADDI_LDFLAGS" \ $ADDITIONAL_CONFIGURE_FLAG make clean make -j4 make install } ADDI_CFLAGS="-marm" build_android
然后保存并开始编译FFmpeg
编译好了 得到的so文件变多了, 如图
/home/ubuntu/桌面/Android/FFmpeg/ffmpeg-3.2/android/arm下
2.编写Android C/C++程序
这里 肯定用jni了…win下也需要安装NDK环境 教程太多太简单 这里不再讲解 请自行解决(能看到这个文章的人一般都装好了 win版的NDK )…
我们回到Windows 打开Android Studio新建一个工程MyFFmpeg
开启Android Studio 对C/C++支持
编辑local.properties文件,添加ndk路径(请按你windows下的ndk实际路径 不要照抄)
ndk.dir=D\:\\Android\\sdk\\ndk-bundle sdk.dir=D\:\\Android\\sdk
编辑gradle.properties文件
末尾添加
android.useDeprecatedNdk = true
然后rebuild project一下
在MyFFmpeg\app\新建一个该路径的文件夹 jnicode\jni
在jni文件夹新建 prebuilt 文件夹
回到Ubuntu系统 进入
/home/ubuntu/桌面/Android/FFmpeg/ffmpeg-3.2
把已解压并且已编译好的FFmpeg so库一起压缩 , 就是直接压缩ffmpeg-3.2 文件夹
压缩格式选择rar 避免win下不能解压..
呐…压缩好了 如图
直接拖动到win下 (如果你的不支持 请想其他办法)
回到Win下 解压ffmpeg-3.2.rar文件 到我们刚才那个工程的目录里
D:\Android\AndroidStudioProjects\MyFFmpeg\ffmpeg-3.2\
进入复制 fmpeg-3.2\android\arm\lib下的所有so文件到
MyFFmpeg\app\jnicode\jni\prebuilt下
复制整个ffmpeg-3.2\android\arm\include文件夹 到MyFFmpeg\app\jnicode\jni\下
以上两次复制 项目结构变成了这样
编写native方法 , 新建类FFmpegCmd .java 简单的添加一个exec()方法(等会还要改)
package com.toshiba.ffmpeg; /** * 作者:东芝(2016/11/23). */ public class FFmpegCmd { public static native int exec(int argc,String[] argv); }
然后编译为class文件 你可以直接使用android studio的make功能
点击菜单项里的>Build>Make Module app
然后class文件生成到 MyFFmpeg\app\build\intermediates\classes\debug\下
然后使用javah命令去生成头文件
打开win命令行
cd 进入到 刚才那个debug文件夹下执行 javah -jni 包名.类名,如:
javah -jni com.toshiba.ffmpeg.FFmpegCmd
如图
然后看到debug目录下生成了头文件
com_toshiba_ffmpeg_FFmpegCmd.h
把刚才生成的头文件复制到MyFFmpeg\app\jnicode\jni\下
com_toshiba_ffmpeg_FFmpegCmd.h的内容为:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_toshiba_ffmpeg_FFmpegCmd */ #ifndef _Included_com_toshiba_ffmpeg_FFmpegCmd #define _Included_com_toshiba_ffmpeg_FFmpegCmd #ifdef __cplusplus extern "C" { #endif /* * Class: com_toshiba_ffmpeg_FFmpegCmd * Method: exec * Signature: (I[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_toshiba_ffmpeg_FFmpegCmd_exec (JNIEnv *, jclass, jint, jobjectArray); #ifdef __cplusplus } #endif #endif
然后, 我们新建一个相同名称的文件 并修改后缀为.c 并导入相关头文件实现其对应函数
com_toshiba_ffmpeg_FFmpegCmd.c的内容为(等会还要改)
#include "com_toshiba_ffmpeg_FFmpegCmd.h" #include <string.h> /* * Class: com_toshiba_ffmpeg_FFmpegCmd * Method: exec * Signature: (I[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_toshiba_ffmpeg_FFmpegCmd_exec (JNIEnv *env, jclass clazz, jint cmdnum, jobjectArray cmdline){ return 1111;//临时测试数字 }
在 MyFFmpeg\app\jnicode\jni\下 创建Android.mk Application.mk 空文件
Android.mk内容为
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libavutil LOCAL_SRC_FILES := prebuilt/libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswresample LOCAL_SRC_FILES := prebuilt/libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswscale LOCAL_SRC_FILES := prebuilt/libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavcodec LOCAL_SRC_FILES := prebuilt/libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavformat LOCAL_SRC_FILES := prebuilt/libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavfilter LOCAL_SRC_FILES := prebuilt/libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavdevice LOCAL_SRC_FILES := prebuilt/libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg LOCAL_SRC_FILES :=com_toshiba_ffmpeg_FFmpegCmd.c LOCAL_C_INCLUDES := D:\Android\AndroidStudioProjects\MyFFmpeg\ffmpeg-3.2 LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice include $(BUILD_SHARED_LIBRARY)
如果你不会这里这些语法 那么你肯定是跳级 直接来学ffmpeg了 这些配置的意义和注意事项应该是在你学完jni基础再来看这篇文章才能懂的 建议先去学一遍jni (我等后面有空再写这方面的文章)
先注意两个地方:
LOCAL_SRC_FILES 这里指定so文件或c/cpp等待编译文件的路径
LOCAL_C_INCLUDES 这里是重点 导入ffmpeg的源码 这样方便打包时不会提示缺少xxx文件 当然不会把ffmpeg全部打包进去
LOCAL_SHARED_LIBRARIES 里的名称要和LOCAL_MODULE 里的名称一致
Application.mk内容为
APP_ABI := armeabi-v7a APP_PLATFORM=android-14
APP_ABI 这里打包只是为了支持ARMv7的设备 其他的请改成 armeabi armeabi-v7a x86以便支持更多设备
APP_PLATFORM 指定哪个版本没太多要求
最后经过上面的一番折腾
项目结构应该是这样子的 (注意观察你的是否跟我的结构一致)
3.移植main函数
到 MyFFmpeg\ffmpeg-3.2\下复制以下文件
cmdutils.c cmdutils.h cmdutils_common_opts.h config.h ffmpeg.c ffmpeg.h ffmpeg_filter.c ffmpeg_opt.c
复制到我们的项目 MyFFmpeg\app\jnicode\jni\下
编辑ffmpeg.c 查找main(int argc, char **argv) 函数
并修改名称为ffmpeg_exec 如下:
int ffmpeg_exec(int argc, char **argv){ //...略 }
来到ffmpeg.h头文件
末尾 添加函数声明如下 (#endif之前)
int ffmpeg_exec(int argc, char **argv);
然后到ffmpeg.c 找到
static void ffmpeg_cleanup(int ret) 函数
在函数末尾添加以下内容 清除计数(实测 如果不这样做 第二次执行命令时会导致闪退 可能是ffmpeg的bug )
nb_filtergraphs = 0; nb_output_files = 0; nb_output_streams = 0; nb_input_files = 0; nb_input_streams = 0;
为了防止ffmpeg_exec执行完成回调和错误时及时返回
我们需要使用子线程来调用ffmpeg_exec 以便某些时刻及时中断停止线程
于是 我们新建一个工具类ffmpeg_thread 目的是子线程执行ffmpeg命令 和 完成回调给com_toshiba_ffmpeg_FFmpegCmd.c
ffmpeg_thread.h内容为
#include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "ffmpeg.h" #include <pthread.h> #include <string.h> int ffmpeg_thread_run_cmd(int cmdnum,char **argv); void ffmpeg_thread_exit(); void ffmpeg_thread_callback(void (*cb)(int ret));
对应的ffmpeg_thread.c内容为:
// // Created by 东芝 on 2016/11/25. // #include "ffmpeg_thread.h" pthread_t ntid; char **argvs = NULL; int num=0; void *thread(void *arg) { //执行 int result = ffmpeg_exec(num, argvs); return ((void *)0); } /** * 新建子线程执行ffmpeg命令 */ int ffmpeg_thread_run_cmd(int cmdnum,char **argv){ num=cmdnum; argvs=argv; int temp =pthread_create(&ntid,NULL,thread,NULL); if(temp!=0) { //LOGE("can't create thread: %s ",strerror(temp)); return 1; } return 0; } static void (*ffmpeg_callback)(int ret); /** * 注册线程回调 */ void ffmpeg_thread_callback(void (*cb)(int ret)){ ffmpeg_callback = cb; } /** * 退出线程 */ void ffmpeg_thread_exit(int ret){ if (ffmpeg_callback) { ffmpeg_callback(ret); } pthread_exit("ffmpeg_thread_exit"); }
来到cmdutils.c文件
导入头文件
#include "ffmpeg_thread.h"
为了防止指令调用错误或完成导致程序终止 我们修改exit_program函数 屏蔽 exit函数换成我们的停止线程函数
则:
void exit_program(int ret) { if (program_exit) program_exit(ret); exit(ret); }
修改为
void exit_program(int ret) { if (program_exit) { program_exit(ret); } //退出线程 ffmpeg_thread_exit(ret); // exit(ret); }
这时 我们要在java层实现相关回调, 此时重新回到我们的FFmpegCmd.java
并完善所有功能
package com.toshiba.ffmpeg; /** * 作者:东芝(2016/11/23). */ public class FFmpegCmd { /** * 加载所有相关链接库 */ static { System.loadLibrary("avutil-55"); System.loadLibrary("avcodec-57"); System.loadLibrary("swresample-2"); System.loadLibrary("avformat-57"); System.loadLibrary("swscale-4"); System.loadLibrary("avfilter-6"); System.loadLibrary("avdevice-57"); System.loadLibrary("ffmpeg"); } private static OnExecListener listener; /** * 调用底层执行 * @param argc * @param argv * @return */ public static native int exec(int argc, String[] argv); public static void onExecuted(int ret) { if (listener != null) { listener.onExecuted(ret); } } /** * 执行ffmoeg命令 * @param cmds * @param listener */ public static void exec(String[] cmds, OnExecListener listener) { FFmpegCmd.listener = listener; exec(cmds.length, cmds); } /** * 执行完成/错误 时的回调接口 */ public interface OnExecListener { void onExecuted(int ret); } }
重新回到我们的com_toshiba_ffmpeg_FFmpegCmd.c文件
导入相关头文件并且通过jni调用该ffmpeg_thread_run_cmd函数 ,如下,
代码注释清晰明了:
#include "com_toshiba_ffmpeg_FFmpegCmd.h" #include <string.h> #include "android_log.h" #include "ffmpeg_thread.h" static JavaVM *jvm = NULL; //java虚拟机 static jclass m_clazz = NULL;//当前类(面向java) /** * 回调执行Java方法 * 参看 Jni反射+Java反射 */ void callJavaMethod(JNIEnv *env, jclass clazz,int ret) { if (clazz == NULL) { LOGE("---------------clazz isNULL---------------"); return; } //获取方法ID (I)V指的是方法签名 通过javap -s -public FFmpegCmd 命令生成 jmethodID methodID = (*env)->GetStaticMethodID(env, clazz, "onExecuted", "(I)V"); if (methodID == NULL) { LOGE("---------------methodID isNULL---------------"); return; } //调用该java方法 (*env)->CallStaticVoidMethod(env, clazz, methodID,ret); } /** * c语言-线程回调 */ static void ffmpeg_callback(int ret) { JNIEnv *env; //附加到当前线程从JVM中取出JNIEnv, C/C++从子线程中直接回到Java里的方法时 必须经过这个步骤 (*jvm)->AttachCurrentThread(jvm, (void **) &env, NULL); callJavaMethod(env, m_clazz,ret); //完毕-脱离当前线程 (*jvm)->DetachCurrentThread(jvm); } /* * Class: com_toshiba_ffmpeg_FFmpegCmd * Method: exec * Signature: (I[Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_toshiba_ffmpeg_FFmpegCmd_exec (JNIEnv *env, jclass clazz, jint cmdnum, jobjectArray cmdline) { //---------------------------------C语言 反射Java 相关---------------------------------------- //在jni的c线程中不允许使用共用的env环境变量 但JavaVM在整个jvm中是共用的 可通过保存JavaVM指针,到时候再通过JavaVM指针取出JNIEnv *env; //ICS之前(你可把NDK sdk版本改成低于11) 可以直接写m_clazz = clazz;直接赋值, 然而ICS(sdk11) 后便改变了这一机制,在线程中回调java时 不能直接共用变量 必须使用NewGlobalRef创建全局对象 //官方文档正在拼命的解释这一原因,参看:http://android-developers.blogspot.jp/2011/11/jni-local-reference-changes-in-ics.html (*env)->GetJavaVM(env, &jvm); m_clazz = (*env)->NewGlobalRef(env, clazz); //---------------------------------C语言 反射Java 相关---------------------------------------- //---------------------------------java 数组转C语言数组---------------------------------------- int i = 0;//满足NDK所需的C99标准 char **argv = NULL;//命令集 二维指针 jstring *strr = NULL; if (cmdline != NULL) { argv = (char **) malloc(sizeof(char *) * cmdnum); strr = (jstring *) malloc(sizeof(jstring) * cmdnum); for (i = 0; i < cmdnum; ++i) {//转换 strr[i] = (jstring)(*env)->GetObjectArrayElement(env, cmdline, i); argv[i] = (char *) (*env)->GetStringUTFChars(env, strr[i], 0); } } //---------------------------------java 数组转C语言数组---------------------------------------- //---------------------------------执行FFmpeg命令相关---------------------------------------- //新建线程 执行ffmpeg 命令 ffmpeg_thread_run_cmd(cmdnum, argv); //注册ffmpeg命令执行完毕时的回调 ffmpeg_thread_callback(ffmpeg_callback); //---------------------------------执行FFmpeg命令相关---------------------------------------- free(strr); return 0; }
回到Android.mk文件 编辑
找到LOCAL_SRC_FILES :=com_toshiba_ffmpeg_FFmpegCmd.c这行
我们继续添加一些需要编译的文件
LOCAL_SRC_FILES :=com_toshiba_ffmpeg_FFmpegCmd.c \ cmdutils.c \ ffmpeg.c \ ffmpeg_opt.c \ ffmpeg_filter.c \ ffmpeg_thread.c
算了 直接贴 完整Android.mk文件内容 ,给你们参考:
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libavutil LOCAL_SRC_FILES := prebuilt/libavutil-55.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswresample LOCAL_SRC_FILES := prebuilt/libswresample-2.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libswscale LOCAL_SRC_FILES := prebuilt/libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavcodec LOCAL_SRC_FILES := prebuilt/libavcodec-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavformat LOCAL_SRC_FILES := prebuilt/libavformat-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavfilter LOCAL_SRC_FILES := prebuilt/libavfilter-6.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := libavdevice LOCAL_SRC_FILES := prebuilt/libavdevice-57.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_MODULE := ffmpeg LOCAL_SRC_FILES :=com_toshiba_ffmpeg_FFmpegCmd.c \ cmdutils.c \ ffmpeg.c \ ffmpeg_opt.c \ ffmpeg_filter.c \ ffmpeg_thread.c LOCAL_C_INCLUDES := D:\Android\AndroidStudioProjects\MyFFmpeg\ffmpeg-3.2 LOCAL_LDLIBS := -llog -ljnigraphics -lz -landroid -lm -pthread -L$(SYSROOT)/usr/lib LOCAL_SHARED_LIBRARIES := libavcodec libavfilter libavformat libavutil libswresample libswscale libavdevice include $(BUILD_SHARED_LIBRARY)
4.NDK编译
终于快完工了…我们打开win下的命令行
cd 到 MyFFmpeg\app\jnicode\jni下
cd D:\Android\AndroidStudioProjects\MyFFmpeg\app\jnicode\jni
然后直接执行
ndk-build
不出意外 so文件正常编译输出…如图:
可能会有一些 warning: ‘xxxx ’ 警告 正常,请无视
但如果遇到error的话 那请检查代码是否有错误
文件输出在 MyFFmpeg\app\jnicode\libs下
5.编写测试例子
修改app的build.gradle指定lib库的位置
sourceSets { main { jniLibs.srcDirs = ['jnicode/libs'] } }
如果手机是6.0的 请把build.gradle修改目标sdk如果大于23请改成小于23 如: targetSdkVersion 22
否则你将要自己申请权限 这是测试例子没必要搞的那么麻烦
然后
回到我们的java代码
新建一个简单的MainActivity
这里演示几个比较简单的
1.音频剪切 剪切一首歌曲从00:01:40 到00:02:30 这段音频
2.任意格式的视频转换为任意格式的视频 支持分辨率 比特率 音频数据流 帧数等相关设置
3.其他的请自己去测试…
布局:
<Button android:id="@+id/btnCutAudio" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="剪切音频" /> <Button android:id="@+id/btnConvertVideo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="视频格式转换" />
MainActivity :
package com.toshiba.ffmpeg; import android.app.ProgressDialog; import android.os.Environment; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.Toast; public class MainActivity extends AppCompatActivity { public ProgressDialog show; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final String path = Environment.getExternalStorageDirectory().getPath(); findViewById(R.id.btnCutAudio).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //原命令 ffmpeg -i input.mp3 -ss hh:mm:ss -t hh:mm:ss -acodec copy output.mp3 //flac有点问题, 支持mp3 wma m4a String cmd = "ffmpeg -i " + path + "/test.mp3" + " -ss 00:01:40 -t 00:02:30 -acodec copy " + path + "/test_out.mp3"; toExec(cmd); } }); findViewById(R.id.btnConvertVideo).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //原命令 ffmpeg -i "xxx.mp4" -y -ab 32 -ar 22050 -qscale 10 -s 640*480 -r 15 xxx_out.flv // -i 是 要转换文件名 // -y是 覆盖输出文件 // -ab 是 音频数据流, 如 128 64 // -ar 是 声音的频率 22050 基本都是这个。 // -qscale 是视频输出质量,后边的值越小质量越高,但是输出文件就越“肥” // -s 是输出 文件的尺寸大小! // -r 是 播放侦数。 String cmd = "ffmpeg -i " + path + "/test.mp4" + " -y -ab 32 -ar 22050 -qscale 10 -s 640*480 -r 15 " + path + "/test_out.flv"; toExec(cmd); } }); } private void toExec(String cmd) { show = ProgressDialog.show(MainActivity.this, null, "执行中...", true); //转换为数组 String[] cmds = cmd.split(" "); FFmpegCmd.exec(cmds, new FFmpegCmd.OnExecListener() { @Override public void onExecuted(final int ret) { runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "执行完成=" + ret, Toast.LENGTH_SHORT).show(); show.dismiss(); } }); } }); } }
最后别忘了添加权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
打包运行apk
先在手机sd卡根目录放一个test.mp3文件
然后回到我们的app 点击’剪切音频’按钮
结果是输出Toast 执行完成=0,回到sd卡下 我们看到test_out.mp3正常输出,笔者试听了下 并注意观察剪切的时间段 答案肯定是正确的—歌曲被剪切了,测试成功
(Ps:第二次点执行前要把sd卡的test_out.mp3删掉,否则执行失败, 这不属于程序的问题)
然后再尝试视频格式转换 为了方便测试 我是在sd卡根目录放了个很小的MP4视频文件
然后回到app转换 成功 视频越大转换时间越久 (用过电脑的格式工厂吧? )
然后回到sd卡 发现test_out.flv已经生成 点击正常播放 功能没问题…
然后 你可以做很多有趣的app了
本Demo完整源码在教程最后 …
6.FFmpeg命令使用
下面贴上ffmpeg常用命令下面的命令部分内容转来自
http://blog.csdn.net/weiyuefei/article/details/51678582
http://www.cnblogs.com/wainiwann/p/4128154.html
命令格式:
ffmpeg -i [输入文件名] [参数选项] -f [格式] [输出文件]
ffmpeg [[options][`-i’ input_file]]… {[options] output_file}…
1、参数选项:
(1) -an: 去掉音频
(2) -acodec: 音频选项, 一般后面加copy表示拷贝
(3) -vcodec:视频选项,一般后面加copy表示拷贝
2、格式:
(1) h264: 表示输出的是h264的视频裸流
(2) mp4: 表示输出的是mp4的视频
(3)mpegts: 表示ts视频流
如果没有输入文件,那么视音频捕捉(只在Linux下有效,因为Linux下把音视频设备当作文件句柄来处理)就会起作用。作为通用的规则,选项一般用于下一个特定的文件。如果你给 –b 64选项,改选会设置下一个视频速率。对于原始输入文件,格式选项可能是需要的。缺省情况下,ffmpeg试图尽可能的无损转换,采用与输入同样的音频视频参数来输出。(by ternence.hsu)
H264视频转ts视频流
ffmpeg -i test.h264 -vcodec copy -f mpegts test.ts
H264视频转mp4
ffmpeg -i test.h264 -vcodec copy -f mp4 test.mp4
ts视频转mp4
ffmpeg -i test.ts -acodec copy -vcodec copy -f mp4 test.mp4
mp4视频转flv
ffmpeg -i test.mp4 -acodec copy -vcodec copy -f flv test.flv
转换文件为3GP格式
ffmpeg -y -i test.mpeg -bitexact -vcodec h263 -b 128 -r 15 -s 176x144 -acodec aac -ac 2 -ar 22500 -ab 24 -f 3gp test.3gp
转换文件为3GP格式 v2
ffmpeg -y -i test.wmv -ac 1 -acodec libamr_nb -ar 8000 -ab 12200 -s 176x144 -b 128 -r 15 test.3gp
使用 ffmpeg 编码得到高质量的视频
ffmpeg.exe -i "D:\Video\Fearless\Fearless.avi" -target film-dvd -s 720x352 -padtop 64 -padbottom 64 -maxrate 7350000 -b 3700000 -sc_threshold 1000000000 -trellis -cgop -g 12 -bf 2 -qblur 0.3 -qcomp 0.7 -me full -dc 10 -mbd 2 -aspect 16:9 -pass 2 -passlogfile "D:\Video\ffmpegencode" -an -f mpeg2video "D:\Fearless.m2v"
转换指定格式文件到FLV格式
ffmpeg.exe -i test.mp3 -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flv ffmpeg.exe -i test.wmv -ab 56 -ar 22050 -b 500 -r 15 -s 320x240 f:\test.flv
转码解密的VOB
ffmpeg -i snatch_1.vob -f avi -vcodec mpeg4 -b 800 -g 300 -bf 2 -acodec mp3 -ab 128 snatch.avi (上面的命令行将vob的文件转化成avi文件,mpeg4的视频和mp3的音频。注意命令中使用了B帧,所以mpeg4流是divx5兼容的。GOP大小是300意味着29.97帧频下每10秒就有INTRA帧。该映射在音频语言的DVD转码时候尤其有用,同时编码到几种格式并且在输入流和输出流之间建立映射)
转换文件为3GP格式
ffmpeg -i test.avi -y -b 20 -s sqcif -r 10 -acodec amr_wb -ab 23.85 -ac 1 -ar 16000 test.3gp (如果要转换为3GP格式,则ffmpeg在编译时必须加上–enable-amr_nb –enable-amr_wb,详细内容可参考:转换视频为3GPP格式)
转换文件为MP4格式(支持iPhone/iTouch)
ffmpeg -y -i input.wmv -f mp4 -async 1-s 480x320 -acodec libfaac -vcodec libxvid -qscale 7 -dts_delta_threshold 1 output.mp4 ffmpeg -y -i source_video.avi input -acodec libfaac -ab 128000 -vcodec mpeg4 -b 1200000 -mbd 2 -flags +4mv+trell -aic 2 -cmp 2 -subcmp 2 -s 320x180 -title X final_video.mp4
将一段音频与一段视频混合
ffmpeg -i son.wav -i video_origine.avi video_finale.mpg
将一段视频转换为DVD格式
ffmpeg -i source_video.avi -target pal-dvd -ps 2000000000 -aspect 16:9 finale_video.mpeg (target pal-dvd : Output format ps 2000000000 maximum size for the output file, in bits (here, 2 Gb) aspect 16:9 : Widescreen)
转换一段视频为DivX格式
ffmpeg -i video_origine.avi -s 320x240 -vcodec msmpeg4v2 video_finale.avi Turn X images to a video sequence ffmpeg -f image2 -i image%d.jpg video.mpg (This command will transform all the images from the current directory (named image1.jpg, image2.jpg, etc...) to a video file named video.mpg.) Turn a video to X images ffmpeg -i video.mpg image%d.jpg (This command will generate the files named image1.jpg, image2.jpg, ... ;The following image formats are also availables : PGM, PPM, PAM, PGMYUV, JPEG, GIF, PNG, TIFF, SGI.)
使用ffmpeg录像屏幕(仅限Linux平台)
ffmpeg -vcodec mpeg4 -b 1000 -r 10 -g 300 -vd x11:0,0 -s 1024x768 ~/test.avi (-vd x11:0,0 指录制所使用的偏移为 x=0 和 y=0,-s 1024×768 指录制视频的大小为 1024×768。录制的视频文件为 test.avi,将保存到用户主目录中;如果你只想录制一个应用程序窗口或者桌面上的一个固定区域,那么可以指定偏移位置和区域大小。使用xwininfo -frame命令可以完成查找上述参数。)
重新调整视频尺寸大小(仅限Linux平台)
ffmpeg -vcodec mpeg4 -b 1000 -r 10 -g 300 -i ~/test.avi -s 800×600 ~/test-800-600.avi
把摄像头的实时视频录制下来,存储为文件(仅限Linux平台)
ffmpeg -f video4linux -s 320*240 -r 10 -i /dev/video0 test.asf
使用ffmpeg压制H.264视频
ffmpeg -threads 4 -i INPUT -r 29.97 -vcodec libx264 -s 480x272 -flags +loop -cmp chroma -deblockalpha 0 -deblockbeta 0 -crf 24 -bt 256k -refs 1 -coder 0 -me umh -me_range 16 -subq 5 -partitions parti4x4+parti8x8+partp8x8 -g 250 -keyint_min 25 -level 30 -qmin 10 -qmax 51 -trellis 2 -sc_threshold 40 -i_qfactor 0.71 -acodec libfaac -ab 128k -ar 48000 -ac 2 OUTPUT (使用该指令可以压缩出比较清晰,而且文件转小的H.264视频文件)
网络推送
udp视频流的推送 ffmpeg -re -i 1.ts -c copy -f mpegts udp://192.168.0.106:1234
视频拼接
裸码流的拼接,先拼接裸码流,再做容器的封装 ffmpeg -i "concat:test1.h264|test2.h264" -vcodec copy -f h264 out12.h264
图像相关
截取一张352x240尺寸大小的,格式为jpg的图片 ffmpeg -i test.asf -y -f image2 -t 0.001 -s 352x240 a.jpg
把视频的前30帧转换成一个Animated Gif
ffmpeg -i test.asf -vframes 30 -y -f gif a.gif
截取指定时间的缩微图,-ss后跟的时间单位为秒
ffmpeg -i test.avi -y -f image2 -ss 8 -t 0.001 -s 350x240 test.jpg
音频处理
转换wav到mp2格式 ffmpeg -i /tmp/a.wav -ab 64 /tmp/a.mp2 -ab 128 /tmp/b.mp2 -map 0:0 -map 0:0 (上面的命令行转换一个64Kbits 的a.wav到128kbits的a.mp2 ‘-map file:index’在输出流的顺序上定义了哪一路输入流是用于每一个输出流的。)
切割ts分片
ffmpeg -i input.mp4 -c:v libx264 -c:a aac -strict -2 -f hls -hls_list_size 6 -hls_time 5 output1.m3u8
分离视频音频流
ffmpeg -i input_file -vcodec copy -an output_file_video //分离视频流 ffmpeg -i input_file -acodec copy -vn output_file_audio //分离音频流
视频解复用
ffmpeg –i test.mp4 –vcodec copy –an –f m4v test.264 ffmpeg –i test.avi –vcodec copy –an –f m4v test.264
视频封装
ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg //提取图片 ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi //剪切视频 //-r 提取图像的频率,-ss 开始时间,-t 持续时间
视频剪切
ffmpeg –i test.avi –r 1 –f image2 image-%3d.jpeg //提取图片 ffmpeg -ss 0:1:30 -t 0:0:20 -i input.avi -vcodec copy -acodec copy output.avi //剪切视频 //-r 提取图像的频率,-ss 开始时间,-t 持续时间
视频录制
ffmpeg –i rtsp://192.168.3.205:5555/test –vcodec copy out.avi
YUV序列播放
ffplay -f rawvideo -video_size 1920x1080 input.yuv
YUV序列转AVI
ffmpeg –s w*h –pix_fmt yuv420p –i input.yuv –vcodec mpeg4 output.avi
最简单的抓屏:
ffmpeg -f gdigrab -i desktop out.mpg
从屏幕的(10,20)点处开始,抓取640x480的屏幕,设定帧率为5 :
ffmpeg -f gdigrab -framerate 5 -offset_x 10 -offset_y 20 -video_size 640x480 -i desktop out.mpg
ffmpeg将图片转换为视频:
http://blog.sina.com.cn/s/blog_40d73279010113c2.html
将直播媒体保存至本地文件
ffmpeg -i rtmp://server/live/streamName -c copy dump.flv
将文件当做直播送至live
ffmpeg -re -i localFile.mp4 -c copy -f flv rtmp://server/live/streamName
7.源码下载
http://download.csdn.net/detail/u014418171/9693793安卓高级开发者交流群欢迎您加入
521231038
相关文章推荐
- 编译Android下可执行命令的FFmpeg
- 编译Android下可执行命令的FFmpeg
- Windows下Qt for Android 编译安卓C语言可执行程序
- android 执行ffmpeg命令
- 编译Android下可执行命令的FFmpeg
- ffmpeg 编译好的库文件和可执行程序----for android
- 解决Android平台移植ffmpeg的一揽子问题
- RF Analyzer for Android 安卓平台连接HackRF的App
- air for android 使用ANE来获取安卓手机IMEI号
- Android编译系统详解(二)——命令执行流程
- android 在手机sd 卡中执行linux 命令 和shell 脚本
- 使用android创建安卓项目工程或者创建安卓测试项目工程命令详解
- android看不见main函数怎么办?程序异常了,能够不提示“xxx软件停止执行”吗?
- android执行外部命令、检测文件是否存在、自动检测U盘路径
- android在apk中获取root权限,并执行命令
- FFmpeg for Android 编译方法 (Linux环境)
- FFmpeg 2.8.4 移植到android平台(一)
- How to Build FFmpeg for Android
- 移植havlenapetr的ffmpeg-->Android播放器
- FFMPEG移植到Android