Interrupt MediaCodec blocking call on reset

When the MediaCodec input is a Surface, no EOS (end-of-stream) will
never occur automatically: it may only be triggered manually by
MediaCodec.signalEndOfInputStream().

Use this signal to interrupt the blocking call to dequeueOutputBuffer()
immediately on reset, without waiting for the next frame to be dequeued.

PR #5432 <https://github.com/Genymobile/scrcpy/pull/5432>
This commit is contained in:
Romain Vimont 2024-10-31 21:42:58 +01:00
parent 69b836930a
commit 9958302e6f
2 changed files with 37 additions and 25 deletions

View File

@ -1,17 +1,29 @@
package com.genymobile.scrcpy.video; package com.genymobile.scrcpy.video;
import android.media.MediaCodec;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
public class CaptureReset implements SurfaceCapture.CaptureListener { public class CaptureReset implements SurfaceCapture.CaptureListener {
private final AtomicBoolean reset = new AtomicBoolean(); private final AtomicBoolean reset = new AtomicBoolean();
// Current instance of MediaCodec to "interrupt" on reset
private MediaCodec runningMediaCodec;
public boolean consumeReset() { public boolean consumeReset() {
return reset.getAndSet(false); return reset.getAndSet(false);
} }
public void reset() { public synchronized void reset() {
reset.set(true); reset.set(true);
if (runningMediaCodec != null) {
runningMediaCodec.signalEndOfInputStream();
}
}
public synchronized void setRunningMediaCodec(MediaCodec runningMediaCodec) {
this.runningMediaCodec = runningMediaCodec;
} }
@Override @Override

View File

@ -94,7 +94,21 @@ public class SurfaceEncoder implements AsyncProcessor {
mediaCodec.start(); mediaCodec.start();
alive = encode(mediaCodec, streamer); // Set the MediaCodec instance to "interrupt" (by signaling an EOS) on reset
reset.setRunningMediaCodec(mediaCodec);
if (stopped.get()) {
alive = false;
} else {
boolean resetRequested = reset.consumeReset();
if (!resetRequested) {
// If a reset is requested during encode(), it will interrupt the encoding by an EOS
encode(mediaCodec, streamer);
}
// The capture might have been closed internally (for example if the camera is disconnected)
alive = !stopped.get() && !capture.isClosed();
}
// do not call stop() on exception, it would trigger an IllegalStateException // do not call stop() on exception, it would trigger an IllegalStateException
mediaCodec.stop(); mediaCodec.stop();
} catch (IllegalStateException | IllegalArgumentException e) { } catch (IllegalStateException | IllegalArgumentException e) {
@ -105,6 +119,7 @@ public class SurfaceEncoder implements AsyncProcessor {
Ln.i("Retrying..."); Ln.i("Retrying...");
alive = true; alive = true;
} finally { } finally {
reset.setRunningMediaCodec(null);
mediaCodec.reset(); mediaCodec.reset();
if (surface != null) { if (surface != null) {
surface.release(); surface.release();
@ -165,25 +180,16 @@ public class SurfaceEncoder implements AsyncProcessor {
return 0; return 0;
} }
private boolean encode(MediaCodec codec, Streamer streamer) throws IOException { private void encode(MediaCodec codec, Streamer streamer) throws IOException {
boolean eof = false;
boolean alive = true;
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
while (!reset.consumeReset() && !eof) { boolean eos;
if (stopped.get()) { do {
alive = false;
break;
}
int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1); int outputBufferId = codec.dequeueOutputBuffer(bufferInfo, -1);
try { try {
if (reset.consumeReset()) { eos = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
// must restart encoding with new size // On EOS, there might be data or not, depending on bufferInfo.size
break; if (outputBufferId >= 0 && bufferInfo.size > 0) {
}
eof = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
if (outputBufferId >= 0) {
ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId); ByteBuffer codecBuffer = codec.getOutputBuffer(outputBufferId);
boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0; boolean isConfig = (bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0;
@ -200,14 +206,7 @@ public class SurfaceEncoder implements AsyncProcessor {
codec.releaseOutputBuffer(outputBufferId, false); codec.releaseOutputBuffer(outputBufferId, false);
} }
} }
} } while (!eos);
if (capture.isClosed()) {
// The capture might have been closed internally (for example if the camera is disconnected)
alive = false;
}
return !eof && alive;
} }
private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException { private static MediaCodec createMediaCodec(Codec codec, String encoderName) throws IOException, ConfigurationException {
@ -300,6 +299,7 @@ public class SurfaceEncoder implements AsyncProcessor {
public void stop() { public void stop() {
if (thread != null) { if (thread != null) {
stopped.set(true); stopped.set(true);
reset.reset();
} }
} }