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
This commit is contained in:
olly 2016-11-30 08:46:49 -08:00 committed by Oliver Woodman
parent f702568776
commit 45c68a2fd5
4 changed files with 19 additions and 10 deletions

View File

@ -621,8 +621,9 @@ import java.util.List;
MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData, MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData,
0 /* subsample timing is absolute */); 0 /* subsample timing is absolute */);
} else if (childAtomType == Atom.TYPE_c608) { } else if (childAtomType == Atom.TYPE_c608) {
// Defined by the QuickTime File Format specification.
out.format = Format.createTextSampleFormat(Integer.toString(trackId), 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; out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
} else if (childAtomType == Atom.TYPE_camm) { } else if (childAtomType == Atom.TYPE_camm) {
out.format = Format.createSampleFormat(Integer.toString(trackId), out.format = Format.createSampleFormat(Integer.toString(trackId),

View File

@ -75,8 +75,8 @@ public interface SubtitleDecoderFactory {
throw new IllegalArgumentException("Attempted to create decoder for unsupported format"); throw new IllegalArgumentException("Attempted to create decoder for unsupported format");
} }
if (clazz == Cea608Decoder.class) { if (clazz == Cea608Decoder.class) {
return clazz.asSubclass(SubtitleDecoder.class) return clazz.asSubclass(SubtitleDecoder.class).getConstructor(String.class, Integer.TYPE)
.getConstructor(Integer.TYPE).newInstance(format.accessibilityChannel); .newInstance(format.sampleMimeType, format.accessibilityChannel);
} else { } else {
return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance(); return clazz.asSubclass(SubtitleDecoder.class).getConstructor().newInstance();
} }
@ -102,6 +102,7 @@ public interface SubtitleDecoderFactory {
case MimeTypes.APPLICATION_TX3G: case MimeTypes.APPLICATION_TX3G:
return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder"); return Class.forName("com.google.android.exoplayer2.text.tx3g.Tx3gDecoder");
case MimeTypes.APPLICATION_CEA608: case MimeTypes.APPLICATION_CEA608:
case MimeTypes.APPLICATION_MP4CEA608:
return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder"); return Class.forName("com.google.android.exoplayer2.text.cea.Cea608Decoder");
default: default:
return null; return null;

View File

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.Subtitle; import com.google.android.exoplayer2.text.Subtitle;
import com.google.android.exoplayer2.text.SubtitleDecoder; import com.google.android.exoplayer2.text.SubtitleDecoder;
import com.google.android.exoplayer2.text.SubtitleInputBuffer; import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; 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. // The default number of rows to display in roll-up captions mode.
private static final int DEFAULT_CAPTIONS_ROW_COUNT = 4; 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 * 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, * 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 ParsableByteArray ccData;
private final StringBuilder captionStringBuilder; private final StringBuilder captionStringBuilder;
private final int packetLength;
private final int selectedField; private final int selectedField;
private int captionMode; private int captionMode;
@ -179,10 +183,11 @@ public final class Cea608Decoder extends CeaDecoder {
private byte repeatableControlCc1; private byte repeatableControlCc1;
private byte repeatableControlCc2; private byte repeatableControlCc2;
public Cea608Decoder(int accessibilityChannel) { public Cea608Decoder(String mimeType, int accessibilityChannel) {
ccData = new ParsableByteArray(); ccData = new ParsableByteArray();
captionStringBuilder = new StringBuilder(); captionStringBuilder = new StringBuilder();
packetLength = MimeTypes.APPLICATION_MP4CEA608.equals(mimeType) ? 2 : 3;
switch (accessibilityChannel) { switch (accessibilityChannel) {
case 3: case 3:
case 4: case 4:
@ -238,8 +243,9 @@ public final class Cea608Decoder extends CeaDecoder {
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit()); ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
boolean captionDataProcessed = false; boolean captionDataProcessed = false;
boolean isRepeatableControl = false; boolean isRepeatableControl = false;
while (ccData.bytesLeft() > 0) { while (ccData.bytesLeft() >= packetLength) {
byte ccDataHeader = (byte) ccData.readUnsignedByte(); byte ccDataHeader = packetLength == 2 ? CC_IMPLICIT_DATA_HEADER
: (byte) ccData.readUnsignedByte();
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F); byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);

View File

@ -70,7 +70,8 @@ public final class MimeTypes {
public static final String APPLICATION_TTML = BASE_TYPE_APPLICATION + "/ttml+xml"; 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_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g"; 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_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc";
public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub"; public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub";
public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs"; public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs";