mirror of https://bitbucket.org/ausocean/av.git
Merge branch 'master' into treatment-newmods
This commit is contained in:
commit
44801e6f71
|
@ -142,6 +142,9 @@ func (d *ALSA) Setup(c config.Config) error {
|
||||||
errs = append(errs, errInvalidCodec)
|
errs = append(errs, errInvalidCodec)
|
||||||
c.InputCodec = defaultCodec
|
c.InputCodec = defaultCodec
|
||||||
}
|
}
|
||||||
|
if errs != nil {
|
||||||
|
return errs
|
||||||
|
}
|
||||||
d.Config = Config{
|
d.Config = Config{
|
||||||
SampleRate: c.SampleRate,
|
SampleRate: c.SampleRate,
|
||||||
Channels: c.Channels,
|
Channels: c.Channels,
|
||||||
|
@ -153,16 +156,14 @@ func (d *ALSA) Setup(c config.Config) error {
|
||||||
// Open the requested audio device.
|
// Open the requested audio device.
|
||||||
err := d.open()
|
err := d.open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.l.Log(logger.Error, pkg+"failed to open device")
|
return fmt.Errorf("failed to open device: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the device to record with desired period.
|
// Setup the device to record with desired period.
|
||||||
ab := d.dev.NewBufferDuration(time.Duration(d.RecPeriod * float64(time.Second)))
|
ab := d.dev.NewBufferDuration(time.Duration(d.RecPeriod * float64(time.Second)))
|
||||||
sf, err := pcm.SFFromString(ab.Format.SampleFormat.String())
|
sf, err := pcm.SFFromString(ab.Format.SampleFormat.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.l.Log(logger.Error, pkg+err.Error())
|
return fmt.Errorf("unable to get sample format from string: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
cf := pcm.BufferFormat{
|
cf := pcm.BufferFormat{
|
||||||
SFormat: sf,
|
SFormat: sf,
|
||||||
|
@ -229,20 +230,20 @@ func (d *ALSA) Stop() error {
|
||||||
func (d *ALSA) open() error {
|
func (d *ALSA) open() error {
|
||||||
// Close any existing device.
|
// Close any existing device.
|
||||||
if d.dev != nil {
|
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.Close()
|
||||||
d.dev = nil
|
d.dev = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open sound card and open recording device.
|
// 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()
|
cards, err := yalsa.OpenCards()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenError(err)
|
return OpenError(err)
|
||||||
}
|
}
|
||||||
defer yalsa.CloseCards(cards)
|
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 {
|
for _, card := range cards {
|
||||||
devices, err := card.Devices()
|
devices, err := card.Devices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -262,19 +263,22 @@ func (d *ALSA) open() error {
|
||||||
return OpenError(errors.New("no ALSA device found"))
|
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()
|
err = d.dev.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenError(err)
|
return OpenError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 channels is what most devices need to record in. If mono is requested,
|
// Try to configure device with chosen channels.
|
||||||
// the recording will be converted in formatBuffer().
|
channels, err := d.dev.NegotiateChannels(int(d.Channels))
|
||||||
channels, err := d.dev.NegotiateChannels(2)
|
if err != nil && d.Channels == 1 {
|
||||||
if err != nil {
|
d.l.Log(logger.Info, "device is unable to record in mono, trying stereo", "error", err)
|
||||||
return OpenError(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
|
// 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.
|
// 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)
|
rate, err = d.dev.NegotiateRate(r)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
foundRate = true
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -302,12 +306,12 @@ func (d *ALSA) open() error {
|
||||||
|
|
||||||
// If no easily divisible rate is found, then use the default rate.
|
// If no easily divisible rate is found, then use the default rate.
|
||||||
if !foundRate {
|
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)
|
rate, err = d.dev.NegotiateRate(defaultSampleRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return OpenError(err)
|
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
|
var aFmt yalsa.FormatType
|
||||||
|
@ -332,7 +336,7 @@ func (d *ALSA) open() error {
|
||||||
default:
|
default:
|
||||||
return OpenError(fmt.Errorf("unsupported sample bits %v", d.BitDepth))
|
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)
|
// 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.
|
// 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 {
|
if err != nil {
|
||||||
return OpenError(err)
|
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 {
|
if err = d.dev.Prepare(); err != nil {
|
||||||
return OpenError(err)
|
return OpenError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
d.l.Log(logger.Debug, pkg+"successfully negotiated device params")
|
d.l.Log(logger.Debug, "successfully negotiated device params")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +375,7 @@ func (d *ALSA) input() {
|
||||||
continue
|
continue
|
||||||
case stopped:
|
case stopped:
|
||||||
if d.dev != nil {
|
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.Close()
|
||||||
d.dev = nil
|
d.dev = nil
|
||||||
}
|
}
|
||||||
|
@ -379,31 +383,31 @@ func (d *ALSA) input() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from audio device.
|
// 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)
|
err := d.dev.Read(d.pb.Data)
|
||||||
if err != nil {
|
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
|
err = d.open() // re-open
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process audio.
|
// Process audio.
|
||||||
d.l.Log(logger.Debug, pkg+"processing audio")
|
d.l.Log(logger.Debug, "processing audio")
|
||||||
toWrite := d.formatBuffer()
|
toWrite := d.formatBuffer()
|
||||||
|
|
||||||
// Write audio to ringbuffer.
|
// Write audio to ringbuffer.
|
||||||
n, err := d.rb.Write(toWrite.Data)
|
n, err := d.rb.Write(toWrite.Data)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
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:
|
case ring.ErrDropped:
|
||||||
d.l.Log(logger.Warning, pkg+"old audio data overwritten")
|
d.l.Log(logger.Warning, "old audio data overwritten")
|
||||||
default:
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -436,7 +440,7 @@ func (d *ALSA) formatBuffer() pcm.Buffer {
|
||||||
if d.pb.Format.Channels == 2 && d.Channels == 1 {
|
if d.pb.Format.Channels == 2 && d.Channels == 1 {
|
||||||
formatted, err = pcm.StereoToMono(d.pb)
|
formatted, err = pcm.StereoToMono(d.pb)
|
||||||
if err != nil {
|
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.
|
// Convert rate.
|
||||||
formatted, err = pcm.Resample(formatted, d.SampleRate)
|
formatted, err = pcm.Resample(formatted, d.SampleRate)
|
||||||
if err != nil {
|
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)
|
enc := adpcm.NewEncoder(b)
|
||||||
_, err = enc.Write(formatted.Data)
|
_, err = enc.Write(formatted.Data)
|
||||||
if err != nil {
|
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()
|
formatted.Data = b.Bytes()
|
||||||
default:
|
default:
|
||||||
d.l.Log(logger.Error, pkg+"unhandled audio codec")
|
d.l.Log(logger.Error, "unhandled audio codec")
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatted
|
return formatted
|
||||||
|
|
|
@ -27,6 +27,7 @@ package alsa
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -35,6 +36,7 @@ import (
|
||||||
|
|
||||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||||
"bitbucket.org/ausocean/av/codec/pcm"
|
"bitbucket.org/ausocean/av/codec/pcm"
|
||||||
|
"bitbucket.org/ausocean/av/device"
|
||||||
"bitbucket.org/ausocean/av/revid/config"
|
"bitbucket.org/ausocean/av/revid/config"
|
||||||
"bitbucket.org/ausocean/utils/logger"
|
"bitbucket.org/ausocean/utils/logger"
|
||||||
)
|
)
|
||||||
|
@ -54,14 +56,14 @@ func TestDevice(t *testing.T) {
|
||||||
l := logger.New(logger.Debug, os.Stderr, true)
|
l := logger.New(logger.Debug, os.Stderr, true)
|
||||||
ai := New(l)
|
ai := New(l)
|
||||||
err := ai.Setup(c)
|
err := ai.Setup(c)
|
||||||
// If there was an error opening the device, skip this test.
|
// Log any config errors, otherwise if there was an error opening a device, skip
|
||||||
if _, ok := err.(OpenError); ok {
|
// 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)
|
t.Skip(err)
|
||||||
}
|
}
|
||||||
// For any other error, report it.
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
err = ai.Start()
|
err = ai.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
|
|
5
go.sum
5
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 h1:dZvENB2cLmeHvEfBULhGxnM9K10IuWFLqjcFKBJHpgw=
|
||||||
bitbucket.org/ausocean/iot v1.2.16/go.mod h1:rRcWt6SoM/jgIZpP1zrpnKb5BhxIMulAJ+q1xTvLh94=
|
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 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/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 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw=
|
||||||
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
|
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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
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 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
|
||||||
|
|
|
@ -25,11 +25,13 @@ LICENSE
|
||||||
package revid
|
package revid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||||
"bitbucket.org/ausocean/av/container/mts"
|
"bitbucket.org/ausocean/av/container/mts"
|
||||||
|
"bitbucket.org/ausocean/av/device"
|
||||||
"bitbucket.org/ausocean/av/device/alsa"
|
"bitbucket.org/ausocean/av/device/alsa"
|
||||||
"bitbucket.org/ausocean/utils/logger"
|
"bitbucket.org/ausocean/utils/logger"
|
||||||
)
|
)
|
||||||
|
@ -42,8 +44,11 @@ func (r *Revid) setupAudio() error {
|
||||||
// Configure ALSA device.
|
// Configure ALSA device.
|
||||||
r.cfg.Logger.Log(logger.Debug, "configuring input device")
|
r.cfg.Logger.Log(logger.Debug, "configuring input device")
|
||||||
err := d.Setup(r.cfg)
|
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)
|
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")
|
r.cfg.Logger.Log(logger.Info, "input device configured")
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue