如何在Go语言中从麦克风获取WAV音频用于Vosk识别
解决方案:Go实时麦克风音频转Vosk兼容的流式WAV输入
核心思路
Vosk的流式识别器并不要求完整的WAV文件结构——它只需要先获取包含音频格式信息的WAV头,之后持续接收原始PCM数据即可。我们可以手动构造固定格式的WAV头(总长度填占位值),再配合malgo直接获取的原始PCM数据,实现无文件的实时流式输入,完全不需要依赖带io.WriteSeeker的WAV编码包。
关键步骤与代码实现
1. 手动构造WAV头
WAV头的核心是告知音频的采样率、位深、声道数,总长度字段可以填0(Vosk流式处理不依赖该值):
import "encoding/binary" // buildWAVHeader 生成适配流式传输的WAV头,参数需与麦克风、Vosk模型一致 func buildWAVHeader(sampleRate int, bitDepth int, channels int) []byte { wavHeader := make([]byte, 44) // RIFF块 copy(wavHeader[0:4], []byte("RIFF")) binary.LittleEndian.PutUint32(wavHeader[4:8], 0) // 总长度占位 copy(wavHeader[8:12], []byte("WAVE")) // fmt块(PCM格式) copy(wavHeader[12:16], []byte("fmt ")) binary.LittleEndian.PutUint32(wavHeader[16:20], 16) binary.LittleEndian.PutUint16(wavHeader[20:22], 1) // PCM编码 binary.LittleEndian.PutUint16(wavHeader[22:24], uint16(channels)) binary.LittleEndian.PutUint32(wavHeader[24:28], uint32(sampleRate)) byteRate := sampleRate * channels * (bitDepth / 8) binary.LittleEndian.PutUint32(wavHeader[28:32], uint32(byteRate)) binary.LittleEndian.PutUint16(wavHeader[32:34], uint16(channels*(bitDepth/8))) binary.LittleEndian.PutUint16(wavHeader[34:36], uint16(bitDepth)) // data块 copy(wavHeader[36:40], []byte("data")) binary.LittleEndian.PutUint32(wavHeader[40:44], 0) // 数据长度占位 return wavHeader }
2. 整合malgo录音与Vosk流式识别
直接用malgo获取16bit单声道PCM数据(与Vosk常用参数匹配),先发送WAV头,之后持续推送PCM数据给Vosk:
package main import ( "fmt" "os" "github.com/alphacep/vosk-api/go" "github.com/gen2brain/malgo" ) func main() { // 初始化Vosk模型与流式识别器(采样率需与麦克风一致) model, err := vosk.NewModel("your-model-path") if err != nil { fmt.Printf("模型加载失败: %v\n", err) os.Exit(1) } defer model.Free() recognizer, err := vosk.NewStreamingRecognizer(model, 16000.0) if err != nil { fmt.Printf("识别器初始化失败: %v\n", err) os.Exit(1) } defer recognizer.Free() // 音频参数:与Vosk模型匹配 sampleRate := 16000 channels := 1 bitDepth := 16 // 发送WAV头(仅需一次) wavHeader := buildWAVHeader(sampleRate, bitDepth, channels) recognizer.AcceptWaveform(wavHeader) // 初始化malgo录音上下文 ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(msg string) { fmt.Printf("malgo日志: %v\n", msg) }) if err != nil { fmt.Printf("malgo上下文初始化失败: %v\n", err) os.Exit(1) } defer ctx.Uninit() // 配置录音设备:16bit单声道、16000Hz采样率 deviceConfig := malgo.DefaultDeviceConfig(malgo.DeviceTypeCapture) deviceConfig.Capture.Format = malgo.FormatS16 // 与WAV位深一致 deviceConfig.Capture.Channels = uint32(channels) deviceConfig.SampleRate = uint32(sampleRate) deviceConfig.Alsa.NoMMap = 1 // Linux下禁用MMAP,避免权限问题 // 音频回调:实时处理麦克风数据 onRecvSamples := func(_, pSample []byte, frameCount uint32) { // 直接推送原始PCM数据给Vosk if err := recognizer.AcceptWaveform(pSample); err != nil { fmt.Printf("音频推送失败: %v\n", err) return } // 输出实时部分识别结果 if partial := recognizer.PartialResult(); partial != "" { fmt.Printf("实时识别: %s\n", partial) } // 输出最终识别结果(用户停顿触发) if recognizer.IsFinalResult() { fmt.Printf("最终结果: %s\n", recognizer.Result()) } } // 启动录音设备 device, err := malgo.InitDevice(ctx.Context, deviceConfig, malgo.DeviceCallbacks{Data: onRecvSamples}) if err != nil { fmt.Printf("录音设备初始化失败: %v\n", err) os.Exit(1) } defer device.Uninit() fmt.Println("开始录音,按Ctrl+C退出...") if err := device.Start(); err != nil { fmt.Printf("录音设备启动失败: %v\n", err) os.Exit(1) } // 阻塞等待退出 select {} }
编译与注意事项
- Linux下静态编译malgo(避免依赖系统Alsa库):
go build -tags static -o vosk-mic . - 确保Vosk模型的采样率与代码中设置的
16000.0一致,否则识别效果会严重下降。 - 普通用户需拥有音频设备访问权限,可通过加入
audio组解决:sudo usermod -aG audio $USER
内容的提问来源于stack exchange,提问作者GGG
相关产品推荐
相关产品推荐

