#7040·scrcpy

audio-in thread: uncaught CodecException from queueInputBuffer kills the server

Author: ronnie849Created Sep 17, 2026Updated Sep 17, 2026

Environment

  • OS: macOS 15.3.2 (24D81), Apple Silicon (arm64)
  • Scrcpy version: 3.3.4
  • Installation method: Homebrew
  • Device model: Samsung SM-A305N (Galaxy A30), Exynos universal7904, security patch 2023-02-01
  • Android version: 11 (API 30)

Problem

The scrcpy server process was killed outright by an uncaught exception on its audio-in thread. Because the Android runtime's default handler terminates the process, the whole session dies — video and control along with the audio — rather than audio degrading and mirroring continuing.

Captured from the device crash buffer (adb logcat -b crash):

--------- beginning of crash
FATAL EXCEPTION: audio-in
PID: 1810
android.media.MediaCodec$CodecException: Error 0xe
	at android.media.MediaCodec.native_queueInputBuffer(Native Method)
	at android.media.MediaCodec.queueInputBuffer(MediaCodec.java:2559)
	at com.genymobile.scrcpy.audio.AudioEncoder.inputThread(AudioEncoder.java:113)
	at com.genymobile.scrcpy.audio.AudioEncoder.lambda$encode$1$com-genymobile-scrcpy-audio-AudioEncoder(AudioEncoder.java:240)
	at com.genymobile.scrcpy.audio.AudioEncoder$$ExternalSyntheticLambda2.run(D8$$SyntheticClass:0)
	at java.lang.Thread.run(Thread.java:923)

FATAL EXCEPTION in the crash buffer means Android's KillApplicationHandler ran, i.e. the exception was never handled by scrcpy.

Root cause

inputThread calls queueInputBuffer at AudioEncoder.java:113:

mediaCodec.queueInputBuffer(task.index, bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs, bufferInfo.flags);

The thread that runs it catches only checked exceptions — AudioEncoder.java:238-246:

inputThread = new Thread(() -> {
    try {
        inputThread(mediaCodecRef, capture);
    } catch (IOException | InterruptedException e) {
        Ln.e("Audio capture error", e);
    } finally {
        end();
    }
}, "audio-in");

MediaCodec.CodecException is a RuntimeException, so it is not caught here. Note the asymmetry in consequences:

  • An IOException from the line just above (throw new IOException("Could not read audio: " + r)) is logged and the encoder is ended cleanly via end() (AudioEncoder.java:190-193), which releases waitEnded() and lets encode() return normally.
  • A CodecException from the very next line escapes the lambda entirely.

The catch (AudioCaptureException) / catch (Throwable) pair in encode() (AudioEncoder.java:269-276) cannot help — it wraps the encoder thread, not the audio-in thread, so it never sees this exception.

What catches it instead is the default handler at Server.java:228-234, which logs and then delegates to the platform handler:

Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
    Ln.e("Exception on thread " + t, e);
    if (defaultHandler != null) {
        defaultHandler.uncaughtException(t, e);
    }
});

That delegation is what turns a codec hiccup on one stream into process death.

Repro

I do not have reliable steps — the crash was recovered from the device crash buffer after the fact, and I could not reproduce it on demand. For completeness, these three attempts on the same device all succeeded (no crash), so the trigger is intermittent:

  1. scrcpy -s <serial> --no-video --no-control --no-window --no-playback --record=a.opus --record-format=opus --time-limit=5 — OK on SM-A305N (Android 11) and on an Android 14 device.
  2. The same audio-only capture with the default opus codec — OK.
  3. scrcpy -s <serial> --new-display=1920x1080/120 --no-window --no-playback --record=m.mkv --time-limit=6 (video + audio encoded on device) — OK on SM-A305N and on an Android 16 device.

The code path above is reachable regardless of what makes the device codec throw, so the report is about the handling, not the trigger.

Expected

A CodecException while encoding audio should degrade the audio stream — logged, audio disabled, client told — and leave video and control mirroring running, the same way an IOException on the identical thread already does. It should not reach the platform's uncaught handler and kill the server process.

Suggested fixes

Fix A (recommended) — treat it like the IOException path. Widen the catch on the audio-in thread so any runtime failure ends the encoder cleanly instead of escaping:

 inputThread = new Thread(() -> {
     try {
         inputThread(mediaCodecRef, capture);
     } catch (IOException | InterruptedException e) {
         Ln.e("Audio capture error", e);
+    } catch (RuntimeException e) {
+        Ln.e("Audio encoding error", e);
     } finally {
         end();
     }
 }, "audio-in");

Tradeoff: end() unblocks waitEnded() and encode() returns normally, so the failure is reported as a normal end of the audio stream rather than as an error. If that loses information you want, pair it with Fix B.

Fix B — mark it as a disabled stream, like AudioCaptureException. Record the failure and let encode() notify the client via streamer.writeDisableStream(false) so scrcpy continues without audio. This matches what you described in #6600 ("we should disable audio silently only on AudioCaptureException") — the same reasoning applies here, except this path currently has no handler at all. Tradeoff: slightly more plumbing, since the failure has to travel from the audio-in thread back to encode().

Fix C — minimal, keeps the current fatal semantics. If an encoding exception is meant to be fatal, it should still not be delegated to the Android default handler from an encoder thread; scrcpy could shut the session down itself so the client prints a usable message. Tradeoff: the user still loses video for an audio-only fault.

Notes

  • Same shape on the sibling thread. audio-out at AudioEncoder.java:248-261 catches only InterruptedException and IOException, so a CodecException out of getOutputBuffer or releaseOutputBuffer would escape identically. Worth auditing the encoder threads together:
    grep -rn "new Thread((" server/src/main/java/com/genymobile/scrcpy/ -A6 | grep -n "catch (IOException"
    
  • Related: #6600 — same class of problem on the encoder thread, where the maintainer's position was that only AudioCaptureException should silently disable audio. This issue is the audio-in thread case, which never reaches that handler.
  • Related: #3791.
  • Verified against master at 19c1261 as well as the v3.3.4 release I hit it on — inputThread, the catch clause and the Server handler are identical in both.
  • Labels bug and codec look appropriate; I can't set them as a non-member.