Add support for CEA608 in MP4 and fMP4

Issue: #1658
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=127312721
This commit is contained in:
aquilescanta 2016-07-13 06:53:34 -07:00 committed by Oliver Woodman
parent e4a3483d6f
commit a0c1595725
8 changed files with 73 additions and 50 deletions

View File

@ -117,6 +117,7 @@ import java.util.List;
public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g"); public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g");
public static final int TYPE_wvtt = Util.getIntegerCodeForString("wvtt"); public static final int TYPE_wvtt = Util.getIntegerCodeForString("wvtt");
public static final int TYPE_stpp = Util.getIntegerCodeForString("stpp"); public static final int TYPE_stpp = Util.getIntegerCodeForString("stpp");
public static final int TYPE_c608 = Util.getIntegerCodeForString("c608");
public static final int TYPE_samr = Util.getIntegerCodeForString("samr"); public static final int TYPE_samr = Util.getIntegerCodeForString("samr");
public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb"); public static final int TYPE_sawb = Util.getIntegerCodeForString("sawb");
public static final int TYPE_udta = Util.getIntegerCodeForString("udta"); public static final int TYPE_udta = Util.getIntegerCodeForString("udta");

View File

@ -44,6 +44,7 @@ import java.util.List;
private static final int TYPE_text = Util.getIntegerCodeForString("text"); private static final int TYPE_text = Util.getIntegerCodeForString("text");
private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl"); private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl");
private static final int TYPE_subt = Util.getIntegerCodeForString("subt"); private static final int TYPE_subt = Util.getIntegerCodeForString("subt");
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
/** /**
* Parses a trak atom (defined in 14496-12). * Parses a trak atom (defined in 14496-12).
@ -84,8 +85,8 @@ import java.util.List;
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts)); Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
return stsdData.format == null ? null return stsdData.format == null ? null
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs, : new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
stsdData.format, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength, stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes,
edtsData.first, edtsData.second); stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second);
} }
/** /**
@ -544,7 +545,8 @@ import java.util.List;
return C.TRACK_TYPE_AUDIO; return C.TRACK_TYPE_AUDIO;
} else if (trackType == TYPE_vide) { } else if (trackType == TYPE_vide) {
return C.TRACK_TYPE_VIDEO; return C.TRACK_TYPE_VIDEO;
} else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt) { } else if (trackType == TYPE_text || trackType == TYPE_sbtl || trackType == TYPE_subt
|| trackType == TYPE_clcp) {
return C.TRACK_TYPE_TEXT; return C.TRACK_TYPE_TEXT;
} else { } else {
return C.TRACK_TYPE_UNKNOWN; return C.TRACK_TYPE_UNKNOWN;
@ -621,6 +623,10 @@ import java.util.List;
out.format = Format.createTextSampleFormat(Integer.toString(trackId), out.format = Format.createTextSampleFormat(Integer.toString(trackId),
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) {
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
MimeTypes.APPLICATION_EIA608, null, Format.NO_VALUE, 0, language, drmInitData);
out.requiredSampleTransformation = Track.TRANSFORMATION_CEA608_CDAT;
} }
stsd.setPosition(childStartPosition + childAtomSize); stsd.setPosition(childStartPosition + childAtomSize);
} }
@ -1173,10 +1179,12 @@ import java.util.List;
public Format format; public Format format;
public int nalUnitLengthFieldLength; public int nalUnitLengthFieldLength;
public int requiredSampleTransformation;
public StsdData(int numberOfEntries) { public StsdData(int numberOfEntries) {
trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries]; trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
nalUnitLengthFieldLength = -1; nalUnitLengthFieldLength = -1;
requiredSampleTransformation = Track.TRANSFORMATION_NONE;
} }
} }

View File

@ -913,6 +913,10 @@ public final class FragmentedMp4Extractor implements Extractor {
} else { } else {
sampleBytesWritten = 0; sampleBytesWritten = 0;
} }
if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
sampleSize -= Atom.HEADER_SIZE;
input.skipFully(Atom.HEADER_SIZE);
}
parserState = STATE_READING_SAMPLE_CONTINUE; parserState = STATE_READING_SAMPLE_CONTINUE;
sampleCurrentNalBytesRemaining = 0; sampleCurrentNalBytesRemaining = 0;
} }

View File

@ -387,13 +387,19 @@ public final class Mp4Extractor implements Extractor, SeekMap {
TrackOutput trackOutput = track.trackOutput; TrackOutput trackOutput = track.trackOutput;
int sampleIndex = track.sampleIndex; int sampleIndex = track.sampleIndex;
long position = track.sampleTable.offsets[sampleIndex]; long position = track.sampleTable.offsets[sampleIndex];
int sampleSize = track.sampleTable.sizes[sampleIndex];
if (track.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
// The sample information is contained in a cdat atom. The header must be discarded for
// committing.
position += Atom.HEADER_SIZE;
sampleSize -= Atom.HEADER_SIZE;
}
long skipAmount = position - input.getPosition() + sampleBytesWritten; long skipAmount = position - input.getPosition() + sampleBytesWritten;
if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) { if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) {
positionHolder.position = position; positionHolder.position = position;
return RESULT_SEEK; return RESULT_SEEK;
} }
input.skipFully((int) skipAmount); input.skipFully((int) skipAmount);
int sampleSize = track.sampleTable.sizes[sampleIndex];
if (track.track.nalUnitLengthFieldLength != -1) { if (track.track.nalUnitLengthFieldLength != -1) {
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
// they're only 1 or 2 bytes long. // they're only 1 or 2 bytes long.

View File

@ -23,6 +23,15 @@ import com.google.android.exoplayer2.Format;
*/ */
public final class Track { public final class Track {
/**
* A no-op sample transformation.
*/
public static final int TRANSFORMATION_NONE = 0;
/**
* A transformation for caption samples in cdat atoms.
*/
public static final int TRANSFORMATION_CEA608_CDAT = 1;
/** /**
* The track identifier. * The track identifier.
*/ */
@ -53,6 +62,12 @@ public final class Track {
*/ */
public final Format format; public final Format format;
/**
* One of {@code TRANSFORMATION_*}. Defines the transformation to apply before outputting each
* sample.
*/
public final int sampleTransformation;
/** /**
* Track encryption boxes for the different track sample descriptions. Entries may be null. * Track encryption boxes for the different track sample descriptions. Entries may be null.
*/ */
@ -75,14 +90,16 @@ public final class Track {
public final int nalUnitLengthFieldLength; public final int nalUnitLengthFieldLength;
public Track(int id, int type, long timescale, long movieTimescale, long durationUs, public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
Format format, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, Format format, int sampleTransformation,
int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) { TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
long[] editListDurations, long[] editListMediaTimes) {
this.id = id; this.id = id;
this.type = type; this.type = type;
this.timescale = timescale; this.timescale = timescale;
this.movieTimescale = movieTimescale; this.movieTimescale = movieTimescale;
this.durationUs = durationUs; this.durationUs = durationUs;
this.format = format; this.format = format;
this.sampleTransformation = sampleTransformation;
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes; this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength; this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
this.editListDurations = editListDurations; this.editListDurations = editListDurations;

View File

@ -52,8 +52,26 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
} while (b == 0xFF); } while (b == 0xFF);
// Process the payload. // Process the payload.
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) { if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
output.sampleData(seiBuffer, payloadSize); // Ignore country_code (1) + provider_code (2) + user_identifier (4)
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, payloadSize, 0, null); // + user_data_type_code (1).
seiBuffer.skipBytes(8);
// Ignore first three bits: reserved (1) + process_cc_data_flag (1) + zero_bit (1).
int ccCount = seiBuffer.readUnsignedByte() & 0x1F;
seiBuffer.skipBytes(1);
int sampleBytes = 0;
for (int i = 0; i < ccCount; i++) {
int ccValidityAndType = seiBuffer.readUnsignedByte() & 0x07;
// Check that validity == 1 and type == 0.
if (ccValidityAndType != 0x04) {
seiBuffer.skipBytes(2);
} else {
sampleBytes += 2;
output.sampleData(seiBuffer, 2);
}
}
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleBytes, 0, null);
// Ignore trailing information in SEI, if any.
seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3));
} else { } else {
seiBuffer.skipBytes(payloadSize); seiBuffer.skipBytes(payloadSize);
} }

View File

@ -90,8 +90,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
for (int j = 0; j < formats.length; j++) { for (int j = 0; j < formats.length; j++) {
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1; int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1;
Track track = new Track(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US, Track track = new Track(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US,
manifest.durationUs, formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength, manifest.durationUs, formats[j], Track.TRANSFORMATION_NONE, trackEncryptionBoxes,
null, null); nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track); | FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);

View File

@ -21,12 +21,10 @@ import com.google.android.exoplayer2.text.SubtitleInputBuffer;
import com.google.android.exoplayer2.text.SubtitleOutputBuffer; import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
import com.google.android.exoplayer2.text.SubtitleParser; import com.google.android.exoplayer2.text.SubtitleParser;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import android.text.TextUtils; import android.text.TextUtils;
import java.nio.ByteBuffer;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.TreeSet; import java.util.TreeSet;
@ -168,7 +166,7 @@ public final class Eia608Parser implements SubtitleParser {
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers; private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers; private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
private final ParsableBitArray seiBuffer; private final ParsableByteArray ccData;
private final StringBuilder captionStringBuilder; private final StringBuilder captionStringBuilder;
@ -197,7 +195,7 @@ public final class Eia608Parser implements SubtitleParser {
} }
queuedInputBuffers = new TreeSet<>(); queuedInputBuffers = new TreeSet<>();
seiBuffer = new ParsableBitArray(); ccData = new ParsableByteArray();
captionStringBuilder = new StringBuilder(); captionStringBuilder = new StringBuilder();
@ -310,58 +308,30 @@ public final class Eia608Parser implements SubtitleParser {
} }
private void decode(SubtitleInputBuffer inputBuffer) { private void decode(SubtitleInputBuffer inputBuffer) {
ByteBuffer inputData = inputBuffer.data; ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
int inputSize = inputData.limit();
if (inputSize < 10) {
return;
}
seiBuffer.reset(inputData.array(), inputSize);
// country_code (8) + provider_code (16) + user_identifier (32) + user_data_type_code (8) +
// reserved (1) + process_cc_data_flag (1) + zero_bit (1)
seiBuffer.skipBits(67);
int ccCount = seiBuffer.readBits(5);
seiBuffer.skipBits(8);
boolean captionDataProcessed = false; boolean captionDataProcessed = false;
boolean isRepeatableControl = false; boolean isRepeatableControl = false;
for (int i = 0; i < ccCount; i++) { while (ccData.bytesLeft() > 0) {
seiBuffer.skipBits(5); // one_bit + reserved byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
boolean ccValid = seiBuffer.readBit(); byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
if (!ccValid) {
seiBuffer.skipBits(18);
continue;
}
int ccType = seiBuffer.readBits(2);
if (ccType != 0) {
seiBuffer.skipBits(16);
continue;
}
seiBuffer.skipBits(1);
byte ccData1 = (byte) seiBuffer.readBits(7);
seiBuffer.skipBits(1);
byte ccData2 = (byte) seiBuffer.readBits(7);
// Ignore empty captions. // Ignore empty captions.
if (ccData1 == 0 && ccData2 == 0) { if (ccData1 == 0 && ccData2 == 0) {
continue; continue;
} }
// If we've reached this point then there is data to process; flag that work has been done. // If we've reached this point then there is data to process; flag that work has been done.
captionDataProcessed = true; captionDataProcessed = true;
// Special North American character set. // Special North American character set.
// ccData2 - P|0|1|1|X|X|X|X // ccData2 - P|0|1|1|X|X|X|X
if ((ccData1 == 0x11 || ccData1 == 0x19) if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
&& ((ccData2 & 0x70) == 0x30)) {
captionStringBuilder.append(getSpecialChar(ccData2)); captionStringBuilder.append(getSpecialChar(ccData2));
continue; continue;
} }
// Extended Spanish/Miscellaneous and French character set. // Extended Spanish/Miscellaneous and French character set.
// ccData2 - P|0|1|X|X|X|X|X // ccData2 - P|0|1|X|X|X|X|X
if ((ccData1 == 0x12 || ccData1 == 0x1A) if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) {
&& ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedEsFrChar(ccData2)); captionStringBuilder.append(getExtendedEsFrChar(ccData2));
continue; continue;
@ -369,8 +339,7 @@ public final class Eia608Parser implements SubtitleParser {
// Extended Portuguese and German/Danish character set. // Extended Portuguese and German/Danish character set.
// ccData2 - P|0|1|X|X|X|X|X // ccData2 - P|0|1|X|X|X|X|X
if ((ccData1 == 0x13 || ccData1 == 0x1B) if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) {
&& ((ccData2 & 0x60) == 0x20)) {
backspace(); // Remove standard equivalent of the special extended char. backspace(); // Remove standard equivalent of the special extended char.
captionStringBuilder.append(getExtendedPtDeChar(ccData2)); captionStringBuilder.append(getExtendedPtDeChar(ccData2));
continue; continue;