Android本地视频压缩方案的示例代码

所属分类: 软件编程 / Android 阅读数: 77
收藏 0 赞 0 分享

前言

本文讨论的不是类似秒拍的短视频录制,而是用户选择本地一个现有视频,压缩后上传。秒拍的实现其实是自定义视频录制功能,从而控制录制时长,分辨率,码率等,生成体积很小的视频再上传。而我们则没办法控制原视频的参数,可能是一个很大的视频需要压缩处理。

思路

利用ffmpeg对视频转码,通过设定参数生成分辨率和码率更小的视频,实现压缩。当然,ffmpeg的功能远不止如此,这是一个很大的专题。

用到的开源库:https://github.com/WritingMinds/ffmpeg-android-java

使用方法

基本原理:将android环境下可执行文件ffmpeg存放在本地,代码执行ffmpeg的压缩命令。

//将开源库中asset目录的ffmpeg可执行文件,拷贝到 app的data/data/files目录

FFmpeg.getInstance(this).loadBinary(null);

这个方法是异步执行,所以最好在Application中执行。方法有执行成功与否的回调,这里我传入null不关心结果。执行完看下手机中的目录:

既然是可执行文件,那么在android shell环境下肯定可以执行了。adb shell进入手机看下(前提是手机已经获取root权限):


执行ffmpeg的一个命令:比如查看ffmpeg的当前版本:./ffmpeg -version

接着就可以在代码中,使用ffmpeg的各种命令了:把命令写入String[],然后调用fFmpeg.execute 即可

获取视频文件的信息

String[] command = new String[]{"-i", arg.filePath};
try {
      fFmpeg.execute(commands, new ExecuteBinaryResponseHandler(){
        @Override
        public void onStart() {}

        @Override
        public void onProgress(String message) {
          Log.e("dml", "onProgress: message is " + message);
        }

        @Override
        public void onFailure(String message) {
          Log.e("dml", "onFailure: message is " + message);
        }

        @Override
        public void onSuccess(String message) {
          Log.e("dml", "onSuccess: message is " + message);
        }

        @Override
        public void onFinish() {
          Log.e("dml", "onFinish: ");
        }
      });
    } catch (FFmpegCommandAlreadyRunningException e) {
      e.printStackTrace();
    }

压缩视频:

String[] commands = new String[]{"-threads","1","-i", arg.filePath, "-c:v", "libx264","-crf","30","-preset", "superfast" ,"-y", "-acodec","libmp3lame",arg.thumbVideoPath};
fFmpeg.execute(commands, new ExecuteBinaryResponseHandler(){});

参数解释:

  1. -threads: 执行线程数,传入1 单线程压缩
  2. -i:input路径,传入视频文件的路径
  3. -c:v:编码格式,一般都是指定libx264
  4. -crf: 编码质量,取值范围是0-51,默认值为23,数字越小输出视频的质量越高。这里的30是我们经过测试得到的经验值
  5. -preset:转码速度,ultrafast,superfast,veryfast,faster,fast,medium,slow,slower,veryslow和placebo。ultrafast编码速度最快,但压缩率低,生成的文件更大,placebo则正好相反。x264所取的默认值为medium。需要说明的是,preset主要是影响编码的速度,并不会很大的影响编码出来的结果的质量。
  6. -acodec:音频编码,一般采用libmp3lame
  7. arg.thumbVideoPath:最后传入的是视频压缩后保存的路径
  8. -y:输出时覆盖输出目录已存在的同名文件(如果不加此参数,就不会覆盖)

问题解决

此开源库用于视频压缩在实际开发中存在不少问题,下面一一解决

1.压缩进度反馈

