From b56b4a759c2f7ef70cd87835cebd9e261c874087 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 6 Apr 2026 21:17:15 +0400 Subject: [PATCH] revert: use ShortArray audio path (DirectByteBuffer causes null ptr crash) DirectByteBuffer.clear() crashes with null pointer in ART's JIT OSR compiled code on Android 16. Revert AudioPipeline to use the original ShortArray writeAudio/readAudio path. The DirectByteBuffer JNI functions remain in WzpEngine.kt and jni_bridge.rs for future use once the OSR issue is resolved. The original SIGBUS from ART GC is rare (~1 crash per 8 min call) and doesn't warrant the DirectByteBuffer approach until we can allocate the buffer as a class field outside the hot loop. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../main/java/com/wzp/audio/AudioPipeline.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/android/app/src/main/java/com/wzp/audio/AudioPipeline.kt b/android/app/src/main/java/com/wzp/audio/AudioPipeline.kt index 363b3c4..9223e50 100644 --- a/android/app/src/main/java/com/wzp/audio/AudioPipeline.kt +++ b/android/app/src/main/java/com/wzp/audio/AudioPipeline.kt @@ -205,8 +205,6 @@ class AudioPipeline(private val context: Context) { Log.i(TAG, "capture started: ${SAMPLE_RATE}Hz mono, buf=$bufSize, aec=${aec?.enabled}, ns=${ns?.enabled}") val pcm = ShortArray(FRAME_SAMPLES) - // DirectByteBuffer for zero-copy JNI (avoids ART GC SIGBUS on Android 16) - val directBuf = ByteBuffer.allocateDirect(FRAME_SAMPLES * 2).order(ByteOrder.LITTLE_ENDIAN) // Debug: PCM file + RMS CSV var pcmOut: BufferedOutputStream? = null var rmsCsv: OutputStreamWriter? = null @@ -226,10 +224,7 @@ class AudioPipeline(private val context: Context) { val read = recorder.read(pcm, 0, FRAME_SAMPLES) if (read > 0) { applyGain(pcm, read, captureGainDb) - // Zero-copy write via DirectByteBuffer (no GC array interaction) - directBuf.clear() - directBuf.asShortBuffer().put(pcm, 0, read) - engine.writeAudioDirect(directBuf, read) + engine.writeAudio(pcm) // Debug: write raw PCM + RMS if (pcmOut != null) { @@ -292,8 +287,6 @@ class AudioPipeline(private val context: Context) { val pcm = ShortArray(FRAME_SAMPLES) val silence = ShortArray(FRAME_SAMPLES) - // DirectByteBuffer for zero-copy JNI (avoids ART GC SIGBUS on Android 16) - val directBuf = ByteBuffer.allocateDirect(FRAME_SAMPLES * 2).order(ByteOrder.LITTLE_ENDIAN) // Debug: PCM file + RMS CSV for playout var pcmOut: BufferedOutputStream? = null var rmsCsv: OutputStreamWriter? = null @@ -310,13 +303,7 @@ class AudioPipeline(private val context: Context) { } try { while (running) { - // Zero-copy read via DirectByteBuffer - directBuf.clear() - val read = engine.readAudioDirect(directBuf, FRAME_SAMPLES) - if (read > 0) { - directBuf.rewind() - directBuf.asShortBuffer().get(pcm, 0, read) - } + val read = engine.readAudio(pcm) if (read >= FRAME_SAMPLES) { applyGain(pcm, read, playoutGainDb) track.write(pcm, 0, read)