Add method to create a MediaFormat based on an ExoPlayer Format
PiperOrigin-RevId: 356157035
This commit is contained in:
parent
01b6061bd2
commit
0b8b03e46d
@ -15,28 +15,95 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.mediacodec;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.media.AudioFormat;
|
||||
import android.media.MediaFormat;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.video.ColorInfo;
|
||||
import java.nio.ByteBuffer;
|
||||
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 {
|
||||
|
||||
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 key The key 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) {
|
||||
if (value != null) {
|
||||
format.setString(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a {@link MediaFormat}'s codec specific data buffers.
|
||||
@ -106,4 +173,57 @@ public final class MediaFormatUtil {
|
||||
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() {}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user