package org.jcodec.containers.mp4.boxes;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.jcodec.common.AudioFormat;
import org.jcodec.common.model.ChannelLabel;
import org.jcodec.common.tools.ToJSON;
import org.jcodec.containers.mp4.boxes.EndianBox.Endian;
import org.jcodec.containers.mp4.boxes.channel.ChannelUtils;
import org.jcodec.containers.mp4.boxes.channel.Label;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Describes audio payload sample
*
* @author The JCodec project
*
*/
public class AudioSampleEntry extends SampleEntry {
public static int kAudioFormatFlagIsFloat = (1 << 0); // 0x1
public static int kAudioFormatFlagIsBigEndian = (1 << 1); // 0x2
public static int kAudioFormatFlagIsSignedInteger = (1 << 2); // 0x4
public static int kAudioFormatFlagIsPacked = (1 << 3); // 0x8
public static int kAudioFormatFlagIsAlignedHigh = (1 << 4); // 0x10
public static int kAudioFormatFlagIsNonInterleaved = (1 << 5); // 0x20
public static int kAudioFormatFlagIsNonMixable = (1 << 6); // 0x40
private static final MyFactory FACTORY = new MyFactory();
private short channelCount;
private short sampleSize;
private float sampleRate;
private short revision;
private int vendor;
private int compressionId;
private int pktSize;
private int samplesPerPkt;
private int bytesPerPkt;
private int bytesPerFrame;
private int bytesPerSample;
private short version;
private int lpcmFlags;
public AudioSampleEntry(Header atom) {
super(atom);
factory = FACTORY;
}
public AudioSampleEntry(Header header, short drefInd, short channelCount, short sampleSize, int sampleRate,
short revision, int vendor, int compressionId, int pktSize, int samplesPerPkt, int bytesPerPkt,
int bytesPerFrame, int bytesPerSample, short version) {
super(header, drefInd);
this.channelCount = channelCount;
this.sampleSize = sampleSize;
this.sampleRate = sampleRate;
this.revision = revision;
this.vendor = vendor;
this.compressionId = compressionId;
this.pktSize = pktSize;
this.samplesPerPkt = samplesPerPkt;
this.bytesPerPkt = bytesPerPkt;
this.bytesPerFrame = bytesPerFrame;
this.bytesPerSample = bytesPerSample;
this.version = version;
}
public void parse(ByteBuffer input) {
super.parse(input);
version = input.getShort();
revision = input.getShort();
vendor = input.getInt();
channelCount = input.getShort();
sampleSize = input.getShort();
compressionId = input.getShort();
pktSize = input.getShort();
long sr = input.getInt() & 0xffffffffL;
sampleRate = (float) sr / 65536f;
if (version == 1) {
samplesPerPkt = input.getInt();
bytesPerPkt = input.getInt();
bytesPerFrame = input.getInt();
bytesPerSample = input.getInt();
} else if (version == 2) {
input.getInt(); /* sizeof struct only */
sampleRate = (float) Double.longBitsToDouble(input.getLong());
channelCount = (short) input.getInt();
input.getInt(); /* always 0x7F000000 */
sampleSize = (short) input.getInt();
lpcmFlags = (int) input.getInt();
bytesPerFrame = (int) input.getInt();
samplesPerPkt = (int) input.getInt();
}
parseExtensions(input);
}
protected void doWrite(ByteBuffer out) {
super.doWrite(out);
out.putShort(version);
out.putShort(revision);
out.putInt(vendor);
if (version < 2) {
out.putShort(channelCount);
if (version == 0)
out.putShort(sampleSize);
else
out.putShort((short) 16);
out.putShort((short) compressionId);
out.putShort((short) pktSize);
out.putInt((int) Math.round(sampleRate * 65536d));
if (version == 1) {
out.putInt(samplesPerPkt);
out.putInt(bytesPerPkt);
out.putInt(bytesPerFrame);
out.putInt(bytesPerSample);
}
} else if (version == 2) {
out.putShort((short)3);
out.putShort((short)16);
out.putShort((short)-2);
out.putShort((short)0);
out.putInt(65536);
out.putInt(72);
out.putLong(Double.doubleToLongBits(sampleRate));
out.putInt(channelCount);
out.putInt(0x7F000000);
out.putInt(sampleSize);
out.putInt(lpcmFlags);
out.putInt(bytesPerFrame);
out.putInt(samplesPerPkt);
}
writeExtensions(out);
}
public short getChannelCount() {
return channelCount;
}
public int calcFrameSize() {
if (version == 0 || bytesPerFrame == 0)
return (sampleSize >> 3) * channelCount;
else
return bytesPerFrame;
}
public int calcSampleSize() {
return calcFrameSize() / channelCount;
}
public short getSampleSize() {
return sampleSize;
}
public float getSampleRate() {
return sampleRate;
}
public int getBytesPerFrame() {
return bytesPerFrame;
}
public int getBytesPerSample() {
return bytesPerSample;
}
public short getVersion() {
return version;
}
public static class MyFactory extends BoxFactory {
private Map<String, Class<? extends Box>> mappings = new HashMap<String, Class<? extends Box>>();
public MyFactory() {
mappings.put(WaveExtension.fourcc(), WaveExtension.class);
mappings.put(ChannelBox.fourcc(), ChannelBox.class);
mappings.put("esds", LeafBox.class);
}
public Class<? extends Box> toClass(String fourcc) {
return mappings.get(fourcc);
}
}
public Endian getEndian() {
EndianBox endianBox = Box.findFirst(this, EndianBox.class, WaveExtension.fourcc(), EndianBox.fourcc());
if (endianBox == null) {
if ("twos".equals(header.getFourcc()))
return Endian.BIG_ENDIAN;
else if ("lpcm".equals(header.getFourcc()))
return (lpcmFlags & kAudioFormatFlagIsBigEndian) != 0 ? Endian.BIG_ENDIAN : Endian.LITTLE_ENDIAN;
else if ("sowt".equals(header.getFourcc()))
return Endian.LITTLE_ENDIAN;
else
return Endian.BIG_ENDIAN;
}
return endianBox.getEndian();
}
public boolean isFloat() {
return "fl32".equals(header.getFourcc()) || "fl64".equals(header.getFourcc())
|| ("lpcm".equals(header.getFourcc()) && (lpcmFlags & kAudioFormatFlagIsFloat) != 0);
}
public static Set<String> pcms = new HashSet<String>();
static {
pcms.add("raw ");
pcms.add("twos");
pcms.add("sowt");
pcms.add("fl32");
pcms.add("fl64");
pcms.add("in24");
pcms.add("in32");
pcms.add("lpcm");
}
public boolean isPCM() {
return pcms.contains(header.getFourcc());
}
public AudioFormat getFormat() {
return new AudioFormat((int)sampleRate, calcSampleSize() << 3, channelCount, true, getEndian() == Endian.BIG_ENDIAN);
}
@Override
public void dump(StringBuilder sb) {
sb.append(header.getFourcc() + ": {\n");
sb.append("entry: ");
ToJSON.toJSON(this, sb, "channelCount", "sampleSize", "sampleRat", "revision", "vendor", "compressionId",
"pktSize", "samplesPerPkt", "bytesPerPkt", "bytesPerFrame", "bytesPerSample", "version", "lpcmFlags");
sb.append(",\nexts: [\n");
dumpBoxes(sb);
sb.append("\n]\n");
sb.append("}\n");
}
public ChannelLabel[] getLabels() {
ChannelBox channelBox = Box.findFirst(this, ChannelBox.class, "chan");
if (channelBox != null) {
Label[] labels = ChannelUtils.getLabels(channelBox);
if (channelCount == 2)
return translate(translationStereo, labels);
else
return translate(translationSurround, labels);
} else {
switch (channelCount) {
case 1:
return new ChannelLabel[] { ChannelLabel.MONO };
case 2:
return new ChannelLabel[] { ChannelLabel.STEREO_LEFT, ChannelLabel.STEREO_RIGHT };
case 6:
return new ChannelLabel[] { ChannelLabel.FRONT_LEFT, ChannelLabel.FRONT_RIGHT, ChannelLabel.CENTER,
ChannelLabel.LFE, ChannelLabel.REAR_LEFT, ChannelLabel.REAR_RIGHT };
default:
ChannelLabel[] lbl = new ChannelLabel[channelCount];
Arrays.fill(lbl, ChannelLabel.MONO);
return lbl;
}
}
}
private ChannelLabel[] translate(Map<Label, ChannelLabel> translation, Label[] labels) {
ChannelLabel[] result = new ChannelLabel[labels.length];
int i = 0;
for (Label label : labels) {
result[i++] = translation.get(label);
}
return result;
}
private static Map<Label, ChannelLabel> translationStereo = new HashMap<Label, ChannelLabel>();
private static Map<Label, ChannelLabel> translationSurround = new HashMap<Label, ChannelLabel>();
static {
translationStereo.put(Label.Left, ChannelLabel.STEREO_LEFT);
translationStereo.put(Label.Right, ChannelLabel.STEREO_RIGHT);
translationStereo.put(Label.HeadphonesLeft, ChannelLabel.STEREO_LEFT);
translationStereo.put(Label.HeadphonesRight, ChannelLabel.STEREO_RIGHT);
translationStereo.put(Label.LeftTotal, ChannelLabel.STEREO_LEFT);
translationStereo.put(Label.RightTotal, ChannelLabel.STEREO_RIGHT);
translationStereo.put(Label.LeftWide, ChannelLabel.STEREO_LEFT);
translationStereo.put(Label.RightWide, ChannelLabel.STEREO_RIGHT);
translationSurround.put(Label.Left, ChannelLabel.FRONT_LEFT);
translationSurround.put(Label.Right, ChannelLabel.FRONT_RIGHT);
translationSurround.put(Label.LeftCenter, ChannelLabel.FRONT_CENTER_LEFT);
translationSurround.put(Label.RightCenter, ChannelLabel.FRONT_CENTER_RIGHT);
translationSurround.put(Label.Center, ChannelLabel.CENTER);
translationSurround.put(Label.CenterSurround, ChannelLabel.REAR_CENTER);
translationSurround.put(Label.CenterSurroundDirect, ChannelLabel.REAR_CENTER);
translationSurround.put(Label.LeftSurround, ChannelLabel.REAR_LEFT);
translationSurround.put(Label.LeftSurroundDirect, ChannelLabel.REAR_LEFT);
translationSurround.put(Label.RightSurround, ChannelLabel.REAR_RIGHT);
translationSurround.put(Label.RightSurroundDirect, ChannelLabel.REAR_RIGHT);
translationSurround.put(Label.RearSurroundLeft, ChannelLabel.SIDE_LEFT);
translationSurround.put(Label.RearSurroundRight, ChannelLabel.SIDE_RIGHT);
translationSurround.put(Label.LFE2, ChannelLabel.LFE);
translationSurround.put(Label.LFEScreen, ChannelLabel.LFE);
translationSurround.put(Label.LeftTotal, ChannelLabel.STEREO_LEFT);
translationSurround.put(Label.RightTotal, ChannelLabel.STEREO_RIGHT);
translationSurround.put(Label.LeftWide, ChannelLabel.STEREO_LEFT);
translationSurround.put(Label.RightWide, ChannelLabel.STEREO_RIGHT);
}
}