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 | 75b1a5569bf9048acefa49f2921b2653a828d545 (patch) | |
| tree | 1b0342936b71ac5e5f59ad3b4e1e37cea2691ff5 /app/src/main/java/invalid/lena/scrcpy/AudioStream.java | |
| parent | 4ea99f1b77ba547014fb7b7becaa8de2fd671a0b (diff) | |
| download | scrcpy-android-75b1a5569bf9048acefa49f2921b2653a828d545.tar.gz | |
audio: harden Opus playback
Preserve decoder timing and keep blocking speaker writes off codec
callbacks. Playback failures no longer take down video or control.
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/AudioStream.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/AudioStream.java | 59 |
1 files changed, 21 insertions, 38 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/AudioStream.java b/app/src/main/java/invalid/lena/scrcpy/AudioStream.java index 13b39f5..45ea69c 100644 --- a/app/src/main/java/invalid/lena/scrcpy/AudioStream.java +++ b/app/src/main/java/invalid/lena/scrcpy/AudioStream.java @@ -2,7 +2,6 @@ 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. // @@ -19,7 +18,10 @@ import java.util.concurrent.TimeUnit; // Takes a plain InputStream; caller owns stream lifecycle. public final class AudioStream { - private static final long FLAG_CONFIG = 1L << 62; + private static final long FLAG_SESSION = 1L << 63; + private static final long FLAG_CONFIG = 1L << 62; + private static final long FLAG_KEYFRAME = 1L << 61; + private static final long PTS_MASK = ~(FLAG_SESSION | FLAG_CONFIG | FLAG_KEYFRAME); // 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 @@ -28,30 +30,12 @@ public final class AudioStream { 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() { @@ -85,9 +69,13 @@ public final class AudioStream { 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; + boolean playback = true; + try { + sink.start(fourcc); + } catch (RuntimeException e) { + Log.e(e, "audio output disabled"); + playback = false; + } byte[] hdr = new byte[12]; byte[] payload = new byte[16 * 1024]; @@ -97,33 +85,28 @@ public final class AudioStream { long ptsAndFlags = Wire.readBe64(hdr, 0); int size = Wire.readBe32(hdr, 8); boolean cfg = (ptsAndFlags & FLAG_CONFIG) != 0; + long ptsUs = ptsAndFlags & PTS_MASK; if (size <= 0 || size > MAX_FRAME_SIZE) { throw new IOException("audio frame size out of range: " + size); } if (size > payload.length) payload = new byte[size]; Wire.readFully(source, payload, 0, size); - lastPacketAtMs = monotonicMs(); - sink.feed(payload, 0, size, cfg); + if (playback) { + try { + sink.feed(payload, 0, size, ptsUs, cfg); + } catch (RuntimeException e) { + Log.e(e, "audio output disabled"); + playback = false; + } + } 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"); - reportFatal(); - } + if (!stop) Log.e(e, "audio reader"); } 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()); - } } |