aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/invalid/lena/scrcpy/AudioSink.java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/AudioSink.java')
-rw-r--r--app/src/main/java/invalid/lena/scrcpy/AudioSink.java74
1 files changed, 53 insertions, 21 deletions
diff --git a/app/src/main/java/invalid/lena/scrcpy/AudioSink.java b/app/src/main/java/invalid/lena/scrcpy/AudioSink.java
index 2b3e32d..7f09620 100644
--- a/app/src/main/java/invalid/lena/scrcpy/AudioSink.java
+++ b/app/src/main/java/invalid/lena/scrcpy/AudioSink.java
@@ -12,6 +12,8 @@ import android.os.HandlerThread;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayDeque;
+import java.util.Deque;
// Audio output: AudioTrack writing 48 kHz stereo 16-bit PCM. The
// upstream feed is either raw PCM (passthrough) or Opus packets
@@ -21,22 +23,32 @@ public final class AudioSink implements AudioFrames {
private static final int SAMPLE_RATE = 48_000;
private static final int CHANNEL_OUT = AudioFormat.CHANNEL_OUT_STEREO;
private static final int ENCODING = AudioFormat.ENCODING_PCM_16BIT;
+ private static final int MAX_PENDING_OPUS = 16;
// Defaults documented at <https://developer.android.com/reference/android/media/MediaCodec#CSD>.
private static final long DEFAULT_PRE_ROLL_NS = 80_000_000L;
private AudioTrack track;
- private MediaCodec opusCodec;
+ private volatile MediaCodec opusCodec;
private HandlerThread opusThread;
private Handler opusHandler;
private boolean opusConfigured;
private int fourcc;
+ private final Object lock = new Object();
+ private final Runnable onFatalError;
+ private final Deque<Integer> freeOpusInputs = new ArrayDeque<>();
+ private final Deque<byte[]> pendingOpus = new ArrayDeque<>(MAX_PENDING_OPUS);
+
private volatile boolean released;
public volatile long frames; // public read for the status overlay
private long droppedBytes;
private long lastDropLogMs;
+ public AudioSink(Runnable onFatalError) {
+ this.onFatalError = onFatalError;
+ }
+
@Override
public void start(int fourcc) {
this.fourcc = fourcc;
@@ -71,11 +83,14 @@ public final class AudioSink implements AudioFrames {
} catch (IOException e) {
throw new IllegalStateException("audio sink: no opus decoder", e);
}
- opusCodec.setCallback(new MediaCodec.Callback() {
- @Override public void onInputBufferAvailable(MediaCodec mc, int idx) {
- // Frames are queued synchronously from feed(); we
- // don't pull on this callback. This handler exists
- // so MediaCodec's async machinery is wired up.
+ opusCodec.setCallback(new MediaCodec.Callback() {
+ @Override public void onInputBufferAvailable(MediaCodec mc, int idx) {
+ synchronized (lock) {
+ if (released || mc != opusCodec) return;
+ byte[] packet = pendingOpus.pollFirst();
+ if (packet == null) freeOpusInputs.offerLast(idx);
+ else submitOpus(mc, idx, packet);
+ }
}
@Override public void onOutputBufferAvailable(MediaCodec mc, int idx,
MediaCodec.BufferInfo info) {
@@ -146,21 +161,33 @@ public final class AudioSink implements AudioFrames {
return;
}
if (!opusConfigured) return;
- // Encoded packet → MediaCodec input.
- int idx;
- try { idx = opusCodec.dequeueInputBuffer(0); }
- catch (IllegalStateException e) { return; }
- if (idx < 0) {
- // No input buffer right now; drop the packet. Opus is forgiving
- // about gaps for short stalls.
- return;
+ byte[] packet = new byte[len];
+ System.arraycopy(data, off, packet, 0, len);
+ synchronized (lock) {
+ if (released || opusCodec == null) return;
+ Integer idx = freeOpusInputs.pollFirst();
+ if (idx != null) {
+ submitOpus(opusCodec, idx, packet);
+ return;
+ }
+ if (pendingOpus.size() == MAX_PENDING_OPUS) pendingOpus.pollFirst();
+ pendingOpus.offerLast(packet);
}
+ }
+
+ // Must be called with lock held. Async MediaCodec input indices belong
+ // to the codec instance that delivered them.
+ private void submitOpus(MediaCodec codec, int idx, byte[] packet) {
try {
- ByteBuffer in = opusCodec.getInputBuffer(idx);
- if (in == null) return;
+ ByteBuffer in = codec.getInputBuffer(idx);
+ if (in == null || packet.length > in.capacity()) {
+ Log.e("audio sink: opus packet exceeds codec input (%d bytes)", packet.length);
+ if (onFatalError != null) onFatalError.run();
+ return;
+ }
in.clear();
- in.put(data, off, len);
- opusCodec.queueInputBuffer(idx, 0, len, 0, 0);
+ in.put(packet);
+ codec.queueInputBuffer(idx, 0, packet.length, 0, 0);
frames++;
} catch (IllegalStateException e) {
Log.w("audio sink: opus queueInputBuffer: %s", e);
@@ -194,9 +221,14 @@ public final class AudioSink implements AudioFrames {
@Override
public void release() {
- released = true;
- MediaCodec c = opusCodec;
- opusCodec = null;
+ MediaCodec c;
+ synchronized (lock) {
+ released = true;
+ c = opusCodec;
+ opusCodec = null;
+ freeOpusInputs.clear();
+ pendingOpus.clear();
+ }
HandlerThread ht = opusThread;
opusThread = null;
opusHandler = null;