在CentOS上从Java创建的FFMpeg进程不会退出

时间:2017-06-21 11:26:18

标签: java linux multithreading ffmpeg centos

我需要同时转换大量的wave文件。大约300个并行文件。新文件不断出现。我在我的Java 1.8应用程序中使用ffmpeg进程调用,该应用程序在CentOS上运行。我知道我必须读取错误和输入流,以便从Java中创建进程可以退出。

我的代码经过几次失效后:

    private void ffmpegconverter(String fileIn, String fileOut){
    String[] comand = new String[]{"ffmpeg", "-v", "-8", "-i", fileIn, "-acodec", "pcm_s16le", fileOut};

    Process process = null;
    BufferedReader reader = null;
    try {
        ProcessBuilder pb = new ProcessBuilder(comand);
        pb.redirectErrorStream(true);
        process = pb.start();

        //Reading from error and standard output console buffer of process. Or it could halts because of nobody
        //reads its buffer
        reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String s;
        //noinspection StatementWithEmptyBody
        while ((s = reader.readLine()) != null) {
            log.info(Thread.currentThread().getName() + " with fileIn " + fileIn + " and fileOut " + fileOut + " writes " + s);
            //Ignored as we just need to empty the output buffer from process
        }
        log.info(Thread.currentThread().getName() + " ffmpeg process will be waited for");
        if (process.waitFor( 10, TimeUnit.SECONDS )) {
            log.info(Thread.currentThread().getName() + " ffmpeg process exited normally");
        } else {
            log.info(Thread.currentThread().getName() + " ffmpeg process timed out and will be killed");
        }

    } catch (IOException | InterruptedException e) {
        log.error(Thread.currentThread().getName() + "Error during ffmpeg process executing", e);
    } finally {
        if (process != null) {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    log.error("Error during closing the process streams reader", e);
                }
            }
            try {
                process.getOutputStream().close();
            } catch (IOException e) {
                log.error("Error during closing the process output stream", e);
            }
            process.destroyForcibly();
            log.info(Thread.currentThread().getName() + " ffmpeg process " + process + " must be dead now");
        }
    }
}

如果我使用此代码运行单独的测试,它会正常运行。但在我的应用程序中,我有数百个RUNNING deamon线程“进程收割者”,它们正在等待ffmpeg进程完成。在我的真实应用程序中,ffpmeg是从计时器线程启动的。另外我在另外的线程中有另一个活动,但我不认为这是问题所在。最大CPU消耗约为10%。

这是我通常在线程转储中看到的:

"process reaper" #454 daemon prio=10 os_prio=0 tid=0x00007f641c007000 nid=0x5247 runnable [0x00007f63ec063000]
   java.lang.Thread.State: RUNNABLE
    at java.lang.UNIXProcess.waitForProcessExit(Native Method)
    at java.lang.UNIXProcess.lambda$initStreams$3(UNIXProcess.java:289)
    at java.lang.UNIXProcess$$Lambda$32/2113551491.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

我做错了什么?

UPD: 我的应用程序接受了大量与语音流量的连接。所以我每时每刻都有大约300-500个“好”线程。可能是原因吗? Deamon线程优先级低。但我不相信他们真的不能在一小时内完成工作。通常它需要几十毫米。

