mirror of
https://github.com/androidx/media.git
synced 2025-05-08 16:10:38 +08:00
Deduce encoder video format from decoder format.
When no encoder video MIME type is specified, the `TransformerTranscodingVideoRenderer` now uses the video MIME type of the input for the encoder format. The input format is now read in a new method `ensureInputFormatRead` which is called before the other configuration methods. This removes the logic for reading the input format from `ensureDecoderConfigured`, because it is now needed for both encoder and decoder configuration but the encoder needs to be configured before GL and GL needs to be configured before the decoder, so the decoder can't read the format. The width and height are now inferred from the input and the frame rate and bit rate are still hard-coded but set by the `MediaCodecAdapterWrapper` instead of `TranscodingTransformer`. PiperOrigin-RevId: 405631263
This commit is contained in:
parent
68729ecd49
commit
649fe702f2
@ -19,7 +19,6 @@ package com.google.android.exoplayer2.transformer;
|
|||||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
import static java.lang.Math.ceil;
|
|
||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
@ -202,9 +201,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
* MediaCodecAdapter} video encoder.
|
* MediaCodecAdapter} video encoder.
|
||||||
*
|
*
|
||||||
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
* @param format The {@link Format} (of the output data) used to determine the underlying {@link
|
||||||
* MediaCodec} and its configuration values. {@link Format#width}, {@link Format#height},
|
* MediaCodec} and its configuration values. {@link Format#sampleMimeType}, {@link
|
||||||
* {@link Format#frameRate} and {@link Format#averageBitrate} must be set to those of the
|
* Format#width} and {@link Format#height} must be set to those of the desired output video
|
||||||
* desired output video format.
|
* format.
|
||||||
* @param additionalEncoderConfig A map of {@link MediaFormat}'s integer settings, where the keys
|
* @param additionalEncoderConfig A map of {@link MediaFormat}'s integer settings, where the keys
|
||||||
* are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code
|
* are from {@code MediaFormat.KEY_*} constants. Its values will override those in {@code
|
||||||
* format}.
|
* format}.
|
||||||
@ -215,8 +214,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
Format format, Map<String, Integer> additionalEncoderConfig) throws IOException {
|
Format format, Map<String, Integer> additionalEncoderConfig) throws IOException {
|
||||||
checkArgument(format.width != Format.NO_VALUE);
|
checkArgument(format.width != Format.NO_VALUE);
|
||||||
checkArgument(format.height != Format.NO_VALUE);
|
checkArgument(format.height != Format.NO_VALUE);
|
||||||
checkArgument(format.frameRate != Format.NO_VALUE);
|
|
||||||
checkArgument(format.averageBitrate != Format.NO_VALUE);
|
|
||||||
|
|
||||||
@Nullable MediaCodecAdapter adapter = null;
|
@Nullable MediaCodecAdapter adapter = null;
|
||||||
try {
|
try {
|
||||||
@ -224,9 +221,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
MediaFormat.createVideoFormat(
|
MediaFormat.createVideoFormat(
|
||||||
checkNotNull(format.sampleMimeType), format.width, format.height);
|
checkNotNull(format.sampleMimeType), format.width, format.height);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface);
|
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, CodecCapabilities.COLOR_FormatSurface);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, (int) ceil(format.frameRate));
|
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
|
||||||
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, format.averageBitrate);
|
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 413_000);
|
||||||
|
|
||||||
for (Map.Entry<String, Integer> encoderSetting : additionalEncoderConfig.entrySet()) {
|
for (Map.Entry<String, Integer> encoderSetting : additionalEncoderConfig.entrySet()) {
|
||||||
mediaFormat.setInteger(encoderSetting.getKey(), encoderSetting.getValue());
|
mediaFormat.setInteger(encoderSetting.getKey(), encoderSetting.getValue());
|
||||||
|
@ -38,7 +38,6 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultLoadControl;
|
import com.google.android.exoplayer2.DefaultLoadControl;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.PlaybackException;
|
import com.google.android.exoplayer2.PlaybackException;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
@ -339,7 +338,12 @@ public final class TranscodingTransformer {
|
|||||||
}
|
}
|
||||||
Transformation transformation =
|
Transformation transformation =
|
||||||
new Transformation(
|
new Transformation(
|
||||||
removeAudio, removeVideo, flattenForSlowMotion, outputMimeType, audioMimeType);
|
removeAudio,
|
||||||
|
removeVideo,
|
||||||
|
flattenForSlowMotion,
|
||||||
|
outputMimeType,
|
||||||
|
audioMimeType,
|
||||||
|
/* videoMimeType= */ null);
|
||||||
return new TranscodingTransformer(
|
return new TranscodingTransformer(
|
||||||
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
|
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
|
||||||
}
|
}
|
||||||
@ -639,17 +643,9 @@ public final class TranscodingTransformer {
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
if (!transformation.removeVideo) {
|
if (!transformation.removeVideo) {
|
||||||
Format encoderOutputFormat =
|
|
||||||
new Format.Builder()
|
|
||||||
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
|
||||||
.setWidth(480)
|
|
||||||
.setHeight(360)
|
|
||||||
.setAverageBitrate(413_000)
|
|
||||||
.setFrameRate(30)
|
|
||||||
.build();
|
|
||||||
renderers[index] =
|
renderers[index] =
|
||||||
new TransformerTranscodingVideoRenderer(
|
new TransformerTranscodingVideoRenderer(
|
||||||
context, muxerWrapper, mediaClock, transformation, encoderOutputFormat);
|
context, muxerWrapper, mediaClock, transformation);
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
return renderers;
|
return renderers;
|
||||||
|
@ -26,17 +26,20 @@ import androidx.annotation.Nullable;
|
|||||||
public final boolean flattenForSlowMotion;
|
public final boolean flattenForSlowMotion;
|
||||||
public final String outputMimeType;
|
public final String outputMimeType;
|
||||||
@Nullable public final String audioMimeType;
|
@Nullable public final String audioMimeType;
|
||||||
|
@Nullable public final String videoMimeType;
|
||||||
|
|
||||||
public Transformation(
|
public Transformation(
|
||||||
boolean removeAudio,
|
boolean removeAudio,
|
||||||
boolean removeVideo,
|
boolean removeVideo,
|
||||||
boolean flattenForSlowMotion,
|
boolean flattenForSlowMotion,
|
||||||
String outputMimeType,
|
String outputMimeType,
|
||||||
@Nullable String audioMimeType) {
|
@Nullable String audioMimeType,
|
||||||
|
@Nullable String videoMimeType) {
|
||||||
this.removeAudio = removeAudio;
|
this.removeAudio = removeAudio;
|
||||||
this.removeVideo = removeVideo;
|
this.removeVideo = removeVideo;
|
||||||
this.flattenForSlowMotion = flattenForSlowMotion;
|
this.flattenForSlowMotion = flattenForSlowMotion;
|
||||||
this.outputMimeType = outputMimeType;
|
this.outputMimeType = outputMimeType;
|
||||||
this.audioMimeType = audioMimeType;
|
this.audioMimeType = audioMimeType;
|
||||||
|
this.videoMimeType = videoMimeType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,7 +304,8 @@ public final class Transformer {
|
|||||||
removeVideo,
|
removeVideo,
|
||||||
flattenForSlowMotion,
|
flattenForSlowMotion,
|
||||||
outputMimeType,
|
outputMimeType,
|
||||||
/* audioMimeType= */ null);
|
/* audioMimeType= */ null,
|
||||||
|
/* videoMimeType= */ null);
|
||||||
return new Transformer(
|
return new Transformer(
|
||||||
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
|
context, mediaSourceFactory, muxerFactory, transformation, listener, looper, clock);
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ import java.nio.ByteBuffer;
|
|||||||
@Nullable private MediaCodecAdapterWrapper decoder;
|
@Nullable private MediaCodecAdapterWrapper decoder;
|
||||||
@Nullable private MediaCodecAdapterWrapper encoder;
|
@Nullable private MediaCodecAdapterWrapper encoder;
|
||||||
@Nullable private SpeedProvider speedProvider;
|
@Nullable private SpeedProvider speedProvider;
|
||||||
@Nullable private Format inputFormat;
|
@Nullable private Format decoderInputFormat;
|
||||||
@Nullable private AudioFormat encoderInputAudioFormat;
|
@Nullable private AudioFormat encoderInputAudioFormat;
|
||||||
|
|
||||||
private ByteBuffer sonicOutputBuffer;
|
private ByteBuffer sonicOutputBuffer;
|
||||||
@ -100,7 +100,7 @@ import java.nio.ByteBuffer;
|
|||||||
encoder = null;
|
encoder = null;
|
||||||
}
|
}
|
||||||
speedProvider = null;
|
speedProvider = null;
|
||||||
inputFormat = null;
|
decoderInputFormat = null;
|
||||||
encoderInputAudioFormat = null;
|
encoderInputAudioFormat = null;
|
||||||
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
sonicOutputBuffer = AudioProcessor.EMPTY_BUFFER;
|
||||||
nextEncoderInputBufferTimeUs = 0;
|
nextEncoderInputBufferTimeUs = 0;
|
||||||
@ -352,7 +352,7 @@ import java.nio.ByteBuffer;
|
|||||||
}
|
}
|
||||||
String audioMimeType =
|
String audioMimeType =
|
||||||
transformation.audioMimeType == null
|
transformation.audioMimeType == null
|
||||||
? checkNotNull(inputFormat).sampleMimeType
|
? checkNotNull(decoderInputFormat).sampleMimeType
|
||||||
: transformation.audioMimeType;
|
: transformation.audioMimeType;
|
||||||
try {
|
try {
|
||||||
encoder =
|
encoder =
|
||||||
@ -385,14 +385,14 @@ import java.nio.ByteBuffer;
|
|||||||
if (result != C.RESULT_FORMAT_READ) {
|
if (result != C.RESULT_FORMAT_READ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
inputFormat = checkNotNull(formatHolder.format);
|
decoderInputFormat = checkNotNull(formatHolder.format);
|
||||||
try {
|
try {
|
||||||
decoder = MediaCodecAdapterWrapper.createForAudioDecoding(inputFormat);
|
decoder = MediaCodecAdapterWrapper.createForAudioDecoding(decoderInputFormat);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// TODO (internal b/184262323): Assign an adequate error code.
|
// TODO (internal b/184262323): Assign an adequate error code.
|
||||||
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
|
throw createRendererException(e, PlaybackException.ERROR_CODE_UNSPECIFIED);
|
||||||
}
|
}
|
||||||
speedProvider = new SegmentSpeedProvider(inputFormat);
|
speedProvider = new SegmentSpeedProvider(decoderInputFormat);
|
||||||
currentSpeed = speedProvider.getSpeed(0);
|
currentSpeed = speedProvider.getSpeed(0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -418,7 +418,7 @@ import java.nio.ByteBuffer;
|
|||||||
cause,
|
cause,
|
||||||
TAG,
|
TAG,
|
||||||
getIndex(),
|
getIndex(),
|
||||||
inputFormat,
|
decoderInputFormat,
|
||||||
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
|
/* rendererFormatSupport= */ C.FORMAT_HANDLED,
|
||||||
/* isRecoverable= */ false,
|
/* isRecoverable= */ false,
|
||||||
errorCode);
|
errorCode);
|
||||||
|
@ -42,6 +42,7 @@ import com.google.android.exoplayer2.util.GlUtil;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
@RequiresApi(18)
|
@RequiresApi(18)
|
||||||
/* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer {
|
/* package */ final class TransformerTranscodingVideoRenderer extends TransformerBaseRenderer {
|
||||||
@ -53,12 +54,12 @@ import java.nio.ByteBuffer;
|
|||||||
private static final String TAG = "TransformerTranscodingVideoRenderer";
|
private static final String TAG = "TransformerTranscodingVideoRenderer";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
/** The format the encoder is configured to output, may differ from the actual output format. */
|
|
||||||
private final Format encoderConfigurationOutputFormat;
|
|
||||||
|
|
||||||
private final DecoderInputBuffer decoderInputBuffer;
|
private final DecoderInputBuffer decoderInputBuffer;
|
||||||
private final float[] decoderTextureTransformMatrix;
|
private final float[] decoderTextureTransformMatrix;
|
||||||
|
|
||||||
|
private @MonotonicNonNull Format decoderInputFormat;
|
||||||
|
|
||||||
@Nullable private EGLDisplay eglDisplay;
|
@Nullable private EGLDisplay eglDisplay;
|
||||||
@Nullable private EGLContext eglContext;
|
@Nullable private EGLContext eglContext;
|
||||||
@Nullable private EGLSurface eglSurface;
|
@Nullable private EGLSurface eglSurface;
|
||||||
@ -81,11 +82,9 @@ import java.nio.ByteBuffer;
|
|||||||
Context context,
|
Context context,
|
||||||
MuxerWrapper muxerWrapper,
|
MuxerWrapper muxerWrapper,
|
||||||
TransformerMediaClock mediaClock,
|
TransformerMediaClock mediaClock,
|
||||||
Transformation transformation,
|
Transformation transformation) {
|
||||||
Format encoderConfigurationOutputFormat) {
|
|
||||||
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation);
|
super(C.TRACK_TYPE_VIDEO, muxerWrapper, mediaClock, transformation);
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.encoderConfigurationOutputFormat = encoderConfigurationOutputFormat;
|
|
||||||
decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
decoderInputBuffer = new DecoderInputBuffer(DecoderInputBuffer.BUFFER_REPLACEMENT_MODE_DIRECT);
|
||||||
decoderTextureTransformMatrix = new float[16];
|
decoderTextureTransformMatrix = new float[16];
|
||||||
decoderTextureId = GlUtil.TEXTURE_ID_UNSET;
|
decoderTextureId = GlUtil.TEXTURE_ID_UNSET;
|
||||||
@ -97,15 +96,13 @@ import java.nio.ByteBuffer;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStarted() throws ExoPlaybackException {
|
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
||||||
super.onStarted();
|
if (!isRendererStarted || isEnded() || !ensureInputFormatRead()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
ensureEncoderConfigured();
|
ensureEncoderConfigured();
|
||||||
ensureOpenGlConfigured();
|
ensureOpenGlConfigured();
|
||||||
}
|
if (!ensureDecoderConfigured()) {
|
||||||
|
|
||||||
@Override
|
|
||||||
public void render(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
|
|
||||||
if (!isRendererStarted || isEnded() || !ensureDecoderConfigured()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +150,22 @@ import java.nio.ByteBuffer;
|
|||||||
muxerWrapperTrackEnded = false;
|
muxerWrapperTrackEnded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean ensureInputFormatRead() {
|
||||||
|
if (decoderInputFormat != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
FormatHolder formatHolder = getFormatHolder();
|
||||||
|
@SampleStream.ReadDataResult
|
||||||
|
int result =
|
||||||
|
readSource(
|
||||||
|
formatHolder, decoderInputBuffer, /* readFlags= */ SampleStream.FLAG_REQUIRE_FORMAT);
|
||||||
|
if (result != C.RESULT_FORMAT_READ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
decoderInputFormat = checkNotNull(formatHolder.format);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void ensureEncoderConfigured() throws ExoPlaybackException {
|
private void ensureEncoderConfigured() throws ExoPlaybackException {
|
||||||
if (encoder != null) {
|
if (encoder != null) {
|
||||||
return;
|
return;
|
||||||
@ -161,7 +174,15 @@ import java.nio.ByteBuffer;
|
|||||||
try {
|
try {
|
||||||
encoder =
|
encoder =
|
||||||
MediaCodecAdapterWrapper.createForVideoEncoding(
|
MediaCodecAdapterWrapper.createForVideoEncoding(
|
||||||
encoderConfigurationOutputFormat, ImmutableMap.of());
|
new Format.Builder()
|
||||||
|
.setWidth(checkNotNull(decoderInputFormat).width)
|
||||||
|
.setHeight(decoderInputFormat.height)
|
||||||
|
.setSampleMimeType(
|
||||||
|
transformation.videoMimeType != null
|
||||||
|
? transformation.videoMimeType
|
||||||
|
: decoderInputFormat.sampleMimeType)
|
||||||
|
.build(),
|
||||||
|
ImmutableMap.of());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw createRendererException(
|
throw createRendererException(
|
||||||
// TODO(claincly): should be "ENCODER_INIT_FAILED"
|
// TODO(claincly): should be "ENCODER_INIT_FAILED"
|
||||||
@ -190,8 +211,8 @@ import java.nio.ByteBuffer;
|
|||||||
eglDisplay,
|
eglDisplay,
|
||||||
eglContext,
|
eglContext,
|
||||||
eglSurface,
|
eglSurface,
|
||||||
encoderConfigurationOutputFormat.width,
|
checkNotNull(decoderInputFormat).width,
|
||||||
encoderConfigurationOutputFormat.height);
|
decoderInputFormat.height);
|
||||||
decoderTextureId = GlUtil.createExternalTexture();
|
decoderTextureId = GlUtil.createExternalTexture();
|
||||||
String vertexShaderCode;
|
String vertexShaderCode;
|
||||||
String fragmentShaderCode;
|
String fragmentShaderCode;
|
||||||
@ -248,26 +269,18 @@ import java.nio.ByteBuffer;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FormatHolder formatHolder = getFormatHolder();
|
|
||||||
@SampleStream.ReadDataResult
|
|
||||||
int result =
|
|
||||||
readSource(
|
|
||||||
formatHolder, decoderInputBuffer, /* readFlags= */ SampleStream.FLAG_REQUIRE_FORMAT);
|
|
||||||
if (result != C.RESULT_FORMAT_READ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Format inputFormat = checkNotNull(formatHolder.format);
|
|
||||||
checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET);
|
checkState(decoderTextureId != GlUtil.TEXTURE_ID_UNSET);
|
||||||
decoderSurfaceTexture = new SurfaceTexture(decoderTextureId);
|
decoderSurfaceTexture = new SurfaceTexture(decoderTextureId);
|
||||||
decoderSurfaceTexture.setOnFrameAvailableListener(
|
decoderSurfaceTexture.setOnFrameAvailableListener(
|
||||||
surfaceTexture -> isDecoderSurfacePopulated = true);
|
surfaceTexture -> isDecoderSurfacePopulated = true);
|
||||||
decoderSurface = new Surface(decoderSurfaceTexture);
|
decoderSurface = new Surface(decoderSurfaceTexture);
|
||||||
try {
|
try {
|
||||||
decoder = MediaCodecAdapterWrapper.createForVideoDecoding(inputFormat, decoderSurface);
|
decoder =
|
||||||
|
MediaCodecAdapterWrapper.createForVideoDecoding(
|
||||||
|
checkNotNull(decoderInputFormat), decoderSurface);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw createRendererException(
|
throw createRendererException(
|
||||||
e, formatHolder.format, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
|
e, decoderInputFormat, PlaybackException.ERROR_CODE_DECODER_INIT_FAILED);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user