/* * Copyright (C) 2014 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 com.google.android.exoplayer; import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Util; import android.annotation.SuppressLint; import android.annotation.TargetApi; import java.nio.ByteBuffer; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * Defines the format of an elementary media stream. */ public final class MediaFormat { 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. */ public static final long OFFSET_SAMPLE_RELATIVE = Long.MAX_VALUE; /** * The mime type of the format. */ public final String mimeType; /** * The average bandwidth in bits per second, or {@link #NO_VALUE} if unknown or not applicable. */ public final int bitrate; /** * The maximum size of a buffer of data (typically one sample) in the format, or {@link #NO_VALUE} * if unknown or not applicable. */ public final int maxInputSize; /** * The duration in microseconds, or {@link C#UNKNOWN_TIME_US} if the duration is unknown, or * {@link C#MATCH_LONGEST_US} if the duration should match the duration of the longest track whose * duration is known. */ public final long durationUs; /** * Initialization data that must be provided to the decoder. Will not be null, but may be empty * if initialization data is not required. */ public final List<byte[]> initializationData; /** * Whether the format represents an adaptive track, meaning that the format of the actual media * data may change (e.g. to adapt to network conditions). */ public final boolean adaptive; // 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; /** * For formats that belong to an adaptive video track (either describing the track, or describing * a specific format within it), this is the maximum width of the video in pixels that will be * encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable. */ public final int maxWidth; /** * For formats that belong to an adaptive video track (either describing the track, or describing * a specific format within it), this is the maximum height of the video in pixels that will be * encountered in the stream. Set to {@link #NO_VALUE} if unknown or not applicable. */ public final int maxHeight; /** * The clockwise rotation that should be applied to the video for it to be rendered in the correct * orientation, or {@link #NO_VALUE} if unknown or not applicable. Only 0, 90, 180 and 270 are * supported. */ public final int rotationDegrees; /** * The width to height ratio of pixels in the video, or {@link #NO_VALUE} if unknown or not * applicable. */ public final float pixelWidthHeightRatio; // 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; // Text specific. /** * The language of the track, or null if unknown or not applicable. */ public final String language; /** * 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. */ public final long subsampleOffsetUs; // Lazy-initialized hashcode and framework media format. private int hashCode; private android.media.MediaFormat frameworkMediaFormat; public static MediaFormat createVideoFormat(String mimeType, int bitrate, int maxInputSize, int width, int height, List<byte[]> initializationData) { return createVideoFormat(mimeType, bitrate, maxInputSize, C.UNKNOWN_TIME_US, width, height, NO_VALUE, initializationData); } public static MediaFormat createVideoFormat(String mimeType, int bitrate, int maxInputSize, long durationUs, int width, int height, int rotationDegrees, List<byte[]> initializationData) { return createVideoFormat(mimeType, bitrate, maxInputSize, durationUs, width, height, rotationDegrees, NO_VALUE, initializationData); } public static MediaFormat createVideoFormat(String mimeType, int bitrate, int maxInputSize, long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio, List<byte[]> initializationData) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, width, height, rotationDegrees, pixelWidthHeightRatio, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, initializationData, false, NO_VALUE, NO_VALUE); } public static MediaFormat createAudioFormat(String mimeType, int bitrate, int maxInputSize, int channelCount, int sampleRate, List<byte[]> initializationData) { return createAudioFormat(mimeType, bitrate, maxInputSize, C.UNKNOWN_TIME_US, channelCount, sampleRate, initializationData); } public static MediaFormat createAudioFormat(String mimeType, int bitrate, int maxInputSize, long durationUs, int channelCount, int sampleRate, List<byte[]> initializationData) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, channelCount, sampleRate, null, OFFSET_SAMPLE_RELATIVE, initializationData, false, NO_VALUE, NO_VALUE); } public static MediaFormat createTextFormat(String mimeType, int bitrate, String language) { return createTextFormat(mimeType, bitrate, language, C.UNKNOWN_TIME_US); } public static MediaFormat createTextFormat(String mimeType, int bitrate, String language, long durationUs) { return createTextFormat(mimeType, bitrate, language, durationUs, OFFSET_SAMPLE_RELATIVE); } public static MediaFormat createTextFormat(String mimeType, int bitrate, String language, long durationUs, long subsampleOffsetUs) { return new MediaFormat(mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, language, subsampleOffsetUs, null, false, NO_VALUE, NO_VALUE); } public static MediaFormat createFormatForMimeType(String mimeType, int bitrate) { return createFormatForMimeType(mimeType, bitrate, C.UNKNOWN_TIME_US); } public static MediaFormat createFormatForMimeType(String mimeType, int bitrate, long durationUs) { return new MediaFormat(mimeType, bitrate, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, false, NO_VALUE, NO_VALUE); } /* package */ MediaFormat(String mimeType, int bitrate, int maxInputSize, long durationUs, int width, int height, int rotationDegrees, float pixelWidthHeightRatio, int channelCount, int sampleRate, String language, long subsampleOffsetUs, List<byte[]> initializationData, boolean adaptive, int maxWidth, int maxHeight) { this.mimeType = Assertions.checkNotEmpty(mimeType); this.bitrate = bitrate; this.maxInputSize = maxInputSize; this.durationUs = durationUs; this.width = width; this.height = height; this.rotationDegrees = rotationDegrees; this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.channelCount = channelCount; this.sampleRate = sampleRate; this.language = language; this.subsampleOffsetUs = subsampleOffsetUs; this.initializationData = initializationData == null ? Collections.<byte[]>emptyList() : initializationData; this.adaptive = adaptive; this.maxWidth = maxWidth; this.maxHeight = maxHeight; } public MediaFormat copyWithMaxVideoDimensions(int maxWidth, int maxHeight) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, width, height, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight); } public MediaFormat copyWithSubsampleOffsetUs(long subsampleOffsetUs) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, width, height, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight); } public MediaFormat copyWithDurationUs(long durationUs) { return new MediaFormat(mimeType, bitrate, maxInputSize, durationUs, width, height, rotationDegrees, pixelWidthHeightRatio, channelCount, sampleRate, language, subsampleOffsetUs, initializationData, adaptive, maxWidth, maxHeight); } public MediaFormat copyAsAdaptive() { return new MediaFormat(mimeType, NO_VALUE, NO_VALUE, durationUs, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, OFFSET_SAMPLE_RELATIVE, null, true, maxWidth, maxHeight); } /** * @return A {@link MediaFormat} representation of this format. */ @SuppressLint("InlinedApi") @TargetApi(16) public final android.media.MediaFormat getFrameworkMediaFormatV16() { if (frameworkMediaFormat == null) { android.media.MediaFormat format = new android.media.MediaFormat(); format.setString(android.media.MediaFormat.KEY_MIME, mimeType); maybeSetStringV16(format, android.media.MediaFormat.KEY_LANGUAGE, language); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_WIDTH, width); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_HEIGHT, height); maybeSetIntegerV16(format, "rotation-degrees", rotationDegrees); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_WIDTH, maxWidth); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_MAX_HEIGHT, maxHeight); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_CHANNEL_COUNT, channelCount); maybeSetIntegerV16(format, android.media.MediaFormat.KEY_SAMPLE_RATE, sampleRate); for (int i = 0; i < initializationData.size(); i++) { format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); } if (durationUs != C.UNKNOWN_TIME_US) { format.setLong(android.media.MediaFormat.KEY_DURATION, durationUs); } frameworkMediaFormat = format; } return frameworkMediaFormat; } /** * Sets the framework format returned by {@link #getFrameworkMediaFormatV16()}. * * @deprecated This method only exists for FrameworkSampleSource, which is itself deprecated. * @param format The framework format. */ @Deprecated @TargetApi(16) /* package */ final void setFrameworkFormatV16(android.media.MediaFormat format) { frameworkMediaFormat = format; } @Override public String toString() { return "MediaFormat(" + mimeType + ", " + bitrate + ", " + maxInputSize + ", " + width + ", " + height + ", " + rotationDegrees + ", " + pixelWidthHeightRatio + ", " + channelCount + ", " + sampleRate + ", " + language + ", " + durationUs + ", " + adaptive + ", " + maxWidth + ", " + maxHeight + ")"; } @Override public int hashCode() { if (hashCode == 0) { int result = 17; result = 31 * result + (mimeType == null ? 0 : mimeType.hashCode()); result = 31 * result + bitrate; result = 31 * result + maxInputSize; result = 31 * result + width; result = 31 * result + height; result = 31 * result + rotationDegrees; result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio); result = 31 * result + (int) durationUs; result = 31 * result + (adaptive ? 1231 : 1237); result = 31 * result + maxWidth; result = 31 * result + maxHeight; result = 31 * result + channelCount; result = 31 * result + sampleRate; result = 31 * result + (language == null ? 0 : language.hashCode()); for (int i = 0; i < initializationData.size(); i++) { result = 31 * result + Arrays.hashCode(initializationData.get(i)); } hashCode = result; } return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } MediaFormat other = (MediaFormat) obj; if (adaptive != other.adaptive || bitrate != other.bitrate || maxInputSize != other.maxInputSize || width != other.width || height != other.height || rotationDegrees != other.rotationDegrees || pixelWidthHeightRatio != other.pixelWidthHeightRatio || maxWidth != other.maxWidth || maxHeight != other.maxHeight || channelCount != other.channelCount || sampleRate != other.sampleRate || !Util.areEqual(language, other.language) || !Util.areEqual(mimeType, other.mimeType) || 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; } @TargetApi(16) private static final void maybeSetStringV16(android.media.MediaFormat format, String key, String value) { if (value != null) { format.setString(key, value); } } @TargetApi(16) private static final void maybeSetIntegerV16(android.media.MediaFormat format, String key, int value) { if (value != NO_VALUE) { format.setInteger(key, value); } } }