diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java index 1eb38de40f..c8c1b4ed93 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/FormatTest.java @@ -64,8 +64,8 @@ public final class FormatTest extends TestCase { Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, - C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, - drmInitData, metadata); + C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, Format.OFFSET_SAMPLE_RELATIVE, + INIT_DATA, drmInitData, metadata); Parcel parcel = Parcel.obtain(); formatToParcel.writeToParcel(parcel, 0); diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java index 2f4da01228..4e99e2745e 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractorTest.java @@ -17,8 +17,10 @@ package com.google.android.exoplayer2.extractor.rawcc; import android.annotation.TargetApi; import android.test.InstrumentationTestCase; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.testutil.TestUtil; +import com.google.android.exoplayer2.util.MimeTypes; /** * Tests for {@link RawCcExtractor}. @@ -27,12 +29,15 @@ import com.google.android.exoplayer2.testutil.TestUtil; 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()); + TestUtil.assertOutput( + new TestUtil.ExtractorFactory() { + @Override + public Extractor create() { + return new RawCcExtractor( + Format.createTextContainerFormat(null, null, MimeTypes.APPLICATION_CEA608, + "cea608", Format.NO_VALUE, 0, null, 1)); + } + }, "rawcc/sample.rawcc", getInstrumentation()); } } diff --git a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java index 460d3bb04e..944781b890 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParserTest.java @@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.dash.manifest; import android.net.Uri; import android.test.InstrumentationTestCase; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.testutil.TestUtil; import java.io.IOException; @@ -68,4 +69,35 @@ public class DashManifestParserTest extends InstrumentationTestCase { } } + public void testParseCea608AccessibilityChannel() { + assertEquals(1, DashManifestParser.parseCea608AccessibilityChannel("CC1=eng")); + assertEquals(2, DashManifestParser.parseCea608AccessibilityChannel("CC2=eng")); + assertEquals(3, DashManifestParser.parseCea608AccessibilityChannel("CC3=eng")); + assertEquals(4, DashManifestParser.parseCea608AccessibilityChannel("CC4=eng")); + + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel(null)); + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("")); + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC0=eng")); + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea608AccessibilityChannel("CC5=eng")); + assertEquals(Format.NO_VALUE, + DashManifestParser.parseCea608AccessibilityChannel("Wrong format")); + } + + public void testParseCea708AccessibilityChannel() { + assertEquals(1, DashManifestParser.parseCea708AccessibilityChannel("1=lang:eng")); + assertEquals(2, DashManifestParser.parseCea708AccessibilityChannel("2=lang:eng")); + assertEquals(3, DashManifestParser.parseCea708AccessibilityChannel("3=lang:eng")); + assertEquals(62, DashManifestParser.parseCea708AccessibilityChannel("62=lang:eng")); + assertEquals(63, DashManifestParser.parseCea708AccessibilityChannel("63=lang:eng")); + + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel(null)); + assertEquals(Format.NO_VALUE, DashManifestParser.parseCea708AccessibilityChannel("")); + assertEquals(Format.NO_VALUE, + DashManifestParser.parseCea708AccessibilityChannel("0=lang:eng")); + assertEquals(Format.NO_VALUE, + DashManifestParser.parseCea708AccessibilityChannel("64=lang:eng")); + assertEquals(Format.NO_VALUE, + DashManifestParser.parseCea708AccessibilityChannel("Wrong format")); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer2/Format.java b/library/src/main/java/com/google/android/exoplayer2/Format.java index 9528536296..14efb6a2c7 100644 --- a/library/src/main/java/com/google/android/exoplayer2/Format.java +++ b/library/src/main/java/com/google/android/exoplayer2/Format.java @@ -178,6 +178,11 @@ public final class Format implements Parcelable { */ public final String language; + /** + * The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. + */ + public final int accessibilityChannel; + // Lazily initialized hashcode and framework media format. private int hashCode; @@ -190,7 +195,8 @@ public final class Format implements Parcelable { float frameRate, List initializationData) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData, null, null); + NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, null, + null); } public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, @@ -215,8 +221,8 @@ public final class Format implements Parcelable { byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, - NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, initializationData, - drmInitData, null); + NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, drmInitData, null); } // Audio. @@ -226,8 +232,8 @@ public final class Format implements Parcelable { List initializationData, @C.SelectionFlags int selectionFlags, String language) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, initializationData, - null, null); + NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, + initializationData, null, null); } public static Format createAudioSampleFormat(String id, String sampleMimeType, String codecs, @@ -254,7 +260,7 @@ public final class Format implements Parcelable { @C.SelectionFlags int selectionFlags, String language, Metadata metadata) { return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, - encoderDelay, encoderPadding, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, + encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, metadata); } @@ -263,23 +269,46 @@ public final class Format implements Parcelable { public static Format createTextContainerFormat(String id, String containerMimeType, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language) { + return createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, bitrate, + selectionFlags, language, NO_VALUE); + } + + public static Format createTextContainerFormat(String id, String containerMimeType, + String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, + String language, int accessibilityChannel) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, selectionFlags, language, OFFSET_SAMPLE_RELATIVE, null, null, null); + NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, + OFFSET_SAMPLE_RELATIVE, null, null, null); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData) { return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, - drmInitData, OFFSET_SAMPLE_RELATIVE); + NO_VALUE, drmInitData, OFFSET_SAMPLE_RELATIVE); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, DrmInitData drmInitData) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + accessibilityChannel, drmInitData, OFFSET_SAMPLE_RELATIVE); } public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String language, DrmInitData drmInitData, long subsampleOffsetUs) { + return createTextSampleFormat(id, sampleMimeType, codecs, bitrate, selectionFlags, language, + NO_VALUE, drmInitData, subsampleOffsetUs); + } + + public static Format createTextSampleFormat(String id, String sampleMimeType, String codecs, + int bitrate, @C.SelectionFlags int selectionFlags, String language, + int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, selectionFlags, language, subsampleOffsetUs, null, drmInitData, null); + NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, null, + drmInitData, null); } // Image. @@ -288,7 +317,8 @@ public final class Format implements Parcelable { int bitrate, List initializationData, String language, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, 0, language, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null); + NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, + null); } // Generic. @@ -297,14 +327,14 @@ public final class Format implements Parcelable { String sampleMimeType, int bitrate) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, null, null, null); + NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, null); } public static Format createSampleFormat(String id, String sampleMimeType, String codecs, int bitrate, DrmInitData drmInitData) { return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, - NO_VALUE, 0, null, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); + NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); } /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, @@ -312,8 +342,8 @@ public final class Format implements Parcelable { float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language, - long subsampleOffsetUs, List initializationData, DrmInitData drmInitData, - Metadata metadata) { + int accessibilityChannel, long subsampleOffsetUs, List initializationData, + DrmInitData drmInitData, Metadata metadata) { this.id = id; this.containerMimeType = containerMimeType; this.sampleMimeType = sampleMimeType; @@ -334,6 +364,7 @@ public final class Format implements Parcelable { this.encoderPadding = encoderPadding; this.selectionFlags = selectionFlags; this.language = language; + this.accessibilityChannel = accessibilityChannel; this.subsampleOffsetUs = subsampleOffsetUs; this.initializationData = initializationData == null ? Collections.emptyList() : initializationData; @@ -364,6 +395,7 @@ public final class Format implements Parcelable { encoderPadding = in.readInt(); selectionFlags = in.readInt(); language = in.readString(); + accessibilityChannel = in.readInt(); subsampleOffsetUs = in.readLong(); int initializationDataSize = in.readInt(); initializationData = new ArrayList<>(initializationDataSize); @@ -378,14 +410,16 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, @@ -393,7 +427,8 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithManifestFormatInfo(Format manifestFormat, @@ -409,28 +444,32 @@ public final class Format implements Parcelable { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, - language, subsampleOffsetUs, initializationData, drmInitData, metadata); + language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, + metadata); } public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithDrmInitData(DrmInitData drmInitData) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } public Format copyWithMetadata(Metadata metadata) { return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, - selectionFlags, language, subsampleOffsetUs, initializationData, drmInitData, metadata); + selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, + drmInitData, metadata); } /** @@ -489,6 +528,7 @@ public final class Format implements Parcelable { result = 31 * result + channelCount; result = 31 * result + sampleRate; result = 31 * result + (language == null ? 0 : language.hashCode()); + result = 31 * result + accessibilityChannel; result = 31 * result + (drmInitData == null ? 0 : drmInitData.hashCode()); result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); hashCode = result; @@ -514,6 +554,7 @@ public final class Format implements Parcelable { || encoderPadding != other.encoderPadding || subsampleOffsetUs != other.subsampleOffsetUs || selectionFlags != other.selectionFlags || !Util.areEqual(id, other.id) || !Util.areEqual(language, other.language) + || accessibilityChannel != other.accessibilityChannel || !Util.areEqual(containerMimeType, other.containerMimeType) || !Util.areEqual(sampleMimeType, other.sampleMimeType) || !Util.areEqual(codecs, other.codecs) @@ -584,6 +625,7 @@ public final class Format implements Parcelable { dest.writeInt(encoderPadding); dest.writeInt(selectionFlags); dest.writeString(language); + dest.writeInt(accessibilityChannel); dest.writeLong(subsampleOffsetUs); int initializationDataSize = initializationData.size(); dest.writeInt(initializationDataSize); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java index 452d09e132..8405f21756 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -24,7 +24,6 @@ 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; @@ -45,6 +44,8 @@ public final class RawCcExtractor implements Extractor { private static final int STATE_READING_TIMESTAMP_AND_COUNT = 1; private static final int STATE_READING_SAMPLES = 2; + private final Format format; + private final ParsableByteArray dataScratch; private TrackOutput trackOutput; @@ -55,7 +56,8 @@ public final class RawCcExtractor implements Extractor { private int remainingSampleCount; private int sampleBytesWritten; - public RawCcExtractor() { + public RawCcExtractor(Format format) { + this.format = format; dataScratch = new ParsableByteArray(SCRATCH_SIZE); parserState = STATE_READING_HEADER; } @@ -65,9 +67,7 @@ public final class RawCcExtractor implements Extractor { output.seekMap(new SeekMap.Unseekable(C.TIME_UNSET)); trackOutput = output.track(0); output.endTracks(); - - trackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, - null, Format.NO_VALUE, 0, null, null)); + trackOutput.format(format); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java index 83b8724170..919a0231ea 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/DefaultDashChunkSource.java @@ -349,7 +349,7 @@ public class DefaultDashChunkSource implements DashChunkSource { boolean resendFormatOnInit = false; Extractor extractor; if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { - extractor = new RawCcExtractor(); + extractor = new RawCcExtractor(representation.format); resendFormatOnInit = true; } else if (mimeTypeIsWebm(containerMimeType)) { extractor = new MatroskaExtractor(); diff --git a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java index fdfe8cb4b3..18e81e53b2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/dash/manifest/DashManifestParser.java @@ -59,6 +59,10 @@ public class DashManifestParser extends DefaultHandler private static final Pattern FRAME_RATE_PATTERN = Pattern.compile("(\\d+)(?:/(\\d+))?"); + private static final Pattern CEA_608_ACCESSIBILITY_PATTERN = Pattern.compile("CC([1-4])=.*"); + private static final Pattern CEA_708_ACCESSIBILITY_PATTERN = + Pattern.compile("([1-9]|[1-5][0-9]|6[0-3])=.*"); + private final String contentId; private final XmlPullParserFactory xmlParserFactory; @@ -235,6 +239,7 @@ public class DashManifestParser extends DefaultHandler int audioChannels = Format.NO_VALUE; int audioSamplingRate = parseInt(xpp, "audioSamplingRate", Format.NO_VALUE); String language = xpp.getAttributeValue(null, "lang"); + int accessibilityChannel = Format.NO_VALUE; ArrayList drmSchemeDatas = new ArrayList<>(); List representationInfos = new ArrayList<>(); @@ -256,12 +261,15 @@ public class DashManifestParser extends DefaultHandler contentType = checkContentTypeConsistency(contentType, parseContentType(xpp)); } else if (XmlPullParserUtil.isStartTag(xpp, "Representation")) { RepresentationInfo representationInfo = parseRepresentation(xpp, baseUrl, mimeType, codecs, - width, height, frameRate, audioChannels, audioSamplingRate, language, segmentBase); + width, height, frameRate, audioChannels, audioSamplingRate, language, + accessibilityChannel, segmentBase); contentType = checkContentTypeConsistency(contentType, getContentType(representationInfo.format)); representationInfos.add(representationInfo); } else if (XmlPullParserUtil.isStartTag(xpp, "AudioChannelConfiguration")) { audioChannels = parseAudioChannelConfiguration(xpp); + } else if (XmlPullParserUtil.isStartTag(xpp, "Accessibility")) { + accessibilityChannel = parseAccessibilityValue(xpp); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentBase")) { segmentBase = parseSegmentBase(xpp, (SingleSegmentBase) segmentBase); } else if (XmlPullParserUtil.isStartTag(xpp, "SegmentList")) { @@ -365,7 +373,8 @@ public class DashManifestParser extends DefaultHandler protected RepresentationInfo parseRepresentation(XmlPullParser xpp, String baseUrl, String adaptationSetMimeType, String adaptationSetCodecs, int adaptationSetWidth, int adaptationSetHeight, float adaptationSetFrameRate, int adaptationSetAudioChannels, - int adaptationSetAudioSamplingRate, String adaptationSetLanguage, SegmentBase segmentBase) + int adaptationSetAudioSamplingRate, String adaptationSetLanguage, + int adaptationSetAccessibilityChannel, SegmentBase segmentBase) throws XmlPullParserException, IOException { String id = xpp.getAttributeValue(null, "id"); int bandwidth = parseInt(xpp, "bandwidth", Format.NO_VALUE); @@ -404,7 +413,8 @@ public class DashManifestParser extends DefaultHandler } while (!XmlPullParserUtil.isEndTag(xpp, "Representation")); Format format = buildFormat(id, mimeType, width, height, frameRate, audioChannels, - audioSamplingRate, bandwidth, adaptationSetLanguage, codecs); + audioSamplingRate, bandwidth, adaptationSetLanguage, adaptationSetAccessibilityChannel, + codecs); segmentBase = segmentBase != null ? segmentBase : new SingleSegmentBase(baseUrl); return new RepresentationInfo(format, baseUrl, segmentBase, drmSchemeDatas); @@ -412,7 +422,7 @@ public class DashManifestParser extends DefaultHandler protected Format buildFormat(String id, String containerMimeType, int width, int height, float frameRate, int audioChannels, int audioSamplingRate, int bitrate, String language, - String codecs) { + int accessiblityChannel, String codecs) { String sampleMimeType = getSampleMimeType(containerMimeType, codecs); if (sampleMimeType != null) { if (MimeTypes.isVideo(sampleMimeType)) { @@ -423,7 +433,10 @@ public class DashManifestParser extends DefaultHandler bitrate, audioChannels, audioSamplingRate, null, 0, language); } else if (mimeTypeIsRawText(sampleMimeType)) { return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, - bitrate, 0, language); + bitrate, 0, language, accessiblityChannel); + } else if (containerMimeType.equals(MimeTypes.APPLICATION_RAWCC)) { + return Format.createTextContainerFormat(id, containerMimeType, sampleMimeType, codecs, + bitrate, 0, language, accessiblityChannel); } else { return Format.createContainerFormat(id, containerMimeType, codecs, sampleMimeType, bitrate); } @@ -726,6 +739,54 @@ public class DashManifestParser extends DefaultHandler } } + private static int parseAccessibilityValue(XmlPullParser xpp) + throws IOException, XmlPullParserException { + String schemeIdUri = parseString(xpp, "schemeIdUri", null); + String valueString = parseString(xpp, "value", null); + int accessibilityValue; + if (schemeIdUri == null || valueString == null) { + accessibilityValue = Format.NO_VALUE; + } else if ("urn:scte:dash:cc:cea-608:2015".equals(schemeIdUri)) { + accessibilityValue = parseCea608AccessibilityChannel(valueString); + } else if ("urn:scte:dash:cc:cea-708:2015".equals(schemeIdUri)) { + accessibilityValue = parseCea708AccessibilityChannel(valueString); + } else { + accessibilityValue = Format.NO_VALUE; + } + do { + xpp.next(); + } while (!XmlPullParserUtil.isEndTag(xpp, "Accessibility")); + return accessibilityValue; + } + + static int parseCea608AccessibilityChannel(String accessibilityValueString) { + if (accessibilityValueString == null) { + return Format.NO_VALUE; + } + Matcher accessibilityValueMatcher = + CEA_608_ACCESSIBILITY_PATTERN.matcher(accessibilityValueString); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse channel number from " + accessibilityValueString); + return Format.NO_VALUE; + } + } + + static int parseCea708AccessibilityChannel(String accessibilityValueString) { + if (accessibilityValueString == null) { + return Format.NO_VALUE; + } + Matcher accessibilityValueMatcher = + CEA_708_ACCESSIBILITY_PATTERN.matcher(accessibilityValueString); + if (accessibilityValueMatcher.matches()) { + return Integer.parseInt(accessibilityValueMatcher.group(1)); + } else { + Log.w(TAG, "Unable to parse service block number from " + accessibilityValueString); + return Format.NO_VALUE; + } + } + protected static float parseFrameRate(XmlPullParser xpp, float defaultValue) { float frameRate = defaultValue; String frameRateAttribute = xpp.getAttributeValue(null, "frameRate"); diff --git a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java b/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java index d1afbe86a8..d1e474d434 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java @@ -74,7 +74,12 @@ public interface SubtitleDecoderFactory { if (clazz == null) { throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); } - return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); + if (clazz == Cea608Decoder.class) { + return clazz.asSubclass(SubtitleDecoder.class) + .getConstructor(Integer.TYPE).newInstance(format.accessibilityChannel); + } else { + return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); + } } catch (Exception e) { throw new IllegalStateException("Unexpected error instantiating decoder", e); } diff --git a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java index 5ff68c2781..c33d2abb89 100644 --- a/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java +++ b/library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.text.cea; import android.text.TextUtils; +import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoder; @@ -27,9 +28,15 @@ import com.google.android.exoplayer2.util.ParsableByteArray; */ public final class Cea608Decoder extends CeaDecoder { - private static final int NTSC_CC_FIELD_1 = 0x00; - private static final int CC_TYPE_MASK = 0x03; + private static final String TAG = "Cea608Decoder"; + private static final int CC_VALID_FLAG = 0x04; + private static final int CC_TYPE_FLAG = 0x02; + private static final int CC_FIELD_FLAG = 0x01; + + private static final int NTSC_CC_FIELD_1 = 0x00; + private static final int NTSC_CC_FIELD_2 = 0x01; + private static final int CC_VALID_608_ID = 0x04; private static final int PAYLOAD_TYPE_CC = 4; private static final int COUNTRY_CODE = 0xB5; @@ -160,6 +167,8 @@ public final class Cea608Decoder extends CeaDecoder { private final StringBuilder captionStringBuilder; + private final int selectedField; + private int captionMode; private int captionRowCount; private String captionString; @@ -170,10 +179,21 @@ public final class Cea608Decoder extends CeaDecoder { private byte repeatableControlCc1; private byte repeatableControlCc2; - public Cea608Decoder() { + public Cea608Decoder(int accessibilityChannel) { ccData = new ParsableByteArray(); captionStringBuilder = new StringBuilder(); + switch (accessibilityChannel) { + case 3: + case 4: + selectedField = 2; + break; + case 1: + case 2: + case Format.NO_VALUE: + default: + selectedField = 1; + } setCaptionMode(CC_MODE_UNKNOWN); captionRowCount = DEFAULT_CAPTIONS_ROW_COUNT; @@ -219,14 +239,18 @@ public final class Cea608Decoder extends CeaDecoder { boolean captionDataProcessed = false; boolean isRepeatableControl = false; while (ccData.bytesLeft() > 0) { - byte ccTypeAndValid = (byte) (ccData.readUnsignedByte() & 0x07); + byte ccDataHeader = (byte) ccData.readUnsignedByte(); byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); - // Only examine valid NTSC_CC_FIELD_1 packets - if ((ccTypeAndValid & CC_VALID_FLAG) == 0 - || (ccTypeAndValid & CC_TYPE_MASK) != NTSC_CC_FIELD_1) { - // TODO: Add support for NTSC_CC_FIELD_2 packets + // Only examine valid CEA-608 packets + if ((ccDataHeader & (CC_VALID_FLAG | CC_TYPE_FLAG)) != CC_VALID_608_ID) { + continue; + } + + // Only examine packets within the selected field + if ((selectedField == 1 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_1) + || (selectedField == 2 && (ccDataHeader & CC_FIELD_FLAG) != NTSC_CC_FIELD_2)) { continue; }