mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Further cleanup to FLV extractor
This commit is contained in:
parent
f91ea9039d
commit
4422e8a015
6 changed files with 290 additions and 338 deletions
|
|
@ -148,7 +148,6 @@ import java.util.Locale;
|
||||||
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm", PlayerActivity.TYPE_OTHER),
|
"http://demos.webmproject.org/exoplayer/glass_vp9_vorbis.webm", PlayerActivity.TYPE_OTHER),
|
||||||
new Sample("Big Buck Bunny (FLV Video)",
|
new Sample("Big Buck Bunny (FLV Video)",
|
||||||
"http://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0", PlayerActivity.TYPE_OTHER),
|
"http://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0", PlayerActivity.TYPE_OTHER),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private Samples() {}
|
private Samples() {}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import android.util.Pair;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses audio tags of from an FLV stream and extracts AAC frames.
|
* Parses audio tags from an FLV stream and extracts AAC frames.
|
||||||
*/
|
*/
|
||||||
/* package */ final class AudioTagPayloadReader extends TagPayloadReader {
|
/* package */ final class AudioTagPayloadReader extends TagPayloadReader {
|
||||||
|
|
||||||
|
|
@ -59,29 +59,22 @@ import java.util.Collections;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException {
|
protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException {
|
||||||
// Parse audio data header, if it was not done, to extract information about the audio codec
|
|
||||||
// and audio configuration.
|
|
||||||
if (!hasParsedAudioDataHeader) {
|
if (!hasParsedAudioDataHeader) {
|
||||||
int header = data.readUnsignedByte();
|
int header = data.readUnsignedByte();
|
||||||
int audioFormat = (header >> 4) & 0x0F;
|
int audioFormat = (header >> 4) & 0x0F;
|
||||||
int sampleRateIndex = (header >> 2) & 0x03;
|
int sampleRateIndex = (header >> 2) & 0x03;
|
||||||
if (sampleRateIndex < 0 || sampleRateIndex >= AUDIO_SAMPLING_RATE_TABLE.length) {
|
if (sampleRateIndex < 0 || sampleRateIndex >= AUDIO_SAMPLING_RATE_TABLE.length) {
|
||||||
throw new UnsupportedFormatException("Invalid sample rate for the audio track");
|
throw new UnsupportedFormatException("Invalid sample rate index: " + sampleRateIndex);
|
||||||
}
|
}
|
||||||
|
// TODO: Add support for MP3 and PCM.
|
||||||
if (audioFormat != AUDIO_FORMAT_AAC) {
|
if (audioFormat != AUDIO_FORMAT_AAC) {
|
||||||
// TODO: Adds support for MP3 and PCM
|
throw new UnsupportedFormatException("Audio format not supported: " + audioFormat);
|
||||||
if (audioFormat != AUDIO_FORMAT_AAC) {
|
|
||||||
throw new UnsupportedFormatException("Audio format not supported: " + audioFormat);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
hasParsedAudioDataHeader = true;
|
hasParsedAudioDataHeader = true;
|
||||||
} else {
|
} else {
|
||||||
// Skip header if it was parsed previously.
|
// Skip header if it was parsed previously.
|
||||||
data.skipBytes(1);
|
data.skipBytes(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In all the cases we will be managing AAC format (otherwise an exception would be fired so we
|
|
||||||
// can just always return true.
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,63 +21,61 @@ import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
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.util.Assertions;
|
|
||||||
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;
|
||||||
|
|
||||||
import java.io.EOFException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of data from the FLV container format.
|
* Facilitates the extraction of data from the FLV container format.
|
||||||
*/
|
*/
|
||||||
public final class FlvExtractor implements Extractor, SeekMap {
|
public final class FlvExtractor implements Extractor, SeekMap {
|
||||||
// Header sizes
|
|
||||||
private static final int FLV_MIN_HEADER_SIZE = 9;
|
// Header sizes.
|
||||||
|
private static final int FLV_HEADER_SIZE = 9;
|
||||||
private static final int FLV_TAG_HEADER_SIZE = 11;
|
private static final int FLV_TAG_HEADER_SIZE = 11;
|
||||||
|
|
||||||
// Parser states.
|
// Parser states.
|
||||||
private static final int STATE_READING_TAG_HEADER = 1;
|
private static final int STATE_READING_FLV_HEADER = 1;
|
||||||
private static final int STATE_READING_SAMPLE = 2;
|
private static final int STATE_SKIPPING_TO_TAG_HEADER = 2;
|
||||||
|
private static final int STATE_READING_TAG_HEADER = 3;
|
||||||
|
private static final int STATE_READING_TAG_DATA = 4;
|
||||||
|
|
||||||
// Tag types
|
// Tag types.
|
||||||
private static final int TAG_TYPE_AUDIO = 8;
|
private static final int TAG_TYPE_AUDIO = 8;
|
||||||
private static final int TAG_TYPE_VIDEO = 9;
|
private static final int TAG_TYPE_VIDEO = 9;
|
||||||
private static final int TAG_TYPE_SCRIPT_DATA = 18;
|
private static final int TAG_TYPE_SCRIPT_DATA = 18;
|
||||||
|
|
||||||
// FLV container identifier
|
// FLV container identifier.
|
||||||
private static final int FLV_TAG = Util.getIntegerCodeForString("FLV");
|
private static final int FLV_TAG = Util.getIntegerCodeForString("FLV");
|
||||||
|
|
||||||
// Temporary buffers
|
// Temporary buffers.
|
||||||
private final ParsableByteArray scratch;
|
private final ParsableByteArray scratch;
|
||||||
private final ParsableByteArray headerBuffer;
|
private final ParsableByteArray headerBuffer;
|
||||||
private final ParsableByteArray tagHeaderBuffer;
|
private final ParsableByteArray tagHeaderBuffer;
|
||||||
private ParsableByteArray tagData;
|
private final ParsableByteArray tagData;
|
||||||
|
|
||||||
// Extractor outputs.
|
// Extractor outputs.
|
||||||
private ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
|
|
||||||
// State variables.
|
// State variables.
|
||||||
private int parserState;
|
private int parserState;
|
||||||
private int dataOffset;
|
private int bytesToNextTagHeader;
|
||||||
private TagHeader currentTagHeader;
|
public int tagType;
|
||||||
|
public int tagDataSize;
|
||||||
|
public long tagTimestampUs;
|
||||||
|
|
||||||
// Tags readers
|
// Tags readers.
|
||||||
private AudioTagPayloadReader audioReader;
|
private AudioTagPayloadReader audioReader;
|
||||||
private VideoTagPayloadReader videoReader;
|
private VideoTagPayloadReader videoReader;
|
||||||
private ScriptTagPayloadReader metadataReader;
|
private ScriptTagPayloadReader metadataReader;
|
||||||
|
|
||||||
public FlvExtractor() {
|
public FlvExtractor() {
|
||||||
scratch = new ParsableByteArray(4);
|
scratch = new ParsableByteArray(4);
|
||||||
headerBuffer = new ParsableByteArray(FLV_MIN_HEADER_SIZE);
|
headerBuffer = new ParsableByteArray(FLV_HEADER_SIZE);
|
||||||
tagHeaderBuffer = new ParsableByteArray(FLV_TAG_HEADER_SIZE);
|
tagHeaderBuffer = new ParsableByteArray(FLV_TAG_HEADER_SIZE);
|
||||||
dataOffset = 0;
|
tagData = new ParsableByteArray();
|
||||||
currentTagHeader = new TagHeader();
|
parserState = STATE_READING_FLV_HEADER;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(ExtractorOutput output) {
|
|
||||||
this.extractorOutput = output;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -112,151 +110,133 @@ public final class FlvExtractor implements Extractor, SeekMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
|
public void init(ExtractorOutput output) {
|
||||||
InterruptedException {
|
this.extractorOutput = output;
|
||||||
if (dataOffset == 0
|
|
||||||
&& !readHeader(input)) {
|
|
||||||
return RESULT_END_OF_INPUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
if (parserState == STATE_READING_TAG_HEADER) {
|
|
||||||
if (!readTagHeader(input)) {
|
|
||||||
return RESULT_END_OF_INPUT;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return readSample(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (AudioTagPayloadReader.UnsupportedFormatException unsupportedTrack) {
|
|
||||||
unsupportedTrack.printStackTrace();
|
|
||||||
return RESULT_END_OF_INPUT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek() {
|
public void seek() {
|
||||||
dataOffset = 0;
|
parserState = STATE_READING_FLV_HEADER;
|
||||||
|
bytesToNextTagHeader = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ExtractorInput input, PositionHolder seekPosition) throws IOException,
|
||||||
|
InterruptedException {
|
||||||
|
while (true) {
|
||||||
|
switch (parserState) {
|
||||||
|
case STATE_READING_FLV_HEADER:
|
||||||
|
if (!readFlvHeader(input)) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_SKIPPING_TO_TAG_HEADER:
|
||||||
|
skipToTagHeader(input);
|
||||||
|
break;
|
||||||
|
case STATE_READING_TAG_HEADER:
|
||||||
|
if (!readTagHeader(input)) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case STATE_READING_TAG_DATA:
|
||||||
|
if (readTagData(input)) {
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads FLV container header from the provided {@link ExtractorInput}.
|
* Reads an FLV container header from the provided {@link ExtractorInput}.
|
||||||
|
*
|
||||||
* @param input The {@link ExtractorInput} from which to read.
|
* @param input The {@link ExtractorInput} from which to read.
|
||||||
* @return True if header was read successfully. Otherwise, false.
|
* @return True if header was read successfully. False if the end of stream was reached.
|
||||||
* @throws IOException If an error occurred reading from the source.
|
* @throws IOException If an error occurred reading or parsing data from the source.
|
||||||
* @throws InterruptedException If the thread was interrupted.
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
*/
|
*/
|
||||||
private boolean readHeader(ExtractorInput input) throws IOException, InterruptedException {
|
private boolean readFlvHeader(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
try {
|
if (!input.readFully(headerBuffer.data, 0, FLV_HEADER_SIZE, true)) {
|
||||||
input.readFully(headerBuffer.data, 0, FLV_MIN_HEADER_SIZE);
|
// We've reached the end of the stream.
|
||||||
headerBuffer.setPosition(0);
|
|
||||||
headerBuffer.skipBytes(4);
|
|
||||||
int flags = headerBuffer.readUnsignedByte();
|
|
||||||
boolean hasAudio = (flags & 0x04) != 0;
|
|
||||||
boolean hasVideo = (flags & 0x01) != 0;
|
|
||||||
|
|
||||||
if (hasAudio && audioReader == null) {
|
|
||||||
audioReader = new AudioTagPayloadReader(extractorOutput.track(TAG_TYPE_AUDIO));
|
|
||||||
}
|
|
||||||
if (hasVideo && videoReader == null) {
|
|
||||||
videoReader = new VideoTagPayloadReader(extractorOutput.track(TAG_TYPE_VIDEO));
|
|
||||||
}
|
|
||||||
if (metadataReader == null) {
|
|
||||||
metadataReader = new ScriptTagPayloadReader(null);
|
|
||||||
}
|
|
||||||
extractorOutput.endTracks();
|
|
||||||
extractorOutput.seekMap(this);
|
|
||||||
|
|
||||||
// Store payload start position and start extended header (if there is one)
|
|
||||||
dataOffset = headerBuffer.readInt();
|
|
||||||
|
|
||||||
input.skipFully(dataOffset - FLV_MIN_HEADER_SIZE);
|
|
||||||
parserState = STATE_READING_TAG_HEADER;
|
|
||||||
} catch (EOFException eof) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
headerBuffer.setPosition(0);
|
||||||
|
headerBuffer.skipBytes(4);
|
||||||
|
int flags = headerBuffer.readUnsignedByte();
|
||||||
|
boolean hasAudio = (flags & 0x04) != 0;
|
||||||
|
boolean hasVideo = (flags & 0x01) != 0;
|
||||||
|
if (hasAudio && audioReader == null) {
|
||||||
|
audioReader = new AudioTagPayloadReader(extractorOutput.track(TAG_TYPE_AUDIO));
|
||||||
|
}
|
||||||
|
if (hasVideo && videoReader == null) {
|
||||||
|
videoReader = new VideoTagPayloadReader(extractorOutput.track(TAG_TYPE_VIDEO));
|
||||||
|
}
|
||||||
|
if (metadataReader == null) {
|
||||||
|
metadataReader = new ScriptTagPayloadReader(null);
|
||||||
|
}
|
||||||
|
extractorOutput.endTracks();
|
||||||
|
extractorOutput.seekMap(this);
|
||||||
|
|
||||||
|
// We need to skip any additional content in the FLV header, plus the 4 byte previous tag size.
|
||||||
|
bytesToNextTagHeader = headerBuffer.readInt() - FLV_HEADER_SIZE + 4;
|
||||||
|
parserState = STATE_SKIPPING_TO_TAG_HEADER;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips over data to reach the next tag header.
|
||||||
|
*
|
||||||
|
* @param input The {@link ExtractorInput} from which to read.
|
||||||
|
* @throws IOException If an error occurred skipping data from the source.
|
||||||
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
|
*/
|
||||||
|
private void skipToTagHeader(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
|
input.skipFully(bytesToNextTagHeader);
|
||||||
|
bytesToNextTagHeader = 0;
|
||||||
|
parserState = STATE_READING_TAG_HEADER;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a tag header from the provided {@link ExtractorInput}.
|
* Reads a tag header from the provided {@link ExtractorInput}.
|
||||||
|
*
|
||||||
* @param input The {@link ExtractorInput} from which to read.
|
* @param input The {@link ExtractorInput} from which to read.
|
||||||
* @return True if tag header was read successfully. Otherwise, false.
|
* @return True if tag header was read successfully. Otherwise, false.
|
||||||
* @throws IOException If an error occurred reading from the source.
|
* @throws IOException If an error occurred reading or parsing data from the source.
|
||||||
* @throws InterruptedException If the thread was interrupted.
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
* @throws TagPayloadReader.UnsupportedFormatException If payload of the tag is using a codec non
|
|
||||||
* supported codec.
|
|
||||||
*/
|
*/
|
||||||
private boolean readTagHeader(ExtractorInput input) throws IOException, InterruptedException,
|
private boolean readTagHeader(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
TagPayloadReader.UnsupportedFormatException {
|
if (!input.readFully(tagHeaderBuffer.data, 0, FLV_TAG_HEADER_SIZE, true)) {
|
||||||
try {
|
// We've reached the end of the stream.
|
||||||
// skipping previous tag size field
|
|
||||||
input.skipFully(4);
|
|
||||||
|
|
||||||
// Read the tag header from the input.
|
|
||||||
input.readFully(tagHeaderBuffer.data, 0, FLV_TAG_HEADER_SIZE);
|
|
||||||
|
|
||||||
tagHeaderBuffer.setPosition(0);
|
|
||||||
int type = tagHeaderBuffer.readUnsignedByte();
|
|
||||||
int dataSize = tagHeaderBuffer.readUnsignedInt24();
|
|
||||||
long timestamp = tagHeaderBuffer.readUnsignedInt24();
|
|
||||||
timestamp = (tagHeaderBuffer.readUnsignedByte() << 24) | timestamp;
|
|
||||||
int streamId = tagHeaderBuffer.readUnsignedInt24();
|
|
||||||
|
|
||||||
currentTagHeader.type = type;
|
|
||||||
currentTagHeader.dataSize = dataSize;
|
|
||||||
currentTagHeader.timestamp = timestamp * 1000;
|
|
||||||
currentTagHeader.streamId = streamId;
|
|
||||||
|
|
||||||
// Sanity checks.
|
|
||||||
Assertions.checkState(type == TAG_TYPE_AUDIO || type == TAG_TYPE_VIDEO
|
|
||||||
|| type == TAG_TYPE_SCRIPT_DATA);
|
|
||||||
// Reuse tagData buffer to avoid lot of memory allocation (performance penalty).
|
|
||||||
if (tagData == null || dataSize > tagData.capacity()) {
|
|
||||||
tagData = new ParsableByteArray(dataSize);
|
|
||||||
} else {
|
|
||||||
tagData.setPosition(0);
|
|
||||||
}
|
|
||||||
tagData.setLimit(dataSize);
|
|
||||||
parserState = STATE_READING_SAMPLE;
|
|
||||||
|
|
||||||
} catch (EOFException eof) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tagHeaderBuffer.setPosition(0);
|
||||||
|
tagType = tagHeaderBuffer.readUnsignedByte();
|
||||||
|
tagDataSize = tagHeaderBuffer.readUnsignedInt24();
|
||||||
|
tagTimestampUs = tagHeaderBuffer.readUnsignedInt24();
|
||||||
|
tagTimestampUs = ((tagHeaderBuffer.readUnsignedByte() << 24) | tagTimestampUs) * 1000L;
|
||||||
|
tagHeaderBuffer.skipBytes(3); // streamId
|
||||||
|
parserState = STATE_READING_TAG_DATA;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads payload of an FLV tag from the provided {@link ExtractorInput}.
|
* Reads the body of a tag from the provided {@link ExtractorInput}.
|
||||||
|
*
|
||||||
* @param input The {@link ExtractorInput} from which to read.
|
* @param input The {@link ExtractorInput} from which to read.
|
||||||
* @return One of {@link Extractor#RESULT_CONTINUE} and {@link Extractor#RESULT_END_OF_INPUT}.
|
* @return True if the data was consumed by a reader. False if it was skipped.
|
||||||
* @throws IOException If an error occurred reading from the source.
|
* @throws IOException If an error occurred reading or parsing data from the source.
|
||||||
* @throws InterruptedException If the thread was interrupted.
|
* @throws InterruptedException If the thread was interrupted.
|
||||||
* @throws TagPayloadReader.UnsupportedFormatException If payload of the tag is using a codec non
|
|
||||||
* supported codec.
|
|
||||||
*/
|
*/
|
||||||
private int readSample(ExtractorInput input) throws IOException,
|
private boolean readTagData(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
InterruptedException, AudioTagPayloadReader.UnsupportedFormatException {
|
boolean wasConsumed = true;
|
||||||
if (tagData != null) {
|
if (tagType == TAG_TYPE_AUDIO && audioReader != null) {
|
||||||
if (!input.readFully(tagData.data, 0, currentTagHeader.dataSize, true)) {
|
audioReader.consume(prepareTagData(input), tagTimestampUs);
|
||||||
return RESULT_END_OF_INPUT;
|
} else if (tagType == TAG_TYPE_VIDEO && videoReader != null) {
|
||||||
}
|
videoReader.consume(prepareTagData(input), tagTimestampUs);
|
||||||
tagData.setPosition(0);
|
} else if (tagType == TAG_TYPE_SCRIPT_DATA && metadataReader != null) {
|
||||||
} else {
|
metadataReader.consume(prepareTagData(input), tagTimestampUs);
|
||||||
input.skipFully(currentTagHeader.dataSize);
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass payload to the right payload reader.
|
|
||||||
if (currentTagHeader.type == TAG_TYPE_AUDIO && audioReader != null) {
|
|
||||||
audioReader.consume(tagData, currentTagHeader.timestamp);
|
|
||||||
} else if (currentTagHeader.type == TAG_TYPE_VIDEO && videoReader != null) {
|
|
||||||
videoReader.consume(tagData, currentTagHeader.timestamp);
|
|
||||||
} else if (currentTagHeader.type == TAG_TYPE_SCRIPT_DATA && metadataReader != null) {
|
|
||||||
metadataReader.consume(tagData, currentTagHeader.timestamp);
|
|
||||||
if (metadataReader.getDurationUs() != C.UNKNOWN_TIME_US) {
|
if (metadataReader.getDurationUs() != C.UNKNOWN_TIME_US) {
|
||||||
if (audioReader != null) {
|
if (audioReader != null) {
|
||||||
audioReader.setDurationUs(metadataReader.getDurationUs());
|
audioReader.setDurationUs(metadataReader.getDurationUs());
|
||||||
|
|
@ -266,16 +246,28 @@ public final class FlvExtractor implements Extractor, SeekMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tagData.reset();
|
input.skipFully(tagDataSize);
|
||||||
|
wasConsumed = false;
|
||||||
}
|
}
|
||||||
|
bytesToNextTagHeader = 4; // There's a 4 byte previous tag size before the next header.
|
||||||
|
parserState = STATE_SKIPPING_TO_TAG_HEADER;
|
||||||
|
return wasConsumed;
|
||||||
|
}
|
||||||
|
|
||||||
parserState = STATE_READING_TAG_HEADER;
|
private ParsableByteArray prepareTagData(ExtractorInput input) throws IOException,
|
||||||
|
InterruptedException {
|
||||||
return RESULT_CONTINUE;
|
if (tagDataSize > tagData.capacity()) {
|
||||||
|
tagData.reset(new byte[Math.max(tagData.capacity() * 2, tagDataSize)], 0);
|
||||||
|
} else {
|
||||||
|
tagData.setPosition(0);
|
||||||
|
}
|
||||||
|
tagData.setLimit(tagDataSize);
|
||||||
|
input.readFully(tagData.data, 0, tagDataSize);
|
||||||
|
return tagData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SeekMap implementation.
|
// SeekMap implementation.
|
||||||
// TODO: Add seeking support
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSeekable() {
|
public boolean isSeekable() {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -286,16 +278,4 @@ public final class FlvExtractor implements Extractor, SeekMap {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines header of a FLV tag
|
|
||||||
*/
|
|
||||||
final class TagHeader {
|
|
||||||
public int type;
|
|
||||||
public int dataSize;
|
|
||||||
public long timestamp;
|
|
||||||
public int streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer.extractor.flv;
|
package com.google.android.exoplayer.extractor.flv;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
|
@ -55,17 +56,16 @@ import java.util.Map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException {
|
protected boolean parseHeader(ParsableByteArray data) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
@Override
|
||||||
protected void parsePayload(ParsableByteArray data, long timeUs) {
|
protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException {
|
||||||
int nameType = readAmfType(data);
|
int nameType = readAmfType(data);
|
||||||
if (nameType != AMF_TYPE_STRING) {
|
if (nameType != AMF_TYPE_STRING) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
return;
|
throw new ParserException();
|
||||||
}
|
}
|
||||||
String name = readAmfString(data);
|
String name = readAmfString(data);
|
||||||
if (!NAME_METADATA.equals(name)) {
|
if (!NAME_METADATA.equals(name)) {
|
||||||
|
|
@ -75,21 +75,118 @@ import java.util.Map;
|
||||||
int type = readAmfType(data);
|
int type = readAmfType(data);
|
||||||
if (type != AMF_TYPE_ECMA_ARRAY) {
|
if (type != AMF_TYPE_ECMA_ARRAY) {
|
||||||
// Should never happen.
|
// Should never happen.
|
||||||
return;
|
throw new ParserException();
|
||||||
}
|
}
|
||||||
// Set the duration to the value contained in the metadata, if present.
|
// Set the duration to the value contained in the metadata, if present.
|
||||||
Map<String, Object> metadata = (Map<String, Object>) readAmfData(data, type);
|
Map<String, Object> metadata = readAmfEcmaArray(data);
|
||||||
if (metadata.containsKey(KEY_DURATION)) {
|
if (metadata.containsKey(KEY_DURATION)) {
|
||||||
double durationSeconds = (double) metadata.get(KEY_DURATION);
|
double durationSeconds = (double) metadata.get(KEY_DURATION);
|
||||||
setDurationUs((long) (durationSeconds * C.MICROS_PER_SECOND));
|
setDurationUs((long) (durationSeconds * C.MICROS_PER_SECOND));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int readAmfType(ParsableByteArray data) {
|
private static int readAmfType(ParsableByteArray data) {
|
||||||
return data.readUnsignedByte();
|
return data.readUnsignedByte();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object readAmfData(ParsableByteArray data, int type) {
|
/**
|
||||||
|
* Read a boolean from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static Boolean readAmfBoolean(ParsableByteArray data) {
|
||||||
|
return data.readUnsignedByte() == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a double number from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static Double readAmfDouble(ParsableByteArray data) {
|
||||||
|
return Double.longBitsToDouble(data.readLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a string from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static String readAmfString(ParsableByteArray data) {
|
||||||
|
int size = data.readUnsignedShort();
|
||||||
|
int position = data.getPosition();
|
||||||
|
data.skipBytes(size);
|
||||||
|
return new String(data.data, position, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an array from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static ArrayList<Object> readAmfStrictArray(ParsableByteArray data) {
|
||||||
|
int count = data.readUnsignedIntToInt();
|
||||||
|
ArrayList<Object> list = new ArrayList<>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
int type = readAmfType(data);
|
||||||
|
list.add(readAmfData(data, type));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an object from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static HashMap<String, Object> readAmfObject(ParsableByteArray data) {
|
||||||
|
HashMap<String, Object> array = new HashMap<>();
|
||||||
|
while (true) {
|
||||||
|
String key = readAmfString(data);
|
||||||
|
int type = readAmfType(data);
|
||||||
|
if (type == AMF_TYPE_END_MARKER) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
array.put(key, readAmfData(data, type));
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read an ECMA array from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static HashMap<String, Object> readAmfEcmaArray(ParsableByteArray data) {
|
||||||
|
int count = data.readUnsignedIntToInt();
|
||||||
|
HashMap<String, Object> array = new HashMap<>(count);
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
String key = readAmfString(data);
|
||||||
|
int type = readAmfType(data);
|
||||||
|
array.put(key, readAmfData(data, type));
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read a date from an AMF encoded buffer.
|
||||||
|
*
|
||||||
|
* @param data The buffer from which to read.
|
||||||
|
* @return The value read from the buffer.
|
||||||
|
*/
|
||||||
|
private static Date readAmfDate(ParsableByteArray data) {
|
||||||
|
Date date = new Date((long) readAmfDouble(data).doubleValue());
|
||||||
|
data.skipBytes(2); // Skip reserved bytes.
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object readAmfData(ParsableByteArray data, int type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case AMF_TYPE_NUMBER:
|
case AMF_TYPE_NUMBER:
|
||||||
return readAmfDouble(data);
|
return readAmfDouble(data);
|
||||||
|
|
@ -110,101 +207,4 @@ import java.util.Map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a boolean from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Boolean readAmfBoolean(ParsableByteArray data) {
|
|
||||||
return data.readUnsignedByte() == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a double number from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Double readAmfDouble(ParsableByteArray data) {
|
|
||||||
return Double.longBitsToDouble(data.readLong());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a string from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private String readAmfString(ParsableByteArray data) {
|
|
||||||
int size = data.readUnsignedShort();
|
|
||||||
int position = data.getPosition();
|
|
||||||
data.skipBytes(size);
|
|
||||||
return new String(data.data, position, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read an array from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Object readAmfStrictArray(ParsableByteArray data) {
|
|
||||||
long count = data.readUnsignedInt();
|
|
||||||
ArrayList<Object> list = new ArrayList<>();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
int type = readAmfType(data);
|
|
||||||
list.add(readAmfData(data, type));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read an object from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Object readAmfObject(ParsableByteArray data) {
|
|
||||||
HashMap<String, Object> array = new HashMap<>();
|
|
||||||
while (true) {
|
|
||||||
String key = readAmfString(data);
|
|
||||||
int type = readAmfType(data);
|
|
||||||
if (type == AMF_TYPE_END_MARKER) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
array.put(key, readAmfData(data, type));
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read an ECMA array from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Object readAmfEcmaArray(ParsableByteArray data) {
|
|
||||||
long count = data.readUnsignedInt();
|
|
||||||
HashMap<String, Object> array = new HashMap<>();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
String key = readAmfString(data);
|
|
||||||
int type = readAmfType(data);
|
|
||||||
array.put(key, readAmfData(data, type));
|
|
||||||
}
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Read a date from an AMF encoded buffer.
|
|
||||||
*
|
|
||||||
* @param data The buffer from which to read.
|
|
||||||
* @return The value read from the buffer.
|
|
||||||
*/
|
|
||||||
private Date readAmfDate(ParsableByteArray data) {
|
|
||||||
Date date = new Date((long) readAmfDouble(data).doubleValue());
|
|
||||||
data.readUnsignedShort(); // Skip reserved bytes.
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package com.google.android.exoplayer.extractor.flv;
|
package com.google.android.exoplayer.extractor.flv;
|
||||||
|
|
||||||
import com.google.android.exoplayer.C;
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.extractor.TrackOutput;
|
import com.google.android.exoplayer.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
/**
|
/**
|
||||||
* Thrown when the format is not supported.
|
* Thrown when the format is not supported.
|
||||||
*/
|
*/
|
||||||
public static final class UnsupportedFormatException extends Exception {
|
public static final class UnsupportedFormatException extends ParserException {
|
||||||
|
|
||||||
public UnsupportedFormatException(String msg) {
|
public UnsupportedFormatException(String msg) {
|
||||||
super(msg);
|
super(msg);
|
||||||
|
|
@ -79,8 +80,9 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
*
|
*
|
||||||
* @param data The payload data to consume.
|
* @param data The payload data to consume.
|
||||||
* @param timeUs The timestamp associated with the payload.
|
* @param timeUs The timestamp associated with the payload.
|
||||||
|
* @throws ParserException If an error occurs parsing the data.
|
||||||
*/
|
*/
|
||||||
public final void consume(ParsableByteArray data, long timeUs) throws UnsupportedFormatException {
|
public final void consume(ParsableByteArray data, long timeUs) throws ParserException {
|
||||||
if (parseHeader(data)) {
|
if (parseHeader(data)) {
|
||||||
parsePayload(data, timeUs);
|
parsePayload(data, timeUs);
|
||||||
}
|
}
|
||||||
|
|
@ -92,16 +94,17 @@ import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
* @param data Buffer where the tag header is stored.
|
* @param data Buffer where the tag header is stored.
|
||||||
* @return True if the header was parsed successfully and the payload should be read. False
|
* @return True if the header was parsed successfully and the payload should be read. False
|
||||||
* otherwise.
|
* otherwise.
|
||||||
* @throws UnsupportedFormatException
|
* @throws ParserException If an error occurs parsing the header.
|
||||||
*/
|
*/
|
||||||
protected abstract boolean parseHeader(ParsableByteArray data) throws UnsupportedFormatException;
|
protected abstract boolean parseHeader(ParsableByteArray data) throws ParserException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses tag payload.
|
* Parses tag payload.
|
||||||
*
|
*
|
||||||
* @param data Buffer where tag payload is stored
|
* @param data Buffer where tag payload is stored
|
||||||
* @param timeUs Time position of the frame
|
* @param timeUs Time position of the frame
|
||||||
|
* @throws ParserException If an error occurs parsing the payload.
|
||||||
*/
|
*/
|
||||||
protected abstract void parsePayload(ParsableByteArray data, long timeUs);
|
protected abstract void parsePayload(ParsableByteArray data, long timeUs) throws ParserException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@ import com.google.android.exoplayer.util.NalUnitUtil;
|
||||||
import com.google.android.exoplayer.util.ParsableBitArray;
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer.util.ParsableByteArray;
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
@ -35,24 +33,22 @@ import java.util.List;
|
||||||
* Parses video tags from an FLV stream and extracts H.264 nal units.
|
* Parses video tags from an FLV stream and extracts H.264 nal units.
|
||||||
*/
|
*/
|
||||||
/* package */ final class VideoTagPayloadReader extends TagPayloadReader {
|
/* package */ final class VideoTagPayloadReader extends TagPayloadReader {
|
||||||
private static final String TAG = "VideoTagPayloadReader";
|
|
||||||
|
|
||||||
// Video codec
|
// Video codec.
|
||||||
private static final int VIDEO_CODEC_AVC = 7;
|
private static final int VIDEO_CODEC_AVC = 7;
|
||||||
|
|
||||||
// FRAME TYPE
|
// Frame types.
|
||||||
private static final int VIDEO_FRAME_KEYFRAME = 1;
|
private static final int VIDEO_FRAME_KEYFRAME = 1;
|
||||||
private static final int VIDEO_FRAME_VIDEO_INFO = 5;
|
private static final int VIDEO_FRAME_VIDEO_INFO = 5;
|
||||||
|
|
||||||
// PACKET TYPE
|
// Packet types.
|
||||||
private static final int AVC_PACKET_TYPE_SEQUENCE_HEADER = 0;
|
private static final int AVC_PACKET_TYPE_SEQUENCE_HEADER = 0;
|
||||||
private static final int AVC_PACKET_TYPE_AVC_NALU = 1;
|
private static final int AVC_PACKET_TYPE_AVC_NALU = 1;
|
||||||
private static final int AVC_PACKET_TYPE_AVC_END_OF_SEQUENCE = 2;
|
|
||||||
|
|
||||||
// Temporary arrays.
|
// Temporary arrays.
|
||||||
private final ParsableByteArray nalStartCode;
|
private final ParsableByteArray nalStartCode;
|
||||||
private final ParsableByteArray nalLength;
|
private final ParsableByteArray nalLength;
|
||||||
private int nalUnitsLength;
|
private int nalUnitLengthFieldLength;
|
||||||
|
|
||||||
// State variables.
|
// State variables.
|
||||||
private boolean hasOutputFormat;
|
private boolean hasOutputFormat;
|
||||||
|
|
@ -86,28 +82,17 @@ import java.util.List;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void parsePayload(ParsableByteArray data, long timeUs) {
|
protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException {
|
||||||
int packetType = data.readUnsignedByte();
|
int packetType = data.readUnsignedByte();
|
||||||
int compositionTime = data.readUnsignedInt24();
|
int compositionTimeMs = data.readUnsignedInt24();
|
||||||
// If there is a composition time, adjust timeUs accordingly
|
timeUs += compositionTimeMs * 1000L;
|
||||||
// Note: compositionTime within AVCVIDEOPACKET is provided in milliseconds
|
|
||||||
// and timeUs is in microseconds.
|
|
||||||
if (compositionTime > 0) {
|
|
||||||
timeUs += compositionTime * 1000;
|
|
||||||
}
|
|
||||||
// Parse avc sequence header in case this was not done before.
|
// Parse avc sequence header in case this was not done before.
|
||||||
if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) {
|
if (packetType == AVC_PACKET_TYPE_SEQUENCE_HEADER && !hasOutputFormat) {
|
||||||
ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]);
|
ParsableByteArray videoSequence = new ParsableByteArray(new byte[data.bytesLeft()]);
|
||||||
data.readBytes(videoSequence.data, 0, data.bytesLeft());
|
data.readBytes(videoSequence.data, 0, data.bytesLeft());
|
||||||
|
|
||||||
AvcSequenceHeaderData avcData;
|
AvcSequenceHeaderData avcData = parseAvcCodecPrivate(videoSequence);
|
||||||
try {
|
nalUnitLengthFieldLength = avcData.nalUnitLengthFieldLength;
|
||||||
avcData = parseAvcCodecPrivate(videoSequence);
|
|
||||||
nalUnitsLength = avcData.nalUnitLengthFieldLength;
|
|
||||||
} catch (ParserException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct and output the format.
|
// Construct and output the format.
|
||||||
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.NO_VALUE,
|
MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.NO_VALUE,
|
||||||
|
|
@ -124,8 +109,7 @@ import java.util.List;
|
||||||
nalLengthData[0] = 0;
|
nalLengthData[0] = 0;
|
||||||
nalLengthData[1] = 0;
|
nalLengthData[1] = 0;
|
||||||
nalLengthData[2] = 0;
|
nalLengthData[2] = 0;
|
||||||
int nalUnitLengthFieldLength = nalUnitsLength;
|
int nalUnitLengthFieldLengthDiff = 4 - nalUnitLengthFieldLength;
|
||||||
int nalUnitLengthFieldLengthDiff = 4 - nalUnitsLength;
|
|
||||||
// NAL units are length delimited, but the decoder requires start code delimited units.
|
// NAL units are length delimited, but the decoder requires start code delimited units.
|
||||||
// Loop until we've written the sample to the track output, replacing length delimiters with
|
// Loop until we've written the sample to the track output, replacing length delimiters with
|
||||||
// start codes as we encounter them.
|
// start codes as we encounter them.
|
||||||
|
|
@ -137,65 +121,58 @@ import java.util.List;
|
||||||
nalLength.setPosition(0);
|
nalLength.setPosition(0);
|
||||||
bytesToWrite = nalLength.readUnsignedIntToInt();
|
bytesToWrite = nalLength.readUnsignedIntToInt();
|
||||||
|
|
||||||
// First, write nal start code (replacing length field by nal delimiter codes)
|
// Write a start code for the current NAL unit.
|
||||||
nalStartCode.setPosition(0);
|
nalStartCode.setPosition(0);
|
||||||
output.sampleData(nalStartCode, 4);
|
output.sampleData(nalStartCode, 4);
|
||||||
bytesWritten += 4;
|
bytesWritten += 4;
|
||||||
|
|
||||||
// Then write nal unit itsef
|
// Write the payload of the NAL unit.
|
||||||
output.sampleData(data, bytesToWrite);
|
output.sampleData(data, bytesToWrite);
|
||||||
bytesWritten += bytesToWrite;
|
bytesWritten += bytesToWrite;
|
||||||
}
|
}
|
||||||
output.sampleMetadata(timeUs, frameType == VIDEO_FRAME_KEYFRAME ? C.SAMPLE_FLAG_SYNC : 0,
|
output.sampleMetadata(timeUs, frameType == VIDEO_FRAME_KEYFRAME ? C.SAMPLE_FLAG_SYNC : 0,
|
||||||
bytesWritten, 0, null);
|
bytesWritten, 0, null);
|
||||||
} else if (packetType == AVC_PACKET_TYPE_AVC_END_OF_SEQUENCE) {
|
|
||||||
Log.d(TAG, "End of seq!!!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data.
|
* Builds initialization data for a {@link MediaFormat} from H.264 (AVC) codec private data.
|
||||||
*
|
*
|
||||||
* @return The AvcSequenceHeader data with all the information needed to initialize
|
* @return The AvcSequenceHeader data needed to initialize the video codec.
|
||||||
* the video codec.
|
|
||||||
* @throws ParserException If the initialization data could not be built.
|
* @throws ParserException If the initialization data could not be built.
|
||||||
*/
|
*/
|
||||||
private AvcSequenceHeaderData parseAvcCodecPrivate(ParsableByteArray buffer)
|
private AvcSequenceHeaderData parseAvcCodecPrivate(ParsableByteArray buffer)
|
||||||
throws ParserException {
|
throws ParserException {
|
||||||
try {
|
// TODO: Deduplicate with AtomParsers.parseAvcCFromParent.
|
||||||
// TODO: Deduplicate with AtomParsers.parseAvcCFromParent.
|
buffer.setPosition(4);
|
||||||
buffer.setPosition(4);
|
int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1;
|
||||||
int nalUnitLengthFieldLength = (buffer.readUnsignedByte() & 0x03) + 1;
|
Assertions.checkState(nalUnitLengthFieldLength != 3);
|
||||||
Assertions.checkState(nalUnitLengthFieldLength != 3);
|
List<byte[]> initializationData = new ArrayList<>();
|
||||||
List<byte[]> initializationData = new ArrayList<>();
|
int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F;
|
||||||
int numSequenceParameterSets = buffer.readUnsignedByte() & 0x1F;
|
for (int i = 0; i < numSequenceParameterSets; i++) {
|
||||||
for (int i = 0; i < numSequenceParameterSets; i++) {
|
initializationData.add(NalUnitUtil.parseChildNalUnit(buffer));
|
||||||
initializationData.add(NalUnitUtil.parseChildNalUnit(buffer));
|
|
||||||
}
|
|
||||||
int numPictureParameterSets = buffer.readUnsignedByte();
|
|
||||||
for (int j = 0; j < numPictureParameterSets; j++) {
|
|
||||||
initializationData.add(NalUnitUtil.parseChildNalUnit(buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
float pixelWidthAspectRatio = 1;
|
|
||||||
int width = MediaFormat.NO_VALUE;
|
|
||||||
int height = MediaFormat.NO_VALUE;
|
|
||||||
if (numSequenceParameterSets > 0) {
|
|
||||||
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
|
|
||||||
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
|
||||||
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
|
||||||
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
|
||||||
CodecSpecificDataUtil.SpsData sps = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray);
|
|
||||||
width = sps.width;
|
|
||||||
height = sps.height;
|
|
||||||
pixelWidthAspectRatio = sps.pixelWidthAspectRatio;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength,
|
|
||||||
width, height, pixelWidthAspectRatio);
|
|
||||||
} catch (ArrayIndexOutOfBoundsException e) {
|
|
||||||
throw new ParserException("Error parsing AVC codec private");
|
|
||||||
}
|
}
|
||||||
|
int numPictureParameterSets = buffer.readUnsignedByte();
|
||||||
|
for (int j = 0; j < numPictureParameterSets; j++) {
|
||||||
|
initializationData.add(NalUnitUtil.parseChildNalUnit(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
float pixelWidthAspectRatio = 1;
|
||||||
|
int width = MediaFormat.NO_VALUE;
|
||||||
|
int height = MediaFormat.NO_VALUE;
|
||||||
|
if (numSequenceParameterSets > 0) {
|
||||||
|
// Parse the first sequence parameter set to obtain pixelWidthAspectRatio.
|
||||||
|
ParsableBitArray spsDataBitArray = new ParsableBitArray(initializationData.get(0));
|
||||||
|
// Skip the NAL header consisting of the nalUnitLengthField and the type (1 byte).
|
||||||
|
spsDataBitArray.setPosition(8 * (nalUnitLengthFieldLength + 1));
|
||||||
|
CodecSpecificDataUtil.SpsData sps = CodecSpecificDataUtil.parseSpsNalUnit(spsDataBitArray);
|
||||||
|
width = sps.width;
|
||||||
|
height = sps.height;
|
||||||
|
pixelWidthAspectRatio = sps.pixelWidthAspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AvcSequenceHeaderData(initializationData, nalUnitLengthFieldLength,
|
||||||
|
width, height, pixelWidthAspectRatio);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue