SyncSampleIntersectFinderImpl.java revision dd9eb897ee7c7b507cbdcf80263bb4b5de6966bf
100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy/*
200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * Copyright 2012 Sebastian Annies, Hamburg
300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy *
400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * Licensed under the Apache License, Version 2.0 (the License);
500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * you may not use this file except in compliance with the License.
600524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * You may obtain a copy of the License at
700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy *
881bd58838403fc8c4a63840f0af42deebe6d4a20Jan Engelhardt *     http://www.apache.org/licenses/LICENSE-2.0
981bd58838403fc8c4a63840f0af42deebe6d4a20Jan Engelhardt *
1000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * Unless required by applicable law or agreed to in writing, software
1100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * distributed under the License is distributed on an AS IS BASIS,
1200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139921f2b9a241750e4730fc7d486687c6a32779f4Jan Engelhardt * See the License for the specific language governing permissions and
149921f2b9a241750e4730fc7d486687c6a32779f4Jan Engelhardt * limitations under the License.
159d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt */
169a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardtpackage com.googlecode.mp4parser.authoring.builder;
17ef18e8147903885708d1c264904129af4fb636d6Jan Engelhardt
1800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport com.coremedia.iso.boxes.TimeToSampleBox;
1900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
2000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport com.googlecode.mp4parser.authoring.Movie;
21d62a9db1295608ef98394b830703389973346716Yasuyuki KOZAKAIimport com.googlecode.mp4parser.authoring.Track;
22d62a9db1295608ef98394b830703389973346716Yasuyuki KOZAKAI
2300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport java.util.*;
2400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport java.util.concurrent.ConcurrentHashMap;
2500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardyimport java.util.logging.Logger;
26abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
27abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphalimport static com.googlecode.mp4parser.util.Math.lcm;
28abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
29abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal/**
3000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * This <code>FragmentIntersectionFinder</code> cuts the input movie video tracks in
3100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * fragments of the same length exactly before the sync samples. Audio tracks are cut
3200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy * into pieces of similar length.
3300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy */
3468146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardtpublic class SyncSampleIntersectFinderImpl implements FragmentIntersectionFinder {
3568146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt
3668146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt    private static Logger LOG = Logger.getLogger(SyncSampleIntersectFinderImpl.class.getName());
3768146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt    private static Map<CacheTuple, long[]> getTimesCache = new ConcurrentHashMap<CacheTuple, long[]>();
38181dead3f13befe02769ef479bcbb51801b7fc4eJan Engelhardt    private static Map<CacheTuple, long[]> getSampleNumbersCache = new ConcurrentHashMap<CacheTuple, long[]>();
3900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
4000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    private final int minFragmentDurationSeconds;
418b7c64d6ba156a99008fcd810cba874c73294333Jan Engelhardt
4200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    public SyncSampleIntersectFinderImpl() {
4300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        minFragmentDurationSeconds = 0;
4400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    }
4500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
4600524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    /**
4700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy     * Creates a <code>SyncSampleIntersectFinderImpl</code> that will not create any fragment
4800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy     * smaller than the given <code>minFragmentDurationSeconds</code>
4900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy     *
5000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy     * @param minFragmentDurationSeconds the smallest allowable duration of a fragment.
5100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy     */
528b7c64d6ba156a99008fcd810cba874c73294333Jan Engelhardt    public SyncSampleIntersectFinderImpl(int minFragmentDurationSeconds) {
538b7c64d6ba156a99008fcd810cba874c73294333Jan Engelhardt        this.minFragmentDurationSeconds = minFragmentDurationSeconds;
5400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    }
5500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
56fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt    /**
57fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     * Gets an array of sample numbers that are meant to be the first sample of each
58fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     * chunk or fragment.
59fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     *
60fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     * @param track concerned track
61fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     * @param movie the context of the track
62fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     * @return an array containing the ordinal of each fragment's first sample
63fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt     */
64fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt    public long[] sampleNumbers(Track track, Movie movie) {
65fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        final CacheTuple key = new CacheTuple(track, movie);
66fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        final long[] result = getSampleNumbersCache.get(key);
67fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        if (result != null) {
68fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            return result;
69abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        }
7068146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt
7168146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt        if ("vide".equals(track.getHandler())) {
7268146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt            if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
73fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                List<long[]> times = getSyncSamplesTimestamps(movie, track);
74fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                final long[] commonIndices = getCommonIndices(track.getSyncSamples(), getTimes(track, movie), track.getTrackMetaData().getTimescale(), times.toArray(new long[times.size()][]));
759a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                getSampleNumbersCache.put(key, commonIndices);
769a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                return commonIndices;
779a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            } else {
789a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                throw new RuntimeException("Video Tracks need sync samples. Only tracks other than video may have no sync samples.");
799a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            }
809a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        } else if ("soun".equals(track.getHandler())) {
819a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            Track referenceTrack = null;
829a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            for (Track candidate : movie.getTracks()) {
839a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                if (candidate.getSyncSamples() != null && "vide".equals(candidate.getHandler()) && candidate.getSyncSamples().length > 0) {
849a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                    referenceTrack = candidate;
859a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                }
869a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            }
879a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            if (referenceTrack != null) {
889a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
899a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                // Gets the reference track's fra
909a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                long[] refSyncSamples = sampleNumbers(referenceTrack, movie);
919a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
929a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                int refSampleCount = referenceTrack.getSamples().size();
939a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
949a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                long[] syncSamples = new long[refSyncSamples.length];
959a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                long minSampleRate = 192000;
96fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                for (Track testTrack : movie.getTracks()) {
97fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    if ("soun".equals(testTrack.getHandler())) {
98fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                        AudioSampleEntry ase = (AudioSampleEntry) testTrack.getSampleDescriptionBox().getSampleEntry();
99de1f06dca906bfcb82d7c7c2d555fbf3229d12b6Jan Engelhardt                        if (ase.getSampleRate() < minSampleRate) {
100fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            minSampleRate = ase.getSampleRate();
101abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                            long sc = testTrack.getSamples().size();
102fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            double stretch = (double) sc / refSampleCount;
103fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            TimeToSampleBox.Entry sttsEntry = testTrack.getDecodingTimeEntries().get(0);
104fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
105fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt
106fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            for (int i = 0; i < syncSamples.length; i++) {
107fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                                long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1) * samplesPerFrame);
108fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                                syncSamples[i] = start;
109fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                                // The Stretch makes sure that there are as much audio and video chunks!
110fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            }
111fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                            break;
112fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                        }
113fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    }
114fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                }
115fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                AudioSampleEntry ase = (AudioSampleEntry) track.getSampleDescriptionBox().getSampleEntry();
116fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                TimeToSampleBox.Entry sttsEntry = track.getDecodingTimeEntries().get(0);
117fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
118fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                double factor = (double) ase.getSampleRate() / (double) minSampleRate;
119fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                if (factor != Math.rint(factor)) { // Not an integer
12000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                    throw new RuntimeException("Sample rates must be a multiple of the lowest sample rate to create a correct file!");
121fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                }
122fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                for (int i = 0; i < syncSamples.length; i++) {
123fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    syncSamples[i] = (long) (1 + syncSamples[i] * factor / (double) samplesPerFrame);
124fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                }
125fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                getSampleNumbersCache.put(key, syncSamples);
126fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                return syncSamples;
127fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            }
128fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
129fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        } else {
130fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            // Ok, my track has no sync samples - let's find one with sync samples.
131fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            for (Track candidate : movie.getTracks()) {
132fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                if (candidate.getSyncSamples() != null && candidate.getSyncSamples().length > 0) {
133abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                    long[] refSyncSamples = sampleNumbers(candidate, movie);
134fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    int refSampleCount = candidate.getSamples().size();
135fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt
136fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    long[] syncSamples = new long[refSyncSamples.length];
137fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    long sc = track.getSamples().size();
138fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    double stretch = (double) sc / refSampleCount;
139fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt
140fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    for (int i = 0; i < syncSamples.length; i++) {
141fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                        long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1)) + 1;
142fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                        syncSamples[i] = start;
143fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                        // The Stretch makes sure that there are as much audio and video chunks!
144fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    }
145fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    getSampleNumbersCache.put(key, syncSamples);
146fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    return syncSamples;
147fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                }
148fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            }
149fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
1509a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        }
151fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt
1529a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
153abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal    }
154abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
155abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal    /**
156abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     * Calculates the timestamp of all tracks' sync samples.
157abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     *
158abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     * @param movie
159abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     * @param track
160abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     * @return
161abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal     */
162abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal    public static List<long[]> getSyncSamplesTimestamps(Movie movie, Track track) {
163abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        List<long[]> times = new LinkedList<long[]>();
164abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        for (Track currentTrack : movie.getTracks()) {
165abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            if (currentTrack.getHandler().equals(track.getHandler())) {
166abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                long[] currentTrackSyncSamples = currentTrack.getSyncSamples();
167abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                if (currentTrackSyncSamples != null && currentTrackSyncSamples.length > 0) {
168abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                    final long[] currentTrackTimes = getTimes(currentTrack, movie);
169abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                    times.add(currentTrackTimes);
170abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                }
171abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            }
172abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        }
173abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        return times;
174abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal    }
175abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
176abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal    public long[] getCommonIndices(long[] syncSamples, long[] syncSampleTimes, long timeScale, long[]... otherTracksTimes) {
177abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        List<Long> nuSyncSamples = new LinkedList<Long>();
178abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        List<Long> nuSyncSampleTimes = new LinkedList<Long>();
179abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
180abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
181abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        for (int i = 0; i < syncSampleTimes.length; i++) {
182abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            boolean foundInEveryRef = true;
183abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            for (long[] times : otherTracksTimes) {
184abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                foundInEveryRef &= (Arrays.binarySearch(times, syncSampleTimes[i]) >= 0);
185abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            }
186abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
187abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            if (foundInEveryRef) {
188abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                // use sample only if found in every other track.
189abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                nuSyncSamples.add(syncSamples[i]);
190abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                nuSyncSampleTimes.add(syncSampleTimes[i]);
191abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            }
192abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        }
193abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        // We have two arrays now:
194abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        // nuSyncSamples: Contains all common sync samples
195abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        // nuSyncSampleTimes: Contains the times of all sync samples
196abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
197abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        // Start: Warn user if samples are not matching!
198abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        if (nuSyncSamples.size() < (syncSamples.length * 0.25)) {
199abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            String log = "";
200abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            log += String.format("%5d - Common:  [", nuSyncSamples.size());
201abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            for (long l : nuSyncSamples) {
202abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                log += (String.format("%10d,", l));
203abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            }
204abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            log += ("]");
205abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            LOG.warning(log);
206abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            log = "";
207abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
208abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            log += String.format("%5d - In    :  [", syncSamples.length);
209abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            for (long l : syncSamples) {
210abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal                log += (String.format("%10d,", l));
211abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            }
212abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            log += ("]");
213abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            LOG.warning(log);
214abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            LOG.warning("There are less than 25% of common sync samples in the given track.");
215abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            throw new RuntimeException("There are less than 25% of common sync samples in the given track.");
216abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        } else if (nuSyncSamples.size() < (syncSamples.length * 0.5)) {
217abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            LOG.fine("There are less than 50% of common sync samples in the given track. This is implausible but I'm ok to continue.");
218abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        } else if (nuSyncSamples.size() < syncSamples.length) {
219abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal            LOG.finest("Common SyncSample positions vs. this tracks SyncSample positions: " + nuSyncSamples.size() + " vs. " + syncSamples.length);
220abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        }
221abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        // End: Warn user if samples are not matching!
222abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
223abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
224abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
225abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
226abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        List<Long> finalSampleList = new LinkedList<Long>();
227abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal
228abdef13f36b63758f8775eb86febd96bf062df6fFlorian Westphal        if (minFragmentDurationSeconds > 0) {
22900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            // if minFragmentDurationSeconds is greater 0
23068146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt            // we need to throw away certain samples.
23100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            long lastSyncSampleTime = -1;
23200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            Iterator<Long> nuSyncSamplesIterator = nuSyncSamples.iterator();
2337ac405297ec38449b30e3b05fd6bf2082fd3d803Jan Engelhardt            Iterator<Long> nuSyncSampleTimesIterator = nuSyncSampleTimes.iterator();
23400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            while (nuSyncSamplesIterator.hasNext() && nuSyncSampleTimesIterator.hasNext()) {
23568146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt                long curSyncSample = nuSyncSamplesIterator.next();
23600524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                long curSyncSampleTime = nuSyncSampleTimesIterator.next();
23700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                if (lastSyncSampleTime == -1 || (curSyncSampleTime - lastSyncSampleTime) / timeScale >= minFragmentDurationSeconds) {
23800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                    finalSampleList.add(curSyncSample);
23900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                    lastSyncSampleTime = curSyncSampleTime;
24000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                }
24100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            }
24268146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt        } else {
24300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            // the list of all samples is the final list of samples
24468146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt            // since minFragmentDurationSeconds ist not used.
24500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            finalSampleList = nuSyncSamples;
24668146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt        }
24700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
24868146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt
24900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        // transform the list to an array
25000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        long[] finalSampleArray = new long[finalSampleList.size()];
25100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        for (int i = 0; i < finalSampleArray.length; i++) {
25200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            finalSampleArray[i] = finalSampleList.get(i);
25300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        }
25400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        return finalSampleArray;
25500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
25668146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt    }
2579d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt
2589d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt
2599d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt    private static long[] getTimes(Track track, Movie m) {
2609d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt        final CacheTuple key = new CacheTuple(track, m);
2619d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt        final long[] result = getTimesCache.get(key);
2629d69da4bdb1d546218d168b72f12ac8aa042e3d8Jan Engelhardt        if (result != null) {
26300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            return result;
26400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        }
26500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
266181dead3f13befe02769ef479bcbb51801b7fc4eJan Engelhardt        long[] syncSamples = track.getSyncSamples();
26700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        long[] syncSampleTimes = new long[syncSamples.length];
26800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
26900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
27000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        int currentSample = 1;  // first syncsample is 1
27100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        long currentDuration = 0;
27200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        long currentDelta = 0;
27300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        int currentSyncSampleIndex = 0;
27400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        long left = 0;
2759a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
2769a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        final long scalingFactor = calculateTracktimesScalingFactor(m, track);
2779a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
2789a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        while (currentSample <= syncSamples[syncSamples.length - 1]) {
2799a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
2809a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                syncSampleTimes[currentSyncSampleIndex++] = currentDuration * scalingFactor;
2819a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            }
2829a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            if (left-- == 0) {
2839a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                TimeToSampleBox.Entry entry = timeQueue.poll();
2849a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                left = entry.getCount() - 1;
2859a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt                currentDelta = entry.getDelta();
2869a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            }
2879a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            currentDuration += currentDelta;
2889a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        }
2899a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        getTimesCache.put(key, syncSampleTimes);
2909a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        return syncSampleTimes;
2919a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt    }
2929a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt
2939a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt    private static long calculateTracktimesScalingFactor(Movie m, Track track) {
2949a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        long timeScale = 1;
2959a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        for (Track track1 : m.getTracks()) {
29600524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            if (track1.getHandler().equals(track.getHandler())) {
29700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                if (track1.getTrackMetaData().getTimescale() != track.getTrackMetaData().getTimescale()) {
298fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt                    timeScale = lcm(timeScale, track1.getTrackMetaData().getTimescale());
29900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy                }
30000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            }
301dbb77543ad6afe29e9a1881b2d4fc212de621a55Jan Engelhardt        }
30200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        return timeScale;
30300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    }
30400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
30500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy    public static class CacheTuple {
30600524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        Track track;
30700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        Movie movie;
30800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
30900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        public CacheTuple(Track track, Movie movie) {
3109a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            this.track = track;
31100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            this.movie = movie;
3129a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        }
31300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
3149a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt        @Override
31500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        public boolean equals(Object o) {
3169a8c77fc8df3155747c34dcea79b7834a2a9a40aJan Engelhardt            if (this == o) return true;
31700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            if (o == null || getClass() != o.getClass()) return false;
31800524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
31900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            CacheTuple that = (CacheTuple) o;
32000524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
32100524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            if (movie != null ? !movie.equals(that.movie) : that.movie != null) return false;
32200524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            if (track != null ? !track.equals(that.track) : that.track != null) return false;
32300524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy
32400524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            return true;
32500524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        }
326fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt
32700524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy        @Override
328fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        public int hashCode() {
32900524b27b5e442d27414cf48e0d6e6372b6113aePatrick McHardy            int result = track != null ? track.hashCode() : 0;
330fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            result = 31 * result + (movie != null ? movie.hashCode() : 0);
331fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt            return result;
332fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt        }
33368146dad91611bd8d6d12c8ba27219130d99607bJan Engelhardt    }
334fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt}
335fe02f76e013941a7f65f57f297d3177bcfeb0623Jan Engelhardt