123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- package recorder
- import (
- "encoding/binary"
- "fmt"
- "os"
- "os/signal"
- "syscall"
- "golang.org/x/sys/unix"
- "golang.org/x/term"
- "github.com/gordonklaus/portaudio"
- )
- const (
- sampleRate = 16000
- numChannels = 1
- bitsPerSample = 16
- )
- func RecordAudio(f *os.File) error {
- fmt.Print("Recording. Press any key to stop.\n\n")
- sig := make(chan os.Signal, 1)
- signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
- portaudio.Initialize()
- defer portaudio.Terminate()
- in := make([]int16, 64)
- stream, err := portaudio.OpenDefaultStream(numChannels, 0, sampleRate, len(in), in)
- if err != nil {
- return err
- }
- defer stream.Close()
- err = stream.Start()
- if err != nil {
- return err
- }
- // Write WAV header with placeholder sizes
- writeWavHeader(f, sampleRate, numChannels, bitsPerSample)
- var totalSamples uint32
- // Set up terminal input reading
- oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
- if err != nil {
- return err
- }
- defer term.Restore(int(os.Stdin.Fd()), oldState)
- // Create a channel to handle the stop signal
- stop := make(chan struct{})
- go func() {
- _, err := unix.Read(int(os.Stdin.Fd()), make([]byte, 1))
- if err != nil {
- fmt.Println("Error reading from stdin:", err)
- return
- }
- // Send signal to stop recording
- stop <- struct{}{}
- }()
- loop:
- for {
- err = stream.Read()
- if err != nil {
- return err
- }
- err = binary.Write(f, binary.LittleEndian, in)
- if err != nil {
- return err
- }
- totalSamples += uint32(len(in))
- select {
- case <-stop:
- break loop
- case <-sig:
- break loop
- default:
- }
- }
- err = stream.Stop()
- if err != nil {
- return err
- }
- // Update WAV header with actual sizes
- updateWavHeader(f, totalSamples, numChannels, bitsPerSample)
- return nil
- }
- func writeWavHeader(f *os.File, sampleRate int, numChannels int, bitsPerSample int) {
- subchunk1Size := 16
- audioFormat := 1
- byteRate := sampleRate * numChannels * (bitsPerSample / 8)
- blockAlign := numChannels * (bitsPerSample / 8)
- // Write the RIFF header
- f.Write([]byte("RIFF"))
- binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for file size
- f.Write([]byte("WAVE"))
- // Write the fmt subchunk
- f.Write([]byte("fmt "))
- binary.Write(f, binary.LittleEndian, uint32(subchunk1Size))
- binary.Write(f, binary.LittleEndian, uint16(audioFormat))
- binary.Write(f, binary.LittleEndian, uint16(numChannels))
- binary.Write(f, binary.LittleEndian, uint32(sampleRate))
- binary.Write(f, binary.LittleEndian, uint32(byteRate))
- binary.Write(f, binary.LittleEndian, uint16(blockAlign))
- binary.Write(f, binary.LittleEndian, uint16(bitsPerSample))
- // Write the data subchunk header
- f.Write([]byte("data"))
- binary.Write(f, binary.LittleEndian, uint32(0)) // Placeholder for data size
- }
- func updateWavHeader(f *os.File, totalSamples uint32, numChannels int, bitsPerSample int) {
- fileSize := 36 + (totalSamples * uint32(numChannels) * uint32(bitsPerSample/8))
- dataSize := totalSamples * uint32(numChannels) * uint32(bitsPerSample/8)
- // Seek to the start of the file and write updated sizes
- f.Seek(4, 0)
- binary.Write(f, binary.LittleEndian, uint32(fileSize))
- f.Seek(40, 0)
- binary.Write(f, binary.LittleEndian, uint32(dataSize))
- }
|