FFmpeg For Android (三) 移植main函数到安卓上执行ffmpeg命令

根据上一篇文章《FFmpeg For Android (二) Ubuntu下编译FFmpeg源码》我们熟悉了如何在Ubuntu下编译FFmpeg, 但我们却不知道有何用…
本篇将讲解 移植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

安卓开发者交流群欢迎您加入
418263790

猜你喜欢

转载自blog.csdn.net/u014418171/article/details/53337759