Commit 1e2ed51f by andrewlewis Committed by kim-vde

Add support for H.263 and MPEG-4 Part 2 in TS

The new reader is named H263Reader as it handles H.263 streams, but
MPEG-4 Part 2 streams are also intended to be handled. The reader's
output format MIME type is video/mp4v as the H.263 streams can be
decoded by decoders supporting this MIME type.

The implementation is based on the framework implementation for
extracting MPEG-4 video in MPEG-TS
(https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/libstagefright/mpeg2ts/ESQueue.cpp;l=1825;drc=86e363c1fac27302ca4ae33e73296f7797672995)
and is similar to the existing H262Reader.

Issue: #1603
Issue: #5107
PiperOrigin-RevId: 320565337
parent a8f1cdcf
...@@ -212,6 +212,9 @@ ...@@ -212,6 +212,9 @@
headers mime type in `DefaultExtractorsFactory`. headers mime type in `DefaultExtractorsFactory`.
* Add support for partially fragmented MP4s * Add support for partially fragmented MP4s
([#7308](https://github.com/google/ExoPlayer/issues/7308)). ([#7308](https://github.com/google/ExoPlayer/issues/7308)).
* Add support for MPEG-4 Part 2 and H.263 in MPEG-TS
([#1603](https://github.com/google/ExoPlayer/issues/1603),
[#5107](https://github.com/google/ExoPlayer/issues/5107)).
* Testing * Testing
* Add `TestExoPlayer`, a utility class with APIs to create * Add `TestExoPlayer`, a utility class with APIs to create
`SimpleExoPlayer` instances with fake components for testing. `SimpleExoPlayer` instances with fake components for testing.
......
...@@ -165,6 +165,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -165,6 +165,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new PesReader(new DtsReader(esInfo.language)); return new PesReader(new DtsReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_H262: case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader(buildUserDataReader(esInfo))); return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H263:
return new PesReader(new H263Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H264: case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM) ? null return isSet(FLAG_IGNORE_H264_STREAM) ? null
: new PesReader(new H264Reader(buildSeiReader(esInfo), : new PesReader(new H264Reader(buildSeiReader(esInfo),
......
...@@ -15,6 +15,9 @@ ...@@ -15,6 +15,9 @@
*/ */
package com.google.android.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor.ts;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkStateNotNull;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -22,7 +25,6 @@ import com.google.android.exoplayer2.Format; ...@@ -22,7 +25,6 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
...@@ -118,7 +120,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -118,7 +120,7 @@ public final class H262Reader implements ElementaryStreamReader {
@Override @Override
public void consume(ParsableByteArray data) { public void consume(ParsableByteArray data) {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called. checkStateNotNull(output); // Asserts that createTracks has been called.
int offset = data.getPosition(); int offset = data.getPosition();
int limit = data.limit(); int limit = data.limit();
byte[] dataArray = data.data; byte[] dataArray = data.data;
...@@ -156,7 +158,7 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -156,7 +158,7 @@ public final class H262Reader implements ElementaryStreamReader {
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, formatId); Pair<Format, Long> result = parseCsdBuffer(csdBuffer, checkNotNull(formatId));
output.format(result.first); output.format(result.first);
frameDurationUs = result.second; frameDurationUs = result.second;
hasOutputFormat = true; hasOutputFormat = true;
...@@ -215,11 +217,11 @@ public final class H262Reader implements ElementaryStreamReader { ...@@ -215,11 +217,11 @@ public final class H262Reader implements ElementaryStreamReader {
* 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. * @param formatId The id for the generated format.
* @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if * @return A pair consisting of the {@link Format} and the frame duration in microseconds, or 0 if
* the duration could not be determined. * the duration could not be determined.
*/ */
private static Pair<Format, Long> parseCsdBuffer(CsdBuffer csdBuffer, @Nullable String formatId) { 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;
......
...@@ -90,6 +90,7 @@ public final class TsExtractor implements Extractor { ...@@ -90,6 +90,7 @@ public final class TsExtractor implements Extractor {
public static final int TS_STREAM_TYPE_E_AC3 = 0x87; public static final int TS_STREAM_TYPE_E_AC3 = 0x87;
public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor public static final int TS_STREAM_TYPE_AC4 = 0xAC; // DVB/ATSC AC-4 Descriptor
public static final int TS_STREAM_TYPE_H262 = 0x02; public static final int TS_STREAM_TYPE_H262 = 0x02;
public static final int TS_STREAM_TYPE_H263 = 0x10; // MPEG-4 Part 2 and H.263
public static final int TS_STREAM_TYPE_H264 = 0x1B; public static final int TS_STREAM_TYPE_H264 = 0x1B;
public static final int TS_STREAM_TYPE_H265 = 0x24; public static final int TS_STREAM_TYPE_H265 = 0x24;
public static final int TS_STREAM_TYPE_ID3 = 0x15; public static final int TS_STREAM_TYPE_ID3 = 0x15;
......
...@@ -61,6 +61,11 @@ public final class TsExtractorTest { ...@@ -61,6 +61,11 @@ public final class TsExtractorTest {
} }
@Test @Test
public void sampleWithH263() throws Exception {
ExtractorAsserts.assertBehavior(TsExtractor::new, "ts/sample_h263.ts", simulationConfig);
}
@Test
public void sampleWithH264AndMpegAudio() throws Exception { public void sampleWithH264AndMpegAudio() throws Exception {
ExtractorAsserts.assertBehavior( ExtractorAsserts.assertBehavior(
TsExtractor::new, "ts/sample_h264_mpeg_audio.ts", simulationConfig); TsExtractor::new, "ts/sample_h264_mpeg_audio.ts", simulationConfig);
......
No preview for this file type
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 39002
sample count = 24
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 0
flags = 1
data = length 8408, hash 718A7985
sample 1:
time = 40000
flags = 0
data = length 2018, hash 7BC8193F
sample 2:
time = 80000
flags = 0
data = length 480, hash C244FFAF
sample 3:
time = 120000
flags = 0
data = length 256, hash 56D68D82
sample 4:
time = 160000
flags = 0
data = length 222, hash FADF6CA9
sample 5:
time = 200000
flags = 0
data = length 217, hash 161BB856
sample 6:
time = 240000
flags = 0
data = length 212, hash 835B0727
sample 7:
time = 280000
flags = 0
data = length 212, hash E9AF0AB7
sample 8:
time = 320000
flags = 0
data = length 212, hash E9517D06
sample 9:
time = 360000
flags = 0
data = length 212, hash 4FA58096
sample 10:
time = 400000
flags = 0
data = length 212, hash 4F47F2E5
sample 11:
time = 440000
flags = 0
data = length 212, hash B59BF675
sample 12:
time = 480000
flags = 1
data = length 11769, hash 3ED9DF06
sample 13:
time = 520000
flags = 0
data = length 230, hash 2AF3505D
sample 14:
time = 560000
flags = 0
data = length 222, hash F4E7436D
sample 15:
time = 600000
flags = 0
data = length 222, hash F0F812FD
sample 16:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 17:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 18:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 19:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 20:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 21:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 22:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 23:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 27354
sample count = 18
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 320000
flags = 0
data = length 212, hash 835B0727
sample 1:
time = 360000
flags = 0
data = length 212, hash E9AF0AB7
sample 2:
time = 400000
flags = 0
data = length 212, hash E9517D06
sample 3:
time = 440000
flags = 0
data = length 212, hash 4FA58096
sample 4:
time = 480000
flags = 0
data = length 212, hash 4F47F2E5
sample 5:
time = 520000
flags = 0
data = length 212, hash B59BF675
sample 6:
time = 560000
flags = 1
data = length 11769, hash 3ED9DF06
sample 7:
time = 600000
flags = 0
data = length 230, hash 2AF3505D
sample 8:
time = 640000
flags = 0
data = length 222, hash F4E7436D
sample 9:
time = 680000
flags = 0
data = length 222, hash F0F812FD
sample 10:
time = 720000
flags = 0
data = length 222, hash 18472E8C
sample 11:
time = 760000
flags = 0
data = length 222, hash 1457FE1C
sample 12:
time = 800000
flags = 0
data = length 222, hash 3BA719AB
sample 13:
time = 840000
flags = 0
data = length 222, hash 37B7E93B
sample 14:
time = 880000
flags = 0
data = length 222, hash 5F0704CA
sample 15:
time = 920000
flags = 0
data = length 222, hash 5B17D45A
sample 16:
time = 960000
flags = 0
data = length 222, hash 8266EFE9
sample 17:
time = 1000000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 13592
sample count = 8
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 1:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 2:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 3:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 4:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 5:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 6:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 7:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true
seekMap:
isSeekable = true
duration = 960000
getPosition(0) = [[timeUs=0, position=0]]
getPosition(1) = [[timeUs=1, position=0]]
getPosition(480000) = [[timeUs=480000, position=21958]]
getPosition(960000) = [[timeUs=960000, position=44104]]
numberOfTracks = 2
track 256:
total output bytes = 0
sample count = 0
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = [[timeUs=0, position=0]]
numberOfTracks = 2
track 256:
total output bytes = 39002
sample count = 24
format 0:
id = 1/256
sampleMimeType = video/mp4v-es
width = 640
height = 360
initializationData:
data = length 47, hash 7696BF67
sample 0:
time = 0
flags = 1
data = length 8408, hash 718A7985
sample 1:
time = 40000
flags = 0
data = length 2018, hash 7BC8193F
sample 2:
time = 80000
flags = 0
data = length 480, hash C244FFAF
sample 3:
time = 120000
flags = 0
data = length 256, hash 56D68D82
sample 4:
time = 160000
flags = 0
data = length 222, hash FADF6CA9
sample 5:
time = 200000
flags = 0
data = length 217, hash 161BB856
sample 6:
time = 240000
flags = 0
data = length 212, hash 835B0727
sample 7:
time = 280000
flags = 0
data = length 212, hash E9AF0AB7
sample 8:
time = 320000
flags = 0
data = length 212, hash E9517D06
sample 9:
time = 360000
flags = 0
data = length 212, hash 4FA58096
sample 10:
time = 400000
flags = 0
data = length 212, hash 4F47F2E5
sample 11:
time = 440000
flags = 0
data = length 212, hash B59BF675
sample 12:
time = 480000
flags = 1
data = length 11769, hash 3ED9DF06
sample 13:
time = 520000
flags = 0
data = length 230, hash 2AF3505D
sample 14:
time = 560000
flags = 0
data = length 222, hash F4E7436D
sample 15:
time = 600000
flags = 0
data = length 222, hash F0F812FD
sample 16:
time = 640000
flags = 0
data = length 222, hash 18472E8C
sample 17:
time = 680000
flags = 0
data = length 222, hash 1457FE1C
sample 18:
time = 720000
flags = 0
data = length 222, hash 3BA719AB
sample 19:
time = 760000
flags = 0
data = length 222, hash 37B7E93B
sample 20:
time = 800000
flags = 0
data = length 222, hash 5F0704CA
sample 21:
time = 840000
flags = 0
data = length 222, hash 5B17D45A
sample 22:
time = 880000
flags = 0
data = length 222, hash 8266EFE9
sample 23:
time = 920000
flags = 0
data = length 222, hash 7E77BF79
track 8448:
total output bytes = 0
sample count = 0
format 0:
id = 1/8448
sampleMimeType = application/cea-608
tracksEnded = true
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment