Android プログラム上でビデオをトリミングするサンプルコード

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です