From f599ed768eeabbcaf4e2f0ec948243c0be50c43e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 20 Mar 2026 20:57:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E9=97=AA=E9=80=80=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../yunqiinnovation/agent_service/StsAgent.kt | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt index 1fbe7ec72..b0bd08d0c 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/StsAgent.kt @@ -2,9 +2,11 @@ package com.yunqiinnovation.agent_service import android.content.Context import android.media.AudioFormat +import android.media.AudioManager import android.media.AudioRecord import android.media.MediaPlayer import android.media.MediaRecorder +import android.media.ToneGenerator import android.os.Handler import android.os.Looper import android.util.Log @@ -91,6 +93,9 @@ class StsAgent( // ── 预缓冲(VAD 触发前 ~1.5s)──────────────────────────────────────────── private val preBuffer = ArrayDeque() private val preBufferLock = Any() + + // ── 麦克风读写锁(防止 read() 与 stop()/release() 并发导致 native SIGABRT)── + private val micLock = Any() private val PRE_BUFFER_MAX_CHUNKS = 75 // ── 本地 VAD ────────────────────────────────────────────────────────────── @@ -406,8 +411,15 @@ class StsAgent( recordingJob = launch { val buf = ByteArray(RECORD_CHUNK) while (isActive && (inCall || pttRecording)) { - val read = audioRecord?.read(buf, 0, buf.size) ?: break + // 使用非阻塞读 + micLock,确保与 stopMicHard() 中的 stop()/release() 互斥。 + // READ_NON_BLOCKING 让 read() 立即返回(微秒级),锁持有时间极短, + // 彻底消除 releaseBuffer 并发导致的 native SIGABRT。 + val read = synchronized(micLock) { + audioRecord?.read(buf, 0, buf.size, AudioRecord.READ_NON_BLOCKING) ?: -1 + } + if (read < 0) break if (read > 0) { micChunkCount++; onMicAudio(buf.copyOf(read)) } + else delay(5) // 无数据时释放协程让出线程,避免忙等 } } } catch (e: Exception) { @@ -417,8 +429,13 @@ class StsAgent( private fun stopMicHard() { recordingJob?.cancel(); recordingJob = null - try { audioRecord?.stop(); audioRecord?.release() } catch (_: Exception) {} - audioRecord = null + // 拿 micLock 后再 stop()/release(),与录音循环的 read() 互斥, + // 保证 stop() 永远不会在 read() 内部 releaseBuffer 期间被调用。 + synchronized(micLock) { + val ar = audioRecord + audioRecord = null + try { ar?.stop(); ar?.release() } catch (_: Exception) {} + } } private fun startMicWatchdog() { @@ -504,12 +521,24 @@ class StsAgent( } forwardingAudio = true; wsConnecting = false + playReadyBeep() emit("recognition_started", emptyMap()) val cached = synchronized(preBufferLock) { preBuffer.toList().also { preBuffer.clear() } } for (c in cached) feedSendBuffer(c) } + /** 唤醒成功提示音:通知用户可以说话了 */ + private fun playReadyBeep() { + try { + val toneGen = ToneGenerator(AudioManager.STREAM_MUSIC, 60) + toneGen.startTone(ToneGenerator.TONE_PROP_BEEP, 180) + mainHandler.postDelayed({ toneGen.release() }, 300) + } catch (e: Exception) { + Log.w(TAG, "playReadyBeep failed: ${e.message}") + } + } + // ═════════════════════════════════════════════════════════════════════════ // WebSocket // ═════════════════════════════════════════════════════════════════════════