执行转码命令后,onProgress只是不停输出字符串,而且文本很长 需要正则表达式从中截取转码进度反馈:

        @Override
        public void onProgress(String s) {
          Pattern timePattern = Pattern.compile("(?<=time=)[\\d:.]*");
          Scanner sc = new Scanner(s);
          String match = sc.findWithinHorizon(timePattern, 0);
          if (match != null) {
            String[] matchSplit = match.split(":");
            if (duration!= 0) {
              float progress = (Integer.parseInt(matchSplit[0]) * 3600 +
                  Integer.parseInt(matchSplit[1]) * 60 +
                  Float.parseFloat(matchSplit[2])) / duration;
              int showProgress = (int) (progress * 100);
              if(showProgress>100){
                showProgress = 100;
              }
              notify.compressProgress(getTag(),showProgress);
            }
          }
        }

2.低码率视频压缩会变大

实际中发现有些原质量较差的视频压缩后,体积反而变大。

处理方法:压缩前先执行对视频提取信息的命令,小于1024kb/s的视频 不压缩:

        @Override
        public void onProgress(String s) {
          //Log.d("dml","pre onProgress = " + s);
          if(s.contains("Stream #0:0")){
            String tem = s.substring(0, s.indexOf("kb/s"));
            String type ;
            int pos = tem.lastIndexOf(",");
            if (pos != -1) {
              type = tem.substring(pos + 1,tem.length()).trim();
              try {
                Integer integer = Integer.parseInt(type);
                if(integer > 1024){
                  pressV(fFmpeg);//执行压缩
                }else {
                  //放弃压缩,直接使用原文件
                }
              }catch (Exception e){
              }
            }
          }
        }

并且在压缩成功后,检查压缩后的文件和原文件大小,如果变大了,直接使用原文件。

3.多线程压缩多个视频

开源库中执行ffmpeg的命令是在AsycTask执行的:

ffmpegExecuteAsyncTask = new FFmpegExecuteAsyncTask(command , timeout, ffmpegExecuteResponseHandler);
ffmpegExecuteAsyncTask.execute();

execute 方法在api 11之后是串行方法,就是说开源库已经限制为单线程。

改为:ffmpegExecuteAsyncTask.executeOnExecutor(Executors.newCachedThreadPool()); 可以使用多线程

测试中发现多个视频同时压缩,手机会严重发热,强烈建议采用原设计 。

4.压缩速度和质量

手机性能有限,压缩视频速度不太理想,即使在PC端用 格式工厂压缩转码视频也不是很快。

压缩质量还可以,基本能保持和原视频一样的清晰度。下面是测试数据:

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

更多精彩内容其他人还在看

Android网络编程之获取网络上的Json数据实例

这篇文章主要介绍了Android网络编程之获取网络上的Json数据实例,本文用完整的代码实例讲解了在Android中读取网络中Json数据的方法,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中的windowSoftInputMode属性详解

这篇文章主要介绍了Android中的windowSoftInputMode属性详解,本文对windowSoftInputMode的9个属性做了详细总结,需要的朋友可以参考下
收藏 0 赞 0 分享

Android网络编程之UDP通信模型实例

这篇文章主要介绍了Android网络编程之UDP通信模型实例,本文给出了服务端代码和客户端代码,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中使用ListView实现漂亮的表格效果

这篇文章主要介绍了Android中使用ListView实现漂亮的表格效果,本文用详细的代码实例创建了一个股票行情表格,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中刷新界面的二种方法

这篇文章主要介绍了Android中刷新界面的二种方法,本文使用Handler、postInvalidate两种方法实现界面刷新,需要的朋友可以参考下
收藏 0 赞 0 分享

Android SDK三种更新失败及其解决方法

这篇文章主要介绍了Android SDK三种更新失败及其解决方法,需要的朋友可以参考下
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(一)

Android3.0(API level 11)开始,Android设备不再需要专门的菜单键。随着这种变化,Android app应该取消对传统6项菜单的依赖。取而代之的是提供anction bar来提供基本的用户功能
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(二)

这次将继续上一篇文章没有讲完的Menu的学习,上下文菜单(Context menu)和弹出菜单(Popup menu)
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(三)

今天继续昨天没有讲完的Menu的学习,主要是Popup Menu的学习,需要的朋友可以参考下
收藏 0 赞 0 分享

Android显示网络图片实例

这篇文章主要介绍了Android显示网络图片的方法,以实例形式展示了Android程序显示网络图片的方法,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多