Commit e86629ef by aquilescanta Committed by Oliver Woodman

Allow exposing multiple CEA608 tracks for Transport Streams

This CL allows passing multiple formats describing CC channels to the
TS payload reader factory. As a simple usecase, ATSC can expose both
608 channels by passing a two element list with the corresponding
accessibility channels. The HLS media source can construct this list
from the EXT-X-MEDIA:TYPE="CLOSED-CAPTIONS" tags, including language
and selection flags. The interface extends without modification to
708.

Pending work:
* Multiple CC channels in HLS.
* caption_service_descriptor parsing for overriding the user's selection.
* 708 support in SEI reader.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=148030293
parent a84216c3
...@@ -157,7 +157,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -157,7 +157,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// Extractor output. // Extractor output.
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
private TrackOutput eventMessageTrackOutput; private TrackOutput eventMessageTrackOutput;
private TrackOutput cea608TrackOutput; private TrackOutput[] cea608TrackOutputs;
// Whether extractorOutput.seekMap has been called. // Whether extractorOutput.seekMap has been called.
private boolean haveOutputSeekMap; private boolean haveOutputSeekMap;
...@@ -459,10 +459,12 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -459,10 +459,12 @@ public final class FragmentedMp4Extractor implements Extractor {
eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG, eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG,
Format.OFFSET_SAMPLE_RELATIVE)); Format.OFFSET_SAMPLE_RELATIVE));
} }
if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutput == null) { if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutputs == null) {
cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1, C.TRACK_TYPE_TEXT); TrackOutput cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1,
C.TRACK_TYPE_TEXT);
cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608, cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
null, Format.NO_VALUE, 0, null, null)); null, Format.NO_VALUE, 0, null, null));
cea608TrackOutputs = new TrackOutput[] {cea608TrackOutput};
} }
} }
...@@ -1085,7 +1087,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -1085,7 +1087,7 @@ public final class FragmentedMp4Extractor implements Extractor {
output.sampleData(nalStartCode, 4); output.sampleData(nalStartCode, 4);
// Write the NAL unit type byte. // Write the NAL unit type byte.
output.sampleData(nalPrefix, 1); output.sampleData(nalPrefix, 1);
processSeiNalUnitPayload = cea608TrackOutput != null processSeiNalUnitPayload = cea608TrackOutputs != null
&& NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]); && NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
sampleBytesWritten += 5; sampleBytesWritten += 5;
sampleSize += nalUnitLengthFieldLengthDiff; sampleSize += nalUnitLengthFieldLengthDiff;
...@@ -1103,7 +1105,7 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -1103,7 +1105,7 @@ public final class FragmentedMp4Extractor implements Extractor {
nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0); nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
nalBuffer.setLimit(unescapedLength); nalBuffer.setLimit(unescapedLength);
CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer, CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer,
cea608TrackOutput); cea608TrackOutputs);
} else { } else {
// Write the payload of the NAL unit. // Write the payload of the NAL unit.
writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false); writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);
......
...@@ -17,9 +17,13 @@ package com.google.android.exoplayer2.extractor.ts; ...@@ -17,9 +17,13 @@ package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.EsInfo;
import com.google.android.exoplayer2.util.MimeTypes;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.List;
/** /**
* Default implementation for {@link TsPayloadReader.Factory}. * Default implementation for {@link TsPayloadReader.Factory}.
...@@ -35,19 +39,36 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -35,19 +39,36 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
public @interface Flags { public @interface Flags {
} }
public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1;
public static final int FLAG_IGNORE_AAC_STREAM = 2; public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1;
public static final int FLAG_IGNORE_H264_STREAM = 4; public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;
public static final int FLAG_DETECT_ACCESS_UNITS = 8; public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 16; public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
@Flags private final int flags; @Flags private final int flags;
private final List<Format> closedCaptionFormats;
public DefaultTsPayloadReaderFactory() { public DefaultTsPayloadReaderFactory() {
this(0); this(0);
} }
/**
* @param flags A combination of {@code FLAG_*} values, which control the behavior of the created
* readers.
*/
public DefaultTsPayloadReaderFactory(@Flags int flags) { public DefaultTsPayloadReaderFactory(@Flags int flags) {
this(flags, Collections.singletonList(Format.createTextSampleFormat(null,
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)));
}
/**
* @param flags A combination of {@code FLAG_*} values, which control the behavior of the created
* readers.
* @param closedCaptionFormats {@link Format}s to be exposed by elementary stream readers for
* streams with embedded closed captions.
*/
public DefaultTsPayloadReaderFactory(@Flags int flags, List<Format> closedCaptionFormats) {
this.flags = flags; this.flags = flags;
this.closedCaptionFormats = closedCaptionFormats;
} }
@Override @Override
...@@ -74,10 +95,10 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -74,10 +95,10 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new PesReader(new H262Reader()); return new PesReader(new H262Reader());
case TsExtractor.TS_STREAM_TYPE_H264: case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM) ? null return isSet(FLAG_IGNORE_H264_STREAM) ? null
: new PesReader(new H264Reader(new SeiReader(), isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), : new PesReader(new H264Reader(buildSeiReader(), isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES),
isSet(FLAG_DETECT_ACCESS_UNITS))); isSet(FLAG_DETECT_ACCESS_UNITS)));
case TsExtractor.TS_STREAM_TYPE_H265: case TsExtractor.TS_STREAM_TYPE_H265:
return new PesReader(new H265Reader(new SeiReader())); return new PesReader(new H265Reader(buildSeiReader()));
case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO: case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM) return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null : new SectionReader(new SpliceInfoSectionReader()); ? null : new SectionReader(new SpliceInfoSectionReader());
...@@ -88,6 +109,11 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact ...@@ -88,6 +109,11 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
} }
} }
private SeiReader buildSeiReader() {
// TODO: Add descriptor parsing to detect channels automatically.
return new SeiReader(closedCaptionFormats);
}
private boolean isSet(@Flags int flag) { private boolean isSet(@Flags int flag) {
return (flags & flag) != 0; return (flags & flag) != 0;
} }
......
...@@ -21,25 +21,45 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -21,25 +21,45 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput; import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.text.cea.CeaUtil; import com.google.android.exoplayer2.text.cea.CeaUtil;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.List;
/** /**
* Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}. * Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
*/ */
/* package */ final class SeiReader { /* package */ final class SeiReader {
private TrackOutput output; private final List<Format> closedCaptionFormats;
private final TrackOutput[] outputs;
/**
* @param closedCaptionFormats A list of formats for the closed caption channels to expose.
*/
public SeiReader(List<Format> closedCaptionFormats) {
this.closedCaptionFormats = closedCaptionFormats;
outputs = new TrackOutput[closedCaptionFormats.size()];
}
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId(); for (int i = 0; i < outputs.length; i++) {
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT); idGenerator.generateNewId();
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); Format channelFormat = closedCaptionFormats.get(i);
String channelMimeType = channelFormat.sampleMimeType;
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
"Invalid closed caption mime type provided: " + channelMimeType);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null,
Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language,
channelFormat.accessibilityChannel, null));
outputs[i] = output;
}
} }
public void consume(long pesTimeUs, ParsableByteArray seiBuffer) { public void consume(long pesTimeUs, ParsableByteArray seiBuffer) {
CeaUtil.consume(pesTimeUs, seiBuffer, output); CeaUtil.consume(pesTimeUs, seiBuffer, outputs);
} }
} }
...@@ -35,14 +35,14 @@ public final class CeaUtil { ...@@ -35,14 +35,14 @@ public final class CeaUtil {
/** /**
* Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages * Consumes the unescaped content of an SEI NAL unit, writing the content of any CEA-608 messages
* as samples to the provided output. * as samples to all of the provided outputs.
* *
* @param presentationTimeUs The presentation time in microseconds for any samples. * @param presentationTimeUs The presentation time in microseconds for any samples.
* @param seiBuffer The unescaped SEI NAL unit data, excluding the NAL unit start code and type. * @param seiBuffer The unescaped SEI NAL unit data, excluding the NAL unit start code and type.
* @param output The output to which any samples should be written. * @param outputs The outputs to which any samples should be written.
*/ */
public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer, public static void consume(long presentationTimeUs, ParsableByteArray seiBuffer,
TrackOutput output) { TrackOutput[] outputs) {
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) { while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
int payloadType = readNon255TerminatedValue(seiBuffer); int payloadType = readNon255TerminatedValue(seiBuffer);
int payloadSize = readNon255TerminatedValue(seiBuffer); int payloadSize = readNon255TerminatedValue(seiBuffer);
...@@ -62,8 +62,12 @@ public final class CeaUtil { ...@@ -62,8 +62,12 @@ public final class CeaUtil {
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2) // Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8). // + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3; int sampleLength = ccCount * 3;
output.sampleData(seiBuffer, sampleLength); int sampleStartPosition = seiBuffer.getPosition();
output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null); for (TrackOutput output : outputs) {
seiBuffer.setPosition(sampleStartPosition);
output.sampleData(seiBuffer, sampleLength);
output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null);
}
// Ignore trailing information in SEI, if any. // Ignore trailing information in SEI, if any.
seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3)); seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3));
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment