Use MediaCodec.stop() before release() for surface switching bug

ExoPlayer used to call `stop()` before `release()`. This was removed in
<unknown commit>.

A framework bug introduced in Android 11 (API 30) resulted in some
DRM -> clear transitions failing during `MediaCodec.configure()`. An
investigation in Issue: google/ExoPlayer#8696 and b/191966399 identified that this was
due to `release()` returning 'too early' and the subsequent
`configure()` call was then trying to re-use a `Surface` that hadn't
been fully detached from the previous codec. This was fixed in
Android 13 (API 33) with http://r.android.com/2094347.

ExoPlayer worked around the framework bug by adding an arbitrary 50ms
sleep after a failed codec initialization, followed by retrying. This
was enough to resolve the problem in the test scenario on a OnePlus
AC2003.

Issue: androidx/media#1497 points out that 50ms might not be the appropriate delay
for all devices, so it's an incomplete fix. They suggested re-adding the
`MediaCodec.stop()` call instead. This also reliably resolves the issue
on the OnePlus AC2003 (with neither workaround in place, the problem
repros almost immediately).
PiperOrigin-RevId: 646461943
This commit is contained in:
ibaker 2024-06-25 06:52:19 -07:00 committed by Copybara-Service
parent 73bf852405
commit 5fcc7433a1
3 changed files with 24 additions and 18 deletions

View File

@ -259,11 +259,21 @@ import java.nio.ByteBuffer;
state = STATE_SHUT_DOWN;
} finally {
if (!codecReleased) {
try {
// Stopping the codec before releasing it works around a bug on APIs 30, 31 and 32 where
// MediaCodec.release() returns too early before fully detaching a Surface, and a
// subsequent MediaCodec.configure() call using the same Surface then fails. See
// https://github.com/google/ExoPlayer/issues/8696 and b/191966399.
if (Util.SDK_INT >= 30 && Util.SDK_INT < 33) {
codec.stop();
}
} finally {
codec.release();
codecReleased = true;
}
}
}
}
@Override
public void setOnFrameRenderedListener(OnFrameRenderedListener listener, Handler handler) {

View File

@ -1128,27 +1128,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
ArrayDeque<MediaCodecInfo> availableCodecInfos = checkNotNull(this.availableCodecInfos);
MediaCodecInfo preferredCodecInfo = availableCodecInfos.peekFirst();
while (codec == null) {
MediaCodecInfo codecInfo = checkNotNull(availableCodecInfos.peekFirst());
if (!shouldInitCodec(codecInfo)) {
return;
}
try {
try {
initCodec(codecInfo, crypto);
} catch (Exception e) {
if (codecInfo == preferredCodecInfo) {
// If creating the preferred decoder failed then sleep briefly before retrying.
// Workaround for [internal b/191966399].
// See also https://github.com/google/ExoPlayer/issues/8696.
Log.w(TAG, "Preferred decoder instantiation failed. Sleeping for 50ms then retrying.");
Thread.sleep(/* millis= */ 50);
initCodec(codecInfo, crypto);
} else {
throw e;
}
}
} catch (Exception e) {
Log.w(TAG, "Failed to initialize decoder: " + codecInfo, e);
// This codec failed to initialize, so fall back to the next codec in the list (if any). We

View File

@ -172,8 +172,18 @@ public final class SynchronousMediaCodecAdapter implements MediaCodecAdapter {
public void release() {
inputByteBuffers = null;
outputByteBuffers = null;
try {
if (Util.SDK_INT >= 30 && Util.SDK_INT < 33) {
// Stopping the codec before releasing it works around a bug on APIs 30, 31 and 32 where
// MediaCodec.release() returns too early before fully detaching a Surface, and a
// subsequent MediaCodec.configure() call using the same Surface then fails. See
// https://github.com/google/ExoPlayer/issues/8696 and b/191966399.
codec.stop();
}
} finally {
codec.release();
}
}
@Override
@RequiresApi(23)