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:
parent
51d27d7575
commit
54f5e0729e
@ -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`
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user