mirror of
https://github.com/androidx/media.git
synced 2025-05-10 00:59:51 +08:00
Add support for multiple programs in a single TS
* Prevents calling endTracks() before all PMTs have been processed. * Adds a unique ID to the format of each track. This allows the track selector to identify which track belongs to each program. The format of each id is "<program number>/<track output id>". Note: This CL will break malformed TS files whose PAT declares more PMTs than it actually contains, which previously were supported. This does not apply for HLS mode. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=146151642
This commit is contained in:
parent
4301606200
commit
feeec77407
@ -6,7 +6,7 @@ numberOfTracks = 1
|
|||||||
track 0:
|
track 0:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 0
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/ac3
|
sampleMimeType = audio/ac3
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
|
@ -6,7 +6,7 @@ numberOfTracks = 2
|
|||||||
track 0:
|
track 0:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 0
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/mp4a-latm
|
sampleMimeType = audio/mp4a-latm
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
@ -606,7 +606,7 @@ track 0:
|
|||||||
track 1:
|
track 1:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 1
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = application/id3
|
sampleMimeType = application/id3
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
|
@ -6,7 +6,7 @@ numberOfTracks = 2
|
|||||||
track 192:
|
track 192:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 192
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/mpeg-L2
|
sampleMimeType = audio/mpeg-L2
|
||||||
maxInputSize = 4096
|
maxInputSize = 4096
|
||||||
@ -45,7 +45,7 @@ track 192:
|
|||||||
track 224:
|
track 224:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 224
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = video/mpeg2
|
sampleMimeType = video/mpeg2
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
|
@ -6,7 +6,7 @@ numberOfTracks = 2
|
|||||||
track 256:
|
track 256:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 1/256
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = video/mpeg2
|
sampleMimeType = video/mpeg2
|
||||||
maxInputSize = -1
|
maxInputSize = -1
|
||||||
@ -38,7 +38,7 @@ track 256:
|
|||||||
track 257:
|
track 257:
|
||||||
format:
|
format:
|
||||||
bitrate = -1
|
bitrate = -1
|
||||||
id = null
|
id = 1/257
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/mpeg-L2
|
sampleMimeType = audio/mpeg-L2
|
||||||
maxInputSize = 4096
|
maxInputSize = 4096
|
||||||
|
@ -92,7 +92,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
TrackOutput trackOutput = reader.getTrackOutput();
|
TrackOutput trackOutput = reader.getTrackOutput();
|
||||||
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
|
assertTrue(trackOutput == output.trackOutputs.get(257 /* PID of audio track. */));
|
||||||
assertEquals(
|
assertEquals(
|
||||||
Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0, "und", null, 0),
|
Format.createTextSampleFormat("1/257", "mime", null, 0, 0, "und", null, 0),
|
||||||
((FakeTrackOutput) trackOutput).format);
|
((FakeTrackOutput) trackOutput).format);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,8 +178,9 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
output.format(Format.createTextSampleFormat("Overriding format", "mime", null, 0, 0,
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
|
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), "mime", null, 0, 0,
|
||||||
language, null, 0));
|
language, null, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
private final ParsableByteArray headerScratchBytes;
|
private final ParsableByteArray headerScratchBytes;
|
||||||
private final String language;
|
private final String language;
|
||||||
|
|
||||||
|
private String trackFormatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
@ -84,7 +85,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator generator) {
|
||||||
output = extractorOutput.track(generator.getNextId());
|
generator.generateNewId();
|
||||||
|
trackFormatId = generator.getFormatId();
|
||||||
|
output = extractorOutput.track(generator.getTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -180,8 +183,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
headerScratchBits.skipBits(40);
|
headerScratchBits.skipBits(40);
|
||||||
isEac3 = headerScratchBits.readBits(5) == 16;
|
isEac3 = headerScratchBits.readBits(5) == 16;
|
||||||
headerScratchBits.setPosition(headerScratchBits.getPosition() - 45);
|
headerScratchBits.setPosition(headerScratchBits.getPosition() - 45);
|
||||||
format = isEac3 ? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, null, language , null)
|
format = isEac3
|
||||||
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, null, language, null);
|
? Ac3Util.parseEac3SyncframeFormat(headerScratchBits, trackFormatId, language , null)
|
||||||
|
: Ac3Util.parseAc3SyncframeFormat(headerScratchBits, trackFormatId, language, null);
|
||||||
output.format(format);
|
output.format(format);
|
||||||
}
|
}
|
||||||
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
|
sampleSize = isEac3 ? Ac3Util.parseEAc3SyncframeSize(headerScratchBits.data)
|
||||||
|
@ -61,6 +61,7 @@ import java.util.Collections;
|
|||||||
private final ParsableByteArray id3HeaderBuffer;
|
private final ParsableByteArray id3HeaderBuffer;
|
||||||
private final String language;
|
private final String language;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
private TrackOutput id3Output;
|
private TrackOutput id3Output;
|
||||||
|
|
||||||
@ -108,11 +109,14 @@ import java.util.Collections;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
if (exposeId3) {
|
if (exposeId3) {
|
||||||
id3Output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
id3Output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null,
|
id3Output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
Format.NO_VALUE, null));
|
id3Output.format(Format.createSampleFormat(idGenerator.getFormatId(),
|
||||||
|
MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE, null));
|
||||||
} else {
|
} else {
|
||||||
id3Output = new DummyTrackOutput();
|
id3Output = new DummyTrackOutput();
|
||||||
}
|
}
|
||||||
@ -300,7 +304,7 @@ import java.util.Collections;
|
|||||||
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
||||||
audioSpecificConfig);
|
audioSpecificConfig);
|
||||||
|
|
||||||
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
|
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
||||||
Collections.singletonList(audioSpecificConfig), null, 0, language);
|
Collections.singletonList(audioSpecificConfig), null, 0, language);
|
||||||
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
||||||
|
@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
private final ParsableByteArray headerScratchBytes;
|
private final ParsableByteArray headerScratchBytes;
|
||||||
private final String language;
|
private final String language;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
@ -79,7 +80,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -165,7 +168,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
private void parseHeader() {
|
private void parseHeader() {
|
||||||
byte[] frameData = headerScratchBytes.data;
|
byte[] frameData = headerScratchBytes.data;
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
format = DtsUtil.parseDtsFormat(frameData, null, language, null);
|
format = DtsUtil.parseDtsFormat(frameData, formatId, language, null);
|
||||||
output.format(format);
|
output.format(format);
|
||||||
}
|
}
|
||||||
sampleSize = DtsUtil.getDtsFrameSize(frameData);
|
sampleSize = DtsUtil.getDtsFrameSize(frameData);
|
||||||
|
@ -37,6 +37,7 @@ import java.util.Collections;
|
|||||||
private static final int START_EXTENSION = 0xB5;
|
private static final int START_EXTENSION = 0xB5;
|
||||||
private static final int START_GROUP = 0xB8;
|
private static final int START_GROUP = 0xB8;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
|
|
||||||
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
|
// Maps (frame_rate_code - 1) indices to values, as defined in ITU-T H.262 Table 6-4.
|
||||||
@ -78,7 +79,9 @@ import java.util.Collections;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -126,7 +129,7 @@ import java.util.Collections;
|
|||||||
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
int bytesAlreadyPassed = lengthToStartCode < 0 ? -lengthToStartCode : 0;
|
||||||
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
if (csdBuffer.onStartCode(startCodeValue, bytesAlreadyPassed)) {
|
||||||
// The csd data is complete, so we can decode and output the media format.
|
// The csd data is complete, so we can decode and output the media format.
|
||||||
Pair<Format, Long> result = parseCsdBuffer(csdBuffer);
|
Pair<Format, Long> result = parseCsdBuffer(csdBuffer, formatId);
|
||||||
output.format(result.first);
|
output.format(result.first);
|
||||||
frameDurationUs = result.second;
|
frameDurationUs = result.second;
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
@ -166,10 +169,11 @@ import java.util.Collections;
|
|||||||
* Parses the {@link Format} and frame duration from a csd buffer.
|
* Parses the {@link Format} and frame duration from a csd buffer.
|
||||||
*
|
*
|
||||||
* @param csdBuffer The csd buffer.
|
* @param csdBuffer The csd buffer.
|
||||||
|
* @param formatId The id for the generated format. May be null.
|
||||||
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
|
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or
|
||||||
* 0 if the duration could not be determined.
|
* 0 if the duration could not be determined.
|
||||||
*/
|
*/
|
||||||
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer) {
|
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, String formatId) {
|
||||||
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
|
byte[] csdData = Arrays.copyOf(csdBuffer.data, csdBuffer.length);
|
||||||
|
|
||||||
int firstByte = csdData[4] & 0xFF;
|
int firstByte = csdData[4] & 0xFF;
|
||||||
@ -195,7 +199,7 @@ import java.util.Collections;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_MPEG2, null,
|
Format format = Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_MPEG2, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
|
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE,
|
||||||
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
|
Collections.singletonList(csdData), Format.NO_VALUE, pixelWidthHeightRatio, null);
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ import java.util.List;
|
|||||||
private long totalBytesWritten;
|
private long totalBytesWritten;
|
||||||
private final boolean[] prefixFlags;
|
private final boolean[] prefixFlags;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
private SeiReader seiReader;
|
private SeiReader seiReader;
|
||||||
private SampleReader sampleReader;
|
private SampleReader sampleReader;
|
||||||
@ -88,9 +89,13 @@ import java.util.List;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
|
sampleReader = new SampleReader(output, allowNonIdrKeyframes, detectAccessUnits);
|
||||||
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
|
idGenerator.generateNewId();
|
||||||
|
seiReader = new SeiReader(extractorOutput.track(idGenerator.getTrackId()),
|
||||||
|
idGenerator.getFormatId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -175,7 +180,7 @@ import java.util.List;
|
|||||||
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
initializationData.add(Arrays.copyOf(pps.nalData, pps.nalLength));
|
||||||
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
|
NalUnitUtil.SpsData spsData = NalUnitUtil.parseSpsNalUnit(sps.nalData, 3, sps.nalLength);
|
||||||
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
|
NalUnitUtil.PpsData ppsData = NalUnitUtil.parsePpsNalUnit(pps.nalData, 3, pps.nalLength);
|
||||||
output.format(Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
|
output.format(Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H264, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
|
Format.NO_VALUE, Format.NO_VALUE, spsData.width, spsData.height, Format.NO_VALUE,
|
||||||
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
|
initializationData, Format.NO_VALUE, spsData.pixelWidthAspectRatio, null));
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
|
@ -44,6 +44,7 @@ import java.util.Collections;
|
|||||||
private static final int PREFIX_SEI_NUT = 39;
|
private static final int PREFIX_SEI_NUT = 39;
|
||||||
private static final int SUFFIX_SEI_NUT = 40;
|
private static final int SUFFIX_SEI_NUT = 40;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
private SampleReader sampleReader;
|
private SampleReader sampleReader;
|
||||||
private SeiReader seiReader;
|
private SeiReader seiReader;
|
||||||
@ -90,9 +91,13 @@ import java.util.Collections;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
sampleReader = new SampleReader(output);
|
sampleReader = new SampleReader(output);
|
||||||
seiReader = new SeiReader(extractorOutput.track(idGenerator.getNextId()));
|
idGenerator.generateNewId();
|
||||||
|
seiReader = new SeiReader(extractorOutput.track(idGenerator.getTrackId()),
|
||||||
|
idGenerator.getFormatId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -183,7 +188,7 @@ import java.util.Collections;
|
|||||||
sps.endNalUnit(discardPadding);
|
sps.endNalUnit(discardPadding);
|
||||||
pps.endNalUnit(discardPadding);
|
pps.endNalUnit(discardPadding);
|
||||||
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
|
if (vps.isCompleted() && sps.isCompleted() && pps.isCompleted()) {
|
||||||
output.format(parseMediaFormat(vps, sps, pps));
|
output.format(parseMediaFormat(formatId, vps, sps, pps));
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,8 +210,8 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Format parseMediaFormat(NalUnitTargetBuffer vps, NalUnitTargetBuffer sps,
|
private static Format parseMediaFormat(String formatId, NalUnitTargetBuffer vps,
|
||||||
NalUnitTargetBuffer pps) {
|
NalUnitTargetBuffer sps, NalUnitTargetBuffer pps) {
|
||||||
// Build codec-specific data.
|
// Build codec-specific data.
|
||||||
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
|
byte[] csd = new byte[vps.nalLength + sps.nalLength + pps.nalLength];
|
||||||
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
|
System.arraycopy(vps.nalData, 0, csd, 0, vps.nalLength);
|
||||||
@ -311,7 +316,7 @@ import java.util.Collections;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
|
return Format.createVideoSampleFormat(formatId, MimeTypes.VIDEO_H265, null, Format.NO_VALUE,
|
||||||
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
|
Format.NO_VALUE, picWidthInLumaSamples, picHeightInLumaSamples, Format.NO_VALUE,
|
||||||
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
|
Collections.singletonList(csd), Format.NO_VALUE, pixelWidthHeightRatio, null);
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_ID3, null, Format.NO_VALUE,
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
null));
|
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_ID3,
|
||||||
|
null, Format.NO_VALUE, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -38,6 +38,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
private final MpegAudioHeader header;
|
private final MpegAudioHeader header;
|
||||||
private final String language;
|
private final String language;
|
||||||
|
|
||||||
|
private String formatId;
|
||||||
private TrackOutput output;
|
private TrackOutput output;
|
||||||
|
|
||||||
private int state;
|
private int state;
|
||||||
@ -76,7 +77,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
|
formatId = idGenerator.getFormatId();
|
||||||
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -176,9 +179,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
frameSize = header.frameSize;
|
frameSize = header.frameSize;
|
||||||
if (!hasOutputFormat) {
|
if (!hasOutputFormat) {
|
||||||
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
frameDurationUs = (C.MICROS_PER_SECOND * header.samplesPerFrame) / header.sampleRate;
|
||||||
Format format = Format.createAudioSampleFormat(null, header.mimeType, null, Format.NO_VALUE,
|
Format format = Format.createAudioSampleFormat(formatId, header.mimeType, null,
|
||||||
MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate, null, null, 0,
|
Format.NO_VALUE, MpegAudioHeader.MAX_FRAME_SIZE_BYTES, header.channels, header.sampleRate,
|
||||||
language);
|
null, null, 0, language);
|
||||||
output.format(format);
|
output.format(format);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,9 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
|
|
||||||
private final TrackOutput output;
|
private final TrackOutput output;
|
||||||
|
|
||||||
public SeiReader(TrackOutput output) {
|
public SeiReader(TrackOutput output, String formatId) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
output.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, null,
|
output.format(Format.createTextSampleFormat(formatId, MimeTypes.APPLICATION_CEA608, null,
|
||||||
Format.NO_VALUE, 0, null, null));
|
Format.NO_VALUE, 0, null, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,9 +36,10 @@ public final class SpliceInfoSectionReader implements SectionPayloadReader {
|
|||||||
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
|
public void init(TimestampAdjuster timestampAdjuster, ExtractorOutput extractorOutput,
|
||||||
TsPayloadReader.TrackIdGenerator idGenerator) {
|
TsPayloadReader.TrackIdGenerator idGenerator) {
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
this.timestampAdjuster = timestampAdjuster;
|
||||||
output = extractorOutput.track(idGenerator.getNextId());
|
idGenerator.generateNewId();
|
||||||
output.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_SCTE35, null,
|
output = extractorOutput.track(idGenerator.getTrackId());
|
||||||
Format.NO_VALUE, null));
|
output.format(Format.createSampleFormat(idGenerator.getFormatId(), MimeTypes.APPLICATION_SCTE35,
|
||||||
|
null, Format.NO_VALUE, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -34,7 +34,10 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
* Facilitates the extraction of data from the MPEG-2 TS container format.
|
||||||
@ -79,7 +82,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
|
private static final int BUFFER_SIZE = TS_PACKET_SIZE * BUFFER_PACKET_COUNT;
|
||||||
|
|
||||||
private final boolean hlsMode;
|
private final boolean hlsMode;
|
||||||
private final TimestampAdjuster timestampAdjuster;
|
private final List<TimestampAdjuster> timestampAdjusters;
|
||||||
private final ParsableByteArray tsPacketBuffer;
|
private final ParsableByteArray tsPacketBuffer;
|
||||||
private final ParsableBitArray tsScratch;
|
private final ParsableBitArray tsScratch;
|
||||||
private final SparseIntArray continuityCounters;
|
private final SparseIntArray continuityCounters;
|
||||||
@ -89,18 +92,12 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
// Accessed only by the loading thread.
|
// Accessed only by the loading thread.
|
||||||
private ExtractorOutput output;
|
private ExtractorOutput output;
|
||||||
|
private int remainingPmts;
|
||||||
private boolean tracksEnded;
|
private boolean tracksEnded;
|
||||||
private TsPayloadReader id3Reader;
|
private TsPayloadReader id3Reader;
|
||||||
|
|
||||||
public TsExtractor() {
|
public TsExtractor() {
|
||||||
this(new TimestampAdjuster(0));
|
this(new TimestampAdjuster(0), new DefaultTsPayloadReaderFactory(), false);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
|
||||||
*/
|
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster) {
|
|
||||||
this(timestampAdjuster, new DefaultTsPayloadReaderFactory(), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -111,7 +108,12 @@ public final class TsExtractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
public TsExtractor(TimestampAdjuster timestampAdjuster,
|
||||||
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) {
|
TsPayloadReader.Factory payloadReaderFactory, boolean hlsMode) {
|
||||||
this.timestampAdjuster = timestampAdjuster;
|
if (hlsMode) {
|
||||||
|
timestampAdjusters = Collections.singletonList(timestampAdjuster);
|
||||||
|
} else {
|
||||||
|
timestampAdjusters = new ArrayList<>();
|
||||||
|
timestampAdjusters.add(timestampAdjuster);
|
||||||
|
}
|
||||||
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
|
this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
|
||||||
this.hlsMode = hlsMode;
|
this.hlsMode = hlsMode;
|
||||||
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
||||||
@ -150,7 +152,10 @@ public final class TsExtractor implements Extractor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(long position, long timeUs) {
|
public void seek(long position, long timeUs) {
|
||||||
timestampAdjuster.reset();
|
int timestampAdjustersCount = timestampAdjusters.size();
|
||||||
|
for (int i = 0; i < timestampAdjustersCount; i++) {
|
||||||
|
timestampAdjusters.get(i).reset();
|
||||||
|
}
|
||||||
tsPacketBuffer.reset();
|
tsPacketBuffer.reset();
|
||||||
continuityCounters.clear();
|
continuityCounters.clear();
|
||||||
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
|
// Elementary stream readers' state should be cleared to get consistent behaviours when seeking.
|
||||||
@ -307,8 +312,12 @@ public final class TsExtractor implements Extractor {
|
|||||||
} else {
|
} else {
|
||||||
int pid = patScratch.readBits(13);
|
int pid = patScratch.readBits(13);
|
||||||
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
|
tsPayloadReaders.put(pid, new SectionReader(new PmtReader(pid)));
|
||||||
|
remainingPmts++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!hlsMode) {
|
||||||
|
tsPayloadReaders.remove(TS_PAT_PID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -345,10 +354,21 @@ public final class TsExtractor implements Extractor {
|
|||||||
// See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
|
// See ISO/IEC 13818-1, section 2.4.4.4 for more information on table id assignment.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12), program_number (16),
|
// TimestampAdjuster assignment.
|
||||||
// reserved (2), version_number (5), current_next_indicator (1), // section_number (8),
|
TimestampAdjuster timestampAdjuster;
|
||||||
|
if (hlsMode || remainingPmts == 1) {
|
||||||
|
timestampAdjuster = timestampAdjusters.get(0);
|
||||||
|
} else {
|
||||||
|
timestampAdjuster = new TimestampAdjuster(timestampAdjusters.get(0).firstSampleTimestampUs);
|
||||||
|
timestampAdjusters.add(timestampAdjuster);
|
||||||
|
}
|
||||||
|
|
||||||
|
// section_syntax_indicator(1), '0'(1), reserved(2), section_length(12)
|
||||||
|
sectionData.skipBytes(2);
|
||||||
|
int programNumber = sectionData.readUnsignedShort();
|
||||||
|
// reserved (2), version_number (5), current_next_indicator (1), section_number (8),
|
||||||
// last_section_number (8), reserved (3), PCR_PID (13)
|
// last_section_number (8), reserved (3), PCR_PID (13)
|
||||||
sectionData.skipBytes(9);
|
sectionData.skipBytes(5);
|
||||||
|
|
||||||
// Read program_info_length.
|
// Read program_info_length.
|
||||||
sectionData.readBytes(pmtScratch, 2);
|
sectionData.readBytes(pmtScratch, 2);
|
||||||
@ -364,7 +384,7 @@ public final class TsExtractor implements Extractor {
|
|||||||
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
|
EsInfo dummyEsInfo = new EsInfo(TS_STREAM_TYPE_ID3, null, new byte[0]);
|
||||||
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
|
id3Reader = payloadReaderFactory.createPayloadReader(TS_STREAM_TYPE_ID3, dummyEsInfo);
|
||||||
id3Reader.init(timestampAdjuster, output,
|
id3Reader.init(timestampAdjuster, output,
|
||||||
new TrackIdGenerator(TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
|
new TrackIdGenerator(programNumber, TS_STREAM_TYPE_ID3, MAX_PID_PLUS_ONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
int remainingEntriesLength = sectionData.bytesLeft();
|
int remainingEntriesLength = sectionData.bytesLeft();
|
||||||
@ -393,7 +413,8 @@ public final class TsExtractor implements Extractor {
|
|||||||
} else {
|
} else {
|
||||||
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
|
reader = payloadReaderFactory.createPayloadReader(streamType, esInfo);
|
||||||
if (reader != null) {
|
if (reader != null) {
|
||||||
reader.init(timestampAdjuster, output, new TrackIdGenerator(trackId, MAX_PID_PLUS_ONE));
|
reader.init(timestampAdjuster, output,
|
||||||
|
new TrackIdGenerator(programNumber, trackId, MAX_PID_PLUS_ONE));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,14 +425,18 @@ public final class TsExtractor implements Extractor {
|
|||||||
if (hlsMode) {
|
if (hlsMode) {
|
||||||
if (!tracksEnded) {
|
if (!tracksEnded) {
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
|
remainingPmts = 0;
|
||||||
|
tracksEnded = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tsPayloadReaders.remove(TS_PAT_PID);
|
|
||||||
tsPayloadReaders.remove(pid);
|
tsPayloadReaders.remove(pid);
|
||||||
|
remainingPmts--;
|
||||||
|
if (remainingPmts == 0) {
|
||||||
output.endTracks();
|
output.endTracks();
|
||||||
}
|
|
||||||
tracksEnded = true;
|
tracksEnded = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the stream info read from the available descriptors. Sets {@code data}'s position to
|
* Returns the stream info read from the available descriptors. Sets {@code data}'s position to
|
||||||
|
@ -81,17 +81,63 @@ public interface TsPayloadReader {
|
|||||||
*/
|
*/
|
||||||
final class TrackIdGenerator {
|
final class TrackIdGenerator {
|
||||||
|
|
||||||
private final int firstId;
|
private static final int ID_UNSET = Integer.MIN_VALUE;
|
||||||
private final int idIncrement;
|
|
||||||
private int generatedIdCount;
|
|
||||||
|
|
||||||
public TrackIdGenerator(int firstId, int idIncrement) {
|
private final String formatIdPrefix;
|
||||||
this.firstId = firstId;
|
private final int firstTrackId;
|
||||||
this.idIncrement = idIncrement;
|
private final int trackIdIncrement;
|
||||||
|
private int trackId;
|
||||||
|
private String formatId;
|
||||||
|
|
||||||
|
public TrackIdGenerator(int firstTrackId, int trackIdIncrement) {
|
||||||
|
this(ID_UNSET, firstTrackId, trackIdIncrement);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNextId() {
|
public TrackIdGenerator(int programNumber, int firstTrackId, int trackIdIncrement) {
|
||||||
return firstId + idIncrement * generatedIdCount++;
|
this.formatIdPrefix = programNumber != ID_UNSET ? programNumber + "/" : "";
|
||||||
|
this.firstTrackId = firstTrackId;
|
||||||
|
this.trackIdIncrement = trackIdIncrement;
|
||||||
|
trackId = ID_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a new set of track and track format ids. Must be called before {@code get*}
|
||||||
|
* methods.
|
||||||
|
*/
|
||||||
|
public void generateNewId() {
|
||||||
|
trackId = trackId == ID_UNSET ? firstTrackId : trackId + trackIdIncrement;
|
||||||
|
formatId = formatIdPrefix + trackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last generated track id. Must be called after the first {@link #generateNewId()}
|
||||||
|
* call.
|
||||||
|
*
|
||||||
|
* @return The last generated track id.
|
||||||
|
*/
|
||||||
|
public int getTrackId() {
|
||||||
|
maybeThrowUninitializedError();
|
||||||
|
return trackId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last generated format id, with the format {@code "programNumber/trackId"}. If no
|
||||||
|
* {@code programNumber} was provided, the {@code trackId} alone is used as format id. Must be
|
||||||
|
* called after the first {@link #generateNewId()} call.
|
||||||
|
*
|
||||||
|
* @return The last generated format id, with the format {@code "programNumber/trackId"}. If no
|
||||||
|
* {@code programNumber} was provided, the {@code trackId} alone is used as
|
||||||
|
* format id.
|
||||||
|
*/
|
||||||
|
public String getFormatId() {
|
||||||
|
maybeThrowUninitializedError();
|
||||||
|
return formatId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void maybeThrowUninitializedError() {
|
||||||
|
if (trackId == ID_UNSET) {
|
||||||
|
throw new IllegalStateException("generateNewId() must be called before retrieving ids.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ public final class TimestampAdjuster {
|
|||||||
*/
|
*/
|
||||||
private static final long MAX_PTS_PLUS_ONE = 0x200000000L;
|
private static final long MAX_PTS_PLUS_ONE = 0x200000000L;
|
||||||
|
|
||||||
private final long firstSampleTimestampUs;
|
public final long firstSampleTimestampUs;
|
||||||
|
|
||||||
private long timestampOffsetUs;
|
private long timestampOffsetUs;
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user