be.tarsos.transcoder.ffmpeg.FFMPEGExecutor.java Source code

Java tutorial

Introduction

Here is the source code for be.tarsos.transcoder.ffmpeg.FFMPEGExecutor.java

Source

/*
 * JAVE - A Java Audio/Video Encoder (based on FFMPEG)
 * 
 * Copyright (C) 2008-2009 Carlo Pelliccia (www.sauronsoftware.it)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package be.tarsos.transcoder.ffmpeg;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ProcessBuilder.Redirect;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;

import be.tarsos.transcoder.Attributes;

/**
 * A ffmpeg process wrapper.
 * 
 * @author Carlo Pelliccia
 */
class FFMPEGExecutor {

    /**
     * Log messages.
     */
    private static final Logger LOG = Logger.getLogger(FFMPEGExecutor.class.getName());

    /**
     * The path of the ffmpeg executable.
     */
    private final String ffmpegExecutablePath;

    /**
     * Arguments for the executable.
     */
    private final ArrayList<String> args = new ArrayList<String>();
    private final ArrayList<Boolean> argIsFile = new ArrayList<Boolean>();

    /**
     * It build the executor.
     * 
     * @param ffmpegExecutablePath
     *            The path of the ffmpeg executable.
     */
    public FFMPEGExecutor(String ffmpegExecutablePath) {
        this.ffmpegExecutablePath = ffmpegExecutablePath;
    }

    /**
     * Adds an argument to the ffmpeg executable call.
     * 
     * @param arg
     *            The argument.
     */
    public void addArgument(String arg) {
        args.add(arg);
        argIsFile.add(false);
    }

    /**
     * Add a file to the ffmpeg executable call.
     * @param arg
     */
    public void addFileArgument(String arg) {
        args.add(arg);
        argIsFile.add(true);
    }

    /**
     * Executes the ffmpeg process with the previous given arguments.
     * 
     * @return The standard output of the child process.
     * 
     * @throws IOException
     *             If the process call fails.
     */
    public String execute() throws IOException {
        CommandLine cmdLine = new CommandLine(ffmpegExecutablePath);

        int fileNumber = 0;
        Map<String, File> map = new HashMap<String, File>();
        for (int i = 0; i < args.size(); i++) {
            final String arg = args.get(i);
            final Boolean isFile = argIsFile.get(i);
            if (isFile) {
                String key = "file" + fileNumber;
                map.put(key, new File(arg));
                cmdLine.addArgument("'${" + key + "}'", false);
                fileNumber++;
            } else {
                cmdLine.addArgument(arg);
            }
        }
        cmdLine.setSubstitutionMap(map);
        LOG.fine("Execute: " + cmdLine);
        DefaultExecutor executor = new DefaultExecutor();
        //5minutes wait
        ExecuteWatchdog watchdog = new ExecuteWatchdog(60 * 1000 * 5);
        executor.setWatchdog(watchdog);
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        executor.setStreamHandler(new PumpStreamHandler(out));
        int[] exitValues = { 0, 1 };
        executor.setExitValues(exitValues);
        executor.execute(cmdLine);
        return out.toString();
    }

    public AudioInputStream pipe(Attributes attributes) throws EncoderException {
        String pipeEnvironment;
        String pipeArgument;
        File pipeLogFile;
        int pipeBuffer;

        if (System.getProperty("os.name").indexOf("indows") > 0) {
            pipeEnvironment = "cmd.exe";
            pipeArgument = "/C";
        } else {
            pipeEnvironment = "/bin/bash";
            pipeArgument = "-c";
        }
        pipeLogFile = new File("decoder_log.txt");
        //buffer 1/4 second of audio.
        pipeBuffer = attributes.getSamplingRate() / 4;

        AudioFormat audioFormat = Encoder.getTargetAudioFormat(attributes);

        String command = toString();

        ProcessBuilder pb = new ProcessBuilder(pipeEnvironment, pipeArgument, command);

        pb.redirectError(Redirect.appendTo(pipeLogFile));

        LOG.fine("Starting piped decoding process");
        final Process process;
        try {
            process = pb.start();
        } catch (IOException e1) {
            throw new EncoderException("Problem starting piped sub process: " + e1.getMessage());
        }

        InputStream stdOut = new BufferedInputStream(process.getInputStream(), pipeBuffer);

        //read and ignore the 46 byte wav header, only pipe the pcm samples to the audioinputstream
        byte[] header = new byte[46];
        double sleepSeconds = 0;
        double timeoutLimit = 20; //seconds

        try {
            while (stdOut.available() < header.length) {
                try {
                    Thread.sleep(100);
                    sleepSeconds += 0.1;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (sleepSeconds > timeoutLimit) {
                    throw new Error("Could not read from pipe within " + timeoutLimit + " seconds: timeout!");
                }
            }
            int bytesRead = stdOut.read(header);
            if (bytesRead != header.length) {
                throw new EncoderException(
                        "Could not read complete WAV-header from pipe. This could result in mis-aligned frames!");
            }
        } catch (IOException e1) {
            throw new EncoderException("Problem reading from piped sub process: " + e1.getMessage());
        }

        final AudioInputStream audioStream = new AudioInputStream(stdOut, audioFormat, AudioSystem.NOT_SPECIFIED);

        //This thread waits for the end of the subprocess.
        new Thread(new Runnable() {
            public void run() {
                try {
                    process.waitFor();
                    LOG.fine("Finished piped decoding process");
                } catch (InterruptedException e) {
                    LOG.severe("Interrupted while waiting for sub process exit.");
                    e.printStackTrace();
                }
            }
        }, "Decoding Pipe Reader").start();
        return audioStream;
    }

    public String toString() {
        CommandLine cmdLine = new CommandLine(ffmpegExecutablePath);

        int fileNumber = 0;
        Map<String, File> map = new HashMap<String, File>();
        for (int i = 0; i < args.size(); i++) {
            final String arg = args.get(i);
            final Boolean isFile = argIsFile.get(i);
            if (isFile) {
                String key = "file" + fileNumber;
                map.put(key, new File(arg));
                cmdLine.addArgument("${" + key + "}", false);
                fileNumber++;
            } else {
                cmdLine.addArgument(arg);
            }
        }
        cmdLine.setSubstitutionMap(map);
        return cmdLine.toString();
    }

}