package net.pterodactylus.sonitus.io.mp3;
+import java.util.Arrays;
import java.util.Map;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
-import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
/**
mpegAudioVersionMapBuilder.put(MpegAudioVersion.VERSION_1, mpeg1Builder.build());
/* MPEG 2 & 2.5. */
- ImmutableBiMap.Builder<LayerDescription, Map<Integer, Integer>> mpeg2Builder = ImmutableBiMap.builder();
+ ImmutableMap.Builder<LayerDescription, Map<Integer, Integer>> mpeg2Builder = ImmutableMap.builder();
/* Layer 1. */
bitrates = ImmutableMap.builder();
/** The decoded emphasis mode. */
private final int emphasis;
- private Frame(int mpegAudioVersionId, int layerDescription, int protectionBit, int bitrateIndex, int samplingRateFrequencyIndex, int paddingBit, int privateBit, int channelMode, int modeExtension, int copyrightBit, int originalBit, int emphasis) {
+ /** The content of the frame. */
+ private final byte[] content;
+
+ /**
+ * Creates a new frame from the given values.
+ *
+ * @param mpegAudioVersionId
+ * The MPEG audio version ID
+ * @param layerDescription
+ * The layer description
+ * @param protectionBit
+ * The protection bit
+ * @param bitrateIndex
+ * The bitrate index
+ * @param samplingRateFrequencyIndex
+ * The sampling rate frequency index
+ * @param paddingBit
+ * The padding bit
+ * @param privateBit
+ * The private bit
+ * @param channelMode
+ * The channel mode
+ * @param modeExtension
+ * The mode extension
+ * @param copyrightBit
+ * The copyright bit
+ * @param originalBit
+ * The original bit
+ * @param emphasis
+ * The emphasis
+ */
+ private Frame(int mpegAudioVersionId, int layerDescription, int protectionBit, int bitrateIndex, int samplingRateFrequencyIndex, int paddingBit, int privateBit, int channelMode, int modeExtension, int copyrightBit, int originalBit, int emphasis, byte[] content) {
this.mpegAudioVersionId = mpegAudioVersionId;
this.layerDescription = layerDescription;
this.protectionBit = protectionBit;
this.copyrightBit = copyrightBit;
this.originalBit = originalBit;
this.emphasis = emphasis;
+ this.content = content;
}
//
// ACCESSORS
//
+ /**
+ * Returns the MPEG audio version.
+ *
+ * @return The MPEG audio version
+ */
public MpegAudioVersion mpegAudioVersion() {
return MpegAudioVersion.values()[mpegAudioVersionId];
}
+ /**
+ * Returns the layer description.
+ *
+ * @return The layer description
+ */
public LayerDescription layerDescription() {
return LayerDescription.values()[layerDescription];
}
+ /**
+ * Returns the protection bit.
+ *
+ * @return {@code true} if the protection bit is set, {@code false} otherwise
+ */
public boolean protectionBit() {
return protectionBit != 0;
}
+ /**
+ * Returns the bitrate of this frame.
+ *
+ * @return The bitrate of this frame (in kbps)
+ */
public int bitrate() {
return bitrateSupplier.get().get(mpegAudioVersion()).get(layerDescription()).get(bitrateIndex);
}
+ /**
+ * Returns the sampling rate of the audio data in this frame.
+ *
+ * @return The sample rate (in Hertz)
+ */
public int samplingRate() {
return samplingRateSupplier.get().get(mpegAudioVersion()).get(samplingRateFrequencyIndex);
}
+ /**
+ * Returns the padding bit.
+ *
+ * @return {@code true} if the padding bit is set, {@code false} otherwise
+ */
public boolean paddingBit() {
return paddingBit != 0;
}
+ /**
+ * Returns the private bit.
+ *
+ * @return {@code true} if the private bit is set, {@code false} otherwise
+ */
public boolean privateBit() {
return privateBit != 0;
}
+ /**
+ * Returns the channel mode.
+ *
+ * @return The channel mode
+ */
public ChannelMode channelMode() {
return ChannelMode.values()[channelMode];
}
/* TODO - mode extension. */
+ /**
+ * Returns the copyright bit.
+ *
+ * @return {@code true} if the copyright bit is set, {@code false} otherwise
+ */
public boolean copyrightBit() {
return copyrightBit != 0;
}
+ /**
+ * Returns the original bit.
+ *
+ * @return {@code true} if the original bit is set, {@code false} otherwise
+ */
public boolean originalBit() {
return originalBit != 0;
}
+ /**
+ * Returns the emphasis.
+ *
+ * @return The emphasis
+ */
public Emphasis emphasis() {
return Emphasis.values()[emphasis];
}
+ /**
+ * Returns the content of this frame.
+ *
+ * @return The content of this frame
+ */
+ public byte[] content() {
+ return content;
+ }
+
//
// STATIC METHODS
//
+ /**
+ * Returns whether the data beginning at the given offset is an MPEG audio
+ * frame.
+ *
+ * @param buffer
+ * The buffer in which the data is stored
+ * @param offset
+ * The beginning of the data to parse
+ * @param length
+ * The length of the data to parse
+ * @return {@code true} if the data at the given offset is an MPEG audio frame
+ */
public static boolean isFrame(byte[] buffer, int offset, int length) {
- return (((buffer[offset] & 0xff) == 0xff) && ((buffer[offset + 1] & 0xe0) == 0xe0));
+ return (length > 3) && (((buffer[offset] & 0xff) == 0xff) && ((buffer[offset + 1] & 0xe0) == 0xe0));
+ }
+
+ /**
+ * Calculates the frame length in bytes for the frame starting at the given
+ * offset in the given buffer. This method should only be called for a buffer
+ * and an offset for which {@link #isFrame(byte[], int, int)} returns {@code
+ * true}.
+ *
+ * @param buffer
+ * The buffer storing the frame
+ * @param offset
+ * The offset of the frame
+ * @return The length of the frame in bytes, or {@code -1} if the frame length
+ * can not be calculated
+ */
+ public static int getFrameLength(byte[] buffer, int offset) {
+ MpegAudioVersion mpegAudioVersion = MpegAudioVersion.values()[(buffer[offset + 1] & 0x18) >>> 3];
+ LayerDescription layerDescription = LayerDescription.values()[(buffer[offset + 1] & 0x06) >>> 1];
+ int bitrate = bitrateSupplier.get().get(mpegAudioVersion).get(layerDescription).get((buffer[offset + 2] & 0xf0) >>> 4) * 1000;
+ int samplingRate = samplingRateSupplier.get().get(mpegAudioVersion).get((buffer[offset + 2] & 0x0c) >>> 2);
+ int paddingBit = (buffer[offset + 2] & 0x02) >>> 1;
+ if (layerDescription == LayerDescription.LAYER_1) {
+ return (12 * bitrate / samplingRate + paddingBit) * 4;
+ } else if ((layerDescription == LayerDescription.LAYER_2) || (layerDescription == LayerDescription.LAYER_3)) {
+ return 144 * bitrate / samplingRate + paddingBit;
+ }
+ return -1;
}
+ /**
+ * Tries to create an MPEG audio from the given data.
+ *
+ * @param buffer
+ * The buffer in which the data is stored
+ * @param offset
+ * The offset at which to look for a frame
+ * @param length
+ * The length of the data in the buffer
+ * @return The frame if it could be parsed, or {@link Optional#absent()} if no
+ * frame could be found
+ */
public static Optional<Frame> create(byte[] buffer, int offset, int length) {
if (isFrame(buffer, offset, length)) {
int mpegAudioVersionId = (buffer[offset + 1] & 0x18) >>> 3;
int copyright = (buffer[offset + 3] & 0x08) >> 3;
int original = (buffer[offset + 3] & 0x04) >> 2;
int emphasis = buffer[offset + 3] & 0x03;
- return Optional.of(new Frame(mpegAudioVersionId, layerDescription, protectionBit, bitrateIndex, samplingRateFrequencyIndex, paddingBit, privateBit, channelMode, modeExtension, copyright, original, emphasis));
+ int frameLength = getFrameLength(buffer, offset);
+ return Optional.of(new Frame(mpegAudioVersionId, layerDescription, protectionBit, bitrateIndex, samplingRateFrequencyIndex, paddingBit, privateBit, channelMode, modeExtension, copyright, original, emphasis, Arrays.copyOfRange(buffer, 4, frameLength)));
}
return Optional.absent();
}