| 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 + |
| '}'; |
| } |
| } |