diff options
Diffstat (limited to 'app/src/main/java/invalid/lena/scrcpy/AudioSink.java')
| -rw-r--r-- | app/src/main/java/invalid/lena/scrcpy/AudioSink.java | 74 |
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; |