From 45c68a2fd55663addce79889c4a068b1f04983cf Mon Sep 17 00:00:00 2001 From: olly Date: Wed, 30 Nov 2016 08:46:49 -0800 Subject: [PATCH] Use separate mimeType for CEA-608 embedded in MP4 When CEA-608 is embedded in MP4 each packet consists of cc_data_1 and cc_data_2 only. The marker_bits, cc_valid and cc_type are implicit. As a result playback of CEA-608 embedded in MP4 broke when we started passing the extra byte for the TS case (and adjusted the decoder to assume the byte was present). This change introduces a special mimeType for the case where the byte is implicit (!). An alternative option was to insert the extra byte every 2 bytes in the MP4 extractor, but this is really quite fiddly to get right. Also made the loops in the 608/708 decoders robust against input of the wrong length. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=140609304 --- .../exoplayer2/extractor/mp4/AtomParsers.java | 3 ++- .../text/SubtitleDecoderFactory.java | 5 +++-- .../exoplayer2/text/cea/Cea608Decoder.java | 18 ++++++++++++------ .../android/exoplayer2/util/MimeTypes.java | 3 ++- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 4272cdaa11..9dc0578263 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -621,8 +621,9 @@ import java.util.List; MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData, 0 /* subsample timing is absolute */); } else if (childAtomType == Atom.TYPE_c608) { + // Defined by the QuickTime File Format specification. out.format = Format.createTextSampleFormat(Integer.toString(trackId), - MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, language, drmInitData); + MimeTypes.APPLICATION_MP4CEA608, null, Format.NO_VALUE, 0, language, drmInitData); out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT; } else if (childAtomType == Atom.TYPE_camm) { out.format = Format.createSampleFormat(Integer.toString(trackId), 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 d1e474d434..a5d1c0a9c0 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 @@ -75,8 +75,8 @@ public interface SubtitleDecoderFactory { throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); } if (clazz == Cea608Decoder.class) { - return clazz.asSubclass(SubtitleDecoder.class) - .getConstructor(Integer.TYPE).newInstance(format.accessibilityChannel); + return clazz.asSubclass(SubtitleDecoder.class).getConstructor(String.class, Integer.TYPE) + .newInstance(format.sampleMimeType, format.accessibilityChannel); } else { return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); } @@ -102,6 +102,7 @@ public interface SubtitleDecoderFactory { case MimeTypes.APPLICATION_TX3G: return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); case MimeTypes.APPLICATION_CEA608: + case MimeTypes.APPLICATION_MP4CEA608: return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder"); default: return null; 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 5a2fc77c6c..2a12679a0b 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 @@ -21,6 +21,7 @@ import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.SubtitleDecoder; import com.google.android.exoplayer2.text.SubtitleInputBuffer; +import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.ParsableByteArray; /** @@ -52,6 +53,10 @@ public final class Cea608Decoder extends CeaDecoder { // The default number of rows to display in roll-up captions mode. private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; + // An implied first byte for packets that are only 2 bytes long, consisting of marker bits + // (0b11111) + valid bit (0b1) + NTSC field 1 type bits (0b00). + private static final byte CC_IMPLICIT_DATA_HEADER = (byte) 0xFC; + /** * Command initiating pop-on style captioning. Subsequent data should be loaded into a * non-displayed memory and held there until the {@link #CTRL_END_OF_CAPTION} command is received, @@ -164,9 +169,8 @@ public final class Cea608Decoder extends CeaDecoder { }; private final ParsableByteArray ccData; - private final StringBuilder captionStringBuilder; - + private final int packetLength; private final int selectedField; private int captionMode; @@ -179,10 +183,11 @@ public final class Cea608Decoder extends CeaDecoder { private byte repeatableControlCc1; private byte repeatableControlCc2; - public Cea608Decoder(int accessibilityChannel) { + public Cea608Decoder(String mimeType, int accessibilityChannel) { ccData = new ParsableByteArray(); - captionStringBuilder = new StringBuilder(); + + packetLength = MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) ? 2 : 3; switch (accessibilityChannel) { case 3: case 4: @@ -238,8 +243,9 @@ public final class Cea608Decoder extends CeaDecoder { ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); boolean captionDataProcessed = false; boolean isRepeatableControl = false; - while (ccData.bytesLeft() > 0) { - byte ccDataHeader = (byte) ccData.readUnsignedByte(); + while (ccData.bytesLeft() >= packetLength) { + byte ccDataHeader = packetLength == 2 ? CC_IMPLICIT_DATA_HEADER + : (byte) ccData.readUnsignedByte(); byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); diff --git a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java index b690362fb7..aef55892a8 100644 --- a/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java +++ b/library/src/main/java/com/google/android/exoplayer2/util/MimeTypes.java @@ -70,7 +70,8 @@ public final class MimeTypes { public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; 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_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4-vtt"; + public static final String APPLICATION_MP4CEA608 = BASE_TYPE_APPLICATION + "/x-mp4-cea-608"; 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";