1/* 2 * Copyright 2012 Sebastian Annies, Hamburg 3 * 4 * Licensed under the Apache License, Version 2.0 (the License); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an AS IS BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.googlecode.mp4parser.authoring.tracks; 17 18import com.coremedia.iso.boxes.*; 19import com.googlecode.mp4parser.authoring.Track; 20import com.googlecode.mp4parser.authoring.TrackMetaData; 21 22import java.nio.ByteBuffer; 23import java.util.ArrayList; 24import java.util.Arrays; 25import java.util.LinkedList; 26import java.util.List; 27import java.util.Queue; 28import java.util.logging.Logger; 29 30/** 31 * Changes the timescale of a track by wrapping the track. 32 */ 33public class ChangeTimeScaleTrack implements Track { 34 private static final Logger LOG = Logger.getLogger(ChangeTimeScaleTrack.class.getName()); 35 36 Track source; 37 List<CompositionTimeToSample.Entry> ctts; 38 List<TimeToSampleBox.Entry> tts; 39 long timeScale; 40 41 /** 42 * Changes the time scale of the source track to the target time scale and makes sure 43 * that any rounding errors that may have summed are corrected exactly before the syncSamples. 44 * 45 * @param source the source track 46 * @param targetTimeScale the resulting time scale of this track. 47 * @param syncSamples at these sync points where rounding error are corrected. 48 */ 49 public ChangeTimeScaleTrack(Track source, long targetTimeScale, long[] syncSamples) { 50 this.source = source; 51 this.timeScale = targetTimeScale; 52 double timeScaleFactor = (double) targetTimeScale / source.getTrackMetaData().getTimescale(); 53 ctts = adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor); 54 tts = adjustTts(source.getDecodingTimeEntries(), timeScaleFactor, syncSamples, getTimes(source, syncSamples, targetTimeScale)); 55 } 56 57 private static long[] getTimes(Track track, long[] syncSamples, long targetTimeScale) { 58 long[] syncSampleTimes = new long[syncSamples.length]; 59 Queue<TimeToSampleBox.Entry> timeQueue = new LinkedList<TimeToSampleBox.Entry>(track.getDecodingTimeEntries()); 60 61 int currentSample = 1; // first syncsample is 1 62 long currentDuration = 0; 63 long currentDelta = 0; 64 int currentSyncSampleIndex = 0; 65 long left = 0; 66 67 68 while (currentSample <= syncSamples[syncSamples.length - 1]) { 69 if (currentSample++ == syncSamples[currentSyncSampleIndex]) { 70 syncSampleTimes[currentSyncSampleIndex++] = (currentDuration * targetTimeScale) / track.getTrackMetaData().getTimescale(); 71 } 72 if (left-- == 0) { 73 TimeToSampleBox.Entry entry = timeQueue.poll(); 74 left = entry.getCount() - 1; 75 currentDelta = entry.getDelta(); 76 } 77 currentDuration += currentDelta; 78 } 79 return syncSampleTimes; 80 81 } 82 83 public SampleDescriptionBox getSampleDescriptionBox() { 84 return source.getSampleDescriptionBox(); 85 } 86 87 public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { 88 return tts; 89 } 90 91 public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { 92 return ctts; 93 } 94 95 public long[] getSyncSamples() { 96 return source.getSyncSamples(); 97 } 98 99 public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { 100 return source.getSampleDependencies(); 101 } 102 103 public TrackMetaData getTrackMetaData() { 104 TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone(); 105 trackMetaData.setTimescale(timeScale); 106 return trackMetaData; 107 } 108 109 public String getHandler() { 110 return source.getHandler(); 111 } 112 113 public boolean isEnabled() { 114 return source.isEnabled(); 115 } 116 117 public boolean isInMovie() { 118 return source.isInMovie(); 119 } 120 121 public boolean isInPreview() { 122 return source.isInPreview(); 123 } 124 125 public boolean isInPoster() { 126 return source.isInPoster(); 127 } 128 129 public List<ByteBuffer> getSamples() { 130 return source.getSamples(); 131 } 132 133 134 /** 135 * Adjusting the composition times is easy. Just scale it by the factor - that's it. There is no rounding 136 * error summing up. 137 * 138 * @param source 139 * @param timeScaleFactor 140 * @return 141 */ 142 static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, double timeScaleFactor) { 143 if (source != null) { 144 List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size()); 145 for (CompositionTimeToSample.Entry entry : source) { 146 entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), (int) Math.round(timeScaleFactor * entry.getOffset()))); 147 } 148 return entries2; 149 } else { 150 return null; 151 } 152 } 153 154 static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, double timeScaleFactor, long[] syncSample, long[] syncSampleTimes) { 155 156 long[] sourceArray = TimeToSampleBox.blowupTimeToSamples(source); 157 long summedDurations = 0; 158 159 LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>(); 160 for (int i = 1; i <= sourceArray.length; i++) { 161 long duration = sourceArray[i - 1]; 162 163 long x = Math.round(timeScaleFactor * duration); 164 165 166 TimeToSampleBox.Entry last = entries2.peekLast(); 167 int ssIndex; 168 if ((ssIndex = Arrays.binarySearch(syncSample, i + 1)) >= 0) { 169 // we are at the sample before sync point 170 if (syncSampleTimes[ssIndex] != summedDurations) { 171 long correction = syncSampleTimes[ssIndex] - (summedDurations + x); 172 LOG.finest(String.format("Sample %d %d / %d - correct by %d", i, summedDurations, syncSampleTimes[ssIndex], correction)); 173 x += correction; 174 } 175 } 176 summedDurations += x; 177 if (last == null) { 178 entries2.add(new TimeToSampleBox.Entry(1, x)); 179 } else if (last.getDelta() != x) { 180 entries2.add(new TimeToSampleBox.Entry(1, x)); 181 } else { 182 last.setCount(last.getCount() + 1); 183 } 184 185 } 186 return entries2; 187 } 188 189 public Box getMediaHeaderBox() { 190 return source.getMediaHeaderBox(); 191 } 192 193 public SubSampleInformationBox getSubsampleInformationBox() { 194 return source.getSubsampleInformationBox(); 195 } 196 197 @Override 198 public String toString() { 199 return "ChangeTimeScaleTrack{" + 200 "source=" + source + 201 '}'; 202 } 203}