diff --git a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java index 8d46bbbfae..551513161f 100644 --- a/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java +++ b/demo/src/main/java/com/google/android/exoplayer/demo/Samples.java @@ -43,8 +43,11 @@ import java.util.Locale; } public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] { - new Sample("XXXXXXXXX", - "http://178.33.229.111/live/mp4:Videolina/playlist.m3u8", PlayerActivity.TYPE_HLS), + new Sample("Google Glass", + "http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?" + + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&" + + "ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7." + + "8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", PlayerActivity.TYPE_DASH), new Sample("Google Play", "http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&" diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index a380d96817..7e127ee1eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -24,7 +24,7 @@ import com.google.android.exoplayer.extractor.ExtractorOutput; import com.google.android.exoplayer.extractor.PositionHolder; import com.google.android.exoplayer.extractor.SeekMap; import com.google.android.exoplayer.extractor.TrackOutput; -import com.google.android.exoplayer.util.MimeTypes; +import com.google.android.exoplayer.util.MpegAudioHeader; import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.Util; @@ -43,20 +43,10 @@ public final class Mp3Extractor implements Extractor { /** Mask that includes the audio header values that must match between frames. */ private static final int HEADER_MASK = 0xFFFE0C00; private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); - public static final String[] MIME_TYPE_BY_LAYER = - new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; private static final int XING_HEADER = Util.getIntegerCodeForString("Xing"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI"); - /** - * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 - * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * - * 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. - * The next power of two size is 4 KiB. - */ - public static final int MAX_FRAME_SIZE_BYTES = 4096; - private final BufferingInput inputBuffer; private final ParsableByteArray scratch; private final MpegAudioHeader synchronizedHeader; @@ -74,7 +64,7 @@ public final class Mp3Extractor implements Extractor { /** Constructs a new {@link Mp3Extractor}. */ public Mp3Extractor() { - inputBuffer = new BufferingInput(MAX_FRAME_SIZE_BYTES * 3); + inputBuffer = new BufferingInput(MpegAudioHeader.MAX_FRAME_SIZE_BYTES * 3); scratch = new ParsableByteArray(4); synchronizedHeader = new MpegAudioHeader(); } @@ -255,10 +245,9 @@ public final class Mp3Extractor implements Extractor { if (seeker == null) { setupSeeker(extractorInput, headerPosition); extractorOutput.seekMap(seeker); - trackOutput.format(MediaFormat.createAudioFormat( - MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], MAX_FRAME_SIZE_BYTES, - seeker.getDurationUs(), synchronizedHeader.channels, synchronizedHeader.sampleRate, - Collections.emptyList())); + trackOutput.format(MediaFormat.createAudioFormat(synchronizedHeader.mimeType, + MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), synchronizedHeader.channels, + synchronizedHeader.sampleRate, Collections.emptyList())); } return headerPosition; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java index 9d096607cb..49cb846cd7 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/VbriSeeker.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer.extractor.mp3; +import com.google.android.exoplayer.util.MpegAudioHeader; import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.Util; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java index 7ddb006705..fd02971a96 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/XingSeeker.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer.extractor.mp3; import com.google.android.exoplayer.C; +import com.google.android.exoplayer.util.MpegAudioHeader; import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.Util; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java index 8bf436a48d..8121c54901 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsReader.java @@ -130,7 +130,7 @@ import java.util.Collections; * Locates the next sync word, advancing the position to the byte that immediately follows it. * If a sync word was not located, the position is advanced to the limit. * - * @param pesBuffer The buffer in which to search for the sync word. + * @param pesBuffer The buffer whose position should be advanced. * @return True if a sync word position was found. False otherwise. */ private boolean skipToNextSync(ParsableByteArray pesBuffer) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java similarity index 53% rename from library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java rename to library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java index 6312ea0add..005c687144 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpaReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/MpegAudioReader.java @@ -18,8 +18,7 @@ package com.google.android.exoplayer.extractor.ts; import com.google.android.exoplayer.C; import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.extractor.TrackOutput; -import com.google.android.exoplayer.extractor.mp3.Mp3Extractor; -import com.google.android.exoplayer.extractor.mp3.MpegAudioHeader; +import com.google.android.exoplayer.util.MpegAudioHeader; import com.google.android.exoplayer.util.ParsableByteArray; import java.util.Collections; @@ -27,7 +26,7 @@ import java.util.Collections; /** * Parses a continuous MPEG Audio byte stream and extracts individual frames. */ -/* package */ public class MpaReader extends ElementaryStreamReader { +/* package */ class MpegAudioReader extends ElementaryStreamReader { private static final int STATE_FINDING_HEADER = 0; private static final int STATE_READING_HEADER = 1; @@ -36,33 +35,35 @@ import java.util.Collections; private static final int HEADER_SIZE = 4; private final ParsableByteArray headerScratch; + private final MpegAudioHeader header; private int state; - private int bytesRead; + private int frameBytesRead; + private boolean hasOutputFormat; - // Used to find the header. + // Used when finding the frame header. private boolean lastByteWasFF; - // Used when parsing the header. - private boolean hasOutputFormat; + // Parsed from the frame header. private long frameDurationUs; - private int sampleSize; + private int frameSize; - // Used when reading the samples. + // The timestamp to attach to the next sample in the current packet. private long timeUs; - public MpaReader(TrackOutput output) { + public MpegAudioReader(TrackOutput output) { super(output); state = STATE_FINDING_HEADER; // The first byte of an MPEG Audio frame header is always 0xFF. headerScratch = new ParsableByteArray(4); headerScratch.data[0] = (byte) 0xFF; + header = new MpegAudioHeader(); } @Override public void seek() { state = STATE_FINDING_HEADER; - bytesRead = 0; + frameBytesRead = 0; lastByteWasFF = false; } @@ -74,19 +75,13 @@ import java.util.Collections; while (data.bytesLeft() > 0) { switch (state) { case STATE_FINDING_HEADER: - if (findHeader(data)) { - state = STATE_READING_HEADER; - } + findHeader(data); break; case STATE_READING_HEADER: - if (readHeaderRemainder(data)) { - state = STATE_READING_FRAME; - } + readHeaderRemainder(data); break; case STATE_READING_FRAME: - if (readFrame(data)) { - state = STATE_FINDING_HEADER; - } + readFrameRemainder(data); break; } } @@ -100,78 +95,83 @@ import java.util.Collections; /** * Attempts to locate the start of the next frame header. *

- * If a frame header is located then true is returned. The first two bytes of the header will have - * been written into {@link #headerScratch}, and the position of the source will have been - * advanced to the byte that immediately follows these two bytes. + * If a frame header is located then the state is changed to {@link #STATE_READING_HEADER}, the + * first two bytes of the header are written into {@link #headerScratch}, and the position of the + * source is advanced to the byte that immediately follows these two bytes. *

- * If a frame header is not located then the position of the source will have been advanced to the - * limit, and the method should be called again with the next source to continue the search. + * If a frame header is not located then the position of the source is advanced to the limit, and + * the method should be called again with the next source to continue the search. * * @param source The source from which to read. - * @return True if the frame header was located. False otherwise. */ - private boolean findHeader(ParsableByteArray source) { - byte[] mpaData = source.data; + private void findHeader(ParsableByteArray source) { + byte[] data = source.data; int startOffset = source.getPosition(); int endOffset = source.limit(); for (int i = startOffset; i < endOffset; i++) { - boolean byteIsFF = (mpaData[i] & 0xFF) == 0xFF; - boolean found = lastByteWasFF && (mpaData[i] & 0xF0) == 0xF0; + boolean byteIsFF = (data[i] & 0xFF) == 0xFF; + boolean found = lastByteWasFF && (data[i] & 0xE0) == 0xE0; lastByteWasFF = byteIsFF; if (found) { source.setPosition(i + 1); // Reset lastByteWasFF for next time. lastByteWasFF = false; - headerScratch.data[0] = (byte) 0xFF; - headerScratch.data[1] = mpaData[i]; - bytesRead = 2; - return true; + headerScratch.data[1] = data[i]; + frameBytesRead = 2; + state = STATE_READING_HEADER; + return; } } source.setPosition(endOffset); - return false; } /** * Attempts to read the remaining two bytes of the frame header. *

- * If a frame header is read in full then true is returned. The media format will have been output - * if this has not previously occurred, the four header bytes will have been output as sample - * data, and the position of the source will have been advanced to the byte that immediately + * If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME}, + * the media format is output if this has not previously occurred, the four header bytes are + * output as sample data, and the position of the source is advanced to the byte that immediately * follows the header. *

- * If a frame header is not read in full then the position of the source will have been advanced - * to the limit, and the method should be called again with the next source to continue the read. + * If a frame header is read in full but cannot be parsed then the state is changed to + * {@link #STATE_READING_HEADER}. + *

+ * If a frame header is not read in full then the position of the source is advanced to the limit, + * and the method should be called again with the next source to continue the read. * * @param source The source from which to read. - * @return True if the frame header was read in full. False otherwise. */ - private boolean readHeaderRemainder(ParsableByteArray source) { - int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - bytesRead); - source.readBytes(headerScratch.data, bytesRead, bytesToRead); - bytesRead += bytesToRead; - if (bytesRead < HEADER_SIZE) { - return false; + private void readHeaderRemainder(ParsableByteArray source) { + int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead); + source.readBytes(headerScratch.data, frameBytesRead, bytesToRead); + frameBytesRead += bytesToRead; + if (frameBytesRead < HEADER_SIZE) { + // We haven't read the whole header yet. + return; } + headerScratch.setPosition(0); + boolean parsedHeader = MpegAudioHeader.populateHeader(headerScratch.readInt(), header); + if (!parsedHeader) { + // We thought we'd located a frame header, but we hadn't. + frameBytesRead = 0; + state = STATE_READING_HEADER; + return; + } + + frameSize = header.frameSize; if (!hasOutputFormat) { - headerScratch.setPosition(0); - int headerInt = headerScratch.readInt(); - MpegAudioHeader synchronizedHeader = new MpegAudioHeader(); - MpegAudioHeader.populateHeader(headerInt, synchronizedHeader); - MediaFormat mediaFormat = MediaFormat.createAudioFormat( - Mp3Extractor.MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], Mp3Extractor.MAX_FRAME_SIZE_BYTES, - C.UNKNOWN_TIME_US, synchronizedHeader.channels, synchronizedHeader.sampleRate, - Collections.emptyList()); + frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate; + MediaFormat mediaFormat = MediaFormat.createAudioFormat(header.mimeType, + MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, header.channels, + header.sampleRate, Collections.emptyList()); output.format(mediaFormat); hasOutputFormat = true; - frameDurationUs = (C.MICROS_PER_SECOND * synchronizedHeader.samplesPerFrame) / mediaFormat.sampleRate; - sampleSize = synchronizedHeader.frameSize; } headerScratch.setPosition(0); output.sampleData(headerScratch, HEADER_SIZE); - return true; + state = STATE_READING_FRAME; } /** @@ -185,20 +185,20 @@ import java.util.Collections; * limit, and the method should be called again with the next source to continue the read. * * @param source The source from which to read. - * @return True if the frame was read in full. False otherwise. */ - private boolean readFrame(ParsableByteArray source) { - int bytesToRead = Math.min(source.bytesLeft(), sampleSize - bytesRead); + private void readFrameRemainder(ParsableByteArray source) { + int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead); output.sampleData(source, bytesToRead); - bytesRead += bytesToRead; - if (bytesRead < sampleSize) { - return false; + frameBytesRead += bytesToRead; + if (frameBytesRead < frameSize) { + // We haven't read the whole of the frame yet. + return; } - output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null); + output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, frameSize, 0, null); timeUs += frameDurationUs; - bytesRead = 0; - return true; + frameBytesRead = 0; + state = STATE_FINDING_HEADER; } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index 30a7a1706d..9d30be0f0c 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -354,10 +354,10 @@ public final class TsExtractor implements Extractor, SeekMap { ElementaryStreamReader pesPayloadReader = null; switch (streamType) { case TS_STREAM_TYPE_MPA: - pesPayloadReader = new MpaReader(output.track(TS_STREAM_TYPE_MPA)); + pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA)); break; case TS_STREAM_TYPE_MPA_LSF: - pesPayloadReader = new MpaReader(output.track(TS_STREAM_TYPE_MPA_LSF)); + pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA_LSF)); break; case TS_STREAM_TYPE_AAC: pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC)); diff --git a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java index c61e6db5a4..fa212d2a19 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/util/CodecSpecificDataUtil.java @@ -41,22 +41,6 @@ public final class CodecSpecificDataUtil { private CodecSpecificDataUtil() {} - /** - * Gets the sample rate index. - * - * @param sampleRate The sample rate in Hz. - * @return The sample rate index. - */ - public static int getSampleRateIndex(int sampleRate) { - int sampleRateIndex = 0; - for (; sampleRateIndex < AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE.length; sampleRateIndex++) { - if (AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[sampleRateIndex] == sampleRate) { - return sampleRateIndex; - } - } - return -1; - } - /** * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * @@ -65,24 +49,13 @@ public final class CodecSpecificDataUtil { */ public static Pair parseAacAudioSpecificConfig(byte[] audioSpecificConfig) { int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F; - if (audioObjectType < 31) { - int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; - int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 | - ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); - Assertions.checkState(frequencyIndex < 13); - int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[ - (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF]; - return Pair.create(sampleRate, channelCount); - } else { - int frequencyIndex = (audioSpecificConfig[1] & 0x1E) >> 1; - Assertions.checkState(frequencyIndex < 13); - int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; - int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[ - (audioSpecificConfig[1] & 0x01) << 3 | - ((audioSpecificConfig[2] >> 5) & 0x07)]; - return Pair.create(sampleRate, channelCount); - } + int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0; + int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 + | ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1); + Assertions.checkState(frequencyIndex < 13); + int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex]; + int channelCount = (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF; + return Pair.create(sampleRate, channelCount); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/MpegAudioHeader.java b/library/src/main/java/com/google/android/exoplayer/util/MpegAudioHeader.java similarity index 77% rename from library/src/main/java/com/google/android/exoplayer/extractor/mp3/MpegAudioHeader.java rename to library/src/main/java/com/google/android/exoplayer/util/MpegAudioHeader.java index 9f6e5c982e..2606235883 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/MpegAudioHeader.java +++ b/library/src/main/java/com/google/android/exoplayer/util/MpegAudioHeader.java @@ -13,11 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.android.exoplayer.extractor.mp3; +package com.google.android.exoplayer.util; -/** Parsed MPEG audio frame header. */ +/** + * Representation of an MPEG audio frame header. + */ public final class MpegAudioHeader { + /** + * Theoretical maximum frame size for an MPEG audio stream, which occurs when playing a Layer 2 + * MPEG 2.5 audio stream at 16 kb/s (with padding). The size is 1152 sample/frame * + * 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/frame. + * The next power of two size is 4 KiB. + */ + public static final int MAX_FRAME_SIZE_BYTES = 4096; + + private static final String[] MIME_TYPE_BY_LAYER = + new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; private static final int[] BITRATE_V1_L1 = {32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}; @@ -30,7 +42,9 @@ public final class MpegAudioHeader { private static final int[] BITRATE_V2 = {8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}; - /** Returns the size of the frame associated with {@code header}, or -1 if it is invalid. */ + /** + * Returns the size of the frame associated with {@code header}, or -1 if it is invalid. + */ public static int getFrameSize(int header) { if ((header & 0xFFE00000) != 0xFFE00000) { return -1; @@ -92,35 +106,37 @@ public final class MpegAudioHeader { } /** - * Returns the header represented by {@code header}, if it is valid; {@code null} otherwise. + * Parses {@code headerData}, populating {@code header} with the parsed data. * * @param headerData Header data to parse. * @param header Header to populate with data from {@code headerData}. + * @return True if the header was populated. False otherwise, indicating that {@code headerData} + * is not a valid MPEG audio header. */ - public static void populateHeader(int headerData, MpegAudioHeader header) { + public static boolean populateHeader(int headerData, MpegAudioHeader header) { if ((headerData & 0xFFE00000) != 0xFFE00000) { - return; + return false; } int version = (headerData >>> 19) & 3; if (version == 1) { - return; + return false; } int layer = (headerData >>> 17) & 3; if (layer == 0) { - return; + return false; } int bitrateIndex = (headerData >>> 12) & 15; if (bitrateIndex == 0 || bitrateIndex == 0xF) { // Disallow "free" bitrate. - return; + return false; } int samplingRateIndex = (headerData >>> 10) & 3; if (samplingRateIndex == 3) { - return; + return false; } int sampleRate = SAMPLING_RATE_V1[samplingRateIndex]; @@ -154,16 +170,16 @@ public final class MpegAudioHeader { } } + String mimeType = MIME_TYPE_BY_LAYER[3 - layer]; int channels = ((headerData >> 6) & 3) == 3 ? 1 : 2; - int layerIndex = 3 - layer; - header.setValues( - version, layerIndex, frameSize, sampleRate, channels, bitrate, samplesPerFrame); + header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame); + return true; } /** MPEG audio header version. */ public int version; - /** MPEG audio layer index, starting at zero. */ - public int layerIndex; + /** The mime type. */ + public String mimeType; /** Size of the frame associated with this header, in bytes. */ public int frameSize; /** Sample rate in samples per second. */ @@ -175,10 +191,10 @@ public final class MpegAudioHeader { /** Number of samples stored in the frame. */ public int samplesPerFrame; - private void setValues(int version, int layerIndex, int frameSize, int sampleRate, int channels, - int bitrate, int samplesPerFrame) { + private void setValues(int version, String mimeType, int frameSize, + int sampleRate, int channels, int bitrate, int samplesPerFrame) { this.version = version; - this.layerIndex = layerIndex; + this.mimeType = mimeType; this.frameSize = frameSize; this.sampleRate = sampleRate; this.channels = channels;