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: * Video:
* `MediaCodecVideoRenderer` avoids decoding samples that are neither * `MediaCodecVideoRenderer` avoids decoding samples that are neither
rendered nor used as reference by other samples. 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: * Text:
* TTML: Fix handling of percentage `tts:fontSize` values to ensure they * TTML: Fix handling of percentage `tts:fontSize` values to ensure they
are correctly inherited from parent nodes with percentage `tts:fontSize` are correctly inherited from parent nodes with percentage `tts:fontSize`

View File

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

View File

@ -284,6 +284,14 @@ public interface MediaCodecAdapter {
@RequiresApi(23) @RequiresApi(23)
void setOutputSurface(Surface surface); 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. * Communicate additional parameter changes to the {@link MediaCodec} instance.
* *

View File

@ -138,6 +138,14 @@ public final class MediaCodecInfo {
*/ */
public final boolean vendor; 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; private final boolean isVideo;
/** /**
@ -179,7 +187,8 @@ public final class MediaCodecInfo {
&& isAdaptive(capabilities) && isAdaptive(capabilities)
&& !needsDisableAdaptationWorkaround(name), && !needsDisableAdaptationWorkaround(name),
/* tunneling= */ capabilities != null && isTunneling(capabilities), /* tunneling= */ capabilities != null && isTunneling(capabilities),
/* secure= */ forceSecure || (capabilities != null && isSecure(capabilities))); /* secure= */ forceSecure || (capabilities != null && isSecure(capabilities)),
isDetachedSurfaceSupported(capabilities));
} }
@VisibleForTesting @VisibleForTesting
@ -193,7 +202,8 @@ public final class MediaCodecInfo {
boolean vendor, boolean vendor,
boolean adaptive, boolean adaptive,
boolean tunneling, boolean tunneling,
boolean secure) { boolean secure,
boolean detachedSurfaceSupported) {
this.name = Assertions.checkNotNull(name); this.name = Assertions.checkNotNull(name);
this.mimeType = mimeType; this.mimeType = mimeType;
this.codecMimeType = codecMimeType; this.codecMimeType = codecMimeType;
@ -204,6 +214,7 @@ public final class MediaCodecInfo {
this.adaptive = adaptive; this.adaptive = adaptive;
this.tunneling = tunneling; this.tunneling = tunneling;
this.secure = secure; this.secure = secure;
this.detachedSurfaceSupported = detachedSurfaceSupported;
isVideo = MimeTypes.isVideo(mimeType); isVideo = MimeTypes.isVideo(mimeType);
} }
@ -661,6 +672,12 @@ public final class MediaCodecInfo {
return capabilities.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback); 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( private static boolean areSizeAndRateSupported(
VideoCapabilities capabilities, int width, int height, double frameRate) { VideoCapabilities capabilities, int width, int height, double frameRate) {
// Don't ever fail due to alignment. See: https://github.com/google/ExoPlayer/issues/6551. // 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 static androidx.media3.common.util.Assertions.checkNotNull;
import android.annotation.SuppressLint;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Bundle; import android.os.Bundle;
@ -43,14 +44,21 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
/** A factory for {@link SynchronousMediaCodecAdapter} instances. */ /** A factory for {@link SynchronousMediaCodecAdapter} instances. */
public static class Factory implements MediaCodecAdapter.Factory { public static class Factory implements MediaCodecAdapter.Factory {
@SuppressLint("WrongConstant") // Can't verify codec flag IntDef
@Override @Override
public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException { public MediaCodecAdapter createAdapter(Configuration configuration) throws IOException {
@Nullable MediaCodec codec = null; @Nullable MediaCodec codec = null;
try { try {
codec = createCodec(configuration); codec = createCodec(configuration);
TraceUtil.beginSection("configureCodec"); 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( codec.configure(
configuration.mediaFormat, configuration.surface, configuration.crypto, /* flags= */ 0); configuration.mediaFormat, configuration.surface, configuration.crypto, flags);
TraceUtil.endSection(); TraceUtil.endSection();
TraceUtil.beginSection("startCodec"); TraceUtil.beginSection("startCodec");
codec.start(); codec.start();
@ -177,6 +185,12 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
codec.setOutputSurface(surface); codec.setOutputSurface(surface);
} }
@RequiresApi(35)
@Override
public void detachOutputSurface() {
codec.detachOutputSurface();
}
@Override @Override
public void setParameters(Bundle params) { public void setParameters(Bundle params) {
codec.setParameters(params); codec.setParameters(params);

View File

@ -948,7 +948,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
MediaCodecInfo codecInfo = checkNotNull(getCodecInfo()); MediaCodecInfo codecInfo = checkNotNull(getCodecInfo());
boolean canUpdateSurface = hasSurfaceForCodec(codecInfo); boolean canUpdateSurface = hasSurfaceForCodec(codecInfo);
if (Util.SDK_INT >= 23 && canUpdateSurface && !codecNeedsSetOutputSurfaceWorkaround) { if (Util.SDK_INT >= 23 && canUpdateSurface && !codecNeedsSetOutputSurfaceWorkaround) {
setOutputSurfaceV23(codec, getSurfaceForCodec(codecInfo)); setOutputSurface(codec, getSurfaceForCodec(codecInfo));
} else { } else {
releaseCodec(); releaseCodec();
maybeInitCodecOrBypass(); maybeInitCodecOrBypass();
@ -1766,18 +1766,24 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
} }
private boolean hasSurfaceForCodec(MediaCodecInfo codecInfo) { 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 * Returns surface to be set on the codec, or null to use detached surface mode.
* #hasSurfaceForCodec(MediaCodecInfo)} returns true. *
* <p>Must only be called if {@link #hasSurfaceForCodec(MediaCodecInfo)} returns true.
*/ */
@Nullable
private Surface getSurfaceForCodec(MediaCodecInfo codecInfo) { private Surface getSurfaceForCodec(MediaCodecInfo codecInfo) {
if (videoSink != null) { if (videoSink != null) {
return videoSink.getInputSurface(); return videoSink.getInputSurface();
} else if (displaySurface != null) { } else if (displaySurface != null) {
return displaySurface; return displaySurface;
} else if (shouldUseDetachedSurface(codecInfo)) {
return null;
} else { } else {
checkState(shouldUsePlaceholderSurface(codecInfo)); checkState(shouldUsePlaceholderSurface(codecInfo));
if (placeholderSurface != null && placeholderSurface.secure != codecInfo.secure) { 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) { private boolean shouldUsePlaceholderSurface(MediaCodecInfo codecInfo) {
return Util.SDK_INT >= 23 return Util.SDK_INT >= 23
&& !tunneling && !tunneling
@ -1898,11 +1908,26 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer
codec.setParameters(codecParameters); 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) @RequiresApi(23)
protected void setOutputSurfaceV23(MediaCodecAdapter codec, Surface surface) { protected void setOutputSurfaceV23(MediaCodecAdapter codec, Surface surface) {
codec.setOutputSurface(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. * 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, /* vendor= */ true,
adaptive, adaptive,
/* tunneling= */ false, /* tunneling= */ false,
/* secure= */ false); /* secure= */ false,
/* detachedSurfaceSupported= */ true);
} }
private static MediaCodecInfo buildAacCodecInfo() { private static MediaCodecInfo buildAacCodecInfo() {
@ -324,7 +325,8 @@ public final class MediaCodecInfoTest {
/* vendor= */ false, /* vendor= */ false,
/* adaptive= */ false, /* adaptive= */ false,
/* tunneling= */ false, /* tunneling= */ false,
/* secure= */ false); /* secure= */ false,
/* detachedSurfaceSupported= */ false);
} }
private static ColorInfo buildHdrColorInfo(@C.ColorSpace int colorSpace) { private static ColorInfo buildHdrColorInfo(@C.ColorSpace int colorSpace) {

View File

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

View File

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

View File

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