/* * 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.common; import static java.lang.annotation.ElementType.TYPE_USE; import android.os.Bundle; import androidx.annotation.IntDef; import androidx.annotation.Nullable; import androidx.media3.common.util.BundleableUtil; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.base.Joiner; 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.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.UUID; /** * Represents a media format. * *

When building formats, populate all fields whose values are known and relevant to the type of * format being constructed. For information about different types of format, see ExoPlayer's Supported formats page. * *

Fields commonly relevant to all formats

* * * *

Fields relevant to container formats

* * * *

Fields relevant to sample formats

* * * *

Fields relevant to video formats

* * * *

Fields relevant to audio formats

* * * *

Fields relevant to text formats

* * */ public final class Format implements Bundleable { /** * Builds {@link Format} instances. * *

Use Format#buildUpon() to obtain a builder representing an existing {@link Format}. * *

When building formats, populate all fields whose values are known and relevant to the type * of format being constructed. See the {@link Format} Javadoc for information about which fields * should be set for different types of format. */ @UnstableApi public static final class Builder { @Nullable private String id; @Nullable private String label; @Nullable private String language; private @C.SelectionFlags int selectionFlags; private @C.RoleFlags int roleFlags; private int averageBitrate; private int peakBitrate; @Nullable private String codecs; @Nullable private Metadata metadata; // Container specific. @Nullable private String containerMimeType; // Sample specific. @Nullable private String sampleMimeType; private int maxInputSize; @Nullable private List initializationData; @Nullable private DrmInitData drmInitData; private long subsampleOffsetUs; // Video specific. private int width; private int height; private float frameRate; private int rotationDegrees; private float pixelWidthHeightRatio; @Nullable private byte[] projectionData; private @C.StereoMode int stereoMode; @Nullable private ColorInfo colorInfo; // Audio specific. private int channelCount; private int sampleRate; private @C.PcmEncoding int pcmEncoding; private int encoderDelay; private int encoderPadding; // Text specific. private int accessibilityChannel; // Provided by the source. private @C.CryptoType int cryptoType; /** Creates a new instance with default values. */ public Builder() { averageBitrate = NO_VALUE; peakBitrate = NO_VALUE; // Sample specific. maxInputSize = NO_VALUE; subsampleOffsetUs = OFFSET_SAMPLE_RELATIVE; // Video specific. width = NO_VALUE; height = NO_VALUE; frameRate = NO_VALUE; pixelWidthHeightRatio = 1.0f; stereoMode = NO_VALUE; // Audio specific. channelCount = NO_VALUE; sampleRate = NO_VALUE; pcmEncoding = NO_VALUE; // Text specific. accessibilityChannel = NO_VALUE; // Provided by the source. cryptoType = C.CRYPTO_TYPE_NONE; } /** * Creates a new instance to build upon the provided {@link Format}. * * @param format The {@link Format} to build upon. */ private Builder(Format format) { this.id = format.id; this.label = format.label; this.language = format.language; this.selectionFlags = format.selectionFlags; this.roleFlags = format.roleFlags; this.averageBitrate = format.averageBitrate; this.peakBitrate = format.peakBitrate; this.codecs = format.codecs; this.metadata = format.metadata; // Container specific. this.containerMimeType = format.containerMimeType; // Sample specific. this.sampleMimeType = format.sampleMimeType; this.maxInputSize = format.maxInputSize; this.initializationData = format.initializationData; this.drmInitData = format.drmInitData; this.subsampleOffsetUs = format.subsampleOffsetUs; // Video specific. this.width = format.width; this.height = format.height; this.frameRate = format.frameRate; this.rotationDegrees = format.rotationDegrees; this.pixelWidthHeightRatio = format.pixelWidthHeightRatio; this.projectionData = format.projectionData; this.stereoMode = format.stereoMode; this.colorInfo = format.colorInfo; // Audio specific. this.channelCount = format.channelCount; this.sampleRate = format.sampleRate; this.pcmEncoding = format.pcmEncoding; this.encoderDelay = format.encoderDelay; this.encoderPadding = format.encoderPadding; // Text specific. this.accessibilityChannel = format.accessibilityChannel; // Provided by the source. this.cryptoType = format.cryptoType; } /** * Sets {@link Format#id}. The default value is {@code null}. * * @param id The {@link Format#id}. * @return The builder. */ public Builder setId(@Nullable String id) { this.id = id; return this; } /** * Sets {@link Format#id} to {@link Integer#toString() Integer.toString(id)}. The default value * is {@code null}. * * @param id The {@link Format#id}. * @return The builder. */ public Builder setId(int id) { this.id = Integer.toString(id); return this; } /** * Sets {@link Format#label}. The default value is {@code null}. * * @param label The {@link Format#label}. * @return The builder. */ public Builder setLabel(@Nullable String label) { this.label = label; return this; } /** * Sets {@link Format#language}. The default value is {@code null}. * * @param language The {@link Format#language}. * @return The builder. */ public Builder setLanguage(@Nullable String language) { this.language = language; return this; } /** * Sets {@link Format#selectionFlags}. The default value is 0. * * @param selectionFlags The {@link Format#selectionFlags}. * @return The builder. */ public Builder setSelectionFlags(@C.SelectionFlags int selectionFlags) { this.selectionFlags = selectionFlags; return this; } /** * Sets {@link Format#roleFlags}. The default value is 0. * * @param roleFlags The {@link Format#roleFlags}. * @return The builder. */ public Builder setRoleFlags(@C.RoleFlags int roleFlags) { this.roleFlags = roleFlags; return this; } /** * Sets {@link Format#averageBitrate}. The default value is {@link #NO_VALUE}. * * @param averageBitrate The {@link Format#averageBitrate}. * @return The builder. */ public Builder setAverageBitrate(int averageBitrate) { this.averageBitrate = averageBitrate; return this; } /** * Sets {@link Format#peakBitrate}. The default value is {@link #NO_VALUE}. * * @param peakBitrate The {@link Format#peakBitrate}. * @return The builder. */ public Builder setPeakBitrate(int peakBitrate) { this.peakBitrate = peakBitrate; return this; } /** * Sets {@link Format#codecs}. The default value is {@code null}. * * @param codecs The {@link Format#codecs}. * @return The builder. */ public Builder setCodecs(@Nullable String codecs) { this.codecs = codecs; return this; } /** * Sets {@link Format#metadata}. The default value is {@code null}. * * @param metadata The {@link Format#metadata}. * @return The builder. */ public Builder setMetadata(@Nullable Metadata metadata) { this.metadata = metadata; return this; } // Container specific. /** * Sets {@link Format#containerMimeType}. The default value is {@code null}. * * @param containerMimeType The {@link Format#containerMimeType}. * @return The builder. */ public Builder setContainerMimeType(@Nullable String containerMimeType) { this.containerMimeType = containerMimeType; return this; } // Sample specific. /** * Sets {@link Format#sampleMimeType}. The default value is {@code null}. * * @param sampleMimeType {@link Format#sampleMimeType}. * @return The builder. */ public Builder setSampleMimeType(@Nullable String sampleMimeType) { this.sampleMimeType = sampleMimeType; return this; } /** * Sets {@link Format#maxInputSize}. The default value is {@link #NO_VALUE}. * * @param maxInputSize The {@link Format#maxInputSize}. * @return The builder. */ public Builder setMaxInputSize(int maxInputSize) { this.maxInputSize = maxInputSize; return this; } /** * Sets {@link Format#initializationData}. The default value is {@code null}. * * @param initializationData The {@link Format#initializationData}. * @return The builder. */ public Builder setInitializationData(@Nullable List initializationData) { this.initializationData = initializationData; return this; } /** * Sets {@link Format#drmInitData}. The default value is {@code null}. * * @param drmInitData The {@link Format#drmInitData}. * @return The builder. */ public Builder setDrmInitData(@Nullable DrmInitData drmInitData) { this.drmInitData = drmInitData; return this; } /** * Sets {@link Format#subsampleOffsetUs}. The default value is {@link #OFFSET_SAMPLE_RELATIVE}. * * @param subsampleOffsetUs The {@link Format#subsampleOffsetUs}. * @return The builder. */ public Builder setSubsampleOffsetUs(long subsampleOffsetUs) { this.subsampleOffsetUs = subsampleOffsetUs; return this; } // Video specific. /** * Sets {@link Format#width}. The default value is {@link #NO_VALUE}. * * @param width The {@link Format#width}. * @return The builder. */ public Builder setWidth(int width) { this.width = width; return this; } /** * Sets {@link Format#height}. The default value is {@link #NO_VALUE}. * * @param height The {@link Format#height}. * @return The builder. */ public Builder setHeight(int height) { this.height = height; return this; } /** * Sets {@link Format#frameRate}. The default value is {@link #NO_VALUE}. * * @param frameRate The {@link Format#frameRate}. * @return The builder. */ public Builder setFrameRate(float frameRate) { this.frameRate = frameRate; return this; } /** * Sets {@link Format#rotationDegrees}. The default value is 0. * * @param rotationDegrees The {@link Format#rotationDegrees}. * @return The builder. */ public Builder setRotationDegrees(int rotationDegrees) { this.rotationDegrees = rotationDegrees; return this; } /** * Sets {@link Format#pixelWidthHeightRatio}. The default value is 1.0f. * * @param pixelWidthHeightRatio The {@link Format#pixelWidthHeightRatio}. * @return The builder. */ public Builder setPixelWidthHeightRatio(float pixelWidthHeightRatio) { this.pixelWidthHeightRatio = pixelWidthHeightRatio; return this; } /** * Sets {@link Format#projectionData}. The default value is {@code null}. * * @param projectionData The {@link Format#projectionData}. * @return The builder. */ public Builder setProjectionData(@Nullable byte[] projectionData) { this.projectionData = projectionData; return this; } /** * Sets {@link Format#stereoMode}. The default value is {@link #NO_VALUE}. * * @param stereoMode The {@link Format#stereoMode}. * @return The builder. */ public Builder setStereoMode(@C.StereoMode int stereoMode) { this.stereoMode = stereoMode; return this; } /** * Sets {@link Format#colorInfo}. The default value is {@code null}. * * @param colorInfo The {@link Format#colorInfo}. * @return The builder. */ public Builder setColorInfo(@Nullable ColorInfo colorInfo) { this.colorInfo = colorInfo; return this; } // Audio specific. /** * Sets {@link Format#channelCount}. The default value is {@link #NO_VALUE}. * * @param channelCount The {@link Format#channelCount}. * @return The builder. */ public Builder setChannelCount(int channelCount) { this.channelCount = channelCount; return this; } /** * Sets {@link Format#sampleRate}. The default value is {@link #NO_VALUE}. * * @param sampleRate The {@link Format#sampleRate}. * @return The builder. */ public Builder setSampleRate(int sampleRate) { this.sampleRate = sampleRate; return this; } /** * Sets {@link Format#pcmEncoding}. The default value is {@link #NO_VALUE}. * * @param pcmEncoding The {@link Format#pcmEncoding}. * @return The builder. */ public Builder setPcmEncoding(@C.PcmEncoding int pcmEncoding) { this.pcmEncoding = pcmEncoding; return this; } /** * Sets {@link Format#encoderDelay}. The default value is 0. * * @param encoderDelay The {@link Format#encoderDelay}. * @return The builder. */ public Builder setEncoderDelay(int encoderDelay) { this.encoderDelay = encoderDelay; return this; } /** * Sets {@link Format#encoderPadding}. The default value is 0. * * @param encoderPadding The {@link Format#encoderPadding}. * @return The builder. */ public Builder setEncoderPadding(int encoderPadding) { this.encoderPadding = encoderPadding; return this; } // Text specific. /** * Sets {@link Format#accessibilityChannel}. The default value is {@link #NO_VALUE}. * * @param accessibilityChannel The {@link Format#accessibilityChannel}. * @return The builder. */ public Builder setAccessibilityChannel(int accessibilityChannel) { this.accessibilityChannel = accessibilityChannel; return this; } // Provided by source. /** * Sets {@link Format#cryptoType}. The default value is {@link C#CRYPTO_TYPE_NONE}. * * @param cryptoType The {@link C.CryptoType}. * @return The builder. */ public Builder setCryptoType(@C.CryptoType int cryptoType) { this.cryptoType = cryptoType; return this; } // Build. public Format build() { return new Format(/* builder= */ this); } } /** A value for various fields to indicate that the field's value is unknown or not applicable. */ public static final int NO_VALUE = -1; /** * A value for {@link #subsampleOffsetUs} to indicate that subsample timestamps are relative to * the timestamps of their parent samples. */ @UnstableApi public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; private static final Format DEFAULT = new Builder().build(); /** An identifier for the format, or null if unknown or not applicable. */ @Nullable public final String id; /** The human readable label, or null if unknown or not applicable. */ @Nullable public final String label; /** The language as an IETF BCP 47 conformant tag, or null if unknown or not applicable. */ @Nullable public final String language; /** Track selection flags. */ public final @C.SelectionFlags int selectionFlags; /** Track role flags. */ public final @C.RoleFlags int roleFlags; /** * The average bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The * way in which this field is populated depends on the type of media to which the format * corresponds: * *

*/ @UnstableApi public final int averageBitrate; /** * The peak bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The way * in which this field is populated depends on the type of media to which the format corresponds: * * */ @UnstableApi public final int peakBitrate; /** * The bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate * if known, or else {@link Format#NO_VALUE}. Equivalent to: {@code peakBitrate != NO_VALUE ? * peakBitrate : averageBitrate}. */ @UnstableApi public final int bitrate; /** Codecs of the format as described in RFC 6381, or null if unknown or not applicable. */ @Nullable public final String codecs; /** Metadata, or null if unknown or not applicable. */ @UnstableApi @Nullable public final Metadata metadata; // Container specific. /** The mime type of the container, or null if unknown or not applicable. */ @Nullable public final String containerMimeType; // Sample specific. /** The sample mime type, or null if unknown or not applicable. */ @Nullable public final String sampleMimeType; /** * The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or * not applicable. */ @UnstableApi public final int maxInputSize; /** * Initialization data that must be provided to the decoder. Will not be null, but may be empty if * initialization data is not required. */ @UnstableApi public final List initializationData; /** DRM initialization data if the stream is protected, or null otherwise. */ @UnstableApi @Nullable public final DrmInitData drmInitData; /** * For samples that contain subsamples, this is an offset that should be added to subsample * timestamps. A value of {@link #OFFSET_SAMPLE_RELATIVE} indicates that subsample timestamps are * relative to the timestamps of their parent samples. */ @UnstableApi public final long subsampleOffsetUs; // Video specific. /** The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. */ public final int width; /** The height of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. */ public final int height; /** The frame rate in frames per second, or {@link #NO_VALUE} if unknown or not applicable. */ public final float frameRate; /** * The clockwise rotation that should be applied to the video for it to be rendered in the correct * orientation, or 0 if unknown or not applicable. Only 0, 90, 180 and 270 are supported. */ @UnstableApi public final int rotationDegrees; /** The width to height ratio of pixels in the video, or 1.0 if unknown or not applicable. */ public final float pixelWidthHeightRatio; /** The projection data for 360/VR video, or null if not applicable. */ @UnstableApi @Nullable public final byte[] projectionData; /** * The stereo layout for 360/3D/VR video, or {@link #NO_VALUE} if not applicable. Valid stereo * modes are {@link C#STEREO_MODE_MONO}, {@link C#STEREO_MODE_TOP_BOTTOM}, {@link * C#STEREO_MODE_LEFT_RIGHT}, {@link C#STEREO_MODE_STEREO_MESH}. */ @UnstableApi public final @C.StereoMode int stereoMode; /** The color metadata associated with the video, or null if not applicable. */ @UnstableApi @Nullable public final ColorInfo colorInfo; // Audio specific. /** The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable. */ public final int channelCount; /** The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. */ public final int sampleRate; /** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */ @UnstableApi public final @C.PcmEncoding int pcmEncoding; /** * The number of frames to trim from the start of the decoded audio stream, or 0 if not * applicable. */ @UnstableApi public final int encoderDelay; /** * The number of frames to trim from the end of the decoded audio stream, or 0 if not applicable. */ @UnstableApi public final int encoderPadding; // Text specific. /** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */ @UnstableApi public final int accessibilityChannel; // Provided by source. /** * The type of crypto that must be used to decode samples associated with this format, or {@link * C#CRYPTO_TYPE_NONE} if the content is not encrypted. Cannot be {@link C#CRYPTO_TYPE_NONE} if * {@link #drmInitData} is non-null, but may be {@link C#CRYPTO_TYPE_UNSUPPORTED} to indicate that * the samples are encrypted using an unsupported crypto type. */ @UnstableApi public final @C.CryptoType int cryptoType; // Lazily initialized hashcode. private int hashCode; // Video. /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createVideoSampleFormat( @Nullable String id, @Nullable String sampleMimeType, @Nullable String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, @Nullable List initializationData, @Nullable DrmInitData drmInitData) { return new Builder() .setId(id) .setAverageBitrate(bitrate) .setPeakBitrate(bitrate) .setCodecs(codecs) .setSampleMimeType(sampleMimeType) .setMaxInputSize(maxInputSize) .setInitializationData(initializationData) .setDrmInitData(drmInitData) .setWidth(width) .setHeight(height) .setFrameRate(frameRate) .build(); } /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createVideoSampleFormat( @Nullable String id, @Nullable String sampleMimeType, @Nullable String codecs, int bitrate, int maxInputSize, int width, int height, float frameRate, @Nullable List initializationData, int rotationDegrees, float pixelWidthHeightRatio, @Nullable DrmInitData drmInitData) { return new Builder() .setId(id) .setAverageBitrate(bitrate) .setPeakBitrate(bitrate) .setCodecs(codecs) .setSampleMimeType(sampleMimeType) .setMaxInputSize(maxInputSize) .setInitializationData(initializationData) .setDrmInitData(drmInitData) .setWidth(width) .setHeight(height) .setFrameRate(frameRate) .setRotationDegrees(rotationDegrees) .setPixelWidthHeightRatio(pixelWidthHeightRatio) .build(); } // Audio. /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createAudioSampleFormat( @Nullable String id, @Nullable String sampleMimeType, @Nullable String codecs, int bitrate, int maxInputSize, int channelCount, int sampleRate, @Nullable List initializationData, @Nullable DrmInitData drmInitData, @C.SelectionFlags int selectionFlags, @Nullable String language) { return new Builder() .setId(id) .setLanguage(language) .setSelectionFlags(selectionFlags) .setAverageBitrate(bitrate) .setPeakBitrate(bitrate) .setCodecs(codecs) .setSampleMimeType(sampleMimeType) .setMaxInputSize(maxInputSize) .setInitializationData(initializationData) .setDrmInitData(drmInitData) .setChannelCount(channelCount) .setSampleRate(sampleRate) .build(); } /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createAudioSampleFormat( @Nullable String id, @Nullable String sampleMimeType, @Nullable String codecs, int bitrate, int maxInputSize, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, @Nullable List initializationData, @Nullable DrmInitData drmInitData, @C.SelectionFlags int selectionFlags, @Nullable String language) { return new Builder() .setId(id) .setLanguage(language) .setSelectionFlags(selectionFlags) .setAverageBitrate(bitrate) .setPeakBitrate(bitrate) .setCodecs(codecs) .setSampleMimeType(sampleMimeType) .setMaxInputSize(maxInputSize) .setInitializationData(initializationData) .setDrmInitData(drmInitData) .setChannelCount(channelCount) .setSampleRate(sampleRate) .setPcmEncoding(pcmEncoding) .build(); } // Generic. /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createContainerFormat( @Nullable String id, @Nullable String label, @Nullable String containerMimeType, @Nullable String sampleMimeType, @Nullable String codecs, int bitrate, @C.SelectionFlags int selectionFlags, @C.RoleFlags int roleFlags, @Nullable String language) { return new Builder() .setId(id) .setLabel(label) .setLanguage(language) .setSelectionFlags(selectionFlags) .setRoleFlags(roleFlags) .setAverageBitrate(bitrate) .setPeakBitrate(bitrate) .setCodecs(codecs) .setContainerMimeType(containerMimeType) .setSampleMimeType(sampleMimeType) .build(); } /** @deprecated Use {@link Format.Builder}. */ @UnstableApi @Deprecated public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) { return new Builder().setId(id).setSampleMimeType(sampleMimeType).build(); } private Format(Builder builder) { id = builder.id; label = builder.label; language = Util.normalizeLanguageCode(builder.language); selectionFlags = builder.selectionFlags; roleFlags = builder.roleFlags; averageBitrate = builder.averageBitrate; peakBitrate = builder.peakBitrate; bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate; codecs = builder.codecs; metadata = builder.metadata; // Container specific. containerMimeType = builder.containerMimeType; // Sample specific. sampleMimeType = builder.sampleMimeType; maxInputSize = builder.maxInputSize; initializationData = builder.initializationData == null ? Collections.emptyList() : builder.initializationData; drmInitData = builder.drmInitData; subsampleOffsetUs = builder.subsampleOffsetUs; // Video specific. width = builder.width; height = builder.height; frameRate = builder.frameRate; rotationDegrees = builder.rotationDegrees == NO_VALUE ? 0 : builder.rotationDegrees; pixelWidthHeightRatio = builder.pixelWidthHeightRatio == NO_VALUE ? 1 : builder.pixelWidthHeightRatio; projectionData = builder.projectionData; stereoMode = builder.stereoMode; colorInfo = builder.colorInfo; // Audio specific. channelCount = builder.channelCount; sampleRate = builder.sampleRate; pcmEncoding = builder.pcmEncoding; encoderDelay = builder.encoderDelay == NO_VALUE ? 0 : builder.encoderDelay; encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding; // Text specific. accessibilityChannel = builder.accessibilityChannel; // Provided by source. if (builder.cryptoType == C.CRYPTO_TYPE_NONE && drmInitData != null) { // Encrypted content cannot use CRYPTO_TYPE_NONE. cryptoType = C.CRYPTO_TYPE_UNSUPPORTED; } else { cryptoType = builder.cryptoType; } } /** Returns a {@link Format.Builder} initialized with the values of this instance. */ @UnstableApi public Builder buildUpon() { return new Builder(this); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */ @UnstableApi @Deprecated public Format copyWithMaxInputSize(int maxInputSize) { return buildUpon().setMaxInputSize(maxInputSize).build(); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */ @UnstableApi @Deprecated public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build(); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */ @UnstableApi @Deprecated public Format copyWithLabel(@Nullable String label) { return buildUpon().setLabel(label).build(); } /** @deprecated Use {@link #withManifestFormatInfo(Format)}. */ @UnstableApi @Deprecated public Format copyWithManifestFormatInfo(Format manifestFormat) { return withManifestFormatInfo(manifestFormat); } @UnstableApi @SuppressWarnings("ReferenceEquality") public Format withManifestFormatInfo(Format manifestFormat) { if (this == manifestFormat) { // No need to copy from ourselves. return this; } @C.TrackType int trackType = MimeTypes.getTrackType(sampleMimeType); // Use manifest value only. @Nullable String id = manifestFormat.id; // Prefer manifest values, but fill in from sample format if missing. @Nullable String label = manifestFormat.label != null ? manifestFormat.label : this.label; @Nullable String language = this.language; if ((trackType == C.TRACK_TYPE_TEXT || trackType == C.TRACK_TYPE_AUDIO) && manifestFormat.language != null) { language = manifestFormat.language; } // Prefer sample format values, but fill in from manifest if missing. int averageBitrate = this.averageBitrate == NO_VALUE ? manifestFormat.averageBitrate : this.averageBitrate; int peakBitrate = this.peakBitrate == NO_VALUE ? manifestFormat.peakBitrate : this.peakBitrate; @Nullable String codecs = this.codecs; if (codecs == null) { // The manifest format may be muxed, so filter only codecs of this format's type. If we still // have more than one codec then we're unable to uniquely identify which codec to fill in. @Nullable String codecsOfType = Util.getCodecsOfType(manifestFormat.codecs, trackType); if (Util.splitCodecs(codecsOfType).length == 1) { codecs = codecsOfType; } } @Nullable Metadata metadata = this.metadata == null ? manifestFormat.metadata : this.metadata.copyWithAppendedEntriesFrom(manifestFormat.metadata); float frameRate = this.frameRate; if (frameRate == NO_VALUE && trackType == C.TRACK_TYPE_VIDEO) { frameRate = manifestFormat.frameRate; } // Merge manifest and sample format values. @C.SelectionFlags int selectionFlags = this.selectionFlags | manifestFormat.selectionFlags; @C.RoleFlags int roleFlags = this.roleFlags | manifestFormat.roleFlags; @Nullable DrmInitData drmInitData = DrmInitData.createSessionCreationData(manifestFormat.drmInitData, this.drmInitData); return buildUpon() .setId(id) .setLabel(label) .setLanguage(language) .setSelectionFlags(selectionFlags) .setRoleFlags(roleFlags) .setAverageBitrate(averageBitrate) .setPeakBitrate(peakBitrate) .setCodecs(codecs) .setMetadata(metadata) .setDrmInitData(drmInitData) .setFrameRate(frameRate) .build(); } /** * @deprecated Use {@link #buildUpon()}, {@link Builder#setEncoderDelay(int)} and {@link * Builder#setEncoderPadding(int)}. */ @UnstableApi @Deprecated public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build(); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */ @UnstableApi @Deprecated public Format copyWithFrameRate(float frameRate) { return buildUpon().setFrameRate(frameRate).build(); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */ @UnstableApi @Deprecated public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) { return buildUpon().setDrmInitData(drmInitData).build(); } /** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */ @UnstableApi @Deprecated public Format copyWithMetadata(@Nullable Metadata metadata) { return buildUpon().setMetadata(metadata).build(); } /** * @deprecated Use {@link #buildUpon()} and {@link Builder#setAverageBitrate(int)} and {@link * Builder#setPeakBitrate(int)}. */ @UnstableApi @Deprecated public Format copyWithBitrate(int bitrate) { return buildUpon().setAverageBitrate(bitrate).setPeakBitrate(bitrate).build(); } /** * @deprecated Use {@link #buildUpon()}, {@link Builder#setWidth(int)} and {@link * Builder#setHeight(int)}. */ @UnstableApi @Deprecated public Format copyWithVideoSize(int width, int height) { return buildUpon().setWidth(width).setHeight(height).build(); } /** Returns a copy of this format with the specified {@link #cryptoType}. */ @UnstableApi public Format copyWithCryptoType(@C.CryptoType int cryptoType) { return buildUpon().setCryptoType(cryptoType).build(); } /** * Returns the number of pixels if this is a video format whose {@link #width} and {@link #height} * are known, or {@link #NO_VALUE} otherwise */ @UnstableApi public int getPixelCount() { return width == NO_VALUE || height == NO_VALUE ? NO_VALUE : (width * height); } @Override public String toString() { return "Format(" + id + ", " + label + ", " + containerMimeType + ", " + sampleMimeType + ", " + codecs + ", " + bitrate + ", " + language + ", [" + width + ", " + height + ", " + frameRate + "]" + ", [" + channelCount + ", " + sampleRate + "])"; } @Override public int hashCode() { if (hashCode == 0) { // Some fields for which hashing is expensive are deliberately omitted. int result = 17; result = 31 * result + (id == null ? 0 : id.hashCode()); result = 31 * result + (label != null ? label.hashCode() : 0); result = 31 * result + (language == null ? 0 : language.hashCode()); result = 31 * result + selectionFlags; result = 31 * result + roleFlags; result = 31 * result + averageBitrate; result = 31 * result + peakBitrate; result = 31 * result + (codecs == null ? 0 : codecs.hashCode()); result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); // Container specific. result = 31 * result + (containerMimeType == null ? 0 : containerMimeType.hashCode()); // Sample specific. result = 31 * result + (sampleMimeType == null ? 0 : sampleMimeType.hashCode()); result = 31 * result + maxInputSize; // [Omitted] initializationData. // [Omitted] drmInitData. result = 31 * result + (int) subsampleOffsetUs; // Video specific. result = 31 * result + width; result = 31 * result + height; result = 31 * result + Float.floatToIntBits(frameRate); result = 31 * result + rotationDegrees; result = 31 * result + Float.floatToIntBits(pixelWidthHeightRatio); // [Omitted] projectionData. result = 31 * result + stereoMode; // [Omitted] colorInfo. // Audio specific. result = 31 * result + channelCount; result = 31 * result + sampleRate; result = 31 * result + pcmEncoding; result = 31 * result + encoderDelay; result = 31 * result + encoderPadding; // Text specific. result = 31 * result + accessibilityChannel; // Provided by the source. result = 31 * result + cryptoType; hashCode = result; } return hashCode; } @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Format other = (Format) obj; if (hashCode != 0 && other.hashCode != 0 && hashCode != other.hashCode) { return false; } // Field equality checks ordered by type, with the cheapest checks first. return selectionFlags == other.selectionFlags && roleFlags == other.roleFlags && averageBitrate == other.averageBitrate && peakBitrate == other.peakBitrate && maxInputSize == other.maxInputSize && subsampleOffsetUs == other.subsampleOffsetUs && width == other.width && height == other.height && rotationDegrees == other.rotationDegrees && stereoMode == other.stereoMode && channelCount == other.channelCount && sampleRate == other.sampleRate && pcmEncoding == other.pcmEncoding && encoderDelay == other.encoderDelay && encoderPadding == other.encoderPadding && accessibilityChannel == other.accessibilityChannel && cryptoType == other.cryptoType && Float.compare(frameRate, other.frameRate) == 0 && Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0 && Util.areEqual(id, other.id) && Util.areEqual(label, other.label) && Util.areEqual(codecs, other.codecs) && Util.areEqual(containerMimeType, other.containerMimeType) && Util.areEqual(sampleMimeType, other.sampleMimeType) && Util.areEqual(language, other.language) && Arrays.equals(projectionData, other.projectionData) && Util.areEqual(metadata, other.metadata) && Util.areEqual(colorInfo, other.colorInfo) && Util.areEqual(drmInitData, other.drmInitData) && initializationDataEquals(other); } /** * Returns whether the {@link #initializationData}s belonging to this format and {@code other} are * equal. * * @param other The other format whose {@link #initializationData} is being compared. * @return Whether the {@link #initializationData}s belonging to this format and {@code other} are * equal. */ @UnstableApi public boolean initializationDataEquals(Format other) { if (initializationData.size() != other.initializationData.size()) { return false; } for (int i = 0; i < initializationData.size(); i++) { if (!Arrays.equals(initializationData.get(i), other.initializationData.get(i))) { return false; } } return true; } // Utility methods /** Returns a prettier {@link String} than {@link #toString()}, intended for logging. */ @UnstableApi public static String toLogString(@Nullable Format format) { if (format == null) { return "null"; } StringBuilder builder = new StringBuilder(); builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType); if (format.bitrate != NO_VALUE) { builder.append(", bitrate=").append(format.bitrate); } if (format.codecs != null) { builder.append(", codecs=").append(format.codecs); } if (format.drmInitData != null) { Set schemes = new LinkedHashSet<>(); for (int i = 0; i < format.drmInitData.schemeDataCount; i++) { UUID schemeUuid = format.drmInitData.get(i).uuid; if (schemeUuid.equals(C.COMMON_PSSH_UUID)) { schemes.add("cenc"); } else if (schemeUuid.equals(C.CLEARKEY_UUID)) { schemes.add("clearkey"); } else if (schemeUuid.equals(C.PLAYREADY_UUID)) { schemes.add("playready"); } else if (schemeUuid.equals(C.WIDEVINE_UUID)) { schemes.add("widevine"); } else if (schemeUuid.equals(C.UUID_NIL)) { schemes.add("universal"); } else { schemes.add("unknown (" + schemeUuid + ")"); } } builder.append(", drm=["); Joiner.on(',').appendTo(builder, schemes); builder.append(']'); } if (format.width != NO_VALUE && format.height != NO_VALUE) { builder.append(", res=").append(format.width).append("x").append(format.height); } if (format.frameRate != NO_VALUE) { builder.append(", fps=").append(format.frameRate); } if (format.channelCount != NO_VALUE) { builder.append(", channels=").append(format.channelCount); } if (format.sampleRate != NO_VALUE) { builder.append(", sample_rate=").append(format.sampleRate); } if (format.language != null) { builder.append(", language=").append(format.language); } if (format.label != null) { builder.append(", label=").append(format.label); } if (format.selectionFlags != 0) { List selectionFlags = new ArrayList<>(); // LINT.IfChange(selection_flags) if ((format.selectionFlags & C.SELECTION_FLAG_AUTOSELECT) != 0) { selectionFlags.add("auto"); } if ((format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0) { selectionFlags.add("default"); } if ((format.selectionFlags & C.SELECTION_FLAG_FORCED) != 0) { selectionFlags.add("forced"); } builder.append(", selectionFlags=["); Joiner.on(',').appendTo(builder, selectionFlags); builder.append("]"); } if (format.roleFlags != 0) { // LINT.IfChange(role_flags) List roleFlags = new ArrayList<>(); if ((format.roleFlags & C.ROLE_FLAG_MAIN) != 0) { roleFlags.add("main"); } if ((format.roleFlags & C.ROLE_FLAG_ALTERNATE) != 0) { roleFlags.add("alt"); } if ((format.roleFlags & C.ROLE_FLAG_SUPPLEMENTARY) != 0) { roleFlags.add("supplementary"); } if ((format.roleFlags & C.ROLE_FLAG_COMMENTARY) != 0) { roleFlags.add("commentary"); } if ((format.roleFlags & C.ROLE_FLAG_DUB) != 0) { roleFlags.add("dub"); } if ((format.roleFlags & C.ROLE_FLAG_EMERGENCY) != 0) { roleFlags.add("emergency"); } if ((format.roleFlags & C.ROLE_FLAG_CAPTION) != 0) { roleFlags.add("caption"); } if ((format.roleFlags & C.ROLE_FLAG_SUBTITLE) != 0) { roleFlags.add("subtitle"); } if ((format.roleFlags & C.ROLE_FLAG_SIGN) != 0) { roleFlags.add("sign"); } if ((format.roleFlags & C.ROLE_FLAG_DESCRIBES_VIDEO) != 0) { roleFlags.add("describes-video"); } if ((format.roleFlags & C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND) != 0) { roleFlags.add("describes-music"); } if ((format.roleFlags & C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY) != 0) { roleFlags.add("enhanced-intelligibility"); } if ((format.roleFlags & C.ROLE_FLAG_TRANSCRIBES_DIALOG) != 0) { roleFlags.add("transcribes-dialog"); } if ((format.roleFlags & C.ROLE_FLAG_EASY_TO_READ) != 0) { roleFlags.add("easy-read"); } if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) { roleFlags.add("trick-play"); } builder.append(", roleFlags=["); Joiner.on(',').appendTo(builder, roleFlags); builder.append("]"); } return builder.toString(); } // Bundleable implementation. @Documented @Retention(RetentionPolicy.SOURCE) @Target(TYPE_USE) @IntDef({ FIELD_ID, FIELD_LABEL, FIELD_LANGUAGE, FIELD_SELECTION_FLAGS, FIELD_ROLE_FLAGS, FIELD_AVERAGE_BITRATE, FIELD_PEAK_BITRATE, FIELD_CODECS, FIELD_METADATA, FIELD_CONTAINER_MIME_TYPE, FIELD_SAMPLE_MIME_TYPE, FIELD_MAX_INPUT_SIZE, FIELD_INITIALIZATION_DATA, FIELD_DRM_INIT_DATA, FIELD_SUBSAMPLE_OFFSET_US, FIELD_WIDTH, FIELD_HEIGHT, FIELD_FRAME_RATE, FIELD_ROTATION_DEGREES, FIELD_PIXEL_WIDTH_HEIGHT_RATIO, FIELD_PROJECTION_DATA, FIELD_STEREO_MODE, FIELD_COLOR_INFO, FIELD_CHANNEL_COUNT, FIELD_SAMPLE_RATE, FIELD_PCM_ENCODING, FIELD_ENCODER_DELAY, FIELD_ENCODER_PADDING, FIELD_ACCESSIBILITY_CHANNEL, FIELD_CRYPTO_TYPE, }) private @interface FieldNumber {} private static final int FIELD_ID = 0; private static final int FIELD_LABEL = 1; private static final int FIELD_LANGUAGE = 2; private static final int FIELD_SELECTION_FLAGS = 3; private static final int FIELD_ROLE_FLAGS = 4; private static final int FIELD_AVERAGE_BITRATE = 5; private static final int FIELD_PEAK_BITRATE = 6; private static final int FIELD_CODECS = 7; private static final int FIELD_METADATA = 8; private static final int FIELD_CONTAINER_MIME_TYPE = 9; private static final int FIELD_SAMPLE_MIME_TYPE = 10; private static final int FIELD_MAX_INPUT_SIZE = 11; private static final int FIELD_INITIALIZATION_DATA = 12; private static final int FIELD_DRM_INIT_DATA = 13; private static final int FIELD_SUBSAMPLE_OFFSET_US = 14; private static final int FIELD_WIDTH = 15; private static final int FIELD_HEIGHT = 16; private static final int FIELD_FRAME_RATE = 17; private static final int FIELD_ROTATION_DEGREES = 18; private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 19; private static final int FIELD_PROJECTION_DATA = 20; private static final int FIELD_STEREO_MODE = 21; private static final int FIELD_COLOR_INFO = 22; private static final int FIELD_CHANNEL_COUNT = 23; private static final int FIELD_SAMPLE_RATE = 24; private static final int FIELD_PCM_ENCODING = 25; private static final int FIELD_ENCODER_DELAY = 26; private static final int FIELD_ENCODER_PADDING = 27; private static final int FIELD_ACCESSIBILITY_CHANNEL = 28; private static final int FIELD_CRYPTO_TYPE = 29; @UnstableApi @Override public Bundle toBundle() { Bundle bundle = new Bundle(); bundle.putString(keyForField(FIELD_ID), id); bundle.putString(keyForField(FIELD_LABEL), label); bundle.putString(keyForField(FIELD_LANGUAGE), language); bundle.putInt(keyForField(FIELD_SELECTION_FLAGS), selectionFlags); bundle.putInt(keyForField(FIELD_ROLE_FLAGS), roleFlags); bundle.putInt(keyForField(FIELD_AVERAGE_BITRATE), averageBitrate); bundle.putInt(keyForField(FIELD_PEAK_BITRATE), peakBitrate); bundle.putString(keyForField(FIELD_CODECS), codecs); // Metadata is currently not Bundleable because Metadata.Entry is an Interface, // which would be difficult to unbundle in a backward compatible way. // The entries are additionally of limited usefulness to remote processes. bundle.putParcelable(keyForField(FIELD_METADATA), metadata); // Container specific. bundle.putString(keyForField(FIELD_CONTAINER_MIME_TYPE), containerMimeType); // Sample specific. bundle.putString(keyForField(FIELD_SAMPLE_MIME_TYPE), sampleMimeType); bundle.putInt(keyForField(FIELD_MAX_INPUT_SIZE), maxInputSize); for (int i = 0; i < initializationData.size(); i++) { bundle.putByteArray(keyForInitializationData(i), initializationData.get(i)); } // DrmInitData doesn't need to be Bundleable as it's only used in the playing process to // initialize the decoder. bundle.putParcelable(keyForField(FIELD_DRM_INIT_DATA), drmInitData); bundle.putLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), subsampleOffsetUs); // Video specific. bundle.putInt(keyForField(FIELD_WIDTH), width); bundle.putInt(keyForField(FIELD_HEIGHT), height); bundle.putFloat(keyForField(FIELD_FRAME_RATE), frameRate); bundle.putInt(keyForField(FIELD_ROTATION_DEGREES), rotationDegrees); bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio); bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData); bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode); bundle.putBundle(keyForField(FIELD_COLOR_INFO), BundleableUtil.toNullableBundle(colorInfo)); // Audio specific. bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount); bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate); bundle.putInt(keyForField(FIELD_PCM_ENCODING), pcmEncoding); bundle.putInt(keyForField(FIELD_ENCODER_DELAY), encoderDelay); bundle.putInt(keyForField(FIELD_ENCODER_PADDING), encoderPadding); // Text specific. bundle.putInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), accessibilityChannel); // Source specific. bundle.putInt(keyForField(FIELD_CRYPTO_TYPE), cryptoType); return bundle; } /** Object that can restore {@code Format} from a {@link Bundle}. */ @UnstableApi public static final Creator CREATOR = Format::fromBundle; private static Format fromBundle(Bundle bundle) { Builder builder = new Builder(); BundleableUtil.ensureClassLoader(bundle); builder .setId(defaultIfNull(bundle.getString(keyForField(FIELD_ID)), DEFAULT.id)) .setLabel(defaultIfNull(bundle.getString(keyForField(FIELD_LABEL)), DEFAULT.label)) .setLanguage(defaultIfNull(bundle.getString(keyForField(FIELD_LANGUAGE)), DEFAULT.language)) .setSelectionFlags( bundle.getInt(keyForField(FIELD_SELECTION_FLAGS), DEFAULT.selectionFlags)) .setRoleFlags(bundle.getInt(keyForField(FIELD_ROLE_FLAGS), DEFAULT.roleFlags)) .setAverageBitrate( bundle.getInt(keyForField(FIELD_AVERAGE_BITRATE), DEFAULT.averageBitrate)) .setPeakBitrate(bundle.getInt(keyForField(FIELD_PEAK_BITRATE), DEFAULT.peakBitrate)) .setCodecs(defaultIfNull(bundle.getString(keyForField(FIELD_CODECS)), DEFAULT.codecs)) .setMetadata( defaultIfNull(bundle.getParcelable(keyForField(FIELD_METADATA)), DEFAULT.metadata)) // Container specific. .setContainerMimeType( defaultIfNull( bundle.getString(keyForField(FIELD_CONTAINER_MIME_TYPE)), DEFAULT.containerMimeType)) // Sample specific. .setSampleMimeType( defaultIfNull( bundle.getString(keyForField(FIELD_SAMPLE_MIME_TYPE)), DEFAULT.sampleMimeType)) .setMaxInputSize(bundle.getInt(keyForField(FIELD_MAX_INPUT_SIZE), DEFAULT.maxInputSize)); List initializationData = new ArrayList<>(); for (int i = 0; ; i++) { @Nullable byte[] data = bundle.getByteArray(keyForInitializationData(i)); if (data == null) { break; } initializationData.add(data); } builder .setInitializationData(initializationData) .setDrmInitData(bundle.getParcelable(keyForField(FIELD_DRM_INIT_DATA))) .setSubsampleOffsetUs( bundle.getLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), DEFAULT.subsampleOffsetUs)) // Video specific. .setWidth(bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT.width)) .setHeight(bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT.height)) .setFrameRate(bundle.getFloat(keyForField(FIELD_FRAME_RATE), DEFAULT.frameRate)) .setRotationDegrees( bundle.getInt(keyForField(FIELD_ROTATION_DEGREES), DEFAULT.rotationDegrees)) .setPixelWidthHeightRatio( bundle.getFloat( keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio)) .setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA))) .setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode)) .setColorInfo( BundleableUtil.fromNullableBundle( ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO)))) // Audio specific. .setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount)) .setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate)) .setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding)) .setEncoderDelay(bundle.getInt(keyForField(FIELD_ENCODER_DELAY), DEFAULT.encoderDelay)) .setEncoderPadding( bundle.getInt(keyForField(FIELD_ENCODER_PADDING), DEFAULT.encoderPadding)) // Text specific. .setAccessibilityChannel( bundle.getInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), DEFAULT.accessibilityChannel)) // Source specific. .setCryptoType(bundle.getInt(keyForField(FIELD_CRYPTO_TYPE), DEFAULT.cryptoType)); return builder.build(); } private static String keyForField(@FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); } private static String keyForInitializationData(int initialisationDataIndex) { return keyForField(FIELD_INITIALIZATION_DATA) + "_" + Integer.toString(initialisationDataIndex, Character.MAX_RADIX); } @Nullable private static T defaultIfNull(@Nullable T value, @Nullable T defaultValue) { return value != null ? value : defaultValue; } }