大家好!今天我要带大家用 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、自动上传到云盘,甚至做个"语音日记"小工具。
好了,我去试试录下我家母老虎打呼的声音了。你呢?打算录点啥?
🎙️ 你的声音,值得被保存——哪怕是吐槽代码的那一刻。