在 Linux 中用 Java 代码执行 FFmpeg 命令的问题

Posted

技术标签:

【中文标题】在 Linux 中用 Java 代码执行 FFmpeg 命令的问题【英文标题】:Issues in executing FFmpeg command in Java code in Linux 【发布时间】:2018-01-15 04:59:23 【问题描述】:

我在我的 java 代码中执行 ffmpeg 命令时遇到问题:

ffmpeg -i sample.mp4 -i ad.mp4 -filter_complex "[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]" -map "[out]" output.mp4

我使用了下面的getRuntime() 方法,但这对我不起作用。即使我只是删除了",它仍然不起作用。当我在终端中简单地复制粘贴等效字符串时,它可以工作。

String c1=" -i "+dir+"sample.mp4 "+"-i "+dir+"ad.mp4 -fi‌​lter_complex [0:v]‌​trim=0:15,setpts=PTS‌​-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out] -map [out] "+dir+"output.‌​mp4";
RunCommand("ffmpeg"+c1);

使用这种方法:

private static void RunCommand(String command) throws InterruptedException 
    try 
        // Execute command
        Process proc = Runtime.getRuntime().exec(command);
        System.out.println(proc.exitValue());

        // Get output stream to write from it
        // Read the output

        BufferedReader reader =  
                new BufferedReader(new InputStreamReader(proc.getInputStream()));
        String line = "";
        while((line = reader.readLine()) != null) 
            System.out.print(line + "\n");
            //              System.out.println(ads.get(0));
        
        proc.waitFor();  

     catch (IOException e) 
    

这个也不起作用,打印退出值显示:

Exception in thread "main" java.lang.IllegalThreadStateException: process hasn't exited
    at java.lang.UNIXProcess.exitValue(UNIXProcess.java:423)
    at parser.Parser.RunCommand(Parser.java:106)
    at parser.Parser.commandGenerator2(Parser.java:79)
    at parser.Parser.main(Parser.java:44)

如果我在打印退出值之前移动proc.waitFor();,它就是1

有什么问题?为什么它不能在 Java 代码中运行?

【问题讨论】:

【参考方案1】:

需要解决的几个问题(大多数问题都由其他答案单独解决,有不同程度的解释,但您需要解决所有问题):

您需要将参数传递给ffmpeg,就像 shell 一样,这意味着您需要将命令构建为带有单个参数的字符串数组,或者,为了更容易(并且与 shell 行为相同),引用您的参数:在大过滤器参数周围添加一对\"。然后你应该能够将命令传递给runCommand,就像你在 shell 中写的一样。 但 java 无法解析那些引号 (*) 并隔离将传递给 ffmpeg 的参数,但 /bin/sh 可以为你做到这一点:用 /bin/sh -c ... 包装你的命令(为此我将使用 ProcessBuilder下面) 您需要消耗输出,否则您的进程可能会永远阻塞。 ProcessBuilder 进行救援:将stderr 重定向到stdout 以仅获取单个流来使用,然后将stdout 重定向到任何你想要的地方(下面我从父进程继承,所以它转到你的@987654332 的输出@ 进程本身。 您需要等待进程完成才能获取其退出值(下面我使用waitFor(),它会根据需要等待,但还有其他选项) [在@wargre 发现之后添加] 不是为了窃取,而是为了完整起见,您需要确保该命令没有被隐形字符侵扰;)例如,您实际上是通过-fi......lter_complex(十六进制中的点为e2 80 8c e2 80 8b),但您的命令中还有更多分散的。

因此:

String c1 = " -i " + dir + "sample.mp4 -i " + dir
        + "ad.mp4 -filter_complex \"[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]\" -map \"[out]\" "
        + dir + "output.mp4";
// Notice additional quotes around filter & map above
runCommand("ffmpeg" + c1);

// ...

static void runCommand(String command) throws IOException, InterruptedException 
    Process p = new ProcessBuilder("/bin/sh", "-c", command)
            .redirectErrorStream(true)
            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
            .start();
    p.waitFor();
    System.out.println("Exit value: " + p.exitValue());

(*) 在 shell 中,如果你想打印 a b,你可以使用 echo "a b",但在 java 中这不起作用:

Runtime.getRuntime().exec("echo \"a  b\"");

它所做的只是天真地分割空格,它会将 2 个参数而不是 1 传递给 echo"a 然后 b"。不是你想要的。


替代方案:单独传递参数。

runCommandAsVarargs(
        "ffmpeg",
        "-i",
        dir + "sample.mp4",
        "-i",
        dir + "ad.mp4",
        "-filter_complex",
        "[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]",
        "-map",
        "[out]",
        dir + "output.mp4"
);
// ...
static void runCommandAsVarargs(String... command) throws IOException, InterruptedException 
    Process p = new ProcessBuilder(command)
            .redirectErrorStream(true)
            .redirectOutput(ProcessBuilder.Redirect.INHERIT)
            .start();
    p.waitFor();
    System.out.println("Exit value: " + p.exitValue());

【讨论】:

它不起作用。但是,至少我们看到了输出错误: Unrecognized option 'fi‌​lter_complex'. Error splitting the argument list: Option not found Exit value: 1 Unrecognized option 'fi‌​lter_complex' 真的出乎意料...您确定您在与您在 shell 中尝试过的系统相同的系统上运行它吗?使用相同的ffmpeg版本? -- 无论如何,我一开始就修复了我的命令中的一个错误,您还需要在"[out]" 周围加上引号...请参阅编辑。我还提供了一种替代方法,您可以逐个显式传递参数以避免歧义和引用问题。 啊啊,那个无法识别的选项是因为@wargre 发现的不可见字符...您实际上传递了-fi......lter_complex(十六进制中的点是e2 80 8c e2 80 8b),但是您的命令中还有更多分散的。 谢谢!现在可以了。老实说,我对所有这些差异感到困惑。【参考方案2】:

您的代码存在一些问题,首先,使用线程流入和内部进程的错误到控制台

创建一个管道流类,如:

class PipeStream extends Thread 
    InputStream is;
    OutputStream os;

    public PipeStream(InputStream is, OutputStream os) 
        this.is = is;
        this.os = os;
    

    public void run() 
        byte[] buffer=new byte[1024];
        int len;
        try 
            while ((len=is.read(buffer))>=0)
                os.write(buffer,0,len);
            
         catch (IOException e) 
            e.printStackTrace();  
        
    

然后将运行时部分适配为:

System.out.println("Launching command: "+command);
ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", command);
Process proc=pb.start();

PipeStream out=new PipeStream(proc.getInputStream(), System.out);
PipeStream err=new PipeStream(proc.getErrorStream(), System.err);
out.start();
err.start();

proc.waitFor();
System.out.println("Exit value is: "+proc.exitValue());

它将显示将要运行的命令、日志以及潜在的错误。

如果需要,您将能够复制粘贴命令以在终端上检查正在发生的事情。

编辑:这很有趣。您的代码缺少一些转义字符并且您的代码中没有可见的字符。当我复制粘贴代码行时,我看到了它们。复制粘贴下面的代码到你的代码中,它将消除错误:

String command="ffmpeg -i "+dir+"sample.mp4 -i "+dir+"ad.mp4 -filter_complex '[0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]' -map '[out]' "+dir+"output.mp4";

【讨论】:

但是当我在终端中简单地复制粘贴等效字符串时,它可以工作。 我的代码的结果是什么?你有执行的痕迹吗?有什么错误? Unrecognized option 'fi‌​lter_complex'. Error splitting the argument list: Option not found Exit value: 1 添加了一行行之有效的。问题是代码中的不可见字符 ( / ) PipeStream out=new PipeStream(proc.getInputStream(), System.out);out.start(); 应该将输出从进程重定向到控制台。如果要检查输出是什么,请将 System.out 更改为 ByteArrayOutputStream,然后通过 toByteArraytoString 获取内容。【参考方案3】:

您似乎错过了 -filter_complex 参数的引号。 Java 会像这样运行:

ffmpeg -i ./sample.mp4 -i ./ad.mp4 -filter_complex [0:v]trim=0:15,setpts=PTS-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out] -map [out] output.mp4

它不起作用,因为; 表示 bash 中的命令结束。 在 java 代码中放回引号应该可以修复命令(确保正确转义它们)。

String c1=" -i "+dir+"sample.mp4 "+"-i "+dir+"ad.mp4 -fi‌​lter_complex \"[0:v]‌​trim=0:15,setpts=PTS‌​-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]\" -map [out] "+dir+"output.‌​mp4";
RunCommand("ffmpeg"+c1);

【讨论】:

所以你的意思是我在代码中使用\",就好像我在终端中运行一样?您可以通过修改我的代码来更新您的答案吗? 所以我在代码中更改了 c1 并在字符串中添加了\",如下所示,但不起作用。 String c1=" -i "+dir+"sample.mp4 "+"-i "+dir+"ad.mp4 -fi‌​lter_complex \"[0:v]‌​trim=0:15,setpts=PTS‌​-STARTPTS[v0]; [1:v]trim=0:5,setpts=PTS-STARTPTS[v1]; [0:v]trim=20:30,setpts=PTS-STARTPTS[v2]; [v0][v1][v2]concat=n=3:v=1:a=0[out]\" -map \"[out]\" "+dir+"output.‌​mp4"; @TinaJ 已更新。如果它不起作用,您能否添加System.out.println(c1); 的输出? 没有仍然没有工作:-i sample.mp4 -i ad.mp4 -fi‌​lter_complex "[0:v]‌​trim=0:15,setpts=PTS‌​-STARTPTS[ v0];[1:v]trim=0:5,setpts=PTS-STARTPTS[v1];[0:v]trim=20:30,setpts=PTS-STARTPTS[v2];[v0][v1][ v2]concat=n=3:v=1:a=0[out]" -map [out] 输出。‌​mp4

以上是关于在 Linux 中用 Java 代码执行 FFmpeg 命令的问题的主要内容,如果未能解决你的问题,请参考以下文章

windows和ubuntu虚拟机设置共享文件夹,在windows中用source insight阅读和编写代码,在linux中用交叉编译工具编译。

在Linux中#!/usr/bin/python之后把后面的代码当成程序来执行。 但是在windows中用IDLE编程的话#后面的都是注释,之后的代码都被当成文本了。 该怎么样才能解决这个问题呢?

栈帧java中用啥表示

在linux中用bash脚本--定期自动备份数据库重要文件

代码块:在Java中用{}括起来的代码

linux 怎么执行java的main方法