Added support for RawCC (i.e. out-of-band EIA-608)

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=128397367
This commit is contained in:
cdrolle 2016-07-25 14:08:33 -07:00 committed by Oliver Woodman
parent 75e2aee92e
commit 1d79c26b34
8 changed files with 592 additions and 12 deletions

Binary file not shown.

View File

@ -0,0 +1,329 @@
seekMap:
isSeekable = false
duration = UNSET TIME
getPosition(0) = 0
numberOfTracks = 1
track 0:
format:
bitrate = -1
id = null
containerMimeType = null
sampleMimeType = application/eia-608
maxInputSize = -1
width = -1
height = -1
frameRate = -1.0
rotationDegrees = -1
pixelWidthHeightRatio = -1.0
channelCount = -1
sampleRate = -1
pcmEncoding = -1
encoderDelay = -1
encoderPadding = -1
subsampleOffsetUs = 9223372036854775807
selectionFlags = 0
language = null
drmInitData = -
initializationData:
sample count = 75
sample 0:
time = 37657512133
flags = 1
data = length 2, hash FFFFF3C1
sample 1:
time = 37657545511
flags = 1
data = length 2, hash FFFFF6CD
sample 2:
time = 37657578866
flags = 1
data = length 2, hash FFFFF6DC
sample 3:
time = 37657612244
flags = 1
data = length 2, hash FFFFF65B
sample 4:
time = 37657645600
flags = 1
data = length 2, hash FFFFF6CD
sample 5:
time = 37657678977
flags = 1
data = length 2, hash FFFFF67B
sample 6:
time = 37657712333
flags = 1
data = length 2, hash 2B5
sample 7:
time = 37657745711
flags = 1
data = length 2, hash F5
sample 8:
time = 37657779066
flags = 1
data = length 2, hash FFFFF87A
sample 9:
time = 37657812444
flags = 1
data = length 2, hash FFFFF698
sample 10:
time = 37657845800
flags = 1
data = length 2, hash 1F4
sample 11:
time = 37657879177
flags = 1
data = length 2, hash 803
sample 12:
time = 37657912533
flags = 1
data = length 2, hash 1F8
sample 13:
time = 37657945911
flags = 1
data = length 2, hash 117A
sample 14:
time = 37657979266
flags = 1
data = length 2, hash 166
sample 15:
time = 37658012644
flags = 1
data = length 2, hash 105A
sample 16:
time = 37658046000
flags = 1
data = length 2, hash FCF
sample 17:
time = 37658079377
flags = 1
data = length 2, hash 1253
sample 18:
time = 37658112733
flags = 1
data = length 2, hash 11DA
sample 19:
time = 37658146111
flags = 1
data = length 2, hash 795
sample 20:
time = 37658179466
flags = 1
data = length 2, hash 103E
sample 21:
time = 37658212844
flags = 1
data = length 2, hash 120F
sample 22:
time = 37658246200
flags = 1
data = length 2, hash FFFFF698
sample 23:
time = 37658279577
flags = 1
data = length 2, hash 1F4
sample 24:
time = 37658312933
flags = 1
data = length 2, hash FFFFF71B
sample 25:
time = 37658346311
flags = 1
data = length 2, hash F91
sample 26:
time = 37658379666
flags = 1
data = length 2, hash 166
sample 27:
time = 37658413044
flags = 1
data = length 2, hash 1023
sample 28:
time = 37658446400
flags = 1
data = length 2, hash 117A
sample 29:
time = 37658479777
flags = 1
data = length 2, hash 784
sample 30:
time = 37658513133
flags = 1
data = length 2, hash 1F8
sample 31:
time = 37658546511
flags = 1
data = length 2, hash 10D9
sample 32:
time = 37658579866
flags = 1
data = length 2, hash 935
sample 33:
time = 37658613244
flags = 1
data = length 2, hash 2B5
sample 34:
time = 37658646600
flags = 1
data = length 2, hash F5
sample 35:
time = 37658679977
flags = 1
data = length 2, hash FFFFF87A
sample 36:
time = 37658713333
flags = 1
data = length 2, hash FFFFF698
sample 37:
time = 37658746711
flags = 1
data = length 2, hash 1F4
sample 38:
time = 37658780066
flags = 1
data = length 2, hash 793
sample 39:
time = 37658813444
flags = 1
data = length 2, hash FF0
sample 40:
time = 37658846800
flags = 1
data = length 2, hash 16B
sample 41:
time = 37658880177
flags = 1
data = length 2, hash 2C0
sample 42:
time = 37658913533
flags = 1
data = length 2, hash FFFFF953
sample 43:
time = 37658946911
flags = 1
data = length 2, hash FFFFF3C1
sample 44:
time = 37658980266
flags = 1
data = length 2, hash FFFFF3C1
sample 45:
time = 37659013644
flags = 1
data = length 2, hash FFFFF3C1
sample 46:
time = 37659047000
flags = 1
data = length 2, hash FFFFF3C1
sample 47:
time = 37659080377
flags = 1
data = length 2, hash FFFFF3C1
sample 48:
time = 37659113733
flags = 1
data = length 2, hash FFFFF3C1
sample 49:
time = 37659147111
flags = 1
data = length 2, hash FFFFF3C1
sample 50:
time = 37659180466
flags = 1
data = length 2, hash FFFFF3C1
sample 51:
time = 37659213844
flags = 1
data = length 2, hash FFFFF3C1
sample 52:
time = 37659247200
flags = 1
data = length 2, hash FFFFF3C1
sample 53:
time = 37659280577
flags = 1
data = length 2, hash FFFFF3C1
sample 54:
time = 37659313933
flags = 1
data = length 2, hash FFFFF3C1
sample 55:
time = 37659347311
flags = 1
data = length 2, hash FFFFF3C1
sample 56:
time = 37659380666
flags = 1
data = length 2, hash FFFFF3C1
sample 57:
time = 37659414044
flags = 1
data = length 2, hash FFFFF3C1
sample 58:
time = 37659447400
flags = 1
data = length 2, hash FFFFF3C1
sample 59:
time = 37659480777
flags = 1
data = length 2, hash FFFFF3C1
sample 60:
time = 37659514133
flags = 1
data = length 2, hash FFFFF3C1
sample 61:
time = 37659547511
flags = 1
data = length 2, hash FFFFF3C1
sample 62:
time = 37659580866
flags = 1
data = length 2, hash FFFFF3C1
sample 63:
time = 37659614244
flags = 1
data = length 2, hash FFFFF6CD
sample 64:
time = 37659647600
flags = 1
data = length 2, hash FFFFF6DC
sample 65:
time = 37659680977
flags = 1
data = length 2, hash FFFFF65B
sample 66:
time = 37659714333
flags = 1
data = length 2, hash FFFFF6CD
sample 67:
time = 37659747711
flags = 1
data = length 2, hash FFFFF6FF
sample 68:
time = 37659781066
flags = 1
data = length 2, hash FFFFF6AC
sample 69:
time = 37659814444
flags = 1
data = length 2, hash FFFFF5FE
sample 70:
time = 37659847800
flags = 1
data = length 2, hash FFFFFEF7
sample 71:
time = 37659881177
flags = 1
data = length 2, hash 120C
sample 72:
time = 37659914533
flags = 1
data = length 2, hash 1124
sample 73:
time = 37659947911
flags = 1
data = length 2, hash 1A9
sample 74:
time = 37659981266
flags = 1
data = length 2, hash 935
tracksEnded = true

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2016 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.exoplayer2.extractor.rawcc;
import android.annotation.TargetApi;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.testutil.TestUtil;
/**
* Tests for {@link RawCcExtractor}.
*/
@TargetApi(16)
public final class RawCcExtractorTest extends InstrumentationTestCase {
public void testRawCcSample() throws Exception {
TestUtil.assertOutput(new TestUtil.ExtractorFactory() {
@Override
public Extractor create() {
return new RawCcExtractor();
}
}, "rawcc/sample.rawcc", getInstrumentation());
}
}

View File

@ -0,0 +1,172 @@
/*
* Copyright (C) 2016 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.exoplayer2.extractor.rawcc;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* Extracts EIA-608 data from a RawCC file
*/
public final class RawCcExtractor implements Extractor {
private static final int SCRATCH_SIZE = 9;
private static final int HEADER_SIZE = 8;
private static final int HEADER_ID = Util.getIntegerCodeForString("RCC\u0001");
private static final int TIMESTAMP_SIZE_V0 = 4;
private static final int TIMESTAMP_SIZE_V1 = 8;
// Parser states.
private static final int STATE_READING_HEADER = 0;
private static final int STATE_READING_TIMESTAMP_AND_COUNT = 1;
private static final int STATE_READING_SAMPLES = 2;
private final ParsableByteArray dataScratch;
private ExtractorOutput extractorOutput;
private TrackOutput trackOutput;
private int parserState;
private int version;
private long timestampUs;
private int remainingSampleCount;
private int sampleBytesWritten;
public RawCcExtractor() {
dataScratch = new ParsableByteArray(SCRATCH_SIZE);
parserState = STATE_READING_HEADER;
}
@Override
public void init(ExtractorOutput output) {
this.extractorOutput = output;
extractorOutput.seekMap(new SeekMap.Unseekable(C.UNSET_TIME_US));
trackOutput = extractorOutput.track(0);
extractorOutput.endTracks();
trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_EIA608,
null, Format.NO_VALUE, 0, null, null));
}
@Override
public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
dataScratch.reset();
input.peekFully(dataScratch.data, 0, HEADER_SIZE);
return dataScratch.readInt() == HEADER_ID;
}
@Override
public int read(ExtractorInput input, PositionHolder seekPosition)
throws IOException, InterruptedException {
while (true) {
switch (parserState) {
case STATE_READING_HEADER:
parseHeader(input);
parserState = STATE_READING_TIMESTAMP_AND_COUNT;
break;
case STATE_READING_TIMESTAMP_AND_COUNT:
if (parseTimestampAndSampleCount(input)) {
parserState = STATE_READING_SAMPLES;
} else {
parserState = STATE_READING_HEADER;
return RESULT_END_OF_INPUT;
}
break;
case STATE_READING_SAMPLES:
parseSamples(input);
parserState = STATE_READING_TIMESTAMP_AND_COUNT;
return RESULT_CONTINUE;
default:
throw new IllegalStateException();
}
}
}
@Override
public void seek(long position) {
parserState = STATE_READING_HEADER;
}
@Override
public void release() {
// Do nothing
}
private void parseHeader(ExtractorInput input) throws IOException, InterruptedException {
dataScratch.reset();
input.readFully(dataScratch.data, 0, HEADER_SIZE);
if (dataScratch.readInt() != HEADER_ID) {
throw new IOException("Input not RawCC");
}
version = dataScratch.readUnsignedByte();
// no versions use the flag fields yet
}
private boolean parseTimestampAndSampleCount(ExtractorInput input) throws IOException,
InterruptedException {
dataScratch.reset();
if (version == 0) {
if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V0 + 1, true)) {
return false;
}
// version 0 timestamps are 45kHz, so we need to convert them into us
timestampUs = dataScratch.readUnsignedInt() * 1000 / 45;
} else if (version == 1) {
if (!input.readFully(dataScratch.data, 0, TIMESTAMP_SIZE_V1 + 1, true)) {
return false;
}
timestampUs = dataScratch.readLong();
} else {
throw new ParserException("Unsupported version number: " + version);
}
remainingSampleCount = dataScratch.readUnsignedByte();
sampleBytesWritten = 0;
return true;
}
private void parseSamples(ExtractorInput input) throws IOException, InterruptedException {
for (; remainingSampleCount > 0; remainingSampleCount--) {
dataScratch.reset();
input.readFully(dataScratch.data, 0, 3);
// only accept EIA-608 packets which have validity (6th bit) == 1 and
// type (7-8th bits) == 0; i.e. ccDataPkt[0] == 0bXXXXX100
int ccValidityAndType = dataScratch.readUnsignedByte() & 0x07;
if (ccValidityAndType == 0x04) {
trackOutput.sampleData(dataScratch, 2);
sampleBytesWritten += 2;
}
}
if (sampleBytesWritten > 0) {
trackOutput.sampleMetadata(timestampUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytesWritten, 0, null);
}
}
}

