1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Modified example based on mp4parser google code open source project. 18 // http://code.google.com/p/mp4parser/source/browse/trunk/examples/src/main/java/com/googlecode/mp4parser/ShortenExample.java 19 20 package com.android.gallery3d.app; 21 22 import com.coremedia.iso.IsoFile; 23 import com.coremedia.iso.boxes.TimeToSampleBox; 24 import com.googlecode.mp4parser.authoring.Movie; 25 import com.googlecode.mp4parser.authoring.Track; 26 import com.googlecode.mp4parser.authoring.builder.DefaultMp4Builder; 27 import com.googlecode.mp4parser.authoring.container.mp4.MovieCreator; 28 import com.googlecode.mp4parser.authoring.tracks.CroppedTrack; 29 30 import java.io.File; 31 import java.io.FileOutputStream; 32 import java.io.IOException; 33 import java.io.RandomAccessFile; 34 import java.nio.channels.FileChannel; 35 import java.util.Arrays; 36 import java.util.LinkedList; 37 import java.util.List; 38 39 /** 40 * Shortens/Crops a track 41 */ 42 public class TrimVideoUtils { 43 44 public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException { 45 RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); 46 Movie movie = MovieCreator.build(randomAccessFile.getChannel()); 47 48 // remove all tracks we will create new tracks from the old 49 List<Track> tracks = movie.getTracks(); 50 movie.setTracks(new LinkedList<Track>()); 51 52 double startTime = startMs/1000; 53 double endTime = endMs/1000; 54 55 boolean timeCorrected = false; 56 57 // Here we try to find a track that has sync samples. Since we can only start decoding 58 // at such a sample we SHOULD make sure that the start of the new fragment is exactly 59 // such a frame 60 for (Track track : tracks) { 61 if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { 62 if (timeCorrected) { 63 // This exception here could be a false positive in case we have multiple tracks 64 // with sync samples at exactly the same positions. E.g. a single movie containing 65 // multiple qualities of the same video (Microsoft Smooth Streaming file) 66 67 throw new RuntimeException("The startTime has already been corrected by another track with SyncSample. Not Supported."); 68 } 69 startTime = correctTimeToSyncSample(track, startTime, false); 70 endTime = correctTimeToSyncSample(track, endTime, true); 71 timeCorrected = true; 72 } 73 } 74 75 for (Track track : tracks) { 76 long currentSample = 0; 77 double currentTime = 0; 78 long startSample = -1; 79 long endSample = -1; 80 81 for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) { 82 TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i); 83 for (int j = 0; j < entry.getCount(); j++) { 84 // entry.getDelta() is the amount of time the current sample covers. 85 86 if (currentTime <= startTime) { 87 // current sample is still before the new starttime 88 startSample = currentSample; 89 } 90 if (currentTime <= endTime) { 91 // current sample is after the new start time and still before the new endtime 92 endSample = currentSample; 93 } else { 94 // current sample is after the end of the cropped video 95 break; 96 } 97 currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale(); 98 currentSample++; 99 } 100 } 101 movie.addTrack(new CroppedTrack(track, startSample, endSample)); 102 } 103 IsoFile out = new DefaultMp4Builder().build(movie); 104 105 if (!dst.exists()) { 106 dst.createNewFile(); 107 } 108 109 FileOutputStream fos = new FileOutputStream(dst); 110 FileChannel fc = fos.getChannel(); 111 out.getBox(fc); // This one build up the memory. 112 113 fc.close(); 114 fos.close(); 115 randomAccessFile.close(); 116 } 117 118 protected static long getDuration(Track track) { 119 long duration = 0; 120 for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) { 121 duration += entry.getCount() * entry.getDelta(); 122 } 123 return duration; 124 } 125 126 private static double correctTimeToSyncSample(Track track, double cutHere, boolean next) { 127 double[] timeOfSyncSamples = new double[track.getSyncSamples().length]; 128 long currentSample = 0; 129 double currentTime = 0; 130 for (int i = 0; i < track.getDecodingTimeEntries().size(); i++) { 131 TimeToSampleBox.Entry entry = track.getDecodingTimeEntries().get(i); 132 for (int j = 0; j < entry.getCount(); j++) { 133 if (Arrays.binarySearch(track.getSyncSamples(), currentSample + 1) >= 0) { 134 // samples always start with 1 but we start with zero therefore +1 135 timeOfSyncSamples[Arrays.binarySearch(track.getSyncSamples(), currentSample + 1)] = currentTime; 136 } 137 currentTime += (double) entry.getDelta() / (double) track.getTrackMetaData().getTimescale(); 138 currentSample++; 139 } 140 } 141 double previous = 0; 142 for (double timeOfSyncSample : timeOfSyncSamples) { 143 if (timeOfSyncSample > cutHere) { 144 if (next) { 145 return timeOfSyncSample; 146 } else { 147 return previous; 148 } 149 } 150 previous = timeOfSyncSample; 151 } 152 return timeOfSyncSamples[timeOfSyncSamples.length - 1]; 153 } 154 155 156 } 157