Java Process中waitFor()的问题

本文详细阐述了在Java程序中如何调用ffmpeg进行音视频转换,并通过合理管理输入输出流缓冲区解决进程阻塞问题。同时,提供了一个简单操作MP3文件的类,包括获取文件大小、歌曲长度及MP3转码功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在编写Java程序时,有时候我们需要调用其他的诸如exe,shell这样的程序或脚本。在Java中提供了两种方法来启动其他程序: (1) 使用Runtime的exec()方法 (2) 使用ProcessBuilder的start()方法 。Runtime和ProcessBulider提供了不同的方式来启动程序,设置启动参数、环境变量和工作目录。但是这两种方法都会返回一个用于管理操作系统进程的Process对象。这个对象中的waitFor()是我们今天要讨论的重点。

来说说我遇到的实际情况:我想调用ffmpeg程序来对一首歌曲进行转码,把高音质版本的歌曲转为多种低码率的文件。但是在转码完成之后需要做以下操作:读取文件大小,写入ID3信息等。这时我们就想等转码操作完成之后我们可以知道。

如下这样代码

Java代码 收藏代码
  1. Processp=null;
  2. try{
  3. p=Runtime.getRuntime().exec("notepad.exe");
  4. }catch(Exceptione){
  5. e.printStackTrace();
  6. }
  7. System.out.println("我想被打印...");

在notepad.exe被执行的同时,打印也发生了,但是我们想要的是任务完成之后它才被打印。


之后发现在Process类中有一个waitFor()方法可以实现。如下:

Java代码 收藏代码
  1. Processp=null;
  2. try{
  3. p=Runtime.getRuntime().exec("notepad.exe");
  4. p.waitFor();
  5. }catch(Exceptione){
  6. e.printStackTrace();
  7. }
  8. System.out.println("我想被打印...");

这下又出现了这样的现象,必须要等我们把记事本关闭打印语句才会被执行。并且你不碰手动关闭它那程序就一直不动,程序貌似挂了.....这是什么情况,想调用个别的程序有这么难吗?让我们来看看waitFor()的说明:


JDK帮助文档上这么说:如有必要,一直要等到由该 Process 对象表示的进程已经终止。如果已终止该子进程,此方法立即返回。但是直接调用这个方法会导致当前线程阻塞,直到退出子进程。对此JDK文档上还有如此解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入何从标准输入快速的读入都有可能造成子进程的所,甚至死锁。好了,问题的关键在缓冲区这个地方:可执行程序的标准输出比较多,而运行窗口的标准缓冲区不够大,所以发生阻塞。接着来分析缓冲区,哪来的这个东西,当Runtime对象调用exec(cmd)后,JVM会启动一个子进程,该进程会与JVM进程建立三个管道连接:标准输入,标准输出和标准错误流。假设该程序不断在向标准输出流和标准错误流写数据,而JVM不读取的话,当缓冲区满之后将无法继续写入数据,最终造成阻塞在waitfor()这里。 知道问题所在,我们解决问题就好办了。查看网上说的方法多数是开两个线程在waitfor()命令之前读出窗口的标准输出缓冲区和标准错误流的内容。代码如下:

Java代码 收藏代码
  1. Runtimert=Runtime.getRuntime();
  2. Stringcommand="cmd/cffmpeg-loglevelquiet-i"+srcpath+"-ab"+bitrate+"k-acodeclibmp3lame"+desfile;
  3. try{
  4. p=rt.exec(command,null,newFile("C:\\ffmpeg-git-670229e-win32-static\\bin"));
  5. //获取进程的标准输入流
  6. finalInputStreamis1=p.getInputStream();
  7. //获取进城的错误流
  8. finalInputStreamis2=p.getErrorStream();
  9. //启动两个线程,一个线程负责读标准输出流,另一个负责读标准错误流
  10. newThread(){
  11. publicvoidrun(){
  12. BufferedReaderbr1=newBufferedReader(newInputStreamReader(is1));
  13. try{
  14. Stringline1=null;
  15. while((line1=br1.readLine())!=null){
  16. if(line1!=null){}
  17. }
  18. }catch(IOExceptione){
  19. e.printStackTrace();
  20. }
  21. finally{
  22. try{
  23. is1.close();
  24. }catch(IOExceptione){
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }.start();
  30. newThread(){
  31. publicvoidrun(){
  32. BufferedReaderbr2=newBufferedReader(newInputStreamReader(is2));
  33. try{
  34. Stringline2=null;
  35. while((line2=br2.readLine())!=null){
  36. if(line2!=null){}
  37. }
  38. }catch(IOExceptione){
  39. e.printStackTrace();
  40. }
  41. finally{
  42. try{
  43. is2.close();
  44. }catch(IOExceptione){
  45. e.printStackTrace();
  46. }
  47. }
  48. }
  49. }.start();
  50. p.waitFor();
  51. p.destroy();
  52. System.out.println("我想被打印...");
  53. }catch(Exceptione){
  54. try{
  55. p.getErrorStream().close();
  56. p.getInputStream().close();
  57. p.getOutputStream().close();
  58. }
  59. catch(Exceptionee){}
  60. }
  61. }

这个方法确实可以解决调用waitFor()方法阻塞无法返回的问题。但是在其中过程中我却发现真正起关键作用的缓冲区是getErrorStream()说对应的那个缓冲区没有被清空,意思就是说其实只要及时读取标准错误流缓冲区的数据程序就不会被block。

Java代码 收藏代码
  1. StringBuffersb=newStringBuffer();
  2. try{
  3. Processpro=Runtime.getRuntime().exec(cmdString);
  4. BufferedReaderbr=newBufferedReader(newInputStreamReader(pro.getInputStream()),4096);
  5. Stringline=null;
  6. inti=0;
  7. while((line=br.readLine())!=null){
  8. if(0!=i)
  9. sb.append("\r\n");
  10. i++;
  11. sb.append(line);
  12. }
  13. }catch(Exceptione){
  14. sb.append(e.getMessage());
  15. }
  16. returnsb.toString();

不过这种写法不知道是不是适合所有的情况,网上其他人说的需要开两个线程可能不是没有道理。这个还是具体问题具体对待吧。

到这里问题的原因也清楚了,问题也被解决了,是不是就结束了。让我们回过头来再分析一下,问题的关键是处在输入流缓冲区那个地方,子进程的产生的输出流没有被JVM及时的读取最后缓冲区满了就卡住了。如果我们能够不让子进程向输入流写入数据,是不是可以解决这个问题。对于这个想法直接去ffmpeg官网查找,最终发现真的可以关闭子进程向窗口写入数据。命令如下:
ffmpeg.exe -loglevel quiet -i 1.mp3 -ab 16k -ar 22050 -acodec libmp3lame r.mp3
稍微分析一下:-acodec音频流编码方式-ab音频流码率(默认是同源文件码率,也需要视codec而定) -ar音频流采样率(大多数情况下使用44100和48000,分别对应PAL制式和NTSC制式,根据需要选择),重点就是-loglevel quiet这句

这才是我们想要的结果:

Java代码 收藏代码
  1. try{
  2. p=Runtime.getRuntime().exec("cmd/cffmpeg-loglevelquiet-iD:\\a.mp3-ab168k-ar22050-acodeclibmp3lameD:\\b.mp3",null,
  3. newFile("C:\\ffmpeg-git-670229e-win32-static\\bin"));
  4. p.waitFor();
  5. }catch(Exceptione){
  6. e.printStackTrace();
  7. }
  8. System.out.println("我想被打印...");

最后是自己写的一个简单的操作MP3文件的类

Java代码 收藏代码
  1. packagecom.yearsaaaa.util;
  2. importjava.io.File;
  3. importjava.io.FileInputStream;
  4. importjava.math.BigDecimal;
  5. importjavazoom.jl.decoder.Bitstream;
  6. importjavazoom.jl.decoder.Header;
  7. /**
  8. *@className:MP3Util.java
  9. *@classDescription:
  10. *@author:MChen
  11. *@createTime:2012-2-9
  12. */
  13. publicclassMP3Util{
  14. /**
  15. *获取文件大小,以M为单位,保留小数点两位
  16. */
  17. publicstaticdoublegetMP3Size(Stringpath)
  18. {
  19. Filefile=newFile(path);
  20. doublesize=(double)file.length()/(1024*1024);
  21. size=newBigDecimal(size).setScale(2,BigDecimal.ROUND_UP).doubleValue();
  22. System.out.println("MP3文件的大小为:"+size);
  23. returnsize;
  24. }
  25. /**
  26. *该方法只能获取mp3格式的歌曲长度
  27. *库地址:http://www.javazoom.net/javalayer/javalayer.html
  28. */
  29. publicstaticStringgetMP3Time(Stringpath)
  30. {
  31. StringsongTime=null;
  32. FileInputStreamfis=null;
  33. Bitstreambt=null;
  34. Filefile=newFile(path);
  35. try{
  36. fis=newFileInputStream(file);
  37. intb=fis.available();
  38. bt=newBitstream(fis);
  39. Headerh=bt.readFrame();
  40. inttime=(int)h.total_ms(b);
  41. inti=time/1000;
  42. bt.close();
  43. fis.close();
  44. if(i%60==0)
  45. songTime=(i/60+":"+i%60+"0");
  46. if(i%60<10)
  47. songTime=(i/60+":"+"0"+i%60);
  48. else
  49. songTime=(i/60+":"+i%60);
  50. System.out.println("该歌曲的长度为:"+songTime);
  51. }
  52. catch(Exceptione){
  53. try{
  54. bt.close();
  55. fis.close();
  56. }catch(Exceptionee){
  57. ee.printStackTrace();
  58. }
  59. }
  60. returnsongTime;
  61. }
  62. /**
  63. *将源MP3向下转码成低品质的文件
  64. *@参数:@paramsrcPath源地址
  65. *@参数:@parambitrate比特率
  66. *@参数:@paramdesfile目标文件
  67. *@returnvoid
  68. *@throws
  69. */
  70. publicstaticvoidmp3Transcoding(StringsrcPath,Stringbitrate,StringdesFile)
  71. {
  72. //Java调用CMD命令时,不能有空格
  73. Stringsrcpath=srcPath.replace("","\"\"");
  74. Stringdesfile=desFile.replace("","\"\"");
  75. Runtimert=Runtime.getRuntime();
  76. Stringcommand="cmd/cffmpeg-loglevelquiet-i"+srcpath+"-ab"+bitrate+"k-acodeclibmp3lame"+desfile;
  77. System.out.println(command);
  78. Processp=null;
  79. try{
  80. //在Linux下调用是其他写法
  81. p=rt.exec(command,null,newFile("C:\\ffmpeg-git-670229e-win32-static\\bin"));
  82. p.waitFor();
  83. System.out.println("线程返回,转码后的文件大小为:"+desFile.length()+",现在可以做其他操作了,比如重新写入ID3信息。");
  84. }
  85. catch(Exceptione){
  86. e.printStackTrace();
  87. try{
  88. p.getErrorStream().close();
  89. p.getInputStream().close();
  90. p.getOutputStream().close();
  91. }
  92. catch(Exceptionee){}
  93. }
  94. }
  95. publicstaticvoidmain(String[]args){
  96. //String[]str={"E:\\Kugou\\陈慧娴-不羁恋人.mp3","E:\\Kugou\\三寸天堂.mp3","E:\\Tmp\\陈淑桦-梦醒时分.mp3","E:\\Tmp\\1.mp3","E:\\Test1\\走天涯、老猫-杨望.acc","E:\\Test1\\因为爱情铃.mp3"};
  97. String[]str={"E:\\Kugou\\三寸天堂.mp3"};
  98. for(Strings:str)
  99. {
  100. //getMP3Size(s);
  101. //getMP3Time(s);
  102. Filef=newFile(s);
  103. mp3Transcoding(f.getAbsolutePath(),"64","d:\\chenmiao.mp3");
  104. }
  105. }
  106. }


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值