调用命令行中的 ffmpeg 并不会等待文件完全写入硬盘就返回。

huangapple go评论77阅读模式
英文:

Calling ffmpeg from command line does not wait until file was fully written do hard drive

问题

我目前正在开发一个用于转换音频文件的服务。我在底层使用ffmpeg工具,并使用Runtime来发起调用。

调用完成后,我会读取转换后的文件并将其上传到云存储。

问题:

问题是,从驱动器中读取文件时,我只得到了一些字节。经过调查,实际上驱动器上有大约5MB的文件,但readFileToByArray()只读取了几KB。我猜测这可能是因为在我想要读取它时,文件还没有完全持久化。

是否有任何方法可以确保ffmpeg完成向硬盘的写入?似乎ffmpeg运行的主要进程在负责写入的并行进程之前结束了。也许是这样的情况?

以下是将任意文件转换为AAC格式的相关代码:

File tempFile = File.createTempFile("input-", ".tmp", new File("/tmp"));
OutputStream outStream = new FileOutputStream(tempFile);
outStream.write(bytes);

String convertedFilePath = String.format("/tmp/output-%s.aac", UUID.randomUUID().toString());

String command = String.format(
        "ffmpeg -i %s -c:a aac -b:a 256k %s",
        tempFile.getAbsolutePath(),
        convertedFilePath
);

