diff options
| author | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
|---|---|---|
| committer | Lena <lena@omega> | 2026-08-01 00:00:00 +0000 |
| commit | 852a8a00273c9128efeed0217a8e5d3fcd8bf780 (patch) | |
| tree | c72f959731fa3c7c809cf1a0222363b6c9c9b03b /app/src/main/java/invalid/lena/scrcpy/AudioStream.java | |
| parent | f379b93d52bf0dbd3816ec3f64d165314771d2a6 (diff) | |
| download | scrcpy-android-852a8a00273c9128efeed0217a8e5d3fcd8bf780.tar.gz | |
app: harden mirroring lifecycle and state
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/AudioStream.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/AudioStream.java | 45 |
1 files changed, 38 insertions, 7 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/AudioStream.java b/app/src/main/java/invalid/lena/scrcpy/AudioStream.java index 7fe33f0..13b39f5 100644 --- a/app/src/main/java/invalid/lena/scrcpy/AudioStream.java +++ b/app/src/main/java/invalid/lena/scrcpy/AudioStream.java @@ -2,6 +2,7 @@ package invalid.lena.scrcpy; import java.io.IOException; import java.io.InputStream; +import java.util.concurrent.TimeUnit; // Reads the scrcpy audio socket and drives an AudioFrames sink. // @@ -23,16 +24,34 @@ public final class AudioStream { // Generous upper bound for one audio packet (a raw PCM block or an // opus packet is a few KB). A corrupt or hostile length field must // not drive the allocation below. - private static final int MAX_FRAME_SIZE = 1024 * 1024; + private static final int MAX_FRAME_SIZE = 256 * 1024; private final InputStream source; private final AudioFrames sink; + private final Runnable onFatalError; private Thread thread; private volatile boolean stop; + // Liveness signal for Session's stall watchdog. The audio socket is + // the only one that is reliably continuous: the server captures PCM + // at 48 kHz whether or not anything on the target's screen moves, so + // silence here means the wire is gone. Video is useless for this - a + // static screen queues nothing to the encoder and legitimately + // produces no frames for minutes. + private volatile boolean started; + private volatile long lastPacketAtMs; + + public boolean isStarted() { return started; } + public long lastPacketAtMs() { return lastPacketAtMs; } + public AudioStream(InputStream source, AudioFrames sink) { + this(source, sink, null); + } + + public AudioStream(InputStream source, AudioFrames sink, Runnable onFatalError) { this.source = source; this.sink = sink; + this.onFatalError = onFatalError; } public void start() { @@ -60,16 +79,15 @@ public final class AudioStream { return; } if (fourcc == 1) { - Log.e("audio: server reports configuration error"); - return; + throw new IOException("audio: server reports configuration error"); } if (fourcc != Wire.CODEC_RAW && fourcc != Wire.CODEC_OPUS) { - Log.w("audio: unexpected codec %s - keeping silent", - Wire.fourccName(fourcc)); - return; + throw new IOException("audio: unexpected codec " + Wire.fourccName(fourcc)); } Log.i("audio meta codec=%s", Wire.fourccName(fourcc)); sink.start(fourcc); + lastPacketAtMs = monotonicMs(); + started = true; byte[] hdr = new byte[12]; byte[] payload = new byte[16 * 1024]; @@ -84,15 +102,28 @@ public final class AudioStream { } if (size > payload.length) payload = new byte[size]; Wire.readFully(source, payload, 0, size); + lastPacketAtMs = monotonicMs(); sink.feed(payload, 0, size, cfg); if (++frames == 1) Log.i("audio frame n=1 size=%d cfg=%s", size, cfg); } } catch (IOException e) { - if (!stop) Log.e(e, "audio reader"); + if (!stop) { + Log.e(e, "audio reader"); + reportFatal(); + } } catch (Exception e) { Log.e(e, "audio reader unexpected"); + if (!stop) reportFatal(); } finally { Log.i("audio reader: end"); } } + + private void reportFatal() { + if (onFatalError != null) onFatalError.run(); + } + + private static long monotonicMs() { + return TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); + } } |