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