用 Go 给 Windows 装个“顺风耳”:两分钟写个录音小工具

59 阅读5分钟

大家好!今天我要带大家用 Go 语言写一个能在 Windows 上录音的小程序。别紧张,这不是什么黑客工具(虽然听起来有点像),而是一个正经的、能生成 output.wav 文件的"声音捕捉器"——比如录下你早上对着电脑喊"为什么又报错了!"的珍贵瞬间 😅。

使用的Go库

这个项目用到了两个非常酷的 Go 库:

  • github.com/Onelio/winmm:让你在 Windows 上调用经典的 winmm.dll 多媒体 API(没错,就是那个 Win95 时代就存在的老古董,但它依然好用!)
  • github.com/go-audio/wav:帮你把原始音频数据打包成标准的 WAV 文件。

话不多说,咱们直接上"操作"!

第一步:看看你的电脑有哪些"耳朵"和"嘴巴"

程序启动后,它会先列出你电脑上所有的输入设备(麦克风)和输出设备(扬声器):

输入设备: 0 麦克风 (Realtek Audio)
输入设备: 1 立体声混音 (Realtek Audio)
输出设备: 0 扬声器 (Realtek Audio)
输出设备: 1 耳机 (Bluetooth Device)

然后它会坏笑着等你按回车——仿佛在说:"准备好了吗?我要开始偷听你了哦~"

第二步:开始录音!

程序会以 8kHz 采样率、16位深度、立体声 的格式录音。虽然音质比不上 Hi-Res,但录个语音备忘、猫叫、或者你模仿 Siri 的声音绰绰有余。

录音过程中,你随时按回车就能停止。程序会把录到的所有声音,乖乖写进一个叫 output.wav 的文件里。

🎧 小知识:8kHz 是电话音质,但对语音识别或简单录音完全够用,而且文件小!

第三步:播放你的"高光时刻"

录完后,双击 output.wav,Windows 自带的播放器就会打开。

  • 如果你录的是"啊——测试测试",那恭喜你,成功了!
  • 如果你录的是"老板根本不懂技术",建议立刻删除文件并装作无事发生 😏。

为什么这个代码有点"复古"?

因为底层用的是 Windows 的 WaveIn API,这可是上世纪 90 年代的产物!但它稳定、简单、无需管理员权限,特别适合做小型录音工具。Go 语言通过 cgo 或 syscall 封装后,用起来居然还挺顺手。

不过要注意:这段代码只能在 Windows 上跑。Mac 和 Linux 用户请绕道,或者考虑用 PortAudio 之类的跨平台库。

完整源码

下面就是我们精心整理、注释全中文、风格清爽的录音小工具源码:

package main

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"log"
	"os"
	"time"

	"github.com/Onelio/winmm"
	"github.com/go-audio/audio"
	"github.com/go-audio/wav"
)

func main() {
	// 列出所有输入设备
	inputDevices, _ := winmm.EnumInDevices()
	for _, dev := range inputDevices {
		fmt.Println("输入设备:", dev.Id(), dev.Name())
	}

	// 列出所有输出设备
	outputDevices, _ := winmm.EnumOutDevices()
	for _, dev := range outputDevices {
		fmt.Println("输出设备:", dev.Id(), dev.Name())
	}

	// 等待用户按回车继续
	fmt.Println("按回车键开始录音...")
	_, _ = fmt.Scanln()

	// 1. 创建录音所需的音频格式:立体声(2通道)、8kHz 采样率、16位深度
	waveFormat := winmm.NewWaveFormat(winmm.ChStereo, winmm.SpS08kHz, winmm.BpS16)

	// 创建用于录音的缓冲区,时长为 2 秒
	recordBuffer := winmm.NewWaveHeader(waveFormat, 2)

	// 创建录音对象并打开默认录音设备(WaveMapper)
	recorder := winmm.NewWaveIn()
	_ = recorder.Open(winmm.WaveMapper, waveFormat)

	// 准备缓冲区,并获取其唯一标识 key(用于后续操作)
	bufferKey, _ := recorder.PrepareBuffer(recordBuffer)

	fmt.Println("正在录音...(按回车键停止)")

	// 将缓冲区添加到录音设备,并开始录音
	_ = recorder.AddBuffer(bufferKey)
	_ = recorder.Start()

	// 稍微延迟,确保录音已开始
	time.Sleep(200 * time.Millisecond)

	// 启动一个 goroutine,监听用户是否按下回车键以停止录音
	shouldStop := false
	go func() {
		_, _ = fmt.Scanln()
		shouldStop = true
	}()

	// 创建输出 WAV 文件
	outFile, err := os.Create("output.wav")
	if err != nil {
		log.Fatal("无法创建输出文件:", err)
	}
	defer outFile.Close()

	// 创建 WAV 编码器:8kHz、16位、2通道(立体声)、1 块(通常为 1)
	encoder := wav.NewEncoder(outFile, 8000, 16, 2, 1)

	// 初始化用于写入的音频缓冲区
	writeBuffer := audio.IntBuffer{
		Format: &audio.Format{
			NumChannels: 2,   // 立体声
			SampleRate:  8000, // 8kHz 采样率
		},
	}

	// 持续监听录音数据,直到用户停止
	for !shouldStop {
		select {
		case data := <-recorder.Channel:
			// 将接收到的原始字节数据转换为 int16 样本,并追加到 writeBuffer
			copyDataToBuffer(data.GetBufferSlice(), &writeBuffer)
			// 重新将缓冲区加入录音队列(循环使用)
			_ = recorder.AddBuffer(bufferKey)
			_ = recorder.Start()
		default:
			// 短暂休眠,避免忙等待
			time.Sleep(10 * time.Millisecond)
		}
	}

	// 停止录音后,释放缓冲区
	_ = recorder.UnPrepareBuffer(bufferKey)
	// 关闭录音设备
	_ = recorder.Close()

	// 将录制的音频数据写入 WAV 文件(包含 RIFF 头和 PCM 数据)
	if err := encoder.Write(&writeBuffer); err != nil {
		log.Fatal("写入 WAV 文件失败:", err)
	}
	if err := encoder.Close(); err != nil {
		log.Fatal("关闭 WAV 编码器失败:", err)
	}

	fmt.Println("录音已保存为 output.wav")
}

// copyDataToBuffer 将原始字节数据(小端序 int16)转换为 audio.IntBuffer 所需的 int 切片
func copyDataToBuffer(data []byte, buffer *audio.IntBuffer) {
	reader := bytes.NewReader(data)
	for {
		var sample int16
		err := binary.Read(reader, binary.LittleEndian, &sample)
		switch {
		case err == io.EOF:
			return // 读取完毕
		case err != nil:
			log.Println("读取音频样本时出错:", err)
			return
		}
		// 将 int16 转为 int 并追加到缓冲区
		buffer.Data = append(buffer.Data, int(sample))
	}
}

小提醒(认真脸)

  • 代码中为了简洁,忽略了大部分错误处理。实际项目中请务必加上!
  • 录音需要麦克风权限,Windows 可能会弹出隐私提示。
  • 如果你录下了什么不可描述的内容……后果自负,本人概不负责 😇

结语

用 Go 在 Windows 上录音,听起来是不是有点"跨界"?但正是这种混搭,才让编程充满乐趣。下次你可以在这个基础上加个 GUI、自动上传到云盘,甚至做个"语音日记"小工具。

好了,我去试试录下我家母老虎打呼的声音了。你呢?打算录点啥?

🎙️ 你的声音,值得被保存——哪怕是吐槽代码的那一刻。