AppendTrack.java revision dd9eb897ee7c7b507cbdcf80263bb4b5de6966bf
15fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant/* 25fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * Copyright 2012 Sebastian Annies, Hamburg 35fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * 45fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * Licensed under the Apache License, Version 2.0 (the License); 55fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * you may not use this file except in compliance with the License. 65fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * You may obtain a copy of the License at 75fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * 85fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * http://www.apache.org/licenses/LICENSE-2.0 9cedb7fcc10556aaf4302917913c672b1bc6a1db0Daniel Dunbar * 10cedb7fcc10556aaf4302917913c672b1bc6a1db0Daniel Dunbar * Unless required by applicable law or agreed to in writing, software 11cedb7fcc10556aaf4302917913c672b1bc6a1db0Daniel Dunbar * distributed under the License is distributed on an AS IS BASIS, 12cedb7fcc10556aaf4302917913c672b1bc6a1db0Daniel Dunbar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13cedb7fcc10556aaf4302917913c672b1bc6a1db0Daniel Dunbar * See the License for the specific language governing permissions and 145fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * limitations under the License. 155fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant */ 165fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantpackage com.googlecode.mp4parser.authoring.tracks; 175fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant 185fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.coremedia.iso.boxes.*; 195fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; 205fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.authoring.AbstractTrack; 215fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.authoring.Track; 225fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.authoring.TrackMetaData; 235fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; 245fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor; 255fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor; 265fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant 275fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport java.io.ByteArrayOutputStream; 285fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport java.io.IOException; 295fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport java.nio.ByteBuffer; 305fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport java.nio.channels.Channels; 315fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnantimport java.util.*; 325fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant 335fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant/** 345fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * Appends two or more <code>Tracks</code> of the same type. No only that the type must be equal 355fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant * also the decoder settings must be the same. 365fec82dc0db3623546038e4a86baa44f749e554fHoward Hinnant */ 37public class AppendTrack extends AbstractTrack { 38 Track[] tracks; 39 SampleDescriptionBox stsd; 40 41 public AppendTrack(Track... tracks) throws IOException { 42 this.tracks = tracks; 43 44 for (Track track : tracks) { 45 46 if (stsd == null) { 47 stsd = track.getSampleDescriptionBox(); 48 } else { 49 ByteArrayOutputStream curBaos = new ByteArrayOutputStream(); 50 ByteArrayOutputStream refBaos = new ByteArrayOutputStream(); 51 track.getSampleDescriptionBox().getBox(Channels.newChannel(curBaos)); 52 stsd.getBox(Channels.newChannel(refBaos)); 53 byte[] cur = curBaos.toByteArray(); 54 byte[] ref = refBaos.toByteArray(); 55 56 if (!Arrays.equals(ref, cur)) { 57 SampleDescriptionBox curStsd = track.getSampleDescriptionBox(); 58 if (stsd.getBoxes().size() == 1 && curStsd.getBoxes().size() == 1) { 59 if (stsd.getBoxes().get(0) instanceof AudioSampleEntry && curStsd.getBoxes().get(0) instanceof AudioSampleEntry) { 60 AudioSampleEntry aseResult = mergeAudioSampleEntries((AudioSampleEntry) stsd.getBoxes().get(0), (AudioSampleEntry) curStsd.getBoxes().get(0)); 61 if (aseResult != null) { 62 stsd.setBoxes(Collections.<Box>singletonList(aseResult)); 63 return; 64 } 65 } 66 } 67 throw new IOException("Cannot append " + track + " to " + tracks[0] + " since their Sample Description Boxes differ: \n" + track.getSampleDescriptionBox() + "\n vs. \n" + tracks[0].getSampleDescriptionBox()); 68 } 69 } 70 } 71 } 72 73 private AudioSampleEntry mergeAudioSampleEntries(AudioSampleEntry ase1, AudioSampleEntry ase2) throws IOException { 74 if (ase1.getType().equals(ase2.getType())) { 75 AudioSampleEntry ase = new AudioSampleEntry(ase2.getType()); 76 if (ase1.getBytesPerFrame() == ase2.getBytesPerFrame()) { 77 ase.setBytesPerFrame(ase1.getBytesPerFrame()); 78 } else { 79 return null; 80 } 81 if (ase1.getBytesPerPacket() == ase2.getBytesPerPacket()) { 82 ase.setBytesPerPacket(ase1.getBytesPerPacket()); 83 } else { 84 return null; 85 } 86 if (ase1.getBytesPerSample() == ase2.getBytesPerSample()) { 87 ase.setBytesPerSample(ase1.getBytesPerSample()); 88 } else { 89 return null; 90 } 91 if (ase1.getChannelCount() == ase2.getChannelCount()) { 92 ase.setChannelCount(ase1.getChannelCount()); 93 } else { 94 return null; 95 } 96 if (ase1.getPacketSize() == ase2.getPacketSize()) { 97 ase.setPacketSize(ase1.getPacketSize()); 98 } else { 99 return null; 100 } 101 if (ase1.getCompressionId() == ase2.getCompressionId()) { 102 ase.setCompressionId(ase1.getCompressionId()); 103 } else { 104 return null; 105 } 106 if (ase1.getSampleRate() == ase2.getSampleRate()) { 107 ase.setSampleRate(ase1.getSampleRate()); 108 } else { 109 return null; 110 } 111 if (ase1.getSampleSize() == ase2.getSampleSize()) { 112 ase.setSampleSize(ase1.getSampleSize()); 113 } else { 114 return null; 115 } 116 if (ase1.getSamplesPerPacket() == ase2.getSamplesPerPacket()) { 117 ase.setSamplesPerPacket(ase1.getSamplesPerPacket()); 118 } else { 119 return null; 120 } 121 if (ase1.getSoundVersion() == ase2.getSoundVersion()) { 122 ase.setSoundVersion(ase1.getSoundVersion()); 123 } else { 124 return null; 125 } 126 if (Arrays.equals(ase1.getSoundVersion2Data(), ase2.getSoundVersion2Data())) { 127 ase.setSoundVersion2Data(ase1.getSoundVersion2Data()); 128 } else { 129 return null; 130 } 131 if (ase1.getBoxes().size() == ase2.getBoxes().size()) { 132 Iterator<Box> bxs1 = ase1.getBoxes().iterator(); 133 Iterator<Box> bxs2 = ase2.getBoxes().iterator(); 134 while (bxs1.hasNext()) { 135 Box cur1 = bxs1.next(); 136 Box cur2 = bxs2.next(); 137 ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); 138 ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); 139 cur1.getBox(Channels.newChannel(baos1)); 140 cur2.getBox(Channels.newChannel(baos2)); 141 if (Arrays.equals(baos1.toByteArray(), baos2.toByteArray())) { 142 ase.addBox(cur1); 143 } else { 144 if (ESDescriptorBox.TYPE.equals(cur1.getType()) && ESDescriptorBox.TYPE.equals(cur2.getType())) { 145 ESDescriptorBox esdsBox1 = (ESDescriptorBox) cur1; 146 ESDescriptorBox esdsBox2 = (ESDescriptorBox) cur2; 147 ESDescriptor esds1 = esdsBox1.getEsDescriptor(); 148 ESDescriptor esds2 = esdsBox2.getEsDescriptor(); 149 if (esds1.getURLFlag() != esds2.getURLFlag()) { 150 return null; 151 } 152 if (esds1.getURLLength() != esds2.getURLLength()) { 153 return null; 154 } 155 if (esds1.getDependsOnEsId() != esds2.getDependsOnEsId()) { 156 return null; 157 } 158 if (esds1.getEsId() != esds2.getEsId()) { 159 return null; 160 } 161 if (esds1.getoCREsId() != esds2.getoCREsId()) { 162 return null; 163 } 164 if (esds1.getoCRstreamFlag() != esds2.getoCRstreamFlag()) { 165 return null; 166 } 167 if (esds1.getRemoteODFlag() != esds2.getRemoteODFlag()) { 168 return null; 169 } 170 if (esds1.getStreamDependenceFlag() != esds2.getStreamDependenceFlag()) { 171 return null; 172 } 173 if (esds1.getStreamPriority() != esds2.getStreamPriority()) { 174 return null; 175 } 176 if (esds1.getURLString() != null ? !esds1.getURLString().equals(esds2.getURLString()) : esds2.getURLString() != null) { 177 return null; 178 } 179 if (esds1.getDecoderConfigDescriptor() != null ? !esds1.getDecoderConfigDescriptor().equals(esds2.getDecoderConfigDescriptor()) : esds2.getDecoderConfigDescriptor() != null) { 180 DecoderConfigDescriptor dcd1 = esds1.getDecoderConfigDescriptor(); 181 DecoderConfigDescriptor dcd2 = esds2.getDecoderConfigDescriptor(); 182 if (!dcd1.getAudioSpecificInfo().equals(dcd2.getAudioSpecificInfo())) { 183 return null; 184 } 185 if (dcd1.getAvgBitRate() != dcd2.getAvgBitRate()) { 186 // I don't care 187 } 188 if (dcd1.getBufferSizeDB() != dcd2.getBufferSizeDB()) { 189 // I don't care 190 } 191 192 if (dcd1.getDecoderSpecificInfo() != null ? !dcd1.getDecoderSpecificInfo().equals(dcd2.getDecoderSpecificInfo()) : dcd2.getDecoderSpecificInfo() != null) { 193 return null; 194 } 195 196 if (dcd1.getMaxBitRate() != dcd2.getMaxBitRate()) { 197 // I don't care 198 } 199 if (!dcd1.getProfileLevelIndicationDescriptors().equals(dcd2.getProfileLevelIndicationDescriptors())) { 200 return null; 201 } 202 203 if (dcd1.getObjectTypeIndication() != dcd2.getObjectTypeIndication()) { 204 return null; 205 } 206 if (dcd1.getStreamType() != dcd2.getStreamType()) { 207 return null; 208 } 209 if (dcd1.getUpStream() != dcd2.getUpStream()) { 210 return null; 211 } 212 213 214 } 215 if (esds1.getOtherDescriptors() != null ? !esds1.getOtherDescriptors().equals(esds2.getOtherDescriptors()) : esds2.getOtherDescriptors() != null) { 216 return null; 217 } 218 if (esds1.getSlConfigDescriptor() != null ? !esds1.getSlConfigDescriptor().equals(esds2.getSlConfigDescriptor()) : esds2.getSlConfigDescriptor() != null) { 219 return null; 220 } 221 ase.addBox(cur1); 222 } 223 } 224 } 225 } 226 return ase; 227 } else { 228 return null; 229 } 230 231 232 } 233 234 235 public List<ByteBuffer> getSamples() { 236 ArrayList<ByteBuffer> lists = new ArrayList<ByteBuffer>(); 237 238 for (Track track : tracks) { 239 lists.addAll(track.getSamples()); 240 } 241 242 return lists; 243 } 244 245 public SampleDescriptionBox getSampleDescriptionBox() { 246 return stsd; 247 } 248 249 public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { 250 if (tracks[0].getDecodingTimeEntries() != null && !tracks[0].getDecodingTimeEntries().isEmpty()) { 251 List<long[]> lists = new LinkedList<long[]>(); 252 for (Track track : tracks) { 253 lists.add(TimeToSampleBox.blowupTimeToSamples(track.getDecodingTimeEntries())); 254 } 255 256 LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>(); 257 for (long[] list : lists) { 258 for (long nuDecodingTime : list) { 259 if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) { 260 TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime); 261 returnDecodingEntries.add(e); 262 } else { 263 TimeToSampleBox.Entry e = returnDecodingEntries.getLast(); 264 e.setCount(e.getCount() + 1); 265 } 266 } 267 } 268 return returnDecodingEntries; 269 } else { 270 return null; 271 } 272 } 273 274 public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { 275 if (tracks[0].getCompositionTimeEntries() != null && !tracks[0].getCompositionTimeEntries().isEmpty()) { 276 List<int[]> lists = new LinkedList<int[]>(); 277 for (Track track : tracks) { 278 lists.add(CompositionTimeToSample.blowupCompositionTimes(track.getCompositionTimeEntries())); 279 } 280 LinkedList<CompositionTimeToSample.Entry> compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>(); 281 for (int[] list : lists) { 282 for (int compositionTime : list) { 283 if (compositionTimeEntries.isEmpty() || compositionTimeEntries.getLast().getOffset() != compositionTime) { 284 CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, compositionTime); 285 compositionTimeEntries.add(e); 286 } else { 287 CompositionTimeToSample.Entry e = compositionTimeEntries.getLast(); 288 e.setCount(e.getCount() + 1); 289 } 290 } 291 } 292 return compositionTimeEntries; 293 } else { 294 return null; 295 } 296 } 297 298 public long[] getSyncSamples() { 299 if (tracks[0].getSyncSamples() != null && tracks[0].getSyncSamples().length > 0) { 300 int numSyncSamples = 0; 301 for (Track track : tracks) { 302 numSyncSamples += track.getSyncSamples().length; 303 } 304 long[] returnSyncSamples = new long[numSyncSamples]; 305 306 int pos = 0; 307 long samplesBefore = 0; 308 for (Track track : tracks) { 309 for (long l : track.getSyncSamples()) { 310 returnSyncSamples[pos++] = samplesBefore + l; 311 } 312 samplesBefore += track.getSamples().size(); 313 } 314 return returnSyncSamples; 315 } else { 316 return null; 317 } 318 } 319 320 public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { 321 if (tracks[0].getSampleDependencies() != null && !tracks[0].getSampleDependencies().isEmpty()) { 322 List<SampleDependencyTypeBox.Entry> list = new LinkedList<SampleDependencyTypeBox.Entry>(); 323 for (Track track : tracks) { 324 list.addAll(track.getSampleDependencies()); 325 } 326 return list; 327 } else { 328 return null; 329 } 330 } 331 332 public TrackMetaData getTrackMetaData() { 333 return tracks[0].getTrackMetaData(); 334 } 335 336 public String getHandler() { 337 return tracks[0].getHandler(); 338 } 339 340 public Box getMediaHeaderBox() { 341 return tracks[0].getMediaHeaderBox(); 342 } 343 344 public SubSampleInformationBox getSubsampleInformationBox() { 345 return tracks[0].getSubsampleInformationBox(); 346 } 347 348} 349