Skip to content

Instantly share code, notes, and snippets.

@kmturley
Created November 10, 2015 16:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save kmturley/695bd1612c2ef47d42f6 to your computer and use it in GitHub Desktop.
Save kmturley/695bd1612c2ef47d42f6 to your computer and use it in GitHub Desktop.
Extending video editor functionality
package org.ffmpeg.android;
import java.io.BufferedOutputStream;
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.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import org.ffmpeg.android.ShellUtils.ShellCallback;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.media.MediaMetadataRetriever;
import android.util.Log;
public class FfmpegController {
private String mFfmpegBin;
private final static String TAG = "FFMPEG";
private File mFileTemp;
private String mCmdCat = "sh cat";
public FfmpegController(Context context, File fileTemp) throws FileNotFoundException, IOException {
mFileTemp = fileTemp;
installBinaries(context, false);
}
public void installBinaries(Context context, boolean overwrite)
{
mFfmpegBin = installBinary(context, R.raw.ffmpeg, "ffmpeg", overwrite);
}
public String getBinaryPath ()
{
return mFfmpegBin;
}
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 int execFFMPEG (List<String> cmd, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
enablePermissions();
return execProcess (cmd, sc, fileExec);
}
private void enablePermissions () throws IOException
{
Runtime.getRuntime().exec("chmod 700 " + mFfmpegBin);
}
private int execFFMPEG (List<String> cmd, ShellCallback sc) throws IOException, InterruptedException {
return execFFMPEG (cmd, sc, new File(mFfmpegBin).getParentFile());
}
private int execProcess(List<String> cmds, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
//ensure that the arguments are in the correct Locale format
for (String cmd :cmds)
{
cmd = String.format(Locale.US, "%s", cmd);
}
//Log.e(TAG, "execProcess: " + cmds.toString());
ProcessBuilder pb = new ProcessBuilder(cmds);
pb.directory(fileExec);
StringBuffer cmdlog = new StringBuffer();
for (String cmd : cmds)
{
cmdlog.append(cmd);
cmdlog.append(' ');
}
Log.e(TAG, "execProcess: " + cmdlog.toString());
sc.shellOut(cmdlog.toString());
//pb.redirectErrorStream(true);
Process process = pb.start();
//Log.e(TAG, "process id: " + process.pid);
// any error message?
StreamGobbler errorGobbler = new StreamGobbler(
process.getErrorStream(), "ERROR", sc);
// any output?
StreamGobbler outputGobbler = new
StreamGobbler(process.getInputStream(), "OUTPUT", sc);
errorGobbler.start();
outputGobbler.start();
int exitVal = process.waitFor();
sc.processComplete(exitVal);
return exitVal;
}
private int execProcess(String cmd, ShellCallback sc, File fileExec) throws IOException, InterruptedException {
//Log.e(TAG, "execProcess3: " + cmd);
//ensure that the argument is in the correct Locale format
cmd = String.format(Locale.US, "%s", cmd);
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.directory(fileExec);
// 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();
sc.processComplete(exitVal);
return exitVal;
}
public class Argument
{
String key;
String value;
public static final String VIDEOCODEC = "-vcodec";
public static final String AUDIOCODEC = "-acodec";
public static final String VIDEOBITSTREAMFILTER = "-vbsf";
public static final String AUDIOBITSTREAMFILTER = "-absf";
public static final String VERBOSITY = "-v";
public static final String FILE_INPUT = "-i";
public static final String SIZE = "-s";
public static final String FRAMERATE = "-r";
public static final String FORMAT = "-f";
public static final String BITRATE_VIDEO = "-b:v";
public static final String BITRATE_AUDIO = "-b:a";
public static final String CHANNELS_AUDIO = "-ac";
public static final String FREQ_AUDIO = "-ar";
public static final String STARTTIME = "-ss";
public static final String DURATION = "-t";
}
public void processVideo(Clip in, Clip out, boolean enableExperimental, ShellCallback sc) throws Exception {
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (in.format != null)
{
cmd.add(Argument.FORMAT);
cmd.add(in.format);
}
if (in.videoCodec != null)
{
cmd.add(Argument.VIDEOCODEC);
cmd.add(in.videoCodec);
if (in.videoCodec == "libx264") {
cmd.add("-preset");
cmd.add("ultrafast"); // needed b/c libx264 doesn't utilize all CPU cores
}
}
if (in.audioCodec != null)
{
cmd.add(Argument.AUDIOCODEC);
cmd.add(in.audioCodec);
}
cmd.add("-i");
cmd.add(new File(in.path).getCanonicalPath());
if (out.videoBitrate > 0)
{
cmd.add(Argument.BITRATE_VIDEO);
cmd.add(out.videoBitrate + "k");
}
if (out.width > 0)
{
cmd.add(Argument.SIZE);
cmd.add(out.width + "x" + out.height);
}
if (out.videoFps != null)
{
cmd.add(Argument.FRAMERATE);
cmd.add(out.videoFps);
}
if (out.videoCodec != null)
{
cmd.add(Argument.VIDEOCODEC);
cmd.add(out.videoCodec);
}
if (out.videoBitStreamFilter != null)
{
cmd.add(Argument.VIDEOBITSTREAMFILTER);
cmd.add(out.videoBitStreamFilter);
}
if (out.videoFilter != null)
{
cmd.add("-vf");
cmd.add(out.videoFilter);
}
if (out.audioCodec != null)
{
cmd.add(Argument.AUDIOCODEC);
cmd.add(out.audioCodec);
}
if (out.audioBitStreamFilter != null)
{
cmd.add(Argument.AUDIOBITSTREAMFILTER);
cmd.add(out.audioBitStreamFilter);
}
if (out.audioChannels > 0)
{
cmd.add(Argument.CHANNELS_AUDIO);
cmd.add(out.audioChannels+"");
}
if (out.audioBitrate > 0)
{
cmd.add(Argument.BITRATE_AUDIO);
cmd.add(out.audioBitrate + "k");
}
if (out.format != null)
{
cmd.add("-f");
cmd.add(out.format);
}
if (enableExperimental)
{
cmd.add("-strict");
cmd.add("-2");//experimental
}
// I had issues with orientation and this fixes it, YMMV
// If orientation is incorrect comment out these 4 lines below
cmd.add("-vf");
cmd.add("transpose=1");
cmd.add("-metadata:s:v:0");
cmd.add("rotate=0");
cmd.add(new File(out.path).getCanonicalPath());
int exitValue = execFFMPEG(cmd, sc);
Log.d(TAG, "execFFMPEG: " + exitValue);
}
public Clip createSlideshowFromImagesAndAudio (ArrayList<Clip> images, Clip audio, Clip out, int durationPerSlide, ShellCallback sc) throws Exception
{
final String imageBasePath = new File(mFileTemp,"image-").getCanonicalPath();
final String imageBaseVariablePath = imageBasePath + "%03d.jpg";
ArrayList<String> cmd = new ArrayList<String>();
String newImagePath = null;
int imageCounter = 0;
Clip imageCover = images.get(0); //add the first image twice
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(new File(imageCover.path).getCanonicalPath());
if (out.width != -1 && out.height != -1)
{
cmd.add("-s");
cmd.add(out.width + "x" + out.height);
}
newImagePath = imageBasePath + String.format(Locale.US, "%03d", imageCounter++) + ".jpg";
cmd.add(newImagePath);
execFFMPEG(cmd, sc);
for (Clip image : images)
{
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(new File(image.path).getCanonicalPath());
if (out.width != -1 && out.height != -1)
{
cmd.add("-s");
cmd.add(out.width + "x" + out.height);
}
newImagePath = imageBasePath + String.format(Locale.US, "%03d", imageCounter++) + ".jpg";
cmd.add(newImagePath);
execFFMPEG(cmd, sc);
}
//then combine them
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-loop");
cmd.add("0");
cmd.add("-f");
cmd.add("image2");
cmd.add("-r");
cmd.add("1/" + durationPerSlide);
cmd.add("-i");
cmd.add(imageBaseVariablePath);
cmd.add("-strict");
cmd.add("-2");//experimental
String fileTempMpg = new File(mFileTemp,"tmp.mpg").getCanonicalPath();
cmd.add(fileTempMpg);
execFFMPEG(cmd, sc);
//now combine and encode
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(fileTempMpg);
if (audio != null && audio.path != null)
{
cmd.add("-i");
cmd.add(new File(audio.path).getCanonicalPath());
cmd.add("-map");
cmd.add("0:0");
cmd.add("-map");
cmd.add("1:0");
cmd.add(Argument.AUDIOCODEC);
cmd.add("aac");
cmd.add(Argument.BITRATE_AUDIO);
cmd.add("128k");
}
cmd.add("-strict");
cmd.add("-2");//experimental
cmd.add(Argument.VIDEOCODEC);
if (out.videoCodec != null)
cmd.add(out.videoCodec);
else
cmd.add("mpeg4");
if (out.videoBitrate != -1)
{
cmd.add(Argument.BITRATE_VIDEO);
cmd.add(out.videoBitrate + "k");
}
cmd.add(new File(out.path).getCanonicalPath());
execFFMPEG(cmd, sc);
return out;
}
/*
* ffmpeg -y -loop 0 -f image2 -r 0.5 -i image-%03d.jpg -s:v 1280x720 -b:v 1M \
-i soundtrack.mp3 -t 01:05:00 -map 0:0 -map 1:0 out.avi
-loop_input – loops the images. Disable this if you want to stop the encoding when all images are used or the soundtrack is finished.
-r 0.5 – sets the framerate to 0.5, which means that each image will be shown for 2 seconds. Just take the inverse, for example if you want each image to last for 3 seconds, set it to 0.33.
-i image-%03d.jpg – use these input files. %03d means that there will be three digit numbers for the images.
-s 1280x720 – sets the output frame size.
-b 1M – sets the bitrate. You want 500MB for one hour, which equals to 4000MBit in 3600 seconds, thus a bitrate of approximately 1MBit/s should be sufficient.
-i soundtrack.mp3 – use this soundtrack file. Can be any format.
-t 01:05:00 – set the output length in hh:mm:ss format.
out.avi – create this output file. Change it as you like, for example using another container like MP4.
*/
public Clip combineAudioAndVideo (Clip videoIn, Clip audioIn, Clip out, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(new File(videoIn.path).getCanonicalPath());
cmd.add("-i");
cmd.add(new File(audioIn.path).getCanonicalPath());
cmd.add("-strict");
cmd.add("-2");//experimental
cmd.add(Argument.AUDIOCODEC);
if (out.audioCodec != null)
cmd.add(out.audioCodec);
else
{
cmd.add("copy");
}
cmd.add(Argument.VIDEOCODEC);
if (out.videoCodec != null)
cmd.add(out.videoCodec);
else
{
cmd.add("copy");
}
if (out.videoBitrate != -1)
{
cmd.add(Argument.BITRATE_VIDEO);
cmd.add(out.videoBitrate + "k");
}
if (out.videoFps != null)
{
cmd.add(Argument.FRAMERATE);
cmd.add(out.videoFps);
}
if (out.audioBitrate != -1)
{
cmd.add(Argument.BITRATE_AUDIO);
cmd.add(out.audioBitrate + "k");
}
cmd.add("-y");
cmd.add("-cutoff");
cmd.add("15000");
if (out.width > 0)
{
cmd.add(Argument.SIZE);
cmd.add(out.width + "x" + out.height);
}
if (out.format != null)
{
cmd.add("-f");
cmd.add(out.format);
}
File fileOut = new File(out.path);
cmd.add(fileOut.getCanonicalPath());
execFFMPEG(cmd, sc);
return out;
}
public Clip convertImageToMP4 (Clip mediaIn, int duration, String outPath, ShellCallback sc) throws Exception
{
Clip result = new Clip ();
ArrayList<String> cmd = new ArrayList<String>();
// ffmpeg -loop 1 -i IMG_1338.jpg -t 10 -r 29.97 -s 640x480 -qscale 5 test.mp4
cmd = new ArrayList<String>();
//convert images to MP4
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-loop");
cmd.add("1");
cmd.add(Argument.DURATION);
cmd.add(duration + "");
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
cmd.add(Argument.FRAMERATE);
cmd.add(mediaIn.videoFps);
cmd.add("-qscale");
cmd.add("5"); //a good value 1 is best 30 is worst
if (mediaIn.width != -1)
{
cmd.add(Argument.SIZE);
cmd.add(mediaIn.width + "x" + mediaIn.height);
// cmd.add("-vf");
// cmd.add("\"scale=-1:" + mediaIn.width + "\"");
}
if (mediaIn.videoBitrate != -1)
{
cmd.add(Argument.BITRATE_VIDEO);
cmd.add(mediaIn.videoBitrate + "");
}
// -ar 44100 -acodec pcm_s16le -f s16le -ac 2 -i /dev/zero -acodec aac -ab 128k \
// -map 0:0 -map 1:0
result.path = outPath;
result.videoBitrate = mediaIn.videoBitrate;
result.videoFps = mediaIn.videoFps;
result.mimeType = "video/mp4";
cmd.add(new File(result.path).getCanonicalPath());
execFFMPEG(cmd, sc);
return result;
}
public Clip getVersion (Clip mediaIn, String startTime, String endTime, String outPath, ShellCallback sc, String overlay) throws Exception
{
Clip mediaOut = new Clip();
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-version");
execFFMPEG(cmd, sc);
return mediaOut;
}
//based on this gist: https://gist.github.com/3757344
//ffmpeg -i input1.mp4 -vcodec copy -vbsf h264_mp4toannexb -acodec copy part1.ts
//ffmpeg -i input2.mp4 -c copy -bsf:v h264_mp4toannexb -f mpegts intermediate2.ts
public Clip convertToMP4Stream (Clip mediaIn, String startTime, String endTime, String outPath, ShellCallback sc, String overlay) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
Clip mediaOut = new Clip();
mediaOut.path = outPath;
String mediaPath = new File(mediaIn.path).getCanonicalPath();
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (startTime != null) {
cmd.add(Argument.STARTTIME);
cmd.add(startTime);
}
if (endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(endTime);
}
cmd.add("-i");
cmd.add(mediaPath);
if (overlay != null && !overlay.isEmpty()) {
cmd.add("-i");
cmd.add(overlay);
cmd.add("-filter_complex");
//cmd.add("[0:v][1:v] overlay=0:0:enable='between(t,0,1)'");
cmd.add("[0:v][1:v] overlay=0:0");
//cmd.add("[0:v]setpts=PTS-STARTPTS[v0];[v0][0:a]concat=n=1:v=1:a=1[v][aout];[v][1:v]overlay=W-w-10:10[vout]");
// cmd.add("-map");
// cmd.add("[vout]");
// cmd.add("-map");
// cmd.add("[aout]");
Log.d(TAG, "added overlay!");
} else {
// we can't use Stream copy (which is faster) when using filters :(
//cmd.add("-c");
//cmd.add("copy");
// mpg only supports 2 channels of audio
cmd.add("-ac");
cmd.add("2");
}
cmd.add("-f");
cmd.add("mpegts");
cmd.add("-preset");
cmd.add("ultrafast");
cmd.add("-qscale");
cmd.add("0");
// cmd.add("-threads");
// cmd.add("4");
//cmd.add("-an");
//cmd.add(Argument.VIDEOBITSTREAMFILTER);
//cmd.add("-bsf:v");
//cmd.add("h264_mp4toannexb");
File fileOut = new File(mediaOut.path);
mediaOut.path = fileOut.getCanonicalPath();
cmd.add(mediaOut.path);
int exitValue = execFFMPEG(cmd, sc);
Log.d(TAG, "execFFMPEG2: " + exitValue);
return mediaOut;
}
public Clip convertToWaveAudio (Clip mediaIn, String outPath, int sampleRate, int channels, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mediaIn.startTime != null) {
cmd.add("-ss");
cmd.add(mediaIn.startTime);
}
if (mediaIn.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mediaIn.endTime);
}
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
cmd.add("-ar");
cmd.add(sampleRate + "");
cmd.add("-ac");
cmd.add(channels + "");
cmd.add("-vn");
Clip mediaOut = new Clip();
File fileOut = new File(outPath);
mediaOut.path = fileOut.getCanonicalPath();
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut;
}
public Clip convertTo3GPAudio (Clip mediaIn, Clip mediaOut, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mediaIn.startTime != null) {
cmd.add("-ss");
cmd.add(mediaIn.startTime);
}
if (mediaIn.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mediaIn.endTime);
}
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
cmd.add("-vn");
if (mediaOut.audioCodec != null)
{
cmd.add("-acodec");
cmd.add(mediaOut.audioCodec);
}
if (mediaOut.audioBitrate != -1)
{
cmd.add("-ab");
cmd.add(mediaOut.audioBitrate + "k");
}
cmd.add("-strict");
cmd.add("-2");
File fileOut = new File(mediaOut.path);
cmd.add(fileOut.getCanonicalPath());
execFFMPEG(cmd, sc);
return mediaOut;
}
public Clip convert (Clip mediaIn, String outPath, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mediaIn.startTime != null) {
cmd.add("-ss");
cmd.add(mediaIn.startTime);
}
if (mediaIn.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mediaIn.endTime);
}
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
Clip mediaOut = new Clip();
File fileOut = new File(outPath);
mediaOut.path = fileOut.getCanonicalPath();
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut;
}
public Clip convertToMPEG (Clip mediaIn, String outPath, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mediaIn.startTime != null) {
cmd.add("-ss");
cmd.add(mediaIn.startTime);
}
if (mediaIn.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mediaIn.endTime);
}
cmd.add("-i");
cmd.add(new File(mediaIn.path).getCanonicalPath());
//cmd.add("-strict");
//cmd.add("experimental");
//everything to mpeg
cmd.add("-f");
cmd.add("mpeg");
Clip mediaOut = mediaIn.clone();
File fileOut = new File(outPath);
mediaOut.path = fileOut.getCanonicalPath();
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut;
}
public void concatAndTrimFilesMPEG (ArrayList<Clip> videos,Clip out, boolean preConvert, ShellCallback sc) throws Exception
{
int idx = 0;
if (preConvert)
{
for (Clip mdesc : videos)
{
if (mdesc.path == null)
continue;
//extract MPG video
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mdesc.startTime != null) {
cmd.add("-ss");
cmd.add(mdesc.startTime);
}
if (mdesc.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mdesc.endTime);
}
cmd.add("-i");
cmd.add(mdesc.path);
// mpg only supports 2 channels of audio
cmd.add("-ac");
cmd.add("2");
/*
cmd.add ("-acodec");
cmd.add("pcm_s16le");
cmd.add ("-vcodec");
cmd.add("mpeg2video");
*/
//if (out.audioCodec == null)
// cmd.add("-an"); //no audio
//cmd.add("-strict");
//cmd.add("experimental");
//everything to mpeg
cmd.add("-f");
cmd.add("mpeg");
cmd.add(out.path + '.' + idx + ".mpg");
execFFMPEG(cmd, sc);
idx++;
}
}
StringBuffer cmdRun = new StringBuffer();
cmdRun.append(mCmdCat);
idx = 0;
for (Clip vdesc : videos)
{
if (vdesc.path == null)
continue;
if (preConvert)
cmdRun.append(out.path).append('.').append(idx++).append(".mpg").append(' '); //leave a space at the end!
else
cmdRun.append(vdesc.path).append(' ');
}
String mCatPath = out.path + ".full.mpg";
cmdRun.append("> ");
cmdRun.append(mCatPath);
String[] cmds = {"sh","-c",cmdRun.toString()};
Runtime.getRuntime().exec(cmds).waitFor();
Clip mInCat = new Clip();
mInCat.path = mCatPath;
processVideo(mInCat, out, false, sc);
out.path = mCatPath;
}
public void extractAudio (Clip mdesc, String audioFormat, File audioOutPath, ShellCallback sc) throws IOException, InterruptedException
{
//no just extract the audio
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mdesc.startTime != null) {
cmd.add("-ss");
cmd.add(mdesc.startTime);
}
if (mdesc.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mdesc.endTime);
}
cmd.add("-i");
cmd.add(new File(mdesc.path).getCanonicalPath());
cmd.add("-vn");
cmd.add("-f");
cmd.add(audioFormat); //wav
//everything to WAV!
cmd.add(audioOutPath.getCanonicalPath());
execFFMPEG(cmd, sc);
}
private class FileMover {
InputStream inputStream;
File destination;
public FileMover(InputStream _inputStream, File _destination) {
inputStream = _inputStream;
destination = _destination;
}
public void moveIt() throws IOException {
OutputStream destinationOut = new BufferedOutputStream(new FileOutputStream(destination));
int numRead;
byte[] buf = new byte[1024];
while ((numRead = inputStream.read(buf) ) >= 0) {
destinationOut.write(buf, 0, numRead);
}
destinationOut.flush();
destinationOut.close();
}
}
public int killVideoProcessor (boolean asRoot, boolean waitFor) throws IOException {
int killDelayMs = 300;
int result = -1;
int procId = -1;
while ((procId = ShellUtils.findProcessId(mFfmpegBin)) != -1)
{
Log.d(TAG, ShellUtils.SHELL_CMD_KILL + ' ' + procId + "");
String[] cmd = { ShellUtils.SHELL_CMD_KILL + ' ' + procId + "" };
try {
result = ShellUtils.doShellCommand(cmd,new ShellCallback ()
{
@Override
public void shellOut(String msg) {
Log.d(TAG, "kill.shellOut: " + msg);
}
@Override
public void processComplete(int exitValue) {
Log.d(TAG, "kill.processComplete: " + exitValue);
}
}, asRoot, waitFor);
Thread.sleep(killDelayMs); }
catch (Exception e){
Log.d(TAG, "kill.Exception: " + e);
}
}
return result;
}
public Clip trim (Clip mediaIn, boolean withSound, String outPath, ShellCallback sc) throws Exception
{
ArrayList<String> cmd = new ArrayList<String>();
Clip mediaOut = new Clip();
String mediaPath = mediaIn.path;
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
if (mediaIn.startTime != null) {
cmd.add(Argument.STARTTIME);
cmd.add(mediaIn.startTime);
}
if (mediaIn.endTime != null) {
cmd.add(Argument.DURATION);
cmd.add(mediaIn.endTime);
}
cmd.add("-i");
cmd.add(mediaPath);
// mpg only supports 2 channels of audio
cmd.add("-ac");
cmd.add("2");
//if (!withSound)
// cmd.add("-an");
cmd.add("-strict");
cmd.add("-2");//experimental
mediaOut.path = outPath;
cmd.add(mediaOut.path);
execFFMPEG(cmd, sc);
return mediaOut;
}
public int concatAndTrimFilesMP4Stream (ArrayList<Clip> videos,Clip out, boolean preconvertClipsToMP4, boolean useCatCmd, ShellCallback sc, String overlay, int overlayIndex) throws Exception
{
File fileExportOut = new File(out.path);
StringBuffer sbCat = new StringBuffer();
int tmpIdx = 0;
int exitValue = 0;
// for debugging
//getVersion(null,null,null,null, sc, null);
for (Clip vdesc : videos)
{
Clip mdOut = null;
if (preconvertClipsToMP4)
{
File fileOut = new File(mFileTemp,tmpIdx + "-trim.mp4");
if (fileOut.exists())
fileOut.delete();
boolean withSound = false;
mdOut = trim(vdesc,withSound,fileOut.getCanonicalPath(), sc);
fileOut = new File(mFileTemp,tmpIdx + ".ts");
if (fileOut.exists())
fileOut.delete();
if (tmpIdx == overlayIndex || overlayIndex == -1) {
mdOut = convertToMP4Stream(mdOut,null,null,fileOut.getCanonicalPath(), sc, overlay);
} else {
mdOut = convertToMP4Stream(mdOut,null,null,fileOut.getCanonicalPath(), sc, null);
}
}
else
{
File fileOut = new File(mFileTemp,tmpIdx + ".ts");
if (fileOut.exists())
fileOut.delete();
if (tmpIdx == overlayIndex || overlayIndex == -1) {
mdOut = convertToMP4Stream(vdesc,vdesc.startTime,vdesc.endTime,fileOut.getCanonicalPath(), sc, overlay);
} else {
mdOut = convertToMP4Stream(vdesc,vdesc.startTime,vdesc.endTime,fileOut.getCanonicalPath(), sc, null);
}
}
if (mdOut != null)
{
if (sbCat.length()>0)
sbCat.append("|");
sbCat.append(new File(mdOut.path).getCanonicalPath());
tmpIdx++;
}
}
File fileExportOutTs = new File(fileExportOut.getCanonicalPath() + ".ts");
if (useCatCmd)
{
//cat 0.ts 1.ts > foo.ts
StringBuffer cmdBuff = new StringBuffer();
cmdBuff.append(mCmdCat);
cmdBuff.append(" ");
StringTokenizer st = new StringTokenizer(sbCat.toString(),"|");
while(st.hasMoreTokens())
cmdBuff.append(st.nextToken()).append(" ");
cmdBuff.append("> ");
cmdBuff.append(fileExportOut.getCanonicalPath() + ".ts");
Runtime.getRuntime().exec(cmdBuff.toString());
ArrayList<String> cmd = new ArrayList<String>();
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(fileExportOut.getCanonicalPath() + ".ts");
/* if we want MPEG output which is faster, but not playable on nexus 6 phones
cmd.add("-ac");
cmd.add("2");
cmd.add("-f");
cmd.add("mpegts");
cmd.add("-qscale");
cmd.add("0");
*/
/* if we want an mp4 codec but it's slower */
cmd.add("-c:v");
cmd.add("libx264");
cmd.add("-preset");
cmd.add("ultrafast");
cmd.add("-qscale");
cmd.add("0");
cmd.add("-strict"); // required for aac support
cmd.add("-2");
// cmd.add("-threads");
// cmd.add("4");
cmd.add(fileExportOut.getCanonicalPath());
exitValue = execFFMPEG(cmd, sc, null);
Log.d(TAG, "execFFMPEG3: " + exitValue);
}
else
{
//ffmpeg -i "concat:intermediate1.ts|intermediate2.ts" -c copy -bsf:a aac_adtstoasc output.mp4
ArrayList<String> cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add("concat:" + sbCat.toString());
/* if we want MPEG output which is faster, but not playable on nexus 6 phones
cmd.add("-ac");
cmd.add("2");
cmd.add("-f");
cmd.add("mpegts");
cmd.add("-qscale");
cmd.add("0");
*/
/* if we want an mp4 codec but it's slower */
cmd.add("-c:v");
cmd.add("libx264");
cmd.add("-preset");
cmd.add("ultrafast");
cmd.add("-qscale");
cmd.add("0");
cmd.add("-strict"); // required for aac support
cmd.add("-2");
// cmd.add("-threads");
// cmd.add("4");
cmd.add(fileExportOut.getCanonicalPath());
exitValue = execFFMPEG(cmd, sc);
Log.d(TAG, "execFFMPEG4: " + exitValue);
}
if ((!fileExportOut.exists()) || fileExportOut.length() == 0)
{
throw new Exception("There was a problem rendering the video: " + fileExportOut.getCanonicalPath());
}
return exitValue;
}
public Clip getInfo (Clip in) throws IOException, InterruptedException
{
ArrayList<String> cmd = new ArrayList<String>();
cmd = new ArrayList<String>();
cmd.add(mFfmpegBin);
cmd.add("-y");
cmd.add("-i");
cmd.add(new File(in.path).getCanonicalPath());
InfoParser ip = new InfoParser(in);
execFFMPEG(cmd,ip, null);
try{Thread.sleep(200);}
catch (Exception e){}
return in;
}
private class InfoParser implements ShellCallback {
private Clip mMedia;
private int retValue;
public InfoParser (Clip media)
{
mMedia = media;
}
@Override
public void shellOut(String shellLine) {
if (shellLine.contains("Duration:"))
{
// Duration: 00:01:01.75, start: 0.000000, bitrate: 8184 kb/s
String[] timecode = shellLine.split(",")[0].split(":");
//String duration = '00:00:00';
// duration = Double.parseDouble(timecode[1].trim())*60*60; //hours
// duration += Double.parseDouble(timecode[2].trim())*60; //minutes
// duration += Double.parseDouble(timecode[3].trim()); //seconds
//mMedia.duration = duration;
}
// Stream #0:0(eng): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 16939 kb/s, 30.02 fps, 30 tbr, 90k tbn, 180k tbc
else if (shellLine.contains(": Video:"))
{
String[] line = shellLine.split(":");
String[] videoInfo = line[3].split(",");
mMedia.videoCodec = videoInfo[0];
}
//Stream #0:1(eng): Audio: aac (mp4a / 0x6134706D), 48000 Hz, stereo, s16, 121 kb/s
else if (shellLine.contains(": Audio:"))
{
String[] line = shellLine.split(":");
String[] audioInfo = line[3].split(",");
mMedia.audioCodec = audioInfo[0];
}
//
//Stream #0.0(und): Video: h264 (Baseline), yuv420p, 1280x720, 8052 kb/s, 29.97 fps, 90k tbr, 90k tbn, 180k tbc
//Stream #0.1(und): Audio: mp2, 22050 Hz, 2 channels, s16, 127 kb/s
}
@Override
public void processComplete(int exitValue) {
retValue = exitValue;
}
}
private 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)
{
// Log.e(TAG,"error reading shell slog",ioe);
ioe.printStackTrace();
}
}
}
public static Bitmap getVideoFrame(String videoPath,long frameTime) throws Exception {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
try {
retriever.setDataSource(videoPath);
return retriever.getFrameAtTime(frameTime, MediaMetadataRetriever.OPTION_CLOSEST);
} finally {
try {
retriever.release();
} catch (RuntimeException ex) {
}
}
}
}
/*
* Main options:
-L show license
-h show help
-? show help
-help show help
--help show help
-version show version
-formats show available formats
-codecs show available codecs
-bsfs show available bit stream filters
-protocols show available protocols
-filters show available filters
-pix_fmts show available pixel formats
-sample_fmts show available audio sample formats
-loglevel loglevel set libav* logging level
-v loglevel set libav* logging level
-debug flags set debug flags
-report generate a report
-f fmt force format
-i filename input file name
-y overwrite output files
-n do not overwrite output files
-c codec codec name
-codec codec codec name
-pre preset preset name
-t duration record or transcode "duration" seconds of audio/video
-fs limit_size set the limit file size in bytes
-ss time_off set the start time offset
-itsoffset time_off set the input ts offset
-itsscale scale set the input ts scale
-timestamp time set the recording timestamp ('now' to set the current time)
-metadata string=string add metadata
-dframes number set the number of data frames to record
-timelimit limit set max runtime in seconds
-target type specify target file type ("vcd", "svcd", "dvd", "dv", "dv50", "pal-vcd", "ntsc-svcd", ...)
-xerror exit on error
-frames number set the number of frames to record
-tag fourcc/tag force codec tag/fourcc
-filter filter_list set stream filterchain
-stats print progress report during encoding
-attach filename add an attachment to the output file
-dump_attachment filename extract an attachment into a file
-bsf bitstream_filters A comma-separated list of bitstream filters
-dcodec codec force data codec ('copy' to copy stream)
Advanced options:
-map file.stream[:syncfile.syncstream] set input stream mapping
-map_channel file.stream.channel[:syncfile.syncstream] map an audio channel from one stream to another
-map_meta_data outfile[,metadata]:infile[,metadata] DEPRECATED set meta data information of outfile from infile
-map_metadata outfile[,metadata]:infile[,metadata] set metadata information of outfile from infile
-map_chapters input_file_index set chapters mapping
-benchmark add timings for benchmarking
-dump dump each input packet
-hex when dumping packets, also dump the payload
-re read input at native frame rate
-loop_input deprecated, use -loop
-loop_output deprecated, use -loop
-vsync video sync method
-async audio sync method
-adrift_threshold threshold audio drift threshold
-copyts copy timestamps
-copytb source copy input stream time base when stream copying
-shortest finish encoding within shortest input
-dts_delta_threshold threshold timestamp discontinuity delta threshold
-copyinkf copy initial non-keyframes
-q q use fixed quality scale (VBR)
-qscale q use fixed quality scale (VBR)
-streamid streamIndex:value set the value of an outfile streamid
-muxdelay seconds set the maximum demux-decode delay
-muxpreload seconds set the initial demux-decode delay
-fpre filename set options from indicated preset file
Video options:
-vframes number set the number of video frames to record
-r rate set frame rate (Hz value, fraction or abbreviation)
-s size set frame size (WxH or abbreviation)
-aspect aspect set aspect ratio (4:3, 16:9 or 1.3333, 1.7777)
-bits_per_raw_sample number set the number of bits per raw sample
-croptop size Removed, use the crop filter instead
-cropbottom size Removed, use the crop filter instead
-cropleft size Removed, use the crop filter instead
-cropright size Removed, use the crop filter instead
-padtop size Removed, use the pad filter instead
-padbottom size Removed, use the pad filter instead
-padleft size Removed, use the pad filter instead
-padright size Removed, use the pad filter instead
-padcolor color Removed, use the pad filter instead
-vn disable video
-vcodec codec force video codec ('copy' to copy stream)
-sameq use same quantizer as source (implies VBR)
-same_quant use same quantizer as source (implies VBR)
-pass n select the pass number (1 or 2)
-passlogfile prefix select two pass log file name prefix
-vf filter list video filters
-b bitrate video bitrate (please use -b:v)
-dn disable data
Advanced Video options:
-pix_fmt format set pixel format
-intra use only intra frames
-vdt n discard threshold
-rc_override override rate control override for specific intervals
-deinterlace deinterlace pictures
-psnr calculate PSNR of compressed frames
-vstats dump video coding statistics to file
-vstats_file file dump video coding statistics to file
-intra_matrix matrix specify intra matrix coeffs
-inter_matrix matrix specify inter matrix coeffs
-top top=1/bottom=0/auto=-1 field first
-dc precision intra_dc_precision
-vtag fourcc/tag force video tag/fourcc
-qphist show QP histogram
-force_fps force the selected framerate, disable the best supported framerate selection
-force_key_frames timestamps force key frames at specified timestamps
-vbsf video bitstream_filters deprecated
-vpre preset set the video options to the indicated preset
Audio options:
-aframes number set the number of audio frames to record
-aq quality set audio quality (codec-specific)
-ar rate set audio sampling rate (in Hz)
-ac channels set number of audio channels
-an disable audio
-acodec codec force audio codec ('copy' to copy stream)
-vol volume change audio volume (256=normal)
-rmvol volume rematrix volume (as factor)
*/
/*
* //./ffmpeg -y -i test.mp4 -vframes 999999 -vf 'redact=blurbox.txt [out] [d], [d]nullsink' -acodec copy outputa.mp4
//ffmpeg -v 10 -y -i /sdcard/org.witness.sscvideoproto/videocapture1042744151.mp4 -vcodec libx264
//-b 3000k -s 720x480 -r 30 -acodec copy -f mp4 -vf 'redact=/data/data/org.witness.sscvideoproto/redact_unsort.txt'
///sdcard/org.witness.sscvideoproto/new.mp4
//"-vf" , "redact=" + Environment.getExternalStorageDirectory().getPath() + "/" + PACKAGENAME + "/redact_unsort.txt",
// Need to make sure this will create a legitimate mp4 file
//"-acodec", "ac3", "-ac", "1", "-ar", "16000", "-ab", "32k",
String[] ffmpegCommand = {"/data/data/"+PACKAGENAME+"/ffmpeg", "-v", "10", "-y", "-i", recordingFile.getPath(),
"-vcodec", "libx264", "-b", "3000k", "-vpre", "baseline", "-s", "720x480", "-r", "30",
//"-vf", "drawbox=10:20:200:60:red@0.5",
"-vf" , "\"movie="+ overlayImage.getPath() +" [logo];[in][logo] overlay=0:0 [out]\"",
"-acodec", "copy",
"-f", "mp4", savePath.getPath()+"/output.mp4"};
//ffmpeg -i source-video.avi -s 480x320 -vcodec mpeg4 -acodec aac -ac 1 -ar 16000 -r 13 -ab 32000 -aspect 3:2 output-video.mp4/
*/
/* concat doesn't seem to work
cmd.add("-i");
StringBuffer concat = new StringBuffer();
for (int i = 0; i < videos.size(); i++)
{
if (i > 0)
concat.append("|");
concat.append(out.path + '.' + i + ".wav");
}
cmd.add("concat:\"" + concat.toString() + "\"");
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment