Commit c06f844e by Oliver Woodman

SUPER!

parent ed856b2a
...@@ -43,7 +43,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -43,7 +43,7 @@ public final class Mp3Extractor implements Extractor {
/** Mask that includes the audio header values that must match between frames. */ /** Mask that includes the audio header values that must match between frames. */
private static final int HEADER_MASK = 0xFFFE0C00; private static final int HEADER_MASK = 0xFFFE0C00;
private static final int ID3_TAG = Util.getIntegerCodeForString("ID3"); private static final int ID3_TAG = Util.getIntegerCodeForString("ID3");
private static final String[] MIME_TYPE_BY_LAYER = public static final String[] MIME_TYPE_BY_LAYER =
new String[] {MimeTypes.AUDIO_MPEG_L1, MimeTypes.AUDIO_MPEG_L2, MimeTypes.AUDIO_MPEG}; 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 XING_HEADER = Util.getIntegerCodeForString("Xing");
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info"); private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
...@@ -55,7 +55,7 @@ public final class Mp3Extractor implements Extractor { ...@@ -55,7 +55,7 @@ public final class Mp3Extractor implements Extractor {
* 160000 bit/s / (8000 sample/s * 8 bit/byte) + 1 padding byte/frame = 2881 byte/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. * The next power of two size is 4 KiB.
*/ */
private static final int MAX_FRAME_SIZE_BYTES = 4096; public static final int MAX_FRAME_SIZE_BYTES = 4096;
private final BufferingInput inputBuffer; private final BufferingInput inputBuffer;
private final ParsableByteArray scratch; private final ParsableByteArray scratch;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer.extractor.mp3; package com.google.android.exoplayer.extractor.mp3;
/** Parsed MPEG audio frame header. */ /** Parsed MPEG audio frame header. */
/* package */ final class MpegAudioHeader { public final class MpegAudioHeader {
private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000}; private static final int[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
private static final int[] BITRATE_V1_L1 = private static final int[] BITRATE_V1_L1 =
......
...@@ -130,7 +130,7 @@ import java.util.Collections; ...@@ -130,7 +130,7 @@ import java.util.Collections;
* Locates the next sync word, advancing the position to the byte that immediately follows it. * 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. * If a sync word was not located, the position is advanced to the limit.
* *
* @param pesBuffer The buffer whose position should be advanced. * @param pesBuffer The buffer in which to search for the sync word.
* @return True if a sync word position was found. False otherwise. * @return True if a sync word position was found. False otherwise.
*/ */
private boolean skipToNextSync(ParsableByteArray pesBuffer) { private boolean skipToNextSync(ParsableByteArray pesBuffer) {
......
...@@ -18,285 +18,187 @@ package com.google.android.exoplayer.extractor.ts; ...@@ -18,285 +18,187 @@ package com.google.android.exoplayer.extractor.ts;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.extractor.TrackOutput; import com.google.android.exoplayer.extractor.TrackOutput;
import com.google.android.exoplayer.util.CodecSpecificDataUtil; import com.google.android.exoplayer.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.extractor.mp3.MpegAudioHeader;
import com.google.android.exoplayer.util.ParsableBitArray;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.util.Pair;
import java.util.Collections; import java.util.Collections;
/** /**
* Parses a continuous MPEG Audio byte stream and extracts individual * Parses a continuous MPEG Audio byte stream and extracts individual frames.
* frames. */
*/
/* package */ public class MpaReader extends ElementaryStreamReader { /* package */ public class MpaReader extends ElementaryStreamReader {
private static final int STATE_FINDING_SYNC = 0; private static final int STATE_FINDING_HEADER = 0;
private static final int STATE_READING_HEADER = 1; private static final int STATE_READING_HEADER = 1;
private static final int STATE_READING_SAMPLE = 2; private static final int STATE_READING_FRAME = 2;
private static final int HEADER_SIZE = 4;
private static final int CRC_SIZE = 2;
private final ParsableBitArray mpaScratch;
private int state;
private int bytesRead;
// Used to find the header.
private boolean hasCrc;
// Used when parsing the header. private static final int HEADER_SIZE = 4;
private boolean hasOutputFormat;
private long frameDurationUs;
private int sampleSize;
// Used when reading the samples. private final ParsableByteArray headerScratch;
private long timeUs;
// private int state;
/** private int bytesRead;
* sampling rates in hertz:
*
* @index MPEG Version ID
* @index sampling rate index
*/
private static final int[][] MPA_SAMPLING_RATES = new int[][] { // Used to find the header.
{11025, 12000, 8000}, // MPEG 2.5 private boolean lastByteWasFF;
{ 0, 0, 0}, // reserved
{22050, 24000, 16000}, // MPEG 2
{44100, 48000, 32000} // MPEG 1
};
/** // Used when parsing the header.
* bitrates: private boolean hasOutputFormat;
* private long frameDurationUs;
* @index LSF private int sampleSize;
* @index Layer
* @index bitrate index
*/
private static final int[][][] MPA_BITRATES = new int[][][] { // Used when reading the samples.
{ // MPEG 1 private long timeUs;
// Layer1
{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448},
// Layer2
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384},
// Layer3
{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}
},
{ // MPEG 2, 2.5
// Layer1
{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256},
// Layer2
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160},
// Layer3
{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}
}
};
/** public MpaReader(TrackOutput output) {
* Samples per Frame: super(output);
* state = STATE_FINDING_HEADER;
* @index LSF // The first byte of an MPEG Audio frame header is always 0xFF.
* @index Layer headerScratch = new ParsableByteArray(4);
*/ headerScratch.data[0] = (byte) 0xFF;
}
private static final int[][] MPA_SAMPLES_PER_FRAME = new int[][] { @Override
{ // MPEG 1 public void seek() {
384, // Layer1 state = STATE_FINDING_HEADER;
1152, // Layer2 bytesRead = 0;
1152 // Layer3 lastByteWasFF = false;
}, }
{ // MPEG 2, 2.5
384, // Layer1
1152, // Layer2
576 // Layer3
}
};
/** @Override
* Coefficients (samples per frame / 8): public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
* if (startOfPacket) {
* @index = LSF timeUs = pesTimeUs;
* @index = Layer
*/
private static final int[][] MPA_COEFFICIENTS = new int[][] {
{ // MPEG 1
12, // Layer1
144, // Layer2
144 // Layer3
},
{ // MPEG 2, 2.5
12, // Layer1
144, // Layer2
72 // Layer3
}
};
/**
* slot size per layer:
*
* @index = Layer
*/
private static final int[] MPA_SLOT_SIZE = new int[] {
4, // Layer1
1, // Layer2
1 // Layer3
};
public MpaReader(TrackOutput output) {
super(output);
mpaScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]);
state = STATE_FINDING_SYNC;
} }
while (data.bytesLeft() > 0) {
@Override switch (state) {
public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) { case STATE_FINDING_HEADER:
if (startOfPacket) { if (findHeader(data)) {
timeUs = pesTimeUs; state = STATE_READING_HEADER;
} }
while (data.bytesLeft() > 0) { break;
switch (state) { case STATE_READING_HEADER:
case STATE_FINDING_SYNC: if (readHeaderRemainder(data)) {
if (skipToNextSync(data)) { state = STATE_READING_FRAME;
bytesRead = 0; }
state = STATE_READING_HEADER; break;
} case STATE_READING_FRAME:
break; if (readFrame(data)) {
case STATE_READING_HEADER: state = STATE_FINDING_HEADER;
int targetLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; }
if (continueRead(data, mpaScratch.data, targetLength)) { break;
parseHeader(); }
bytesRead = targetLength;
state = STATE_READING_SAMPLE;
}
break;
case STATE_READING_SAMPLE:
int bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
output.sampleData(data, bytesToRead);
bytesRead += bytesToRead;
if (bytesRead == sampleSize) {
output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
timeUs += frameDurationUs;
bytesRead = 0;
state = STATE_FINDING_SYNC;
}
break;
}
}
} }
}
@Override
public void packetFinished() { @Override
// Do nothing. public void packetFinished() {
} // Do nothing.
}
/**
* Continues a read from the provided {@code source} into a given {@code target}. It's assumed /**
* that the data should be written into {@code target} starting from an offset of zero. * Attempts to locate the start of the next frame header.
* * <p>
* @param source The source from which to read. * If a frame header is located then true is returned. The first two bytes of the header will have
* @param target The target into which data is to be read. * been written into {@link #headerScratch}, and the position of the source will have been
* @param targetLength The target length of the read. * advanced to the byte that immediately follows these two bytes.
* @return Whether the target length was reached. * <p>
*/ * If a frame header is not located then the position of the source will have been advanced to the
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { * limit, and the method should be called again with the next source to continue the search.
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); *
source.readBytes(target, bytesRead, bytesToRead); * @param source The source from which to read.
bytesRead += bytesToRead; * @return True if the frame header was located. False otherwise.
return bytesRead == targetLength; */
private boolean findHeader(ParsableByteArray source) {
byte[] mpaData = 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;
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;
}
} }
source.setPosition(endOffset);
/** return false;
* 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 whose position should be advanced. * Attempts to read the remaining two bytes of the frame header.
* @return True if a sync word position was found. False otherwise. * <p>
*/ * If a frame header is read in full then true is returned. The media format will have been output
private boolean skipToNextSync(ParsableByteArray pesBuffer) { * if this has not previously occurred, the four header bytes will have been output as sample
byte[] mpaData = pesBuffer.data; * data, and the position of the source will have been advanced to the byte that immediately
int startOffset = pesBuffer.getPosition(); * follows the header.
int endOffset = pesBuffer.limit(); * <p>
for (int i = startOffset; i < endOffset - 1; i++) { * If a frame header is not read in full then the position of the source will have been advanced
int syncBits = ((mpaData[i] & 0xFF) << 8 ) | (mpaData[i + 1] & 0xFF); * to the limit, and the method should be called again with the next source to continue the read.
if ((syncBits & 0xFFF0) == 0xFFF0) { *
hasCrc = (mpaData[i + 1] & 0x1) == 0; * @param source The source from which to read.
pesBuffer.setPosition(i); * @return True if the frame header was read in full. False otherwise.
return true; */
} private boolean readHeaderRemainder(ParsableByteArray source) {
} int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - bytesRead);
pesBuffer.setPosition(endOffset); source.readBytes(headerScratch.data, bytesRead, bytesToRead);
return false; bytesRead += bytesToRead;
if (bytesRead < HEADER_SIZE) {
return false;
} }
/** if (!hasOutputFormat) {
* Calculates MPEG Audio frame size headerScratch.setPosition(0);
* int headerInt = headerScratch.readInt();
* @param layer The MPEG layer MpegAudioHeader synchronizedHeader = new MpegAudioHeader();
* @param LSF Low Sample rate Format (MPEG 2) MpegAudioHeader.populateHeader(headerInt, synchronizedHeader);
* @param bitrate The bitrate in bits per second MediaFormat mediaFormat = MediaFormat.createAudioFormat(
* @param samplesPerSec The sampling rate in hertz Mp3Extractor.MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], Mp3Extractor.MAX_FRAME_SIZE_BYTES,
* @param -paddingSize C.UNKNOWN_TIME_US, synchronizedHeader.channels, synchronizedHeader.sampleRate,
* @return Frame size in bytes Collections.<byte[]>emptyList());
*/ output.format(mediaFormat);
private static int CalcMpaFrameSize (int layer, int LSF, int bitrate, int samplesPerSec, int paddingSize) { hasOutputFormat = true;
return (int)(Math.floor(MPA_COEFFICIENTS[LSF][layer] * bitrate / samplesPerSec) + paddingSize) * MPA_SLOT_SIZE[layer]; frameDurationUs = (C.MICROS_PER_SECOND * synchronizedHeader.samplesPerFrame) / mediaFormat.sampleRate;
sampleSize = synchronizedHeader.frameSize;
} }
/** headerScratch.setPosition(0);
* Parses the sample header. output.sampleData(headerScratch, HEADER_SIZE);
*/ return true;
private void parseHeader() { }
int headerLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE;
/**
if (!hasOutputFormat) { * Attempts to read the remainder of the frame.
mpaScratch.setPosition(0); * <p>
mpaScratch.skipBits(12); * If a frame is read in full then true is returned. The frame will have been output, and the
int isLSF = (!mpaScratch.readBit()) ? 1 : 0; * position of the source will have been advanced to the byte that immediately follows the end of
int layer = mpaScratch.readBits(2) ^ 3; * the frame.
mpaScratch.skipBits(1); * <p>
int audioObjectType = 32 + layer; * If a frame is not read in full then the position of the source will have been advanced to the
int bitRate = MPA_BITRATES[isLSF][layer][mpaScratch.readBits(4)]; * limit, and the method should be called again with the next source to continue the read.
int sampleRate = MPA_SAMPLING_RATES[3 - isLSF][mpaScratch.readBits(2)]; *
int sampleRateIndex = CodecSpecificDataUtil.getSampleRateIndex(sampleRate); * @param source The source from which to read.
int paddingBit = (mpaScratch.readBit()) ? 1 : 0; * @return True if the frame was read in full. False otherwise.
mpaScratch.skipBits(1); */
int channelConfig = mpaScratch.readBits(2) == 3 ? 1 : 2; private boolean readFrame(ParsableByteArray source) {
int bytesToRead = Math.min(source.bytesLeft(), sampleSize - bytesRead);
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig( output.sampleData(source, bytesToRead);
audioObjectType, sampleRateIndex, channelConfig); bytesRead += bytesToRead;
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAudioSpecificConfig( if (bytesRead < sampleSize) {
audioSpecificConfig); return false;
// need to investigate how to detect if the mpeg decoder supports Layers other than Layer III
MediaFormat mediaFormat = MediaFormat.createAudioFormat(/*isLSF == 1 ?*/ MimeTypes.AUDIO_MPEG/* : MimeTypes.AUDIO_MP1L2*/,
MediaFormat.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig));
output.format(mediaFormat);
hasOutputFormat = true;
frameDurationUs = (C.MICROS_PER_SECOND * MPA_SAMPLES_PER_FRAME[isLSF][layer]) / mediaFormat.sampleRate;
sampleSize = CalcMpaFrameSize(layer, isLSF, bitRate * 1000, sampleRate, paddingBit);
}
mpaScratch.setPosition(0);
ParsableByteArray header = new ParsableByteArray(mpaScratch.data,headerLength);
output.sampleData(header, headerLength);
} }
/* (non-Javadoc) output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
* @see com.google.android.exoplayer.extractor.ts.ElementaryStreamReader#seek() timeUs += frameDurationUs;
*/ bytesRead = 0;
@Override return true;
public void seek() { }
// TODO(olly): Auto-generated method stub
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment