aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/AudioStream.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/AudioStream.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/AudioStream.java45
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());
+ }
}