aman/internal/audio/record.go

73 lines
1.5 KiB
Go

package audio
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"time"
)
type Recorder struct {
Input string
}
type RecordResult struct {
WavPath string
TempDir string
}
func (r Recorder) Start(ctx context.Context) (*exec.Cmd, *RecordResult, error) {
tmpdir, err := os.MkdirTemp("", "lel-")
if err != nil {
return nil, nil, err
}
wav := filepath.Join(tmpdir, "mic.wav")
args := []string{"-hide_banner", "-loglevel", "error"}
args = append(args, ffmpegInputArgs(r.Input)...)
args = append(args, "-ac", "1", "-ar", "16000", "-c:a", "pcm_s16le", wav)
cmd := exec.CommandContext(ctx, "ffmpeg", args...)
// Put ffmpeg in its own process group so Ctrl+C only targets the daemon.
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err := cmd.Start(); err != nil {
_ = os.RemoveAll(tmpdir)
return nil, nil, err
}
return cmd, &RecordResult{WavPath: wav, TempDir: tmpdir}, nil
}
func WaitWithTimeout(cmd *exec.Cmd, timeout time.Duration) error {
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case err := <-done:
return err
case <-time.After(timeout):
if cmd.Process != nil {
_ = cmd.Process.Kill()
}
return fmt.Errorf("process timeout after %s", timeout)
}
}
func ffmpegInputArgs(spec string) []string {
if spec == "" {
spec = "pulse:default"
}
kind := spec
name := "default"
if idx := strings.Index(spec, ":"); idx != -1 {
kind = spec[:idx]
name = spec[idx+1:]
}
return []string{"-f", kind, "-i", name}
}