From d9393b46b0c3e09166e08a4fc1639878b42c2ce5 Mon Sep 17 00:00:00 2001 From: Trek H Date: Wed, 19 Aug 2020 16:55:35 +0930 Subject: [PATCH 1/3] alsa: negotiate channels both stereo and mono, also fix error handling --- device/alsa/alsa.go | 56 +++++++++++++++++++++----------------------- go.mod | 2 +- go.sum | 4 ++++ revid/audio_linux.go | 7 +++++- 4 files changed, 38 insertions(+), 31 deletions(-) diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 0ebfa931..4c722ace 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -153,16 +153,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, @@ -182,7 +180,7 @@ func (d *ALSA) Setup(c config.Config) error { d.mode = paused go d.input() - return nil + return errs } // Set exists to satisfy the implementation of the Device interface that revid uses. @@ -229,20 +227,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,7 +260,7 @@ 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) @@ -270,11 +268,11 @@ func (d *ALSA) open() error { // 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) + channels, err := d.dev.NegotiateChannels(2, 1) if err != nil { return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device channels set", "channels", channels) + 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 +292,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 +300,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 +330,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 +345,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 +369,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 +377,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 +434,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 +443,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 +454,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/go.mod b/go.mod index 727e60bb..6e1c1b0f 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module bitbucket.org/ausocean/av go 1.13 require ( - bitbucket.org/ausocean/iot v1.2.14 + bitbucket.org/ausocean/iot v1.2.16 bitbucket.org/ausocean/utils v1.2.14 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 diff --git a/go.sum b/go.sum index 5b5ce051..44188d19 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ bitbucket.org/ausocean/iot v1.2.13 h1:E9LcW3HYqRgJqxNhPJUCfVRvoV2IAU4B7JSDNxB/x2 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= bitbucket.org/ausocean/utils v1.2.11/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.13 h1:tUaIywtoMc1+zl1GCVQokX4mL5X7LNHX5O51AgAPrWA= @@ -26,10 +28,12 @@ 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/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= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= +github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d h1:dPUSr0RGzXAdsUTMtiyQ/2RBLIIwkv6jGnhxrufitvQ= github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 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") From a475b6f67f876bcc603ff496ef1f139fe0ba2d3b Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 21 Aug 2020 14:13:15 +0930 Subject: [PATCH 2/3] alsa: try requested channel --- device/alsa/alsa.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 4c722ace..0f72f009 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -266,11 +266,14 @@ func (d *ALSA) open() error { 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, 1) + // 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) + } if err != nil { - return OpenError(err) + 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) From c86e3c4ce946ce6f575e15db00e7af15ef7f7604 Mon Sep 17 00:00:00 2001 From: Trek H Date: Fri, 21 Aug 2020 14:34:13 +0930 Subject: [PATCH 3/3] alsa: improve error checking --- device/alsa/alsa.go | 5 ++++- device/alsa/alsa_test.go | 14 ++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 0f72f009..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, @@ -180,7 +183,7 @@ func (d *ALSA) Setup(c config.Config) error { d.mode = paused go d.input() - return errs + return nil } // Set exists to satisfy the implementation of the Device interface that revid uses. 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)