View File

@ -50,10 +50,12 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
private final Extractor extractor;
private final Format manifestFormat;
private final boolean preferManifestDrmInitData;
private final boolean resendFormatOnInit;
private boolean extractorInitialized;
private SingleTrackMetadataOutput metadataOutput;
private TrackOutput trackOutput;
private Format sentFormat;
// Accessed only on the loader thread.
private boolean seenTrack;
@ -67,9 +69,24 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
*/
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat,
boolean preferManifestDrmInitData) {
this(extractor, manifestFormat, preferManifestDrmInitData, true);
}
/**
* @param extractor The extractor to wrap.
* @param manifestFormat A manifest defined {@link Format} whose data should be merged into any
* sample {@link Format} output from the {@link Extractor}.
* @param preferManifestDrmInitData Whether {@link DrmInitData} defined in {@code manifestFormat}
* should be preferred when the sample and manifest {@link Format}s are merged.
* @param resendFormatOnInit Whether the extractor should resend the previous {@link Format} when
* it is initialized via {@link #init(SingleTrackMetadataOutput, TrackOutput)}.
*/
public ChunkExtractorWrapper(Extractor extractor, Format manifestFormat,
boolean preferManifestDrmInitData, boolean resendFormatOnInit) {
this.extractor = extractor;
this.manifestFormat = manifestFormat;
this.preferManifestDrmInitData = preferManifestDrmInitData;
this.resendFormatOnInit = resendFormatOnInit;
}
/**
@ -87,6 +104,9 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
extractorInitialized = true;
} else {
extractor.seek(0);
if (resendFormatOnInit && sentFormat != null) {
trackOutput.format(sentFormat);
}
}
}
@ -127,8 +147,8 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput
@Override
public void format(Format format) {
trackOutput.format(format.copyWithManifestFormatInfo(manifestFormat,
preferManifestDrmInitData));
sentFormat = format.copyWithManifestFormatInfo(manifestFormat, preferManifestDrmInitData);
trackOutput.format(sentFormat);
}
@Override

View File

@ -15,12 +15,15 @@
*/
package com.google.android.exoplayer2.source.dash;
import android.os.SystemClock;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ChunkIndex;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.mkv.MatroskaExtractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.rawcc.RawCcExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.source.chunk.ChunkExtractorWrapper;
@ -41,9 +44,6 @@ import com.google.android.exoplayer2.upstream.HttpDataSource.InvalidResponseCode
import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import android.os.SystemClock;
import java.io.IOException;
import java.util.List;
@ -384,12 +384,25 @@ public class DefaultDashChunkSource implements DashChunkSource {
this.periodDurationUs = periodDurationUs;
this.representation = representation;
String containerMimeType = representation.format.containerMimeType;
if (mimeTypeIsRawText(containerMimeType)) {
extractorWrapper = null;
} else {
boolean resendFormatOnInit = false;
Extractor extractor;
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
extractor = new RawCcExtractor();
resendFormatOnInit = true;
} else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new MatroskaExtractor();
} else {
extractor = new FragmentedMp4Extractor();
}
// Prefer drmInitData obtained from the manifest over drmInitData obtained from the stream,
// as per DASH IF Interoperability Recommendations V3.0, 7.5.3.
extractorWrapper = mimeTypeIsRawText(containerMimeType) ? null : new ChunkExtractorWrapper(
mimeTypeIsWebm(containerMimeType) ? new MatroskaExtractor()
: new FragmentedMp4Extractor(),
representation.format, true /* preferManifestDrmInitData */);
extractorWrapper = new ChunkExtractorWrapper(extractor,
representation.format, true /* preferManifestDrmInitData */,
resendFormatOnInit);
}
segmentIndex = representation.getIndex();
}

