Java 调用 FFMPEG 命令时用 url 作为输入源,Linux 下出现 “no such file or directory” 问题的解决

        Windows 下执行 ffmpeg 命令,
        D:/tools/ffmpeg/bin>ffmpeg.exe -i "某视频文件下载URL" -f flv D:/1.flv

        可以成功直接将下载链接输入源转为 1.flv。

String raw2flvCmd = "D:/tools/ffmpeg/bin/ffmpeg.exe -i \"某视频文件下载URL\" -f flv 1.flv";
Runtime.getRuntime().exec(raw2flvCmd);

        可以进行成功调用。
        Linux 下执行 ffmpeg 命令,
        /usr/local/ffmpeg/bin/ffmpeg -i "某视频文件下载URL" -f flv /usr/userfile/ffmpeg/tempfile/1.flv

        也可以成功直接将下载链接输入源转为 1.flv。

String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
Runtime.getRuntime().exec(raw2flvCmd);

        FFmpeg 会报错:
        No such file or directory:"某视频文件下载URL"。
        stackoverflow 上有人遇到了类似的问题:
        FFMPEG “no such file or directory” on Android
        I am trying to use the ffmpeg binary and call it via a native linux command in android. Most of the commands work fine but the problem is that when i need to pass an http url as an input to the -i option i get "No such file or directory" for the url. The url however is existing and running the SAME command on a mac does the job as expected.

        但最终没人给出正确的解决方案。

        为什么 terminal 执行正常的同一条命令行语句,Java 调用就挂了呢?看来 Java 并没有将程序员的意图良好地转达给底层。

        笔者经过多次测试,终于找到解决办法。既然 terminal 可以成功执行,启动 shell,然后自定义命令行作为参数传递给 shell 解释器。shell 知道如何将程序员的意图转达给底层。使用 sh -c,将自定义 CMD 行作为其参数,最后使用 java.lang.Runtimeexec(String[] cmdarray):

String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});

        问题迎刃而解。

        Runtime.getRuntime().exec(raw2flvCmd);会开启一个子进程,如果当前线程想等待该子进程执行完毕之后再继续往下执行,可以调用 java.lang.Process 的 waitFor() 方法:

Process process = null;
try {
			String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
			process = Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});
            process.waitFor();  
		} catch (Exception e) {
			//do some thing
		}

        当前线程会等待子进程 process 执行结束,然后继续往下执行。
        值得注意的一点是,ffmpeg 进程在执行时,会产生大量输出信息,如果我们没有及时将流输出的话,存放这些信息的缓存会很快填满,之后该进程等待我们将这些信息输出,然而我们也在等待该进程执行结束(process.waitFor();很明显 process 不会结束因为它也在等待我们),于是一个很经典的死锁案例就此产生。
        这种情况表现为我们的子进程阻塞住了,而我们启动该子进程的线程由于一直没有拿到 waitFor() 的返回也就此止步于那条语句。
        所以我们需要不断地从该子进程中的 input stream 中读出数据以确保它不会阻塞。
        When Runtime.exec() won't
        Navigate yourself around pitfalls related to the Runtime.exec() method

        这篇文章对此进行了深入分析,并给出了推荐解决方案。我们依据该文将我们 Linux 下的 Java 调用 FFmpeg 最终完善为:

Process process = null;
try {
			String raw2flvCmd = "/usr/local/ffmpeg/bin/ffmpeg -i \"某视频文件下载URL\" -f flv /usr/userfile/ffmpeg/tempfile/1.flv";
			process = Runtime.getRuntime().exec(new String[]{"sh","-c",raw2flvCmd});
			StreamGobbler  errorGobbler  =  new  StreamGobbler(process.getErrorStream(),  "ERROR");
            errorGobbler.start();//  kick  off  stderr 
            StreamGobbler  outGobbler  =  new  StreamGobbler(process.getInputStream(),  "STDOUT");  
            outGobbler.start();//  kick  off  stdout 
            process.waitFor();  
		} catch (Exception e) {
			//do some thing
		}

        其中 StreamGobbler 源码为:

import  java.io.BufferedReader;  
import  java.io.IOException;  
import  java.io.InputStream;  
import  java.io.InputStreamReader;  
import  java.io.OutputStream;  
import  java.io.PrintWriter;  

public class StreamGobbler extends  Thread {
	InputStream is;
	String type;
	OutputStream os;

	public StreamGobbler(InputStream is, String type) {
		this(is, type, null);
	}

	public StreamGobbler(InputStream is, String type, OutputStream redirect) {
		this.is = is;
		this.type = type;
		this.os = redirect;
	}

	@Override
	public void run() {
		try {
			PrintWriter pw = null;
			if (os != null)
				pw = new PrintWriter(os);

			InputStreamReader isr = new InputStreamReader(is);
			BufferedReader br = new BufferedReader(isr);
			String line = null;
			while ((line = br.readLine()) != null) {
				if (pw != null)
					pw.println(line);
				System.out.println(type + ">" + line);
			}
			if (pw != null)
				pw.flush();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
	}
}
        最后补充一点,关闭所有的 io 输入/输出以及错误流、调用 Process 的 destroy() 方法关闭子进程。不然程序可能会出现"java.io.IOException: error=24, Too many open files"。

©️2020 CSDN 皮肤主题: 创作都市 设计师:CSDN官方博客 返回首页