/* * 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.adaptivestreaming; import com.coremedia.iso.Hex; import com.coremedia.iso.boxes.SampleDescriptionBox; import com.coremedia.iso.boxes.SoundMediaHeaderBox; import com.coremedia.iso.boxes.VideoMediaHeaderBox; import com.coremedia.iso.boxes.h264.AvcConfigurationBox; import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; import com.coremedia.iso.boxes.sampleentry.VisualSampleEntry; import com.googlecode.mp4parser.Version; import com.googlecode.mp4parser.authoring.Movie; import com.googlecode.mp4parser.authoring.Track; import com.googlecode.mp4parser.authoring.builder.FragmentIntersectionFinder; import com.googlecode.mp4parser.boxes.DTSSpecificBox; import com.googlecode.mp4parser.boxes.EC3SpecificBox; import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.*; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringWriter; import java.nio.ByteBuffer; import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; public class FlatManifestWriterImpl extends AbstractManifestWriter { private static final Logger LOG = Logger.getLogger(FlatManifestWriterImpl.class.getName()); protected FlatManifestWriterImpl(FragmentIntersectionFinder intersectionFinder) { super(intersectionFinder); } /** * Overwrite this method in subclasses to add your specialities. * * @param manifest the original manifest * @return your customized version of the manifest */ protected Document customizeManifest(Document manifest) { return manifest; } public String getManifest(Movie movie) throws IOException { LinkedList<VideoQuality> videoQualities = new LinkedList<VideoQuality>(); long videoTimescale = -1; LinkedList<AudioQuality> audioQualities = new LinkedList<AudioQuality>(); long audioTimescale = -1; for (Track track : movie.getTracks()) { if (track.getMediaHeaderBox() instanceof VideoMediaHeaderBox) { videoFragmentsDurations = checkFragmentsAlign(videoFragmentsDurations, calculateFragmentDurations(track, movie)); SampleDescriptionBox stsd = track.getSampleDescriptionBox(); videoQualities.add(getVideoQuality(track, (VisualSampleEntry) stsd.getSampleEntry())); if (videoTimescale == -1) { videoTimescale = track.getTrackMetaData().getTimescale(); } else { assert videoTimescale == track.getTrackMetaData().getTimescale(); } } if (track.getMediaHeaderBox() instanceof SoundMediaHeaderBox) { audioFragmentsDurations = checkFragmentsAlign(audioFragmentsDurations, calculateFragmentDurations(track, movie)); SampleDescriptionBox stsd = track.getSampleDescriptionBox(); audioQualities.add(getAudioQuality(track, (AudioSampleEntry) stsd.getSampleEntry())); if (audioTimescale == -1) { audioTimescale = track.getTrackMetaData().getTimescale(); } else { assert audioTimescale == track.getTrackMetaData().getTimescale(); } } } DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder; try { documentBuilder = documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new IOException(e); } Document document = documentBuilder.newDocument(); Element smoothStreamingMedia = document.createElement("SmoothStreamingMedia"); document.appendChild(smoothStreamingMedia); smoothStreamingMedia.setAttribute("MajorVersion", "2"); smoothStreamingMedia.setAttribute("MinorVersion", "1"); // silverlight ignores the timescale attr smoothStreamingMedia.addAttribute(new Attribute("TimeScale", Long.toString(movieTimeScale))); smoothStreamingMedia.setAttribute("Duration", "0"); smoothStreamingMedia.appendChild(document.createComment(Version.VERSION)); Element videoStreamIndex = document.createElement("StreamIndex"); videoStreamIndex.setAttribute("Type", "video"); videoStreamIndex.setAttribute("TimeScale", Long.toString(videoTimescale)); // silverlight ignores the timescale attr videoStreamIndex.setAttribute("Chunks", Integer.toString(videoFragmentsDurations.length)); videoStreamIndex.setAttribute("Url", "video/{bitrate}/{start time}"); videoStreamIndex.setAttribute("QualityLevels", Integer.toString(videoQualities.size())); smoothStreamingMedia.appendChild(videoStreamIndex); for (int i = 0; i < videoQualities.size(); i++) { VideoQuality vq = videoQualities.get(i); Element qualityLevel = document.createElement("QualityLevel"); qualityLevel.setAttribute("Index", Integer.toString(i)); qualityLevel.setAttribute("Bitrate", Long.toString(vq.bitrate)); qualityLevel.setAttribute("FourCC", vq.fourCC); qualityLevel.setAttribute("MaxWidth", Long.toString(vq.width)); qualityLevel.setAttribute("MaxHeight", Long.toString(vq.height)); qualityLevel.setAttribute("CodecPrivateData", vq.codecPrivateData); qualityLevel.setAttribute("NALUnitLengthField", Integer.toString(vq.nalLength)); videoStreamIndex.appendChild(qualityLevel); } for (int i = 0; i < videoFragmentsDurations.length; i++) { Element c = document.createElement("c"); c.setAttribute("n", Integer.toString(i)); c.setAttribute("d", Long.toString(videoFragmentsDurations[i])); videoStreamIndex.appendChild(c); } if (audioFragmentsDurations != null) { Element audioStreamIndex = document.createElement("StreamIndex"); audioStreamIndex.setAttribute("Type", "audio"); audioStreamIndex.setAttribute("TimeScale", Long.toString(audioTimescale)); // silverlight ignores the timescale attr audioStreamIndex.setAttribute("Chunks", Integer.toString(audioFragmentsDurations.length)); audioStreamIndex.setAttribute("Url", "audio/{bitrate}/{start time}"); audioStreamIndex.setAttribute("QualityLevels", Integer.toString(audioQualities.size())); smoothStreamingMedia.appendChild(audioStreamIndex); for (int i = 0; i < audioQualities.size(); i++) { AudioQuality aq = audioQualities.get(i); Element qualityLevel = document.createElement("QualityLevel"); qualityLevel.setAttribute("Index", Integer.toString(i)); qualityLevel.setAttribute("FourCC", aq.fourCC); qualityLevel.setAttribute("Bitrate", Long.toString(aq.bitrate)); qualityLevel.setAttribute("AudioTag", Integer.toString(aq.audioTag)); qualityLevel.setAttribute("SamplingRate", Long.toString(aq.samplingRate)); qualityLevel.setAttribute("Channels", Integer.toString(aq.channels)); qualityLevel.setAttribute("BitsPerSample", Integer.toString(aq.bitPerSample)); qualityLevel.setAttribute("PacketSize", Integer.toString(aq.packetSize)); qualityLevel.setAttribute("CodecPrivateData", aq.codecPrivateData); audioStreamIndex.appendChild(qualityLevel); } for (int i = 0; i < audioFragmentsDurations.length; i++) { Element c = document.createElement("c"); c.setAttribute("n", Integer.toString(i)); c.setAttribute("d", Long.toString(audioFragmentsDurations[i])); audioStreamIndex.appendChild(c); } } document.setXmlStandalone(true); Source source = new DOMSource(customizeManifest(document)); StringWriter stringWriter = new StringWriter(); Result result = new StreamResult(stringWriter); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer; try { transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.transform(source, result); } catch (TransformerConfigurationException e) { throw new IOException(e); } catch (TransformerException e) { throw new IOException(e); } return stringWriter.getBuffer().toString(); } private AudioQuality getAudioQuality(Track track, AudioSampleEntry ase) { if (getFormat(ase).equals("mp4a")) { return getAacAudioQuality(track, ase); } else if (getFormat(ase).equals("ec-3")) { return getEc3AudioQuality(track, ase); } else if (getFormat(ase).startsWith("dts")) { return getDtsAudioQuality(track, ase); } else { throw new InternalError("I don't know what to do with audio of type " + getFormat(ase)); } } private AudioQuality getAacAudioQuality(Track track, AudioSampleEntry ase) { AudioQuality l = new AudioQuality(); final ESDescriptorBox esDescriptorBox = ase.getBoxes(ESDescriptorBox.class).get(0); final AudioSpecificConfig audioSpecificConfig = esDescriptorBox.getEsDescriptor().getDecoderConfigDescriptor().getAudioSpecificInfo(); if (audioSpecificConfig.getSbrPresentFlag() == 1) { l.fourCC = "AACH"; } else if (audioSpecificConfig.getPsPresentFlag() == 1) { 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... } else { l.fourCC = "AACL"; } l.bitrate = getBitrate(track); l.audioTag = 255; l.samplingRate = ase.getSampleRate(); l.channels = ase.getChannelCount(); l.bitPerSample = ase.getSampleSize(); l.packetSize = 4; l.codecPrivateData = getAudioCodecPrivateData(audioSpecificConfig); //Index="0" Bitrate="103000" AudioTag="255" SamplingRate="44100" Channels="2" BitsPerSample="16" packetSize="4" CodecPrivateData="" return l; } private AudioQuality getEc3AudioQuality(Track track, AudioSampleEntry ase) { final EC3SpecificBox ec3SpecificBox = ase.getBoxes(EC3SpecificBox.class).get(0); if (ec3SpecificBox == null) { throw new RuntimeException("EC-3 track misses EC3SpecificBox!"); } short nfchans = 0; //full bandwidth channels short lfechans = 0; byte dWChannelMaskFirstByte = 0; byte dWChannelMaskSecondByte = 0; for (EC3SpecificBox.Entry entry : ec3SpecificBox.getEntries()) { /* Table 4.3: Audio coding mode acmod Audio coding mode Nfchans Channel array ordering 000 1 + 1 2 Ch1, Ch2 001 1/0 1 C 010 2/0 2 L, R 011 3/0 3 L, C, R 100 2/1 3 L, R, S 101 3/1 4 L, C, R, S 110 2/2 4 L, R, SL, SR 111 3/2 5 L, C, R, SL, SR Table F.2: Chan_loc field bit assignments Bit Location 0 Lc/Rc pair 1 Lrs/Rrs pair 2 Cs 3 Ts 4 Lsd/Rsd pair 5 Lw/Rw pair 6 Lvh/Rvh pair 7 Cvh 8 LFE2 */ switch (entry.acmod) { case 0: //1+1; Ch1, Ch2 nfchans += 2; throw new RuntimeException("Smooth Streaming doesn't support DDP 1+1 mode"); case 1: //1/0; C nfchans += 1; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0x20; } break; case 2: //2/0; L, R nfchans += 2; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xC0; } break; case 3: //3/0; L, C, R nfchans += 3; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xE0; } break; case 4: //2/1; L, R, S nfchans += 3; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xC0; dWChannelMaskSecondByte |= 0x80; } break; case 5: //3/1; L, C, R, S nfchans += 4; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xE0; dWChannelMaskSecondByte |= 0x80; } break; case 6: //2/2; L, R, SL, SR nfchans += 4; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xCC; } break; case 7: //3/2; L, C, R, SL, SR nfchans += 5; if (entry.num_dep_sub > 0) { DependentSubstreamMask dependentSubstreamMask = new DependentSubstreamMask(dWChannelMaskFirstByte, dWChannelMaskSecondByte, entry).process(); dWChannelMaskFirstByte |= dependentSubstreamMask.getdWChannelMaskFirstByte(); dWChannelMaskSecondByte |= dependentSubstreamMask.getdWChannelMaskSecondByte(); } else { dWChannelMaskFirstByte |= 0xEC; } break; } if (entry.lfeon == 1) { lfechans++; dWChannelMaskFirstByte |= 0x10; } } final ByteBuffer waveformatex = ByteBuffer.allocate(22); waveformatex.put(new byte[]{0x00, 0x06}); //1536 wSamplesPerBlock - little endian waveformatex.put(dWChannelMaskFirstByte); waveformatex.put(dWChannelMaskSecondByte); waveformatex.put(new byte[]{0x00, 0x00}); //pad dwChannelMask to 32bit 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 final ByteBuffer dec3Content = ByteBuffer.allocate((int) ec3SpecificBox.getContentSize()); ec3SpecificBox.getContent(dec3Content); AudioQuality l = new AudioQuality(); l.fourCC = "EC-3"; l.bitrate = getBitrate(track); l.audioTag = 65534; l.samplingRate = ase.getSampleRate(); l.channels = nfchans + lfechans; l.bitPerSample = 16; // TODO: was .limit() but .remaining() seems logical unless the position has moved l.packetSize = (int) track.getSamples().get(0).getSize(); //assuming all are same size l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dec3Content.array()); //append EC3SpecificBox (big endian) at the end of waveformatex return l; } private AudioQuality getDtsAudioQuality(Track track, AudioSampleEntry ase) { final DTSSpecificBox dtsSpecificBox = ase.getBoxes(DTSSpecificBox.class).get(0); if (dtsSpecificBox == null) { throw new RuntimeException("DTS track misses DTSSpecificBox!"); } final ByteBuffer waveformatex = ByteBuffer.allocate(22); final int frameDuration = dtsSpecificBox.getFrameDuration(); short samplesPerBlock = 0; switch (frameDuration) { case 0: samplesPerBlock = 512; break; case 1: samplesPerBlock = 1024; break; case 2: samplesPerBlock = 2048; break; case 3: samplesPerBlock = 4096; break; } waveformatex.put((byte) (samplesPerBlock & 0xff)); waveformatex.put((byte) (samplesPerBlock >>> 8)); final int dwChannelMask = getNumChannelsAndMask(dtsSpecificBox)[1]; waveformatex.put((byte) (dwChannelMask & 0xff)); waveformatex.put((byte) (dwChannelMask >>> 8)); waveformatex.put((byte) (dwChannelMask >>> 16)); waveformatex.put((byte) (dwChannelMask >>> 24)); 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 final ByteBuffer dtsCodecPrivateData = ByteBuffer.allocate(8); dtsCodecPrivateData.put((byte) dtsSpecificBox.getStreamConstruction()); final int channelLayout = dtsSpecificBox.getChannelLayout(); dtsCodecPrivateData.put((byte) (channelLayout & 0xff)); dtsCodecPrivateData.put((byte) (channelLayout >>> 8)); dtsCodecPrivateData.put((byte) (channelLayout >>> 16)); dtsCodecPrivateData.put((byte) (channelLayout >>> 24)); byte dtsFlags = (byte) (dtsSpecificBox.getMultiAssetFlag() << 1); dtsFlags |= dtsSpecificBox.getLBRDurationMod(); dtsCodecPrivateData.put(dtsFlags); dtsCodecPrivateData.put(new byte[]{0x00, 0x00}); //reserved AudioQuality l = new AudioQuality(); l.fourCC = getFormat(ase); l.bitrate = dtsSpecificBox.getAvgBitRate(); l.audioTag = 65534; l.samplingRate = dtsSpecificBox.getDTSSamplingFrequency(); l.channels = getNumChannelsAndMask(dtsSpecificBox)[0]; l.bitPerSample = 16; l.packetSize = (int) track.getSamples().get(0).getSize(); //assuming all are same size l.codecPrivateData = Hex.encodeHex(waveformatex.array()) + Hex.encodeHex(dtsCodecPrivateData.array()); return l; } /* dwChannelMask L SPEAKER_FRONT_LEFT 0x00000001 R SPEAKER_FRONT_RIGHT 0x00000002 C SPEAKER_FRONT_CENTER 0x00000004 LFE1 SPEAKER_LOW_FREQUENCY 0x00000008 Ls or Lsr* SPEAKER_BACK_LEFT 0x00000010 Rs or Rsr* SPEAKER_BACK_RIGHT 0x00000020 Lc SPEAKER_FRONT_LEFT_OF_CENTER 0x00000040 Rc SPEAKER_FRONT_RIGHT_OF_CENTER 0x00000080 Cs SPEAKER_BACK_CENTER 0x00000100 Lss SPEAKER_SIDE_LEFT 0x00000200 Rss SPEAKER_SIDE_RIGHT 0x00000400 Oh SPEAKER_TOP_CENTER 0x00000800 Lh SPEAKER_TOP_FRONT_LEFT 0x00001000 Ch SPEAKER_TOP_FRONT_CENTER 0x00002000 Rh SPEAKER_TOP_FRONT_RIGHT 0x00004000 Lhr SPEAKER_TOP_BACK_LEFT 0x00008000 Chf SPEAKER_TOP_BACK_CENTER 0x00010000 Rhr SPEAKER_TOP_BACK_RIGHT 0x00020000 SPEAKER_RESERVED 0x80000000 * if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively */ private int[] getNumChannelsAndMask(DTSSpecificBox dtsSpecificBox) { final int channelLayout = dtsSpecificBox.getChannelLayout(); int numChannels = 0; int dwChannelMask = 0; if ((channelLayout & 0x0001) == 0x0001) { //0001h Center in front of listener 1 numChannels += 1; dwChannelMask |= 0x00000004; //SPEAKER_FRONT_CENTER } if ((channelLayout & 0x0002) == 0x0002) { //0002h Left/Right in front 2 numChannels += 2; dwChannelMask |= 0x00000001; //SPEAKER_FRONT_LEFT dwChannelMask |= 0x00000002; //SPEAKER_FRONT_RIGHT } if ((channelLayout & 0x0004) == 0x0004) { //0004h Left/Right surround on side in rear 2 numChannels += 2; //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT } if ((channelLayout & 0x0008) == 0x0008) { //0008h Low frequency effects subwoofer 1 numChannels += 1; dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY } if ((channelLayout & 0x0010) == 0x0010) { //0010h Center surround in rear 1 numChannels += 1; dwChannelMask |= 0x00000100; //SPEAKER_BACK_CENTER } if ((channelLayout & 0x0020) == 0x0020) { //0020h Left/Right height in front 2 numChannels += 2; dwChannelMask |= 0x00001000; //SPEAKER_TOP_FRONT_LEFT dwChannelMask |= 0x00004000; //SPEAKER_TOP_FRONT_RIGHT } if ((channelLayout & 0x0040) == 0x0040) { //0040h Left/Right surround in rear 2 numChannels += 2; dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT } if ((channelLayout & 0x0080) == 0x0080) { //0080h Center Height in front 1 numChannels += 1; dwChannelMask |= 0x00002000; //SPEAKER_TOP_FRONT_CENTER } if ((channelLayout & 0x0100) == 0x0100) { //0100h Over the listener’s head 1 numChannels += 1; dwChannelMask |= 0x00000800; //SPEAKER_TOP_CENTER } if ((channelLayout & 0x0200) == 0x0200) { //0200h Between left/right and center in front 2 numChannels += 2; dwChannelMask |= 0x00000040; //SPEAKER_FRONT_LEFT_OF_CENTER dwChannelMask |= 0x00000080; //SPEAKER_FRONT_RIGHT_OF_CENTER } if ((channelLayout & 0x0400) == 0x0400) { //0400h Left/Right on side in front 2 numChannels += 2; dwChannelMask |= 0x00000200; //SPEAKER_SIDE_LEFT dwChannelMask |= 0x00000400; //SPEAKER_SIDE_RIGHT } if ((channelLayout & 0x0800) == 0x0800) { //0800h Left/Right surround on side 2 numChannels += 2; //* if Lss, Rss exist, then this position is equivalent to Lsr, Rsr respectively dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT } if ((channelLayout & 0x1000) == 0x1000) { //1000h Second low frequency effects subwoofer 1 numChannels += 1; dwChannelMask |= 0x00000008; //SPEAKER_LOW_FREQUENCY } if ((channelLayout & 0x2000) == 0x2000) { //2000h Left/Right height on side 2 numChannels += 2; dwChannelMask |= 0x00000010; //SPEAKER_BACK_LEFT dwChannelMask |= 0x00000020; //SPEAKER_BACK_RIGHT } if ((channelLayout & 0x4000) == 0x4000) { //4000h Center height in rear 1 numChannels += 1; dwChannelMask |= 0x00010000; //SPEAKER_TOP_BACK_CENTER } if ((channelLayout & 0x8000) == 0x8000) { //8000h Left/Right height in rear 2 numChannels += 2; dwChannelMask |= 0x00008000; //SPEAKER_TOP_BACK_LEFT dwChannelMask |= 0x00020000; //SPEAKER_TOP_BACK_RIGHT } if ((channelLayout & 0x10000) == 0x10000) { //10000h Center below in front numChannels += 1; } if ((channelLayout & 0x20000) == 0x20000) { //20000h Left/Right below in front numChannels += 2; } return new int[]{numChannels, dwChannelMask}; } private String getAudioCodecPrivateData(AudioSpecificConfig audioSpecificConfig) { byte[] configByteArray = audioSpecificConfig.getConfigBytes(); return Hex.encodeHex(configByteArray); } private VideoQuality getVideoQuality(Track track, VisualSampleEntry vse) { VideoQuality l; if ("avc1".equals(getFormat(vse))) { AvcConfigurationBox avcConfigurationBox = vse.getBoxes(AvcConfigurationBox.class).get(0); l = new VideoQuality(); l.bitrate = getBitrate(track); l.codecPrivateData = Hex.encodeHex(getAvcCodecPrivateData(avcConfigurationBox)); l.fourCC = "AVC1"; l.width = vse.getWidth(); l.height = vse.getHeight(); l.nalLength = avcConfigurationBox.getLengthSizeMinusOne() + 1; } else { throw new InternalError("I don't know how to handle video of type " + getFormat(vse)); } return l; } private byte[] getAvcCodecPrivateData(AvcConfigurationBox avcConfigurationBox) { List<byte[]> sps = avcConfigurationBox.getSequenceParameterSets(); List<byte[]> pps = avcConfigurationBox.getPictureParameterSets(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { baos.write(new byte[]{0, 0, 0, 1}); for (byte[] sp : sps) { baos.write(sp); } baos.write(new byte[]{0, 0, 0, 1}); for (byte[] pp : pps) { baos.write(pp); } } catch (IOException ex) { throw new RuntimeException("ByteArrayOutputStream do not throw IOException ?!?!?"); } return baos.toByteArray(); } private class DependentSubstreamMask { private byte dWChannelMaskFirstByte; private byte dWChannelMaskSecondByte; private EC3SpecificBox.Entry entry; public DependentSubstreamMask(byte dWChannelMaskFirstByte, byte dWChannelMaskSecondByte, EC3SpecificBox.Entry entry) { this.dWChannelMaskFirstByte = dWChannelMaskFirstByte; this.dWChannelMaskSecondByte = dWChannelMaskSecondByte; this.entry = entry; } public byte getdWChannelMaskFirstByte() { return dWChannelMaskFirstByte; } public byte getdWChannelMaskSecondByte() { return dWChannelMaskSecondByte; } public DependentSubstreamMask process() { switch (entry.chan_loc) { case 0: dWChannelMaskFirstByte |= 0x3; break; case 1: dWChannelMaskFirstByte |= 0xC; break; case 2: dWChannelMaskSecondByte |= 0x80; break; case 3: dWChannelMaskSecondByte |= 0x8; break; case 6: dWChannelMaskSecondByte |= 0x5; break; case 7: dWChannelMaskSecondByte |= 0x2; break; } return this; } } }