View File

@ -299,7 +299,8 @@ public class DashManifestParser extends DefaultHandler
return C.TRACK_TYPE_VIDEO;
} else if (MimeTypes.isAudio(sampleMimeType)) {
return C.TRACK_TYPE_AUDIO;
} else if (mimeTypeIsRawText(sampleMimeType)) {
} else if (mimeTypeIsRawText(sampleMimeType)
|| MimeTypes.APPLICATION_RAWCC.equals(format.containerMimeType)) {
return C.TRACK_TYPE_TEXT;
}
return C.TRACK_TYPE_UNKNOWN;
@ -650,6 +651,12 @@ public class DashManifestParser extends DefaultHandler
return MimeTypes.getAudioMediaMimeType(codecs);
} else if (MimeTypes.isVideo(containerMimeType)) {
return MimeTypes.getVideoMediaMimeType(codecs);
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
// We currently only support EIA-608 through RawCC
if (codecs != null && codecs.contains("eia608")) {
return MimeTypes.APPLICATION_EIA608;
}
return null;
} else if (mimeTypeIsRawText(containerMimeType)) {
return containerMimeType;
} else if (MimeTypes.APPLICATION_MP4.equals(containerMimeType)) {

View File

@ -66,6 +66,7 @@ public final class MimeTypes {
public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g";
public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4vtt";
public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc";
public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub";
public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs";