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:
parent
e4a3483d6f
commit
a0c1595725
@ -117,6 +117,7 @@ import java.util.List;
|
||||
public static final int TYPE_tx3g = Util.getIntegerCodeForString("tx3g");
|
||||
public static final int TYPE_wvtt = Util.getIntegerCodeForString("wvtt");
|
||||
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_sawb = Util.getIntegerCodeForString("sawb");
|
||||
public static final int TYPE_udta = Util.getIntegerCodeForString("udta");
|
||||
|
@ -44,6 +44,7 @@ import java.util.List;
|
||||
private static final int TYPE_text = Util.getIntegerCodeForString("text");
|
||||
private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl");
|
||||
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).
|
||||
@ -84,8 +85,8 @@ import java.util.List;
|
||||
Pair<long[], long[]> edtsData = parseEdts(trak.getContainerAtomOfType(Atom.TYPE_edts));
|
||||
return stsdData.format == null ? null
|
||||
: new Track(tkhdData.id, trackType, mdhdData.first, movieTimescale, durationUs,
|
||||
stsdData.format, stsdData.trackEncryptionBoxes, stsdData.nalUnitLengthFieldLength,
|
||||
edtsData.first, edtsData.second);
|
||||
stsdData.format, stsdData.requiredSampleTransformation, stsdData.trackEncryptionBoxes,
|
||||
stsdData.nalUnitLengthFieldLength, edtsData.first, edtsData.second);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -544,7 +545,8 @@ import java.util.List;
|
||||
return C.TRACK_TYPE_AUDIO;
|
||||
} else if (trackType == TYPE_vide) {
|
||||
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;
|
||||
} else {
|
||||
return C.TRACK_TYPE_UNKNOWN;
|
||||
@ -621,6 +623,10 @@ import java.util.List;
|
||||
out.format = Format.createTextSampleFormat(Integer.toString(trackId),
|
||||
MimeTypes.APPLICATION_TTML, null, Format.NO_VALUE, 0, language, drmInitData,
|
||||
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);
|
||||
}
|
||||
@ -1173,10 +1179,12 @@ import java.util.List;
|
||||
|
||||
public Format format;
|
||||
public int nalUnitLengthFieldLength;
|
||||
public int requiredSampleTransformation;
|
||||
|
||||
public StsdData(int numberOfEntries) {
|
||||
trackEncryptionBoxes = new TrackEncryptionBox[numberOfEntries];
|
||||
nalUnitLengthFieldLength = -1;
|
||||
requiredSampleTransformation = Track.TRANSFORMATION_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -913,6 +913,10 @@ public final class FragmentedMp4Extractor implements Extractor {
|
||||
} else {
|
||||
sampleBytesWritten = 0;
|
||||
}
|
||||
if (currentTrackBundle.track.sampleTransformation == Track.TRANSFORMATION_CEA608_CDAT) {
|
||||
sampleSize -= Atom.HEADER_SIZE;
|
||||
input.skipFully(Atom.HEADER_SIZE);
|
||||
}
|
||||
parserState = STATE_READING_SAMPLE_CONTINUE;
|
||||
sampleCurrentNalBytesRemaining = 0;
|
||||
}
|
||||
|
@ -387,13 +387,19 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
TrackOutput trackOutput = track.trackOutput;
|
||||
int sampleIndex = track.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;
|
||||
if (skipAmount < 0 || skipAmount >= RELOAD_MINIMUM_SEEK_DISTANCE) {
|
||||
positionHolder.position = position;
|
||||
return RESULT_SEEK;
|
||||
}
|
||||
input.skipFully((int) skipAmount);
|
||||
int sampleSize = track.sampleTable.sizes[sampleIndex];
|
||||
if (track.track.nalUnitLengthFieldLength != -1) {
|
||||
// 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.
|
||||
|
@ -23,6 +23,15 @@ import com.google.android.exoplayer2.Format;
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -53,6 +62,12 @@ public final class Track {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@ -75,14 +90,16 @@ public final class Track {
|
||||
public final int nalUnitLengthFieldLength;
|
||||
|
||||
public Track(int id, int type, long timescale, long movieTimescale, long durationUs,
|
||||
Format format, TrackEncryptionBox[] sampleDescriptionEncryptionBoxes,
|
||||
int nalUnitLengthFieldLength, long[] editListDurations, long[] editListMediaTimes) {
|
||||
Format format, int sampleTransformation,
|
||||
TrackEncryptionBox[] sampleDescriptionEncryptionBoxes, int nalUnitLengthFieldLength,
|
||||
long[] editListDurations, long[] editListMediaTimes) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.timescale = timescale;
|
||||
this.movieTimescale = movieTimescale;
|
||||
this.durationUs = durationUs;
|
||||
this.format = format;
|
||||
this.sampleTransformation = sampleTransformation;
|
||||
this.sampleDescriptionEncryptionBoxes = sampleDescriptionEncryptionBoxes;
|
||||
this.nalUnitLengthFieldLength = nalUnitLengthFieldLength;
|
||||
this.editListDurations = editListDurations;
|
||||
|
@ -52,8 +52,26 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
} while (b == 0xFF);
|
||||
// Process the payload.
|
||||
if (Eia608Parser.isSeiMessageEia608(payloadType, payloadSize, seiBuffer)) {
|
||||
output.sampleData(seiBuffer, payloadSize);
|
||||
output.sampleMetadata(pesTimeUs, C.BUFFER_FLAG_KEY_FRAME, payloadSize, 0, null);
|
||||
// Ignore country_code (1) + provider_code (2) + user_identifier (4)
|
||||
// + 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 {
|
||||
seiBuffer.skipBytes(payloadSize);
|
||||
}
|
||||
|
@ -90,8 +90,8 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
||||
for (int j = 0; j < formats.length; j++) {
|
||||
int nalUnitLengthFieldLength = streamElement.type == C.TRACK_TYPE_VIDEO ? 4 : -1;
|
||||
Track track = new Track(j, streamElement.type, streamElement.timescale, C.UNSET_TIME_US,
|
||||
manifest.durationUs, formats[j], trackEncryptionBoxes, nalUnitLengthFieldLength,
|
||||
null, null);
|
||||
manifest.durationUs, formats[j], Track.TRANSFORMATION_NONE, trackEncryptionBoxes,
|
||||
nalUnitLengthFieldLength, null, null);
|
||||
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
|
||||
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
|
||||
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
|
||||
|
@ -21,12 +21,10 @@ import com.google.android.exoplayer2.text.SubtitleInputBuffer;
|
||||
import com.google.android.exoplayer2.text.SubtitleOutputBuffer;
|
||||
import com.google.android.exoplayer2.text.SubtitleParser;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.LinkedList;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@ -168,7 +166,7 @@ public final class Eia608Parser implements SubtitleParser {
|
||||
private final LinkedList<SubtitleOutputBuffer> availableOutputBuffers;
|
||||
private final TreeSet<SubtitleInputBuffer> queuedInputBuffers;
|
||||
|
||||
private final ParsableBitArray seiBuffer;
|
||||
private final ParsableByteArray ccData;
|
||||
|
||||
private final StringBuilder captionStringBuilder;
|
||||
|
||||
@ -197,7 +195,7 @@ public final class Eia608Parser implements SubtitleParser {
|
||||
}
|
||||
queuedInputBuffers = new TreeSet<>();
|
||||
|
||||
seiBuffer = new ParsableBitArray();
|
||||
ccData = new ParsableByteArray();
|
||||
|
||||
captionStringBuilder = new StringBuilder();
|
||||
|
||||
@ -310,58 +308,30 @@ public final class Eia608Parser implements SubtitleParser {
|
||||
}
|
||||
|
||||
private void decode(SubtitleInputBuffer inputBuffer) {
|
||||
ByteBuffer inputData = inputBuffer.data;
|
||||
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);
|
||||
|
||||
ccData.reset(inputBuffer.data.array(), inputBuffer.data.limit());
|
||||
boolean captionDataProcessed = false;
|
||||
boolean isRepeatableControl = false;
|
||||
for (int i = 0; i < ccCount; i++) {
|
||||
seiBuffer.skipBits(5); // one_bit + reserved
|
||||
boolean ccValid = seiBuffer.readBit();
|
||||
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);
|
||||
while (ccData.bytesLeft() > 0) {
|
||||
byte ccData1 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||
byte ccData2 = (byte) (ccData.readUnsignedByte() & 0x7F);
|
||||
|
||||
// Ignore empty captions.
|
||||
if (ccData1 == 0 && ccData2 == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we've reached this point then there is data to process; flag that work has been done.
|
||||
captionDataProcessed = true;
|
||||
|
||||
// Special North American character set.
|
||||
// ccData2 - P|0|1|1|X|X|X|X
|
||||
if ((ccData1 == 0x11 || ccData1 == 0x19)
|
||||
&& ((ccData2 & 0x70) == 0x30)) {
|
||||
if ((ccData1 == 0x11 || ccData1 == 0x19) && ((ccData2 & 0x70) == 0x30)) {
|
||||
captionStringBuilder.append(getSpecialChar(ccData2));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extended Spanish/Miscellaneous and French character set.
|
||||
// ccData2 - P|0|1|X|X|X|X|X
|
||||
if ((ccData1 == 0x12 || ccData1 == 0x1A)
|
||||
&& ((ccData2 & 0x60) == 0x20)) {
|
||||
if ((ccData1 == 0x12 || ccData1 == 0x1A) && ((ccData2 & 0x60) == 0x20)) {
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedEsFrChar(ccData2));
|
||||
continue;
|
||||
@ -369,8 +339,7 @@ public final class Eia608Parser implements SubtitleParser {
|
||||
|
||||
// Extended Portuguese and German/Danish character set.
|
||||
// ccData2 - P|0|1|X|X|X|X|X
|
||||
if ((ccData1 == 0x13 || ccData1 == 0x1B)
|
||||
&& ((ccData2 & 0x60) == 0x20)) {
|
||||
if ((ccData1 == 0x13 || ccData1 == 0x1B) && ((ccData2 & 0x60) == 0x20)) {
|
||||
backspace(); // Remove standard equivalent of the special extended char.
|
||||
captionStringBuilder.append(getExtendedPtDeChar(ccData2));
|
||||
continue;
|
||||
|
Loading…
x
Reference in New Issue
Block a user