diff options
Diffstat (limited to 'isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn')
16 files changed, 4180 insertions, 0 deletions
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops new file mode 100644 index 0000000..496d7bb --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/all-wcprops @@ -0,0 +1,89 @@ +K 25 +svn:wc:ra_dav:version-url +V 89 +/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks +END +DivideTimeScaleTrack.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/DivideTimeScaleTrack.java +END +CroppedTrack.java +K 25 +svn:wc:ra_dav:version-url +V 107 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/CroppedTrack.java +END +ChangeTimeScaleTrack.java +K 25 +svn:wc:ra_dav:version-url +V 115 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ChangeTimeScaleTrack.java +END +EC3TrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 107 +/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/EC3TrackImpl.java +END +ReplaceSampleTrack.java +K 25 +svn:wc:ra_dav:version-url +V 113 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/ReplaceSampleTrack.java +END +QuicktimeTextTrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/svn/!svn/ver/691/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/QuicktimeTextTrackImpl.java +END +Amf0Track.java +K 25 +svn:wc:ra_dav:version-url +V 104 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/Amf0Track.java +END +SilenceTrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 111 +/svn/!svn/ver/698/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/SilenceTrackImpl.java +END +H264TrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 108 +/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/H264TrackImpl.java +END +TextTrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 108 +/svn/!svn/ver/684/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/TextTrackImpl.java +END +AACTrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 107 +/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AACTrackImpl.java +END +MultiplyTimeScaleTrack.java +K 25 +svn:wc:ra_dav:version-url +V 117 +/svn/!svn/ver/686/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/MultiplyTimeScaleTrack.java +END +AppendTrack.java +K 25 +svn:wc:ra_dav:version-url +V 106 +/svn/!svn/ver/714/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AppendTrack.java +END +AC3TrackImpl.java +K 25 +svn:wc:ra_dav:version-url +V 107 +/svn/!svn/ver/756/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/AC3TrackImpl.java +END diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries new file mode 100644 index 0000000..dbe8ae3 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/entries @@ -0,0 +1,504 @@ +10 + +dir +778 +http://mp4parser.googlecode.com/svn/trunk/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks +http://mp4parser.googlecode.com/svn + + + +2012-08-17T01:19:11.953078Z +756 +michael.stattmann@gmail.com + + + + + + + + + + + + + + +7decde4b-c250-0410-a0da-51896bc88be6 + +DivideTimeScaleTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +6aa41cdb7489e16e879ddebd68b7ac52 +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +3893 + +CroppedTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +5d9c65d9aac52a26372aa3fdf6f1b7ea +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +6025 + +ChangeTimeScaleTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +ba01d30ac4b7c4fa9a2c6538ee537594 +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +7152 + +EC3TrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +1d568bfaa0f41c3771986a7790347072 +2012-08-17T01:19:11.953078Z +756 +michael.stattmann@gmail.com + + + + + + + + + + + + + + + + + + + + + +13623 + +ReplaceSampleTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +f6846b7a262ab0e530da46f4d7e34850 +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +3139 + +QuicktimeTextTrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +7ccd01a58545fb02b507b57892eb53e5 +2012-06-24T21:35:59.546504Z +691 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +5128 + +Amf0Track.java +file + + + + +2012-09-14T17:27:50.507219Z +4718a34bc271adf4517de9829ac74a9d +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +3858 + +SilenceTrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +a897677e602dfa0d64af1b0d33a04ca8 +2012-06-26T08:37:32.910396Z +698 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +2623 + +H264TrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +76abb4b21c13d11215a2ac4cb0c7e461 +2012-08-17T01:19:11.953078Z +756 +michael.stattmann@gmail.com + + + + + + + + + + + + + + + + + + + + + +28816 + +TextTrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +5a643c876754eb5c7bcf75b4b71114a1 +2012-06-24T14:45:45.932648Z +684 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +4963 + +AACTrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +ece8d364a9a5aeabf6a281f6d428e3cf +2012-08-17T01:19:11.953078Z +756 +michael.stattmann@gmail.com + + + + + + + + + + + + + + + + + + + + + +10097 + +MultiplyTimeScaleTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +b0ea53239c124607a26a181f594f82a1 +2012-06-24T19:52:05.961412Z +686 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +4192 + +AppendTrack.java +file + + + + +2012-09-14T17:27:50.507219Z +e7814aebc4500724771fd0582455a7ca +2012-07-18T23:22:45.506793Z +714 +Sebastian.Annies@gmail.com + + + + + + + + + + + + + + + + + + + + + +15783 + +AC3TrackImpl.java +file + + + + +2012-09-14T17:27:50.507219Z +fbd724739cd9a5f2b28f16b412b27309 +2012-08-17T01:19:11.953078Z +756 +michael.stattmann@gmail.com + + + + + + + + + + + + + + + + + + + + + +19303 + diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base new file mode 100644 index 0000000..df51a1a --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AACTrackImpl.java.svn-base @@ -0,0 +1,292 @@ +/* + * Copyright 2012 castLabs GmbH, Berlin + * + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.AC3SpecificBox; +import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.*; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.*; + +/** + */ +public class AACTrackImpl extends AbstractTrack { + public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>(); + + static { + samplingFrequencyIndexMap.put(96000, 0); + samplingFrequencyIndexMap.put(88200, 1); + samplingFrequencyIndexMap.put(64000, 2); + samplingFrequencyIndexMap.put(48000, 3); + samplingFrequencyIndexMap.put(44100, 4); + samplingFrequencyIndexMap.put(32000, 5); + samplingFrequencyIndexMap.put(24000, 6); + samplingFrequencyIndexMap.put(22050, 7); + samplingFrequencyIndexMap.put(16000, 8); + samplingFrequencyIndexMap.put(12000, 9); + samplingFrequencyIndexMap.put(11025, 10); + samplingFrequencyIndexMap.put(8000, 11); + samplingFrequencyIndexMap.put(0x0, 96000); + samplingFrequencyIndexMap.put(0x1, 88200); + samplingFrequencyIndexMap.put(0x2, 64000); + samplingFrequencyIndexMap.put(0x3, 48000); + samplingFrequencyIndexMap.put(0x4, 44100); + samplingFrequencyIndexMap.put(0x5, 32000); + samplingFrequencyIndexMap.put(0x6, 24000); + samplingFrequencyIndexMap.put(0x7, 22050); + samplingFrequencyIndexMap.put(0x8, 16000); + samplingFrequencyIndexMap.put(0x9, 12000); + samplingFrequencyIndexMap.put(0xa, 11025); + samplingFrequencyIndexMap.put(0xb, 8000); + } + + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + + int samplerate; + int bitrate; + int channelCount; + int channelconfig; + + int bufferSizeDB; + long maxBitRate; + long avgBitRate; + + private BufferedInputStream inputStream; + private List<ByteBuffer> samples; + boolean readSamples = false; + List<TimeToSampleBox.Entry> stts; + private String lang = "und"; + + + public AACTrackImpl(InputStream inputStream, String lang) throws IOException { + this.lang = lang; + parse(inputStream); + } + + public AACTrackImpl(InputStream inputStream) throws IOException { + parse(inputStream); + } + + private void parse(InputStream inputStream) throws IOException { + this.inputStream = new BufferedInputStream(inputStream); + stts = new LinkedList<TimeToSampleBox.Entry>(); + + if (!readVariables()) { + throw new IOException(); + } + + samples = new LinkedList<ByteBuffer>(); + if (!readSamples()) { + throw new IOException(); + } + + double packetsPerSecond = (double)samplerate / 1024.0; + double duration = samples.size() / packetsPerSecond; + + long dataSize = 0; + LinkedList<Integer> queue = new LinkedList<Integer>(); + for (int i = 0; i < samples.size(); i++) { + int size = samples.get(i).capacity(); + dataSize += size; + queue.add(size); + while (queue.size() > packetsPerSecond) { + queue.pop(); + } + if (queue.size() == (int) packetsPerSecond) { + int currSize = 0; + for (int j = 0 ; j < queue.size(); j++) { + currSize += queue.get(j); + } + double currBitrate = 8.0 * currSize / queue.size() * packetsPerSecond; + if (currBitrate > maxBitRate) { + maxBitRate = (int)currBitrate; + } + } + } + + avgBitRate = (int) (8 * dataSize / duration); + + bufferSizeDB = 1536; /* TODO: Calcultate this somehow! */ + + sampleDescriptionBox = new SampleDescriptionBox(); + AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a"); + audioSampleEntry.setChannelCount(2); + audioSampleEntry.setSampleRate(samplerate); + audioSampleEntry.setDataReferenceIndex(1); + audioSampleEntry.setSampleSize(16); + + + ESDescriptorBox esds = new ESDescriptorBox(); + ESDescriptor descriptor = new ESDescriptor(); + descriptor.setEsId(0); + + SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor(); + slConfigDescriptor.setPredefined(2); + descriptor.setSlConfigDescriptor(slConfigDescriptor); + + DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor(); + decoderConfigDescriptor.setObjectTypeIndication(0x40); + decoderConfigDescriptor.setStreamType(5); + decoderConfigDescriptor.setBufferSizeDB(bufferSizeDB); + decoderConfigDescriptor.setMaxBitRate(maxBitRate); + decoderConfigDescriptor.setAvgBitRate(avgBitRate); + + AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig(); + audioSpecificConfig.setAudioObjectType(2); // AAC LC + audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get(samplerate)); + audioSpecificConfig.setChannelConfiguration(channelconfig); + decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig); + + descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor); + + ByteBuffer data = descriptor.serialize(); + esds.setData(data); + audioSampleEntry.addBox(esds); + sampleDescriptionBox.addBox(audioSampleEntry); + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setLanguage(lang); + trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "soun"; + } + + public List<ByteBuffer> getSamples() { + return samples; + } + + public Box getMediaHeaderBox() { + return new SoundMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + + private boolean readVariables() throws IOException { + byte[] data = new byte[100]; + inputStream.mark(100); + if (100 != inputStream.read(data, 0, 100)) { + return false; + } + inputStream.reset(); // Rewind + ByteBuffer bb = ByteBuffer.wrap(data); + BitReaderBuffer brb = new BitReaderBuffer(bb); + int syncword = brb.readBits(12); + if (syncword != 0xfff) { + return false; + } + int id = brb.readBits(1); + int layer = brb.readBits(2); + int protectionAbsent = brb.readBits(1); + int profile = brb.readBits(2); + samplerate = samplingFrequencyIndexMap.get(brb.readBits(4)); + brb.readBits(1); + channelconfig = brb.readBits(3); + int original = brb.readBits(1); + int home = brb.readBits(1); + int emphasis = brb.readBits(2); + + return true; + } + + private boolean readSamples() throws IOException { + if (readSamples) { + return true; + } + + readSamples = true; + byte[] header = new byte[15]; + boolean ret = false; + inputStream.mark(15); + while (-1 != inputStream.read(header)) { + ret = true; + ByteBuffer bb = ByteBuffer.wrap(header); + inputStream.reset(); + BitReaderBuffer brb = new BitReaderBuffer(bb); + int syncword = brb.readBits(12); + if (syncword != 0xfff) { + return false; + } + brb.readBits(3); + int protectionAbsent = brb.readBits(1); + brb.readBits(14); + int frameSize = brb.readBits(13); + int bufferFullness = brb.readBits(11); + int noBlocks = brb.readBits(2); + int used = (int) Math.ceil(brb.getPosition() / 8.0); + if (protectionAbsent == 0) { + used += 2; + } + inputStream.skip(used); + frameSize -= used; +// System.out.println("Size: " + frameSize + " fullness: " + bufferFullness + " no blocks: " + noBlocks); + byte[] data = new byte[frameSize]; + inputStream.read(data); + samples.add(ByteBuffer.wrap(data)); + stts.add(new TimeToSampleBox.Entry(1, 1024)); + inputStream.mark(15); + } + return ret; + } + + @Override + public String toString() { + return "AACTrackImpl{" + + "samplerate=" + samplerate + + ", bitrate=" + bitrate + + ", channelCount=" + channelCount + + ", channelconfig=" + channelconfig + + '}'; + } +} + diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base new file mode 100644 index 0000000..5e5b2cd --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AC3TrackImpl.java.svn-base @@ -0,0 +1,513 @@ +package com.googlecode.mp4parser.authoring.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.AC3SpecificBox; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer; + +import java.io.InputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +public class AC3TrackImpl extends AbstractTrack { + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + + int samplerate; + int bitrate; + int channelCount; + + int fscod; + int bsid; + int bsmod; + int acmod; + int lfeon; + int frmsizecod; + + int frameSize; + int[][][][] bitRateAndFrameSizeTable; + + private InputStream inputStream; + private List<ByteBuffer> samples; + boolean readSamples = false; + List<TimeToSampleBox.Entry> stts; + private String lang = "und"; + + public AC3TrackImpl(InputStream fin, String lang) throws IOException { + this.lang = lang; + parse(fin); + } + + public AC3TrackImpl(InputStream fin) throws IOException { + parse(fin); + } + + private void parse(InputStream fin) throws IOException { + inputStream = fin; + bitRateAndFrameSizeTable = new int[19][2][3][2]; + stts = new LinkedList<TimeToSampleBox.Entry>(); + initBitRateAndFrameSizeTable(); + if (!readVariables()) { + throw new IOException(); + } + + sampleDescriptionBox = new SampleDescriptionBox(); + AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ac-3"); + audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F + audioSampleEntry.setSampleRate(samplerate); + audioSampleEntry.setDataReferenceIndex(1); + audioSampleEntry.setSampleSize(16); + + AC3SpecificBox ac3 = new AC3SpecificBox(); + ac3.setAcmod(acmod); + ac3.setBitRateCode(frmsizecod >> 1); + ac3.setBsid(bsid); + ac3.setBsmod(bsmod); + ac3.setFscod(fscod); + ac3.setLfeon(lfeon); + ac3.setReserved(0); + + audioSampleEntry.addBox(ac3); + sampleDescriptionBox.addBox(audioSampleEntry); + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setLanguage(lang); + trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale + + samples = new LinkedList<ByteBuffer>(); + if (!readSamples()) { + throw new IOException(); + } + } + + + public List<ByteBuffer> getSamples() { + + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "soun"; + } + + public Box getMediaHeaderBox() { + return new SoundMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + + private boolean readVariables() throws IOException { + byte[] data = new byte[100]; + inputStream.mark(100); + if (100 != inputStream.read(data, 0, 100)) { + return false; + } + inputStream.reset(); // Rewind + ByteBuffer bb = ByteBuffer.wrap(data); + BitReaderBuffer brb = new BitReaderBuffer(bb); + int syncword = brb.readBits(16); + if (syncword != 0xb77) { + return false; + } + brb.readBits(16); // CRC-1 + fscod = brb.readBits(2); + + switch (fscod) { + case 0: + samplerate = 48000; + break; + + case 1: + samplerate = 44100; + break; + + case 2: + samplerate = 32000; + break; + + case 3: + samplerate = 0; + break; + + } + if (samplerate == 0) { + return false; + } + + frmsizecod = brb.readBits(6); + + if (!calcBitrateAndFrameSize(frmsizecod)) { + return false; + } + + if (frameSize == 0) { + return false; + } + bsid = brb.readBits(5); + bsmod = brb.readBits(3); + acmod = brb.readBits(3); + + if (bsid == 9) { + samplerate /= 2; + } else if (bsid != 8 && bsid != 6) { + return false; + } + + if ((acmod != 1) && ((acmod & 1) == 1)) { + brb.readBits(2); + } + + if (0 != (acmod & 4)) { + brb.readBits(2); + } + + if (acmod == 2) { + brb.readBits(2); + } + + switch (acmod) { + case 0: + channelCount = 2; + break; + + case 1: + channelCount = 1; + break; + + case 2: + channelCount = 2; + break; + + case 3: + channelCount = 3; + break; + + case 4: + channelCount = 3; + break; + + case 5: + channelCount = 4; + break; + + case 6: + channelCount = 4; + break; + + case 7: + channelCount = 5; + break; + + } + + lfeon = brb.readBits(1); + + if (lfeon == 1) { + channelCount++; + } + return true; + } + + private boolean calcBitrateAndFrameSize(int code) { + int frmsizecode = code >>> 1; + int flag = code & 1; + if (frmsizecode > 18 || flag > 1 || fscod > 2) { + return false; + } + bitrate = bitRateAndFrameSizeTable[frmsizecode][flag][fscod][0]; + frameSize = 2 * bitRateAndFrameSizeTable[frmsizecode][flag][fscod][1]; + return true; + } + + private boolean readSamples() throws IOException { + if (readSamples) { + return true; + } + readSamples = true; + byte[] header = new byte[5]; + boolean ret = false; + inputStream.mark(5); + while (-1 != inputStream.read(header)) { + ret = true; + int frmsizecode = header[4] & 63; + calcBitrateAndFrameSize(frmsizecode); + inputStream.reset(); + byte[] data = new byte[frameSize]; + inputStream.read(data); + samples.add(ByteBuffer.wrap(data)); + stts.add(new TimeToSampleBox.Entry(1, 1536)); + inputStream.mark(5); + } + return ret; + } + + private void initBitRateAndFrameSizeTable() { + // ETSI 102 366 Table 4.13, in frmsizecod, flag, fscod, bitrate/size order. Note that all sizes are in words, and all bitrates in kbps + + // 48kHz + bitRateAndFrameSizeTable[0][0][0][0] = 32; + bitRateAndFrameSizeTable[0][1][0][0] = 32; + bitRateAndFrameSizeTable[0][0][0][1] = 64; + bitRateAndFrameSizeTable[0][1][0][1] = 64; + bitRateAndFrameSizeTable[1][0][0][0] = 40; + bitRateAndFrameSizeTable[1][1][0][0] = 40; + bitRateAndFrameSizeTable[1][0][0][1] = 80; + bitRateAndFrameSizeTable[1][1][0][1] = 80; + bitRateAndFrameSizeTable[2][0][0][0] = 48; + bitRateAndFrameSizeTable[2][1][0][0] = 48; + bitRateAndFrameSizeTable[2][0][0][1] = 96; + bitRateAndFrameSizeTable[2][1][0][1] = 96; + bitRateAndFrameSizeTable[3][0][0][0] = 56; + bitRateAndFrameSizeTable[3][1][0][0] = 56; + bitRateAndFrameSizeTable[3][0][0][1] = 112; + bitRateAndFrameSizeTable[3][1][0][1] = 112; + bitRateAndFrameSizeTable[4][0][0][0] = 64; + bitRateAndFrameSizeTable[4][1][0][0] = 64; + bitRateAndFrameSizeTable[4][0][0][1] = 128; + bitRateAndFrameSizeTable[4][1][0][1] = 128; + bitRateAndFrameSizeTable[5][0][0][0] = 80; + bitRateAndFrameSizeTable[5][1][0][0] = 80; + bitRateAndFrameSizeTable[5][0][0][1] = 160; + bitRateAndFrameSizeTable[5][1][0][1] = 160; + bitRateAndFrameSizeTable[6][0][0][0] = 96; + bitRateAndFrameSizeTable[6][1][0][0] = 96; + bitRateAndFrameSizeTable[6][0][0][1] = 192; + bitRateAndFrameSizeTable[6][1][0][1] = 192; + bitRateAndFrameSizeTable[7][0][0][0] = 112; + bitRateAndFrameSizeTable[7][1][0][0] = 112; + bitRateAndFrameSizeTable[7][0][0][1] = 224; + bitRateAndFrameSizeTable[7][1][0][1] = 224; + bitRateAndFrameSizeTable[8][0][0][0] = 128; + bitRateAndFrameSizeTable[8][1][0][0] = 128; + bitRateAndFrameSizeTable[8][0][0][1] = 256; + bitRateAndFrameSizeTable[8][1][0][1] = 256; + bitRateAndFrameSizeTable[9][0][0][0] = 160; + bitRateAndFrameSizeTable[9][1][0][0] = 160; + bitRateAndFrameSizeTable[9][0][0][1] = 320; + bitRateAndFrameSizeTable[9][1][0][1] = 320; + bitRateAndFrameSizeTable[10][0][0][0] = 192; + bitRateAndFrameSizeTable[10][1][0][0] = 192; + bitRateAndFrameSizeTable[10][0][0][1] = 384; + bitRateAndFrameSizeTable[10][1][0][1] = 384; + bitRateAndFrameSizeTable[11][0][0][0] = 224; + bitRateAndFrameSizeTable[11][1][0][0] = 224; + bitRateAndFrameSizeTable[11][0][0][1] = 448; + bitRateAndFrameSizeTable[11][1][0][1] = 448; + bitRateAndFrameSizeTable[12][0][0][0] = 256; + bitRateAndFrameSizeTable[12][1][0][0] = 256; + bitRateAndFrameSizeTable[12][0][0][1] = 512; + bitRateAndFrameSizeTable[12][1][0][1] = 512; + bitRateAndFrameSizeTable[13][0][0][0] = 320; + bitRateAndFrameSizeTable[13][1][0][0] = 320; + bitRateAndFrameSizeTable[13][0][0][1] = 640; + bitRateAndFrameSizeTable[13][1][0][1] = 640; + bitRateAndFrameSizeTable[14][0][0][0] = 384; + bitRateAndFrameSizeTable[14][1][0][0] = 384; + bitRateAndFrameSizeTable[14][0][0][1] = 768; + bitRateAndFrameSizeTable[14][1][0][1] = 768; + bitRateAndFrameSizeTable[15][0][0][0] = 448; + bitRateAndFrameSizeTable[15][1][0][0] = 448; + bitRateAndFrameSizeTable[15][0][0][1] = 896; + bitRateAndFrameSizeTable[15][1][0][1] = 896; + bitRateAndFrameSizeTable[16][0][0][0] = 512; + bitRateAndFrameSizeTable[16][1][0][0] = 512; + bitRateAndFrameSizeTable[16][0][0][1] = 1024; + bitRateAndFrameSizeTable[16][1][0][1] = 1024; + bitRateAndFrameSizeTable[17][0][0][0] = 576; + bitRateAndFrameSizeTable[17][1][0][0] = 576; + bitRateAndFrameSizeTable[17][0][0][1] = 1152; + bitRateAndFrameSizeTable[17][1][0][1] = 1152; + bitRateAndFrameSizeTable[18][0][0][0] = 640; + bitRateAndFrameSizeTable[18][1][0][0] = 640; + bitRateAndFrameSizeTable[18][0][0][1] = 1280; + bitRateAndFrameSizeTable[18][1][0][1] = 1280; + + // 44.1 kHz + bitRateAndFrameSizeTable[0][0][1][0] = 32; + bitRateAndFrameSizeTable[0][1][1][0] = 32; + bitRateAndFrameSizeTable[0][0][1][1] = 69; + bitRateAndFrameSizeTable[0][1][1][1] = 70; + bitRateAndFrameSizeTable[1][0][1][0] = 40; + bitRateAndFrameSizeTable[1][1][1][0] = 40; + bitRateAndFrameSizeTable[1][0][1][1] = 87; + bitRateAndFrameSizeTable[1][1][1][1] = 88; + bitRateAndFrameSizeTable[2][0][1][0] = 48; + bitRateAndFrameSizeTable[2][1][1][0] = 48; + bitRateAndFrameSizeTable[2][0][1][1] = 104; + bitRateAndFrameSizeTable[2][1][1][1] = 105; + bitRateAndFrameSizeTable[3][0][1][0] = 56; + bitRateAndFrameSizeTable[3][1][1][0] = 56; + bitRateAndFrameSizeTable[3][0][1][1] = 121; + bitRateAndFrameSizeTable[3][1][1][1] = 122; + bitRateAndFrameSizeTable[4][0][1][0] = 64; + bitRateAndFrameSizeTable[4][1][1][0] = 64; + bitRateAndFrameSizeTable[4][0][1][1] = 139; + bitRateAndFrameSizeTable[4][1][1][1] = 140; + bitRateAndFrameSizeTable[5][0][1][0] = 80; + bitRateAndFrameSizeTable[5][1][1][0] = 80; + bitRateAndFrameSizeTable[5][0][1][1] = 174; + bitRateAndFrameSizeTable[5][1][1][1] = 175; + bitRateAndFrameSizeTable[6][0][1][0] = 96; + bitRateAndFrameSizeTable[6][1][1][0] = 96; + bitRateAndFrameSizeTable[6][0][1][1] = 208; + bitRateAndFrameSizeTable[6][1][1][1] = 209; + bitRateAndFrameSizeTable[7][0][1][0] = 112; + bitRateAndFrameSizeTable[7][1][1][0] = 112; + bitRateAndFrameSizeTable[7][0][1][1] = 243; + bitRateAndFrameSizeTable[7][1][1][1] = 244; + bitRateAndFrameSizeTable[8][0][1][0] = 128; + bitRateAndFrameSizeTable[8][1][1][0] = 128; + bitRateAndFrameSizeTable[8][0][1][1] = 278; + bitRateAndFrameSizeTable[8][1][1][1] = 279; + bitRateAndFrameSizeTable[9][0][1][0] = 160; + bitRateAndFrameSizeTable[9][1][1][0] = 160; + bitRateAndFrameSizeTable[9][0][1][1] = 348; + bitRateAndFrameSizeTable[9][1][1][1] = 349; + bitRateAndFrameSizeTable[10][0][1][0] = 192; + bitRateAndFrameSizeTable[10][1][1][0] = 192; + bitRateAndFrameSizeTable[10][0][1][1] = 417; + bitRateAndFrameSizeTable[10][1][1][1] = 418; + bitRateAndFrameSizeTable[11][0][1][0] = 224; + bitRateAndFrameSizeTable[11][1][1][0] = 224; + bitRateAndFrameSizeTable[11][0][1][1] = 487; + bitRateAndFrameSizeTable[11][1][1][1] = 488; + bitRateAndFrameSizeTable[12][0][1][0] = 256; + bitRateAndFrameSizeTable[12][1][1][0] = 256; + bitRateAndFrameSizeTable[12][0][1][1] = 557; + bitRateAndFrameSizeTable[12][1][1][1] = 558; + bitRateAndFrameSizeTable[13][0][1][0] = 320; + bitRateAndFrameSizeTable[13][1][1][0] = 320; + bitRateAndFrameSizeTable[13][0][1][1] = 696; + bitRateAndFrameSizeTable[13][1][1][1] = 697; + bitRateAndFrameSizeTable[14][0][1][0] = 384; + bitRateAndFrameSizeTable[14][1][1][0] = 384; + bitRateAndFrameSizeTable[14][0][1][1] = 835; + bitRateAndFrameSizeTable[14][1][1][1] = 836; + bitRateAndFrameSizeTable[15][0][1][0] = 448; + bitRateAndFrameSizeTable[15][1][1][0] = 448; + bitRateAndFrameSizeTable[15][0][1][1] = 975; + bitRateAndFrameSizeTable[15][1][1][1] = 975; + bitRateAndFrameSizeTable[16][0][1][0] = 512; + bitRateAndFrameSizeTable[16][1][1][0] = 512; + bitRateAndFrameSizeTable[16][0][1][1] = 1114; + bitRateAndFrameSizeTable[16][1][1][1] = 1115; + bitRateAndFrameSizeTable[17][0][1][0] = 576; + bitRateAndFrameSizeTable[17][1][1][0] = 576; + bitRateAndFrameSizeTable[17][0][1][1] = 1253; + bitRateAndFrameSizeTable[17][1][1][1] = 1254; + bitRateAndFrameSizeTable[18][0][1][0] = 640; + bitRateAndFrameSizeTable[18][1][1][0] = 640; + bitRateAndFrameSizeTable[18][0][1][1] = 1393; + bitRateAndFrameSizeTable[18][1][1][1] = 1394; + + // 32kHz + bitRateAndFrameSizeTable[0][0][2][0] = 32; + bitRateAndFrameSizeTable[0][1][2][0] = 32; + bitRateAndFrameSizeTable[0][0][2][1] = 96; + bitRateAndFrameSizeTable[0][1][2][1] = 96; + bitRateAndFrameSizeTable[1][0][2][0] = 40; + bitRateAndFrameSizeTable[1][1][2][0] = 40; + bitRateAndFrameSizeTable[1][0][2][1] = 120; + bitRateAndFrameSizeTable[1][1][2][1] = 120; + bitRateAndFrameSizeTable[2][0][2][0] = 48; + bitRateAndFrameSizeTable[2][1][2][0] = 48; + bitRateAndFrameSizeTable[2][0][2][1] = 144; + bitRateAndFrameSizeTable[2][1][2][1] = 144; + bitRateAndFrameSizeTable[3][0][2][0] = 56; + bitRateAndFrameSizeTable[3][1][2][0] = 56; + bitRateAndFrameSizeTable[3][0][2][1] = 168; + bitRateAndFrameSizeTable[3][1][2][1] = 168; + bitRateAndFrameSizeTable[4][0][2][0] = 64; + bitRateAndFrameSizeTable[4][1][2][0] = 64; + bitRateAndFrameSizeTable[4][0][2][1] = 192; + bitRateAndFrameSizeTable[4][1][2][1] = 192; + bitRateAndFrameSizeTable[5][0][2][0] = 80; + bitRateAndFrameSizeTable[5][1][2][0] = 80; + bitRateAndFrameSizeTable[5][0][2][1] = 240; + bitRateAndFrameSizeTable[5][1][2][1] = 240; + bitRateAndFrameSizeTable[6][0][2][0] = 96; + bitRateAndFrameSizeTable[6][1][2][0] = 96; + bitRateAndFrameSizeTable[6][0][2][1] = 288; + bitRateAndFrameSizeTable[6][1][2][1] = 288; + bitRateAndFrameSizeTable[7][0][2][0] = 112; + bitRateAndFrameSizeTable[7][1][2][0] = 112; + bitRateAndFrameSizeTable[7][0][2][1] = 336; + bitRateAndFrameSizeTable[7][1][2][1] = 336; + bitRateAndFrameSizeTable[8][0][2][0] = 128; + bitRateAndFrameSizeTable[8][1][2][0] = 128; + bitRateAndFrameSizeTable[8][0][2][1] = 384; + bitRateAndFrameSizeTable[8][1][2][1] = 384; + bitRateAndFrameSizeTable[9][0][2][0] = 160; + bitRateAndFrameSizeTable[9][1][2][0] = 160; + bitRateAndFrameSizeTable[9][0][2][1] = 480; + bitRateAndFrameSizeTable[9][1][2][1] = 480; + bitRateAndFrameSizeTable[10][0][2][0] = 192; + bitRateAndFrameSizeTable[10][1][2][0] = 192; + bitRateAndFrameSizeTable[10][0][2][1] = 576; + bitRateAndFrameSizeTable[10][1][2][1] = 576; + bitRateAndFrameSizeTable[11][0][2][0] = 224; + bitRateAndFrameSizeTable[11][1][2][0] = 224; + bitRateAndFrameSizeTable[11][0][2][1] = 672; + bitRateAndFrameSizeTable[11][1][2][1] = 672; + bitRateAndFrameSizeTable[12][0][2][0] = 256; + bitRateAndFrameSizeTable[12][1][2][0] = 256; + bitRateAndFrameSizeTable[12][0][2][1] = 768; + bitRateAndFrameSizeTable[12][1][2][1] = 768; + bitRateAndFrameSizeTable[13][0][2][0] = 320; + bitRateAndFrameSizeTable[13][1][2][0] = 320; + bitRateAndFrameSizeTable[13][0][2][1] = 960; + bitRateAndFrameSizeTable[13][1][2][1] = 960; + bitRateAndFrameSizeTable[14][0][2][0] = 384; + bitRateAndFrameSizeTable[14][1][2][0] = 384; + bitRateAndFrameSizeTable[14][0][2][1] = 1152; + bitRateAndFrameSizeTable[14][1][2][1] = 1152; + bitRateAndFrameSizeTable[15][0][2][0] = 448; + bitRateAndFrameSizeTable[15][1][2][0] = 448; + bitRateAndFrameSizeTable[15][0][2][1] = 1344; + bitRateAndFrameSizeTable[15][1][2][1] = 1344; + bitRateAndFrameSizeTable[16][0][2][0] = 512; + bitRateAndFrameSizeTable[16][1][2][0] = 512; + bitRateAndFrameSizeTable[16][0][2][1] = 1536; + bitRateAndFrameSizeTable[16][1][2][1] = 1536; + bitRateAndFrameSizeTable[17][0][2][0] = 576; + bitRateAndFrameSizeTable[17][1][2][0] = 576; + bitRateAndFrameSizeTable[17][0][2][1] = 1728; + bitRateAndFrameSizeTable[17][1][2][1] = 1728; + bitRateAndFrameSizeTable[18][0][2][0] = 640; + bitRateAndFrameSizeTable[18][1][2][0] = 640; + bitRateAndFrameSizeTable[18][0][2][1] = 1920; + bitRateAndFrameSizeTable[18][1][2][1] = 1920; + } +}
\ No newline at end of file diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base new file mode 100644 index 0000000..0917767 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/Amf0Track.java.svn-base @@ -0,0 +1,116 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.adobe.ActionMessageFormat0SampleEntryBox; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; + +public class Amf0Track extends AbstractTrack { + SortedMap<Long, byte[]> rawSamples = new TreeMap<Long, byte[]>() { + }; + private TrackMetaData trackMetaData = new TrackMetaData(); + + + /** + * Creates a new AMF0 track from + * + * @param rawSamples + */ + public Amf0Track(Map<Long, byte[]> rawSamples) { + this.rawSamples = new TreeMap<Long, byte[]>(rawSamples); + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setTimescale(1000); // Text tracks use millieseconds + trackMetaData.setLanguage("eng"); + } + + public List<ByteBuffer> getSamples() { + LinkedList<ByteBuffer> samples = new LinkedList<ByteBuffer>(); + for (byte[] bytes : rawSamples.values()) { + samples.add(ByteBuffer.wrap(bytes)); + } + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + SampleDescriptionBox stsd = new SampleDescriptionBox(); + ActionMessageFormat0SampleEntryBox amf0 = new ActionMessageFormat0SampleEntryBox(); + amf0.setDataReferenceIndex(1); + stsd.addBox(amf0); + return stsd; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + LinkedList<TimeToSampleBox.Entry> timesToSample = new LinkedList<TimeToSampleBox.Entry>(); + LinkedList<Long> keys = new LinkedList<Long>(rawSamples.keySet()); + Collections.sort(keys); + long lastTimeStamp = 0; + for (Long key : keys) { + long delta = key - lastTimeStamp; + if (timesToSample.size() > 0 && timesToSample.peek().getDelta() == delta) { + timesToSample.peek().setCount(timesToSample.peek().getCount() + 1); + } else { + timesToSample.add(new TimeToSampleBox.Entry(1, delta)); + } + lastTimeStamp = key; + } + return timesToSample; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + // AMF0 tracks do not have Composition Time + return null; + } + + public long[] getSyncSamples() { + // AMF0 tracks do not have Sync Samples + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + // AMF0 tracks do not have Sample Dependencies + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; //To change body of implemented methods use File | Settings | File Templates. + } + + public String getHandler() { + return "data"; + } + + public Box getMediaHeaderBox() { + return new NullMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base new file mode 100644 index 0000000..93ee0cd --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/AppendTrack.java.svn-base @@ -0,0 +1,348 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.util.*; + +/** + * Appends two or more <code>Tracks</code> of the same type. No only that the type must be equal + * also the decoder settings must be the same. + */ +public class AppendTrack extends AbstractTrack { + Track[] tracks; + SampleDescriptionBox stsd; + + public AppendTrack(Track... tracks) throws IOException { + this.tracks = tracks; + + for (Track track : tracks) { + + if (stsd == null) { + stsd = track.getSampleDescriptionBox(); + } else { + ByteArrayOutputStream curBaos = new ByteArrayOutputStream(); + ByteArrayOutputStream refBaos = new ByteArrayOutputStream(); + track.getSampleDescriptionBox().getBox(Channels.newChannel(curBaos)); + stsd.getBox(Channels.newChannel(refBaos)); + byte[] cur = curBaos.toByteArray(); + byte[] ref = refBaos.toByteArray(); + + if (!Arrays.equals(ref, cur)) { + SampleDescriptionBox curStsd = track.getSampleDescriptionBox(); + if (stsd.getBoxes().size() == 1 && curStsd.getBoxes().size() == 1) { + if (stsd.getBoxes().get(0) instanceof AudioSampleEntry && curStsd.getBoxes().get(0) instanceof AudioSampleEntry) { + AudioSampleEntry aseResult = mergeAudioSampleEntries((AudioSampleEntry) stsd.getBoxes().get(0), (AudioSampleEntry) curStsd.getBoxes().get(0)); + if (aseResult != null) { + stsd.setBoxes(Collections.<Box>singletonList(aseResult)); + return; + } + } + } + throw new IOException("Cannot append " + track + " to " + tracks[0] + " since their Sample Description Boxes differ: \n" + track.getSampleDescriptionBox() + "\n vs. \n" + tracks[0].getSampleDescriptionBox()); + } + } + } + } + + private AudioSampleEntry mergeAudioSampleEntries(AudioSampleEntry ase1, AudioSampleEntry ase2) throws IOException { + if (ase1.getType().equals(ase2.getType())) { + AudioSampleEntry ase = new AudioSampleEntry(ase2.getType()); + if (ase1.getBytesPerFrame() == ase2.getBytesPerFrame()) { + ase.setBytesPerFrame(ase1.getBytesPerFrame()); + } else { + return null; + } + if (ase1.getBytesPerPacket() == ase2.getBytesPerPacket()) { + ase.setBytesPerPacket(ase1.getBytesPerPacket()); + } else { + return null; + } + if (ase1.getBytesPerSample() == ase2.getBytesPerSample()) { + ase.setBytesPerSample(ase1.getBytesPerSample()); + } else { + return null; + } + if (ase1.getChannelCount() == ase2.getChannelCount()) { + ase.setChannelCount(ase1.getChannelCount()); + } else { + return null; + } + if (ase1.getPacketSize() == ase2.getPacketSize()) { + ase.setPacketSize(ase1.getPacketSize()); + } else { + return null; + } + if (ase1.getCompressionId() == ase2.getCompressionId()) { + ase.setCompressionId(ase1.getCompressionId()); + } else { + return null; + } + if (ase1.getSampleRate() == ase2.getSampleRate()) { + ase.setSampleRate(ase1.getSampleRate()); + } else { + return null; + } + if (ase1.getSampleSize() == ase2.getSampleSize()) { + ase.setSampleSize(ase1.getSampleSize()); + } else { + return null; + } + if (ase1.getSamplesPerPacket() == ase2.getSamplesPerPacket()) { + ase.setSamplesPerPacket(ase1.getSamplesPerPacket()); + } else { + return null; + } + if (ase1.getSoundVersion() == ase2.getSoundVersion()) { + ase.setSoundVersion(ase1.getSoundVersion()); + } else { + return null; + } + if (Arrays.equals(ase1.getSoundVersion2Data(), ase2.getSoundVersion2Data())) { + ase.setSoundVersion2Data(ase1.getSoundVersion2Data()); + } else { + return null; + } + if (ase1.getBoxes().size() == ase2.getBoxes().size()) { + Iterator<Box> bxs1 = ase1.getBoxes().iterator(); + Iterator<Box> bxs2 = ase2.getBoxes().iterator(); + while (bxs1.hasNext()) { + Box cur1 = bxs1.next(); + Box cur2 = bxs2.next(); + ByteArrayOutputStream baos1 = new ByteArrayOutputStream(); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + cur1.getBox(Channels.newChannel(baos1)); + cur2.getBox(Channels.newChannel(baos2)); + if (Arrays.equals(baos1.toByteArray(), baos2.toByteArray())) { + ase.addBox(cur1); + } else { + if (ESDescriptorBox.TYPE.equals(cur1.getType()) && ESDescriptorBox.TYPE.equals(cur2.getType())) { + ESDescriptorBox esdsBox1 = (ESDescriptorBox) cur1; + ESDescriptorBox esdsBox2 = (ESDescriptorBox) cur2; + ESDescriptor esds1 = esdsBox1.getEsDescriptor(); + ESDescriptor esds2 = esdsBox2.getEsDescriptor(); + if (esds1.getURLFlag() != esds2.getURLFlag()) { + return null; + } + if (esds1.getURLLength() != esds2.getURLLength()) { + return null; + } + if (esds1.getDependsOnEsId() != esds2.getDependsOnEsId()) { + return null; + } + if (esds1.getEsId() != esds2.getEsId()) { + return null; + } + if (esds1.getoCREsId() != esds2.getoCREsId()) { + return null; + } + if (esds1.getoCRstreamFlag() != esds2.getoCRstreamFlag()) { + return null; + } + if (esds1.getRemoteODFlag() != esds2.getRemoteODFlag()) { + return null; + } + if (esds1.getStreamDependenceFlag() != esds2.getStreamDependenceFlag()) { + return null; + } + if (esds1.getStreamPriority() != esds2.getStreamPriority()) { + return null; + } + if (esds1.getURLString() != null ? !esds1.getURLString().equals(esds2.getURLString()) : esds2.getURLString() != null) { + return null; + } + if (esds1.getDecoderConfigDescriptor() != null ? !esds1.getDecoderConfigDescriptor().equals(esds2.getDecoderConfigDescriptor()) : esds2.getDecoderConfigDescriptor() != null) { + DecoderConfigDescriptor dcd1 = esds1.getDecoderConfigDescriptor(); + DecoderConfigDescriptor dcd2 = esds2.getDecoderConfigDescriptor(); + if (!dcd1.getAudioSpecificInfo().equals(dcd2.getAudioSpecificInfo())) { + return null; + } + if (dcd1.getAvgBitRate() != dcd2.getAvgBitRate()) { + // I don't care + } + if (dcd1.getBufferSizeDB() != dcd2.getBufferSizeDB()) { + // I don't care + } + + if (dcd1.getDecoderSpecificInfo() != null ? !dcd1.getDecoderSpecificInfo().equals(dcd2.getDecoderSpecificInfo()) : dcd2.getDecoderSpecificInfo() != null) { + return null; + } + + if (dcd1.getMaxBitRate() != dcd2.getMaxBitRate()) { + // I don't care + } + if (!dcd1.getProfileLevelIndicationDescriptors().equals(dcd2.getProfileLevelIndicationDescriptors())) { + return null; + } + + if (dcd1.getObjectTypeIndication() != dcd2.getObjectTypeIndication()) { + return null; + } + if (dcd1.getStreamType() != dcd2.getStreamType()) { + return null; + } + if (dcd1.getUpStream() != dcd2.getUpStream()) { + return null; + } + + + } + if (esds1.getOtherDescriptors() != null ? !esds1.getOtherDescriptors().equals(esds2.getOtherDescriptors()) : esds2.getOtherDescriptors() != null) { + return null; + } + if (esds1.getSlConfigDescriptor() != null ? !esds1.getSlConfigDescriptor().equals(esds2.getSlConfigDescriptor()) : esds2.getSlConfigDescriptor() != null) { + return null; + } + ase.addBox(cur1); + } + } + } + } + return ase; + } else { + return null; + } + + + } + + + public List<ByteBuffer> getSamples() { + ArrayList<ByteBuffer> lists = new ArrayList<ByteBuffer>(); + + for (Track track : tracks) { + lists.addAll(track.getSamples()); + } + + return lists; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return stsd; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + if (tracks[0].getDecodingTimeEntries() != null && !tracks[0].getDecodingTimeEntries().isEmpty()) { + List<long[]> lists = new LinkedList<long[]>(); + for (Track track : tracks) { + lists.add(TimeToSampleBox.blowupTimeToSamples(track.getDecodingTimeEntries())); + } + + LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>(); + for (long[] list : lists) { + for (long nuDecodingTime : list) { + if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) { + TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime); + returnDecodingEntries.add(e); + } else { + TimeToSampleBox.Entry e = returnDecodingEntries.getLast(); + e.setCount(e.getCount() + 1); + } + } + } + return returnDecodingEntries; + } else { + return null; + } + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + if (tracks[0].getCompositionTimeEntries() != null && !tracks[0].getCompositionTimeEntries().isEmpty()) { + List<int[]> lists = new LinkedList<int[]>(); + for (Track track : tracks) { + lists.add(CompositionTimeToSample.blowupCompositionTimes(track.getCompositionTimeEntries())); + } + LinkedList<CompositionTimeToSample.Entry> compositionTimeEntries = new LinkedList<CompositionTimeToSample.Entry>(); + for (int[] list : lists) { + for (int compositionTime : list) { + if (compositionTimeEntries.isEmpty() || compositionTimeEntries.getLast().getOffset() != compositionTime) { + CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, compositionTime); + compositionTimeEntries.add(e); + } else { + CompositionTimeToSample.Entry e = compositionTimeEntries.getLast(); + e.setCount(e.getCount() + 1); + } + } + } + return compositionTimeEntries; + } else { + return null; + } + } + + public long[] getSyncSamples() { + if (tracks[0].getSyncSamples() != null && tracks[0].getSyncSamples().length > 0) { + int numSyncSamples = 0; + for (Track track : tracks) { + numSyncSamples += track.getSyncSamples().length; + } + long[] returnSyncSamples = new long[numSyncSamples]; + + int pos = 0; + long samplesBefore = 0; + for (Track track : tracks) { + for (long l : track.getSyncSamples()) { + returnSyncSamples[pos++] = samplesBefore + l; + } + samplesBefore += track.getSamples().size(); + } + return returnSyncSamples; + } else { + return null; + } + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + if (tracks[0].getSampleDependencies() != null && !tracks[0].getSampleDependencies().isEmpty()) { + List<SampleDependencyTypeBox.Entry> list = new LinkedList<SampleDependencyTypeBox.Entry>(); + for (Track track : tracks) { + list.addAll(track.getSampleDependencies()); + } + return list; + } else { + return null; + } + } + + public TrackMetaData getTrackMetaData() { + return tracks[0].getTrackMetaData(); + } + + public String getHandler() { + return tracks[0].getHandler(); + } + + public Box getMediaHeaderBox() { + return tracks[0].getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return tracks[0].getSubsampleInformationBox(); + } + +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base new file mode 100644 index 0000000..50f76c2 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ChangeTimeScaleTrack.java.svn-base @@ -0,0 +1,203 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.logging.Logger; + +/** + * Changes the timescale of a track by wrapping the track. + */ +public class ChangeTimeScaleTrack implements Track { + private static final Logger LOG = Logger.getLogger(ChangeTimeScaleTrack.class.getName()); + + Track source; + List<CompositionTimeToSample.Entry> ctts; + List<TimeToSampleBox.Entry> tts; + long timeScale; + + /** + * Changes the time scale of the source track to the target time scale and makes sure + * that any rounding errors that may have summed are corrected exactly before the syncSamples. + * + * @param source the source track + * @param targetTimeScale the resulting time scale of this track. + * @param syncSamples at these sync points where rounding error are corrected. + */ + public ChangeTimeScaleTrack(Track source, long targetTimeScale, long[] syncSamples) { + this.source = source; + this.timeScale = targetTimeScale; + double timeScaleFactor = (double) targetTimeScale / source.getTrackMetaData().getTimescale(); + ctts = adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor); + tts = adjustTts(source.getDecodingTimeEntries(), timeScaleFactor, syncSamples, getTimes(source, syncSamples, targetTimeScale)); + } + + private static long[] getTimes(Track track, long[] syncSamples, long targetTimeScale) { + 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; + + + while (currentSample <= syncSamples[syncSamples.length - 1]) { + if (currentSample++ == syncSamples[currentSyncSampleIndex]) { + syncSampleTimes[currentSyncSampleIndex++] = (currentDuration * targetTimeScale) / track.getTrackMetaData().getTimescale(); + } + if (left-- == 0) { + TimeToSampleBox.Entry entry = timeQueue.poll(); + left = entry.getCount() - 1; + currentDelta = entry.getDelta(); + } + currentDuration += currentDelta; + } + return syncSampleTimes; + + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return source.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return tts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return ctts; + } + + public long[] getSyncSamples() { + return source.getSyncSamples(); + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return source.getSampleDependencies(); + } + + public TrackMetaData getTrackMetaData() { + TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone(); + trackMetaData.setTimescale(timeScale); + return trackMetaData; + } + + public String getHandler() { + return source.getHandler(); + } + + public boolean isEnabled() { + return source.isEnabled(); + } + + public boolean isInMovie() { + return source.isInMovie(); + } + + public boolean isInPreview() { + return source.isInPreview(); + } + + public boolean isInPoster() { + return source.isInPoster(); + } + + public List<ByteBuffer> getSamples() { + return source.getSamples(); + } + + + /** + * Adjusting the composition times is easy. Just scale it by the factor - that's it. There is no rounding + * error summing up. + * + * @param source + * @param timeScaleFactor + * @return + */ + static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, double timeScaleFactor) { + if (source != null) { + List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size()); + for (CompositionTimeToSample.Entry entry : source) { + entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), (int) Math.round(timeScaleFactor * entry.getOffset()))); + } + return entries2; + } else { + return null; + } + } + + static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, double timeScaleFactor, long[] syncSample, long[] syncSampleTimes) { + + long[] sourceArray = TimeToSampleBox.blowupTimeToSamples(source); + long summedDurations = 0; + + LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>(); + for (int i = 1; i <= sourceArray.length; i++) { + long duration = sourceArray[i - 1]; + + long x = Math.round(timeScaleFactor * duration); + + + TimeToSampleBox.Entry last = entries2.peekLast(); + int ssIndex; + if ((ssIndex = Arrays.binarySearch(syncSample, i + 1)) >= 0) { + // we are at the sample before sync point + if (syncSampleTimes[ssIndex] != summedDurations) { + long correction = syncSampleTimes[ssIndex] - (summedDurations + x); + LOG.finest(String.format("Sample %d %d / %d - correct by %d", i, summedDurations, syncSampleTimes[ssIndex], correction)); + x += correction; + } + } + summedDurations += x; + if (last == null) { + entries2.add(new TimeToSampleBox.Entry(1, x)); + } else if (last.getDelta() != x) { + entries2.add(new TimeToSampleBox.Entry(1, x)); + } else { + last.setCount(last.getCount() + 1); + } + + } + return entries2; + } + + public Box getMediaHeaderBox() { + return source.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return source.getSubsampleInformationBox(); + } + + @Override + public String toString() { + return "ChangeTimeScaleTrack{" + + "source=" + source + + '}'; + } +}
\ No newline at end of file diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base new file mode 100644 index 0000000..2389961 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/CroppedTrack.java.svn-base @@ -0,0 +1,151 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +/** + * Generates a Track that starts at fromSample and ends at toSample (exclusive). The user of this class + * has to make sure that the fromSample is a random access sample. + * <ul> + * <li>In AAC this is every single sample</li> + * <li>In H264 this is every sample that is marked in the SyncSampleBox</li> + * </ul> + */ +public class CroppedTrack extends AbstractTrack { + Track origTrack; + private int fromSample; + private int toSample; + private long[] syncSampleArray; + + public CroppedTrack(Track origTrack, long fromSample, long toSample) { + this.origTrack = origTrack; + assert fromSample <= Integer.MAX_VALUE; + assert toSample <= Integer.MAX_VALUE; + this.fromSample = (int) fromSample; + this.toSample = (int) toSample; + } + + public List<ByteBuffer> getSamples() { + return origTrack.getSamples().subList(fromSample, toSample); + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return origTrack.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + if (origTrack.getDecodingTimeEntries() != null && !origTrack.getDecodingTimeEntries().isEmpty()) { + // todo optimize! too much long is allocated but then not used + long[] decodingTimes = TimeToSampleBox.blowupTimeToSamples(origTrack.getDecodingTimeEntries()); + long[] nuDecodingTimes = new long[toSample - fromSample]; + System.arraycopy(decodingTimes, fromSample, nuDecodingTimes, 0, toSample - fromSample); + + LinkedList<TimeToSampleBox.Entry> returnDecodingEntries = new LinkedList<TimeToSampleBox.Entry>(); + + for (long nuDecodingTime : nuDecodingTimes) { + if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getDelta() != nuDecodingTime) { + TimeToSampleBox.Entry e = new TimeToSampleBox.Entry(1, nuDecodingTime); + returnDecodingEntries.add(e); + } else { + TimeToSampleBox.Entry e = returnDecodingEntries.getLast(); + e.setCount(e.getCount() + 1); + } + } + return returnDecodingEntries; + } else { + return null; + } + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + if (origTrack.getCompositionTimeEntries() != null && !origTrack.getCompositionTimeEntries().isEmpty()) { + int[] compositionTime = CompositionTimeToSample.blowupCompositionTimes(origTrack.getCompositionTimeEntries()); + int[] nuCompositionTimes = new int[toSample - fromSample]; + System.arraycopy(compositionTime, fromSample, nuCompositionTimes, 0, toSample - fromSample); + + LinkedList<CompositionTimeToSample.Entry> returnDecodingEntries = new LinkedList<CompositionTimeToSample.Entry>(); + + for (int nuDecodingTime : nuCompositionTimes) { + if (returnDecodingEntries.isEmpty() || returnDecodingEntries.getLast().getOffset() != nuDecodingTime) { + CompositionTimeToSample.Entry e = new CompositionTimeToSample.Entry(1, nuDecodingTime); + returnDecodingEntries.add(e); + } else { + CompositionTimeToSample.Entry e = returnDecodingEntries.getLast(); + e.setCount(e.getCount() + 1); + } + } + return returnDecodingEntries; + } else { + return null; + } + } + + synchronized public long[] getSyncSamples() { + if (this.syncSampleArray == null) { + if (origTrack.getSyncSamples() != null && origTrack.getSyncSamples().length > 0) { + List<Long> syncSamples = new LinkedList<Long>(); + for (long l : origTrack.getSyncSamples()) { + if (l >= fromSample && l < toSample) { + syncSamples.add(l - fromSample); + } + } + syncSampleArray = new long[syncSamples.size()]; + for (int i = 0; i < syncSampleArray.length; i++) { + syncSampleArray[i] = syncSamples.get(i); + + } + return syncSampleArray; + } else { + return null; + } + } else { + return this.syncSampleArray; + } + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + if (origTrack.getSampleDependencies() != null && !origTrack.getSampleDependencies().isEmpty()) { + return origTrack.getSampleDependencies().subList(fromSample, toSample); + } else { + return null; + } + } + + public TrackMetaData getTrackMetaData() { + return origTrack.getTrackMetaData(); + } + + public String getHandler() { + return origTrack.getHandler(); + } + + public Box getMediaHeaderBox() { + return origTrack.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return origTrack.getSubsampleInformationBox(); + } + +}
\ No newline at end of file diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base new file mode 100644 index 0000000..c51e8e0 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/DivideTimeScaleTrack.java.svn-base @@ -0,0 +1,126 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +/** + * Changes the timescale of a track by wrapping the track. + */ +public class DivideTimeScaleTrack implements Track { + Track source; + private int timeScaleDivisor; + + public DivideTimeScaleTrack(Track source, int timeScaleDivisor) { + this.source = source; + this.timeScaleDivisor = timeScaleDivisor; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return source.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return adjustTts(); + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return adjustCtts(); + } + + public long[] getSyncSamples() { + return source.getSyncSamples(); + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return source.getSampleDependencies(); + } + + public TrackMetaData getTrackMetaData() { + TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone(); + trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() / this.timeScaleDivisor); + return trackMetaData; + } + + public String getHandler() { + return source.getHandler(); + } + + public boolean isEnabled() { + return source.isEnabled(); + } + + public boolean isInMovie() { + return source.isInMovie(); + } + + public boolean isInPreview() { + return source.isInPreview(); + } + + public boolean isInPoster() { + return source.isInPoster(); + } + + public List<ByteBuffer> getSamples() { + return source.getSamples(); + } + + + List<CompositionTimeToSample.Entry> adjustCtts() { + List<CompositionTimeToSample.Entry> origCtts = this.source.getCompositionTimeEntries(); + if (origCtts != null) { + List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(origCtts.size()); + for (CompositionTimeToSample.Entry entry : origCtts) { + entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), entry.getOffset() / timeScaleDivisor)); + } + return entries2; + } else { + return null; + } + } + + List<TimeToSampleBox.Entry> adjustTts() { + List<TimeToSampleBox.Entry> origTts = source.getDecodingTimeEntries(); + LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>(); + for (TimeToSampleBox.Entry e : origTts) { + entries2.add(new TimeToSampleBox.Entry(e.getCount(), e.getDelta() / timeScaleDivisor)); + } + return entries2; + } + + public Box getMediaHeaderBox() { + return source.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return source.getSubsampleInformationBox(); + } + + @Override + public String toString() { + return "MultiplyTimeScaleTrack{" + + "source=" + source + + '}'; + } +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base new file mode 100644 index 0000000..d0b2d76 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/EC3TrackImpl.java.svn-base @@ -0,0 +1,436 @@ +package com.googlecode.mp4parser.authoring.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.EC3SpecificBox; +import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by IntelliJ IDEA. + * User: magnus + * Date: 2012-03-14 + * Time: 10:39 + * To change this template use File | Settings | File Templates. + */ +public class EC3TrackImpl extends AbstractTrack { + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + + int samplerate; + int bitrate; + int frameSize; + + List<BitStreamInfo> entries = new LinkedList<BitStreamInfo>(); + + private BufferedInputStream inputStream; + private List<ByteBuffer> samples; + List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>(); + private String lang = "und"; + + public EC3TrackImpl(InputStream fin, String lang) throws IOException { + this.lang = lang; + parse(fin); + } + + public EC3TrackImpl(InputStream fin) throws IOException { + parse(fin); + } + + private void parse(InputStream fin) throws IOException { + inputStream = new BufferedInputStream(fin); + + boolean done = false; + inputStream.mark(10000); + while (!done) { + BitStreamInfo bsi = readVariables(); + if (bsi == null) { + throw new IOException(); + } + for (BitStreamInfo entry : entries) { + if (bsi.strmtyp != 1 && entry.substreamid == bsi.substreamid) { + done = true; + } + } + if (!done) { + entries.add(bsi); + long skipped = inputStream.skip(bsi.frameSize); + assert skipped == bsi.frameSize; + } + } + + inputStream.reset(); + + if (entries.size() == 0) { + throw new IOException(); + } + samplerate = entries.get(0).samplerate; + + sampleDescriptionBox = new SampleDescriptionBox(); + AudioSampleEntry audioSampleEntry = new AudioSampleEntry("ec-3"); + audioSampleEntry.setChannelCount(2); // According to ETSI TS 102 366 Annex F + audioSampleEntry.setSampleRate(samplerate); + audioSampleEntry.setDataReferenceIndex(1); + audioSampleEntry.setSampleSize(16); + + EC3SpecificBox ec3 = new EC3SpecificBox(); + int[] deps = new int[entries.size()]; + int[] chan_locs = new int[entries.size()]; + for (BitStreamInfo bsi : entries) { + if (bsi.strmtyp == 1) { + deps[bsi.substreamid]++; + chan_locs[bsi.substreamid] = ((bsi.chanmap >> 6) & 0x100) | ((bsi.chanmap >> 5) & 0xff); + } + } + for (BitStreamInfo bsi : entries) { + if (bsi.strmtyp != 1) { + EC3SpecificBox.Entry e = new EC3SpecificBox.Entry(); + e.fscod = bsi.fscod; + e.bsid = bsi.bsid; + e.bsmod = bsi.bsmod; + e.acmod = bsi.acmod; + e.lfeon = bsi.lfeon; + e.reserved = 0; + e.num_dep_sub = deps[bsi.substreamid]; + e.chan_loc = chan_locs[bsi.substreamid]; + e.reserved2 = 0; + ec3.addEntry(e); + } + bitrate += bsi.bitrate; + frameSize += bsi.frameSize; + } + + ec3.setDataRate(bitrate / 1000); + audioSampleEntry.addBox(ec3); + sampleDescriptionBox.addBox(audioSampleEntry); + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setLanguage(lang); + trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale + + samples = new LinkedList<ByteBuffer>(); + if (!readSamples()) { + throw new IOException(); + } + } + + + public List<ByteBuffer> getSamples() { + + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "soun"; + } + + public AbstractMediaHeaderBox getMediaHeaderBox() { + return new SoundMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + + private BitStreamInfo readVariables() throws IOException { + byte[] data = new byte[200]; + inputStream.mark(200); + if (200 != inputStream.read(data, 0, 200)) { + return null; + } + inputStream.reset(); // Rewind + ByteBuffer bb = ByteBuffer.wrap(data); + BitReaderBuffer brb = new BitReaderBuffer(bb); + int syncword = brb.readBits(16); + if (syncword != 0xb77) { + return null; + } + + BitStreamInfo entry = new BitStreamInfo(); + + entry.strmtyp = brb.readBits(2); + entry.substreamid = brb.readBits(3); + int frmsiz = brb.readBits(11); + entry.frameSize = 2 * (frmsiz + 1); + + entry.fscod = brb.readBits(2); + int fscod2 = -1; + int numblkscod; + if (entry.fscod == 3) { + fscod2 = brb.readBits(2); + numblkscod = 3; + } else { + numblkscod = brb.readBits(2); + } + int numberOfBlocksPerSyncFrame = 0; + switch (numblkscod) { + case 0: + numberOfBlocksPerSyncFrame = 1; + break; + + case 1: + numberOfBlocksPerSyncFrame = 2; + break; + + case 2: + numberOfBlocksPerSyncFrame = 3; + break; + + case 3: + numberOfBlocksPerSyncFrame = 6; + break; + + } + entry.frameSize *= (6 / numberOfBlocksPerSyncFrame); + + entry.acmod = brb.readBits(3); + entry.lfeon = brb.readBits(1); + entry.bsid = brb.readBits(5); + brb.readBits(5); + if (1 == brb.readBits(1)) { + brb.readBits(8); // compr + } + if (0 == entry.acmod) { + brb.readBits(5); + if (1 == brb.readBits(1)) { + brb.readBits(8); + } + } + if (1 == entry.strmtyp) { + if (1 == brb.readBits(1)) { + entry.chanmap = brb.readBits(16); + } + } + if (1 == brb.readBits(1)) { // mixing metadata + if (entry.acmod > 2) { + brb.readBits(2); + } + if (1 == (entry.acmod & 1) && entry.acmod > 2) { + brb.readBits(3); + brb.readBits(3); + } + if (0 < (entry.acmod & 4)) { + brb.readBits(3); + brb.readBits(3); + } + if (1 == entry.lfeon) { + if (1 == brb.readBits(1)) { + brb.readBits(5); + } + } + if (0 == entry.strmtyp) { + if (1 == brb.readBits(1)) { + brb.readBits(6); + } + if (0 == entry.acmod) { + if (1 == brb.readBits(1)) { + brb.readBits(6); + } + } + if (1 == brb.readBits(1)) { + brb.readBits(6); + } + int mixdef = brb.readBits(2); + if (1 == mixdef) { + brb.readBits(5); + } else if (2 == mixdef) { + brb.readBits(12); + } else if (3 == mixdef) { + int mixdeflen = brb.readBits(5); + if (1 == brb.readBits(1)) { + brb.readBits(5); + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + if (1 == brb.readBits(1)) { + brb.readBits(4); + } + } + } + if (1 == brb.readBits(1)) { + brb.readBits(5); + if (1 == brb.readBits(1)) { + brb.readBits(7); + if (1 == brb.readBits(1)) { + brb.readBits(8); + } + } + } + for (int i = 0; i < (mixdeflen + 2); i++) { + brb.readBits(8); + } + brb.byteSync(); + } + if (entry.acmod < 2) { + if (1 == brb.readBits(1)) { + brb.readBits(14); + } + if (0 == entry.acmod) { + if (1 == brb.readBits(1)) { + brb.readBits(14); + } + } + if (1 == brb.readBits(1)) { + if (numblkscod == 0) { + brb.readBits(5); + } else { + for (int i = 0; i < numberOfBlocksPerSyncFrame; i++) { + if (1 == brb.readBits(1)) { + brb.readBits(5); + } + } + } + + } + } + } + } + if (1 == brb.readBits(1)) { // infomdate + entry.bsmod = brb.readBits(3); + } + + switch (entry.fscod) { + case 0: + entry.samplerate = 48000; + break; + + case 1: + entry.samplerate = 44100; + break; + + case 2: + entry.samplerate = 32000; + break; + + case 3: { + switch (fscod2) { + case 0: + entry.samplerate = 24000; + break; + + case 1: + entry.samplerate = 22050; + break; + + case 2: + entry.samplerate = 16000; + break; + + case 3: + entry.samplerate = 0; + break; + } + break; + } + + } + if (entry.samplerate == 0) { + return null; + } + + entry.bitrate = (int) (((double) entry.samplerate) / 1536.0 * entry.frameSize * 8); + + return entry; + } + + private boolean readSamples() throws IOException { + int read = frameSize; + boolean ret = false; + while (frameSize == read) { + ret = true; + byte[] data = new byte[frameSize]; + read = inputStream.read(data); + if (read == frameSize) { + samples.add(ByteBuffer.wrap(data)); + stts.add(new TimeToSampleBox.Entry(1, 1536)); + } + } + return ret; + } + + public static class BitStreamInfo extends EC3SpecificBox.Entry { + public int frameSize; + public int substreamid; + public int bitrate; + public int samplerate; + public int strmtyp; + public int chanmap; + + @Override + public String toString() { + return "BitStreamInfo{" + + "frameSize=" + frameSize + + ", substreamid=" + substreamid + + ", bitrate=" + bitrate + + ", samplerate=" + samplerate + + ", strmtyp=" + strmtyp + + ", chanmap=" + chanmap + + '}'; + } + } + + @Override + public String toString() { + return "EC3TrackImpl{" + + "bitrate=" + bitrate + + ", samplerate=" + samplerate + + ", entries=" + entries + + '}'; + } +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base new file mode 100644 index 0000000..b3c1866 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/H264TrackImpl.java.svn-base @@ -0,0 +1,740 @@ +package com.googlecode.mp4parser.authoring.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.h264.AvcConfigurationBox; +import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.h264.model.PictureParameterSet; +import com.googlecode.mp4parser.h264.model.SeqParameterSet; +import com.googlecode.mp4parser.h264.read.CAVLCReader; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Logger; + +/** + * The <code>H264TrackImpl</code> creates a <code>Track</code> from an H.264 + * Annex B file. + */ +public class H264TrackImpl extends AbstractTrack { + private static final Logger LOG = Logger.getLogger(H264TrackImpl.class.getName()); + + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + + private ReaderWrapper reader; + private List<ByteBuffer> samples; + boolean readSamples = false; + + List<TimeToSampleBox.Entry> stts; + List<CompositionTimeToSample.Entry> ctts; + List<SampleDependencyTypeBox.Entry> sdtp; + List<Integer> stss; + + SeqParameterSet seqParameterSet = null; + PictureParameterSet pictureParameterSet = null; + LinkedList<byte[]> seqParameterSetList = new LinkedList<byte[]>(); + LinkedList<byte[]> pictureParameterSetList = new LinkedList<byte[]>(); + + private int width; + private int height; + private int timescale; + private int frametick; + private int currentScSize; + private int prevScSize; + + private SEIMessage seiMessage; + int frameNrInGop = 0; + private boolean determineFrameRate = true; + private String lang = "und"; + + public H264TrackImpl(InputStream inputStream, String lang, long timescale) throws IOException { + this.lang = lang; + if (timescale > 1000) { + timescale = timescale; //e.g. 23976 + frametick = 1000; + determineFrameRate = false; + } else { + throw new IllegalArgumentException("Timescale must be specified in milliseconds!"); + } + parse(inputStream); + } + + public H264TrackImpl(InputStream inputStream, String lang) throws IOException { + this.lang = lang; + parse(inputStream); + } + + public H264TrackImpl(InputStream inputStream) throws IOException { + parse(inputStream); + } + + private void parse(InputStream inputStream) throws IOException { + this.reader = new ReaderWrapper(inputStream); + stts = new LinkedList<TimeToSampleBox.Entry>(); + ctts = new LinkedList<CompositionTimeToSample.Entry>(); + sdtp = new LinkedList<SampleDependencyTypeBox.Entry>(); + stss = new LinkedList<Integer>(); + + samples = new LinkedList<ByteBuffer>(); + if (!readSamples()) { + throw new IOException(); + } + + if (!readVariables()) { + throw new IOException(); + } + + sampleDescriptionBox = new SampleDescriptionBox(); + VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(width); + visualSampleEntry.setHeight(height); + visualSampleEntry.setCompressorname("AVC Coding"); + + AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox(); + + avcConfigurationBox.setSequenceParameterSets(seqParameterSetList); + avcConfigurationBox.setPictureParameterSets(pictureParameterSetList); + avcConfigurationBox.setAvcLevelIndication(seqParameterSet.level_idc); + avcConfigurationBox.setAvcProfileIndication(seqParameterSet.profile_idc); + avcConfigurationBox.setBitDepthLumaMinus8(seqParameterSet.bit_depth_luma_minus8); + avcConfigurationBox.setBitDepthChromaMinus8(seqParameterSet.bit_depth_chroma_minus8); + avcConfigurationBox.setChromaFormat(seqParameterSet.chroma_format_idc.getId()); + avcConfigurationBox.setConfigurationVersion(1); + avcConfigurationBox.setLengthSizeMinusOne(3); + avcConfigurationBox.setProfileCompatibility(seqParameterSetList.get(0)[1]); + + visualSampleEntry.addBox(avcConfigurationBox); + sampleDescriptionBox.addBox(visualSampleEntry); + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setLanguage(lang); + trackMetaData.setTimescale(timescale); + trackMetaData.setWidth(width); + trackMetaData.setHeight(height); + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return ctts; + } + + public long[] getSyncSamples() { + long[] returns = new long[stss.size()]; + for (int i = 0; i < stss.size(); i++) { + returns[i] = stss.get(i); + } + return returns; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return sdtp; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "vide"; + } + + public List<ByteBuffer> getSamples() { + return samples; + } + + public AbstractMediaHeaderBox getMediaHeaderBox() { + return new VideoMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + + private boolean readVariables() { + width = (seqParameterSet.pic_width_in_mbs_minus1 + 1) * 16; + int mult = 2; + if (seqParameterSet.frame_mbs_only_flag) { + mult = 1; + } + height = 16 * (seqParameterSet.pic_height_in_map_units_minus1 + 1) * mult; + if (seqParameterSet.frame_cropping_flag) { + int chromaArrayType = 0; + if (seqParameterSet.residual_color_transform_flag == false) { + chromaArrayType = seqParameterSet.chroma_format_idc.getId(); + } + int cropUnitX = 1; + int cropUnitY = mult; + if (chromaArrayType != 0) { + cropUnitX = seqParameterSet.chroma_format_idc.getSubWidth(); + cropUnitY = seqParameterSet.chroma_format_idc.getSubHeight() * mult; + } + + width -= cropUnitX * (seqParameterSet.frame_crop_left_offset + seqParameterSet.frame_crop_right_offset); + height -= cropUnitY * (seqParameterSet.frame_crop_top_offset + seqParameterSet.frame_crop_bottom_offset); + } + return true; + } + + private boolean findNextStartcode() throws IOException { + byte[] test = new byte[]{-1, -1, -1, -1}; + + int c; + while ((c = reader.read()) != -1) { + test[0] = test[1]; + test[1] = test[2]; + test[2] = test[3]; + test[3] = (byte) c; + if (test[0] == 0 && test[1] == 0 && test[2] == 0 && test[3] == 1) { + prevScSize = currentScSize; + currentScSize = 4; + return true; + } + if (test[0] == 0 && test[1] == 0 && test[2] == 1) { + prevScSize = currentScSize; + currentScSize = 3; + return true; + } + } + return false; + } + + private enum NALActions { + IGNORE, BUFFER, STORE, END + } + + private boolean readSamples() throws IOException { + if (readSamples) { + return true; + } + + readSamples = true; + + + findNextStartcode(); + reader.mark(); + long pos = reader.getPos(); + + ArrayList<byte[]> buffered = new ArrayList<byte[]>(); + + int frameNr = 0; + + while (findNextStartcode()) { + long newpos = reader.getPos(); + int size = (int) (newpos - pos - prevScSize); + reader.reset(); + byte[] data = new byte[size ]; + reader.read(data); + int type = data[0]; + int nal_ref_idc = (type >> 5) & 3; + int nal_unit_type = type & 0x1f; + LOG.fine("Found startcode at " + (pos -4) + " Type: " + nal_unit_type + " ref idc: " + nal_ref_idc + " (size " + size + ")"); + NALActions action = handleNALUnit(nal_ref_idc, nal_unit_type, data); + switch (action) { + case IGNORE: + break; + + case BUFFER: + buffered.add(data); + break; + + case STORE: + int stdpValue = 22; + frameNr++; + buffered.add(data); + ByteBuffer bb = createSample(buffered); + boolean IdrPicFlag = false; + if (nal_unit_type == 5) { + stdpValue += 16; + IdrPicFlag = true; + } + ByteArrayInputStream bs = cleanBuffer(buffered.get(buffered.size() - 1)); + SliceHeader sh = new SliceHeader(bs, seqParameterSet, pictureParameterSet, IdrPicFlag); + if (sh.slice_type == SliceHeader.SliceType.B) { + stdpValue += 4; + } + LOG.fine("Adding sample with size " + bb.capacity() + " and header " + sh); + buffered.clear(); + samples.add(bb); + stts.add(new TimeToSampleBox.Entry(1, frametick)); + if (nal_unit_type == 5) { // IDR Picture + stss.add(frameNr); + } + if (seiMessage.n_frames == 0) { + frameNrInGop = 0; + } + int offset = 0; + if (seiMessage.clock_timestamp_flag) { + offset = seiMessage.n_frames - frameNrInGop; + } else if (seiMessage.removal_delay_flag) { + offset = seiMessage.dpb_removal_delay / 2; + } + ctts.add(new CompositionTimeToSample.Entry(1, offset * frametick)); + sdtp.add(new SampleDependencyTypeBox.Entry(stdpValue)); + frameNrInGop++; + break; + + case END: + return true; + + + } + pos = newpos; + reader.seek(currentScSize); + reader.mark(); + } + return true; + } + + private ByteBuffer createSample(List<byte[]> buffers) { + int outsize = 0; + for (int i = 0; i < buffers.size(); i++) { + outsize += buffers.get(i).length + 4; + } + byte[] output = new byte[outsize]; + + ByteBuffer bb = ByteBuffer.wrap(output); + for (int i = 0; i < buffers.size(); i++) { + bb.putInt(buffers.get(i).length); + bb.put(buffers.get(i)); + } + bb.rewind(); + return bb; + } + + private ByteArrayInputStream cleanBuffer(byte[] data) { + byte[] output = new byte[data.length]; + int inPos = 0; + int outPos = 0; + while (inPos < data.length) { + if (data[inPos] == 0 && data[inPos + 1] == 0 && data[inPos + 2] == 3) { + output[outPos] = 0; + output[outPos + 1] = 0; + inPos += 3; + outPos += 2; + } else { + output[outPos] = data[inPos]; + inPos++; + outPos++; + } + } + return new ByteArrayInputStream(output, 0, outPos); + } + + private NALActions handleNALUnit(int nal_ref_idc, int nal_unit_type, byte[] data) throws IOException { + NALActions action; + switch (nal_unit_type) { + case 1: + case 2: + case 3: + case 4: + case 5: + action = NALActions.STORE; // Will only work in single slice per frame mode! + break; + + case 6: + seiMessage = new SEIMessage(cleanBuffer(data), seqParameterSet); + action = NALActions.BUFFER; + break; + + case 9: +// printAccessUnitDelimiter(data); + int type = data[1] >> 5; + LOG.fine("Access unit delimiter type: " + type); + action = NALActions.BUFFER; + break; + + + case 7: + if (seqParameterSet == null) { + ByteArrayInputStream is = cleanBuffer(data); + is.read(); + seqParameterSet = SeqParameterSet.read(is); + seqParameterSetList.add(data); + configureFramerate(); + } + action = NALActions.IGNORE; + break; + + case 8: + if (pictureParameterSet == null) { + ByteArrayInputStream is = new ByteArrayInputStream(data); + is.read(); + pictureParameterSet = PictureParameterSet.read(is); + pictureParameterSetList.add(data); + } + action = NALActions.IGNORE; + break; + + case 10: + case 11: + action = NALActions.END; + break; + + default: + System.err.println("Unknown NAL unit type: " + nal_unit_type); + action = NALActions.IGNORE; + + } + + return action; + } + + private void configureFramerate() { + if (determineFrameRate) { + if (seqParameterSet.vuiParams != null) { + timescale = seqParameterSet.vuiParams.time_scale >> 1; // Not sure why, but I found this in several places, and it works... + frametick = seqParameterSet.vuiParams.num_units_in_tick; + if (timescale == 0 || frametick == 0) { + System.err.println("Warning: vuiParams contain invalid values: time_scale: " + timescale + " and frame_tick: " + frametick + ". Setting frame rate to 25fps"); + timescale = 90000; + frametick = 3600; + } + } else { + System.err.println("Warning: Can't determine frame rate. Guessing 25 fps"); + timescale = 90000; + frametick = 3600; + } + } + } + + public void printAccessUnitDelimiter(byte[] data) { + LOG.fine("Access unit delimiter: " + (data[1] >> 5)); + } + + public static class SliceHeader { + + public enum SliceType { + P, B, I, SP, SI + } + + public int first_mb_in_slice; + public SliceType slice_type; + public int pic_parameter_set_id; + public int colour_plane_id; + public int frame_num; + public boolean field_pic_flag = false; + public boolean bottom_field_flag = false; + public int idr_pic_id; + public int pic_order_cnt_lsb; + public int delta_pic_order_cnt_bottom; + + public SliceHeader(InputStream is, SeqParameterSet sps, PictureParameterSet pps, boolean IdrPicFlag) throws IOException { + is.read(); + CAVLCReader reader = new CAVLCReader(is); + first_mb_in_slice = reader.readUE("SliceHeader: first_mb_in_slice"); + switch (reader.readUE("SliceHeader: slice_type")) { + case 0: + case 5: + slice_type = SliceType.P; + break; + + case 1: + case 6: + slice_type = SliceType.B; + break; + + case 2: + case 7: + slice_type = SliceType.I; + break; + + case 3: + case 8: + slice_type = SliceType.SP; + break; + + case 4: + case 9: + slice_type = SliceType.SI; + break; + + } + pic_parameter_set_id = reader.readUE("SliceHeader: pic_parameter_set_id"); + if (sps.residual_color_transform_flag) { + colour_plane_id = reader.readU(2, "SliceHeader: colour_plane_id"); + } + frame_num = reader.readU(sps.log2_max_frame_num_minus4 + 4, "SliceHeader: frame_num"); + + if (!sps.frame_mbs_only_flag) { + field_pic_flag = reader.readBool("SliceHeader: field_pic_flag"); + if (field_pic_flag) { + bottom_field_flag = reader.readBool("SliceHeader: bottom_field_flag"); + } + } + if (IdrPicFlag) { + idr_pic_id = reader.readUE("SliceHeader: idr_pic_id"); + if (sps.pic_order_cnt_type == 0) { + pic_order_cnt_lsb = reader.readU(sps.log2_max_pic_order_cnt_lsb_minus4 + 4, "SliceHeader: pic_order_cnt_lsb"); + if (pps.pic_order_present_flag && !field_pic_flag) { + delta_pic_order_cnt_bottom = reader.readSE("SliceHeader: delta_pic_order_cnt_bottom"); + } + } + } + } + + @Override + public String toString() { + return "SliceHeader{" + + "first_mb_in_slice=" + first_mb_in_slice + + ", slice_type=" + slice_type + + ", pic_parameter_set_id=" + pic_parameter_set_id + + ", colour_plane_id=" + colour_plane_id + + ", frame_num=" + frame_num + + ", field_pic_flag=" + field_pic_flag + + ", bottom_field_flag=" + bottom_field_flag + + ", idr_pic_id=" + idr_pic_id + + ", pic_order_cnt_lsb=" + pic_order_cnt_lsb + + ", delta_pic_order_cnt_bottom=" + delta_pic_order_cnt_bottom + + '}'; + } + } + + private class ReaderWrapper { + private InputStream inputStream; + private long pos = 0; + + private long markPos = 0; + + + private ReaderWrapper(InputStream inputStream) { + this.inputStream = inputStream; + } + + int read() throws IOException { + pos++; + return inputStream.read(); + } + + long read(byte[] data) throws IOException { + long read = inputStream.read(data); + pos += read; + return read; + } + + long seek(int dist) throws IOException { + long seeked = inputStream.skip(dist); + pos += seeked; + return seeked; + } + + public long getPos() { + return pos; + } + + public void mark() { + int i = 1048576; + LOG.fine("Marking with " + i + " at " + pos); + inputStream.mark(i); + markPos = pos; + } + + + public void reset() throws IOException { + long diff = pos - markPos; + LOG.fine("Resetting to " + markPos + " (pos is " + pos + ") which makes the buffersize " + diff); + inputStream.reset(); + pos = markPos; + } + } + + public class SEIMessage { + + int payloadType = 0; + int payloadSize = 0; + + boolean removal_delay_flag; + int cpb_removal_delay; + int dpb_removal_delay; + + boolean clock_timestamp_flag; + int pic_struct; + int ct_type; + int nuit_field_based_flag; + int counting_type; + int full_timestamp_flag; + int discontinuity_flag; + int cnt_dropped_flag; + int n_frames; + int seconds_value; + int minutes_value; + int hours_value; + int time_offset_length; + int time_offset; + + SeqParameterSet sps; + + public SEIMessage(InputStream is, SeqParameterSet sps) throws IOException { + this.sps = sps; + is.read(); + int datasize = is.available(); + int read = 0; + while (read < datasize) { + payloadType = 0; + payloadSize = 0; + int last_payload_type_bytes = is.read(); + read++; + while (last_payload_type_bytes == 0xff) { + payloadType += last_payload_type_bytes; + last_payload_type_bytes = is.read(); + read++; + } + payloadType += last_payload_type_bytes; + int last_payload_size_bytes = is.read(); + read++; + + while (last_payload_size_bytes == 0xff) { + payloadSize += last_payload_size_bytes; + last_payload_size_bytes = is.read(); + read++; + } + payloadSize += last_payload_size_bytes; + if (datasize - read >= payloadSize) { + if (payloadType == 1) { // pic_timing is what we are interested in! + if (sps.vuiParams != null && (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null || sps.vuiParams.pic_struct_present_flag)) { + byte[] data = new byte[payloadSize]; + is.read(data); + read += payloadSize; + CAVLCReader reader = new CAVLCReader(new ByteArrayInputStream(data)); + if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) { + removal_delay_flag = true; + cpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.cpb_removal_delay_length_minus1 + 1, "SEI: cpb_removal_delay"); + dpb_removal_delay = reader.readU(sps.vuiParams.nalHRDParams.dpb_output_delay_length_minus1 + 1, "SEI: dpb_removal_delay"); + } else { + removal_delay_flag = false; + } + if (sps.vuiParams.pic_struct_present_flag) { + pic_struct = reader.readU(4, "SEI: pic_struct"); + int numClockTS; + switch (pic_struct) { + case 0: + case 1: + case 2: + default: + numClockTS = 1; + break; + + case 3: + case 4: + case 7: + numClockTS = 2; + break; + + case 5: + case 6: + case 8: + numClockTS = 3; + break; + } + for (int i = 0; i < numClockTS; i++) { + clock_timestamp_flag = reader.readBool("pic_timing SEI: clock_timestamp_flag[" + i + "]"); + if (clock_timestamp_flag) { + ct_type = reader.readU(2, "pic_timing SEI: ct_type"); + nuit_field_based_flag = reader.readU(1, "pic_timing SEI: nuit_field_based_flag"); + counting_type = reader.readU(5, "pic_timing SEI: counting_type"); + full_timestamp_flag = reader.readU(1, "pic_timing SEI: full_timestamp_flag"); + discontinuity_flag = reader.readU(1, "pic_timing SEI: discontinuity_flag"); + cnt_dropped_flag = reader.readU(1, "pic_timing SEI: cnt_dropped_flag"); + n_frames = reader.readU(8, "pic_timing SEI: n_frames"); + if (full_timestamp_flag == 1) { + seconds_value = reader.readU(6, "pic_timing SEI: seconds_value"); + minutes_value = reader.readU(6, "pic_timing SEI: minutes_value"); + hours_value = reader.readU(5, "pic_timing SEI: hours_value"); + } else { + if (reader.readBool("pic_timing SEI: seconds_flag")) { + seconds_value = reader.readU(6, "pic_timing SEI: seconds_value"); + if (reader.readBool("pic_timing SEI: minutes_flag")) { + minutes_value = reader.readU(6, "pic_timing SEI: minutes_value"); + if (reader.readBool("pic_timing SEI: hours_flag")) { + hours_value = reader.readU(5, "pic_timing SEI: hours_value"); + } + } + } + } + if (true) { + if (sps.vuiParams.nalHRDParams != null) { + time_offset_length = sps.vuiParams.nalHRDParams.time_offset_length; + } else if (sps.vuiParams.vclHRDParams != null) { + time_offset_length = sps.vuiParams.vclHRDParams.time_offset_length; + } else { + time_offset_length = 24; + } + time_offset = reader.readU(24, "pic_timing SEI: time_offset"); + } + } + } + } + + } else { + for (int i = 0; i < payloadSize; i++) { + is.read(); + read++; + } + } + } else { + for (int i = 0; i < payloadSize; i++) { + is.read(); + read++; + } + } + } else { + read = datasize; + } + LOG.fine(this.toString()); + } + } + + @Override + public String toString() { + String out = "SEIMessage{" + + "payloadType=" + payloadType + + ", payloadSize=" + payloadSize; + if (payloadType == 1) { + if (sps.vuiParams.nalHRDParams != null || sps.vuiParams.vclHRDParams != null) { + + out += ", cpb_removal_delay=" + cpb_removal_delay + + ", dpb_removal_delay=" + dpb_removal_delay; + } + if (sps.vuiParams.pic_struct_present_flag) { + out += ", pic_struct=" + pic_struct; + if (clock_timestamp_flag) { + out += ", ct_type=" + ct_type + + ", nuit_field_based_flag=" + nuit_field_based_flag + + ", counting_type=" + counting_type + + ", full_timestamp_flag=" + full_timestamp_flag + + ", discontinuity_flag=" + discontinuity_flag + + ", cnt_dropped_flag=" + cnt_dropped_flag + + ", n_frames=" + n_frames + + ", seconds_value=" + seconds_value + + ", minutes_value=" + minutes_value + + ", hours_value=" + hours_value + + ", time_offset_length=" + time_offset_length + + ", time_offset=" + time_offset; + } + } + } + out += '}'; + return out; + } + } +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base new file mode 100644 index 0000000..e9a90e4 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/MultiplyTimeScaleTrack.java.svn-base @@ -0,0 +1,130 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.Movie; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import static com.googlecode.mp4parser.util.CastUtils.l2i; +import static com.googlecode.mp4parser.util.Math.gcd; +import static com.googlecode.mp4parser.util.Math.lcm; +import static java.lang.Math.round; + +/** + * Changes the timescale of a track by wrapping the track. + */ +public class MultiplyTimeScaleTrack implements Track { + Track source; + private int timeScaleFactor; + + public MultiplyTimeScaleTrack(Track source, int timeScaleFactor) { + this.source = source; + this.timeScaleFactor = timeScaleFactor; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return source.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return adjustTts(source.getDecodingTimeEntries(), timeScaleFactor); + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return adjustCtts(source.getCompositionTimeEntries(), timeScaleFactor); + } + + public long[] getSyncSamples() { + return source.getSyncSamples(); + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return source.getSampleDependencies(); + } + + public TrackMetaData getTrackMetaData() { + TrackMetaData trackMetaData = (TrackMetaData) source.getTrackMetaData().clone(); + trackMetaData.setTimescale(source.getTrackMetaData().getTimescale() * this.timeScaleFactor); + return trackMetaData; + } + + public String getHandler() { + return source.getHandler(); + } + + public boolean isEnabled() { + return source.isEnabled(); + } + + public boolean isInMovie() { + return source.isInMovie(); + } + + public boolean isInPreview() { + return source.isInPreview(); + } + + public boolean isInPoster() { + return source.isInPoster(); + } + + public List<ByteBuffer> getSamples() { + return source.getSamples(); + } + + + static List<CompositionTimeToSample.Entry> adjustCtts(List<CompositionTimeToSample.Entry> source, int timeScaleFactor) { + if (source != null) { + List<CompositionTimeToSample.Entry> entries2 = new ArrayList<CompositionTimeToSample.Entry>(source.size()); + for (CompositionTimeToSample.Entry entry : source) { + entries2.add(new CompositionTimeToSample.Entry(entry.getCount(), timeScaleFactor * entry.getOffset())); + } + return entries2; + } else { + return null; + } + } + + static List<TimeToSampleBox.Entry> adjustTts(List<TimeToSampleBox.Entry> source, int timeScaleFactor) { + LinkedList<TimeToSampleBox.Entry> entries2 = new LinkedList<TimeToSampleBox.Entry>(); + for (TimeToSampleBox.Entry e : source) { + entries2.add(new TimeToSampleBox.Entry(e.getCount(), timeScaleFactor * e.getDelta())); + } + return entries2; + } + + public Box getMediaHeaderBox() { + return source.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return source.getSubsampleInformationBox(); + } + + @Override + public String toString() { + return "MultiplyTimeScaleTrack{" + + "source=" + source + + '}'; + } +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base new file mode 100644 index 0000000..8efa399 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/QuicktimeTextTrackImpl.java.svn-base @@ -0,0 +1,165 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.TextSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.apple.BaseMediaInfoAtom; +import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderAtom; +import com.googlecode.mp4parser.boxes.apple.GenericMediaHeaderTextAtom; +import com.googlecode.mp4parser.boxes.apple.QuicktimeTextSampleEntry; +import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * A Text track as Quicktime Pro would create. + */ +public class QuicktimeTextTrackImpl extends AbstractTrack { + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + List<Line> subs = new LinkedList<Line>(); + + public List<Line> getSubs() { + return subs; + } + + public QuicktimeTextTrackImpl() { + sampleDescriptionBox = new SampleDescriptionBox(); + QuicktimeTextSampleEntry textTrack = new QuicktimeTextSampleEntry(); + textTrack.setDataReferenceIndex(1); + sampleDescriptionBox.addBox(textTrack); + + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setTimescale(1000); + + + } + + + public List<ByteBuffer> getSamples() { + List<ByteBuffer> samples = new LinkedList<ByteBuffer>(); + long lastEnd = 0; + for (Line sub : subs) { + long silentTime = sub.from - lastEnd; + if (silentTime > 0) { + samples.add(ByteBuffer.wrap(new byte[]{0, 0})); + } else if (silentTime < 0) { + throw new Error("Subtitle display times may not intersect"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + try { + dos.writeShort(sub.text.getBytes("UTF-8").length); + dos.write(sub.text.getBytes("UTF-8")); + dos.close(); + } catch (IOException e) { + throw new Error("VM is broken. Does not support UTF-8"); + } + samples.add(ByteBuffer.wrap(baos.toByteArray())); + lastEnd = sub.to; + } + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>(); + long lastEnd = 0; + for (Line sub : subs) { + long silentTime = sub.from - lastEnd; + if (silentTime > 0) { + stts.add(new TimeToSampleBox.Entry(1, silentTime)); + } else if (silentTime < 0) { + throw new Error("Subtitle display times may not intersect"); + } + stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from)); + lastEnd = sub.to; + } + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "text"; + } + + + public static class Line { + long from; + long to; + String text; + + + public Line(long from, long to, String text) { + this.from = from; + this.to = to; + this.text = text; + } + + public long getFrom() { + return from; + } + + public String getText() { + return text; + } + + public long getTo() { + return to; + } + } + + public Box getMediaHeaderBox() { + GenericMediaHeaderAtom ghmd = new GenericMediaHeaderAtom(); + ghmd.addBox(new BaseMediaInfoAtom()); + ghmd.addBox(new GenericMediaHeaderTextAtom()); + return ghmd; + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base new file mode 100644 index 0000000..81a129d --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/ReplaceSampleTrack.java.svn-base @@ -0,0 +1,104 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.AbstractList; +import java.util.LinkedList; +import java.util.List; + +/** + * Generates a Track where a single sample has been replaced by a given <code>ByteBuffer</code>. + */ + +public class ReplaceSampleTrack extends AbstractTrack { + Track origTrack; + private long sampleNumber; + private ByteBuffer sampleContent; + private List<ByteBuffer> samples; + + public ReplaceSampleTrack(Track origTrack, long sampleNumber, ByteBuffer content) { + this.origTrack = origTrack; + this.sampleNumber = sampleNumber; + this.sampleContent = content; + this.samples = new ReplaceASingleEntryList(); + + } + + public List<ByteBuffer> getSamples() { + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return origTrack.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return origTrack.getDecodingTimeEntries(); + + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return origTrack.getCompositionTimeEntries(); + + } + + synchronized public long[] getSyncSamples() { + return origTrack.getSyncSamples(); + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return origTrack.getSampleDependencies(); + } + + public TrackMetaData getTrackMetaData() { + return origTrack.getTrackMetaData(); + } + + public String getHandler() { + return origTrack.getHandler(); + } + + public Box getMediaHeaderBox() { + return origTrack.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return origTrack.getSubsampleInformationBox(); + } + + private class ReplaceASingleEntryList extends AbstractList<ByteBuffer> { + @Override + public ByteBuffer get(int index) { + if (ReplaceSampleTrack.this.sampleNumber == index) { + return ReplaceSampleTrack.this.sampleContent; + } else { + return ReplaceSampleTrack.this.origTrack.getSamples().get(index); + } + } + + @Override + public int size() { + return ReplaceSampleTrack.this.origTrack.getSamples().size(); + } + } + +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base new file mode 100644 index 0000000..f74ab3c --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/SilenceTrackImpl.java.svn-base @@ -0,0 +1,98 @@ +package com.googlecode.mp4parser.authoring.tracks; + +import com.coremedia.iso.boxes.*; +import com.googlecode.mp4parser.authoring.Mp4TrackImpl; +import com.googlecode.mp4parser.authoring.Track; +import com.googlecode.mp4parser.authoring.TrackMetaData; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * This is just a basic idea how things could work but they don't. + */ +public class SilenceTrackImpl implements Track { + Track source; + + List<ByteBuffer> samples = new LinkedList<ByteBuffer>(); + TimeToSampleBox.Entry entry; + + public SilenceTrackImpl(Track ofType, long ms) { + source = ofType; + if ("mp4a".equals(ofType.getSampleDescriptionBox().getSampleEntry().getType())) { + long numFrames = getTrackMetaData().getTimescale() * ms / 1000 / 1024; + long standZeit = getTrackMetaData().getTimescale() * ms / numFrames / 1000; + entry = new TimeToSampleBox.Entry(numFrames, standZeit); + + + while (numFrames-- > 0) { + samples.add((ByteBuffer) ByteBuffer.wrap(new byte[]{ + 0x21, 0x10, 0x04, 0x60, (byte) 0x8c, 0x1c, + }).rewind()); + } + + } else { + throw new RuntimeException("Tracks of type " + ofType.getClass().getSimpleName() + " are not supported"); + } + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return source.getSampleDescriptionBox(); + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + return Collections.singletonList(entry); + + } + + public TrackMetaData getTrackMetaData() { + return source.getTrackMetaData(); + } + + public String getHandler() { + return source.getHandler(); + } + + public boolean isEnabled() { + return source.isEnabled(); + } + + public boolean isInMovie() { + return source.isInMovie(); + } + + public boolean isInPreview() { + return source.isInPreview(); + } + + public boolean isInPoster() { + return source.isInPoster(); + } + + public List<ByteBuffer> getSamples() { + return samples; + } + + public Box getMediaHeaderBox() { + return source.getMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + +} diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base new file mode 100644 index 0000000..3bae143 --- /dev/null +++ b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/tracks/.svn/text-base/TextTrackImpl.java.svn-base @@ -0,0 +1,165 @@ +/* + * 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.tracks; + +import com.coremedia.iso.boxes.*; +import com.coremedia.iso.boxes.sampleentry.TextSampleEntry; +import com.googlecode.mp4parser.authoring.AbstractTrack; +import com.googlecode.mp4parser.authoring.TrackMetaData; +import com.googlecode.mp4parser.boxes.threegpp26245.FontTableBox; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; + +/** + * + */ +public class TextTrackImpl extends AbstractTrack { + TrackMetaData trackMetaData = new TrackMetaData(); + SampleDescriptionBox sampleDescriptionBox; + List<Line> subs = new LinkedList<Line>(); + + public List<Line> getSubs() { + return subs; + } + + public TextTrackImpl() { + sampleDescriptionBox = new SampleDescriptionBox(); + TextSampleEntry tx3g = new TextSampleEntry("tx3g"); + tx3g.setDataReferenceIndex(1); + tx3g.setStyleRecord(new TextSampleEntry.StyleRecord()); + tx3g.setBoxRecord(new TextSampleEntry.BoxRecord()); + sampleDescriptionBox.addBox(tx3g); + + FontTableBox ftab = new FontTableBox(); + ftab.setEntries(Collections.singletonList(new FontTableBox.FontRecord(1, "Serif"))); + + tx3g.addBox(ftab); + + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setTimescale(1000); // Text tracks use millieseconds + + + } + + + public List<ByteBuffer> getSamples() { + List<ByteBuffer> samples = new LinkedList<ByteBuffer>(); + long lastEnd = 0; + for (Line sub : subs) { + long silentTime = sub.from - lastEnd; + if (silentTime > 0) { + samples.add(ByteBuffer.wrap(new byte[]{0, 0})); + } else if (silentTime < 0) { + throw new Error("Subtitle display times may not intersect"); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos); + try { + dos.writeShort(sub.text.getBytes("UTF-8").length); + dos.write(sub.text.getBytes("UTF-8")); + dos.close(); + } catch (IOException e) { + throw new Error("VM is broken. Does not support UTF-8"); + } + samples.add(ByteBuffer.wrap(baos.toByteArray())); + lastEnd = sub.to; + } + return samples; + } + + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } + + public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { + List<TimeToSampleBox.Entry> stts = new LinkedList<TimeToSampleBox.Entry>(); + long lastEnd = 0; + for (Line sub : subs) { + long silentTime = sub.from - lastEnd; + if (silentTime > 0) { + stts.add(new TimeToSampleBox.Entry(1, silentTime)); + } else if (silentTime < 0) { + throw new Error("Subtitle display times may not intersect"); + } + stts.add(new TimeToSampleBox.Entry(1, sub.to - sub.from)); + lastEnd = sub.to; + } + return stts; + } + + public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { + return null; + } + + public long[] getSyncSamples() { + return null; + } + + public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { + return null; + } + + public TrackMetaData getTrackMetaData() { + return trackMetaData; + } + + public String getHandler() { + return "sbtl"; + } + + + public static class Line { + long from; + long to; + String text; + + + public Line(long from, long to, String text) { + this.from = from; + this.to = to; + this.text = text; + } + + public long getFrom() { + return from; + } + + public String getText() { + return text; + } + + public long getTo() { + return to; + } + } + + public AbstractMediaHeaderBox getMediaHeaderBox() { + return new NullMediaHeaderBox(); + } + + public SubSampleInformationBox getSubsampleInformationBox() { + return null; + } +} |