mirror of https://bitbucket.org/ausocean/av.git
revid: added concurrency support to start and stop
This commit is contained in:
parent
c58c573cd7
commit
17d59014c6
|
@ -41,9 +41,9 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
logPath = "/var/log/netsender"
|
logPath = "/var/log/netsender"
|
||||||
rbDuration = 300 // seconds
|
|
||||||
rbTimeout = 100 * time.Millisecond
|
rbTimeout = 100 * time.Millisecond
|
||||||
rbNextTimeout = 100 * time.Millisecond
|
rbNextTimeout = 100 * time.Millisecond
|
||||||
|
rbLen = 200
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -77,7 +77,7 @@ type AudioConfig struct {
|
||||||
SampleRate int
|
SampleRate int
|
||||||
Channels int
|
Channels int
|
||||||
BitDepth int
|
BitDepth int
|
||||||
RecPeriod int
|
RecPeriod float64
|
||||||
Codec uint8
|
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.
|
// 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.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
|
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.rb = ring.NewBuffer(rbLen, a.chunkSize, rbTimeout)
|
||||||
|
|
||||||
a.mode = paused
|
a.mode = paused
|
||||||
|
@ -120,13 +119,11 @@ func NewAudioDevice(cfg *AudioConfig) *audioDevice {
|
||||||
// Start will start recording audio and writing to the output.
|
// Start will start recording audio and writing to the output.
|
||||||
func (a *audioDevice) Start() {
|
func (a *audioDevice) Start() {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
mode := a.mode
|
switch a.mode {
|
||||||
a.mu.Unlock()
|
|
||||||
switch mode {
|
|
||||||
case paused:
|
case paused:
|
||||||
// Start Recording
|
// Start Recording
|
||||||
go a.input()
|
go a.input()
|
||||||
mode = running
|
a.mode = running
|
||||||
case stopped:
|
case stopped:
|
||||||
// Open the audio device and start recording.
|
// Open the audio device and start recording.
|
||||||
err := a.open()
|
err := a.open()
|
||||||
|
@ -134,25 +131,23 @@ func (a *audioDevice) Start() {
|
||||||
log.Log(logger.Fatal, "alsa.open failed", "error", err.Error())
|
log.Log(logger.Fatal, "alsa.open failed", "error", err.Error())
|
||||||
}
|
}
|
||||||
go a.input()
|
go a.input()
|
||||||
mode = running
|
a.mode = running
|
||||||
case running:
|
case running:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.mu.Lock()
|
|
||||||
a.mode = mode
|
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop will stop recording audio and close the device
|
// Stop will stop recording audio and close the device
|
||||||
func (a *audioDevice) Stop() {
|
func (a *audioDevice) Stop() {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
a.mode = stopped
|
|
||||||
a.mu.Unlock()
|
|
||||||
if a.dev != nil {
|
if a.dev != nil {
|
||||||
log.Log(logger.Debug, "Closing", "source", a.source)
|
log.Log(logger.Debug, "Closing", "source", a.source)
|
||||||
a.dev.Close()
|
a.dev.Close()
|
||||||
a.dev = nil
|
a.dev = nil
|
||||||
}
|
}
|
||||||
|
a.mode = stopped
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,17 +268,16 @@ func (a *audioDevice) open() error {
|
||||||
func (a *audioDevice) input() {
|
func (a *audioDevice) input() {
|
||||||
for {
|
for {
|
||||||
a.mu.Lock()
|
a.mu.Lock()
|
||||||
mode := a.mode
|
switch a.mode {
|
||||||
a.mu.Unlock()
|
|
||||||
switch mode {
|
|
||||||
case paused:
|
case paused:
|
||||||
|
a.mu.Unlock()
|
||||||
time.Sleep(time.Duration(a.RecPeriod) * time.Second)
|
time.Sleep(time.Duration(a.RecPeriod) * time.Second)
|
||||||
continue
|
continue
|
||||||
case stopped:
|
case stopped:
|
||||||
break
|
a.mu.Unlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
log.Log(logger.Debug, "Recording audio for period", "seconds", a.RecPeriod)
|
log.Log(logger.Debug, "Recording audio for period", "seconds", a.RecPeriod)
|
||||||
a.mu.Lock()
|
|
||||||
err := a.dev.Read(a.ab.Data)
|
err := a.dev.Read(a.ab.Data)
|
||||||
a.mu.Unlock()
|
a.mu.Unlock()
|
||||||
if err != nil {
|
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.
|
// 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.
|
// Any errors returned are unexpected and should be considered fatal.
|
||||||
func (a *audioDevice) Read(p []byte) (n int, err error) {
|
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)
|
chunk, err := a.rb.Next(rbNextTimeout)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
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())
|
log.Log(logger.Error, "Unexpected error from ring.Read", "error", err.Error())
|
||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Log(logger.Debug, "Read audio from ringbuffer", "length", n)
|
log.Log(logger.Debug, "Read audio from ringbuffer", "length", n)
|
||||||
|
a.mu.Unlock()
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,20 @@
|
||||||
package revid
|
package revid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bitbucket.org/ausocean/av/codec/lex"
|
||||||
"github.com/yobert/alsa"
|
"github.com/yobert/alsa"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAudioDevice(t *testing.T) {
|
// Check that a device exists with the given config parameters.
|
||||||
// We want to open a device with a standard configuration.
|
func checkDevice(ac *AudioConfig) error {
|
||||||
ac := &AudioConfig{
|
|
||||||
SampleRate: 8000,
|
|
||||||
Channels: 1,
|
|
||||||
RecPeriod: 1,
|
|
||||||
BitDepth: 16,
|
|
||||||
Codec: ADPCM,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that a device exists with the desired parameters.
|
|
||||||
cards, err := alsa.OpenCards()
|
cards, err := alsa.OpenCards()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("skipping, no audio card found")
|
return errors.New("no audio cards found")
|
||||||
}
|
}
|
||||||
defer alsa.CloseCards(cards)
|
defer alsa.CloseCards(cards)
|
||||||
var testDev *alsa.Device
|
var testDev *alsa.Device
|
||||||
|
@ -37,11 +32,15 @@ func TestAudioDevice(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if testDev == nil {
|
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)
|
_, err = testDev.NegotiateChannels(2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("skipping, no suitable audio device found")
|
return err
|
||||||
}
|
}
|
||||||
foundRate := false
|
foundRate := false
|
||||||
for i := 0; i < len(rates) && !foundRate; i++ {
|
for i := 0; i < len(rates) && !foundRate; i++ {
|
||||||
|
@ -58,25 +57,57 @@ func TestAudioDevice(t *testing.T) {
|
||||||
if !foundRate {
|
if !foundRate {
|
||||||
_, err = testDev.NegotiateRate(defaultSampleRate)
|
_, err = testDev.NegotiateRate(defaultSampleRate)
|
||||||
if err != nil {
|
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 {
|
if err != nil {
|
||||||
t.Skip("skipping, no suitable audio device found")
|
return err
|
||||||
}
|
}
|
||||||
_, err = testDev.NegotiateBufferSize(8192, 16384)
|
_, err = testDev.NegotiateBufferSize(8192, 16384)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Skip("skipping, no suitable audio device found")
|
return err
|
||||||
}
|
}
|
||||||
if err = testDev.Prepare(); err != nil {
|
if err = testDev.Prepare(); err != nil {
|
||||||
t.Skip("skipping, no suitable audio device found")
|
return err
|
||||||
}
|
}
|
||||||
|
if testDev != nil {
|
||||||
testDev.Close()
|
testDev.Close()
|
||||||
|
}
|
||||||
ai := NewAudioDevice(ac)
|
return nil
|
||||||
|
}
|
||||||
ai.Start()
|
|
||||||
|
func TestAudio(t *testing.T) {
|
||||||
ai.Stop()
|
// 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()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,7 +75,7 @@ type Config struct {
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
SampleRate int // Samples a second (Hz).
|
SampleRate int // Samples a second (Hz).
|
||||||
RecPeriod int // How many seconds to record at a time.
|
RecPeriod float64 // How many seconds to record at a time.
|
||||||
Channels int // Number of audio channels, 1 for mono, 2 for stereo.
|
Channels int // Number of audio channels, 1 for mono, 2 for stereo.
|
||||||
BitDepth int // Sample bit depth.
|
BitDepth int // Sample bit depth.
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,7 @@ const (
|
||||||
defaultSampleRate = 48000
|
defaultSampleRate = 48000
|
||||||
defaultBitDepth = 16
|
defaultBitDepth = 16
|
||||||
defaultChannels = 1
|
defaultChannels = 1
|
||||||
defaultRecPeriod = 1
|
defaultRecPeriod = 1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
// Validate checks for any errors in the config fields and defaults settings
|
// Validate checks for any errors in the config fields and defaults settings
|
||||||
|
|
Loading…
Reference in New Issue