Commit 0c577ce2 by Oliver Woodman

Merge branch 'tresvecesseis-dev' into dev

parents 7fb5b865 c06f844e
...@@ -43,11 +43,8 @@ import java.util.Locale; ...@@ -43,11 +43,8 @@ import java.util.Locale;
} }
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] { public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
new Sample("Google Glass", new Sample("XXXXXXXXX",
"http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?" "http://178.33.229.111/live/mp4:Videolina/playlist.m3u8", PlayerActivity.TYPE_HLS),
+ "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", new Sample("Google Play",
"http://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?" "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&" + "as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&"
......
...@@ -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,276 +18,187 @@ package com.google.android.exoplayer.extractor.ts; ...@@ -18,276 +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 boolean hasOutputFormat;
private long frameDurationUs;
private int sampleSize;
// Used when reading the samples.
private long timeUs;
//
/**
* sampling rates in hertz:
*
* @index MPEG Version ID
* @index sampling rate index
*/
private static final int[][] MPA_SAMPLING_RATES = new int[][] {
{11025, 12000, 8000}, // MPEG 2.5
{ 0, 0, 0}, // reserved
{22050, 24000, 16000}, // MPEG 2
{44100, 48000, 32000} // MPEG 1
};
/** private static final int HEADER_SIZE = 4;
* bitrates:
*
* @index LSF
* @index Layer
* @index bitrate index
*/
private static final int[][][] MPA_BITRATES = new int[][][] { private final ParsableByteArray headerScratch;
{ // MPEG 1
// 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}
}
};
/** private int state;
* Samples per Frame: private int bytesRead;
*
* @index LSF
* @index Layer
*/
private static final int[][] MPA_SAMPLES_PER_FRAME = new int[][] { // Used to find the header.
{ // MPEG 1 private boolean lastByteWasFF;
384, // Layer1
1152, // Layer2
1152 // Layer3
},
{ // MPEG 2, 2.5
384, // Layer1
1152, // Layer2
576 // Layer3
}
};
/** // Used when parsing the header.
* Coefficients (samples per frame / 8): private boolean hasOutputFormat;
* private long frameDurationUs;
* @index = LSF private int sampleSize;
* @index = Layer
*/
private static final int[][] MPA_COEFFICIENTS = new int[][] { // Used when reading the samples.
{ // MPEG 1 private long timeUs;
12, // Layer1
144, // Layer2
144 // Layer3
},
{ // MPEG 2, 2.5
12, // Layer1
144, // Layer2
72 // Layer3
}
};
/** public MpaReader(TrackOutput output) {
* slot size per layer: super(output);
* state = STATE_FINDING_HEADER;
* @index = Layer // The first byte of an MPEG Audio frame header is always 0xFF.
*/ headerScratch = new ParsableByteArray(4);
headerScratch.data[0] = (byte) 0xFF;
}
private static final int[] MPA_SLOT_SIZE = new int[] { @Override
4, // Layer1 public void seek() {
1, // Layer2 state = STATE_FINDING_HEADER;
1 // Layer3 bytesRead = 0;
}; lastByteWasFF = false;
}
public MpaReader(TrackOutput output) { @Override
super(output); public void consume(ParsableByteArray data, long pesTimeUs, boolean startOfPacket) {
mpaScratch = new ParsableBitArray(new byte[HEADER_SIZE + CRC_SIZE]); if (startOfPacket) {
state = STATE_FINDING_SYNC; timeUs = pesTimeUs;
} }
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.getData(), 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.
}
/**
* Attempts to locate the start of the next frame header.
* <p>
* 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.
* <p>
* 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.
*
* @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;
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;
* 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.
* /**
* @param source The source from which to read. * Attempts to read the remaining two bytes of the frame header.
* @param target The target into which data is to be read. * <p>
* @param targetLength The target length of the read. * If a frame header is read in full then true is returned. The media format will have been output
* @return Whether the target length was reached. * 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
private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) { * follows the header.
int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead); * <p>
source.readBytes(target, bytesRead, bytesToRead); * If a frame header is not read in full then the position of the source will have been advanced
bytesRead += bytesToRead; * to the limit, and the method should be called again with the next source to continue the read.
return bytesRead == targetLength; *
* @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;
} }
/** if (!hasOutputFormat) {
* Locates the next sync word, advancing the position to the byte that immediately follows it. headerScratch.setPosition(0);
* If a sync word was not located, the position is advanced to the limit. int headerInt = headerScratch.readInt();
* MpegAudioHeader synchronizedHeader = new MpegAudioHeader();
* @param pesBuffer The buffer whose position should be advanced. MpegAudioHeader.populateHeader(headerInt, synchronizedHeader);
* @return True if a sync word position was found. False otherwise. MediaFormat mediaFormat = MediaFormat.createAudioFormat(
*/ Mp3Extractor.MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], Mp3Extractor.MAX_FRAME_SIZE_BYTES,
private boolean skipToNextSync(ParsableByteArray pesBuffer) { C.UNKNOWN_TIME_US, synchronizedHeader.channels, synchronizedHeader.sampleRate,
byte[] mpaData = pesBuffer.data; Collections.<byte[]>emptyList());
int startOffset = pesBuffer.getPosition(); output.format(mediaFormat);
int endOffset = pesBuffer.limit(); hasOutputFormat = true;
for (int i = startOffset; i < endOffset - 1; i++) { frameDurationUs = (C.MICROS_PER_SECOND * synchronizedHeader.samplesPerFrame) / mediaFormat.sampleRate;
int syncBits = ((mpaData[i] & 0xFF) << 8 ) | (mpaData[i + 1] & 0xFF); sampleSize = synchronizedHeader.frameSize;
if ((syncBits & 0xFFF0) == 0xFFF0) {
hasCrc = (mpaData[i + 1] & 0x1) == 0;
pesBuffer.setPosition(i);
return true;
}
}
pesBuffer.setPosition(endOffset);
return false;
} }
/** headerScratch.setPosition(0);
* Calculates MPEG Audio frame size output.sampleData(headerScratch, HEADER_SIZE);
* return true;
* @param layer The MPEG layer }
* @param LSF Low Sample rate Format (MPEG 2)
* @param bitrate The bitrate in bits per second /**
* @param samplesPerSec The sampling rate in hertz * Attempts to read the remainder of the frame.
* @param -paddingSize * <p>
* @return Frame size in bytes * If a frame is read in full then true is returned. The frame will have been output, and the
*/ * position of the source will have been advanced to the byte that immediately follows the end of
private static int CalcMpaFrameSize (int layer, int LSF, int bitrate, int samplesPerSec, int paddingSize) { * the frame.
return (int)(Math.floor(MPA_COEFFICIENTS[LSF][layer] * bitrate / samplesPerSec) + paddingSize) * MPA_SLOT_SIZE[layer]; * <p>
* If a frame 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.
*
* @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);
output.sampleData(source, bytesToRead);
bytesRead += bytesToRead;
if (bytesRead < sampleSize) {
return false;
} }
/** output.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, sampleSize, 0, null);
* Parses the sample header. timeUs += frameDurationUs;
*/ bytesRead = 0;
private void parseHeader() { return true;
int headerLength = hasCrc ? HEADER_SIZE + CRC_SIZE : HEADER_SIZE; }
if (!hasOutputFormat) {
mpaScratch.setPosition(0);
mpaScratch.skipBits(12);
int isLSF = (!mpaScratch.readBit()) ? 1 : 0;
int layer = mpaScratch.readBits(2) ^ 3;
mpaScratch.skipBits(1);
int audioObjectType = 32 + layer;
int bitRate = MPA_BITRATES[isLSF][layer][mpaScratch.readBits(4)];
int sampleRate = MPA_SAMPLING_RATES[3 - isLSF][mpaScratch.readBits(2)];
int sampleRateIndex = CodecSpecificDataUtil.getSampleRateIndex(sampleRate);
int paddingBit = (mpaScratch.readBit()) ? 1 : 0;
mpaScratch.skipBits(1);
int channelConfig = mpaScratch.readBits(2) == 3 ? 1 : 2;
byte[] audioSpecificConfig = CodecSpecificDataUtil.buildAudioSpecificConfig(
audioObjectType, sampleRateIndex, channelConfig);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAudioSpecificConfig(
audioSpecificConfig);
// 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.getData(),headerLength);
output.sampleData(header, headerLength);
}
} }
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