Added MPEG2-Program Stream extractor.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117219856
This commit is contained in:
parent
2b4dcbef3f
commit
ba88091c7b
@ -161,6 +161,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
|
|||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
// Extractor not found.
|
// Extractor not found.
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
DEFAULT_EXTRACTOR_CLASSES.add(
|
||||||
|
Class.forName("com.google.android.exoplayer.extractor.ts.PsExtractor")
|
||||||
|
.asSubclass(Extractor.class));
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
// Extractor not found.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ExtractorHolder extractorHolder;
|
private final ExtractorHolder extractorHolder;
|
||||||
|
@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.extractor.ts;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.C;
|
||||||
|
import com.google.android.exoplayer.extractor.Extractor;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||||
|
import com.google.android.exoplayer.extractor.ExtractorOutput;
|
||||||
|
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||||
|
import com.google.android.exoplayer.extractor.SeekMap;
|
||||||
|
import com.google.android.exoplayer.util.ParsableBitArray;
|
||||||
|
import com.google.android.exoplayer.util.ParsableByteArray;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.SparseArray;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||||
|
*/
|
||||||
|
public final class PsExtractor implements Extractor {
|
||||||
|
|
||||||
|
private static final String TAG = "PsExtractor";
|
||||||
|
|
||||||
|
private static final int PACK_START_CODE = 0x000001BA;
|
||||||
|
private static final int SYSTEM_HEADER_START_CODE = 0x000001BB;
|
||||||
|
private static final int PACKET_START_CODE_PREFIX = 0x000001;
|
||||||
|
private static final int MPEG_PROGRAM_END_CODE = 0x000001B9;
|
||||||
|
private static final long MAX_SEARCH_LENGTH = 1024 * 1024;
|
||||||
|
|
||||||
|
public static final int PRIVATE_STREAM_1 = 0xBD;
|
||||||
|
public static final int AUDIO_STREAM = 0xC0;
|
||||||
|
public static final int AUDIO_STREAM_MASK = 0xE0;
|
||||||
|
public static final int VIDEO_STREAM = 0xE0;
|
||||||
|
public static final int VIDEO_STREAM_MASK = 0xF0;
|
||||||
|
|
||||||
|
private final PtsTimestampAdjuster ptsTimestampAdjuster;
|
||||||
|
private final SparseArray<PesReader> psPayloadReaders; // Indexed by pid
|
||||||
|
private final ParsableByteArray psPacketBuffer;
|
||||||
|
private boolean foundAllTracks;
|
||||||
|
private boolean foundAudioTrack;
|
||||||
|
private boolean foundVideoTrack;
|
||||||
|
|
||||||
|
// Accessed only by the loading thread.
|
||||||
|
private ExtractorOutput output;
|
||||||
|
|
||||||
|
public PsExtractor() {
|
||||||
|
this(new PtsTimestampAdjuster(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public PsExtractor(PtsTimestampAdjuster ptsTimestampAdjuster) {
|
||||||
|
this.ptsTimestampAdjuster = ptsTimestampAdjuster;
|
||||||
|
psPacketBuffer = new ParsableByteArray(4096);
|
||||||
|
psPayloadReaders = new SparseArray<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extractor implementation.
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
|
||||||
|
byte[] scratch = new byte[14];
|
||||||
|
input.peekFully(scratch, 0, 14);
|
||||||
|
|
||||||
|
// Verify the PACK_START_CODE for the first 4 bytes
|
||||||
|
if (PACK_START_CODE != (((scratch[0] & 0xFF) << 24) | ((scratch[1] & 0xFF) << 16)
|
||||||
|
| ((scratch[2] & 0xFF) << 8) | (scratch[3] & 0xFF))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Verify the 01xxx1xx marker on the 5th byte
|
||||||
|
if ((scratch[4] & 0xC4) != 0x44) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Verify the xxxxx1xx marker on the 7th byte
|
||||||
|
if ((scratch[6] & 0x04) != 0x04) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Verify the xxxxx1xx marker on the 9th byte
|
||||||
|
if ((scratch[8] & 0x04) != 0x04) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Verify the xxxxxxx1 marker on the 10th byte
|
||||||
|
if ((scratch[9] & 0x01) != 0x01) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Verify the xxxxxx11 marker on the 13th byte
|
||||||
|
if ((scratch[12] & 0x03) != 0x03) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Read the stuffing length from the 14th byte (last 3 bits)
|
||||||
|
int packStuffingLength = scratch[13] & 0x07;
|
||||||
|
input.advancePeekPosition(packStuffingLength);
|
||||||
|
// Now check that the next 3 bytes are the beginning of an MPEG start code
|
||||||
|
input.peekFully(scratch, 0, 3);
|
||||||
|
return (PACKET_START_CODE_PREFIX == (((scratch[0] & 0xFF) << 16) | ((scratch[1] & 0xFF) << 8)
|
||||||
|
| (scratch[2] & 0xFF)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(ExtractorOutput output) {
|
||||||
|
this.output = output;
|
||||||
|
output.seekMap(new SeekMap.Unseekable(C.UNKNOWN_TIME_US));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek() {
|
||||||
|
ptsTimestampAdjuster.reset();
|
||||||
|
for (int i = 0; i < psPayloadReaders.size(); i++) {
|
||||||
|
psPayloadReaders.valueAt(i).seek();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ExtractorInput input, PositionHolder seekPosition)
|
||||||
|
throws IOException, InterruptedException {
|
||||||
|
// First peek and check what type of start code is next.
|
||||||
|
if (!input.peekFully(psPacketBuffer.data, 0, 4, true)) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
psPacketBuffer.setPosition(0);
|
||||||
|
int nextStartCode = psPacketBuffer.readInt();
|
||||||
|
if (nextStartCode == MPEG_PROGRAM_END_CODE) {
|
||||||
|
return RESULT_END_OF_INPUT;
|
||||||
|
} else if (nextStartCode == PACK_START_CODE) {
|
||||||
|
// Now peek the rest of the pack_header.
|
||||||
|
input.peekFully(psPacketBuffer.data, 0, 10);
|
||||||
|
|
||||||
|
// We only care about the pack_stuffing_length in here, skip the first 77 bits.
|
||||||
|
psPacketBuffer.setPosition(0);
|
||||||
|
psPacketBuffer.skipBytes(9);
|
||||||
|
|
||||||
|
// Last 3 bits is the length.
|
||||||
|
int packStuffingLength = psPacketBuffer.readUnsignedByte() & 0x07;
|
||||||
|
|
||||||
|
// Now skip the stuffing and the pack header.
|
||||||
|
input.skipFully(packStuffingLength + 14);
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
} else if (nextStartCode == SYSTEM_HEADER_START_CODE) {
|
||||||
|
// We just skip all this, but we need to get the length first.
|
||||||
|
input.peekFully(psPacketBuffer.data, 0, 2);
|
||||||
|
|
||||||
|
// Length is the next 2 bytes.
|
||||||
|
psPacketBuffer.setPosition(0);
|
||||||
|
int systemHeaderLength = psPacketBuffer.readUnsignedShort();
|
||||||
|
input.skipFully(systemHeaderLength + 6);
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
} else if (((nextStartCode & 0xFFFFFF00) >> 8) != PACKET_START_CODE_PREFIX) {
|
||||||
|
Log.w(TAG, "Missing PACKET_START_CODE_PREFIX!!");
|
||||||
|
input.skipFully(1); // Skip bytes until we see a valid start code again.
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're at the start of a regular PES packet now.
|
||||||
|
// Get the stream ID off the last byte of the start code.
|
||||||
|
int streamId = nextStartCode & 0xFF;
|
||||||
|
|
||||||
|
// Check to see if we have this one in our map yet, and if not, then add it.
|
||||||
|
PesReader payloadReader = psPayloadReaders.get(streamId);
|
||||||
|
if (!foundAllTracks) {
|
||||||
|
if (payloadReader == null) {
|
||||||
|
if (streamId == PRIVATE_STREAM_1 && !foundAudioTrack) {
|
||||||
|
// Private stream, used for AC3 audio.
|
||||||
|
// NOTE: This may need further parsing to determine if its DTS,
|
||||||
|
// but that's likely only valid for DVDs.
|
||||||
|
payloadReader = new PesReader(new Ac3Reader(output.track(streamId), false));
|
||||||
|
psPayloadReaders.put(streamId, payloadReader);
|
||||||
|
foundAudioTrack = true;
|
||||||
|
Log.d(TAG, "Setup payload reader for AC3");
|
||||||
|
} else if ((streamId & AUDIO_STREAM_MASK) == AUDIO_STREAM && !foundAudioTrack) {
|
||||||
|
payloadReader = new PesReader(new MpegAudioReader(output.track(streamId)));
|
||||||
|
psPayloadReaders.put(streamId, payloadReader);
|
||||||
|
foundAudioTrack = true;
|
||||||
|
Log.d(TAG, "Setup payload reader for MP2");
|
||||||
|
} else if ((streamId & VIDEO_STREAM_MASK) == VIDEO_STREAM && !foundVideoTrack) {
|
||||||
|
payloadReader = new PesReader(new H262Reader(output.track(streamId)));
|
||||||
|
psPayloadReaders.put(streamId, payloadReader);
|
||||||
|
foundVideoTrack = true;
|
||||||
|
Log.d(TAG, "Setup payload reader for MPEG2Video");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((foundAudioTrack && foundVideoTrack) || input.getPosition() > MAX_SEARCH_LENGTH) {
|
||||||
|
foundAllTracks = true;
|
||||||
|
output.endTracks();
|
||||||
|
Log.d(TAG, "Signalled that all tracks were found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The next 2 bytes are the length, once we have that we can consume the complete packet.
|
||||||
|
input.peekFully(psPacketBuffer.data, 0, 2);
|
||||||
|
psPacketBuffer.setPosition(0);
|
||||||
|
int payloadLength = psPacketBuffer.readUnsignedShort();
|
||||||
|
int pesLength = payloadLength + 6;
|
||||||
|
|
||||||
|
if (payloadReader == null) {
|
||||||
|
// Just skip this data.
|
||||||
|
input.skipFully(pesLength);
|
||||||
|
} else {
|
||||||
|
if (psPacketBuffer.capacity() < pesLength) {
|
||||||
|
// Reallocate for this and future packets.
|
||||||
|
psPacketBuffer.reset(new byte[pesLength], pesLength);
|
||||||
|
}
|
||||||
|
// Read the whole packet and the header for consumption.
|
||||||
|
input.readFully(psPacketBuffer.data, 0, pesLength);
|
||||||
|
psPacketBuffer.setPosition(6);
|
||||||
|
psPacketBuffer.setLimit(pesLength);
|
||||||
|
payloadReader.consume(psPacketBuffer, output);
|
||||||
|
psPacketBuffer.setLimit(psPacketBuffer.capacity());
|
||||||
|
}
|
||||||
|
|
||||||
|
return RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internals.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses PES packet data and extracts samples.
|
||||||
|
*/
|
||||||
|
private class PesReader {
|
||||||
|
|
||||||
|
private static final int PES_SCRATCH_SIZE = 64;
|
||||||
|
|
||||||
|
private final ParsableBitArray pesScratch;
|
||||||
|
private final ElementaryStreamReader pesPayloadReader;
|
||||||
|
|
||||||
|
private boolean ptsFlag;
|
||||||
|
private boolean dtsFlag;
|
||||||
|
private boolean seenFirstDts;
|
||||||
|
private int extendedHeaderLength;
|
||||||
|
private long timeUs;
|
||||||
|
|
||||||
|
public PesReader(ElementaryStreamReader pesPayloadReader) {
|
||||||
|
this.pesPayloadReader = pesPayloadReader;
|
||||||
|
pesScratch = new ParsableBitArray(new byte[PES_SCRATCH_SIZE]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies the reader that a seek has occurred.
|
||||||
|
* <p>
|
||||||
|
* Following a call to this method, the data passed to the next invocation of
|
||||||
|
* {@link #consume(ParsableByteArray, ExtractorOutput)} will not be a continuation of
|
||||||
|
* the data that was previously passed. Hence the reader should reset any internal state.
|
||||||
|
*/
|
||||||
|
public void seek() {
|
||||||
|
seenFirstDts = false;
|
||||||
|
pesPayloadReader.seek();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes the payload of a PS packet.
|
||||||
|
*
|
||||||
|
* @param data The PES packet. The position will be set to the start of the payload.
|
||||||
|
* @param output The output to which parsed data should be written.
|
||||||
|
*/
|
||||||
|
public void consume(ParsableByteArray data, ExtractorOutput output) {
|
||||||
|
data.readBytes(pesScratch.data, 0, 3);
|
||||||
|
pesScratch.setPosition(0);
|
||||||
|
parseHeader();
|
||||||
|
data.readBytes(pesScratch.data, 0, extendedHeaderLength);
|
||||||
|
pesScratch.setPosition(0);
|
||||||
|
parseHeaderExtension();
|
||||||
|
pesPayloadReader.packetStarted(timeUs, true);
|
||||||
|
pesPayloadReader.consume(data);
|
||||||
|
// We always have complete PES packets with program stream.
|
||||||
|
pesPayloadReader.packetFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseHeader() {
|
||||||
|
// Note: see ISO/IEC 13818-1, section 2.4.3.6 for detailed information on the format of
|
||||||
|
// the header.
|
||||||
|
// First 8 bits are skipped: '10' (2), PES_scrambling_control (2), PES_priority (1),
|
||||||
|
// data_alignment_indicator (1), copyright (1), original_or_copy (1)
|
||||||
|
pesScratch.skipBits(8);
|
||||||
|
ptsFlag = pesScratch.readBit();
|
||||||
|
dtsFlag = pesScratch.readBit();
|
||||||
|
// ESCR_flag (1), ES_rate_flag (1), DSM_trick_mode_flag (1),
|
||||||
|
// additional_copy_info_flag (1), PES_CRC_flag (1), PES_extension_flag (1)
|
||||||
|
pesScratch.skipBits(6);
|
||||||
|
extendedHeaderLength = pesScratch.readBits(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseHeaderExtension() {
|
||||||
|
timeUs = 0;
|
||||||
|
if (ptsFlag) {
|
||||||
|
pesScratch.skipBits(4); // '0010' or '0011'
|
||||||
|
long pts = (long) pesScratch.readBits(3) << 30;
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
pts |= pesScratch.readBits(15) << 15;
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
pts |= pesScratch.readBits(15);
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
if (!seenFirstDts && dtsFlag) {
|
||||||
|
pesScratch.skipBits(4); // '0011'
|
||||||
|
long dts = (long) pesScratch.readBits(3) << 30;
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
dts |= pesScratch.readBits(15) << 15;
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
dts |= pesScratch.readBits(15);
|
||||||
|
pesScratch.skipBits(1); // marker_bit
|
||||||
|
// Subsequent PES packets may have earlier presentation timestamps than this one, but they
|
||||||
|
// should all be greater than or equal to this packet's decode timestamp. We feed the
|
||||||
|
// decode timestamp to the adjuster here so that in the case that this is the first to be
|
||||||
|
// fed, the adjuster will be able to compute an offset to apply such that the adjusted
|
||||||
|
// presentation timestamps of all future packets are non-negative.
|
||||||
|
ptsTimestampAdjuster.adjustTimestamp(dts);
|
||||||
|
seenFirstDts = true;
|
||||||
|
}
|
||||||
|
timeUs = ptsTimestampAdjuster.adjustTimestamp(pts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user