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