FlatManifestWriterImpl.java revision dd9eb897ee7c7b507cbdcf80263bb4b5de6966bf
1/*
2 * Copyright 2012 Sebastian Annies, Hamburg
3 *
4 * Licensed under the Apache License, Version 2.0 (the License);
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an AS IS BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.googlecode.mp4parser.authoring.adaptivestreaming;
17
18import com.coremedia.iso.Hex;
19import com.coremedia.iso.boxes.SampleDescriptionBox;
20import com.coremedia.iso.boxes.SoundMediaHeaderBox;
21import com.coremedia.iso.boxes.VideoMediaHeaderBox;
22import com.coremedia.iso.boxes.h264.AvcConfigurationBox;
23import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry;
24import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry;
25import com.googlecode.mp4parser.Version;
26import com.googlecode.mp4parser.authoring.Movie;
27import com.googlecode.mp4parser.authoring.Track;
28import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder;
29import com.googlecode.mp4parser.boxes.DTSSpecificBox;
30import com.googlecode.mp4parser.boxes.EC3SpecificBox;
31import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox;
32import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig;
33import org.w3c.dom.Document;
34import org.w3c.dom.Element;
35
36import javax.xml.parsers.DocumentBuilder;
37import javax.xml.parsers.DocumentBuilderFactory;
38import javax.xml.parsers.ParserConfigurationException;
39import javax.xml.transform.*;
40import javax.xml.transform.dom.DOMSource;
41import javax.xml.transform.stream.StreamResult;
42import java.io.ByteArrayOutputStream;
43import java.io.IOException;
44import java.io.StringWriter;
45import java.nio.ByteBuffer;
46import java.util.LinkedList;
47import java.util.List;
48import java.util.logging.Logger;
49
50public class FlatManifestWriterImpl extends AbstractManifestWriter {
51    private static final Logger LOG = Logger.getLogger(FlatManifestWriterImpl.class.getName());
52
53    protected FlatManifestWriterImpl(FragmentIntersectionFinder intersectionFinder) {
54        super(intersectionFinder);
55    }
56
57    /**
58     * Overwrite this method in subclasses to add your specialities.
59     *
60     * @param manifest the original manifest
61     * @return your customized version of the manifest
62     */
63    protected Document customizeManifest(Document manifest) {
64        return manifest;
65    }
66
67    public String getManifest(Movie movie) throws IOException {
68
69        LinkedList<VideoQuality> videoQualities = new LinkedList<VideoQuality>();
70        long videoTimescale = -1;
71
72        LinkedList<AudioQuality> audioQualities = new LinkedList<AudioQuality>();
73        long audioTimescale = -1;
74
75        for (Track track : movie.getTracks()) {
76            if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) {
77                videoFragmentsDurations = checkFragmentsAlign(videoFragmentsDurations, calculateFragmentDurations(track, movie));
78                SampleDescriptionBox stsd = track.getSampleDescriptionBox();
79                videoQualities.add(getVideoQuality(track, (VisualSampleEntry) stsd.getSampleEntry()));
80                if (videoTimescale == -1) {
81                    videoTimescale = track.getTrackMetaData().getTimescale();
82                } else {
83                    assert videoTimescale == track.getTrackMetaData().getTimescale();
84                }
85            }
86            if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) {
87                audioFragmentsDurations = checkFragmentsAlign(audioFragmentsDurations, calculateFragmentDurations(track, movie));
88                SampleDescriptionBox stsd = track.getSampleDescriptionBox();
89                audioQualities.add(getAudioQuality(track, (AudioSampleEntry) stsd.getSampleEntry()));
90                if (audioTimescale == -1) {
91                    audioTimescale = track.getTrackMetaData().getTimescale();
92                } else {
93                    assert audioTimescale == track.getTrackMetaData().getTimescale();
94                }
95
96            }
97        }
98        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
99        DocumentBuilder documentBuilder;
100        try {
101            documentBuilder = documentBuilderFactory.newDocumentBuilder();
102        } catch (ParserConfigurationException e) {
103            throw new IOException(e);
104        }
105        Document document = documentBuilder.newDocument();
106
107
108        Element smoothStreamingMedia = document.createElement("SmoothStreamingMedia");
109        document.appendChild(smoothStreamingMedia);
110        smoothStreamingMedia.setAttribute("MajorVersion", "2");
111        smoothStreamingMedia.setAttribute("MinorVersion", "1");
112// silverlight ignores the timescale attr        smoothStreamingMedia.addAttribute(new Attribute("TimeScale", Long.toString(movieTimeScale)));
113        smoothStreamingMedia.setAttribute("Duration", "0");
114
115        smoothStreamingMedia.appendChild(document.createComment(Version.VERSION));
116        Element videoStreamIndex = document.createElement("StreamIndex");
117        videoStreamIndex.setAttribute("Type", "video");
118        videoStreamIndex.setAttribute("TimeScale", Long.toString(videoTimescale)); // silverlight ignores the timescale attr
119        videoStreamIndex.setAttribute("Chunks", Integer.toString(videoFragmentsDurations.length));
120        videoStreamIndex.setAttribute("Url", "video/{bitrate}/{start time}");
121        videoStreamIndex.setAttribute("QualityLevels", Integer.toString(videoQualities.size()));
122        smoothStreamingMedia.appendChild(videoStreamIndex);
123
124        for (int i = 0; i < videoQualities.size(); i++) {
125            VideoQuality vq = videoQualities.get(i);
126            Element qualityLevel = document.createElement("QualityLevel");
127            qualityLevel.setAttribute("Index", Integer.toString(i));
128            qualityLevel.setAttribute("Bitrate", Long.toString(vq.bitrate));
129            qualityLevel.setAttribute("FourCC", vq.fourCC);
130            qualityLevel.setAttribute("MaxWidth", Long.toString(vq.width));
131            qualityLevel.setAttribute("MaxHeight", Long.toString(vq.height));
132            qualityLevel.setAttribute("CodecPrivateData", vq.codecPrivateData);
133            qualityLevel.setAttribute("NALUnitLengthField", Integer.toString(vq.nalLength));
134            videoStreamIndex.appendChild(qualityLevel);
135        }
136
137        for (int i = 0; i < videoFragmentsDurations.length; i++) {
138            Element c = document.createElement("c");
139            c.setAttribute("n", Integer.toString(i));
140            c.setAttribute("d", Long.toString(videoFragmentsDurations[i]));
141            videoStreamIndex.appendChild(c);
142        }
143
144        if (audioFragmentsDurations != null) {
145            Element audioStreamIndex = document.createElement("StreamIndex");
146            audioStreamIndex.setAttribute("Type", "audio");
147            audioStreamIndex.setAttribute("TimeScale", Long.toString(audioTimescale)); // silverlight ignores the timescale attr
148            audioStreamIndex.setAttribute("Chunks", Integer.toString(audioFragmentsDurations.length));
149            audioStreamIndex.setAttribute("Url", "audio/{bitrate}/{start time}");
150            audioStreamIndex.setAttribute("QualityLevels", Integer.toString(audioQualities.size()));
151            smoothStreamingMedia.appendChild(audioStreamIndex);
152
153            for (int i = 0; i < audioQualities.size(); i++) {
154                AudioQuality aq = audioQualities.get(i);
155                Element qualityLevel = document.createElement("QualityLevel");
156                qualityLevel.setAttribute("Index", Integer.toString(i));
157                qualityLevel.setAttribute("FourCC", aq.fourCC);
158                qualityLevel.setAttribute("Bitrate", Long.toString(aq.bitrate));
159                qualityLevel.setAttribute("AudioTag", Integer.toString(aq.audioTag));
160                qualityLevel.setAttribute("SamplingRate", Long.toString(aq.samplingRate));
161                qualityLevel.setAttribute("Channels", Integer.toString(aq.channels));
162                qualityLevel.setAttribute("BitsPerSample", Integer.toString(aq.bitPerSample));
163                qualityLevel.setAttribute("PacketSize", Integer.toString(aq.packetSize));
164                qualityLevel.setAttribute("CodecPrivateData", aq.codecPrivateData);
165                audioStreamIndex.appendChild(qualityLevel);
166            }
167            for (int i = 0; i < audioFragmentsDurations.length; i++) {
168                Element c = document.createElement("c");
169                c.setAttribute("n", Integer.toString(i));
170                c.setAttribute("d", Long.toString(audioFragmentsDurations[i]));
171                audioStreamIndex.appendChild(c);
172            }
173        }
174
175        document.setXmlStandalone(true);
176        Source source = new DOMSource(document);
177        StringWriter stringWriter = new StringWriter();
178        Result result = new StreamResult(stringWriter);
179        TransformerFactory factory = TransformerFactory.newInstance();
180        Transformer transformer;
181        try {
182            transformer = factory.newTransformer();
183            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
184            transformer.transform(source, result);
185        } catch (TransformerConfigurationException e) {
186            throw new IOException(e);
187        } catch (TransformerException e) {
188            throw new IOException(e);
189        }
190        return stringWriter.getBuffer().toString();
191
192
193    }
194
195    private AudioQuality getAudioQuality(Track track, AudioSampleEntry ase) {
196        if (getFormat(ase).equals("mp4a")) {
197            return getAacAudioQuality(track, ase);
198        } else if (getFormat(ase).equals("ec-3")) {
199            return getEc3AudioQuality(track, ase);
200        } else if (getFormat(ase).startsWith("dts")) {
201            return getDtsAudioQuality(track, ase);
202        } else {
203            throw new InternalError("I don't know what to do with audio of type " + getFormat(ase));
204        }
205
206    }
207
208    private AudioQuality getAacAudioQuality(Track track, AudioSampleEntry ase) {
209        AudioQuality l = new AudioQuality();
210        final ESDescriptorBox esDescriptorBox = ase.getBoxes(ESDescriptorBox.class).get(0);
211        final AudioSpecificConfig audioSpecificConfig = esDescriptorBox.getEsDescriptor().getDecoderConfigDescriptor().getAudioSpecificInfo();
212        if (audioSpecificConfig.getSbrPresentFlag() == 1) {
213            l.fourCC = "AACH";
214        } else if (audioSpecificConfig.getPsPresentFlag() == 1) {
215            l.fourCC = "AACP"; //I'm not sure if that's what MS considers as AAC+ - because actually AAC+ and AAC-HE should be the same...
216        } else {
217            l.fourCC = "AACL";
218        }
219        l.bitrate = getBitrate(track);
220        l.audioTag = 255;
221        l.samplingRate = ase.getSampleRate();
222        l.channels = ase.getChannelCount();
223        l.bitPerSample = ase.getSampleSize();
224        l.packetSize = 4;
225        l.codecPrivateData = getAudioCodecPrivateData(audioSpecificConfig);
226        //Index="0" Bitrate="103000" AudioTag="255" SamplingRate="44100" Channels="2" BitsPerSample="16" packetSize="4" CodecPrivateData=""
227        return l;
228    }
229
230    private AudioQuality getEc3AudioQuality(Track track, AudioSampleEntry ase) {
231        final EC3SpecificBox ec3SpecificBox = ase.getBoxes(EC3SpecificBox.class).get(0);
232        if (ec3SpecificBox == null) {
233            throw new RuntimeException("EC-3 track misses EC3SpecificBox!");
234        }
235
236        short nfchans = 0; //full bandwidth channels
237        short lfechans = 0;
238        byte dWChannelMaskFirstByte = 0;
239        byte dWChannelMaskSecondByte = 0;
240        for (EC3SpecificBox.Entry entry : ec3SpecificBox.getEntries()) {
241            /*
242            Table 4.3: Audio coding mode
243            acmod Audio coding mode Nfchans Channel array ordering
244            000 1 + 1 2 Ch1, Ch2
245            001 1/0 1 C
246            010 2/0 2 L, R
247            011 3/0 3 L, C, R
248            100 2/1 3 L, R, S
249            101 3/1 4 L, C, R, S
250            110 2/2 4 L, R, SL, SR
251            111 3/2 5 L, C, R, SL, SR
252
253            Table F.2: Chan_loc field bit assignments
254            Bit Location
255            0 Lc/Rc pair
256            1 Lrs/Rrs pair
257            2 Cs
258            3 Ts
259            4 Lsd/Rsd pair
260            5 Lw/Rw pair
261            6 Lvh/Rvh pair
262            7 Cvh
263            8 LFE2
264            */
265            switch (entry.acmod) {
266                case 0: //1+1; Ch1, Ch2
267                    nfchans += 2;
268                    throw new RuntimeException("Smooth Streaming doesn't support DDP 1+1 mode");
269                case 1: //1/0; C
270                    nfchans += 1;
271                    if (entry.num_dep_sub > 0) {
272                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
273                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
274                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
275                    } else {
276                        dWChannelMaskFirstByte |= 0x20;
277                    }
278                    break;
279                case 2: //2/0; L, R
280                    nfchans += 2;
281                    if (entry.num_dep_sub > 0) {
282                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
283                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
284                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
285                    } else {
286                        dWChannelMaskFirstByte |= 0xC0;
287                    }
288                    break;
289                case 3: //3/0; L, C, R
290                    nfchans += 3;
291                    if (entry.num_dep_sub > 0) {
292                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
293                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
294                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
295                    } else {
296                        dWChannelMaskFirstByte |= 0xE0;
297                    }
298                    break;
299                case 4: //2/1; L, R, S
300                    nfchans += 3;
301                    if (entry.num_dep_sub > 0) {
302                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
303                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
304                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
305                    } else {
306                        dWChannelMaskFirstByte |= 0xC0;
307                        dWChannelMaskSecondByte |= 0x80;
308                    }
309                    break;
310                case 5: //3/1; L, C, R, S
311                    nfchans += 4;
312                    if (entry.num_dep_sub > 0) {
313                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
314                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
315                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
316                    } else {
317                        dWChannelMaskFirstByte |= 0xE0;
318                        dWChannelMaskSecondByte |= 0x80;
319                    }
320                    break;
321                case 6: //2/2; L, R, SL, SR
322                    nfchans += 4;
323                    if (entry.num_dep_sub > 0) {
324                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
325                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
326                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
327                    } else {
328                        dWChannelMaskFirstByte |= 0xCC;
329                    }
330                    break;
331                case 7: //3/2; L, C, R, SL, SR
332                    nfchans += 5;
333                    if (entry.num_dep_sub > 0) {
334                        DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process();
335                        dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte();
336                        dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte();
337                    } else {
338                        dWChannelMaskFirstByte |= 0xEC;
339                    }
340                    break;
341            }
342            if (entry.lfeon == 1) {
343                lfechans ++;
344                dWChannelMaskFirstByte |= 0x10;
345            }
346        }
347
348        final ByteBuffer waveformatex = ByteBuffer.allocate(22);
349        waveformatex.put(new byte[]{0x00, 0x06}); //1536 wSamplesPerBlock - little endian
350        waveformatex.put(dWChannelMaskFirstByte);
351        waveformatex.put(dWChannelMaskSecondByte);
352        waveformatex.put(new byte[]{0x00, 0x00}); //pad dwChannelMask to 32bit
353        waveformatex.put(new byte[]{(byte)0xAF, (byte)0x87, (byte)0xFB, (byte)0xA7, 0x02, 0x2D, (byte)0xFB, 0x42, (byte)0xA4, (byte)0xD4, 0x05, (byte)0xCD, (byte)0x93, (byte)0x84, 0x3B, (byte)0xDD}); //SubFormat - Dolby Digital Plus GUID
354
355        final ByteBuffer dec3Content = ByteBuffer.allocate((int) ec3SpecificBox.getContentSize());
356        ec3SpecificBox.getContent(dec3Content);
357
358        AudioQuality l = new AudioQuality();
359        l.fourCC = "EC-3";
360        l.bitrate = getBitrate(track);
361        l.audioTag = 65534;
362        l.samplingRate = ase.getSampleRate();
363        l.channels = nfchans + lfechans;
364        l.bitPerSample = 16;
365        l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
366        l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dec3Content.array()); //append EC3SpecificBox (big endian) at the end of waveformatex
367        return l;
368    }
369
370    private AudioQuality getDtsAudioQuality(Track track, AudioSampleEntry ase) {
371        final DTSSpecificBox dtsSpecificBox = ase.getBoxes(DTSSpecificBox.class).get(0);
372        if (dtsSpecificBox == null) {
373            throw new RuntimeException("DTS track misses DTSSpecificBox!");
374        }
375
376        final ByteBuffer waveformatex = ByteBuffer.allocate(22);
377        final int frameDuration = dtsSpecificBox.getFrameDuration();
378        short samplesPerBlock = 0;
379        switch (frameDuration) {
380            case 0:
381                samplesPerBlock = 512;
382                break;
383            case 1:
384                samplesPerBlock = 1024;
385                break;
386            case 2:
387                samplesPerBlock = 2048;
388                break;
389            case 3:
390                samplesPerBlock = 4096;
391                break;
392        }
393        waveformatex.put((byte) (samplesPerBlock & 0xff));
394        waveformatex.put((byte) (samplesPerBlock >>> 8));
395        final int dwChannelMask = getNumChannelsAndMask(dtsSpecificBox)[1];
396        waveformatex.put((byte) (dwChannelMask & 0xff));
397        waveformatex.put((byte) (dwChannelMask >>> 8));
398        waveformatex.put((byte) (dwChannelMask >>> 16));
399        waveformatex.put((byte) (dwChannelMask >>> 24));
400        waveformatex.put(new byte[]{(byte)0xAE, (byte)0xE4, (byte)0xBF, (byte)0x5E, (byte)0x61, (byte)0x5E, (byte)0x41, (byte)0x87, (byte)0x92, (byte)0xFC, (byte)0xA4, (byte)0x81, (byte)0x26, (byte)0x99, (byte)0x02, (byte)0x11}); //DTS-HD GUID
401
402        final ByteBuffer dtsCodecPrivateData = ByteBuffer.allocate(8);
403        dtsCodecPrivateData.put((byte) dtsSpecificBox.getStreamConstruction());
404
405        final int channelLayout = dtsSpecificBox.getChannelLayout();
406        dtsCodecPrivateData.put((byte) (channelLayout & 0xff));
407        dtsCodecPrivateData.put((byte) (channelLayout >>> 8));
408        dtsCodecPrivateData.put((byte) (channelLayout >>> 16));
409        dtsCodecPrivateData.put((byte) (channelLayout >>> 24));
410
411        byte dtsFlags = (byte) (dtsSpecificBox.getMultiAssetFlag() << 1);
412        dtsFlags |= dtsSpecificBox.getLBRDurationMod();
413        dtsCodecPrivateData.put(dtsFlags);
414        dtsCodecPrivateData.put(new byte[]{0x00, 0x00}); //reserved
415
416        AudioQuality l = new AudioQuality();
417        l.fourCC = getFormat(ase);
418        l.bitrate = dtsSpecificBox.getAvgBitRate();
419        l.audioTag = 65534;
420        l.samplingRate = dtsSpecificBox.getDTSSamplingFrequency();
421        l.channels = getNumChannelsAndMask(dtsSpecificBox)[0];
422        l.bitPerSample = 16;
423        l.packetSize = track.getSamples().get(0).limit(); //assuming all are same size
424        l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dtsCodecPrivateData.array());
425        return l;
426
427    }
428
429    /* dwChannelMask
430    L SPEAKER_FRONT_LEFT 0x00000001
431    R SPEAKER_FRONT_RIGHT 0x00000002
432    C SPEAKER_FRONT_CENTER 0x00000004
433    LFE1 SPEAKER_LOW_FREQUENCY 0x00000008
434    Ls or Lsr* SPEAKER_BACK_LEFT 0x00000010
435    Rs or Rsr* SPEAKER_BACK_RIGHT 0x00000020
436    Lc SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040
437    Rc SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080
438    Cs SPEAKER_BACK_CENTER 0x00000100
439    Lss SPEAKER_SIDE_LEFT 0x00000200
440    Rss SPEAKER_SIDE_RIGHT 0x00000400
441    Oh SPEAKER_TOP_CENTER 0x00000800
442    Lh SPEAKER_TOP_FRONT_LEFT 0x00001000
443    Ch SPEAKER_TOP_FRONT_CENTER 0x00002000
444    Rh SPEAKER_TOP_FRONT_RIGHT 0x00004000
445    Lhr SPEAKER_TOP_BACK_LEFT 0x00008000
446    Chf SPEAKER_TOP_BACK_CENTER 0x00010000
447    Rhr SPEAKER_TOP_BACK_RIGHT 0x00020000
448    SPEAKER_RESERVED 0x80000000
449
450    * if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
451     */
452    private int[] getNumChannelsAndMask(DTSSpecificBox dtsSpecificBox) {
453        final int channelLayout = dtsSpecificBox.getChannelLayout();
454        int numChannels = 0;
455        int dwChannelMask = 0;
456        if ((channelLayout & 0x0001) == 0x0001) {
457            //0001h Center in front of listener 1
458            numChannels += 1;
459            dwChannelMask |= 0x00000004; //SPEAKER_FRONT_CENTER
460        }
461        if ((channelLayout & 0x0002) == 0x0002) {
462            //0002h Left/Right in front 2
463            numChannels += 2;
464            dwChannelMask |= 0x00000001; //SPEAKER_FRONT_LEFT
465            dwChannelMask |= 0x00000002; //SPEAKER_FRONT_RIGHT
466        }
467        if ((channelLayout & 0x0004) == 0x0004) {
468            //0004h Left/Right surround on side in rear 2
469            numChannels += 2;
470            //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
471            dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
472            dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
473        }
474        if ((channelLayout & 0x0008) == 0x0008) {
475            //0008h Low frequency effects subwoofer 1
476            numChannels += 1;
477            dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
478        }
479        if ((channelLayout & 0x0010) == 0x0010) {
480            //0010h Center surround in rear 1
481            numChannels += 1;
482            dwChannelMask |= 0x00000100; //SPEAKER_BACK_CENTER
483        }
484        if ((channelLayout & 0x0020) == 0x0020) {
485            //0020h Left/Right height in front 2
486            numChannels += 2;
487            dwChannelMask |= 0x00001000; //SPEAKER_TOP_FRONT_LEFT
488            dwChannelMask |= 0x00004000; //SPEAKER_TOP_FRONT_RIGHT
489        }
490        if ((channelLayout & 0x0040) == 0x0040) {
491            //0040h Left/Right surround in rear 2
492            numChannels += 2;
493            dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
494            dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
495        }
496        if ((channelLayout & 0x0080) == 0x0080) {
497            //0080h Center Height in front 1
498            numChannels += 1;
499            dwChannelMask |= 0x00002000; //SPEAKER_TOP_FRONT_CENTER
500        }
501        if ((channelLayout & 0x0100) == 0x0100) {
502            //0100h Over the listener’s head 1
503            numChannels += 1;
504            dwChannelMask |= 0x00000800; //SPEAKER_TOP_CENTER
505        }
506        if ((channelLayout & 0x0200) == 0x0200) {
507            //0200h Between left/right and center in front 2
508            numChannels += 2;
509            dwChannelMask |= 0x00000040; //SPEAKER_FRONT_LEFT_OF_CENTER
510            dwChannelMask |= 0x00000080; //SPEAKER_FRONT_RIGHT_OF_CENTER
511        }
512        if ((channelLayout & 0x0400) == 0x0400) {
513            //0400h Left/Right on side in front 2
514            numChannels += 2;
515            dwChannelMask |= 0x00000200; //SPEAKER_SIDE_LEFT
516            dwChannelMask |= 0x00000400; //SPEAKER_SIDE_RIGHT
517        }
518        if ((channelLayout & 0x0800) == 0x0800) {
519            //0800h Left/Right surround on side 2
520            numChannels += 2;
521            //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively
522            dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
523            dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
524        }
525        if ((channelLayout & 0x1000) == 0x1000) {
526            //1000h Second low frequency effects subwoofer 1
527            numChannels += 1;
528            dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY
529        }
530        if ((channelLayout & 0x2000) == 0x2000) {
531            //2000h Left/Right height on side 2
532            numChannels += 2;
533            dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT
534            dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT
535        }
536        if ((channelLayout & 0x4000) == 0x4000) {
537            //4000h Center height in rear 1
538            numChannels += 1;
539            dwChannelMask |= 0x00010000; //SPEAKER_TOP_BACK_CENTER
540        }
541        if ((channelLayout & 0x8000) == 0x8000) {
542            //8000h Left/Right height in rear 2
543            numChannels += 2;
544            dwChannelMask |= 0x00008000; //SPEAKER_TOP_BACK_LEFT
545            dwChannelMask |= 0x00020000; //SPEAKER_TOP_BACK_RIGHT
546        }
547        if ((channelLayout & 0x10000) == 0x10000) {
548            //10000h Center below in front
549            numChannels += 1;
550        }
551        if ((channelLayout & 0x20000) == 0x20000) {
552            //20000h Left/Right below in front
553            numChannels += 2;
554        }
555        return new int[]{numChannels, dwChannelMask};
556    }
557
558    private String getAudioCodecPrivateData(AudioSpecificConfig audioSpecificConfig) {
559        byte[] configByteArray = audioSpecificConfig.getConfigBytes();
560        return Hex.encodeHex(configByteArray);
561    }
562
563    private VideoQuality getVideoQuality(Track track, VisualSampleEntry vse) {
564        VideoQuality l;
565        if ("avc1".equals(getFormat(vse))) {
566            AvcConfigurationBox avcConfigurationBox = vse.getBoxes(AvcConfigurationBox.class).get(0);
567            l = new VideoQuality();
568            l.bitrate = getBitrate(track);
569            l.codecPrivateData = Hex.encodeHex(getAvcCodecPrivateData(avcConfigurationBox));
570            l.fourCC = "AVC1";
571            l.width = vse.getWidth();
572            l.height = vse.getHeight();
573            l.nalLength = avcConfigurationBox.getLengthSizeMinusOne() + 1;
574        } else {
575            throw new InternalError("I don't know how to handle video of type " + getFormat(vse));
576        }
577        return l;
578    }
579
580    private byte[] getAvcCodecPrivateData(AvcConfigurationBox avcConfigurationBox) {
581        List<byte[]> sps = avcConfigurationBox.getSequenceParameterSets();
582        List<byte[]> pps = avcConfigurationBox.getPictureParameterSets();
583        ByteArrayOutputStream baos = new ByteArrayOutputStream();
584        try {
585            baos.write(new byte[]{0, 0, 0, 1});
586
587            for (byte[] sp : sps) {
588                baos.write(sp);
589            }
590            baos.write(new byte[]{0, 0, 0, 1});
591            for (byte[] pp : pps) {
592                baos.write(pp);
593            }
594        } catch (IOException ex) {
595            throw new RuntimeException("ByteArrayOutputStream do not throw IOException ?!?!?");
596        }
597        return baos.toByteArray();
598    }
599
600    private class DependentSubstreamMask {
601        private byte dWChannelMaskFirstByte;
602        private byte dWChannelMaskSecondByte;
603        private EC3SpecificBox.Entry entry;
604
605        public DependentSubstreamMask(byte dWChannelMaskFirstByte, byte dWChannelMaskSecondByte, EC3SpecificBox.Entry entry) {
606            this.dWChannelMaskFirstByte = dWChannelMaskFirstByte;
607            this.dWChannelMaskSecondByte = dWChannelMaskSecondByte;
608            this.entry = entry;
609        }
610
611        public byte getdWChannelMaskFirstByte() {
612            return dWChannelMaskFirstByte;
613        }
614
615        public byte getdWChannelMaskSecondByte() {
616            return dWChannelMaskSecondByte;
617        }
618
619        public DependentSubstreamMask process() {
620            switch (entry.chan_loc) {
621                case 0:
622                    dWChannelMaskFirstByte |= 0x3;
623                    break;
624                case 1:
625                    dWChannelMaskFirstByte |= 0xC;
626                    break;
627                case 2:
628                    dWChannelMaskSecondByte |= 0x80;
629                    break;
630                case 3:
631                    dWChannelMaskSecondByte |= 0x8;
632                    break;
633                case 6:
634                    dWChannelMaskSecondByte |= 0x5;
635                    break;
636                case 7:
637                    dWChannelMaskSecondByte |= 0x2;
638                    break;
639            }
640            return this;
641        }
642    }
643}
644