Java+Windows+ffmpeg实现视频转换功能

最近由于项目需要,研究了一下如何用Java实现视频转换,“着实”废了点心思,整理整理,写出给自己备忘下。

思路

由于之前没有没法过相关功能的经验,一开始来真不知道从哪里入手。当然,这个解决,google一下立马就发现了ffmpeg,网上讲解用Java+ffmpeg来进行视频转换的文章也不在少数,我主要参考的这篇文章。

上文提到的这篇文章,基本已经把开发流程什么的讲的很清楚了,这里总结下:

1)核心是利用ffmpeg进行视频转换,我们自己并不写转换视频的代码,只是调用ffmpeg,它会帮我们完成视频的转换。ffmpeg支持的类型有:asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等,这些类型,可以利用ffmpeg进行直接转换。ffmpeg不支持的类型有:wmv9,rm,rmvb等,这些类型需要先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式。

2)了解Java如何调用外部程序,这会是最困难的,也会是坑最多的地方。

3)根据我们的需求设置ffmpeg的参数。(这类文章网上已经有很多了,我也不用复制黏贴了,见这里)

代码

上文中提到的那篇文章中的代码其实已经写的很友好了,基本拿来就能用,不过仍然存在许多问题,接下来会讲到,下面是文中的代码:

import java.io.File;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List; 

public class ConvertVideo { 

 private final static String PATH = "c:\\ffmpeg\\input\\c.mp4"; 

 public static void main(String[] args) {
  if (!checkfile(PATH)) {
   System.out.println(PATH + " is not file");
   return;
  }
  if (process()) {
   System.out.println("ok");
  }
 } 

 private static boolean process() {
  int type = checkContentType();
  boolean status = false;
  if (type == 0) {
   System.out.println("直接将文件转为flv文件");
   status = processFLV(PATH);// 直接将文件转为flv文件
  } else if (type == 1) {
   String avifilepath = processAVI(type);
   if (avifilepath == null)
    return false;// avi文件没有得到
   status = processFLV(avifilepath);// 将avi转为flv
  }
  return status;
 } 

 private static int checkContentType() {
  String type = PATH.substring(PATH.lastIndexOf(".") + 1, PATH.length())
    .toLowerCase();
  // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
  if (type.equals("avi")) {
   return 0;
  } else if (type.equals("mpg")) {
   return 0;
  } else if (type.equals("wmv")) {
   return 0;
  } else if (type.equals("3gp")) {
   return 0;
  } else if (type.equals("mov")) {
   return 0;
  } else if (type.equals("mp4")) {
   return 0;
  } else if (type.equals("asf")) {
   return 0;
  } else if (type.equals("asx")) {
   return 0;
  } else if (type.equals("flv")) {
   return 0;
  }
  // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
  // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
  else if (type.equals("wmv9")) {
   return 1;
  } else if (type.equals("rm")) {
   return 1;
  } else if (type.equals("rmvb")) {
   return 1;
  }
  return 9;
 } 

 private static boolean checkfile(String path) {
  File file = new File(path);
  if (!file.isFile()) {
   return false;
  }
  return true;
 } 

 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
 private static String processAVI(int type) {
  List<String> commend = new ArrayList<String>();
  commend.add("c:\\ffmpeg\\mencoder");
  commend.add(PATH);
  commend.add("-oac");
  commend.add("lavc");
  commend.add("-lavcopts");
  commend.add("acodec=mp3:abitrate=64");
  commend.add("-ovc");
  commend.add("xvid");
  commend.add("-xvidencopts");
  commend.add("bitrate=600");
  commend.add("-of");
  commend.add("avi");
  commend.add("-o");
  commend.add("c:\\ffmpeg\\output\\a.avi");
  try {
   ProcessBuilder builder = new ProcessBuilder();
   builder.command(commend);
   builder.start();
   return "c:\\ffmpeg\\output\\a.avi";
  } catch (Exception e) {
   e.printStackTrace();
   return null;
  }
 } 

 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
 private static boolean processFLV(String oldfilepath) { 

  if (!checkfile(PATH)) {
   System.out.println(oldfilepath + " is not file");
   return false;
  } 

  // 文件命名
  Calendar c = Calendar.getInstance();
  String savename = String.valueOf(c.getTimeInMillis())+ Math.round(Math.random() * 100000);
  List<String> commend = new ArrayList<String>();
  commend.add("c:\\ffmpeg\\ffmpeg");
  commend.add("-i");
  commend.add(oldfilepath);
  commend.add("-ab");
  commend.add("56");
  commend.add("-ar");
  commend.add("22050");
  commend.add("-qscale");
  commend.add("8");
  commend.add("-r");
  commend.add("15");
  commend.add("-s");
  commend.add("600x500");
  commend.add("c:\\ffmpeg\\output\\a.flv"); 

  try {
   Runtime runtime = Runtime.getRuntime();
   Process proce = null;
   String cmd = "";
   String cut = "  c:\\ffmpeg\\ffmpeg.exe -i "
     + oldfilepath
     + " -y -f image2 -ss 8 -t 0.001 -s 600x500 c:\\ffmpeg\\output\\"
     + "a.jpg";
   String cutCmd = cmd + cut;
   proce = runtime.exec(cutCmd);
   ProcessBuilder builder = new ProcessBuilder(commend);
    builder.command(commend);
   builder.start(); 

   return true;
  } catch (Exception e) {
   e.printStackTrace();
   return false;
  }
 }
}

