package org.jcodec.codecs.aac; import static org.jcodec.codecs.aac.ObjectType.AOT_ESCAPE; import org.jcodec.codecs.mpeg4.mp4.EsdsBox; import org.jcodec.common.AudioFormat; import org.jcodec.common.io.BitReader; import org.jcodec.common.io.BitWriter; import org.jcodec.common.model.ChannelLabel; import org.jcodec.containers.mp4.boxes.Box.LeafBox; import org.jcodec.containers.mp4.boxes.NodeBox; import org.jcodec.containers.mp4.boxes.SampleEntry; import java.lang.IllegalArgumentException; import java.nio.ByteBuffer; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author The JCodec project * */ public class AACUtils { public static class AACMetadata { private AudioFormat format; private ChannelLabel[] labels; public AACMetadata(AudioFormat format, ChannelLabel[] labels) { this.format = format; this.labels = labels; } public AudioFormat getFormat() { return format; } public ChannelLabel[] getLabels() { return labels; } } private static int getObjectType(BitReader reader) { int objectType = reader.readNBit(5); if (objectType == AOT_ESCAPE.ordinal()) objectType = 32 + reader.readNBit(6); return objectType; } private static ChannelLabel[][] AAC_DEFAULT_CONFIGS = { null, // { ChannelLabel.MONO }, // { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT }, // { ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT }, // { ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_CENTER }, // { ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT }, // { ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT, ChannelLabel.LFE }, // { ChannelLabel.CENTER, ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.SIDE_LEFT, ChannelLabel.SIDE_RIGHT, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT, ChannelLabel.LFE } // }; public static AACMetadata parseAudioInfo(ByteBuffer privData) { BitReader reader = BitReader.createBitReader(privData); int objectType = getObjectType(reader); int index = reader.readNBit(4); int sampleRate = index == 0x0f ? reader.readNBit(24) : AACConts.AAC_SAMPLE_RATES[index]; int channelConfig = reader.readNBit(4); if (channelConfig == 0 || channelConfig >= AAC_DEFAULT_CONFIGS.length) return null; ChannelLabel[] channels = AAC_DEFAULT_CONFIGS[channelConfig]; return new AACMetadata(new AudioFormat(sampleRate, 16, channels.length, true, false), channels); } public static AACMetadata getMetadata(SampleEntry mp4a) { if (!"mp4a".equals(mp4a.getFourcc())) throw new IllegalArgumentException("Not mp4a sample entry"); ByteBuffer b = getCodecPrivate(mp4a); if (b == null) return null; return parseAudioInfo(b); } public static ByteBuffer getCodecPrivate(SampleEntry mp4a) { LeafBox b = NodeBox.findFirst(mp4a, LeafBox.class, "esds"); if (b == null) { b = NodeBox.findFirstPath(mp4a, LeafBox.class, new String[] { null, "esds" }); } if (b == null) return null; EsdsBox esds = EsdsBox.newEsdsBox(); esds.parse(b.getData()); return esds.getStreamInfo(); } public static ByteBuffer adtsToStreamInfo(org.jcodec.codecs.aac.ADTSParser.Header hdr) { ByteBuffer si = ByteBuffer.allocate(2); BitWriter wr = new BitWriter(si); wr.writeNBit(hdr.getObjectType(), 5); wr.writeNBit(hdr.getSamplingIndex(), 4); wr.writeNBit(hdr.getChanConfig(), 4); wr.flush(); si.clear(); return si; } public static org.jcodec.codecs.aac.ADTSParser.Header streamInfoToADTS(ByteBuffer si, boolean crcAbsent, int numAACFrames, int frameSize) { BitReader rd = BitReader.createBitReader(si.duplicate()); int objectType = rd.readNBit(5); int samplingIndex = rd.readNBit(4); int chanConfig = rd.readNBit(4); return new ADTSParser.Header(objectType, chanConfig, crcAbsent ? 1 : 0, numAACFrames, samplingIndex, 7 + frameSize); } }