diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 0ebfa931..9c10e021 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -142,6 +142,9 @@ func (d *ALSA) Setup(c config.Config) error { errs = append(errs, errInvalidCodec) c.InputCodec = defaultCodec } + if errs != nil { + return errs + } d.Config = Config{ SampleRate: c.SampleRate, Channels: c.Channels, @@ -153,16 +156,14 @@ func (d *ALSA) Setup(c config.Config) error { // Open the requested audio device. err := d.open() if err != nil { - d.l.Log(logger.Error, pkg+"failed to open device") - return err + return fmt.Errorf("failed to open device: %w", err) } // Setup the device to record with desired period. ab := d.dev.NewBufferDuration(time.Duration(d.RecPeriod * float64(time.Second))) sf, err := pcm.SFFromString(ab.Format.SampleFormat.String()) if err != nil { - d.l.Log(logger.Error, pkg+err.Error()) - return err + return fmt.Errorf("unable to get sample format from string: %w", err) } cf := pcm.BufferFormat{ SFormat: sf, @@ -229,20 +230,20 @@ func (d *ALSA) Stop() error { func (d *ALSA) open() error { // Close any existing device. if d.dev != nil { - d.l.Log(logger.Debug, pkg+"closing device", "title", d.title) + d.l.Log(logger.Debug, "closing device", "title", d.title) d.dev.Close() d.dev = nil } // Open sound card and open recording device. - d.l.Log(logger.Debug, pkg+"opening sound card") + d.l.Log(logger.Debug, "opening sound card") cards, err := yalsa.OpenCards() if err != nil { return OpenError(err) } defer yalsa.CloseCards(cards) - d.l.Log(logger.Debug, pkg+"finding audio device") + d.l.Log(logger.Debug, "finding audio device") for _, card := range cards { devices, err := card.Devices() if err != nil { @@ -262,19 +263,22 @@ func (d *ALSA) open() error { return OpenError(errors.New("no ALSA device found")) } - d.l.Log(logger.Debug, pkg+"opening ALSA device", "title", d.dev.Title) + d.l.Log(logger.Debug, "opening ALSA device", "title", d.dev.Title) err = d.dev.Open() if err != nil { return OpenError(err) } - // 2 channels is what most devices need to record in. If mono is requested, - // the recording will be converted in formatBuffer(). - channels, err := d.dev.NegotiateChannels(2) - if err != nil { - return OpenError(err) + // Try to configure device with chosen channels. + channels, err := d.dev.NegotiateChannels(int(d.Channels)) + if err != nil && d.Channels == 1 { + d.l.Log(logger.Info, "device is unable to record in mono, trying stereo", "error", err) + channels, err = d.dev.NegotiateChannels(2) } - d.l.Log(logger.Debug, pkg+"alsa device channels set", "channels", channels) + if err != nil { + return OpenError(fmt.Errorf("device is unable to record with requested number of channels: %w", err)) + } + d.l.Log(logger.Debug, "alsa device channels set", "channels", channels) // Try to negotiate a rate to record in that is divisible by the wanted rate // so that it can be easily downsampled to the wanted rate. @@ -294,7 +298,7 @@ func (d *ALSA) open() error { rate, err = d.dev.NegotiateRate(r) if err == nil { foundRate = true - d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", rate) + d.l.Log(logger.Debug, "alsa device sample rate set", "rate", rate) break } } @@ -302,12 +306,12 @@ func (d *ALSA) open() error { // If no easily divisible rate is found, then use the default rate. if !foundRate { - d.l.Log(logger.Warning, pkg+"Unable to sample at requested rate, default used.", "rateRequested", d.SampleRate) + d.l.Log(logger.Warning, "unable to sample at requested rate, default used.", "rateRequested", d.SampleRate) rate, err = d.dev.NegotiateRate(defaultSampleRate) if err != nil { return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", rate) + d.l.Log(logger.Debug, "alsa device sample rate set", "rate", rate) } var aFmt yalsa.FormatType @@ -332,7 +336,7 @@ func (d *ALSA) open() error { default: return OpenError(fmt.Errorf("unsupported sample bits %v", d.BitDepth)) } - d.l.Log(logger.Debug, pkg+"alsa device bit depth set", "bitdepth", bitdepth) + d.l.Log(logger.Debug, "alsa device bit depth set", "bitdepth", bitdepth) // A 50ms period is a sensible value for low-ish latency. (this could be made configurable if needed) // Some devices only accept even period sizes while others want powers of 2. @@ -347,13 +351,13 @@ func (d *ALSA) open() error { if err != nil { return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device buffer size set", "buffersize", bufSize) + d.l.Log(logger.Debug, "alsa device buffer size set", "buffersize", bufSize) if err = d.dev.Prepare(); err != nil { return OpenError(err) } - d.l.Log(logger.Debug, pkg+"successfully negotiated device params") + d.l.Log(logger.Debug, "successfully negotiated device params") return nil } @@ -371,7 +375,7 @@ func (d *ALSA) input() { continue case stopped: if d.dev != nil { - d.l.Log(logger.Debug, pkg+"closing ALSA device", "title", d.title) + d.l.Log(logger.Debug, "closing ALSA device", "title", d.title) d.dev.Close() d.dev = nil } @@ -379,31 +383,31 @@ func (d *ALSA) input() { } // Read from audio device. - d.l.Log(logger.Debug, pkg+"recording audio for period", "seconds", d.RecPeriod) + d.l.Log(logger.Debug, "recording audio for period", "seconds", d.RecPeriod) err := d.dev.Read(d.pb.Data) if err != nil { - d.l.Log(logger.Debug, pkg+"read failed", "error", err.Error()) + d.l.Log(logger.Debug, "read failed", "error", err.Error()) err = d.open() // re-open if err != nil { - d.l.Log(logger.Fatal, pkg+"reopening device failed", "error", err.Error()) + d.l.Log(logger.Fatal, "reopening device failed", "error", err.Error()) return } continue } // Process audio. - d.l.Log(logger.Debug, pkg+"processing audio") + d.l.Log(logger.Debug, "processing audio") toWrite := d.formatBuffer() // Write audio to ringbuffer. n, err := d.rb.Write(toWrite.Data) switch err { case nil: - d.l.Log(logger.Debug, pkg+"wrote audio to ringbuffer", "length", n) + d.l.Log(logger.Debug, "wrote audio to ringbuffer", "length", n) case ring.ErrDropped: - d.l.Log(logger.Warning, pkg+"old audio data overwritten") + d.l.Log(logger.Warning, "old audio data overwritten") default: - d.l.Log(logger.Error, pkg+"unexpected ringbuffer error", "error", err.Error()) + d.l.Log(logger.Error, "unexpected ringbuffer error", "error", err.Error()) return } } @@ -436,7 +440,7 @@ func (d *ALSA) formatBuffer() pcm.Buffer { if d.pb.Format.Channels == 2 && d.Channels == 1 { formatted, err = pcm.StereoToMono(d.pb) if err != nil { - d.l.Log(logger.Fatal, pkg+"channel conversion failed", "error", err.Error()) + d.l.Log(logger.Fatal, "channel conversion failed", "error", err.Error()) } } } @@ -445,7 +449,7 @@ func (d *ALSA) formatBuffer() pcm.Buffer { // Convert rate. formatted, err = pcm.Resample(formatted, d.SampleRate) if err != nil { - d.l.Log(logger.Fatal, pkg+"rate conversion failed", "error", err.Error()) + d.l.Log(logger.Fatal, "rate conversion failed", "error", err.Error()) } } @@ -456,11 +460,11 @@ func (d *ALSA) formatBuffer() pcm.Buffer { enc := adpcm.NewEncoder(b) _, err = enc.Write(formatted.Data) if err != nil { - d.l.Log(logger.Fatal, pkg+"unable to encode", "error", err.Error()) + d.l.Log(logger.Fatal, "unable to encode", "error", err.Error()) } formatted.Data = b.Bytes() default: - d.l.Log(logger.Error, pkg+"unhandled audio codec") + d.l.Log(logger.Error, "unhandled audio codec") } return formatted diff --git a/device/alsa/alsa_test.go b/device/alsa/alsa_test.go index 918a022a..8124e131 100644 --- a/device/alsa/alsa_test.go +++ b/device/alsa/alsa_test.go @@ -27,6 +27,7 @@ package alsa import ( "bytes" + "errors" "io/ioutil" "os" "strconv" @@ -35,6 +36,7 @@ import ( "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/codec/pcm" + "bitbucket.org/ausocean/av/device" "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" ) @@ -54,14 +56,14 @@ func TestDevice(t *testing.T) { l := logger.New(logger.Debug, os.Stderr, true) ai := New(l) err := ai.Setup(c) - // If there was an error opening the device, skip this test. - if _, ok := err.(OpenError); ok { + // Log any config errors, otherwise if there was an error opening a device, skip + // this test since not all testing environments will have recording devices. + var e *device.MultiError + if err != nil && errors.As(err, &e) { + t.Logf("errors from configuring device: %s", err.Error()) + } else if err != nil { t.Skip(err) } - // For any other error, report it. - if err != nil { - t.Error(err) - } err = ai.Start() if err != nil { t.Error(err) diff --git a/go.sum b/go.sum index 31c41a01..2b59f69e 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +bitbucket.org/ausocean/iot v1.2.13 h1:E9LcW3HYqRgJqxNhPJUCfVRvoV2IAU4B7JSDNxB/x2k= +bitbucket.org/ausocean/iot v1.2.13/go.mod h1:Q5FwaOKnCty3dVeVtki6DLwYa5vhNpOaeu1lwLyPCg8= +bitbucket.org/ausocean/iot v1.2.14 h1:+2up1G1g28RnXFw+1tpobdpZxzZDYEXgsXwmpNZDCWA= +bitbucket.org/ausocean/iot v1.2.14/go.mod h1:Q5FwaOKnCty3dVeVtki6DLwYa5vhNpOaeu1lwLyPCg8= bitbucket.org/ausocean/iot v1.2.16 h1:dZvENB2cLmeHvEfBULhGxnM9K10IuWFLqjcFKBJHpgw= bitbucket.org/ausocean/iot v1.2.16/go.mod h1:rRcWt6SoM/jgIZpP1zrpnKb5BhxIMulAJ+q1xTvLh94= bitbucket.org/ausocean/utils v1.2.11 h1:zA0FOaPjN960ryp8PKCkV5y50uWBYrIxCVnXjwbvPqg= @@ -22,6 +26,7 @@ github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+ github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= +github.com/go-delve/delve v1.5.0/go.mod h1:c6b3a1Gry6x8a4LGCe/CWzrocrfaHvkUxCj3k4bvSUQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= diff --git a/revid/audio_linux.go b/revid/audio_linux.go index d3d08a58..caac84e1 100644 --- a/revid/audio_linux.go +++ b/revid/audio_linux.go @@ -25,11 +25,13 @@ LICENSE package revid import ( + "errors" "fmt" "strconv" "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/container/mts" + "bitbucket.org/ausocean/av/device" "bitbucket.org/ausocean/av/device/alsa" "bitbucket.org/ausocean/utils/logger" ) @@ -42,8 +44,11 @@ func (r *Revid) setupAudio() error { // Configure ALSA device. r.cfg.Logger.Log(logger.Debug, "configuring input device") err := d.Setup(r.cfg) - if err != nil { + var e *device.MultiError + if err != nil && errors.As(err, &e) { r.cfg.Logger.Log(logger.Warning, "errors from configuring input device", "errors", err) + } else if err != nil { + return err } r.cfg.Logger.Log(logger.Info, "input device configured")