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
相关产品推荐
相关产品推荐

