mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Simplify HLS MP3 support.
This commit is contained in:
parent
0c577ce2ce
commit
3bcd9ca6c3
@ -43,8 +43,11 @@ import java.util.Locale;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
|
public static final Sample[] YOUTUBE_DASH_MP4 = new Sample[] {
|
||||||
new Sample("XXXXXXXXX",
|
new Sample("Google Glass",
|
||||||
"http://178.33.229.111/live/mp4:Videolina/playlist.m3u8", PlayerActivity.TYPE_HLS),
|
"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",
|
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&"
|
||||||
|
@ -24,7 +24,7 @@ import com.google.android.exoplayer.extractor.ExtractorOutput;
|
|||||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||||
import com.google.android.exoplayer.extractor.SeekMap;
|
import com.google.android.exoplayer.extractor.SeekMap;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
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.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
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. */
|
/** 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");
|
||||||
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 XING_HEADER = Util.getIntegerCodeForString("Xing");
|
||||||
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
|
private static final int INFO_HEADER = Util.getIntegerCodeForString("Info");
|
||||||
private static final int VBRI_HEADER = Util.getIntegerCodeForString("VBRI");
|
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 BufferingInput inputBuffer;
|
||||||
private final ParsableByteArray scratch;
|
private final ParsableByteArray scratch;
|
||||||
private final MpegAudioHeader synchronizedHeader;
|
private final MpegAudioHeader synchronizedHeader;
|
||||||
@ -74,7 +64,7 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
|
|
||||||
/** Constructs a new {@link Mp3Extractor}. */
|
/** Constructs a new {@link Mp3Extractor}. */
|
||||||
public Mp3Extractor() {
|
public Mp3Extractor() {
|
||||||
inputBuffer = new BufferingInput(MAX_FRAME_SIZE_BYTES * 3);
|
inputBuffer = new BufferingInput(MpegAudioHeader.MAX_FRAME_SIZE_BYTES * 3);
|
||||||
scratch = new ParsableByteArray(4);
|
scratch = new ParsableByteArray(4);
|
||||||
synchronizedHeader = new MpegAudioHeader();
|
synchronizedHeader = new MpegAudioHeader();
|
||||||
}
|
}
|
||||||
@ -255,10 +245,9 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
if (seeker == null) {
|
if (seeker == null) {
|
||||||
setupSeeker(extractorInput, headerPosition);
|
setupSeeker(extractorInput, headerPosition);
|
||||||
extractorOutput.seekMap(seeker);
|
extractorOutput.seekMap(seeker);
|
||||||
trackOutput.format(MediaFormat.createAudioFormat(
|
trackOutput.format(MediaFormat.createAudioFormat(synchronizedHeader.mimeType,
|
||||||
MIME_TYPE_BY_LAYER[synchronizedHeader.layerIndex], MAX_FRAME_SIZE_BYTES,
|
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, seeker.getDurationUs(), synchronizedHeader.channels,
|
||||||
seeker.getDurationUs(), synchronizedHeader.channels, synchronizedHeader.sampleRate,
|
synchronizedHeader.sampleRate, Collections.<byte[]>emptyList()));
|
||||||
Collections.<byte[]>emptyList()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return headerPosition;
|
return headerPosition;
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer.extractor.mp3;
|
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.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer.extractor.mp3;
|
package com.google.android.exoplayer.extractor.mp3;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
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.ParsableByteArray;
|
||||||
import com.google.android.exoplayer.util.Util;
|
import com.google.android.exoplayer.util.Util;
|
||||||
|
|
||||||
|
@ -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 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.
|
* @return True if a sync word position was found. False otherwise.
|
||||||
*/
|
*/
|
||||||
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
|
private boolean skipToNextSync(ParsableByteArray pesBuffer) {
|
||||||
|
@ -18,8 +18,7 @@ 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.extractor.mp3.Mp3Extractor;
|
import com.google.android.exoplayer.util.MpegAudioHeader;
|
||||||
import com.google.android.exoplayer.extractor.mp3.MpegAudioHeader;
|
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -27,7 +26,7 @@ import java.util.Collections;
|
|||||||
/**
|
/**
|
||||||
* Parses a continuous MPEG Audio byte stream and extracts individual frames.
|
* 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_FINDING_HEADER = 0;
|
||||||
private static final int STATE_READING_HEADER = 1;
|
private static final int STATE_READING_HEADER = 1;
|
||||||
@ -36,33 +35,35 @@ import java.util.Collections;
|
|||||||
private static final int HEADER_SIZE = 4;
|
private static final int HEADER_SIZE = 4;
|
||||||
|
|
||||||
private final ParsableByteArray headerScratch;
|
private final ParsableByteArray headerScratch;
|
||||||
|
private final MpegAudioHeader header;
|
||||||
|
|
||||||
private int state;
|
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;
|
private boolean lastByteWasFF;
|
||||||
|
|
||||||
// Used when parsing the header.
|
// Parsed from the frame header.
|
||||||
private boolean hasOutputFormat;
|
|
||||||
private long frameDurationUs;
|
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;
|
private long timeUs;
|
||||||
|
|
||||||
public MpaReader(TrackOutput output) {
|
public MpegAudioReader(TrackOutput output) {
|
||||||
super(output);
|
super(output);
|
||||||
state = STATE_FINDING_HEADER;
|
state = STATE_FINDING_HEADER;
|
||||||
// The first byte of an MPEG Audio frame header is always 0xFF.
|
// The first byte of an MPEG Audio frame header is always 0xFF.
|
||||||
headerScratch = new ParsableByteArray(4);
|
headerScratch = new ParsableByteArray(4);
|
||||||
headerScratch.data[0] = (byte) 0xFF;
|
headerScratch.data[0] = (byte) 0xFF;
|
||||||
|
header = new MpegAudioHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
state = STATE_FINDING_HEADER;
|
state = STATE_FINDING_HEADER;
|
||||||
bytesRead = 0;
|
frameBytesRead = 0;
|
||||||
lastByteWasFF = false;
|
lastByteWasFF = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,19 +75,13 @@ import java.util.Collections;
|
|||||||
while (data.bytesLeft() > 0) {
|
while (data.bytesLeft() > 0) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_FINDING_HEADER:
|
case STATE_FINDING_HEADER:
|
||||||
if (findHeader(data)) {
|
findHeader(data);
|
||||||
state = STATE_READING_HEADER;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case STATE_READING_HEADER:
|
case STATE_READING_HEADER:
|
||||||
if (readHeaderRemainder(data)) {
|
readHeaderRemainder(data);
|
||||||
state = STATE_READING_FRAME;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case STATE_READING_FRAME:
|
case STATE_READING_FRAME:
|
||||||
if (readFrame(data)) {
|
readFrameRemainder(data);
|
||||||
state = STATE_FINDING_HEADER;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,78 +95,83 @@ import java.util.Collections;
|
|||||||
/**
|
/**
|
||||||
* Attempts to locate the start of the next frame header.
|
* Attempts to locate the start of the next frame header.
|
||||||
* <p>
|
* <p>
|
||||||
* If a frame header is located then true is returned. The first two bytes of the header will have
|
* If a frame header is located then the state is changed to {@link #STATE_READING_HEADER}, the
|
||||||
* been written into {@link #headerScratch}, and the position of the source will have been
|
* first two bytes of the header are written into {@link #headerScratch}, and the position of the
|
||||||
* advanced to the byte that immediately follows these two bytes.
|
* source is advanced to the byte that immediately follows these two bytes.
|
||||||
* <p>
|
* <p>
|
||||||
* If a frame header is not located then the position of the source will have been advanced to the
|
* If a frame header is not located then the position of the source is advanced to the limit, and
|
||||||
* limit, and the method should be called again with the next source to continue the search.
|
* the method should be called again with the next source to continue the search.
|
||||||
*
|
*
|
||||||
* @param source The source from which to read.
|
* @param source The source from which to read.
|
||||||
* @return True if the frame header was located. False otherwise.
|
|
||||||
*/
|
*/
|
||||||
private boolean findHeader(ParsableByteArray source) {
|
private void findHeader(ParsableByteArray source) {
|
||||||
byte[] mpaData = source.data;
|
byte[] data = source.data;
|
||||||
int startOffset = source.getPosition();
|
int startOffset = source.getPosition();
|
||||||
int endOffset = source.limit();
|
int endOffset = source.limit();
|
||||||
for (int i = startOffset; i < endOffset; i++) {
|
for (int i = startOffset; i < endOffset; i++) {
|
||||||
boolean byteIsFF = (mpaData[i] & 0xFF) == 0xFF;
|
boolean byteIsFF = (data[i] & 0xFF) == 0xFF;
|
||||||
boolean found = lastByteWasFF && (mpaData[i] & 0xF0) == 0xF0;
|
boolean found = lastByteWasFF && (data[i] & 0xE0) == 0xE0;
|
||||||
lastByteWasFF = byteIsFF;
|
lastByteWasFF = byteIsFF;
|
||||||
if (found) {
|
if (found) {
|
||||||
source.setPosition(i + 1);
|
source.setPosition(i + 1);
|
||||||
// Reset lastByteWasFF for next time.
|
// Reset lastByteWasFF for next time.
|
||||||
lastByteWasFF = false;
|
lastByteWasFF = false;
|
||||||
headerScratch.data[0] = (byte) 0xFF;
|
headerScratch.data[1] = data[i];
|
||||||
headerScratch.data[1] = mpaData[i];
|
frameBytesRead = 2;
|
||||||
bytesRead = 2;
|
state = STATE_READING_HEADER;
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
source.setPosition(endOffset);
|
source.setPosition(endOffset);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to read the remaining two bytes of the frame header.
|
* Attempts to read the remaining two bytes of the frame header.
|
||||||
* <p>
|
* <p>
|
||||||
* If a frame header is read in full then true is returned. The media format will have been output
|
* If a frame header is read in full then the state is changed to {@link #STATE_READING_FRAME},
|
||||||
* if this has not previously occurred, the four header bytes will have been output as sample
|
* the media format is output if this has not previously occurred, the four header bytes are
|
||||||
* data, and the position of the source will have been advanced to the byte that immediately
|
* output as sample data, and the position of the source is advanced to the byte that immediately
|
||||||
* follows the header.
|
* follows the header.
|
||||||
* <p>
|
* <p>
|
||||||
* If a frame header is not read in full then the position of the source will have been advanced
|
* If a frame header is read in full but cannot be parsed then the state is changed to
|
||||||
* to the limit, and the method should be called again with the next source to continue the read.
|
* {@link #STATE_READING_HEADER}.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
* @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) {
|
private void readHeaderRemainder(ParsableByteArray source) {
|
||||||
int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - bytesRead);
|
int bytesToRead = Math.min(source.bytesLeft(), HEADER_SIZE - frameBytesRead);
|
||||||
source.readBytes(headerScratch.data, bytesRead, bytesToRead);
|
source.readBytes(headerScratch.data, frameBytesRead, bytesToRead);
|
||||||
bytesRead += bytesToRead;
|
frameBytesRead += bytesToRead;
|
||||||
if (bytesRead < HEADER_SIZE) {
|
if (frameBytesRead < HEADER_SIZE) {
|
||||||
return false;
|
// 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) {
|
if (!hasOutputFormat) {
|
||||||
headerScratch.setPosition(0);
|
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
||||||
int headerInt = headerScratch.readInt();
|
MediaFormat mediaFormat = MediaFormat.createAudioFormat(header.mimeType,
|
||||||
MpegAudioHeader synchronizedHeader = new MpegAudioHeader();
|
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, C.UNKNOWN_TIME_US, header.channels,
|
||||||
MpegAudioHeader.populateHeader(headerInt, synchronizedHeader);
|
header.sampleRate, Collections.<byte[]>emptyList());
|
||||||
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.<byte[]>emptyList());
|
|
||||||
output.format(mediaFormat);
|
output.format(mediaFormat);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
frameDurationUs = (C.MICROS_PER_SECOND * synchronizedHeader.samplesPerFrame) / mediaFormat.sampleRate;
|
|
||||||
sampleSize = synchronizedHeader.frameSize;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
headerScratch.setPosition(0);
|
headerScratch.setPosition(0);
|
||||||
output.sampleData(headerScratch, HEADER_SIZE);
|
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.
|
* limit, and the method should be called again with the next source to continue the read.
|
||||||
*
|
*
|
||||||
* @param source The source from which to 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) {
|
private void readFrameRemainder(ParsableByteArray source) {
|
||||||
int bytesToRead = Math.min(source.bytesLeft(), sampleSize - bytesRead);
|
int bytesToRead = Math.min(source.bytesLeft(), frameSize - frameBytesRead);
|
||||||
output.sampleData(source, bytesToRead);
|
output.sampleData(source, bytesToRead);
|
||||||
bytesRead += bytesToRead;
|
frameBytesRead += bytesToRead;
|
||||||
if (bytesRead < sampleSize) {
|
if (frameBytesRead < frameSize) {
|
||||||
return false;
|
// 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;
|
timeUs += frameDurationUs;
|
||||||
bytesRead = 0;
|
frameBytesRead = 0;
|
||||||
return true;
|
state = STATE_FINDING_HEADER;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -354,10 +354,10 @@ public final class TsExtractor implements Extractor, SeekMap {
|
|||||||
ElementaryStreamReader pesPayloadReader = null;
|
ElementaryStreamReader pesPayloadReader = null;
|
||||||
switch (streamType) {
|
switch (streamType) {
|
||||||
case TS_STREAM_TYPE_MPA:
|
case TS_STREAM_TYPE_MPA:
|
||||||
pesPayloadReader = new MpaReader(output.track(TS_STREAM_TYPE_MPA));
|
pesPayloadReader = new MpegAudioReader(output.track(TS_STREAM_TYPE_MPA));
|
||||||
break;
|
break;
|
||||||
case TS_STREAM_TYPE_MPA_LSF:
|
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;
|
break;
|
||||||
case TS_STREAM_TYPE_AAC:
|
case TS_STREAM_TYPE_AAC:
|
||||||
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
|
pesPayloadReader = new AdtsReader(output.track(TS_STREAM_TYPE_AAC));
|
||||||
|
@ -41,22 +41,6 @@ public final class CodecSpecificDataUtil {
|
|||||||
|
|
||||||
private 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
|
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
|
||||||
*
|
*
|
||||||
@ -65,24 +49,13 @@ public final class CodecSpecificDataUtil {
|
|||||||
*/
|
*/
|
||||||
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig) {
|
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig) {
|
||||||
int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F;
|
int audioObjectType = (audioSpecificConfig[0] >> 3) & 0x1F;
|
||||||
if (audioObjectType < 31) {
|
int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0;
|
||||||
int byteOffset = audioObjectType == 5 || audioObjectType == 29 ? 1 : 0;
|
int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1
|
||||||
int frequencyIndex = (audioSpecificConfig[byteOffset] & 0x7) << 1 |
|
| ((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1);
|
||||||
((audioSpecificConfig[byteOffset + 1] >> 7) & 0x1);
|
Assertions.checkState(frequencyIndex < 13);
|
||||||
Assertions.checkState(frequencyIndex < 13);
|
int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex];
|
||||||
int sampleRate = AUDIO_SPECIFIC_CONFIG_SAMPLING_RATE_TABLE[frequencyIndex];
|
int channelCount = (audioSpecificConfig[byteOffset + 1] >> 3) & 0xF;
|
||||||
int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[
|
return Pair.create(sampleRate, channelCount);
|
||||||
(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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,11 +13,23 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* 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 {
|
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[] SAMPLING_RATE_V1 = {44100, 48000, 32000};
|
||||||
private static final int[] BITRATE_V1_L1 =
|
private static final int[] BITRATE_V1_L1 =
|
||||||
{32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448};
|
{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 =
|
private static final int[] BITRATE_V2 =
|
||||||
{8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160};
|
{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) {
|
public static int getFrameSize(int header) {
|
||||||
if ((header & 0xFFE00000) != 0xFFE00000) {
|
if ((header & 0xFFE00000) != 0xFFE00000) {
|
||||||
return -1;
|
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 headerData Header data to parse.
|
||||||
* @param header Header to populate with data from {@code headerData}.
|
* @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) {
|
if ((headerData & 0xFFE00000) != 0xFFE00000) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int version = (headerData >>> 19) & 3;
|
int version = (headerData >>> 19) & 3;
|
||||||
if (version == 1) {
|
if (version == 1) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int layer = (headerData >>> 17) & 3;
|
int layer = (headerData >>> 17) & 3;
|
||||||
if (layer == 0) {
|
if (layer == 0) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int bitrateIndex = (headerData >>> 12) & 15;
|
int bitrateIndex = (headerData >>> 12) & 15;
|
||||||
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
if (bitrateIndex == 0 || bitrateIndex == 0xF) {
|
||||||
// Disallow "free" bitrate.
|
// Disallow "free" bitrate.
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int samplingRateIndex = (headerData >>> 10) & 3;
|
int samplingRateIndex = (headerData >>> 10) & 3;
|
||||||
if (samplingRateIndex == 3) {
|
if (samplingRateIndex == 3) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sampleRate = SAMPLING_RATE_V1[samplingRateIndex];
|
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 channels = ((headerData >> 6) & 3) == 3 ? 1 : 2;
|
||||||
int layerIndex = 3 - layer;
|
header.setValues(version, mimeType, frameSize, sampleRate, channels, bitrate, samplesPerFrame);
|
||||||
header.setValues(
|
return true;
|
||||||
version, layerIndex, frameSize, sampleRate, channels, bitrate, samplesPerFrame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** MPEG audio header version. */
|
/** MPEG audio header version. */
|
||||||
public int version;
|
public int version;
|
||||||
/** MPEG audio layer index, starting at zero. */
|
/** The mime type. */
|
||||||
public int layerIndex;
|
public String mimeType;
|
||||||
/** Size of the frame associated with this header, in bytes. */
|
/** Size of the frame associated with this header, in bytes. */
|
||||||
public int frameSize;
|
public int frameSize;
|
||||||
/** Sample rate in samples per second. */
|
/** Sample rate in samples per second. */
|
||||||
@ -175,10 +191,10 @@ public final class MpegAudioHeader {
|
|||||||
/** Number of samples stored in the frame. */
|
/** Number of samples stored in the frame. */
|
||||||
public int samplesPerFrame;
|
public int samplesPerFrame;
|
||||||
|
|
||||||
private void setValues(int version, int layerIndex, int frameSize, int sampleRate, int channels,
|
private void setValues(int version, String mimeType, int frameSize,
|
||||||
int bitrate, int samplesPerFrame) {
|
int sampleRate, int channels, int bitrate, int samplesPerFrame) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.layerIndex = layerIndex;
|
this.mimeType = mimeType;
|
||||||
this.frameSize = frameSize;
|
this.frameSize = frameSize;
|
||||||
this.sampleRate = sampleRate;
|
this.sampleRate = sampleRate;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
Loading…
x
Reference in New Issue
Block a user