接下来是我自己经过修改后的代码:

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class ConvertVideo {

 private static String inputPath = "";

 private static String outputPath = "";

 private static String ffmpegPath = "";

 public static void main(String args[]) throws IOException {

  getPath();

  if (!checkfile(inputPath)) {
   System.out.println(inputPath + " is not file");
   return;
  }
  if (process()) {
   System.out.println("ok");
  }
 }

 private static void getPath() { // 先获取当前项目路径,在获得源文件、目标文件、转换器的路径
  File diretory = new File("");
  try {
   String currPath = diretory.getAbsolutePath();
   inputPath = currPath + "\\input\\test.wmv";
   outputPath = currPath + "\\output\\";
   ffmpegPath = currPath + "\\ffmpeg\\";
   System.out.println(currPath);
  }
  catch (Exception e) {
   System.out.println("getPath出错");
  }
 }

 private static boolean process() {
  int type = checkContentType();
  boolean status = false;
  if (type == 0) {
   System.out.println("直接转成flv格式");
   status = processFLV(inputPath);// 直接转成flv格式
  } else if (type == 1) {
   String avifilepath = processAVI(type);
   if (avifilepath == null)
    return false;// 没有得到avi格式
   status = processFLV(avifilepath);// 将avi转成flv格式
  }
  return status;
 }

 private static int checkContentType() {
  String type = inputPath.substring(inputPath.lastIndexOf(".") + 1, inputPath.length())
    .toLowerCase();
  // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
  if (type.equals("avi")) {
   return 0;
  } else if (type.equals("mpg")) {
   return 0;
  } else if (type.equals("wmv")) {
   return 0;
  } else if (type.equals("3gp")) {
   return 0;
  } else if (type.equals("mov")) {
   return 0;
  } else if (type.equals("mp4")) {
   return 0;
  } else if (type.equals("asf")) {
   return 0;
  } else if (type.equals("asx")) {
   return 0;
  } else if (type.equals("flv")) {
   return 0;
  }
  // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),
  // 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
  else if (type.equals("wmv9")) {
   return 1;
  } else if (type.equals("rm")) {
   return 1;
  } else if (type.equals("rmvb")) {
   return 1;
  }
  return 9;
 }

 private static boolean checkfile(String path) {
  File file = new File(path);
  if (!file.isFile()) {
   return false;
  }
  return true;
 }

 // 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等), 可以先用别的工具(mencoder)转换为avi(ffmpeg能解析的)格式.
 private static String processAVI(int type) {
  List<String> commend = new ArrayList<String>();
  commend.add(ffmpegPath + "mencoder");
  commend.add(inputPath);
  commend.add("-oac");
  commend.add("lavc");
  commend.add("-lavcopts");
  commend.add("acodec=mp3:abitrate=64");
  commend.add("-ovc");
  commend.add("xvid");
  commend.add("-xvidencopts");
  commend.add("bitrate=600");
  commend.add("-of");
  commend.add("avi");
  commend.add("-o");
  commend.add(outputPath + "a.avi");
  try {
   ProcessBuilder builder = new ProcessBuilder();
   Process process = builder.command(commend).redirectErrorStream(true).start();
   new PrintStream(process.getInputStream());
   new PrintStream(process.getErrorStream());
   process.waitFor();
   return outputPath + "a.avi";
  } catch (Exception e) {
   e.printStackTrace();
   return null;
  }
 }

 // ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等)
 private static boolean processFLV(String oldfilepath) {

  if (!checkfile(inputPath)) {
   System.out.println(oldfilepath + " is not file");
   return false;
  }

  List<String> command = new ArrayList<String>();
  command.add(ffmpegPath + "ffmpeg");
  command.add("-i");
  command.add(oldfilepath);
  command.add("-ab");
  command.add("56");
  command.add("-ar");
  command.add("22050");
  command.add("-qscale");
  command.add("8");
  command.add("-r");
  command.add("15");
  command.add("-s");
  command.add("600x500");
  command.add(outputPath + "a.flv");

  try {

   // 方案1
//   Process videoProcess = Runtime.getRuntime().exec(ffmpegPath + "ffmpeg -i " + oldfilepath
//     + " -ab 56 -ar 22050 -qscale 8 -r 15 -s 600x500 "
//     + outputPath + "a.flv");

   // 方案2
   Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();

   new PrintStream(videoProcess.getErrorStream()).start();

   new PrintStream(videoProcess.getInputStream()).start();

   videoProcess.waitFor();

   return true;
  } catch (Exception e) {
   e.printStackTrace();
   return false;
  }
 }
}

class PrintStream extends Thread
{
 java.io.InputStream __is = null;
 public PrintStream(java.io.InputStream is)
 {
  __is = is;
 }

 public void run()
 {
  try
  {
   while(this != null)
   {
    int _ch = __is.read();
    if(_ch != -1)
     System.out.print((char)_ch);
    else break;
   }
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
 }
}

问题

原文的代码中有一个很大的问题,便是不知道视频转换到底什么时候结束。看原文中的这两处代码:

98行处

builder.command(commend);
 builder.start();
 return "c:\\ffmpeg\\output\\a.avi";

145行处

builder.start();
 return true;

在进程开始之后,直接就返回结果了。要知道,这样的写法,是不会阻塞当前进程的,也就是说,当然程序返回的时候,转码程序(ffmpeg和mencoder)还在执行。如果需要mencoder进行中间转码,那原文中的写法会造成在avi文件还未转换完成时,程序就调用了ffmpeg进行转换。而对于最终的flv文件,我们也无法知道到底是什么时候转换好的,这显然是无法满足我们的业务需求的 。

解决方案

最先想到的办法自然就是阻塞当前进程(主进程),实例代码:

Process process = new ProcessBuilder(command).start();
 process.waitFor();
 return true;

采用这种的方案运行程序,发现视频转到十几秒的时候就不转了,但是程序还没返回,打开进程管理器一开,ffmpeg进程还在,内存还占着,但是CPU为0,如图:

当时不知道什么原因,在网上查了半天,才明白这是死锁了,但是不知道是什么原因造成的。当时就一直觉得死锁是waitFor()函数造成了,看来用它来判断子进程是否结果是不行了,所以又在网上查了半天其他判断子进程结束的办法(这里其实就已经走弯路了)。有人说可以用exitValue(),于是就有了下面的代码:

Process process = new ProcessBuilder(command).start();
while (true) {
  try {
    if (process.exitValue() == 0)
      break;
  }
  catch (IllegalThreadStateException e) {
    continue;
  }
}
return true;

当子进程没有结束的时候,如果执行exitValue()就会抛出异常,我采用的办法是捕获这个异常然后不去理他,直到程序结束exitValue()返回0为止。但是,还是失败了,出现的情况和用waitFor()方式时的一模一样,我才觉得可能是另外的原因,在去google,发现可能是是由于JVM只提供有限缓存空间,当外部程序(子进程)的输出流超出了这个有限空间而父进程又不读出这些数据,子进程会被阻塞waitFor()永远都不会返回,就会造成死锁。

官方解释:

Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.

知道问题了就要对症下药(其实当时我也不知道这是不是就是我遇到的问题,只能各种打散弹了,打中了算)。关于如何读出子进程的输出流,如何解决这个死锁,网上的办法都大同小异,写的比较好的可以看这个地址。

于是程序被改成这样:

Process process = new ProcessBuilder(command).start();

 new PrintStream(process.getInputStream()).start();
 process.waitFor();

PrintStream类如下:

class PrintStream extends Thread
{
  java.io.InputStream __is = null;
  public PrintStream(java.io.InputStream is)
  {
    __is = is;
  }

