X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=src%2Fmain%2Fjava%2Fnet%2Fpterodactylus%2Fsonitus%2Fio%2Fmp3%2FFrame.java;h=b157e2cde5a704eff5cfc4e559fc80fc1c49ca7c;hb=ed7b4e864c29bbf8b5d1a394c1d41f23a3394e27;hp=1d3a3505e7d169a3ece9e46b479c5f69e71366cb;hpb=5e7624c78c82e47a5d6040ac27186e55f5ff4c8f;p=sonitus.git diff --git a/src/main/java/net/pterodactylus/sonitus/io/mp3/Frame.java b/src/main/java/net/pterodactylus/sonitus/io/mp3/Frame.java index 1d3a350..b157e2c 100644 --- a/src/main/java/net/pterodactylus/sonitus/io/mp3/Frame.java +++ b/src/main/java/net/pterodactylus/sonitus/io/mp3/Frame.java @@ -17,16 +17,18 @@ 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; /** * A single MPEG audio frame. + *

+ * This uses information from mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm. * * @author David ‘Bombe’ Roden */ @@ -130,7 +132,7 @@ public class Frame { mpegAudioVersionMapBuilder.put(MpegAudioVersion.VERSION_1, mpeg1Builder.build()); /* MPEG 2 & 2.5. */ - ImmutableBiMap.Builder> mpeg2Builder = ImmutableBiMap.builder(); + ImmutableMap.Builder> mpeg2Builder = ImmutableMap.builder(); /* Layer 1. */ bitrates = ImmutableMap.builder(); @@ -206,15 +208,46 @@ public class Frame { private final int modeExtension; /** The decoded copyright bit. */ - private final int copyright; + private final int copyrightBit; /** The deocded original bit. */ - private final int original; + private final int originalBit; /** 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 copyright, int original, 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; @@ -224,69 +257,185 @@ public class Frame { this.privateBit = privateBit; this.channelMode = channelMode; this.modeExtension = modeExtension; - this.copyright = copyright; - this.original = original; + 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 copyright != 0; + return copyrightBit != 0; } + /** + * Returns the original bit. + * + * @return {@code true} if the original bit is set, {@code false} otherwise + */ public boolean originalBit() { - return original != 0; + 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 create(byte[] buffer, int offset, int length) { if (isFrame(buffer, offset, length)) { int mpegAudioVersionId = (buffer[offset + 1] & 0x18) >>> 3; @@ -301,7 +450,8 @@ public class Frame { 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(); }