diff --git a/revid/audio-input.go b/revid/audio-input.go index c19a4d21..ff480a1c 100644 --- a/revid/audio-input.go +++ b/revid/audio-input.go @@ -41,9 +41,9 @@ import ( const ( logPath = "/var/log/netsender" - rbDuration = 300 // seconds rbTimeout = 100 * time.Millisecond rbNextTimeout = 100 * time.Millisecond + rbLen = 200 ) const ( @@ -77,7 +77,7 @@ type AudioConfig struct { SampleRate int Channels int BitDepth int - RecPeriod int + RecPeriod float64 Codec uint8 } @@ -109,7 +109,6 @@ func NewAudioDevice(cfg *AudioConfig) *audioDevice { // Setup ring buffer to capture audio in periods of a.RecPeriod seconds, and buffer rbDuration seconds in total. a.ab = a.dev.NewBufferDuration(time.Second * time.Duration(a.RecPeriod)) a.chunkSize = (((len(a.ab.Data) / a.dev.BufferFormat().Channels) * a.Channels) / a.dev.BufferFormat().Rate) * a.SampleRate - rbLen := rbDuration / a.RecPeriod a.rb = ring.NewBuffer(rbLen, a.chunkSize, rbTimeout) a.mode = paused @@ -120,13 +119,11 @@ func NewAudioDevice(cfg *AudioConfig) *audioDevice { // Start will start recording audio and writing to the output. func (a *audioDevice) Start() { a.mu.Lock() - mode := a.mode - a.mu.Unlock() - switch mode { + switch a.mode { case paused: // Start Recording go a.input() - mode = running + a.mode = running case stopped: // Open the audio device and start recording. err := a.open() @@ -134,25 +131,23 @@ func (a *audioDevice) Start() { log.Log(logger.Fatal, "alsa.open failed", "error", err.Error()) } go a.input() - mode = running + a.mode = running case running: return } - a.mu.Lock() - a.mode = mode a.mu.Unlock() } // Stop will stop recording audio and close the device func (a *audioDevice) Stop() { a.mu.Lock() - a.mode = stopped - a.mu.Unlock() if a.dev != nil { log.Log(logger.Debug, "Closing", "source", a.source) a.dev.Close() a.dev = nil } + a.mode = stopped + a.mu.Unlock() } @@ -273,17 +268,16 @@ func (a *audioDevice) open() error { func (a *audioDevice) input() { for { a.mu.Lock() - mode := a.mode - a.mu.Unlock() - switch mode { + switch a.mode { case paused: + a.mu.Unlock() time.Sleep(time.Duration(a.RecPeriod) * time.Second) continue case stopped: - break + a.mu.Unlock() + return } log.Log(logger.Debug, "Recording audio for period", "seconds", a.RecPeriod) - a.mu.Lock() err := a.dev.Read(a.ab.Data) a.mu.Unlock() if err != nil { @@ -318,6 +312,16 @@ func (a *audioDevice) input() { // Read reads a full PCM chunk from the ringbuffer, returning the number of bytes read upon success. // Any errors returned are unexpected and should be considered fatal. func (a *audioDevice) Read(p []byte) (n int, err error) { + a.mu.Lock() + if a.rb == nil { + fmt.Println("READ: RB IS NIL") + } + switch a.mode { + case paused: + return 0, nil + case stopped: + return 0, nil + } chunk, err := a.rb.Next(rbNextTimeout) switch err { case nil: @@ -336,8 +340,8 @@ func (a *audioDevice) Read(p []byte) (n int, err error) { log.Log(logger.Error, "Unexpected error from ring.Read", "error", err.Error()) return n, err } - log.Log(logger.Debug, "Read audio from ringbuffer", "length", n) + a.mu.Unlock() return n, nil } diff --git a/revid/audio-input_test.go b/revid/audio-input_test.go index 0a1cb44e..8f0d888b 100644 --- a/revid/audio-input_test.go +++ b/revid/audio-input_test.go @@ -1,25 +1,20 @@ package revid import ( + "bytes" + "errors" "testing" + "time" + "bitbucket.org/ausocean/av/codec/lex" "github.com/yobert/alsa" ) -func TestAudioDevice(t *testing.T) { - // We want to open a device with a standard configuration. - ac := &AudioConfig{ - SampleRate: 8000, - Channels: 1, - RecPeriod: 1, - BitDepth: 16, - Codec: ADPCM, - } - - // Check that a device exists with the desired parameters. +// Check that a device exists with the given config parameters. +func checkDevice(ac *AudioConfig) error { cards, err := alsa.OpenCards() if err != nil { - t.Skip("skipping, no audio card found") + return errors.New("no audio cards found") } defer alsa.CloseCards(cards) var testDev *alsa.Device @@ -37,11 +32,15 @@ func TestAudioDevice(t *testing.T) { } } if testDev == nil { - t.Skip("skipping, no suitable audio device found") + return errors.New("no suitable device found") + } + err = testDev.Open() + if err != nil { + return err } _, err = testDev.NegotiateChannels(2) if err != nil { - t.Skip("skipping, no suitable audio device found") + return err } foundRate := false for i := 0; i < len(rates) && !foundRate; i++ { @@ -58,25 +57,57 @@ func TestAudioDevice(t *testing.T) { if !foundRate { _, err = testDev.NegotiateRate(defaultSampleRate) if err != nil { - t.Skip("skipping, no suitable audio device found") + return err } } - _, err = testDev.NegotiateFormat(alsa.S16_LE) + 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 { - t.Skip("skipping, no suitable audio device found") + return err } _, err = testDev.NegotiateBufferSize(8192, 16384) if err != nil { - t.Skip("skipping, no suitable audio device found") + return err } if err = testDev.Prepare(); err != nil { - t.Skip("skipping, no suitable audio device found") + return err } - testDev.Close() - - ai := NewAudioDevice(ac) - - ai.Start() - - ai.Stop() + if testDev != nil { + testDev.Close() + } + return nil +} + +func TestAudio(t *testing.T) { + // We want to open a device with a standard configuration. + ac := &AudioConfig{ + SampleRate: 8000, + Channels: 1, + RecPeriod: 0.01, + BitDepth: 16, + Codec: ADPCM, + } + + // Skip if there are no suitable devices to test with. + err := checkDevice(ac) + if err != nil { + t.Error(err) + } + + // Create a new audioDevice, start, read/lex, and then stop it. + ai := NewAudioDevice(ac) + dst := bytes.NewBuffer(make([]byte, 0, ai.ChunkSize()*4)) + ai.Start() + go lex.ADPCM(dst, ai, time.Duration(ac.RecPeriod), ai.ChunkSize()) + time.Sleep(time.Millisecond * 10) + ai.Stop() + } diff --git a/revid/config.go b/revid/config.go index 82a940a1..e4ff1a46 100644 --- a/revid/config.go +++ b/revid/config.go @@ -74,10 +74,10 @@ type Config struct { AutoWhiteBalance string // Audio - SampleRate int // Samples a second (Hz). - RecPeriod int // How many seconds to record at a time. - Channels int // Number of audio channels, 1 for mono, 2 for stereo. - BitDepth int // Sample bit depth. + SampleRate int // Samples a second (Hz). + RecPeriod float64 // How many seconds to record at a time. + Channels int // Number of audio channels, 1 for mono, 2 for stereo. + BitDepth int // Sample bit depth. } // Possible modes for raspivid --exposure parameter. @@ -168,7 +168,7 @@ const ( defaultSampleRate = 48000 defaultBitDepth = 16 defaultChannels = 1 - defaultRecPeriod = 1 + defaultRecPeriod = 1.0 ) // Validate checks for any errors in the config fields and defaults settings