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