package net.sourceforge.sox;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.ffmpeg.android.R;
import org.ffmpeg.android.ShellUtils.ShellCallback;
import android.content.Context;
import android.util.Log;
public class SoxController {
private final static String TAG = "SOX";
String[] libraryAssets = {"sox"};
private String soxBin;
private File fileBinDir;
private ShellCallback callback;
public SoxController(Context context, File fileAppRoot, ShellCallback _callback) throws FileNotFoundException, IOException {
callback = _callback;
installBinaries(context, false);
fileBinDir = new File(soxBin).getParentFile();
}
public void installBinaries(Context context, boolean overwrite)
{
soxBin = installBinary(context, R.raw.sox, "sox", overwrite);
}
public String getBinaryPath ()
{
return soxBin;
}
private static String installBinary(Context ctx, int resId, String filename, boolean upgrade) {
try {
File f = new File(ctx.getDir("bin", 0), filename);
if (f.exists()) {
f.delete();
}
copyRawFile(ctx, resId, f, "0755");
return f.getCanonicalPath();
} catch (Exception e) {
Log.e(TAG, "installBinary failed: " + e.getLocalizedMessage());
return null;
}
}
/**
* Copies a raw resource file, given its ID to the given location
* @param ctx context
* @param resid resource id
* @param file destination file
* @param mode file permissions (E.g.: "755")
* @throws IOException on error
* @throws InterruptedException when interrupted
*/
private static void copyRawFile(Context ctx, int resid, File file, String mode) throws IOException, InterruptedException
{
final String abspath = file.getAbsolutePath();
// Write the iptables binary
final FileOutputStream out = new FileOutputStream(file);
final InputStream is = ctx.getResources().openRawResource(resid);
byte buf[] = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.close();
is.close();
// Change the permissions
Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor();
}
private class LengthParser implements ShellCallback {
public double length;
public int retValue = -1;
@Override
public void shellOut(String shellLine) {
if( !shellLine.startsWith("Length") )
return;
String[] split = shellLine.split(":");
if(split.length != 2) return;
String lengthStr = split[1].trim();
try {
length = Double.parseDouble( lengthStr );
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
@Override
public void processComplete(int exitValue) {
retValue = exitValue;
}
}
/**
* Retrieve the length of the audio file
* sox file.wav 2>&1 -n stat | grep Length | cut -d : -f 2 | cut -f 1
* @return the length in seconds or null
*/
public double getLength(String path) {
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(soxBin);
cmd.add(path);
cmd.add("-n");
cmd.add("stat");
LengthParser sc = new LengthParser();
try {
execSox(cmd, sc);
} catch (Exception e) {
return -1;
}
return sc.length;
}
/**
* Discard all audio not between start and length (length = end by default)
* sox <path> -e signed-integer -b 16 outFile trim <start> <length>
* @param start
* @param length (optional)
* @return path to trimmed audio
*/
public String trimAudio(String path, double start, double length) throws Exception {
ArrayList<String> cmd = new ArrayList<String>();
File file = new File(path);
String outFile = file.getCanonicalPath() + "_trimmed.wav";
cmd.add(soxBin);
cmd.add(path);
cmd.add("-e");
cmd.add("signed-integer");
cmd.add("-b");
cmd.add("16");
cmd.add(outFile);
cmd.add("trim");
cmd.add(start+"");
if( length != -1 )
cmd.add(length+"");
int rc = execSox(cmd, callback);
if( rc != 0 ) {
outFile = null;
}
if (file.exists())
return outFile;
else
return null;
}
/**
* Fade audio file
* sox <path> outFile fade <type> <fadeInLength> <stopTime> <fadeOutLength>
* @param path
* @param type
* @param fadeInLength specify 0 if no fade in is desired
* @param stopTime (optional)
* @param fadeOutLength (optional)
* @return
*/
public String fadeAudio(String path, String type, double fadeInLength, double stopTime, double fadeOutLength ) throws IOException {
final List<String> curves = Arrays.asList( new String[]{ "q", "h", "t", "l", "p"} );
if(!curves.contains(type)) {
throw new RuntimeException("fadeAudio: passed invalid type: " + type);
}
File file = new File(path);
String outFile = file.getCanonicalPath() + "_faded.wav";
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(soxBin);
cmd.add(path);
cmd.add(outFile);
cmd.add("fade");
cmd.add(type);
cmd.add(fadeInLength+"");
if(stopTime != -1)
cmd.add(stopTime+"");
if(fadeOutLength != -1)
cmd.add(fadeOutLength+"");
try {
int rc = execSox(cmd, callback);
if(rc != 0) {
//Log.e(TAG, "fadeAudio receieved non-zero return code!");
outFile = null;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return outFile;
}
/**
* Combine and mix audio files
* sox -m -v 1.0 file[0] -v 1.0 file[1] ... -v 1.0 file[n] outFile
* TODO support passing of volume
* @param files
* @return combined and mixed file (null on failure)
*/
public String combineMix(List<String> files, String outFile) {
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(soxBin);
cmd.add("-m");
for(String file : files) {
cmd.add("-v");
cmd.add("1.0");
cmd.add(file);
}
cmd.add(outFile);
try {
int rc = execSox(cmd, callback);
if(rc != 0) {
// Log.e(TAG, "combineMix receieved non-zero return code!");
outFile = null;
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return outFile;
}
/**
* Simple combiner
* sox file[0] file[1] ... file[n] <outFile>
* @param files
* @param outFile
* @return outFile or null on failure
*/
public String combine(List<String> files, String outFile) throws Exception {
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(soxBin);
for(String file : files) {
cmd.add(file);
}
cmd.add(outFile);
int rc = execSox(cmd, callback);
if(rc != 0) {
throw new Exception ("exit code: " + rc);
}
return outFile;
}
/**
* Takes a seconds.frac value and formats it into:
* hh:mm:ss:ss.frac
* @param seconds
*/
/*
public String formatTimePeriod(double seconds) {
long milliTime = (long)(seconds * 100f);
Date dateTime = new Date(milliTime);
return String.format(Locale.US, "%s:%s.%s", dateTime.getHours(),dateTime.getMinutes(),dateTime.getSeconds());
}*/
public int execSox(List<String> cmd, ShellCallback sc) throws IOException,
InterruptedException {
String soxBin = new File(fileBinDir, "sox").getCanonicalPath();
Runtime.getRuntime().exec("chmod 700 " + soxBin);
return execProcess(cmd, sc);
}
private int execProcess(List<String> cmds, ShellCallback sc)
throws IOException, InterruptedException {
//ensure that the arguments are in the correct Locale format
for (String cmd :cmds)
{
cmd = String.format(Locale.US, "%s", cmd);
}
ProcessBuilder pb = new ProcessBuilder(cmds);
pb.directory(fileBinDir);
StringBuffer cmdlog = new StringBuffer();
for (String cmd : cmds) {
cmdlog.append(cmd);
cmdlog.append(' ');
}
sc.shellOut(cmdlog.toString());
// pb.redirectErrorStream(true);
Process process = pb.start();
// any error message?
StreamGobbler errorGobbler = new StreamGobbler(
process.getErrorStream(), "ERROR", sc);
// any output?
StreamGobbler outputGobbler = new StreamGobbler(
process.getInputStream(), "OUTPUT", sc);
// kick them off
errorGobbler.start();
outputGobbler.start();
int exitVal = process.waitFor();
while (outputGobbler.isAlive() || errorGobbler.isAlive());
sc.processComplete(exitVal);
return exitVal;
}
class StreamGobbler extends Thread {
InputStream is;
String type;
ShellCallback sc;
StreamGobbler(InputStream is, String type, ShellCallback sc) {
this.is = is;
this.type = type;
this.sc = sc;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
if (sc != null)
sc.shellOut(line);
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}