LOGGER.debug(String.format("Converting file to AAC; Running %s", command));

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
try {
    process.waitFor(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
    throw new RuntimeException("Time out");
}

File convertedFile = new File(convertedFilePath);
byte[] result = FileUtils.readFileToByteArray(convertedFile);

// 将 "result" 上传到云存储...
英文:

I am currently working on a service to allower conversion of audio files. I am using ffmpeg under the hood and use the Runtime in order to make the call.

After the call I read the converted file and upload it to a cloud storage.

The problem:

The problem is, that reading the file back from the drive gives me only a few bytes. After investigating, it actually has like 5 MB on the drive but readFileToByArray() reads only a few kb. I assume this is because the file was not completely persisted at the point where I want to read it back.

Is there any way I can make sure that ffmpeg is done writing to the hard drive? It seems that the main process that ffmpeg was running in finishes before a parallel process that is responsible for writing. Maybe?

Below is the relevant code that converts an arbitrary file to AAC-format:

File tempFile = File.createTempFile("input-", ".tmp", new File("/tmp"));
OutputStream outStream = new FileOutputStream(tempFile);
outStream.write(bytes);

String convertedFilePath = String.format("/tmp/output-%s.aac", UUID.randomUUID().toString());

String command = String.format(
        "ffmpeg -i %s -c:a aac -b:a 256k %s",
        tempFile.getAbsolutePath(),
        convertedFilePath
);

LOGGER.debug(String.format("Converting file to AAC; Running %s", command));

Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
try {
    process.waitFor(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
    throw new RuntimeException("Time out");
}

File convertedFile = new File(convertedFilePath);
byte[] result = FileUtils.readFileToByteArray(convertedFile);

// Upload "result" to cloud storage ..

答案1

得分: 1

我认为这里的问题在于您没有等待足够长的时间以使ffmpeg进程完成,仅在200毫秒后,您就继续尝试读取转换后的文件,而没有检查ffmpeg进程是否正确退出。

以下是您的代码的修改版本,利用了waitFor()返回值来决定是否需要继续等待:

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.Runtime;
import java.lang.RuntimeException;
import java.nio.file.Files;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

public class FFmpeg
{
    public static void main(String[] args) throws Exception
    {
        /* 仅用于测试目的 */
        File fooFile = new File("foo.mp3");
        byte[] bytes = Files.readAllBytes(fooFile.toPath());

        File tempFile = File.createTempFile("input-", ".tmp", new File("/tmp"));
        OutputStream outStream = new FileOutputStream(tempFile);
        outStream.write(bytes);
        outStream.close();

        String convertedFilePath = String.format("output-%s.aac", UUID.randomUUID().toString());
        String command = String.format(
                "ffmpeg -nostdin -i %s -c:a aac -b:a 256k %s",
                tempFile.getAbsolutePath(),
                convertedFilePath
        );

        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec(command);

        System.out.print("转换中");
        while (!process.waitFor(500, TimeUnit.MILLISECONDS)) {
            /* 在这里,您有机会在进程运行时间过长时终止进程、打印一些信息等 */
            System.out.print(".");
        }
        System.out.print("\n");

        if (process.exitValue() != 0) {
            System.err.printf("ffmpeg 失败,退出值为 %d\n", process.exitValue());
            return;
        }

        File convertedFile = new File(convertedFilePath);
        byte[] result = Files.readAllBytes(convertedFile.toPath());
        System.out.println(result.length);
    }
}

还有一个小演示:

$ ls -l
总用量 5368
-rw-r--r-- 1 marco marco    1557 十月 10 17:46 FFmpeg.java
-rw-r--r-- 1 marco marco 5486341 十月 10 17:09 foo.mp3
$ javac FFmpeg.java
$ java FFmpeg
转换中.........
7329962
$ ls -l
总用量 12528
-rw-r--r-- 1 marco marco    1793 十月 10 17:49 FFmpeg.class
-rw-r--r-- 1 marco marco    1557 十月 10 17:46 FFmpeg.java
-rw-r--r-- 1 marco marco 5486341 十月 10 17:09 foo.mp3
-rw-r--r-- 1 marco marco 7329962 十月 10 17:50 output-176a2e73-82d6-483b-8a40-aec0819c749f.aac
$

(转换后文件的长度在最后被打印出来,注意它与ls输出中的长度匹配)。

我还添加了ffmpeg命令中的-nostdin标志。ffmpeg通常作为交互式程序运行,从stdin获取用户输入,例如当它询问您是否要覆盖目标文件时。在这里,当然我们没有机会回答“是”或“否”,-nostdin将使进程在需要用户交互时失败并退出。如果没有这个标志,它将无限期等待用户输入(您可能还对-y-n标志感兴趣)。

英文:

I think the problem here is that you don't wait enough for ffmpeg process to finish and after only 200 milliseconds you go on trying to read the converted file without checking whether ffmpeg process exited correctly.

from waitFor​(long timeout, TimeUnit unit) docs (emphasis mine):

> Causes the current thread to wait, if necessary, until the process represented by this Process object has terminated, or the specified waiting time elapses.
>
> ...
>
> Returns:
>
> true if the process has exited and false if the waiting time elapsed before the process has exited.

here a rewrite of your code that takes advantage of waitFor() return value to decide if it needs to keep waiting or not:

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.Runtime;
import java.lang.RuntimeException;
import java.nio.file.Files;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class FFmpeg
{
public static void main(String[] args) throws Exception
{
/* just for testing purpose */
File fooFile = new File("foo.mp3");
byte[] bytes = Files.readAllBytes(fooFile.toPath());
File tempFile = File.createTempFile("input-", ".tmp", new File("/tmp"));
OutputStream outStream = new FileOutputStream(tempFile);
outStream.write(bytes);
outStream.close();
String convertedFilePath = String.format("output-%s.aac", UUID.randomUUID().toString());
String command = String.format(
"ffmpeg -nostdin -i %s -c:a aac -b:a 256k %s",
tempFile.getAbsolutePath(),
convertedFilePath
);
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec(command);
System.out.print("converting");
while ( ! process.waitFor(500, TimeUnit.MILLISECONDS)) {
/* here you have the opportunity to kill the process if
* it is taking too long, print something etc.. */
System.out.print(".");
}
System.out.print("\n");
if (process.exitValue() != 0) {
System.err.printf("ffmpeg failed with value %d\n", process.exitValue());
return;
}
File convertedFile = new File(convertedFilePath);
byte[] result = Files.readAllBytes(convertedFile.toPath());
System.out.println(result.length);
}
}

and a little demo:

$ ls -l
total 5368
-rw-r--r-- 1 marco marco    1557 Oct 10 17:46 FFmpeg.java
-rw-r--r-- 1 marco marco 5486341 Oct 10 17:09 foo.mp3
$ javac FFmpeg.java
$ java FFmpeg
converting.........
7329962
$ ls -l
total 12528
-rw-r--r-- 1 marco marco    1793 Oct 10 17:49 FFmpeg.class
-rw-r--r-- 1 marco marco    1557 Oct 10 17:46 FFmpeg.java
-rw-r--r-- 1 marco marco 5486341 Oct 10 17:09 foo.mp3
-rw-r--r-- 1 marco marco 7329962 Oct 10 17:50 output-176a2e73-82d6-483b-8a40-aec0819c749f.aac
$

(length of converted file is printed at the end, notice how it matches the one in ls output).

another important thing I added is the -nostdin flag to ffmpeg command. ffmpeg is often run as an interactive program taking user input from stdin e.g. when it asks if you want to overwrite target file. here of course we don't have the chance to answer "yes" or "no" and -nostdin will make the process to fail and exit when a user interaction is required. without that flag, it will wait for user input indefinitely (you maybe interested in -y and -n flags also).

huangapple
  • 本文由 发表于 2020年10月10日 18:22:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/64292363.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定