/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.extractor.ts;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.util.SparseArray;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.CodecSpecificDataUtil;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.ts.TsPayloadReader.EsInfo;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
/** Default {@link TsPayloadReader.Factory} implementation. */
@UnstableApi
public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Factory {
/**
* Flags controlling elementary stream readers' behavior. Possible flag values are {@link
* #FLAG_ALLOW_NON_IDR_KEYFRAMES}, {@link #FLAG_IGNORE_AAC_STREAM}, {@link
* #FLAG_IGNORE_H264_STREAM}, {@link #FLAG_DETECT_ACCESS_UNITS}, {@link
* #FLAG_IGNORE_SPLICE_INFO_STREAM}, {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} and {@link
* #FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@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_OVERRIDE_CAPTION_DESCRIPTORS,
FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS
})
public @interface Flags {}
/**
* When extracting H.264 samples, whether to treat samples consisting of non-IDR I slices as
* synchronization samples (key-frames).
*/
public static final int FLAG_ALLOW_NON_IDR_KEYFRAMES = 1;
/**
* Prevents the creation of {@link AdtsReader} and {@link LatmReader} instances. This flag should
* be enabled if the transport stream contains no packets for an AAC elementary stream that is
* declared in the PMT.
*/
public static final int FLAG_IGNORE_AAC_STREAM = 1 << 1;
/**
* Prevents the creation of {@link H264Reader} instances. This flag should be enabled if the
* transport stream contains no packets for an H.264 elementary stream that is declared in the
* PMT.
*/
public static final int FLAG_IGNORE_H264_STREAM = 1 << 2;
/**
* When extracting H.264 samples, whether to split the input stream into access units (samples)
* based on slice headers. This flag should be disabled if the stream contains access unit
* delimiters (AUDs).
*/
public static final int FLAG_DETECT_ACCESS_UNITS = 1 << 3;
/**
* Prevents the creation of {@link SectionPayloadReader}s for splice information sections
* (SCTE-35).
*/
public static final int FLAG_IGNORE_SPLICE_INFO_STREAM = 1 << 4;
/**
* Whether the list of {@code closedCaptionFormats} passed to {@link
* DefaultTsPayloadReaderFactory#DefaultTsPayloadReaderFactory(int, List)} should be used in spite
* of any closed captions service descriptors. If this flag is disabled, {@code
* closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors.
*/
public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5;
/**
* Sets whether HDMV DTS audio streams will be handled. If this flag is set, SCTE subtitles will
* not be detected, as they share the same elementary stream type as HDMV DTS.
*/
public static final int FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS = 1 << 6;
private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
private final @Flags int flags;
private final List<Format> closedCaptionFormats;
public DefaultTsPayloadReaderFactory() {
this(0);
}
/**
* @param flags A combination of {@code FLAG_*} values that control the behavior of the created
* readers.
*/
public DefaultTsPayloadReaderFactory(@Flags int flags) {
this(flags, ImmutableList.of());
}
/**
* @param flags A combination of {@code FLAG_*} values that control the behavior of the created
* readers.
* @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<Format> closedCaptionFormats) {
this.flags = flags;
this.closedCaptionFormats = closedCaptionFormats;
}
@Override
public SparseArray<TsPayloadReader> createInitialPayloadReaders() {
return new SparseArray<>();
}
@Override
@Nullable
public TsPayloadReader createPayloadReader(int streamType, EsInfo esInfo) {
switch (streamType) {
case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new PesReader(new MpegAudioReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AAC_ADTS:
return isSet(FLAG_IGNORE_AAC_STREAM)
? null
: new PesReader(new AdtsReader(false, esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AAC_LATM:
return isSet(FLAG_IGNORE_AAC_STREAM)
? null
: new PesReader(new LatmReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new PesReader(new Ac3Reader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AC4:
return new PesReader(new Ac4Reader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
if (!isSet(FLAG_ENABLE_HDMV_DTS_AUDIO_STREAMS)) {
return null;
}
// Fall through.
case TsExtractor.TS_STREAM_TYPE_DTS:
return new PesReader(new DtsReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_H262:
case TsExtractor.TS_STREAM_TYPE_DC2_H262:
return new PesReader(new H262Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H263:
return new PesReader(new H263Reader(buildUserDataReader(esInfo)));
case TsExtractor.TS_STREAM_TYPE_H264:
return isSet(FLAG_IGNORE_H264_STREAM)
? null
: 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(esInfo)));
case TsExtractor.TS_STREAM_TYPE_SPLICE_INFO:
return isSet(FLAG_IGNORE_SPLICE_INFO_STREAM)
? null
: new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_SCTE35));
case TsExtractor.TS_STREAM_TYPE_ID3:
return new PesReader(new Id3Reader());
case TsExtractor.TS_STREAM_TYPE_DVBSUBS:
return new PesReader(new DvbSubtitleReader(esInfo.dvbSubtitleInfos));
case TsExtractor.TS_STREAM_TYPE_AIT:
return new SectionReader(new PassthroughSectionPayloadReader(MimeTypes.APPLICATION_AIT));
default:
return null;
}
}
/**
* 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) {
return new SeiReader(getClosedCaptionFormats(esInfo));
}
/**
* If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link UserDataReader} for
* {@link #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a
* {@link UserDataReader} 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 UserDataReader} for closed caption tracks.
*/
private UserDataReader buildUserDataReader(EsInfo esInfo) {
return new UserDataReader(getClosedCaptionFormats(esInfo));
}
/**
* If {@link #FLAG_OVERRIDE_CAPTION_DESCRIPTORS} is set, returns a {@link List<Format>} of {@link
* #closedCaptionFormats}. If unset, parses the PMT descriptor information and returns a {@link
* List<Format>} 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 List<Format>} containing list of closed caption formats.
*/
private List<Format> getClosedCaptionFormats(EsInfo esInfo) {
if (isSet(FLAG_OVERRIDE_CAPTION_DESCRIPTORS)) {
return closedCaptionFormats;
}
ParsableByteArray scratchDescriptorData = new ParsableByteArray(esInfo.descriptorBytes);
List<Format> 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;
}
// easy_reader(1), wide_aspect_ratio(1), reserved(6).
byte flags = (byte) scratchDescriptorData.readUnsignedByte();
// Skip reserved (8).
scratchDescriptorData.skipBytes(1);
@Nullable List<byte[]> initializationData = null;
// The wide_aspect_ratio flag only has meaning for CEA-708.
if (isDigital) {
boolean isWideAspectRatio = (flags & 0x40) != 0;
initializationData =
CodecSpecificDataUtil.buildCea708InitializationData(isWideAspectRatio);
}
closedCaptionFormats.add(
new Format.Builder()
.setSampleMimeType(mimeType)
.setLanguage(language)
.setAccessibilityChannel(accessibilityChannel)
.setInitializationData(initializationData)
.build());
}
} else {
// Unknown descriptor. Ignore.
}
scratchDescriptorData.setPosition(nextDescriptorPosition);
}
return closedCaptionFormats;
}
private boolean isSet(@Flags int flag) {
return (flags & flag) != 0;
}
}