summaryrefslogtreecommitdiff
path: root/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base
diff options
context:
space:
mode:
Diffstat (limited to 'isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base')
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base334
1 files changed, 334 insertions, 0 deletions
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base
new file mode 100644
index 0000000..2766c5e
--- /dev/null
+++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/SyncSampleIntersectFinderImpl.java.svn-base
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2012 Sebastian Annies, Hamburg
+ *
+ * Licensed under the Apache License, Version 2.0 (the License);
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an AS IS BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.googlecode.mp4parser.authoring.builder;
+
+import com.coremedia.iso.boxes.TimeToSampleBox;
+import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
+import com.googlecode.mp4parser.authoring.Movie;
+import com.googlecode.mp4parser.authoring.Track;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import static com.googlecode.mp4parser.util.Math.lcm;
+
+/**
+ * This <code>FragmentIntersectionFinder</code> cuts the input movie video tracks in
+ * fragments of the same length exactly before the sync samples. Audio tracks are cut
+ * into pieces of similar length.
+ */
+public class SyncSampleIntersectFinderImpl implements FragmentIntersectionFinder {
+
+ private static Logger LOG = Logger.getLogger(SyncSampleIntersectFinderImpl.class.getName());
+ private static Map<CacheTuple, long[]> getTimesCache = new ConcurrentHashMap<CacheTuple, long[]>();
+ private static Map<CacheTuple, long[]> getSampleNumbersCache = new ConcurrentHashMap<CacheTuple, long[]>();
+
+ private final int minFragmentDurationSeconds;
+
+ public SyncSampleIntersectFinderImpl() {
+ minFragmentDurationSeconds = 0;
+ }
+
+ /**
+ * Creates a <code>SyncSampleIntersectFinderImpl</code> that will not create any fragment
+ * smaller than the given <code>minFragmentDurationSeconds</code>
+ *
+ * @param minFragmentDurationSeconds the smallest allowable duration of a fragment.
+ */
+ public SyncSampleIntersectFinderImpl(int minFragmentDurationSeconds) {
+ this.minFragmentDurationSeconds = minFragmentDurationSeconds;
+ }
+
+ /**
+ * Gets an array of sample numbers that are meant to be the first sample of each
+ * chunk or fragment.
+ *
+ * @param track concerned track
+ * @param movie the context of the track
+ * @return an array containing the ordinal of each fragment's first sample
+ */
+ public long[] sampleNumbers(Track track, Movie movie) {
+ final CacheTuple key = new CacheTuple(track, movie);
+ final long[] result = getSampleNumbersCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ if ("vide".equals(track.getHandler())) {
+ if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
+ List<long[]> times = getSyncSamplesTimestamps(movie, track);
+ final long[] commonIndices = getCommonIndices(track.getSyncSamples(), getTimes(track, movie), track.getTrackMetaData().getTimescale(), times.toArray(new long[times.size()][]));
+ getSampleNumbersCache.put(key, commonIndices);
+ return commonIndices;
+ } else {
+ throw new RuntimeException("Video Tracks need sync samples. Only tracks other than video may have no sync samples.");
+ }
+ } else if ("soun".equals(track.getHandler())) {
+ Track referenceTrack = null;
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && "vide".equals(candidate.getHandler()) && candidate.getSyncSamples().length > 0) {
+ referenceTrack = candidate;
+ }
+ }
+ if (referenceTrack != null) {
+
+ // Gets the reference track's fra
+ long[] refSyncSamples = sampleNumbers(referenceTrack, movie);
+
+ int refSampleCount = referenceTrack.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long minSampleRate = 192000;
+ for (Track testTrack : movie.getTracks()) {
+ if ("soun".equals(testTrack.getHandler())) {
+ AudioSampleEntry ase = (AudioSampleEntry) testTrack.getSampleDescriptionBox().getSampleEntry();
+ if (ase.getSampleRate() < minSampleRate) {
+ minSampleRate = ase.getSampleRate();
+ long sc = testTrack.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+ TimeToSampleBox.Entry sttsEntry = testTrack.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1) * samplesPerFrame);
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ break;
+ }
+ }
+ }
+ AudioSampleEntry ase = (AudioSampleEntry) track.getSampleDescriptionBox().getSampleEntry();
+ TimeToSampleBox.Entry sttsEntry = track.getDecodingTimeEntries().get(0);
+ long samplesPerFrame = sttsEntry.getDelta(); // Assuming all audio tracks have the same number of samples per frame, which they do for all known types
+ double factor = (double) ase.getSampleRate() / (double) minSampleRate;
+ if (factor != Math.rint(factor)) { // Not an integer
+ throw new RuntimeException("Sample rates must be a multiple of the lowest sample rate to create a correct file!");
+ }
+ for (int i = 0; i < syncSamples.length; i++) {
+ syncSamples[i] = (long) (1 + syncSamples[i] * factor / (double) samplesPerFrame);
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ } else {
+ // Ok, my track has no sync samples - let's find one with sync samples.
+ for (Track candidate : movie.getTracks()) {
+ if (candidate.getSyncSamples() != null && candidate.getSyncSamples().length > 0) {
+ long[] refSyncSamples = sampleNumbers(candidate, movie);
+ int refSampleCount = candidate.getSamples().size();
+
+ long[] syncSamples = new long[refSyncSamples.length];
+ long sc = track.getSamples().size();
+ double stretch = (double) sc / refSampleCount;
+
+ for (int i = 0; i < syncSamples.length; i++) {
+ long start = (long) Math.ceil(stretch * (refSyncSamples[i] - 1)) + 1;
+ syncSamples[i] = start;
+ // The Stretch makes sure that there are as much audio and video chunks!
+ }
+ getSampleNumbersCache.put(key, syncSamples);
+ return syncSamples;
+ }
+ }
+ throw new RuntimeException("There was absolutely no Track with sync samples. I can't work with that!");
+ }
+
+
+ }
+
+ /**
+ * Calculates the timestamp of all tracks' sync samples.
+ *
+ * @param movie
+ * @param track
+ * @return
+ */
+ public static List<long[]> getSyncSamplesTimestamps(Movie movie, Track track) {
+ List<long[]> times = new LinkedList<long[]>();
+ for (Track currentTrack : movie.getTracks()) {
+ if (currentTrack.getHandler().equals(track.getHandler())) {
+ long[] currentTrackSyncSamples = currentTrack.getSyncSamples();
+ if (currentTrackSyncSamples != null && currentTrackSyncSamples.length > 0) {
+ final long[] currentTrackTimes = getTimes(currentTrack, movie);
+ times.add(currentTrackTimes);
+ }
+ }
+ }
+ return times;
+ }
+
+ public long[] getCommonIndices(long[] syncSamples, long[] syncSampleTimes, long timeScale, long[]... otherTracksTimes) {
+ List<Long> nuSyncSamples = new LinkedList<Long>();
+ List<Long> nuSyncSampleTimes = new LinkedList<Long>();
+
+
+ for (int i = 0; i < syncSampleTimes.length; i++) {
+ boolean foundInEveryRef = true;
+ for (long[] times : otherTracksTimes) {
+ foundInEveryRef &= (Arrays.binarySearch(times, syncSampleTimes[i]) >= 0);
+ }
+
+ if (foundInEveryRef) {
+ // use sample only if found in every other track.
+ nuSyncSamples.add(syncSamples[i]);
+ nuSyncSampleTimes.add(syncSampleTimes[i]);
+ }
+ }
+ // We have two arrays now:
+ // nuSyncSamples: Contains all common sync samples
+ // nuSyncSampleTimes: Contains the times of all sync samples
+
+ // Start: Warn user if samples are not matching!
+ if (nuSyncSamples.size() < (syncSamples.length * 0.25)) {
+ String log = "";
+ log += String.format("%5d - Common: [", nuSyncSamples.size());
+ for (long l : nuSyncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ log = "";
+
+ log += String.format("%5d - In : [", syncSamples.length);
+ for (long l : syncSamples) {
+ log += (String.format("%10d,", l));
+ }
+ log += ("]");
+ LOG.warning(log);
+ LOG.warning("There are less than 25% of common sync samples in the given track.");
+ throw new RuntimeException("There are less than 25% of common sync samples in the given track.");
+ } else if (nuSyncSamples.size() < (syncSamples.length * 0.5)) {
+ LOG.fine("There are less than 50% of common sync samples in the given track. This is implausible but I'm ok to continue.");
+ } else if (nuSyncSamples.size() < syncSamples.length) {
+ LOG.finest("Common SyncSample positions vs. this tracks SyncSample positions: " + nuSyncSamples.size() + " vs. " + syncSamples.length);
+ }
+ // End: Warn user if samples are not matching!
+
+
+
+
+ List<Long> finalSampleList = new LinkedList<Long>();
+
+ if (minFragmentDurationSeconds > 0) {
+ // if minFragmentDurationSeconds is greater 0
+ // we need to throw away certain samples.
+ long lastSyncSampleTime = -1;
+ Iterator<Long> nuSyncSamplesIterator = nuSyncSamples.iterator();
+ Iterator<Long> nuSyncSampleTimesIterator = nuSyncSampleTimes.iterator();
+ while (nuSyncSamplesIterator.hasNext() && nuSyncSampleTimesIterator.hasNext()) {
+ long curSyncSample = nuSyncSamplesIterator.next();
+ long curSyncSampleTime = nuSyncSampleTimesIterator.next();
+ if (lastSyncSampleTime == -1 || (curSyncSampleTime - lastSyncSampleTime) / timeScale >= minFragmentDurationSeconds) {
+ finalSampleList.add(curSyncSample);
+ lastSyncSampleTime = curSyncSampleTime;
+ }
+ }
+ } else {
+ // the list of all samples is the final list of samples
+ // since minFragmentDurationSeconds ist not used.
+ finalSampleList = nuSyncSamples;
+ }
+
+
+ // transform the list to an array
+ long[] finalSampleArray = new long[finalSampleList.size()];
+ for (int i = 0; i < finalSampleArray.length; i++) {
+ finalSampleArray[i] = finalSampleList.get(i);
+ }
+ return finalSampleArray;
+
+ }
+
+
+ private static long[] getTimes(Track track, Movie m) {
+ final CacheTuple key = new CacheTuple(track, m);
+ final long[] result = getTimesCache.get(key);
+ if (result != null) {
+ return result;
+ }
+
+ long[] syncSamples = track.getSyncSamples();
+ long[] syncSampleTimes = new long[syncSamples.length];
+ Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries());
+
+ int currentSample = 1; // first syncsample is 1
+ long currentDuration = 0;
+ long currentDelta = 0;
+ int currentSyncSampleIndex = 0;
+ long left = 0;
+
+ final long scalingFactor = calculateTracktimesScalingFactor(m, track);
+
+ while (currentSample <= syncSamples[syncSamples.length - 1]) {
+ if (currentSample++ == syncSamples[currentSyncSampleIndex]) {
+ syncSampleTimes[currentSyncSampleIndex++] = currentDuration * scalingFactor;
+ }
+ if (left-- == 0) {
+ TimeToSampleBox.Entry entry = timeQueue.poll();
+ left = entry.getCount() - 1;
+ currentDelta = entry.getDelta();
+ }
+ currentDuration += currentDelta;
+ }
+ getTimesCache.put(key, syncSampleTimes);
+ return syncSampleTimes;
+ }
+
+ private static long calculateTracktimesScalingFactor(Movie m, Track track) {
+ long timeScale = 1;
+ for (Track track1 : m.getTracks()) {
+ if (track1.getHandler().equals(track.getHandler())) {
+ if (track1.getTrackMetaData().getTimescale() != track.getTrackMetaData().getTimescale()) {
+ timeScale = lcm(timeScale, track1.getTrackMetaData().getTimescale());
+ }
+ }
+ }
+ return timeScale;
+ }
+
+ public static class CacheTuple {
+ Track track;
+ Movie movie;
+
+ public CacheTuple(Track track, Movie movie) {
+ this.track = track;
+ this.movie = movie;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ CacheTuple that = (CacheTuple) o;
+
+ if (movie != null ? !movie.equals(that.movie) : that.movie != null) return false;
+ if (track != null ? !track.equals(that.track) : that.track != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = track != null ? track.hashCode() : 0;
+ result = 31 * result + (movie != null ? movie.hashCode() : 0);
+ return result;
+ }
+ }
+}