Capturando grandes quantidades de saída do Apache Commons-Exec
-
12-09-2019 - |
Pergunta
Estou escrevendo um aplicativo de vídeo em Java executando ffmpeg
e captura sua saída para saída padrão. Eu decidi usar o Apache Commons-Exec em vez do Java's Runtime
, porque parece melhor. No entanto, tenho dificuldade em capturar toda a saída.
Eu pensei que usar tubos seria o caminho a percorrer, porque é uma maneira padrão de comunicação entre processos. No entanto, minha configuração usando PipedInputStream
e PipedOutputStream
está errado. Parece funcionar, mas apenas para os primeiros 1042 bytes do fluxo, que curiosamente são o valor de PipedInputStream.PIPE_SIZE
.
Não tenho um caso de amor com o uso de tubos, mas quero evitar o uso de E/S de disco (se possível), devido à velocidade e volume de dados (um vídeo de 1m 20s em 512x384 Resolution produz 690M
de dados canalizados).
Pensamentos sobre a melhor solução para lidar com grandes quantidades de dados provenientes de um tubo? Meu código para minhas duas classes está abaixo. (sim, sleep
é ruim. Pensamentos sobre isso? wait()
e notifyAll()
?)
WriteFrames.java
public class WriteFrames {
public static void main(String[] args) {
String commandName = "ffmpeg";
CommandLine commandLine = new CommandLine(commandName);
File filename = new File(args[0]);
String[] options = new String[] {
"-i",
filename.getAbsolutePath(),
"-an",
"-f",
"yuv4mpegpipe",
"-"};
for (String s : options) {
commandLine.addArgument(s);
}
PipedOutputStream output = new PipedOutputStream();
PumpStreamHandler streamHandler = new PumpStreamHandler(output, System.err);
DefaultExecutor executor = new DefaultExecutor();
try {
DataInputStream is = new DataInputStream(new PipedInputStream(output));
YUV4MPEGPipeParser p = new YUV4MPEGPipeParser(is);
p.start();
executor.setStreamHandler(streamHandler);
executor.execute(commandLine);
} catch (IOException e) {
e.printStackTrace();
}
}
}
YUV4MPEGPipeParser.java
public class YUV4MPEGPipeParser extends Thread {
private InputStream is;
int width, height;
public YUV4MPEGPipeParser(InputStream is) {
this.is = is;
}
public void run() {
try {
while (is.available() == 0) {
Thread.sleep(100);
}
while (is.available() != 0) {
// do stuff.... like write out YUV frames
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Solução
O problema está no método de execução da classe yuv4mpegpipeparsser. Existem dois loops sucessivos. O segundo loop termina imediatamente se não houver dados atualmente disponíveis no fluxo (por exemplo, todas as entradas até agora foram processadas pelo analisador, e o FFMPEG ou a bomba de fluxo não foram rápidos o suficiente para servir alguns dados novos para ele -> disponível () == 0 -> O loop é encerrado -> acabamentos da rosca da bomba).
Basta livrar -se desses dois loops e dormir e apenas executar uma leitura simples de bloqueio () em vez de verificar se houver algum dado disponível para processamento. Provavelmente também não há necessidade de esperar ()/notify () ou até dormir () porque o código do analisador é iniciado em um encadeamento separado.
Você pode reescrever o método Code of Run () como este:
public class YUV4MPEGPipeParser extends Thread {
...
// optimal size of buffer for reading from pipe stream :-)
private static final int BUFSIZE = PipedInputStream.PIPE_SIZE;
public void run() {
try {
byte buffer[] = new byte[BUFSIZE];
int len = 0;
while ((len = is.read(buffer, 0, BUFSIZE) != -1) {
// we have valid data available
// in first 'len' bytes of 'buffer' array.
// do stuff.... like write out YUV frames
}
} catch ...
}
}