Home | History | Annotate | Download | only in app
      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