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
This commit is contained in:
aquilescanta 2017-02-20 10:11:19 -08:00 committed by Oliver Woodman
parent a84216c3a9
commit e86629ef3a
4 changed files with 74 additions and 22 deletions

View File

@ -157,7 +157,7 @@ public final class FragmentedMp4Extractor implements Extractor {
// Extractor output.
private ExtractorOutput extractorOutput;
private TrackOutput eventMessageTrackOutput;
private TrackOutput cea608TrackOutput;
private TrackOutput[] cea608TrackOutputs;
// Whether extractorOutput.seekMap has been called.
private boolean haveOutputSeekMap;
@ -459,10 +459,12 @@ public final class FragmentedMp4Extractor implements Extractor {
eventMessageTrackOutput.format(Format.createSampleFormat(null, MimeTypes.APPLICATION_EMSG,
Format.OFFSET_SAMPLE_RELATIVE));
}
if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutput == null) {
cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1, C.TRACK_TYPE_TEXT);
if ((flags & FLAG_ENABLE_CEA608_TRACK) != 0 && cea608TrackOutputs == null) {
TrackOutput cea608TrackOutput = extractorOutput.track(trackBundles.size() + 1,
C.TRACK_TYPE_TEXT);
cea608TrackOutput.format(Format.createTextSampleFormat(null, MimeTypes.APPLICATION_CEA608,
null, Format.NO_VALUE, 0, null, null));
cea608TrackOutputs = new TrackOutput[] {cea608TrackOutput};
}
}
@ -1085,7 +1087,7 @@ public final class FragmentedMp4Extractor implements Extractor {
output.sampleData(nalStartCode, 4);
// Write the NAL unit type byte.
output.sampleData(nalPrefix, 1);
processSeiNalUnitPayload = cea608TrackOutput != null
processSeiNalUnitPayload = cea608TrackOutputs != null
&& NalUnitUtil.isNalUnitSei(track.format.sampleMimeType, nalPrefixData[4]);
sampleBytesWritten += 5;
sampleSize += nalUnitLengthFieldLengthDiff;
@ -1103,7 +1105,7 @@ public final class FragmentedMp4Extractor implements Extractor {
nalBuffer.setPosition(MimeTypes.VIDEO_H265.equals(track.format.sampleMimeType) ? 1 : 0);
nalBuffer.setLimit(unescapedLength);
CeaUtil.consume(fragment.getSamplePresentationTime(sampleIndex) * 1000L, nalBuffer,
cea608TrackOutput);
cea608TrackOutputs);
} else {
// Write the payload of the NAL unit.
writtenBytes = output.sampleData(input, sampleCurrentNalBytesRemaining, false);

View File

@ -17,9 +17,13 @@ package com.google.android.exoplayer2.extractor.ts;
import android.support.annotation.IntDef;
import android.util.SparseArray;
import com.google.android.exoplayer2.Format;
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.RetentionPolicy;
import java.util.Collections;
import java.util.List;
/**
* Default implementation for {@link TsPayloadReader.Factory}.
@ -35,19 +39,36 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
public @interface Flags {
}
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_H264_STREAM = 4;
public static final int FLAG_DETECT_ACCESS_UNITS = 8;
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 16;
public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1;
public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;
public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
@Flags private final int flags;
private final List<Format> closedCaptionFormats;
public DefaultTsPayloadReaderFactory() {
this(0);
}
/**
* @param flags A combination of {@code FLAG_*} values, which control the behavior of the created
* readers.
*/
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.closedCaptionFormats = closedCaptionFormats;
}
@Override
@ -74,10 +95,10 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
return new PesReader(new H262Reader());
case TsExtractor.TS_STREAM_TYPE_H264:
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)));
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:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null : new SectionReader(new SpliceInfoSectionReader());
@ -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) {
return (flags & flag) != 0;
}

View File

@ -21,25 +21,45 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
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.ParsableByteArray;
import java.util.List;
/**
* Consumes SEI buffers, outputting contained CEA-608 messages to a {@link TrackOutput}.
*/
/* 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) {
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(),
MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null));
for (int i = 0; i < outputs.length; i++) {
idGenerator.generateNewId();
TrackOutput output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_TEXT);
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) {
CeaUtil.consume(pesTimeUs, seiBuffer, output);
CeaUtil.consume(pesTimeUs, seiBuffer, outputs);
}
}

View File

@ -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
* 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 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,
TrackOutput output) {
TrackOutput[] outputs) {
while (seiBuffer.bytesLeft() > 1 /* last byte will be rbsp_trailing_bits */) {
int payloadType = readNon255TerminatedValue(seiBuffer);
int payloadSize = readNon255TerminatedValue(seiBuffer);
@ -62,8 +62,12 @@ public final class CeaUtil {
// Each data packet consists of 24 bits: marker bits (5) + cc_valid (1) + cc_type (2)
// + cc_data_1 (8) + cc_data_2 (8).
int sampleLength = ccCount * 3;
output.sampleData(seiBuffer, sampleLength);
output.sampleMetadata(presentationTimeUs, C.BUFFER_FLAG_KEY_FRAME, sampleLength, 0, null);
int sampleStartPosition = seiBuffer.getPosition();
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.
seiBuffer.skipBytes(payloadSize - (10 + ccCount * 3));
} else {