package org.mp4parser.streaming.input.aac;
import org.mp4parser.boxes.iso14496.part1.objectdescriptors.AudioSpecificConfig;
import org.mp4parser.boxes.iso14496.part1.objectdescriptors.DecoderConfigDescriptor;
import org.mp4parser.boxes.iso14496.part1.objectdescriptors.ESDescriptor;
import org.mp4parser.boxes.iso14496.part1.objectdescriptors.SLConfigDescriptor;
import org.mp4parser.boxes.iso14496.part12.SampleDescriptionBox;
import org.mp4parser.boxes.iso14496.part14.ESDescriptorBox;
import org.mp4parser.boxes.sampleentry.AudioSampleEntry;
import org.mp4parser.streaming.extensions.DefaultSampleFlagsTrackExtension;
import org.mp4parser.streaming.extensions.TrackIdTrackExtension;
import org.mp4parser.streaming.input.AbstractStreamingTrack;
import org.mp4parser.streaming.input.StreamingSampleImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
public class AdtsAacStreamingTrack extends AbstractStreamingTrack implements Callable<Void> {
private static Map<Integer, Integer> samplingFrequencyIndexMap = new HashMap<Integer, Integer>();
private static Logger LOG = LoggerFactory.getLogger(AdtsAacStreamingTrack.class.getName());
static {
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);
}
CountDownLatch gotFirstSample = new CountDownLatch(1);
SampleDescriptionBox stsd = null;
private InputStream is;
private boolean closed;
private AdtsHeader firstHeader;
private String lang = "und";
private long avgBitrate;
private long maxBitrate;
public AdtsAacStreamingTrack(InputStream is, long avgBitrate, long maxBitrate) {
this.avgBitrate = avgBitrate;
this.maxBitrate = maxBitrate;
assert is != null;
this.is = is;
DefaultSampleFlagsTrackExtension defaultSampleFlagsTrackExtension = new DefaultSampleFlagsTrackExtension();
defaultSampleFlagsTrackExtension.setIsLeading(2);
defaultSampleFlagsTrackExtension.setSampleDependsOn(2);
defaultSampleFlagsTrackExtension.setSampleIsDependedOn(2);
defaultSampleFlagsTrackExtension.setSampleHasRedundancy(2);
defaultSampleFlagsTrackExtension.setSampleIsNonSyncSample(false);
this.addTrackExtension(defaultSampleFlagsTrackExtension);
}
private static AdtsHeader readADTSHeader(InputStream fis) throws IOException {
AdtsHeader hdr = new AdtsHeader();
int x = fis.read(); // A
int syncword = x << 4;
x = fis.read();
if (x == -1) {
return null;
}
syncword += (x >> 4);
if (syncword != 0xfff) {
throw new IOException("Expected Start Word 0xfff");
}
hdr.mpegVersion = (x & 0x8) >> 3;
hdr.layer = (x & 0x6) >> 1;
; // C
hdr.protectionAbsent = (x & 0x1); // D
x = fis.read();
hdr.profile = ((x & 0xc0) >> 6) + 1; // E
//System.err.println(String.format("Profile %s", audioObjectTypes.get(hdr.profile)));
hdr.sampleFrequencyIndex = (x & 0x3c) >> 2;
assert hdr.sampleFrequencyIndex != 15;
hdr.sampleRate = samplingFrequencyIndexMap.get(hdr.sampleFrequencyIndex); // F
hdr.channelconfig = (x & 1) << 2; // H
x = fis.read();
hdr.channelconfig += (x & 0xc0) >> 6;
hdr.original = (x & 0x20) >> 5; // I
hdr.home = (x & 0x10) >> 4; // J
hdr.copyrightedStream = (x & 0x8) >> 3; // K
hdr.copyrightStart = (x & 0x4) >> 2; // L
hdr.frameLength = (x & 0x3) << 9; // M
x = fis.read();
hdr.frameLength += (x << 3);
x = fis.read();
hdr.frameLength += (x & 0xe0) >> 5;
hdr.bufferFullness = (x & 0x1f) << 6;
x = fis.read();
hdr.bufferFullness += (x & 0xfc) >> 2;
hdr.numAacFramesPerAdtsFrame = ((x & 0x3)) + 1;
if (hdr.numAacFramesPerAdtsFrame != 1) {
throw new IOException("This muxer can only work with 1 AAC frame per ADTS frame");
}
if (hdr.protectionAbsent == 0) {
int crc1 = fis.read();
int crc2 = fis.read();
}
return hdr;
}
public boolean isClosed() {
return closed;
}
public synchronized SampleDescriptionBox getSampleDescriptionBox() {
waitForFirstSample();
if (stsd == null) {
stsd = new SampleDescriptionBox();
AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a");
if (firstHeader.channelconfig == 7) {
audioSampleEntry.setChannelCount(8);
} else {
audioSampleEntry.setChannelCount(firstHeader.channelconfig);
}
audioSampleEntry.setSampleRate(firstHeader.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(1536);
decoderConfigDescriptor.setMaxBitRate(maxBitrate);
decoderConfigDescriptor.setAvgBitRate(avgBitrate);
AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig();
audioSpecificConfig.setOriginalAudioObjectType(2); // AAC LC
audioSpecificConfig.setSamplingFrequencyIndex(firstHeader.sampleFrequencyIndex);
audioSpecificConfig.setChannelConfiguration(firstHeader.channelconfig);
decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig);
descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor);
esds.setEsDescriptor(descriptor);
audioSampleEntry.addBox(esds);
stsd.addBox(audioSampleEntry);
}
return stsd;
}
void waitForFirstSample() {
try {
gotFirstSample.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public long getTimescale() {
waitForFirstSample();
return firstHeader.sampleRate;
}
public String getHandler() {
return "soun";
}
public String getLanguage() {
return lang;
}
public void setLanguage(String lang) {
this.lang = lang;
}
public void close() throws IOException {
closed = true;
is.close();
}
public Void call() throws Exception {
AdtsHeader header;
int i = 1;
try {
while ((header = readADTSHeader(is)) != null) {
if (firstHeader == null) {
firstHeader = header;
gotFirstSample.countDown();
}
byte[] frame = new byte[header.frameLength - header.getSize()];
int n = 0;
while (n < frame.length) {
int count = is.read(frame, n, frame.length - n);
if (count < 0)
throw new EOFException();
n += count;
}
//System.err.println("Sample " + i++);
sampleSink.acceptSample(new StreamingSampleImpl(ByteBuffer.wrap(frame), 1024), this);
}
} catch (EOFException e) {
LOG.info("Done reading ADTS AAC file.");
}
return null;
}
@Override
public String toString() {
TrackIdTrackExtension trackIdTrackExtension = this.getTrackExtension(TrackIdTrackExtension.class);
if (trackIdTrackExtension != null) {
return "AdtsAacStreamingTrack{trackId=" + trackIdTrackExtension.getTrackId() + "}";
} else {
return "AdtsAacStreamingTrack{}";
}
}
}