73 lines
1.5 KiB
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}
|
|
}
|