diff --git a/input/audio/audio.go b/input/audio/audio.go index 4cfbd695..9ca858f0 100644 --- a/input/audio/audio.go +++ b/input/audio/audio.go @@ -59,9 +59,6 @@ const ( stopped ) -// Rates contains the standard audio sample rates used by package audio. -var Rates = [8]int{8000, 16000, 32000, 44100, 48000, 88200, 96000, 192000} - // Device holds everything we need to know about the audio input stream and implements io.Reader. type Device struct { l Logger // Logger for device's routines to log to. @@ -91,6 +88,9 @@ type Logger interface { Log(level int8, message string, params ...interface{}) } +// OpenError is used to determine whether an error has originated from attempting to open a device. +type OpenError error + // NewDevice initializes and returns an Device which can be started, read from, and stopped. func NewDevice(cfg *Config, l Logger) (*Device, error) { d := &Device{ @@ -135,6 +135,7 @@ func NewDevice(cfg *Config, l Logger) (*Device, error) { } // Start will start recording audio and writing to the ringbuffer. +// Once a Device has been stopped it cannot be started again. This is likely to change in future. func (d *Device) Start() error { d.mu.Lock() mode := d.mode @@ -156,6 +157,7 @@ func (d *Device) Start() error { } // Stop will stop recording audio and close the device. +// Once a Device has been stopped it cannot be started again. This is likely to change in future. func (d *Device) Stop() { d.mu.Lock() d.mode = stopped @@ -181,7 +183,7 @@ func (d *Device) open() error { d.l.Log(logger.Debug, pkg+"opening sound card") cards, err := alsa.OpenCards() if err != nil { - return err + return OpenError(err) } defer alsa.CloseCards(cards) @@ -202,39 +204,41 @@ func (d *Device) open() error { } } if d.dev == nil { - return errors.New("no audio device found") + return OpenError(errors.New("no audio device found")) } d.l.Log(logger.Debug, pkg+"opening audio device", "title", d.dev.Title) err = d.dev.Open() if err != nil { - return err + return OpenError(err) } // 2 channels is what most devices need to record in. If mono is requested, // the recording will be converted in formatBuffer(). - devChan, err := d.dev.NegotiateChannels(2) + channels, err := d.dev.NegotiateChannels(2) if err != nil { - return err + return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device channels set", "channels", devChan) + d.l.Log(logger.Debug, pkg+"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. + // rates is a slice of common sample rates including the standard for CD (44100Hz) and standard for professional audio recording (48000Hz). // Note: if a card thinks it can record at a rate but can't actually, this can cause a failure. // Eg. the audioinjector sound card is supposed to record at 8000Hz and 16000Hz but it can't due to a firmware issue, - // a fix for this is to remove 8000 and 16000 from the Rates slice. + // a fix for this is to remove 8000 and 16000 from the rates slice. + var rates = [8]int{8000, 16000, 32000, 44100, 48000, 88200, 96000, 192000} foundRate := false - var devRate int - for i := 0; i < len(Rates) && !foundRate; i++ { - if Rates[i] < d.SampleRate { + var rate int + for i := 0; i < len(rates) && !foundRate; i++ { + if rates[i] < d.SampleRate { continue } - if Rates[i]%d.SampleRate == 0 { - devRate, err = d.dev.NegotiateRate(Rates[i]) + if rates[i]%d.SampleRate == 0 { + rate, err = d.dev.NegotiateRate(rates[i]) if err == nil { foundRate = true - d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", devRate) + d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", rate) } } } @@ -242,11 +246,11 @@ func (d *Device) 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) - devRate, err = d.dev.NegotiateRate(defaultSampleRate) + rate, err = d.dev.NegotiateRate(defaultSampleRate) if err != nil { - return err + return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", devRate) + d.l.Log(logger.Debug, pkg+"alsa device sample rate set", "rate", rate) } var aFmt alsa.FormatType @@ -256,40 +260,40 @@ func (d *Device) open() error { case 32: aFmt = alsa.S32_LE default: - return fmt.Errorf("unsupported sample bits %v", d.BitDepth) + return OpenError(fmt.Errorf("unsupported sample bits %v", d.BitDepth)) } devFmt, err := d.dev.NegotiateFormat(aFmt) if err != nil { return err } - var devBits int + var bitdepth int switch devFmt { case alsa.S16_LE: - devBits = 16 + bitdepth = 16 case alsa.S32_LE: - devBits = 32 + bitdepth = 32 default: - return 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", devBits) + d.l.Log(logger.Debug, pkg+"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. // So we will find the closest power of 2 to the desired period size. const wantPeriod = 0.05 //seconds - bytesPerSecond := devRate * devChan * (devBits / 8) + bytesPerSecond := rate * channels * (bitdepth / 8) wantPeriodSize := int(float64(bytesPerSecond) * wantPeriod) nearWantPeriodSize := nearestPowerOfTwo(wantPeriodSize) // At least two period sizes should fit within the buffer. - devBufferSize, err := d.dev.NegotiateBufferSize(nearWantPeriodSize * 2) + bufSize, err := d.dev.NegotiateBufferSize(nearWantPeriodSize * 2) if err != nil { - return err + return OpenError(err) } - d.l.Log(logger.Debug, pkg+"alsa device buffer size set", "buffersize", devBufferSize) + d.l.Log(logger.Debug, pkg+"alsa device buffer size set", "buffersize", bufSize) if err = d.dev.Prepare(); err != nil { - return err + return OpenError(err) } d.l.Log(logger.Debug, pkg+"successfully negotiated ALSA params") diff --git a/input/audio/audio_test.go b/input/audio/audio_test.go index dc4556b4..f26bb4c3 100644 --- a/input/audio/audio_test.go +++ b/input/audio/audio_test.go @@ -25,7 +25,6 @@ LICENSE package audio import ( - "errors" "io/ioutil" "os" "strconv" @@ -34,85 +33,8 @@ import ( "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/utils/logger" - "github.com/yobert/alsa" ) -// Check that a device exists with the given config parameters. -func checkDevice(ac *Config) error { - cards, err := alsa.OpenCards() - if err != nil { - return errors.New("no audio cards found") - } - defer alsa.CloseCards(cards) - var testDev *alsa.Device - for _, card := range cards { - devices, err := card.Devices() - if err != nil { - continue - } - for _, dev := range devices { - if dev.Type != alsa.PCM || !dev.Record { - continue - } - testDev = dev - break - } - } - if testDev == nil { - return errors.New("no suitable device found") - } - err = testDev.Open() - if err != nil { - return err - } - _, err = testDev.NegotiateChannels(2) - if err != nil { - return err - } - foundRate := false - for i := 0; i < len(Rates) && !foundRate; i++ { - if Rates[i] < ac.SampleRate { - continue - } - if Rates[i]%ac.SampleRate == 0 { - _, err = testDev.NegotiateRate(Rates[i]) - if err == nil { - foundRate = true - } - } - } - if !foundRate { - _, err = testDev.NegotiateRate(defaultSampleRate) - if err != nil { - return err - } - } - var aFmt alsa.FormatType - switch ac.BitDepth { - case 16: - aFmt = alsa.S16_LE - case 32: - aFmt = alsa.S32_LE - default: - return errors.New("unsupported bitdepth") - } - _, err = testDev.NegotiateFormat(aFmt) - if err != nil { - return err - } - _, err = testDev.NegotiateBufferSize(8192, 16384) - if err != nil { - return err - } - if err = testDev.Prepare(); err != nil { - return err - } - if testDev != nil { - testDev.Close() - } - return nil -} - func TestDevice(t *testing.T) { // We want to open a device with a standard configuration. ac := &Config{ @@ -124,15 +46,14 @@ func TestDevice(t *testing.T) { } n := 2 // Number of periods to wait while recording. - // Skip if there are no suitable devices to test with. - err := checkDevice(ac) - if err != nil { - t.Skip(err) - } - // Create a new audio Device, start, read/lex, and then stop it. l := logger.New(logger.Debug, os.Stderr) ai, err := NewDevice(ac, l) + // If there was an error opening the device, skip this test. + if _, ok := err.(OpenError); ok { + t.Skip(err) + } + // For any other error, report it. if err != nil { t.Error(err) }