216 lines
4.1 KiB
Go
216 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gen2brain/malgo"
|
|
"github.com/go-audio/audio"
|
|
"github.com/go-audio/wav"
|
|
hook "github.com/robotn/gohook"
|
|
)
|
|
|
|
const (
|
|
sampleRate = 44100
|
|
channelCount = 1
|
|
bitDepth = 16
|
|
bufferSize = 1024
|
|
)
|
|
|
|
type Recorder struct {
|
|
context *malgo.AllocatedContext
|
|
device *malgo.Device
|
|
isRecording bool
|
|
audioData []byte
|
|
lock sync.Mutex
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func NewRecorder() (*Recorder, error) {
|
|
ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
|
|
fmt.Printf("MALGO LOG: %s\n", message)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Recorder{
|
|
context: ctx,
|
|
}, nil
|
|
}
|
|
|
|
func (r *Recorder) Start() {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
|
|
if r.isRecording {
|
|
return
|
|
}
|
|
|
|
r.audioData = make([]byte, 0, 1024*1024) // 预分配1MB
|
|
r.isRecording = true
|
|
|
|
deviceConfig := malgo.DefaultDeviceConfig(malgo.Capture)
|
|
deviceConfig.Capture.Format = malgo.FormatS16
|
|
deviceConfig.Capture.Channels = channelCount
|
|
deviceConfig.SampleRate = sampleRate
|
|
deviceConfig.Alsa.NoMMap = 1
|
|
|
|
// 回调函数处理音频数据
|
|
onRecvFrames := func(pSample2, pSample []byte, framecount uint32) {
|
|
if !r.isRecording {
|
|
return
|
|
}
|
|
r.lock.Lock()
|
|
r.audioData = append(r.audioData, pSample...)
|
|
r.lock.Unlock()
|
|
}
|
|
|
|
var err error
|
|
r.device, err = malgo.InitDevice(r.context.Context, deviceConfig, malgo.DeviceCallbacks{
|
|
Data: onRecvFrames,
|
|
})
|
|
if err != nil {
|
|
fmt.Println("设备初始化失败:", err)
|
|
return
|
|
}
|
|
|
|
r.wg.Add(1)
|
|
go func() {
|
|
defer r.wg.Done()
|
|
if err := r.device.Start(); err != nil {
|
|
fmt.Println("录音设备启动失败:", err)
|
|
}
|
|
}()
|
|
|
|
fmt.Println("录音开始...")
|
|
}
|
|
|
|
func (r *Recorder) Stop(filename string) {
|
|
r.lock.Lock()
|
|
defer r.lock.Unlock()
|
|
|
|
if !r.isRecording {
|
|
return
|
|
}
|
|
|
|
r.isRecording = false
|
|
time.Sleep(100 * time.Millisecond) // 等待最后数据写入
|
|
|
|
if r.device != nil {
|
|
r.device.Uninit()
|
|
r.device = nil
|
|
}
|
|
|
|
if len(r.audioData) == 0 {
|
|
fmt.Println("无录音数据")
|
|
return
|
|
}
|
|
|
|
// 保存为WAV文件
|
|
if err := saveWAV(filename, r.audioData); err != nil {
|
|
fmt.Println("保存失败:", err)
|
|
return
|
|
}
|
|
|
|
fmt.Printf("录音已保存: %s (大小: %.2f KB)\n",
|
|
filename, float64(len(r.audioData))/1024)
|
|
r.audioData = nil
|
|
}
|
|
|
|
func (r *Recorder) Close() {
|
|
if r.context != nil {
|
|
r.context.Uninit()
|
|
r.context = nil
|
|
}
|
|
}
|
|
|
|
func saveWAV(filename string, data []byte) error {
|
|
file, err := os.Create(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer file.Close()
|
|
|
|
// 创建WAV编码器
|
|
enc := wav.NewEncoder(file,
|
|
sampleRate,
|
|
bitDepth,
|
|
channelCount,
|
|
1) // PCM格式
|
|
|
|
// 将byte转换为int16
|
|
numSamples := len(data) / 2
|
|
intData := make([]int, numSamples)
|
|
for i := 0; i < numSamples; i++ {
|
|
// 小端字节序转换
|
|
val := int16(data[i*2]) | int16(data[i*2+1])<<8
|
|
intData[i] = int(val)
|
|
}
|
|
|
|
// 创建音频Buffer
|
|
buf := &audio.IntBuffer{
|
|
Format: &audio.Format{
|
|
SampleRate: sampleRate,
|
|
NumChannels: channelCount,
|
|
},
|
|
Data: intData,
|
|
SourceBitDepth: bitDepth,
|
|
}
|
|
|
|
// 写入WAV文件
|
|
if err := enc.Write(buf); err != nil {
|
|
return err
|
|
}
|
|
return enc.Close()
|
|
}
|
|
|
|
func main() {
|
|
recorder, err := NewRecorder()
|
|
if err != nil {
|
|
fmt.Println("录音器初始化失败:", err)
|
|
return
|
|
}
|
|
defer recorder.Close()
|
|
|
|
fmt.Println("按下 T 键开始录音,松开 T 键保存录音")
|
|
fmt.Println("按 ESC 退出程序")
|
|
|
|
// 创建事件通道
|
|
evChan := hook.Start()
|
|
defer hook.End()
|
|
|
|
// 状态跟踪
|
|
isTDown := false
|
|
fileCounter := 1
|
|
|
|
// 事件处理循环
|
|
for ev := range evChan {
|
|
// 只处理键盘事件
|
|
if ev.Kind != hook.KeyDown && ev.Kind != hook.KeyUp {
|
|
continue
|
|
}
|
|
// 检查 T 键
|
|
if ev.Rawcode == 84 { // T 键的键码
|
|
|
|
if ev.Kind == hook.KeyDown && !isTDown {
|
|
isTDown = true
|
|
recorder.Start()
|
|
} else if ev.Kind == hook.KeyUp && isTDown {
|
|
isTDown = false
|
|
filename := fmt.Sprintf("recording_%d.wav", fileCounter)
|
|
fileCounter++
|
|
recorder.Stop(filename)
|
|
}
|
|
}
|
|
|
|
// 检查 ESC 键
|
|
if ev.Rawcode == 27 && ev.Kind == hook.KeyDown {
|
|
fmt.Println("退出程序")
|
|
return
|
|
}
|
|
}
|
|
}
|