From 3fc3349e9535d8763f9a4c34c1d9a9e2ce7024a3 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 23 Feb 2017 01:55:56 -0800 Subject: [PATCH] Add support for Caption Format Descriptor This allows the TsExtractor to automatically determine the closed caption tracks to expose by parsing available descriptors. Issue:#2161 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=148321380 --- .../ts/DefaultTsPayloadReaderFactory.java | 85 +++++++++++++++---- .../exoplayer2/source/hls/HlsMediaChunk.java | 4 + 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 21050d2bbb..587f036797 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -20,8 +20,10 @@ 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 com.google.android.exoplayer2.util.ParsableByteArray; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -35,7 +37,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, value = {FLAG_ALLOW_NON_IDR_KEYFRAMES, FLAG_IGNORE_AAC_STREAM, - FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM}) + FLAG_IGNORE_H264_STREAM, FLAG_DETECT_ACCESS_UNITS, FLAG_IGNORE_SPLICE_INFO_STREAM, + FLAG_OVERRIDE_CAPTION_DESCRIPTORS}) public @interface Flags { } public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1; @@ -43,31 +46,33 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact 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; + public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; + + private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; @Flags private final int flags; private final List closedCaptionFormats; public DefaultTsPayloadReaderFactory() { - this(0); + this(0, Collections.emptyList()); } /** * @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. + * @param closedCaptionFormats {@link Format}s to be exposed by payload readers for streams with + * embedded closed captions when no caption service descriptors are provided. If + * {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, {@code closedCaptionFormats} overrides + * any descriptor information. If not set, and {@code closedCaptionFormats} is empty, a + * closed caption track with {@link Format#accessibilityChannel} {@link Format#NO_VALUE} will + * be exposed. */ public DefaultTsPayloadReaderFactory(@Flags int flags, List closedCaptionFormats) { this.flags = flags; + if (!isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS) && closedCaptionFormats.isEmpty()) { + closedCaptionFormats = Collections.singletonList(Format.createTextSampleFormat(null, + MimeTypes.APPLICATION_CEA608, null, Format.NO_VALUE, 0, null, null)); + } this.closedCaptionFormats = closedCaptionFormats; } @@ -95,10 +100,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(buildSeiReader(), isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), - isSet(FLAG_DETECT_ACCESS_UNITS))); + : new PesReader(new H264Reader(buildSeiReader(esInfo), + isSet(FLAG_ALLOW_NON_IDR_KEYFRAMES), isSet(FLAG_DETECT_ACCESS_UNITS))); case TsExtractor.TS_STREAM_TYPE_H265: - return new PesReader(new H265Reader(buildSeiReader())); + return new PesReader(new H265Reader(buildSeiReader(esInfo))); case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO: return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM) ? null : new SectionReader(new SpliceInfoSectionReader()); @@ -109,8 +114,52 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact } } - private SeiReader buildSeiReader() { - // TODO: Add descriptor parsing to detect channels automatically. + /** + * If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link SeiReader} for + * {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a + * {@link SeiReader} for the declared formats, or {@link #closedCaptionFormats} if the descriptor + * is not present. + * + * @param esInfo The {@link EsInfo} passed to {@link #createPayloadReader(int, EsInfo)}. + * @return A {@link SeiReader} for closed caption tracks. + */ + private SeiReader buildSeiReader(EsInfo esInfo) { + if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) { + return new SeiReader(closedCaptionFormats); + } + ParsableByteArray scratchDescriptorData = new ParsableByteArray(esInfo.descriptorBytes); + List closedCaptionFormats = this.closedCaptionFormats; + while (scratchDescriptorData.bytesLeft() > 0) { + int descriptorTag = scratchDescriptorData.readUnsignedByte(); + int descriptorLength = scratchDescriptorData.readUnsignedByte(); + int nextDescriptorPosition = scratchDescriptorData.getPosition() + descriptorLength; + if (descriptorTag == DESCRIPTOR_TAG_CAPTION_SERVICE) { + // Note: see ATSC A/65 for detailed information about the caption service descriptor. + closedCaptionFormats = new ArrayList<>(); + int numberOfServices = scratchDescriptorData.readUnsignedByte() & 0x1F; + for (int i = 0; i < numberOfServices; i++) { + String language = scratchDescriptorData.readString(3); + int captionTypeByte = scratchDescriptorData.readUnsignedByte(); + boolean isDigital = (captionTypeByte & 0x80) != 0; + String mimeType; + int accessibilityChannel; + if (isDigital) { + mimeType = MimeTypes.APPLICATION_CEA708; + accessibilityChannel = captionTypeByte & 0x3F; + } else { + mimeType = MimeTypes.APPLICATION_CEA608; + accessibilityChannel = 1; + } + closedCaptionFormats.add(Format.createTextSampleFormat(null, mimeType, null, + Format.NO_VALUE, 0, language, accessibilityChannel, null)); + // Skip easy_reader(1), wide_aspect_ratio(1), reserved(14). + scratchDescriptorData.skipBytes(2); + } + } else { + // Unknown descriptor. Ignore. + } + scratchDescriptorData.setPosition(nextDescriptorPosition); + } return new SeiReader(closedCaptionFormats); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java index 357a32f086..cc3b1087e4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java @@ -356,6 +356,10 @@ import java.util.concurrent.atomic.AtomicInteger; // This flag ensures the change of pid between streams does not affect the sample queues. @DefaultTsPayloadReaderFactory.Flags int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM; + if (!muxedCaptionFormats.isEmpty()) { + // The playlist declares closed caption renditions, we should ignore descriptors. + esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS; + } String codecs = trackFormat.codecs; if (!TextUtils.isEmpty(codecs)) { // Sometimes AAC and H264 streams are declared in TS chunks even though they don't really