Android平台同时使用AudioRecord与Vosk离线语音识别的麦克风冲突及架构方案咨询
Android平台同时使用AudioRecord与Vosk离线语音识别的麦克风冲突及架构方案咨询
核心基础问题解答
1. Android允许多个麦克风消费者同时工作吗?
Android的麦克风访问默认是独占式的——同一时间只能有一个App(或App内的一个实例)独占麦克风资源,这和iOS的音频会话共享机制有本质区别。iOS允许同一App内的多个音频消费者共享同一条输入流,甚至不同App也能通过音频会话协作共享,但Android的音频输入栈天生对独占性管控更严格,不同厂商的硬件实现还会放大这种差异。
2. 能不能在录音和识别之间共享同一条音频流?
当然可以,而且这是唯一正确的解决方案。你尝试的「用单个AudioRecord读取buffer,同时喂给Vosk和录音逻辑」方向没错,现在遇到的不稳定问题,大概率是配置、线程调度或buffer处理不当导致的,不是共享流本身的问题。
3. 为啥iOS能正常工作,Android不行?
核心是两个平台的音频架构设计差异:
- iOS的AudioSession系统天生支持音频流共享,只要配置正确,同一App内的录音和识别可以无缝复用同一条输入流
- Android的音频输入栈默认采用独占模式,加上硬件抽象层(HAL)的厂商定制化程度高,共享流时容易出现时序延迟、数据丢失问题,进而影响Vosk的识别精度
Android上同时录音+实时离线转录的推荐架构
标准方案:单音频流复用+线程分离
这是绝大多数正规App的实现方式,不需要特殊API,关键是做好配置和线程调度:
- 统一音频源配置:把音频源从普通
MIC改成MediaRecorder.AudioSource.VOICE_RECOGNITION——这个源是系统专门为语音识别优化的,会自动做降噪、自动增益等预处理,比普通MIC更适配Vosk模型,还能减少硬件层面的冲突概率 - 单AudioRecord实例:只创建一个AudioRecord读取原始PCM buffer
- 多线程并行处理:用线程池或独立工作线程分离三个核心逻辑,避免阻塞音频读取线程:
- 主线程:控制录音启停、UI交互
- 读取线程:专门负责从AudioRecord读取buffer,不做任何耗时操作
- 处理分支1:把buffer写入文件(注意先复制buffer避免数据覆盖)
- 处理分支2:把buffer喂给Vosk的recognizer做识别
- 线程安全的buffer队列:用
BlockingQueue缓存读取到的buffer,让两个处理分支异步消费,保证读取线程的实时性
代码实现示例(简化版)
import java.util.concurrent.LinkedBlockingQueue import android.media.AudioFormat import android.media.AudioRecord import android.media.MediaRecorder import org.vosk.Model import org.vosk.Recognizer import java.io.FileOutputStream // 线程安全的buffer队列,存储PCM数据和有效长度 val bufferQueue = LinkedBlockingQueue<Pair<ByteArray, Int>>() val isRecording = java.util.concurrent.atomic.AtomicBoolean(true) // 1. 启动音频读取线程(核心:只做读取,不做其他耗时操作) Thread { val sampleRate = 16000 // Vosk模型默认推荐16kHz,必须匹配 val bufferSize = AudioRecord.getMinBufferSize( sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ) * 2 // 缓冲加倍,避免数据丢失 val recorder = AudioRecord( MediaRecorder.AudioSource.VOICE_RECOGNITION, // 改用优化后的音频源 sampleRate, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize ) recorder.startRecording() val tempBuffer = ByteArray(4096) // 合适的单次读取大小,适配16kHz采样率 while (isRecording.get()) { val readLength = recorder.read(tempBuffer, 0, tempBuffer.size) if (readLength > 0) { // 复制buffer避免后续覆盖 val copiedBuffer = tempBuffer.copyOf(readLength) bufferQueue.put(Pair(copiedBuffer, readLength)) } } recorder.stop() recorder.release() }.start() // 2. 启动文件写入线程 Thread { val outputStream = FileOutputStream("/sdcard/recorded_audio.pcm") while (isRecording.get() || !bufferQueue.isEmpty()) { val (buffer, length) = bufferQueue.take() outputStream.write(buffer, 0, length) } outputStream.close() // 可选:把PCM转成WAV格式(需要手动写入RIFF文件头) }.start() // 3. 启动Vosk识别线程 Thread { val model = Model("/sdcard/vosk-model-small-en-us-0.15") // 替换为你的Vosk模型路径 val recognizer = Recognizer(model, sampleRate.toFloat()) while (isRecording.get() || !bufferQueue.isEmpty()) { val (buffer, length) = bufferQueue.take() recognizer.acceptWaveForm(buffer, length) val result = recognizer.result // 处理识别结果(比如回调到Flutter层更新UI) println("识别结果:$result") } recognizer.close() model.close() }.start()
关于你提到的其他疑问
1. 那些App是怎么做到同时录音+转录的?
- 都是用单音频流复用的方式,没有黑科技
- 会优先用
VOICE_RECOGNITION音频源,而不是普通MIC - 不会使用未公开的系统API,都是基于标准的AudioRecord和识别库实现
2. iOS和Android的差异核心原因
主要是音频架构设计不同,和云端API、预处理的关系不大:
- iOS的音频会话天生支持共享,Android的音频输入默认独占
- 云端API(比如谷歌Speech-to-Text)会用系统级优化,但离线方案一样可以做到稳定,只是需要更注意配置和线程调度
- 高级预处理(降噪、AGC)会提升识别精度,但不是解决「同时工作」的核心原因
3. Vosk的精度问题
Vosk作为离线引擎,和在线引擎(比如谷歌Speech-to-Text)比确实有精度差距——在线引擎有更大的模型、云端实时优化,但这不是你现在遇到「不稳定」的主要原因。你当前的问题更多是线程调度、音频源配置或buffer处理不当导致的时序问题,进而影响了识别精度。
4. 你遇到的不稳定问题的可能原因
- 音频源用了普通
MIC,缺少系统级预处理 - 读取线程被阻塞:比如在读取线程里直接做文件写入或Vosk识别,导致读取不及时,buffer丢失
- 采样率不匹配:Vosk模型默认是16kHz,如果你的AudioRecord用了其他采样率(比如44.1kHz),会导致识别精度急剧下降
- buffer大小不合适:太小会导致频繁读取,太大会导致延迟过高,Vosk推荐用基于16kHz采样率的4096/8192字节buffer
最终实现建议
- 优先配置
VOICE_RECOGNITION音频源,匹配Vosk模型的16kHz采样率 - 严格分离读取线程和处理线程,用
BlockingQueue做buffer缓存 - 测试不同设备:Android厂商的音频HAL定制化程度高,部分设备可能需要微调buffer大小或采样率
- 可选添加基础预处理:比如用Android的
AudioEffectAPI做简单降噪,进一步提升Vosk的识别精度




