You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android SpeechRecognizer用EXTRA_AUDIO_SOURCE未读文件仍调用麦克风问题

问题:SpeechRecognizer未读取本地录音文件,转而调用麦克风

我正在开发一款录音转文字应用,因无法同时运行MediaRecorder和SpeechRecognizer,故采用先完成录音、再通过RecognizerIntent.EXTRA_AUDIO_SOURCE传入录音文件的方案。但SpeechRecognizer未按预期读取本地文件,反而打开麦克风监听——点击停止录音按钮后说话,它会捕捉到语音并返回结果。

录音文件本身无问题:ParcelFileDescriptor的getStatSize返回非零值,且可通过MediaPlayer正常播放。当前测试环境为Android 13,怀疑是ParcelFileDescriptor使用错误,或识别器不支持该功能但不知如何检查支持列表。

代码片段

private var recorder: MediaRecorder? = null
private var recognizer: SpeechRecognizer? = null

private val mediaFormat = MediaRecorder.OutputFormat.MPEG_4
private val audioEncoding = MediaRecorder.AudioEncoder.DEFAULT

private var currentRecordingFile: String = "recording_0.3gp"
private var recordingParcel: ParcelFileDescriptor? = null

// [ {"text": "speech to text result", "file": "path to clip recording"}, "time": "datetime" ]
private var translations = mutableStateListOf<Map<String, String>>()

private fun startTalking () {
    startRecording()
}
private fun stopTalking () {
    stopRecording()
    startRecognizing()
}

private fun startRecording () {
    val num = translations.count()
    currentRecordingFile = "$externalCacheDir/recording_$num.3gp"

    recorder = MediaRecorder(this).apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setOutputFormat(mediaFormat)
        setAudioEncoder(audioEncoding)
        setAudioChannels(1)
        setAudioSamplingRate(16000)
        setAudioEncodingBitRate(64000)
        setOutputFile(currentRecordingFile)

        try {
            prepare()
        } catch (e: IOException) {
            Log.e("startRecording", e.toString())
        }

        start()
    }
}
private fun stopRecording () {
    recorder?.apply {
        stop()
        release()
    }
    recorder = null
}

private fun startRecognizing () {
    val file = File(currentRecordingFile)
    recordingParcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)

    val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "in-ID")
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "in-ID")
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE, recordingParcel)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_ENCODING, audioEncoding)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_CHANNEL_COUNT, 1)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_SAMPLING_RATE, 16000)
    try {
        recognizer = SpeechRecognizer.createSpeechRecognizer(this)
        recognizer?.setRecognitionListener(this)
        recognizer?.startListening(intent)
    } catch (e: Exception) {
        Log.e("SpeechRecognizer", e.message.toString())
    }
}
private fun stopRecognizing () {
    recordingParcel?.close()

    recognizer?.stopListening()
    recognizer?.destroy()
    recognizer = null
}
override fun onError(error: Int) {
    Log.e("Speech onError", error.toString())
    stopRecognizing()
}

override fun onResults(results: Bundle){
    val words: ArrayList<String>? = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
    if (words != null) {
        val sentence = words.joinToString(separator = " ")
        val translation = mapOf("text" to sentence, "file" to currentRecordingFile)
        translations.add(translation)
        Log.e("CURR RESULT", sentence)
    }
    stopRecognizing()
}

问题分析与解决方案

1. 核心错误:EXTRA_AUDIO_SOURCE参数传递错误

RecognizerIntent.EXTRA_AUDIO_SOURCE要求传入的是ParcelFileDescriptor的文件描述符整数(fd),而非ParcelFileDescriptor对象本身。代码直接传递recordingParcel对象,导致识别器无法解析音频源, fallback到麦克风输入。

2. 音频编码格式不明确

使用MediaRecorder.AudioEncoder.DEFAULT会导致编码格式随设备变化,部分格式可能不被SpeechRecognizer支持。建议指定明确的编码类型,如AMR_NB或AAC,保持录音与识别的参数一致。

3. 修正后的代码

修改startRecognizing和startRecording函数,调整参数传递并明确编码:

private fun startRecognizing () {
    val file = File(currentRecordingFile)
    if (!file.exists() || !file.canRead()) {
        Log.e("startRecognizing", "录音文件不可读或不存在")
        return
    }

    recordingParcel = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
    val fd = recordingParcel?.fd ?: run {
        Log.e("startRecognizing", "无法获取文件描述符")
        return
    }

    // 明确编码类型,避免DEFAULT的不确定性
    val fixedAudioEncoding = MediaRecorder.AudioEncoder.AMR_NB

    val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "in-ID")
    intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_PREFERENCE, "in-ID")
    // 传递文件描述符整数而非ParcelFileDescriptor对象
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE, fd)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_ENCODING, fixedAudioEncoding)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_CHANNEL_COUNT, 1)
    intent.putExtra(RecognizerIntent.EXTRA_AUDIO_SOURCE_SAMPLING_RATE, 16000)

    try {
        recognizer = SpeechRecognizer.createSpeechRecognizer(this)
        recognizer?.setRecognitionListener(this)
        recognizer?.startListening(intent)
    } catch (e: Exception) {
        Log.e("SpeechRecognizer", e.message.toString())
        stopRecognizing()
    }
}

// 同时修改startRecording中的编码设置,保持一致
private fun startRecording () {
    val num = translations.count()
    currentRecordingFile = "$externalCacheDir/recording_$num.3gp"

    val fixedAudioEncoding = MediaRecorder.AudioEncoder.AMR_NB

    recorder = MediaRecorder(this).apply {
        setAudioSource(MediaRecorder.AudioSource.MIC)
        setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) // 对应AMR_NB的推荐格式
        setAudioEncoder(fixedAudioEncoding)
        setAudioChannels(1)
        setAudioSamplingRate(16000)
        setAudioEncodingBitRate(64000)
        setOutputFile(currentRecordingFile)

        try {
            prepare()
        } catch (e: IOException) {
            Log.e("startRecording", e.toString())
        }

        start()
    }
}

4. 额外排查步骤

  • 检查识别器支持:并非所有语音识别器都支持EXTRA_AUDIO_SOURCE参数,优先使用系统默认的Google语音识别进行测试。可通过SpeechRecognizer.createOnDeviceSpeechRecognizer(context)强制使用设备端识别器,或查看系统应用列表中的语音识别服务。
  • 错误码解析:在onError中添加错误码对应的含义,快速定位问题:
    override fun onError(error: Int) {
        val errorMsg = when(error) {
            SpeechRecognizer.ERROR_UNSUPPORTED_FORMAT -> "不支持的音频格式"
            SpeechRecognizer.ERROR_CLIENT -> "客户端错误"
            SpeechRecognizer.ERROR_SERVER -> "服务器错误"
            else -> "错误码:$error"
        }
        Log.e("Speech onError", errorMsg)
        stopRecognizing()
    }
    
  • 权限验证:确保已申请RECORD_AUDIO和READ_MEDIA_AUDIO(Android 13+)权限,避免文件读取失败。

内容的提问来源于stack exchange,提问作者Herwin P

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 10:02:11