【问题标题】:Issues in executing FFmpeg command in Java code in Linux在 Linux 中用 Java 代码执行 FFmpeg 命令的问题
【发布时间】: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 代码中运行?

【问题讨论】:

    标签: java linux video terminal ffmpeg


    【解决方案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
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-09
    • 2011-06-11
    • 2013-09-25
    • 2012-04-17
    • 1970-01-01
    • 2012-07-08
    • 2019-06-11
    相关资源
    最近更新 更多