1/*
2 * Copyright 2012 castLabs GmbH, Berlin
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.googlecode.mp4parser.authoring.tracks;
17
18import com.coremedia.iso.boxes.*;
19import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
20import com.googlecode.mp4parser.authoring.AbstractTrack;
21import com.googlecode.mp4parser.authoring.TrackMetaData;
22import com.googlecode.mp4parser.boxes.AC3SpecificBox;
23import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
24import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.*;
25
26import java.io.BufferedInputStream;
27import java.io.IOException;
28import java.io.InputStream;
29import java.nio.ByteBuffer;
30import java.util.*;
31
32/**
33 */
34public class AACTrackImpl extends AbstractTrack {
35    public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
36
37    static {
38        samplingFrequencyIndexMap.put(96000, 0);
39        samplingFrequencyIndexMap.put(88200, 1);
40        samplingFrequencyIndexMap.put(64000, 2);
41        samplingFrequencyIndexMap.put(48000, 3);
42        samplingFrequencyIndexMap.put(44100, 4);
43        samplingFrequencyIndexMap.put(32000, 5);
44        samplingFrequencyIndexMap.put(24000, 6);
45        samplingFrequencyIndexMap.put(22050, 7);
46        samplingFrequencyIndexMap.put(16000, 8);
47        samplingFrequencyIndexMap.put(12000, 9);
48        samplingFrequencyIndexMap.put(11025, 10);
49        samplingFrequencyIndexMap.put(8000, 11);
50        samplingFrequencyIndexMap.put(0x0, 96000);
51        samplingFrequencyIndexMap.put(0x1, 88200);
52        samplingFrequencyIndexMap.put(0x2, 64000);
53        samplingFrequencyIndexMap.put(0x3, 48000);
54        samplingFrequencyIndexMap.put(0x4, 44100);
55        samplingFrequencyIndexMap.put(0x5, 32000);
56        samplingFrequencyIndexMap.put(0x6, 24000);
57        samplingFrequencyIndexMap.put(0x7, 22050);
58        samplingFrequencyIndexMap.put(0x8, 16000);
59        samplingFrequencyIndexMap.put(0x9, 12000);
60        samplingFrequencyIndexMap.put(0xa, 11025);
61        samplingFrequencyIndexMap.put(0xb, 8000);
62    }
63
64    TrackMetaData trackMetaData = new TrackMetaData();
65    SampleDescriptionBox sampleDescriptionBox;
66
67    int samplerate;
68    int bitrate;
69    int channelCount;
70    int channelconfig;
71
72    int bufferSizeDB;
73    long maxBitRate;
74    long avgBitRate;
75
76    private BufferedInputStream inputStream;
77    private List<ByteBuffer> samples;
78    boolean readSamples = false;
79    List<TimeToSampleBox.Entry> stts;
80    private String lang = "und";
81
82
83    public AACTrackImpl(InputStream inputStream, String lang) throws IOException {
84        this.lang = lang;
85        parse(inputStream);
86     }
87
88    public AACTrackImpl(InputStream inputStream) throws IOException {
89        parse(inputStream);
90     }
91
92    private void parse(InputStream inputStream) throws IOException {
93        this.inputStream = new BufferedInputStream(inputStream);
94        stts = new LinkedList<TimeToSampleBox.Entry>();
95
96        if (!readVariables()) {
97            throw new IOException();
98        }
99
100        samples = new LinkedList<ByteBuffer>();
101        if (!readSamples()) {
102            throw new IOException();
103        }
104
105        double packetsPerSecond = (double)samplerate / 1024.0;
106        double duration = samples.size() / packetsPerSecond;
107
108        long dataSize = 0;
109        LinkedList<Integer> queue = new LinkedList<Integer>();
110        for (int i = 0; i < samples.size(); i++) {
111            int size = samples.get(i).capacity();
112            dataSize += size;
113            queue.add(size);
114            while (queue.size() > packetsPerSecond) {
115                queue.pop();
116            }
117            if (queue.size() == (int) packetsPerSecond) {
118                int currSize = 0;
119                for (int j = 0 ; j < queue.size(); j++) {
120                    currSize += queue.get(j);
121                }
122                double currBitrate = 8.0 * currSize / queue.size() * packetsPerSecond;
123                if (currBitrate > maxBitRate) {
124                    maxBitRate = (int)currBitrate;
125                }
126            }
127        }
128
129        avgBitRate = (int) (8 * dataSize / duration);
130
131        bufferSizeDB = 1536; /* TODO: Calcultate this somehow! */
132
133        sampleDescriptionBox = new SampleDescriptionBox();
134        AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
135        audioSampleEntry.setChannelCount(2);
136        audioSampleEntry.setSampleRate(samplerate);
137        audioSampleEntry.setDataReferenceIndex(1);
138        audioSampleEntry.setSampleSize(16);
139
140
141        ESDescriptorBox esds = new ESDescriptorBox();
142        ESDescriptor descriptor = new ESDescriptor();
143        descriptor.setEsId(0);
144
145        SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor();
146        slConfigDescriptor.setPredefined(2);
147        descriptor.setSlConfigDescriptor(slConfigDescriptor);
148
149        DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor();
150        decoderConfigDescriptor.setObjectTypeIndication(0x40);
151        decoderConfigDescriptor.setStreamType(5);
152        decoderConfigDescriptor.setBufferSizeDB(bufferSizeDB);
153        decoderConfigDescriptor.setMaxBitRate(maxBitRate);
154        decoderConfigDescriptor.setAvgBitRate(avgBitRate);
155
156        AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
157        audioSpecificConfig.setAudioObjectType(2); // AAC LC
158        audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get(samplerate));
159        audioSpecificConfig.setChannelConfiguration(channelconfig);
160        decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);
161
162        descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);
163
164        ByteBuffer data = descriptor.serialize();
165        esds.setData(data);
166        audioSampleEntry.addBox(esds);
167        sampleDescriptionBox.addBox(audioSampleEntry);
168
169        trackMetaData.setCreationTime(new Date());
170        trackMetaData.setModificationTime(new Date());
171        trackMetaData.setLanguage(lang);
172        trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale
173    }
174
175    public SampleDescriptionBox getSampleDescriptionBox() {
176        return sampleDescriptionBox;
177    }
178
179    public List<TimeToSampleBox.Entry> getDecodingTimeEntries() {
180        return stts;
181    }
182
183    public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() {
184        return null;
185    }
186
187    public long[] getSyncSamples() {
188        return null;
189    }
190
191    public List<SampleDependencyTypeBox.Entry> getSampleDependencies() {
192        return null;
193    }
194
195    public TrackMetaData getTrackMetaData() {
196        return trackMetaData;
197    }
198
199    public String getHandler() {
200        return "soun";
201    }
202
203    public List<ByteBuffer> getSamples() {
204        return samples;
205    }
206
207    public Box getMediaHeaderBox() {
208        return new SoundMediaHeaderBox();
209    }
210
211    public SubSampleInformationBox getSubsampleInformationBox() {
212        return null;
213    }
214
215    private boolean readVariables() throws IOException {
216        byte[] data = new byte[100];
217        inputStream.mark(100);
218        if (100 != inputStream.read(data, 0, 100)) {
219            return false;
220        }
221        inputStream.reset(); // Rewind
222        ByteBuffer bb = ByteBuffer.wrap(data);
223        BitReaderBuffer brb = new BitReaderBuffer(bb);
224        int syncword = brb.readBits(12);
225        if (syncword != 0xfff) {
226            return false;
227        }
228        int id = brb.readBits(1);
229        int layer = brb.readBits(2);
230        int protectionAbsent = brb.readBits(1);
231        int profile = brb.readBits(2);
232        samplerate = samplingFrequencyIndexMap.get(brb.readBits(4));
233        brb.readBits(1);
234        channelconfig = brb.readBits(3);
235        int original = brb.readBits(1);
236        int home = brb.readBits(1);
237        int emphasis = brb.readBits(2);
238
239        return true;
240    }
241
242    private boolean readSamples() throws IOException {
243        if (readSamples) {
244            return true;
245        }
246
247        readSamples = true;
248        byte[] header = new byte[15];
249        boolean ret = false;
250        inputStream.mark(15);
251        while (-1 != inputStream.read(header)) {
252            ret = true;
253            ByteBuffer bb = ByteBuffer.wrap(header);
254            inputStream.reset();
255            BitReaderBuffer brb = new BitReaderBuffer(bb);
256            int syncword = brb.readBits(12);
257            if (syncword != 0xfff) {
258                return false;
259            }
260            brb.readBits(3);
261            int protectionAbsent = brb.readBits(1);
262            brb.readBits(14);
263            int frameSize = brb.readBits(13);
264            int bufferFullness = brb.readBits(11);
265            int noBlocks = brb.readBits(2);
266            int used = (int) Math.ceil(brb.getPosition() / 8.0);
267            if (protectionAbsent == 0) {
268                used += 2;
269            }
270            inputStream.skip(used);
271            frameSize -= used;
272//            System.out.println("Size: " + frameSize + " fullness: " + bufferFullness + " no blocks: " + noBlocks);
273            byte[] data = new byte[frameSize];
274            inputStream.read(data);
275            samples.add(ByteBuffer.wrap(data));
276            stts.add(new TimeToSampleBox.Entry(1, 1024));
277            inputStream.mark(15);
278        }
279        return ret;
280    }
281
282    @Override
283    public String toString() {
284        return "AACTrackImpl{" +
285                "samplerate=" + samplerate +
286                ", bitrate=" + bitrate +
287                ", channelCount=" + channelCount +
288                ", channelconfig=" + channelconfig +
289                '}';
290    }
291}
292
293