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; } }