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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 03:07:44