UPD2: 我的综合测试运行良好。我尝试了新的线程选项,没有它只是使用straigt调用run方法。

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class FFmpegConvert {

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

        FFmpegConvert f = new FFmpegConvert();
        f.processDir(args[0], args[1], args.length > 2);
    }

    private void processDir(String dirPath, String dirOutPath, boolean isNewThread) {
        File dir = new File(dirPath);
        File dirOut = new File(dirOutPath);
        if(!dirOut.exists()){
            dirOut.mkdir();
        }
        for (int i = 0; i < 1000; i++) {
            for (File f : dir.listFiles()) {
                try {
                    System.out.println(f.getName());
                    FFmpegRunner fFmpegRunner = new FFmpegRunner(f.getAbsolutePath(), dirOut.getAbsolutePath() + "/" + System.currentTimeMillis() + f.getName());
                    if (isNewThread) {
                        new Thread(fFmpegRunner).start();
                    } else {
                        fFmpegRunner.run();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class FFmpegRunner implements Runnable {
        private String fileIn;
        private String fileOut;

        FFmpegRunner(String fileIn, String fileOut) {
            this.fileIn = fileIn;
            this.fileOut = fileOut;
        }

        @Override
        public void run() {
            try {
                ffmpegconverter(fileIn, fileOut);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void ffmpegconverter(String fileIn, String fileOut) throws Exception{
            String[] comand = new String[]{"ffmpeg", "-i", fileIn, "-acodec", "pcm_s16le", fileOut};

            Process process = null;
            try {
                ProcessBuilder pb = new ProcessBuilder(comand);
                pb.redirectErrorStream(true);
                process = pb.start();

                //Reading from error and standard output console buffer of process. Or it could halts because of nobody
                //reads its buffer
                BufferedReader reader =
                        new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                //noinspection StatementWithEmptyBody
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                    //Ignored as we just need to empty the output buffer from process
                }

                process.waitFor();
            } catch (IOException | InterruptedException e) {
                throw e;
            } finally {
                if (process != null)
                    process.destroy();
            }
        }

    }

}

UPD3: 对不起,我忘了注意到我看到了所有这些过程的工作 - 他们创建了新的转换文件,但无论如何都不退出。

2 个答案:

答案 0 :(得分:1)

您的应用程序受I / O限制,而不受CPU限制。如果所有文件都在同一个硬盘中,那么同时打开300个文件肯定会降低性能。 (这可能是导致您有数百个进程等待的原因。)

如果我理解正确,你提到处理1个文件需要几十毫秒?

(这是顺序读取 - 你的硬盘读取文件的速度最快)

在这种情况下,顺序处理300个文件应该不超过30秒。

100毫秒 - 处理1个文件

1秒 - 处理10个文件

30秒 - 处理300个文件

修改

我对你的示例代码进行了2次简单的更改(我删除了第一个循环,然后更改了编解码器)最后我将一首歌放入&#34; ogg&#34;格式在&#34; / tmp / origin&#34;目录。现在该计划运作良好)。

请参阅以下代码:

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;

public class FFMpegConvert {

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

        FFMpegConvert f = new FFMpegConvert();
        f.processDir("/tmp/origin", "/tmp/destination", false);
    }

    private void processDir(String dirPath, String dirOutPath, boolean isNewThread) {
        File dir = new File(dirPath);
        File dirOut = new File(dirOutPath);
        if (!dirOut.exists()) {
            dirOut.mkdir();
        }

        for (File f : dir.listFiles()) {
            try {
                System.out.println(f.getName());
                FFmpegRunner fFmpegRunner = new FFmpegRunner(f.getAbsolutePath(), dirOut.getAbsolutePath() + "/" + System.currentTimeMillis() + f.getName());
                if (isNewThread) {
                    new Thread(fFmpegRunner).start();
                } else {
                    fFmpegRunner.run();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    class FFmpegRunner implements Runnable {
        private String fileIn;
        private String fileOut;

        FFmpegRunner(String fileIn, String fileOut) {
            this.fileIn = fileIn;
            this.fileOut = fileOut;
        }

        @Override
        public void run() {
            try {
                ffmpegconverter(fileIn, fileOut);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        private void ffmpegconverter(String fileIn, String fileOut) throws Exception {
            String[] comand = new String[]{"ffmpeg", "-i", fileIn, "-acodec", "copy", fileOut};

            Process process = null;
            try {
                ProcessBuilder pb = new ProcessBuilder(comand);
                pb.redirectErrorStream(true);
                process = pb.start();

                //Reading from error and standard output console buffer of process. Or it could halts because of nobody
                //reads its buffer
                BufferedReader reader =
                        new BufferedReader(new InputStreamReader(process.getInputStream()));
                String line;
                //noinspection StatementWithEmptyBody
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                    //Ignored as we just need to empty the output buffer from process
                }

                process.waitFor();
            } catch (IOException | InterruptedException e) {
                throw e;
            } finally {
                if (process != null)
                    process.destroy();
            }
        }

    }

}

答案 1 :(得分:0)

知道了! 在某些情况下,ffmpeg想要问我是否应该覆盖已存在的文件。在我的代码中,我关闭了这个子进程的输出流。但是看起来这只会关闭Java的输出流而不是关闭流程。

所以我的解决方案是让ffmpeg保持沉默:没有来自这个过程的输出&#34; -v -8&#34;,没有提出默认问题&#34;是&#34; &#34; -y&#34;