~geb/numen

851a766ffce863d72d851e0d1b6f20489d322cbb — John Gebbie a month ago 1fda5e5 master
refactor audio recording into struct

Also now runs Wait() to free resources if arecord dies.
3 files changed, 76 insertions(+), 61 deletions(-)

A audio.go
M numen.go
D record.go
A audio.go => audio.go +60 -0
@@ 0,0 1,60 @@
package main

import (
	"bytes"
	"io"
	"os"
	"os/exec"
)

type Audio struct {
	Device string
	cmd    *exec.Cmd
	stdout io.ReadCloser

	Filename string
	file     *os.File
}

func (a *Audio) SetDevice(device string) {
	if device == "" {
		out, _ := exec.Command("arecord", "-L").Output()
		if bytes.Contains(out, []byte("sysdefault:CARD=Microphone\n")) {
			device = "sysdefault:CARD=Microphone"
		} else {
			device = "default"
		}
	}
	a.Device = device
}

func (a *Audio) Start() error {
	if a.Filename == "" {
		a.cmd = exec.Command("arecord", "-q", "-fS16_LE", "-c1", "-r16000", "-D", a.Device)
		a.cmd.Stderr = os.Stderr
		var err error
		a.stdout, err = a.cmd.StdoutPipe()
		if err != nil {
			return err
		}
		return a.cmd.Start()
	}

	var err error
	a.file, err = os.Open(a.Filename)
	return err
}

func (a *Audio) Reader() io.Reader {
	if a.stdout != nil {
		return a.stdout
	}
	return a.file
}

func (a *Audio) Close() error {
	if a.cmd != nil {
		return a.cmd.Wait()
	}
	return a.file.Close()
}

M numen.go => numen.go +16 -27
@@ 309,7 309,6 @@ CANCEL:

func main() {
	var opts struct {
		Audio     string
		AudioLog  *os.File
		Files     []string
		Handler   string


@@ 318,11 317,12 @@ func main() {
		Verbose   bool
	}
	opts.Handler = "uinput"
	audio := &Audio{}
	{
		o := opt.NewOptionSet()

		o.Func("audio", func(s string) error {
			opts.Audio = s
			audio.Filename = s
			return nil
		})



@@ 482,28 482,19 @@ func main() {
	defer cmdRec.Free()
	defer transRec.Free()

	var mic string
	var audio io.Reader
	var noiseRec *NoiseRecognizer
	var noiseBuffer *bytes.Buffer
	if opts.Audio == "" {
		mic = getMic(opts.Mic)
	if audio.Filename == "" {
		audio.SetDevice(opts.Mic)
		if opts.Verbose {
			fmt.Fprintln(os.Stderr, "Microphone: "+mic)
		}
		var err error
		audio, err = record(mic)
		if err != nil {
			fatal(err)
		}
	} else {
		f, err := os.Open(opts.Audio)
		if err != nil {
			fatal(err)
			fmt.Fprintln(os.Stderr, "Microphone: "+audio.Device)
		}
		defer f.Close()
		audio = f
	}
	if err := audio.Start(); err != nil {
		fatal(err)
	}
	defer audio.Close()

	var noiseRec *NoiseRecognizer
	var noiseBuffer *bytes.Buffer
	if blow, hiss, shush := haveNoises(actions); blow || hiss || shush {
		noiseBuffer = new(bytes.Buffer)
		noiseRec = NewNoiseRecognizer(noiseBuffer, blow, hiss, shush)


@@ 594,14 585,12 @@ func main() {
		default:
		}
		chunk := make([]byte, 4096)
		_, err := io.ReadFull(audio, chunk)
		_, err := io.ReadFull(audio.Reader(), chunk)
		if err != nil {
			if err == io.EOF || err == io.ErrUnexpectedEOF {
				if mic != "" && retry {
					r, err := record(mic)
					if err == nil {
						audio = r
					} else {
				if audio.Filename == "" && retry {
					_ = audio.Close()
					if err := audio.Start(); err != nil {
						warn(err)
					}
					continue

D record.go => record.go +0 -34
@@ 1,34 0,0 @@
package main

import (
	"bytes"
	"io"
	"os"
	"os/exec"
)

func getMic(mic string) string {
	if mic == "" {
		out, _ := exec.Command("arecord", "-L").Output()
		if bytes.Contains(out, []byte("sysdefault:CARD=Microphone\n")) {
			mic = "sysdefault:CARD=Microphone"
		} else {
			mic = "default"
		}
	}
	return mic
}

func record(mic string) (io.Reader, error) {
	cmd := exec.Command("arecord", "-q", "-fS16_LE", "-c1", "-r16000", "-D", mic)
	cmd.Stderr = os.Stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	err = cmd.Start()
	if err != nil {
		return nil, err
	}
	return stdout, nil
}