Support detached surface mode from API 35

This replaces the existing PlaceholderSurface mode with a more
efficient solution that doesn't require a GL texture or a new
thread.

PiperOrigin-RevId: 653537596
This commit is contained in:
tonihei 2024-07-18 02:27:18 -07:00 committed by Copybara-Service
parent 51d27d7575
commit 54f5e0729e
10 changed files with 106 additions and 9 deletions

View File

@ -41,6 +41,10 @@
* Video:
* `MediaCodecVideoRenderer` avoids decoding samples that are neither
rendered nor used as reference by other samples.
* On API 35 and above, `MediaCodecAdapter` may now receive a `null`
`Surface` in `configure` and calls to a new method `detachOutputSurface`
to remove a previously set `Surface` if the codec supports this
(`MediaCodecInfo.detachedSurfaceSupported`).
* Text:
* TTML: Fix handling of percentage `tts:fontSize` values to ensure they
are correctly inherited from parent nodes with percentage `tts:fontSize`

View File

@ -116,6 +116,11 @@ import java.nio.ByteBuffer;
codecAdapter =
new AsynchronousMediaCodecAdapter(codec, callbackThreadSupplier.get(), bufferEnqueuer);
TraceUtil.endSection();
if (configuration.surface == null
&& configuration.codecInfo.detachedSurfaceSupported
&& Util.SDK_INT >= 35) {
flags |= MediaCodec.CONFIGURE_FLAG_DETACHED_SURFACE;
}
codecAdapter.initialize(
configuration.mediaFormat, configuration.surface, configuration.crypto, flags);
return codecAdapter;
@ -295,6 +300,12 @@ import java.nio.ByteBuffer;
codec.setOutputSurface(surface);
}
@RequiresApi(35)
@Override
public void detachOutputSurface() {
codec.detachOutputSurface();
}
@Override
public void setParameters(Bundle params) {
bufferEnqueuer.setParameters(params);

View File

@ -284,6 +284,14 @@ public interface MediaCodecAdapter {
@RequiresApi(23)
void setOutputSurface(Surface surface);
/**
* Detaches the current output surface.
*
* @see MediaCodec#detachOutputSurface()
*/
@RequiresApi(35)
void detachOutputSurface();
/**
* Communicate additional parameter changes to the {@link MediaCodec} instance.
*

View File

@ -138,6 +138,14 @@ public final class MediaCodecInfo {
*/
public final boolean vendor;
/**
* Whether the codec supports "detached" surface mode where it is able to decode without an
* attached surface. Only relevant for video codecs.
*
* @see android.media.MediaCodecInfo.CodecCapabilities#FEATURE_DetachedSurface
*/
public final boolean detachedSurfaceSupported;
private final boolean isVideo;
/**
@ -179,7 +187,8 @@ public final class MediaCodecInfo {
&& isAdaptive(capabilities)
&& !needsDisableAdaptationWorkaround(name),
/* tunneling= */ capabilities != null && isTunneling(capabilities),
/* secure= */ forceSecure || (capabilities != null && isSecure(capabilities)));
/* secure= */ forceSecure || (capabilities != null && isSecure(capabilities)),
isDetachedSurfaceSupported(capabilities));
}
@VisibleForTesting
@ -193,7 +202,8 @@ public final class MediaCodecInfo {
boolean vendor,
boolean adaptive,
boolean tunneling,
boolean secure) {
boolean secure,
boolean detachedSurfaceSupported) {
this.name = Assertions.checkNotNull(name);
this.mimeType = mimeType;
this.codecMimeType = codecMimeType;
@ -204,6 +214,7 @@ public final class MediaCodecInfo {
this.adaptive = adaptive;
this.tunneling = tunneling;
this.secure = secure;
this.detachedSurfaceSupported = detachedSurfaceSupported;
isVideo = MimeTypes.isVideo(mimeType);
}
@ -661,6 +672,12 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback);
}
private static boolean isDetachedSurfaceSupported(@Nullable CodecCapabilities capabilities) {
return Util.SDK_INT >= 35
&& capabilities != null
&& capabilities.isFeatureSupported(CodecCapabilities.FEATURE_DetachedSurface);
}
private static boolean areSizeAndRateSupported(
VideoCapabilities capabilities, int width, int height, double frameRate) {
// Don't ever fail due to alignment. See: https://github.com/google/ExoPlayer/issues/6551.

View File

@ -18,6 +18,7 @@ package androidx.media3.exoplayer.mediacodec;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.os.Bundle;
@ -43,14 +44,21 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
/** A factory for {@link SynchronousMediaCodecAdapter} instances. */
public static class Factory implements MediaCodecAdapter.Factory {
@SuppressLint("WrongConstant") // Can't verify codec flag IntDef
@Override
public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException {
@Nullable MediaCodec codec = null;
try {
codec = createCodec(configuration);
TraceUtil.beginSection("configureCodec");
int flags = 0;
if (configuration.surface == null
&& configuration.codecInfo.detachedSurfaceSupported
&& Util.SDK_INT >= 35) {
flags |= MediaCodec.CONFIGURE_FLAG_DETACHED_SURFACE;
}
codec.configure(
configuration.mediaFormat, configuration.surface, configuration.crypto, /* flags= */ 0);
configuration.mediaFormat, configuration.surface, configuration.crypto, flags);
TraceUtil.endSection();
TraceUtil.beginSection("startCodec");
codec.start();
@ -177,6 +185,12 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
codec.setOutputSurface(surface);
}
@RequiresApi(35)
@Override
public void detachOutputSurface() {
codec.detachOutputSurface();
}
@Override
public void setParameters(Bundle params) {
codec.setParameters(params);

View File

@ -948,7 +948,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
MediaCodecInfo codecInfo = checkNotNull(getCodecInfo());
boolean canUpdateSurface = hasSurfaceForCodec(codecInfo);
if (Util.SDK_INT >= 23 && canUpdateSurface && !codecNeedsSetOutputSurfaceWorkaround) {
setOutputSurfaceV23(codec, getSurfaceForCodec(codecInfo));
setOutputSurface(codec, getSurfaceForCodec(codecInfo));
} else {
releaseCodec();
maybeInitCodecOrBypass();
@ -1766,18 +1766,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
}
private boolean hasSurfaceForCodec(MediaCodecInfo codecInfo) {
return displaySurface != null || shouldUsePlaceholderSurface(codecInfo);
return displaySurface != null
|| shouldUseDetachedSurface(codecInfo)
|| shouldUsePlaceholderSurface(codecInfo);
}
/**
* Returns surface to be set on the codec. Must only be called if {@link
* #hasSurfaceForCodec(MediaCodecInfo)} returns true.
* Returns surface to be set on the codec, or null to use detached surface mode.
*
* <p>Must only be called if {@link #hasSurfaceForCodec(MediaCodecInfo)} returns true.
*/
@Nullable
private Surface getSurfaceForCodec(MediaCodecInfo codecInfo) {
if (videoSink != null) {
return videoSink.getInputSurface();
} else if (displaySurface != null) {
return displaySurface;
} else if (shouldUseDetachedSurface(codecInfo)) {
return null;
} else {
checkState(shouldUsePlaceholderSurface(codecInfo));
if (placeholderSurface != null && placeholderSurface.secure != codecInfo.secure) {
@ -1791,6 +1797,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
}
}
protected boolean shouldUseDetachedSurface(MediaCodecInfo codecInfo) {
return Util.SDK_INT >= 35 && codecInfo.detachedSurfaceSupported;
}
private boolean shouldUsePlaceholderSurface(MediaCodecInfo codecInfo) {
return Util.SDK_INT >= 23
&& !tunneling
@ -1898,11 +1908,26 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
codec.setParameters(codecParameters);
}
private void setOutputSurface(MediaCodecAdapter codec, @Nullable Surface surface) {
if (Util.SDK_INT >= 23 && surface != null) {
setOutputSurfaceV23(codec, surface);
} else if (Util.SDK_INT >= 35) {
detachOutputSurfaceV35(codec);
} else {
throw new IllegalStateException();
}
}
@RequiresApi(23)
protected void setOutputSurfaceV23(MediaCodecAdapter codec, Surface surface) {
codec.setOutputSurface(surface);
}
@RequiresApi(35)
protected void detachOutputSurfaceV35(MediaCodecAdapter codec) {
codec.detachOutputSurface();
}
/**
* Returns the framework {@link MediaFormat} that should be used to configure the decoder.
*

View File

@ -310,7 +310,8 @@ public final class MediaCodecInfoTest {
/* vendor= */ true,
adaptive,
/* tunneling= */ false,
/* secure= */ false);
/* secure= */ false,
/* detachedSurfaceSupported= */ true);
}
private static MediaCodecInfo buildAacCodecInfo() {
@ -324,7 +325,8 @@ public final class MediaCodecInfoTest {
/* vendor= */ false,
/* adaptive= */ false,
/* tunneling= */ false,
/* secure= */ false);
/* secure= */ false,
/* detachedSurfaceSupported= */ false);
}
private static ColorInfo buildHdrColorInfo(@C.ColorSpace int colorSpace) {

View File

@ -733,6 +733,11 @@ public class MediaCodecRendererTest {
throw exceptionSupplier.get();
}
@Override
public void detachOutputSurface() {
throw exceptionSupplier.get();
}
@Override
public void setParameters(Bundle params) {
throw exceptionSupplier.get();

View File

@ -1978,6 +1978,11 @@ public class MediaCodecVideoRendererTest {
adapter.setOutputSurface(surface);
}
@Override
public void detachOutputSurface() {
adapter.detachOutputSurface();
}
@Override
public void setParameters(Bundle params) {
adapter.setParameters(params);

View File

@ -373,6 +373,12 @@ public class CapturingRenderersFactory implements RenderersFactory, Dumper.Dumpa
delegate.setOutputSurface(surface);
}
@RequiresApi(35)
@Override
public void detachOutputSurface() {
delegate.detachOutputSurface();
}
@Override
public void setParameters(Bundle params) {
delegate.setParameters(params);