按t录音
This commit is contained in:
236
main.go
236
main.go
@@ -1,61 +1,215 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/gen2brain/malgo"
|
||||
"github.com/go-audio/audio"
|
||||
"github.com/go-audio/wav"
|
||||
hook "github.com/robotn/gohook"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // 生产环境应校验来源域名
|
||||
},
|
||||
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 wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil) // 升级HTTP为WebSocket
|
||||
func NewRecorder() (*Recorder, error) {
|
||||
ctx, err := malgo.InitContext(nil, malgo.ContextConfig{}, func(message string) {
|
||||
fmt.Printf("MALGO LOG: %s\n", message)
|
||||
})
|
||||
if err != nil {
|
||||
log.Println("升级失败:", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Recorder{
|
||||
context: ctx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Recorder) Start() {
|
||||
r.lock.Lock()
|
||||
defer r.lock.Unlock()
|
||||
|
||||
if r.isRecording {
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
messageType, p, err := conn.ReadMessage() // 接收消息
|
||||
if err != nil {
|
||||
log.Println("读取失败:", err)
|
||||
break
|
||||
}
|
||||
log.Printf("收到: %s", p)
|
||||
r.audioData = make([]byte, 0, 1024*1024) // 预分配1MB
|
||||
r.isRecording = true
|
||||
|
||||
// 处理消息(示例:原样返回)
|
||||
if err := conn.WriteMessage(messageType, p); err != nil {
|
||||
log.Println("写入失败:", err)
|
||||
break
|
||||
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() {
|
||||
http.HandleFunc("/ws", wsHandler)
|
||||
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||
}
|
||||
|
||||
// 处理关闭信号的函数
|
||||
func handleShutdownSignals() {
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
|
||||
|
||||
// 阻塞等待信号
|
||||
sig := <-sigChan
|
||||
log.Printf("接收到信号: %v, 关闭所有浏览器...", sig)
|
||||
|
||||
// 强制退出程序
|
||||
os.Exit(0)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user