1dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu/* 2dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Copyright 2012 Sebastian Annies, Hamburg 3dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 4dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Licensed under the Apache License, Version 2.0 (the License); 5dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * you may not use this file except in compliance with the License. 6dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * You may obtain a copy of the License at 7dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 8dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * http://www.apache.org/licenses/LICENSE-2.0 9dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 10dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Unless required by applicable law or agreed to in writing, software 11dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * distributed under the License is distributed on an AS IS BASIS, 12dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * See the License for the specific language governing permissions and 14dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * limitations under the License. 15dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */ 16dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhupackage com.googlecode.mp4parser.authoring.builder; 17dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 18dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.coremedia.iso.boxes.TimeToSampleBox; 19dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; 20dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.googlecode.mp4parser.authoring.Movie; 21dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport com.googlecode.mp4parser.authoring.Track; 22dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 23dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.util.*; 24dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.util.concurrent.ConcurrentHashMap; 25dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport java.util.logging.Logger; 26dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 27dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhuimport static com.googlecode.mp4parser.util.Math.lcm; 28dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 29dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu/** 30dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * This <code>FragmentIntersectionFinder</code> cuts the input movie video tracks in 31dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * fragments of the same length exactly before the sync samples. Audio tracks are cut 32dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * into pieces of similar length. 33dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */ 34dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhupublic class SyncSampleIntersectFinderImpl implements FragmentIntersectionFinder { 35dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 36dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private static Logger LOG = Logger.getLogger(SyncSampleIntersectFinderImpl.class.getName()); 37dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private static Map<CacheTuple, long[]> getTimesCache = new ConcurrentHashMap<CacheTuple, long[]>(); 38dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private static Map<CacheTuple, long[]> getSampleNumbersCache = new ConcurrentHashMap<CacheTuple, long[]>(); 39dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 40dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private final int minFragmentDurationSeconds; 41dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 42dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public SyncSampleIntersectFinderImpl() { 43dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu minFragmentDurationSeconds = 0; 44dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 45dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 46dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu /** 47dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Creates a <code>SyncSampleIntersectFinderImpl</code> that will not create any fragment 48dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * smaller than the given <code>minFragmentDurationSeconds</code> 49dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 50dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @param minFragmentDurationSeconds the smallest allowable duration of a fragment. 51dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */ 52dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public SyncSampleIntersectFinderImpl(int minFragmentDurationSeconds) { 53dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu this.minFragmentDurationSeconds = minFragmentDurationSeconds; 54dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 55dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 56dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu /** 57dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Gets an array of sample numbers that are meant to be the first sample of each 58dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * chunk or fragment. 59dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 60dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @param track concerned track 61dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @param movie the context of the track 62dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @return an array containing the ordinal of each fragment's first sample 63dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */ 64dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public long[] sampleNumbers(Track track, Movie movie) { 65dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final CacheTuple key = new CacheTuple(track, movie); 66dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final long[] result = getSampleNumbersCache.get(key); 67dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (result != null) { 68dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return result; 69dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 70dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 71dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if ("vide".equals(track.getHandler())) { 72dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) { 73dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu List<long[]> times = getSyncSamplesTimestamps(movie, track); 74dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final long[] commonIndices = getCommonIndices(track.getSyncSamples(), getTimes(track, movie), track.getTrackMetaData().getTimescale(), times.toArray(new long[times.size()][])); 75dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu getSampleNumbersCache.put(key, commonIndices); 76dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return commonIndices; 77dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else { 78dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu throw new RuntimeException("Video Tracks need sync samples. Only tracks other than video may have no sync samples."); 79dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 80dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else if ("soun".equals(track.getHandler())) { 81dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Track referenceTrack = null; 82dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (Track candidate : movie.getTracks()) { 83dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (candidate.getSyncSamples() != null && "vide".equals(candidate.getHandler()) && candidate.getSyncSamples().length > 0) { 84dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu referenceTrack = candidate; 85dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 86dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 87dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (referenceTrack != null) { 88dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 89dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // Gets the reference track's fra 90dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] refSyncSamples = sampleNumbers(referenceTrack, movie); 91dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 92dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu int refSampleCount = referenceTrack.getSamples().size(); 93dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 94dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] syncSamples = new long[refSyncSamples.length]; 95dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long minSampleRate = 192000; 96dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (Track testTrack : movie.getTracks()) { 97dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if ("soun".equals(testTrack.getHandler())) { 98dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu AudioSampleEntry ase = (AudioSampleEntry) testTrack.getSampleDescriptionBox().getSampleEntry(); 99dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (ase.getSampleRate() < minSampleRate) { 100dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu minSampleRate = ase.getSampleRate(); 101dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long sc = testTrack.getSamples().size(); 102dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu double stretch = (double) sc / refSampleCount; 103dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu TimeToSampleBox.Entry sttsEntry = testTrack.getDecodingTimeEntries().get(0); 104dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types 105dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 106dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (int i = 0; i < syncSamples.length; i++) { 107dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1) * samplesPerFrame); 108dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu syncSamples[i] = start; 109dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // The Stretch makes sure that there are as much audio and video chunks! 110dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 111dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu break; 112dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 113dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 114dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 115dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu AudioSampleEntry ase = (AudioSampleEntry) track.getSampleDescriptionBox().getSampleEntry(); 116dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu TimeToSampleBox.Entry sttsEntry = track.getDecodingTimeEntries().get(0); 117dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types 118dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu double factor = (double) ase.getSampleRate() / (double) minSampleRate; 119dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (factor != Math.rint(factor)) { // Not an integer 120dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu throw new RuntimeException("Sample rates must be a multiple of the lowest sample rate to create a correct file!"); 121dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 122dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (int i = 0; i < syncSamples.length; i++) { 123dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu syncSamples[i] = (long) (1 + syncSamples[i] * factor / (double) samplesPerFrame); 124dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 125dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu getSampleNumbersCache.put(key, syncSamples); 126dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return syncSamples; 127dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 128dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!"); 129dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else { 130dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // Ok, my track has no sync samples - let's find one with sync samples. 131dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (Track candidate : movie.getTracks()) { 132dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (candidate.getSyncSamples() != null && candidate.getSyncSamples().length > 0) { 133dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] refSyncSamples = sampleNumbers(candidate, movie); 134dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu int refSampleCount = candidate.getSamples().size(); 135dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 136dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] syncSamples = new long[refSyncSamples.length]; 137dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long sc = track.getSamples().size(); 138dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu double stretch = (double) sc / refSampleCount; 139dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 140dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (int i = 0; i < syncSamples.length; i++) { 141dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1)) + 1; 142dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu syncSamples[i] = start; 143dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // The Stretch makes sure that there are as much audio and video chunks! 144dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 145dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu getSampleNumbersCache.put(key, syncSamples); 146dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return syncSamples; 147dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 148dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 149dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!"); 150dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 151dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 152dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 153dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 154dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 155dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu /** 156dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * Calculates the timestamp of all tracks' sync samples. 157dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * 158dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @param movie 159dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @param track 160dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu * @return 161dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu */ 162dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public static List<long[]> getSyncSamplesTimestamps(Movie movie, Track track) { 163dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu List<long[]> times = new LinkedList<long[]>(); 164dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (Track currentTrack : movie.getTracks()) { 165dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (currentTrack.getHandler().equals(track.getHandler())) { 166dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] currentTrackSyncSamples = currentTrack.getSyncSamples(); 167dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (currentTrackSyncSamples != null && currentTrackSyncSamples.length > 0) { 168dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final long[] currentTrackTimes = getTimes(currentTrack, movie); 169dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu times.add(currentTrackTimes); 170dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 171dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 172dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 173dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return times; 174dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 175dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 176dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public long[] getCommonIndices(long[] syncSamples, long[] syncSampleTimes, long timeScale, long[]... otherTracksTimes) { 177dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu List<Long> nuSyncSamples = new LinkedList<Long>(); 178dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu List<Long> nuSyncSampleTimes = new LinkedList<Long>(); 179dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 180dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 181dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (int i = 0; i < syncSampleTimes.length; i++) { 182dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu boolean foundInEveryRef = true; 183dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (long[] times : otherTracksTimes) { 184dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu foundInEveryRef &= (Arrays.binarySearch(times, syncSampleTimes[i]) >= 0); 185dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 186dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 187dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (foundInEveryRef) { 188dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // use sample only if found in every other track. 189dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu nuSyncSamples.add(syncSamples[i]); 190dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu nuSyncSampleTimes.add(syncSampleTimes[i]); 191dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 192dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 193dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // We have two arrays now: 194dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // nuSyncSamples: Contains all common sync samples 195dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // nuSyncSampleTimes: Contains the times of all sync samples 196dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 197dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // Start: Warn user if samples are not matching! 198dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (nuSyncSamples.size() < (syncSamples.length * 0.25)) { 199dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu String log = ""; 200dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += String.format("%5d - Common: [", nuSyncSamples.size()); 201dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (long l : nuSyncSamples) { 202dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += (String.format("%10d,", l)); 203dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 204dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += ("]"); 205dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu LOG.warning(log); 206dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log = ""; 207dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 208dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += String.format("%5d - In : [", syncSamples.length); 209dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (long l : syncSamples) { 210dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += (String.format("%10d,", l)); 211dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 212dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu log += ("]"); 213dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu LOG.warning(log); 214dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu LOG.warning("There are less than 25% of common sync samples in the given track."); 215dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu throw new RuntimeException("There are less than 25% of common sync samples in the given track."); 216dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else if (nuSyncSamples.size() < (syncSamples.length * 0.5)) { 217dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu LOG.fine("There are less than 50% of common sync samples in the given track. This is implausible but I'm ok to continue."); 218dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else if (nuSyncSamples.size() < syncSamples.length) { 219dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu LOG.finest("Common SyncSample positions vs. this tracks SyncSample positions: " + nuSyncSamples.size() + " vs. " + syncSamples.length); 220dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 221dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // End: Warn user if samples are not matching! 222dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 223dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 224dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 225dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 226dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu List<Long> finalSampleList = new LinkedList<Long>(); 227dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 228dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (minFragmentDurationSeconds > 0) { 229dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // if minFragmentDurationSeconds is greater 0 230dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // we need to throw away certain samples. 231dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long lastSyncSampleTime = -1; 232dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Iterator<Long> nuSyncSamplesIterator = nuSyncSamples.iterator(); 233dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Iterator<Long> nuSyncSampleTimesIterator = nuSyncSampleTimes.iterator(); 234dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu while (nuSyncSamplesIterator.hasNext() && nuSyncSampleTimesIterator.hasNext()) { 235dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long curSyncSample = nuSyncSamplesIterator.next(); 236dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long curSyncSampleTime = nuSyncSampleTimesIterator.next(); 237dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (lastSyncSampleTime == -1 || (curSyncSampleTime - lastSyncSampleTime) / timeScale >= minFragmentDurationSeconds) { 238dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu finalSampleList.add(curSyncSample); 239dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu lastSyncSampleTime = curSyncSampleTime; 240dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 241dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 242dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } else { 243dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // the list of all samples is the final list of samples 244dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // since minFragmentDurationSeconds ist not used. 245dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu finalSampleList = nuSyncSamples; 246dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 247dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 248dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 249dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu // transform the list to an array 250dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] finalSampleArray = new long[finalSampleList.size()]; 251dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (int i = 0; i < finalSampleArray.length; i++) { 252dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu finalSampleArray[i] = finalSampleList.get(i); 253dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 254dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return finalSampleArray; 255dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 256dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 257dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 258dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 259dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private static long[] getTimes(Track track, Movie m) { 260dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final CacheTuple key = new CacheTuple(track, m); 261dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final long[] result = getTimesCache.get(key); 262dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (result != null) { 263dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return result; 264dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 265dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 266dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] syncSamples = track.getSyncSamples(); 267dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long[] syncSampleTimes = new long[syncSamples.length]; 268dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries()); 269dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 270dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu int currentSample = 1; // first syncsample is 1 271dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long currentDuration = 0; 272dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long currentDelta = 0; 273dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu int currentSyncSampleIndex = 0; 274dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long left = 0; 275dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 276dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu final long scalingFactor = calculateTracktimesScalingFactor(m, track); 277dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 278dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu while (currentSample <= syncSamples[syncSamples.length - 1]) { 279dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (currentSample++ == syncSamples[currentSyncSampleIndex]) { 280dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu syncSampleTimes[currentSyncSampleIndex++] = currentDuration * scalingFactor; 281dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 282dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (left-- == 0) { 283dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu TimeToSampleBox.Entry entry = timeQueue.poll(); 284dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu left = entry.getCount() - 1; 285dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu currentDelta = entry.getDelta(); 286dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 287dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu currentDuration += currentDelta; 288dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 289dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu getTimesCache.put(key, syncSampleTimes); 290dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return syncSampleTimes; 291dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 292dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 293dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu private static long calculateTracktimesScalingFactor(Movie m, Track track) { 294dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu long timeScale = 1; 295dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu for (Track track1 : m.getTracks()) { 296dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (track1.getHandler().equals(track.getHandler())) { 297dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (track1.getTrackMetaData().getTimescale() != track.getTrackMetaData().getTimescale()) { 298dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu timeScale = lcm(timeScale, track1.getTrackMetaData().getTimescale()); 299dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 300dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 301dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 302dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return timeScale; 303dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 304dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 305dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public static class CacheTuple { 306dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Track track; 307dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu Movie movie; 308dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 309dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public CacheTuple(Track track, Movie movie) { 310dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu this.track = track; 311dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu this.movie = movie; 312dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 313dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 314dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu @Override 315dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public boolean equals(Object o) { 316dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (this == o) return true; 317dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (o == null || getClass() != o.getClass()) return false; 318dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 319dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu CacheTuple that = (CacheTuple) o; 320dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 321dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (movie != null ? !movie.equals(that.movie) : that.movie != null) return false; 322dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu if (track != null ? !track.equals(that.track) : that.track != null) return false; 323dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 324dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return true; 325dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 326dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu 327dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu @Override 328dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu public int hashCode() { 329dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu int result = track != null ? track.hashCode() : 0; 330dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu result = 31 * result + (movie != null ? movie.hashCode() : 0); 331dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu return result; 332dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 333dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu } 334dd9eb897ee7c7b507cbdcf80263bb4b5de6966bfTeng-Hui Zhu} 335