  public void run()
  {
    try
    {
      while(this != null)
      {
        int _ch = __is.read();
        if(_ch != -1)
          System.out.print((char)_ch);
        else break;
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }
}

运行,发现还是不对,症状和之前的一模一样,我还以为是不是输出流太多了,一个线程读的不够快(好吧,真的很傻很天真,人被逼急了真的什么想法都有),于是我就再开了几个一模一样的线程,结果还是一样。

就在我快要放弃的时候,在百度知道上,看了个无关痛痒的例子,于是做了个小修改,在进程启动之前,重定向了下错误输出流,如下:

Process videoProcess = new ProcessBuilder(command).redirectErrorStream(true).start();

 new PrintStream(videoProcess.getInputStream()).start();
 videoProcess.waitFor();

 return true;

然后,然后,然后就可以了。

结论

其实有两种写法可以解决这个问题,这种事像我上面那样写,还有一种如下:

Process videoProcess = new ProcessBuilder(command).start();

 new PrintStream(videoProcess.getErrorStream()).start();
 new PrintStream(videoProcess.getInputStream()).start();

 videoProcess.waitFor();

 return true;

其实道理还是一样的,就是读出ffmpeg的输出流,避免ffmpeg的输出流塞满缓存造成死锁。但是不知道为什么,ffmpeg的输出信息是在错误输出流里面的,我看了下控制台打印结果,发现只是一些当前转换状态的信息,并没有错误,令人费解。

在Process类中,getInputStream用来获取进程的输出流,getOutputStream用来获取进程的输入流,getErrorStream用来获取进程的错误信息流。为了保险起见,在读出的时候,最好把子进程的输出流和错误流都读出来,这样可以保证清空缓存区。

其实,我深刻地感觉到,这些解决的问题的经历是标准的散弹式编程,打到哪算哪,以后引以为戒。

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

(0)

相关推荐

  • 详解java调用ffmpeg转换视频格式为flv

    详解java调用ffmpeg转换视频格式为flv 注意:下面的程序是在Linux下运行的,如果在windows下rmvb转换成avi会出现问题,想成功需要下载下个drv43260.dll东西放到C:WindowsSystem32下面 这几天在写一个视频管理系统,遇到一个很大的问题就是如果把不同格式转换为flv,格式!经过网上的一番搜索,自己在总结,整理,整理,终于整出来了!实现了视频进行转换的同时还能够进行视频截图和删除原文件的功能! 格式转换主要原理就是先用java调用ffmpeg的exe文件

  • java调用ffmpeg实现视频转换的方法

    本文实例讲述了java调用ffmpeg实现视频转换的方法.分享给大家供大家参考.具体分析如下: 这里环境我是在windows平台下测试的... 需要在e:\下有ffmpeg.exe;mencoder.exe;drv43260.dll;pncrt.dll共4个文件.   还要在e:\input下放各种文件名为a的以下各种视频文件:还要e:\output:java程序执行后能得到一个a.flv的已转换的文件. ffmpeg.exe能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov

  • Java使用FFmpeg处理视频文件的方法教程

    前言 本文主要讲述如何使用Java + FFmpeg实现对视频文件的信息提取.码率压缩.分辨率转换等功能: 之前在网上浏览了一大圈Java使用FFmpeg处理音视频的文章,大多都讲的比较简单,楼主在实操过程中踩了很多坑也填了很多坑,希望这份详细的踩坑&填坑指南能帮助到大家: 1. 什么是FFmpeg 点我了解 2. 开发前准备 在使用Java调用FFmpeg处理音视频之前,需要先安装FFmpeg,安装方法分为两种: 引入封装了FFmpeg的开源框架 在系统中手动安装FFmpeg 2.1 引入封装

  • java调用ffmpeg实现转换视频

    最近由于项目需要把不同格式的视频转换为ts流,故研究了一下ffmpeg.在网上找了很多资料,主要参考了Java+Windows+ffmpeg实现视频转换功能. 期间也加了几个qq群,咨询了各大高手,其中在代码中关于ffmpeg的命令就是来自其中一个qq群里面的大神. 下载相关文件 ffmpeg地址,我下载是windows 64位static版本. xuggler下载地址 下面的代码我上传到了github,需要的可以下载下来看看. 步骤: 1.研究java如何调用外部程序 2.研究ffmpeg转换

  • Java实现对视频进行截图的方法【附ffmpeg下载】

    本文实例讲述了Java实现对视频进行截图的方法.分享给大家供大家参考,具体如下: 之前介绍过Java使用ffmpeg进行视频转换,这里演示一下ffmpeg进行视频截图的方法. 具体代码如下: import java.io.File; import java.util.List; //生成视频文件的首帧为图片 //windows下的版本 public class CreatePh { // public static final String FFMPEG_PATH = "E:/ffmpeg/ff

  • Javacv使用ffmpeg实现音视频同步播放

    最近用javaCV的ffmpeg包的FFmpegFrameGrabber帧捕捉器对捕捉到的音频帧和视频帧做了同步的播放.采用的同步方法是视频向音频同步. 程序和源码 具体的思路如下: (1)首先介绍ffmpeg是如何捕捉视频文件的图像和声音的 FFmpegFrameGrabber fg = new FFmpegFrameGrabber("a video file path or a url); 得到帧捕捉器对象后,调用它的grab()方法就会返回捕捉到的Frame对象.这个Frame可以是视频帧

  • Java通过调用FFMPEG获取视频时长

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的. 由此看来FFmpeg很强大,很多主流的音频.视频处理软件都使用了FFmpeg. FFmpeg下载下来解压,cmd进入到FFmpeg.exe目录中,即可在命令行下进行各种操作,查看视频信息命令:f

  • Java使用ffmpeg和mencoder实现视频转码

    本文为大家分享了Java使用ffmpeg和mencoder实现视频转码的具体代码,供大家参考,具体内容如下 准备: 需要下载ffmpeg和mencoder,百度一搜就有了,请自行下载. 不墨迹,上代码: 1)首先需要定义几个量:Contants.java public class Contants { public static final String ffmpegpath = "D:\\DevTools\\ffmpeg\\bin\\ffmpeg.exe";//ffmpeg的安装位置

  • Java+Windows+ffmpeg实现视频转换功能

    最近由于项目需要,研究了一下如何用Java实现视频转换,"着实"废了点心思,整理整理,写出给自己备忘下. 思路 由于之前没有没法过相关功能的经验,一开始来真不知道从哪里入手.当然,这个解决,google一下立马就发现了ffmpeg,网上讲解用Java+ffmpeg来进行视频转换的文章也不在少数,我主要参考的这篇文章. 上文提到的这篇文章,基本已经把开发流程什么的讲的很清楚了,这里总结下: 1)核心是利用ffmpeg进行视频转换,我们自己并不写转换视频的代码,只是调用ffmpeg,它会帮

  • Java 使用 FFmpeg 处理视频文件示例代码详解

    目前在公司做一个小东西,里面用到了 FFmpeg 简单处理音视频,感觉功能特别强大,在做之前我写了一个小例子,现在记录一下分享给大家,希望大家遇到这个问题知道解决方案. FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的. FFmpeg在Linux平

  • java使用ffmpeg处理视频的方法

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案. 官网链接http://ffmpeg.org/ 1.下载并解压windows版本安装包 2.windows本地使用命令行测试 1.修改格式测试(转码) 将需要修改的视频A.avi 提前放在bin目录下 在bin目录下cmd进入命令行 输入命令完成转码成B.mp4 ffmpeg.exe -i A.avi -y B.mp4 2.视频音频结合

  • 使用Java和ffmpeg把音频和视频合成视频的操作方法

    FFmpeg是一个开源免费跨平台的视频和音频流方案,属于自由软件,采用LGPL或GPL许可证(依据你选择的组件).它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多codec都是从头开发的. FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.它包括了目前领先的音/视频编码库libavcodec. FFmpeg是在Linux下开发出来的,但它可以在包

  • java使用ffmpeg实现上传视频的转码提取视频的截图等功能(代码操作)

    ffmpeg视频采集功能非常强大,不仅可以采集视频采集卡或USB摄像头的图像,还可以进行屏幕录制,同时还支持以RTP方式将视频流传送给支持RTSP的流媒体服务器,支持直播应用.ffmpeg能解析的格式和不能解析的格式都一一给大家说明了,具体内容详情跟随一起看看吧, 1.能支持的格式 ffmpeg能解析的格式:(asx,asf,mpg,wmv,3gp,mp4,mov,avi,flv等) 2.不能支持的格式 对ffmpeg无法解析的文件格式(wmv9,rm,rmvb等),可以先用别的工具(menco

  • java使用FFmpeg合成视频和音频并获取视频中的音频等操作(实例代码详解)

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序. ffmpeg命令参数如下: 通用选项 -L license -h 帮助 -fromats 显示可用的格式,编解码的,协议的... -f fmt 强迫采用格式fmt -I filename 输入文件 -y 覆盖输出文件 -t duration 设置纪录时间 hh:mm:ss[.xxx]格式的记录时间也支持 -ss position 搜索到指定的时间 [-]hh:mm:ss[.xxx]的格式也支持 -title

随机推荐