Merged in channel-nego (pull request #424)

alsa: negotiate requested num of channels, fix error logging and checking

Approved-by: Saxon Milton
This commit is contained in:
Trek Hopton 2020-08-21 12:05:03 +00:00
commit 755b9a6687
5 changed files with 53 additions and 43 deletions

View File

@ -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

View File

@ -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)

2
go.mod
View File

@ -3,7 +3,7 @@ module bitbucket.org/ausocean/av
go 1.13
require (
bitbucket.org/ausocean/iot v1.2.15
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

5
go.sum
View File

@ -2,8 +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.15 h1:fJ3hP/jZ/5irLn74bMIlztKrKAqpdhbNR/QiNz5ZgPE=
bitbucket.org/ausocean/iot v1.2.15/go.mod h1:rRcWt6SoM/jgIZpP1zrpnKb5BhxIMulAJ+q1xTvLh94=
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=
@ -31,7 +31,6 @@ 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 h1:gQsRvFdR0BGk19NROQZsAv6iG4w5QIZoJlxJeEUBb0c=
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=

View File

@ -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")