Android
Android ビデオ録画機能を作る
Android プログラム上でビデオをトリミングするサンプルコード
Androidでビデオ(動画)を作って、トリミングするアプリを作っています。
トリミングするというのはひとつの動画があるとすると、どこからかどこかまでを切り取ってコピーし他のファイルにペーストするというアレです。
最初の録画とかプレビューについて、苦戦したのはAndroid ビデオを録画するサンプルコードに記述したとおりです。
で、トリミングについても予想通り苦戦しましたので、書いておきます。
基本的にはライブラリとしてmp4parserを使います。sanniesさん、ありがとう!!
https://github.com/sannies/mp4parser
二つの動画をあわせたりするプログラムは、下記のサイトさんが公開していて大変参考になりました。ありがとうございます!
Developers.IO
http://dev.classmethod.jp/smartphone/android/mp4parser/
しかし、秒数を指定して、何秒から何秒までをトリミングするのか、というのはどうやってやればよいのかわかりませんでした。
最初は単純にDevelopers.IOさんのサイトで書かれている
movie.addTrack(new AppendTrack(new CroppedTrack(track, 200, 400)));
で引数にされている、200とか400はミリセカンドだろうぐらいに思っていたのですが、これはサンプリングとあわせる必要があり、適当にやっていると
mp4parser java.lang.IndexOutOfBoundsException
の嵐になります。
あーらし、あーらし、For dream。
さて、サンプリングとか、どうでもいいんだよ!というせっかちな貴殿のために、もうサンプルコードに突入します。
これは、下記で公開されているトリミングのコードを元に、mp4parserの1.0.5.4にあわせました。
TrimVideoUtils.java
https://android.googlesource.com/platform/packages/apps/Gallery2/+/android-4.2.2_r1/src/com/android/gallery3d/app/TrimVideoUtils.java
合わせる方法は、上記のTrimVideoUtils.javaの作者、Eduardさんが、下記にかいてくれていますが、なんか抜粋とかでわかりにくかったので、まとめた次第です。
https://groups.google.com/forum/#!topic/mp4parser-discussion/d6qklvfdDZg
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.WritableByteChannel;
import java.util.LinkedList;
import java.util.List;
import com.coremedia.iso.boxes.Container;
import com.coremedia.iso.boxes.MovieHeaderBox;
import com.googlecode.mp4parser.FileDataSourceImpl;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder;
import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator;
import com.googlecode.mp4parser.authoring.tracks.CroppedTrack;
import com.googlecode.mp4parser.util.Matrix;
import com.googlecode.mp4parser.util.Path;
public class TrimVideoUtils {
/**
* ビデオをトリミングする
* @param src トリミングするもともとのファイル
* @param dst トリミングしたファイルの書きだし場所
* @param startMs スタートのミリセカンド
* @param endMs 終了のミリセカンド
* @throws IOException
*/
public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException {
FileDataSourceImpl file = new FileDataSourceImpl(src);
Movie movie = MovieCreator.build(file);
// remove all tracks we will create new tracks from the old
List<Track> tracks = movie.getTracks();
movie.setTracks(new LinkedList<Track>());
double startTime = startMs / 1000;
double endTime = endMs / 1000;
boolean timeCorrected = false;
// Here we try to find a track that has sync samples. Since we can only
// start decoding
// at such a sample we SHOULD make sure that the start of the new
// fragment is exactly
// such a frame
for (Track track : tracks) {
if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
if (timeCorrected) {
// This exception here could be a false positive in case we
// have multiple tracks
// with sync samples at exactly the same positions. E.g. a
// single movie containing
// multiple qualities of the same video (Microsoft Smooth
// Streaming file)
throw new RuntimeException(
"The startTime has already been corrected by another track with SyncSample. Not Supported.");
}
startTime = correctTimeToSyncSample(track, startTime, false);
endTime = correctTimeToSyncSample(track, endTime, true);
timeCorrected = true;
}
}
for (Track track : tracks) {
long currentSample = 0;
double currentTime = 0;
long startSample = -1;
long endSample = -1;
for (int i = 0; i < track.getSampleDurations().length; i++) {
if (currentTime <= startTime) {
// current sample is still before the new starttime
startSample = currentSample;
}
if (currentTime <= endTime) {
// current sample is after the new start time and still
// before the new endtime
endSample = currentSample;
} else {
// current sample is after the end of the cropped video
break;
}
currentTime += (double) track.getSampleDurations()[i]
/ (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
movie.addTrack(new CroppedTrack(track, startSample, endSample));
}
Container out = new DefaultMp4Builder().build(movie);
MovieHeaderBox mvhd = Path.getPath(out, "moov/mvhd");
mvhd.setMatrix(Matrix.ROTATE_180);
if (!dst.exists()) {
dst.createNewFile();
}
FileOutputStream fos = new FileOutputStream(dst);
WritableByteChannel fc = fos.getChannel();
try {
out.writeContainer(fc);
} finally {
fc.close();
fos.close();
file.close();
}
}
private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) {
long currentSample = 0;
double currentTime = 0;
long startSample = -1;
long endSample = -1;
for (int i = 0; i < track.getSampleDurations().length; i++) {
if (currentTime <= cutHere) {
// current sample is still before the new starttime
startSample = currentSample;
}
if (currentTime <= cutHere) {
// current sample is after the new start time and still before the new endtime
endSample = currentSample;
} else {
// current sample is after the end of the cropped video
break;
}
currentTime += (double) track.getSampleDurations()[i] / (double) track.getTrackMetaData().getTimescale();
currentSample++;
}
return currentTime;
}
}