Add method to create a MediaFormat based on an ExoPlayer Format

PiperOrigin-RevId: 356157035
This commit is contained in:
aquilescanta 2021-02-07 22:46:42 +00:00 committed by Oliver Woodman
parent 01b6061bd2
commit 0b8b03e46d
2 changed files with 284 additions and 5 deletions

View File

@ -15,27 +15,94 @@
*/ */
package com.google.android.exoplayer2.mediacodec; package com.google.android.exoplayer2.mediacodec;
import android.annotation.SuppressLint;
import android.media.AudioFormat;
import android.media.MediaFormat; import android.media.MediaFormat;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.video.ColorInfo; import com.google.android.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
/** Helper class for configuring {@link MediaFormat} instances. */ /** Helper class containing utility methods for managing {@link MediaFormat} instances. */
public final class MediaFormatUtil { public final class MediaFormatUtil {
private MediaFormatUtil() {} /**
* Custom {@link MediaFormat} key associated with a float representing the ratio between a pixel's
* width and height.
*/
public static final String KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT =
"exo-pixel-width-height-ratio-float";
/** /**
* Sets a {@link MediaFormat} {@link String} value. * Custom {@link MediaFormat} key associated with an integer representing the PCM encoding.
*
* <p>Equivalent to {@link MediaFormat#KEY_PCM_ENCODING}, except it allows additional
* ExoPlayer-specific values including {@link C#ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link
* C#ENCODING_PCM_24BIT}, and {@link C#ENCODING_PCM_32BIT}.
*/
public static final String KEY_EXO_PCM_ENCODING = "exo-pcm-encoding-int";
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
/**
* Returns a {@link MediaFormat} representing the given ExoPlayer {@link Format}.
*
* <p>May include the following custom keys:
*
* <ul>
* <li>{@link #KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT}.
* <li>{@link #KEY_EXO_PCM_ENCODING}.
* </ul>
*/
@SuppressLint("InlinedApi") // Inlined MediaFormat keys.
public static MediaFormat createMediaFormatFromFormat(Format format) {
MediaFormat result = new MediaFormat();
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
maybeSetColorInfo(result, format.colorInfo);
maybeSetString(result, MediaFormat.KEY_MIME, format.sampleMimeType);
maybeSetString(result, MediaFormat.KEY_CODECS_STRING, format.codecs);
maybeSetFloat(result, MediaFormat.KEY_FRAME_RATE, format.frameRate);
maybeSetInteger(result, MediaFormat.KEY_WIDTH, format.width);
maybeSetInteger(result, MediaFormat.KEY_HEIGHT, format.height);
setCsdBuffers(result, format.initializationData);
maybeSetPcmEncoding(result, format.pcmEncoding);
maybeSetString(result, MediaFormat.KEY_LANGUAGE, format.language);
maybeSetInteger(result, MediaFormat.KEY_MAX_INPUT_SIZE, format.maxInputSize);
maybeSetInteger(result, MediaFormat.KEY_SAMPLE_RATE, format.sampleRate);
maybeSetInteger(result, MediaFormat.KEY_CAPTION_SERVICE_NUMBER, format.accessibilityChannel);
result.setInteger(MediaFormat.KEY_ROTATION, format.rotationDegrees);
int selectionFlags = format.selectionFlags;
setBooleanAsInt(
result, MediaFormat.KEY_IS_AUTOSELECT, selectionFlags & C.SELECTION_FLAG_AUTOSELECT);
setBooleanAsInt(result, MediaFormat.KEY_IS_DEFAULT, selectionFlags & C.SELECTION_FLAG_DEFAULT);
setBooleanAsInt(
result, MediaFormat.KEY_IS_FORCED_SUBTITLE, selectionFlags & C.SELECTION_FLAG_FORCED);
result.setInteger(MediaFormat.KEY_ENCODER_DELAY, format.encoderDelay);
result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
maybeSetPixelAspectRatio(result, format.pixelWidthHeightRatio);
return result;
}
/**
* Sets a {@link MediaFormat} {@link String} value. Does nothing if {@code value} is null.
* *
* @param format The {@link MediaFormat} being configured. * @param format The {@link MediaFormat} being configured.
* @param key The key to set. * @param key The key to set.
* @param value The value to set. * @param value The value to set.
*/ */
public static void setString(MediaFormat format, String key, String value) { public static void maybeSetString(MediaFormat format, String key, @Nullable String value) {
format.setString(key, value); if (value != null) {
format.setString(key, value);
}
} }
/** /**
@ -106,4 +173,57 @@ public final class MediaFormatUtil {
maybeSetByteBuffer(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo); maybeSetByteBuffer(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo);
} }
} }
// Internal methods.
private static void setBooleanAsInt(MediaFormat format, String key, int value) {
format.setInteger(key, value != 0 ? 1 : 0);
}
// Inlined MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH and MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT.
@SuppressLint("InlinedApi")
private static void maybeSetPixelAspectRatio(
MediaFormat mediaFormat, float pixelWidthHeightRatio) {
mediaFormat.setFloat(KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT, pixelWidthHeightRatio);
int pixelAspectRatioWidth = 1;
int pixelAspectRatioHeight = 1;
// ExoPlayer extractors output the pixel aspect ratio as a float. Do our best to recreate the
// pixel aspect ratio width and height by using a large power of two factor.
if (pixelWidthHeightRatio < 1.0f) {
pixelAspectRatioHeight = MAX_POWER_OF_TWO_INT;
pixelAspectRatioWidth = (int) (pixelWidthHeightRatio * pixelAspectRatioHeight);
} else if (pixelWidthHeightRatio > 1.0f) {
pixelAspectRatioWidth = MAX_POWER_OF_TWO_INT;
pixelAspectRatioHeight = (int) (pixelAspectRatioWidth / pixelWidthHeightRatio);
}
mediaFormat.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH, pixelAspectRatioWidth);
mediaFormat.setInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT, pixelAspectRatioHeight);
}
@SuppressLint("InlinedApi") // Inlined KEY_PCM_ENCODING.
private static void maybeSetPcmEncoding(
MediaFormat mediaFormat, @C.PcmEncoding int exoPcmEncoding) {
if (exoPcmEncoding == Format.NO_VALUE) {
return;
}
int mediaFormatPcmEncoding;
maybeSetInteger(mediaFormat, KEY_EXO_PCM_ENCODING, exoPcmEncoding);
switch (exoPcmEncoding) {
case C.ENCODING_PCM_8BIT:
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_8BIT;
break;
case C.ENCODING_PCM_16BIT:
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_16BIT;
break;
case C.ENCODING_PCM_FLOAT:
mediaFormatPcmEncoding = AudioFormat.ENCODING_PCM_FLOAT;
break;
default:
// No matching value. Do nothing.
return;
}
mediaFormat.setInteger(MediaFormat.KEY_PCM_ENCODING, mediaFormatPcmEncoding);
}
private MediaFormatUtil() {}
} }

View File

@ -0,0 +1,159 @@
/*
* Copyright (C) 2021 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.exoplayer2.mediacodec;
import static com.google.common.truth.Truth.assertThat;
import android.media.MediaFormat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
/** Unit tests for {@link MediaFormatUtil}. */
@RunWith(AndroidJUnit4.class)
@Config(sdk = 29) // Allows using MediaFormat.getKeys() to make assertions over the expected keys.
public class MediaFormatUtilTest {
@Test
public void createMediaFormatFromEmptyExoPlayerFormat_generatesExpectedEntries() {
MediaFormat mediaFormat =
MediaFormatUtil.createMediaFormatFromFormat(new Format.Builder().build());
// Assert that no invalid keys are accidentally being populated.
assertThat(mediaFormat.getKeys())
.containsExactly(
MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT,
MediaFormat.KEY_ENCODER_DELAY,
MediaFormat.KEY_ENCODER_PADDING,
MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH,
MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT,
MediaFormat.KEY_IS_DEFAULT,
MediaFormat.KEY_IS_FORCED_SUBTITLE,
MediaFormat.KEY_IS_AUTOSELECT,
MediaFormat.KEY_ROTATION);
assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT))
.isEqualTo(1.f);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY)).isEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING)).isEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)).isEqualTo(1);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)).isEqualTo(1);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)).isEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)).isEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)).isEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ROTATION)).isEqualTo(0);
}
@Test
public void createMediaFormatFromPopulatedExoPlayerFormat_generatesExpectedMediaFormatEntries() {
Format format =
new Format.Builder()
.setAverageBitrate(1)
.setChannelCount(2)
.setColorInfo(
new ColorInfo(
/* colorSpace= */ C.COLOR_SPACE_BT601,
/* colorRange= */ C.COLOR_RANGE_FULL,
/* colorTransfer= */ C.COLOR_TRANSFER_HLG,
new byte[] {3}))
.setSampleMimeType(MimeTypes.VIDEO_H264)
.setCodecs("avc.123")
.setFrameRate(4)
.setWidth(5)
.setHeight(6)
.setInitializationData(ImmutableList.of(new byte[] {7}, new byte[] {8}))
.setPcmEncoding(C.ENCODING_PCM_8BIT)
.setLanguage("en")
.setMaxInputSize(9)
.setRotationDegrees(10)
.setSampleRate(11)
.setAccessibilityChannel(12)
.setSelectionFlags(
C.SELECTION_FLAG_AUTOSELECT | C.SELECTION_FLAG_DEFAULT | C.SELECTION_FLAG_FORCED)
.setEncoderDelay(13)
.setEncoderPadding(14)
.setPixelWidthHeightRatio(.5f)
.build();
MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE)).isEqualTo(format.bitrate);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT))
.isEqualTo(format.channelCount);
ColorInfo colorInfo = Assertions.checkNotNull(format.colorInfo);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER))
.isEqualTo(colorInfo.colorTransfer);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE)).isEqualTo(colorInfo.colorRange);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD))
.isEqualTo(colorInfo.colorSpace);
assertThat(mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO).array())
.isEqualTo(colorInfo.hdrStaticInfo);
assertThat(mediaFormat.getString(MediaFormat.KEY_MIME)).isEqualTo(format.sampleMimeType);
assertThat(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING)).isEqualTo(format.codecs);
assertThat(mediaFormat.getFloat(MediaFormat.KEY_FRAME_RATE)).isEqualTo(format.frameRate);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_WIDTH)).isEqualTo(format.width);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_HEIGHT)).isEqualTo(format.height);
assertThat(mediaFormat.getByteBuffer("csd-0").array())
.isEqualTo(format.initializationData.get(0));
assertThat(mediaFormat.getByteBuffer("csd-1").array())
.isEqualTo(format.initializationData.get(1));
assertThat(mediaFormat.getInteger(MediaFormat.KEY_PCM_ENCODING)).isEqualTo(format.pcmEncoding);
assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING))
.isEqualTo(format.pcmEncoding);
assertThat(mediaFormat.getString(MediaFormat.KEY_LANGUAGE)).isEqualTo(format.language);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE))
.isEqualTo(format.maxInputSize);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ROTATION)).isEqualTo(format.rotationDegrees);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE)).isEqualTo(format.sampleRate);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_CAPTION_SERVICE_NUMBER))
.isEqualTo(format.accessibilityChannel);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT)).isNotEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_DEFAULT)).isNotEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_IS_FORCED_SUBTITLE)).isNotEqualTo(0);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_DELAY))
.isEqualTo(format.encoderDelay);
assertThat(mediaFormat.getInteger(MediaFormat.KEY_ENCODER_PADDING))
.isEqualTo(format.encoderPadding);
float calculatedPixelAspectRatio =
(float) mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)
/ mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT);
assertThat(calculatedPixelAspectRatio).isWithin(.0001f).of(format.pixelWidthHeightRatio);
assertThat(mediaFormat.getFloat(MediaFormatUtil.KEY_EXO_PIXEL_WIDTH_HEIGHT_RATIO_FLOAT))
.isEqualTo(format.pixelWidthHeightRatio);
}
@Test
public void createMediaFormatWithExoPlayerPcmEncoding_containsExoPlayerSpecificEncoding() {
Format format = new Format.Builder().setPcmEncoding(C.ENCODING_PCM_32BIT).build();
MediaFormat mediaFormat = MediaFormatUtil.createMediaFormatFromFormat(format);
assertThat(mediaFormat.getInteger(MediaFormatUtil.KEY_EXO_PCM_ENCODING))
.isEqualTo(C.ENCODING_PCM_32BIT);
assertThat(mediaFormat.containsKey(MediaFormat.KEY_PCM_ENCODING)).isFalse();
}
}