package com.googlecode.mp4parser.authoring.tracks; import java.io.IOException; import java.io.PushbackInputStream; import java.nio.ByteBuffer; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import com.coremedia.iso.boxes.AbstractMediaHeaderBox; import com.coremedia.iso.boxes.CompositionTimeToSample; import com.coremedia.iso.boxes.SampleDependencyTypeBox; import com.coremedia.iso.boxes.SampleDescriptionBox; import com.coremedia.iso.boxes.SoundMediaHeaderBox; import com.coremedia.iso.boxes.SubSampleInformationBox; import com.coremedia.iso.boxes.TimeToSampleBox; import com.coremedia.iso.boxes.sampleentry.AudioSampleEntry; import com.googlecode.mp4parser.authoring.AbstractTrack; import com.googlecode.mp4parser.authoring.TrackMetaData; import com.googlecode.mp4parser.boxes.mp4.ESDescriptorBox; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.AudioSpecificConfig; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.BitReaderBuffer; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescriptor; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor; /** * Created with IntelliJ IDEA. * User: magnus * Date: 2012-04-20 * Time: 11:14 * To change this template use File | Settings | File Templates. */ public class AACTrackImpl extends AbstractTrack { TrackMetaData trackMetaData = new TrackMetaData(); SampleDescriptionBox sampleDescriptionBox; int samplerate; int bitrate; int channelCount; int channelconfig; int bufferSizeDB; long maxBitRate; long avgBitRate; private PushbackInputStream inputStream; private List<ByteBuffer> samples; boolean readSamples = false; List<TimeToSampleBox.Entry> stts; public static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>(); public AACTrackImpl(PushbackInputStream inputStream) throws IOException { this.inputStream = inputStream; stts = new LinkedList<TimeToSampleBox.Entry>(); 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); 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.removeFirst(); } 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("eng"); trackMetaData.setTimescale(samplerate); // Audio tracks always use samplerate as timescale } public SampleDescriptionBox getSampleDescriptionBox() { return sampleDescriptionBox; //To change body of implemented methods use File | Settings | File Templates. } public List<TimeToSampleBox.Entry> getDecodingTimeEntries() { return stts; //To change body of implemented methods use File | Settings | File Templates. } public List<CompositionTimeToSample.Entry> getCompositionTimeEntries() { return null; //To change body of implemented methods use File | Settings | File Templates. } public long[] getSyncSamples() { return null; //To change body of implemented methods use File | Settings | File Templates. } public List<SampleDependencyTypeBox.Entry> getSampleDependencies() { return null; //To change body of implemented methods use File | Settings | File Templates. } public TrackMetaData getTrackMetaData() { return trackMetaData; //To change body of implemented methods use File | Settings | File Templates. } public String getHandler() { return "soun"; //To change body of implemented methods use File | Settings | File Templates. } public List<ByteBuffer> getSamples() { return samples; //To change body of implemented methods use File | Settings | File Templates. } public AbstractMediaHeaderBox getMediaHeaderBox() { return new SoundMediaHeaderBox(); } public SubSampleInformationBox getSubsampleInformationBox() { return null; //To change body of implemented methods use File | Settings | File Templates. } private boolean readVariables() throws IOException { byte[] data = new byte[100]; if (100 != inputStream.read(data, 0, 100)) { return false; } inputStream.unread(data); 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; while (-1 != inputStream.read(header)) { ret = true; ByteBuffer bb = ByteBuffer.wrap(header); inputStream.unread(header); 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)); } return ret; } }