|
|
@ -22,6 +22,7 @@ import com.yunqiinnovation.azure_speech.tools.RecordFile |
|
|
import com.microsoft.cognitiveservices.speech.audio.AudioConfig |
|
|
import com.microsoft.cognitiveservices.speech.audio.AudioConfig |
|
|
import com.microsoft.cognitiveservices.speech.audio.AudioInputStream |
|
|
import com.microsoft.cognitiveservices.speech.audio.AudioInputStream |
|
|
import com.microsoft.cognitiveservices.speech.audio.PushAudioInputStream |
|
|
import com.microsoft.cognitiveservices.speech.audio.PushAudioInputStream |
|
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -52,8 +53,7 @@ class AzureAsrHelper(private val context: Context) { |
|
|
var audioSourceType = AudioSourceType.MICROPHONE |
|
|
var audioSourceType = AudioSourceType.MICROPHONE |
|
|
|
|
|
|
|
|
// 音频处理 |
|
|
// 音频处理 |
|
|
private var audioStream: AudioStream? = null |
|
|
var audioStream: AudioStream? = null |
|
|
private var externalAudioStream: ExternalAudioPullStream? = null |
|
|
|
|
|
|
|
|
|
|
|
// 音频模式管理 |
|
|
// 音频模式管理 |
|
|
private var audioManager: AudioManager? = null |
|
|
private var audioManager: AudioManager? = null |
|
|
@ -61,7 +61,6 @@ class AzureAsrHelper(private val context: Context) { |
|
|
|
|
|
|
|
|
// 录音文件处理 |
|
|
// 录音文件处理 |
|
|
var recordfile: RecordFile? = null |
|
|
var recordfile: RecordFile? = null |
|
|
var isPaused = true |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 音频来源类型 |
|
|
* 音频来源类型 |
|
|
@ -74,29 +73,6 @@ class AzureAsrHelper(private val context: Context) { |
|
|
EXTERNAL |
|
|
EXTERNAL |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 获取设备支持的最佳音频格式 |
|
|
|
|
|
* 优先选择16000Hz,若不支持则降级到8000Hz |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun getOptimalAudioFormat(): AudioStreamFormat { |
|
|
|
|
|
// 支持的采样率列表(按优先级排序) |
|
|
|
|
|
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) |
|
|
|
|
|
|
|
|
|
|
|
// 查找设备支持的最佳采样率 |
|
|
|
|
|
val sampleRate = supportedSampleRates.firstOrNull { rate -> |
|
|
|
|
|
val bufferSize = AudioRecord.getMinBufferSize( |
|
|
|
|
|
rate, |
|
|
|
|
|
AudioFormat.CHANNEL_IN_MONO, |
|
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT |
|
|
|
|
|
) |
|
|
|
|
|
bufferSize > 0 // 返回正值表示支持 |
|
|
|
|
|
} ?: 16000 // 默认回退值 |
|
|
|
|
|
|
|
|
|
|
|
Log.i(tag, "使用采样率: ${sampleRate}Hz") |
|
|
|
|
|
|
|
|
|
|
|
// 创建对应的音频格式 |
|
|
|
|
|
return AudioStreamFormat.getWaveFormatPCM(sampleRate.toLong(), 16, 1) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 初始化Azure语音服务 |
|
|
* 初始化Azure语音服务 |
|
|
@ -122,7 +98,6 @@ class AzureAsrHelper(private val context: Context) { |
|
|
|
|
|
|
|
|
// 释放之前的资源 |
|
|
// 释放之前的资源 |
|
|
dispose() |
|
|
dispose() |
|
|
|
|
|
|
|
|
// 初始化音频管理器 |
|
|
// 初始化音频管理器 |
|
|
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|
|
audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager |
|
|
originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL |
|
|
originalAudioMode = audioManager?.mode ?: AudioManager.MODE_NORMAL |
|
|
@ -169,47 +144,30 @@ class AzureAsrHelper(private val context: Context) { |
|
|
} |
|
|
} |
|
|
// 录音文件类 |
|
|
// 录音文件类 |
|
|
recordfile = RecordFile; |
|
|
recordfile = RecordFile; |
|
|
//进入界面手机麦克风就被占用,导致其他app无法使用麦克风,在开始录音再去申请 |
|
|
|
|
|
// // 创建识别器 |
|
|
|
|
|
// val setupSuccess = setupRecognizer() |
|
|
|
|
|
// if (setupSuccess) { |
|
|
|
|
|
// // 优化:初始化完成后进行预热 |
|
|
|
|
|
// warmupRecognizer() |
|
|
|
|
|
// } |
|
|
|
|
|
return true |
|
|
return true |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(tag, "初始化失败: ${e.message}") |
|
|
Log.e(tag, "初始化失败: ${e.message}") |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
private fun isRecognizerValid(): Boolean { |
|
|
|
|
|
return recognizer != null |
|
|
|
|
|
} |
|
|
/** |
|
|
/** |
|
|
* 设置识别器 |
|
|
* 设置识别器 |
|
|
*/ |
|
|
*/ |
|
|
private fun setupRecognizer(): Boolean { |
|
|
private fun setupRecognizer(): Boolean { |
|
|
try { |
|
|
try { |
|
|
|
|
|
// 如果正在进行连续识别,先停止 |
|
|
// 清理旧的识别器 |
|
|
if (isContinuousRecognitionActive) { |
|
|
recognizer?.close() |
|
|
// 直接停止,不等待结果 |
|
|
recognizer = null |
|
|
recognizer?.stopContinuousRecognitionAsync() |
|
|
|
|
|
isContinuousRecognitionActive = false |
|
|
// 设置音频配置 |
|
|
|
|
|
when (audioSourceType) { |
|
|
|
|
|
AudioSourceType.MICROPHONE -> { |
|
|
|
|
|
// 总是使用拉流方式并启用回音消除 |
|
|
|
|
|
Log.i(tag, "使用拉流方式并启用回音消除") |
|
|
|
|
|
setupMicrophoneStream() |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
setupMicrophoneStream() |
|
|
|
|
|
|
|
|
AudioSourceType.EXTERNAL -> { |
|
|
|
|
|
// 改用拉流方式处理外部音频 |
|
|
|
|
|
Log.d(tag, "改用拉流方式处理外部音频") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setupExternalAudioStream() |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//创建识别器 |
|
|
//创建识别器 |
|
|
recognizer = if (isAutoDetectLanguage) { |
|
|
recognizer = if (isAutoDetectLanguage) { |
|
|
@ -234,22 +192,13 @@ class AzureAsrHelper(private val context: Context) { |
|
|
private fun setupMicrophoneStream() { |
|
|
private fun setupMicrophoneStream() { |
|
|
try { |
|
|
try { |
|
|
Log.d(tag, "设置麦克风流 - 使用拉流方式: }") |
|
|
Log.d(tag, "设置麦克风流 - 使用拉流方式: }") |
|
|
restoreOriginalAudioState() |
|
|
|
|
|
if (audioStream == null) { |
|
|
if (audioStream == null) { |
|
|
// 创建外部音频拉流对象 |
|
|
// 创建外部音频拉流对象 |
|
|
audioStream = AudioStream() |
|
|
audioStream = AudioStream() |
|
|
audioStream!!.initAudioRecord() |
|
|
audioStream!!.initAudioRecord() |
|
|
} |
|
|
} |
|
|
// else |
|
|
|
|
|
// { |
|
|
|
|
|
// audioStream.stopCaptureThread() |
|
|
|
|
|
// audioStream.startCaptureThread() |
|
|
|
|
|
// } |
|
|
|
|
|
// 创建麦克风流 |
|
|
|
|
|
// microphoneStream = MicrophoneStream() |
|
|
|
|
|
|
|
|
|
|
|
// 创建音频配置 - 正确使用fromStreamInput方法,只传入回调 |
|
|
|
|
|
// audioConfig = AudioConfig.fromStreamInput(microphoneStream) |
|
|
|
|
|
audioConfig = AudioConfig.fromStreamInput(audioStream!!.pushAudioStream) |
|
|
audioConfig = AudioConfig.fromStreamInput(audioStream!!.pushAudioStream) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -259,60 +208,6 @@ class AzureAsrHelper(private val context: Context) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 预热识别器(减少首次识别延迟) |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun warmupRecognizer() { |
|
|
|
|
|
try { |
|
|
|
|
|
// 创建极短的音频数据进行预热 |
|
|
|
|
|
val warmupData = ByteArray(320) // 10ms 16kHz 单声道 |
|
|
|
|
|
// 模拟静音数据 |
|
|
|
|
|
warmupData.fill(0) |
|
|
|
|
|
|
|
|
|
|
|
// 如果是外部音频流,进行预热 |
|
|
|
|
|
if (audioSourceType == AudioSourceType.EXTERNAL) { |
|
|
|
|
|
externalAudioStream?.pushAudio(warmupData) |
|
|
|
|
|
} |
|
|
|
|
|
Log.d(tag, "ASR预热完成") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
// 预热失败不影响正常使用 |
|
|
|
|
|
Log.d(tag, "ASR预热失败: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 设置外部音频流 - 使用拉流方式 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun setupExternalAudioStream() { |
|
|
|
|
|
try { |
|
|
|
|
|
Log.d(tag, "设置外部音频流 - 使用拉流方式: }") |
|
|
|
|
|
//disableBluetoothAudio() |
|
|
|
|
|
// 创建外部音频拉流对象 |
|
|
|
|
|
externalAudioStream = ExternalAudioPullStream() |
|
|
|
|
|
|
|
|
|
|
|
// 创建音频配置 |
|
|
|
|
|
audioConfig = AudioConfig.fromStreamInput(externalAudioStream) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(tag, "设置外部音频流失败: ${e.message}") |
|
|
|
|
|
externalAudioStream = null |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 向音频流写入音频数据 |
|
|
|
|
|
* 仅当音频源设置为EXTERNAL时有效 |
|
|
|
|
|
* |
|
|
|
|
|
* @param data 音频数据字节数组 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun pushAudioData(data: ByteArray) { |
|
|
|
|
|
if (audioSourceType != AudioSourceType.EXTERNAL) { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
// 使用拉流模式,将数据推入队列 |
|
|
|
|
|
externalAudioStream?.pushAudio(data) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 执行一次性语音识别 |
|
|
* 执行一次性语音识别 |
|
|
@ -327,21 +222,18 @@ class AzureAsrHelper(private val context: Context) { |
|
|
callback.onError("语音服务未初始化") |
|
|
callback.onError("语音服务未初始化") |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
this.audioSourceType = audioSourceType |
|
|
this.audioSourceType = audioSourceType |
|
|
// 确保不在连续识别中 |
|
|
// 确保不在连续识别中 |
|
|
if (isContinuousRecognitionActive) { |
|
|
if (isContinuousRecognitionActive) { |
|
|
stopContinuousRecognition() |
|
|
stopContinuousRecognition() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 重置识别器 |
|
|
|
|
|
if (!setupRecognizer()) { |
|
|
|
|
|
callback.onError("重置识别器失败") |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 启动音频处理 |
|
|
// 启动音频处理 |
|
|
startAudioProcessing() |
|
|
audioStream!!.startAudioRecord() |
|
|
|
|
|
|
|
|
// 执行同步识别 |
|
|
// 执行同步识别 |
|
|
val result = recognizer?.recognizeOnceAsync()?.get() |
|
|
val result = recognizer?.recognizeOnceAsync()?.get() |
|
|
@ -369,43 +261,43 @@ class AzureAsrHelper(private val context: Context) { |
|
|
* @return 是否成功开始识别 |
|
|
* @return 是否成功开始识别 |
|
|
*/ |
|
|
*/ |
|
|
fun startContinuousRecognition( |
|
|
fun startContinuousRecognition( |
|
|
callback: ContinuousRecognizeCallback, |
|
|
|
|
|
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE |
|
|
audioSourceType: AudioSourceType = AudioSourceType.MICROPHONE |
|
|
): Boolean { |
|
|
): Boolean { |
|
|
if (speechConfig == null) { |
|
|
|
|
|
callback.onError("语音服务未初始化") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(tag, "startContinuousRecognition:$isContinuousRecognitionActive ") |
|
|
if (isContinuousRecognitionActive) { |
|
|
if (isContinuousRecognitionActive) { |
|
|
return true |
|
|
return true |
|
|
} |
|
|
} |
|
|
this.audioSourceType = audioSourceType |
|
|
if (!isRecognizerValid()) { |
|
|
// 重置识别器 |
|
|
Log.w(tag, "识别器已失效,正在重新创建...") |
|
|
if (!setupRecognizer()) { |
|
|
if (!setupRecognizer()) { |
|
|
callback.onError("重置识别器失败") |
|
|
Log.e(tag, "重新创建识别器失败") |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
this.audioSourceType = audioSourceType |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
|
|
|
|
|
|
// 设置各种事件监听 |
|
|
|
|
|
setupEventListeners(callback) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Log.d(tag, "startContinuousRecognition: ") |
|
|
// 启动音频处理 |
|
|
// 启动音频处理 |
|
|
//startAudioProcessing() |
|
|
//startAudioProcessing() |
|
|
|
|
|
|
|
|
// 开始连续识别 |
|
|
// 开始连续识别 |
|
|
|
|
|
|
|
|
|
|
|
// 启动音频处理 |
|
|
|
|
|
audioStream!!.startAudioRecord() |
|
|
recognizer?.startContinuousRecognitionAsync() |
|
|
recognizer?.startContinuousRecognitionAsync() |
|
|
audioStream?.startCaptureThread() |
|
|
|
|
|
isContinuousRecognitionActive = true |
|
|
isContinuousRecognitionActive = true |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return true |
|
|
return true |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
stopAudioProcessing() |
|
|
audioStream!!.stopMicrophoneCapture() |
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
callback.onError("启动连续识别失败: ${e.message}") |
|
|
|
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -413,7 +305,20 @@ class AzureAsrHelper(private val context: Context) { |
|
|
/** |
|
|
/** |
|
|
* 设置事件监听器 |
|
|
* 设置事件监听器 |
|
|
*/ |
|
|
*/ |
|
|
private fun setupEventListeners(callback: ContinuousRecognizeCallback) { |
|
|
fun setupEventListeners(callback: ContinuousRecognizeCallback): Boolean { |
|
|
|
|
|
Log.d(tag, "设置ssssss监听器:${speechConfig} ") |
|
|
|
|
|
if (speechConfig == null) { |
|
|
|
|
|
callback.onError("语音服务未初始化") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 重设识别器 |
|
|
|
|
|
if (!setupRecognizer()) { |
|
|
|
|
|
Log.d(tag, "初始化失败: ") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
Log.d(tag, "设置ssssss监听器: ") |
|
|
// 优化:识别中事件 - 添加文本长度检查 |
|
|
// 优化:识别中事件 - 添加文本长度检查 |
|
|
recognizer?.recognizing?.addEventListener( |
|
|
recognizer?.recognizing?.addEventListener( |
|
|
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
|
|
EventHandler<SpeechRecognitionEventArgs> { _, event -> |
|
|
@ -462,7 +367,7 @@ class AzureAsrHelper(private val context: Context) { |
|
|
|
|
|
|
|
|
callback.onSessionStopped() |
|
|
callback.onSessionStopped() |
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
stopAudioProcessing() |
|
|
// stopAudioProcessing() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
) |
|
|
) |
|
|
@ -473,8 +378,15 @@ class AzureAsrHelper(private val context: Context) { |
|
|
val errorDetails = event.errorDetails ?: "未知错误" |
|
|
val errorDetails = event.errorDetails ?: "未知错误" |
|
|
val reason = event.reason.toString() |
|
|
val reason = event.reason.toString() |
|
|
callback.onCanceled(reason, errorDetails) |
|
|
callback.onCanceled(reason, errorDetails) |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
callback.onError("启动连续识别失败: ${e.message}") |
|
|
|
|
|
return false |
|
|
|
|
|
} |
|
|
|
|
|
return true |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -494,36 +406,34 @@ class AzureAsrHelper(private val context: Context) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
if (recognizer == null) { |
|
|
|
|
|
Log.w(tag, "识别器为空,重置状态") |
|
|
|
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
return true |
|
|
|
|
|
} |
|
|
|
|
|
if (audioSourceType == AudioSourceType.EXTERNAL) { |
|
|
if (audioSourceType == AudioSourceType.EXTERNAL) { |
|
|
pushAudioData(ByteArray(0)) |
|
|
//pushAudioData(ByteArray(0)) |
|
|
} |
|
|
} |
|
|
|
|
|
Log.d(tag, "停止连续语音识别: ") |
|
|
// 直接停止连续识别(SDK内部已是异步操作) |
|
|
// 直接停止连续识别(SDK内部已是异步操作) |
|
|
recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) |
|
|
recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) |
|
|
|
|
|
|
|
|
// 停止音频处理 |
|
|
// 停止音频处理 |
|
|
stopAudioProcessing() |
|
|
audioStream!!.stopMicrophoneCapture() |
|
|
// 会话结束事件会设置isContinuousRecognitionActive = false |
|
|
// 会话结束事件会设置isContinuousRecognitionActive = false |
|
|
return true |
|
|
return true |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
// 强制重置状态 |
|
|
// 强制重置状态 |
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
Log.e(tag, "停止连续识别失败: ${e.message}") |
|
|
Log.e(tag, "停止连续识别失败: ${e.message}") |
|
|
|
|
|
recognizer?.stopContinuousRecognitionAsync() |
|
|
// 停止音频处理 |
|
|
// 停止音频处理 |
|
|
stopAudioProcessing() |
|
|
audioStream!!.stopMicrophoneCapture() |
|
|
// 尝试强制关闭识别器 |
|
|
// 尝试强制关闭识别器 |
|
|
try { |
|
|
// try { |
|
|
recognizer?.close() |
|
|
// recognizer?.close() |
|
|
recognizer = null |
|
|
// recognizer = null |
|
|
} catch (ex: Exception) { |
|
|
// } catch (ex: Exception) { |
|
|
Log.e(tag, "关闭识别器失败: ${ex.message}") |
|
|
// Log.e(tag, "关闭识别器失败: ${ex.message}") |
|
|
} |
|
|
// } |
|
|
|
|
|
|
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
@ -545,7 +455,7 @@ class AzureAsrHelper(private val context: Context) { |
|
|
recognizer?.stopContinuousRecognitionAsync() |
|
|
recognizer?.stopContinuousRecognitionAsync() |
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
} |
|
|
} |
|
|
|
|
|
// recognizer?.stopContinuousRecognitionAsync()?.get(1000, TimeUnit.MILLISECONDS) |
|
|
// 停止音频处理 |
|
|
// 停止音频处理 |
|
|
stopAudioProcessing() |
|
|
stopAudioProcessing() |
|
|
// 停止录音 |
|
|
// 停止录音 |
|
|
@ -566,7 +476,7 @@ class AzureAsrHelper(private val context: Context) { |
|
|
// 确保状态被重置 |
|
|
// 确保状态被重置 |
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
//microphoneStream = null |
|
|
//microphoneStream = null |
|
|
externalAudioStream = null |
|
|
|
|
|
audioConfig = null |
|
|
audioConfig = null |
|
|
recognizer = null |
|
|
recognizer = null |
|
|
speechConfig = null |
|
|
speechConfig = null |
|
|
@ -575,7 +485,7 @@ class AzureAsrHelper(private val context: Context) { |
|
|
|
|
|
|
|
|
isContinuousRecognitionActive = false |
|
|
isContinuousRecognitionActive = false |
|
|
//microphoneStream = null |
|
|
//microphoneStream = null |
|
|
externalAudioStream = null |
|
|
|
|
|
audioConfig = null |
|
|
audioConfig = null |
|
|
recognizer = null |
|
|
recognizer = null |
|
|
speechConfig = null |
|
|
speechConfig = null |
|
|
@ -583,30 +493,13 @@ class AzureAsrHelper(private val context: Context) { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 音频处理相关方法 |
|
|
// 音频处理相关方法 |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 启动音频处理 |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun startAudioProcessing() { |
|
|
|
|
|
when (audioSourceType) { |
|
|
|
|
|
AudioSourceType.MICROPHONE -> { |
|
|
|
|
|
// 拉流模式不需要额外启动,SDK会自动拉取数据 |
|
|
|
|
|
// 无需执行任何操作 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
AudioSourceType.EXTERNAL -> { |
|
|
|
|
|
// 外部音频数据模式下不需要启动处理,等待外部调用pushAudioData |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 停止音频处理 |
|
|
* 停止音频处理 |
|
|
*/ |
|
|
*/ |
|
|
private fun stopAudioProcessing() { |
|
|
private fun stopAudioProcessing() { |
|
|
audioStream?.let { |
|
|
audioStream?.let { |
|
|
try { |
|
|
try { |
|
|
it.stopMicrophoneCapture() |
|
|
Log.e(tag, "停止音频处理: }") |
|
|
it.releaseAudioResources() |
|
|
it.releaseAudioResources() |
|
|
|
|
|
|
|
|
it.pushAudioStream?.close() |
|
|
it.pushAudioStream?.close() |
|
|
@ -617,49 +510,9 @@ class AzureAsrHelper(private val context: Context) { |
|
|
e.printStackTrace() |
|
|
e.printStackTrace() |
|
|
} |
|
|
} |
|
|
} // 关闭音频流(根据实际实现可能需要) |
|
|
} // 关闭音频流(根据实际实现可能需要) |
|
|
externalAudioStream?.let { |
|
|
|
|
|
try { |
|
|
|
|
|
it.close() |
|
|
|
|
|
externalAudioStream = null |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(tag, "关闭外部音频流失败: ${e.message}") |
|
|
|
|
|
e.printStackTrace() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
暂停音频输入 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun audioPause() { |
|
|
|
|
|
Log.d(tag, "关闭麦克风stopAudioPause: ") |
|
|
|
|
|
// 清理旧的识别器 |
|
|
|
|
|
isPaused = true |
|
|
|
|
|
// 如果正在进行连续识别,先停止 |
|
|
|
|
|
if (isContinuousRecognitionActive) { |
|
|
|
|
|
// 直接停止,不等待结果 |
|
|
|
|
|
recognizer?.stopContinuousRecognitionAsync() |
|
|
|
|
|
isContinuousRecognitionActive = false |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
继续音频输入 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun audioContinue() { |
|
|
|
|
|
Log.d(tag, "继续麦克风stopAudioPause: ") |
|
|
|
|
|
// 如果正在进行连续识别,先停止 |
|
|
|
|
|
if (!isContinuousRecognitionActive) { |
|
|
|
|
|
// 直接停止,不等待结果 |
|
|
|
|
|
recognizer?.startContinuousRecognitionAsync() |
|
|
|
|
|
isContinuousRecognitionActive = true |
|
|
|
|
|
} |
|
|
} |
|
|
// 开始连续识别 |
|
|
|
|
|
isPaused = false |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 禁用蓝牙音频功能,切换回正常音频模式 |
|
|
* 禁用蓝牙音频功能,切换回正常音频模式 |
|
|
@ -753,21 +606,22 @@ class AzureAsrHelper(private val context: Context) { |
|
|
recordfile!!.closeFile(isSave) |
|
|
recordfile!!.closeFile(isSave) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 麦克风流 - 拉流模式 |
|
|
* 麦克风流 - 拉流模式 |
|
|
* 实现PullAudioInputStreamCallback,为Azure SDK提供音频数据 |
|
|
* 实现PullAudioInputStreamCallback,为Azure SDK提供音频数据 |
|
|
*/ |
|
|
*/ |
|
|
inner class AudioStream { |
|
|
inner class AudioStream { |
|
|
|
|
|
private val bufferSize = 4096 // 可根据需要调整 |
|
|
private var echoCanceler: AcousticEchoCanceler? = null |
|
|
|
|
|
private var noiseSuppressor: NoiseSuppressor? = null |
|
|
|
|
|
private var automaticGainControl: AutomaticGainControl? = null |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var audioRecord: AudioRecord? = null |
|
|
var audioRecord: AudioRecord? = null |
|
|
private var captureThread: Thread? = null |
|
|
|
|
|
var pushAudioStream: PushAudioInputStream? = null |
|
|
var pushAudioStream: PushAudioInputStream? = null |
|
|
|
|
|
|
|
|
|
|
|
// 新增:用于异步写入的队列和线程 |
|
|
|
|
|
private val writeQueue = LinkedBlockingQueue<ByteArray>() |
|
|
|
|
|
private val isRunning = AtomicBoolean(false)// 控制线程是否继续存在 |
|
|
|
|
|
private val isWriting = AtomicBoolean(false) // 控制是否应该写入数据 |
|
|
|
|
|
private var writeThread: Thread? = null |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 音频配置 |
|
|
// 音频配置 |
|
|
|
|
|
|
|
|
@ -775,12 +629,130 @@ class AzureAsrHelper(private val context: Context) { |
|
|
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT |
|
|
private val audioFormat = AudioFormat.ENCODING_PCM_16BIT |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* 开始麦克风捕获 |
|
|
* 获取设备支持的最佳音频格式 |
|
|
|
|
|
* 优先选择16000Hz,若不支持则降级到8000Hz |
|
|
|
|
|
*/ |
|
|
|
|
|
private fun getOptimalAudioFormat(): AudioStreamFormat { |
|
|
|
|
|
// 支持的采样率列表(按优先级排序) |
|
|
|
|
|
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) |
|
|
|
|
|
|
|
|
|
|
|
// 查找设备支持的最佳采样率 |
|
|
|
|
|
val sampleRate = supportedSampleRates.firstOrNull { rate -> |
|
|
|
|
|
val bufferSize = AudioRecord.getMinBufferSize( |
|
|
|
|
|
rate, |
|
|
|
|
|
AudioFormat.CHANNEL_IN_MONO, |
|
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT |
|
|
|
|
|
) |
|
|
|
|
|
bufferSize > 0 // 返回正值表示支持 |
|
|
|
|
|
} ?: 16000 // 默认回退值 |
|
|
|
|
|
|
|
|
|
|
|
Log.i(tag, "使用采样率: ${sampleRate}Hz") |
|
|
|
|
|
|
|
|
|
|
|
// 创建对应的音频格式 |
|
|
|
|
|
return AudioStreamFormat.getWaveFormatPCM(sampleRate.toLong(), 16, 1) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 初始化 |
|
|
*/ |
|
|
*/ |
|
|
fun initAudioRecord() { |
|
|
fun initAudioRecord() { |
|
|
|
|
|
|
|
|
val format = getOptimalAudioFormat() |
|
|
val format = getOptimalAudioFormat() |
|
|
pushAudioStream = AudioInputStream.createPushStream(format) |
|
|
pushAudioStream = AudioInputStream.createPushStream(format) |
|
|
|
|
|
isRunning.set(true) |
|
|
|
|
|
startWriteThread() // 再启动数据读取线程 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun startWriteThread() { |
|
|
|
|
|
writeThread = Thread { |
|
|
|
|
|
try { |
|
|
|
|
|
|
|
|
|
|
|
while (isRunning.get()) { |
|
|
|
|
|
// 等待录音信号 |
|
|
|
|
|
if (!isWriting.get()) { |
|
|
|
|
|
Thread.sleep(10) // 短暂休眠避免空转 |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
var data: ByteArray? = null |
|
|
|
|
|
var bytesToWrite = 0 |
|
|
|
|
|
// 情况1:正在录制中 -> 直接从AudioRecord读取 |
|
|
|
|
|
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { |
|
|
|
|
|
data = ByteArray(bufferSize) |
|
|
|
|
|
val bytesRead = audioRecord?.read(data, 0, bufferSize) ?: -1 |
|
|
|
|
|
|
|
|
|
|
|
when { |
|
|
|
|
|
bytesRead < 0 -> { |
|
|
|
|
|
Log.e("tag", "读取音频失败,错误码: $bytesRead") |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
bytesRead == 0 -> continue // 无数据可读 |
|
|
|
|
|
else -> bytesToWrite = bytesRead // 有效数据 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
// 情况2:不在录制但队列有数据 -> 从队列获取 |
|
|
|
|
|
else if (writeQueue.isNotEmpty()) { |
|
|
|
|
|
data = writeQueue.poll() |
|
|
|
|
|
Log.d("tag", "写入数据: ${ data?.size}") |
|
|
|
|
|
bytesToWrite = data?.size ?: 0 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 确保有有效数据再写入 |
|
|
|
|
|
if (data != null && bytesToWrite > 0) { |
|
|
|
|
|
// 处理实际读取长度 < bufferSize 的情况 |
|
|
|
|
|
val finalData = |
|
|
|
|
|
if (bytesToWrite < data.size) data.copyOf(bytesToWrite) else data |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
pushAudioStream?.write(finalData) |
|
|
|
|
|
recordfile?.saveAudioDataToWav(finalData) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e("tag", "写入失败: ${e.message}") |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Thread.yield() // 避免空转消耗CPU |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e("tag", "写入线程异常: ${e.stackTraceToString()}") |
|
|
|
|
|
} finally { |
|
|
|
|
|
Log.d("tag", "音频写入线程退出") |
|
|
|
|
|
writeQueue.clear() |
|
|
|
|
|
} |
|
|
|
|
|
}.apply { |
|
|
|
|
|
name = "AudioWriteThread" |
|
|
|
|
|
start() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 外部音频输入 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun saveAudioDataTo(buffer: ByteArray) { |
|
|
|
|
|
|
|
|
|
|
|
if (audioSourceType == AudioSourceType.MICROPHONE) return |
|
|
|
|
|
// 放入队列,由写线程写入 |
|
|
|
|
|
writeQueue.offer(buffer.copyOf()) |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fun startAudioRecord() { |
|
|
|
|
|
|
|
|
|
|
|
isWriting.set(true) |
|
|
|
|
|
|
|
|
|
|
|
when (audioSourceType) { |
|
|
|
|
|
|
|
|
|
|
|
AudioSourceType.MICROPHONE -> runMicrophoneCapture() |
|
|
|
|
|
AudioSourceType.EXTERNAL -> runExternalCapture() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private fun runMicrophoneCapture() { |
|
|
|
|
|
try { |
|
|
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) |
|
|
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) |
|
|
// 查找设备支持的最佳采样率 |
|
|
// 查找设备支持的最佳采样率 |
|
|
val sampleRate = supportedSampleRates.firstOrNull { rate -> |
|
|
val sampleRate = supportedSampleRates.firstOrNull { rate -> |
|
|
@ -824,89 +796,38 @@ class AzureAsrHelper(private val context: Context) { |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 检查初始化状态 |
|
|
// 检查初始化状态 |
|
|
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) { |
|
|
if (audioRecord?.state != AudioRecord.STATE_INITIALIZED) { |
|
|
throw IllegalStateException("AudioRecord初始化失败") |
|
|
throw IllegalStateException("AudioRecord初始化失败") |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
startCaptureThread() // 再启动数据读取线程 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. 单独封装线程启动逻辑 |
|
|
|
|
|
fun startCaptureThread() { |
|
|
|
|
|
try { |
|
|
|
|
|
// 确保之前的线程已停止 |
|
|
|
|
|
stopCaptureThread() |
|
|
|
|
|
|
|
|
|
|
|
// 开始录音 |
|
|
// 4. 开始录音 |
|
|
audioRecord?.startRecording() |
|
|
audioRecord?.startRecording() |
|
|
|
|
|
Log.d("TAG", "录音开始,采样率: $sampleRate Hz") |
|
|
|
|
|
|
|
|
captureThread = Thread({ |
|
|
|
|
|
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO) |
|
|
|
|
|
|
|
|
|
|
|
val supportedSampleRates = intArrayOf(16000, 8000, 11025, 22050, 44100) |
|
|
|
|
|
var bufferSize = 0 |
|
|
|
|
|
val sampleRate = supportedSampleRates.firstOrNull { rate -> |
|
|
|
|
|
bufferSize = AudioRecord.getMinBufferSize( |
|
|
|
|
|
rate, |
|
|
|
|
|
AudioFormat.CHANNEL_IN_MONO, |
|
|
|
|
|
AudioFormat.ENCODING_PCM_16BIT |
|
|
|
|
|
) |
|
|
|
|
|
bufferSize > 0 |
|
|
|
|
|
} ?: 16000 |
|
|
|
|
|
|
|
|
|
|
|
val buffer = ByteArray(bufferSize) |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
while (!Thread.currentThread().isInterrupted && |
|
|
|
|
|
audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { |
|
|
|
|
|
|
|
|
|
|
|
if (!isPaused) { |
|
|
|
|
|
val bytesRead = audioRecord?.read(buffer, 0, buffer.size) ?: 0 |
|
|
|
|
|
if (bytesRead > 0) { |
|
|
|
|
|
// 安全地写入Azure流 |
|
|
|
|
|
try { |
|
|
|
|
|
pushAudioStream?.write(buffer) |
|
|
|
|
|
recordfile?.saveAudioDataToWav(buffer) |
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.w(tag, "写入音频流失败: ${e.message}") |
|
|
Log.e("TAG", "音频捕获异常: ${e.message}") |
|
|
} |
|
|
|
|
|
} else if (bytesRead < 0) { |
|
|
|
|
|
Log.w(tag, "AudioRecord读取错误: $bytesRead") |
|
|
|
|
|
break |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
Thread.sleep(10) // 暂停时短暂休眠 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} catch (e: InterruptedException) { |
|
|
|
|
|
Log.d(tag, "音频捕获线程被中断") |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(tag, "音频捕获异常: ${e.message}", e) |
|
|
|
|
|
} |
|
|
} |
|
|
}, "AudioCaptureThread") |
|
|
|
|
|
|
|
|
|
|
|
captureThread?.start() |
|
|
private fun runExternalCapture() { |
|
|
} catch (e: Exception) { |
|
|
Log.d("TAG", "外部音频捕获启动") |
|
|
Log.e(tag, "启动音频捕获线程失败: ${e.message}", e) |
|
|
try { // TODO: 实现外部音频源捕获逻辑 |
|
|
throw e |
|
|
// 停止录音 |
|
|
} |
|
|
if (audioRecord?.recordingState == AudioRecord.STATE_INITIALIZED) { |
|
|
|
|
|
Log.d("TAG", "外部音频捕获启动 释放audioRecord") |
|
|
|
|
|
audioRecord?.stop() |
|
|
|
|
|
// 释放录音实例 |
|
|
|
|
|
audioRecord?.release() |
|
|
|
|
|
audioRecord = null |
|
|
} |
|
|
} |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e("TAG", "外部音频捕获异常: ${e.message}") |
|
|
|
|
|
} finally { |
|
|
|
|
|
|
|
|
private fun stopCaptureThread() { |
|
|
|
|
|
captureThread?.let { thread -> |
|
|
|
|
|
if (thread.isAlive) { |
|
|
|
|
|
thread.interrupt() |
|
|
|
|
|
try { |
|
|
|
|
|
thread.join(500) // 等待最多500ms |
|
|
|
|
|
} catch (e: InterruptedException) { |
|
|
|
|
|
Log.w(tag, "等待音频线程结束被中断") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
captureThread = null |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
@ -914,20 +835,25 @@ class AzureAsrHelper(private val context: Context) { |
|
|
*/ |
|
|
*/ |
|
|
fun stopMicrophoneCapture() { |
|
|
fun stopMicrophoneCapture() { |
|
|
try { |
|
|
try { |
|
|
if (captureThread != null) { |
|
|
if (!isWriting.get()) return |
|
|
// 中断并等待捕获线程结束 |
|
|
|
|
|
captureThread?.interrupt() |
|
|
|
|
|
captureThread?.join(300) // 最多等待300ms |
|
|
|
|
|
|
|
|
|
|
|
// 停止录音并释放AudioRecord资源 |
|
|
|
|
|
audioRecord?.stop() // 必须先停止再释放 |
|
|
isWriting.set(false) |
|
|
isPaused= true |
|
|
|
|
|
|
|
|
// 停止录音 |
|
|
|
|
|
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { |
|
|
|
|
|
audioRecord?.stop() |
|
|
} |
|
|
} |
|
|
|
|
|
// 停止录音并释放AudioRecord资源 |
|
|
|
|
|
// 释放录音实例 |
|
|
|
|
|
audioRecord?.release() |
|
|
|
|
|
audioRecord = null |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e("AudioConfig", "Error releasing resources: ${e.message}") |
|
|
Log.e("AudioConfig", "Error releasing resources: ${e.message}") |
|
|
} finally { |
|
|
} finally { |
|
|
|
|
|
writeQueue.clear() |
|
|
// 确保恢复原始音频状态 |
|
|
// 确保恢复原始音频状态 |
|
|
// restoreOriginalAudioState() |
|
|
// restoreOriginalAudioState() |
|
|
} |
|
|
} |
|
|
@ -939,32 +865,19 @@ class AzureAsrHelper(private val context: Context) { |
|
|
*/ |
|
|
*/ |
|
|
fun releaseAudioResources() { |
|
|
fun releaseAudioResources() { |
|
|
try { |
|
|
try { |
|
|
isPaused= true |
|
|
if (!isWriting.get()) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isWriting.set(false) |
|
|
|
|
|
isRunning.set(false) |
|
|
// 停止录音 |
|
|
// 停止录音 |
|
|
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { |
|
|
if (audioRecord?.recordingState == AudioRecord.RECORDSTATE_RECORDING) { |
|
|
audioRecord?.stop() |
|
|
audioRecord?.stop() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// // 释放回音消除器 |
|
|
// 中断并等待捕获线程结束 |
|
|
// echoCanceler?.let { |
|
|
writeThread?.interrupt() |
|
|
// it.enabled = false |
|
|
writeThread?.join(300) // 最多等待300ms |
|
|
// it.release() |
|
|
|
|
|
// echoCanceler = null |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
// // 释放噪声抑制器 |
|
|
|
|
|
// noiseSuppressor?.let { |
|
|
|
|
|
// it.enabled = false |
|
|
|
|
|
// it.release() |
|
|
|
|
|
// noiseSuppressor = null |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
// // 释放自动增益控制 |
|
|
|
|
|
// automaticGainControl?.let { |
|
|
|
|
|
// it.enabled = false |
|
|
|
|
|
// it.release() |
|
|
|
|
|
// automaticGainControl = null |
|
|
|
|
|
// } |
|
|
|
|
|
|
|
|
|
|
|
// 恢复音频模式 |
|
|
// 恢复音频模式 |
|
|
this@AzureAsrHelper.audioManager?.mode = this@AzureAsrHelper.originalAudioMode |
|
|
this@AzureAsrHelper.audioManager?.mode = this@AzureAsrHelper.originalAudioMode |
|
|
@ -975,64 +888,13 @@ class AzureAsrHelper(private val context: Context) { |
|
|
} catch (e: Exception) { |
|
|
} catch (e: Exception) { |
|
|
Log.e(tag, "释放音频资源失败: ${e.message}") |
|
|
Log.e(tag, "释放音频资源失败: ${e.message}") |
|
|
e.printStackTrace() |
|
|
e.printStackTrace() |
|
|
|
|
|
} finally { |
|
|
|
|
|
writeQueue.clear() |
|
|
|
|
|
writeThread = null |
|
|
|
|
|
// 确保恢复原始音频状态 |
|
|
|
|
|
// restoreOriginalAudioState() |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 外部音频拉流 |
|
|
|
|
|
* 实现PullAudioInputStreamCallback,将外部推送的音频数据转换为SDK可拉取的形式 |
|
|
|
|
|
*/ |
|
|
|
|
|
private inner class ExternalAudioPullStream : PullAudioInputStreamCallback() { |
|
|
|
|
|
private val queue: BlockingQueue<ByteArray> = LinkedBlockingQueue() |
|
|
|
|
|
private var closed = false |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 外部调用:推送音频数据到队列 |
|
|
|
|
|
* @param data 音频数据 |
|
|
|
|
|
*/ |
|
|
|
|
|
fun pushAudio(data: ByteArray) { |
|
|
|
|
|
// 将音频数据保存成wav格式的音频文件 |
|
|
|
|
|
recordfile?.saveAudioDataToWav(data) |
|
|
|
|
|
if (!closed) { |
|
|
|
|
|
queue.offer(data) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 修复:外部音频流恢复阻塞读取 |
|
|
|
|
|
* @param buffer SDK提供的缓冲区 |
|
|
|
|
|
* @return 读取的字节数,0表示流结束 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun read(buffer: ByteArray): Int { |
|
|
|
|
|
try { |
|
|
|
|
|
val chunk = queue.take() |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否是结束标志(空数组) |
|
|
|
|
|
if (chunk.isEmpty()) { |
|
|
|
|
|
return 0 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
val toCopy = minOf(chunk.size, buffer.size) |
|
|
|
|
|
System.arraycopy(chunk, 0, buffer, 0, toCopy) |
|
|
|
|
|
return toCopy |
|
|
|
|
|
} catch (e: InterruptedException) { |
|
|
|
|
|
Thread.currentThread().interrupt() |
|
|
|
|
|
return 0 |
|
|
|
|
|
} catch (e: Exception) { |
|
|
|
|
|
Log.e(tag, "读取外部音频数据失败: ${e.message}") |
|
|
|
|
|
return 0 |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* SDK调用:关闭流 |
|
|
|
|
|
*/ |
|
|
|
|
|
override fun close() { |
|
|
|
|
|
closed = true |
|
|
|
|
|
queue.clear() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
|