From 409dcabe0a4bdff3197a6258e20230e3ab98a4b4 Mon Sep 17 00:00:00 2001 From: Trek H Date: Tue, 4 Jun 2019 02:31:35 +0930 Subject: [PATCH] revid: added codec conversion after recording --- codec/codecutil/lex.go | 31 +++++++++++++++++++++++++++++++ revid/audio-input.go | 39 +++++++++++++++++++-------------------- revid/audio-input_test.go | 8 +++----- 3 files changed, 53 insertions(+), 25 deletions(-) create mode 100644 codec/codecutil/lex.go diff --git a/codec/codecutil/lex.go b/codec/codecutil/lex.go new file mode 100644 index 00000000..8e8c36f6 --- /dev/null +++ b/codec/codecutil/lex.go @@ -0,0 +1,31 @@ +package codecutil + +import ( + "io" + "time" +) + +// LexBytes reads n bytes from src and writes them to dst every t seconds. +func LexBytes(dst io.Writer, src io.Reader, t time.Duration, n int) error { + var tick <-chan time.Time + if t == 0 { + tick = make(chan time.Time) + } else { + ticker := time.NewTicker(t) + defer ticker.Stop() + tick = ticker.C + } + + for { + <-tick + buf := make([]byte, n) + _, err := src.Read(buf) + if err != nil { + return err + } + _, err = dst.Write(buf) + if err != nil { + return err + } + } +} diff --git a/revid/audio-input.go b/revid/audio-input.go index c86ee336..0592a6d2 100644 --- a/revid/audio-input.go +++ b/revid/audio-input.go @@ -25,6 +25,7 @@ LICENSE package revid import ( + "bytes" "errors" "fmt" "io" @@ -33,6 +34,7 @@ import ( "github.com/yobert/alsa" + "bitbucket.org/ausocean/av/codec/adpcm" "bitbucket.org/ausocean/av/codec/pcm" "bitbucket.org/ausocean/iot/pi/smartlogger" "bitbucket.org/ausocean/utils/logger" @@ -118,7 +120,11 @@ func NewAudioDevice(cfg *AudioConfig) (*AudioDevice, error) { a.l.Log(logger.Error, "given AudioConfig parameters are too small", "error", err.Error()) return nil, errors.New("given AudioConfig parameters are too small") } - a.chunkSize = int(cs) + if a.Codec == ADPCM { + a.chunkSize = adpcm.EncBytes(int(cs)) + } else { + a.chunkSize = int(cs) + } a.rb = ring.NewBuffer(rbLen, a.chunkSize, rbTimeout) a.mode = paused @@ -349,8 +355,7 @@ func (a *AudioDevice) Read(p []byte) (n int, err error) { return n, nil } -// formatBuffer returns an ALSA buffer that has the recording data from the ac's original ALSA buffer but stored -// in the desired format specified by the ac's parameters. +// formatBuffer returns audio that has been converted to the desired format. func (a *AudioDevice) formatBuffer() alsa.Buffer { var err error @@ -359,42 +364,36 @@ func (a *AudioDevice) formatBuffer() alsa.Buffer { return a.ab } - formatted := alsa.Buffer{Format: a.ab.Format} - bufCopied := false + formatted := alsa.Buffer{Format: a.ab.Format, Data: a.ab.Data} if a.ab.Format.Channels != a.Channels { - // Convert channels. // TODO(Trek): Make this work for conversions other than stereo to mono. if a.ab.Format.Channels == 2 && a.Channels == 1 { formatted.Data, err = pcm.StereoToMono(a.ab) if err != nil { - a.l.Log(logger.Warning, "channel conversion failed, audio has remained stereo", "error", err.Error()) - } else { - formatted.Format.Channels = 1 + a.l.Log(logger.Fatal, "channel conversion failed", "error", err.Error()) } - bufCopied = true } } if a.ab.Format.Rate != a.SampleRate { - // Convert rate. - if bufCopied { - formatted.Data, err = pcm.Resample(formatted, a.SampleRate) - } else { - formatted.Data, err = pcm.Resample(a.ab, a.SampleRate) - } + formatted.Data, err = pcm.Resample(formatted, a.SampleRate) if err != nil { - a.l.Log(logger.Warning, "rate conversion failed, audio has remained original rate", "error", err.Error()) - } else { - formatted.Format.Rate = a.SampleRate + a.l.Log(logger.Fatal, "rate conversion failed", "error", err.Error()) } } switch a.Codec { case PCM: case ADPCM: - // TODO(Trek):Add ADPCM conversion. + b := bytes.NewBuffer(make([]byte, 0, adpcm.EncBytes(len(formatted.Data)))) + enc := adpcm.NewEncoder(b) + _, err = enc.Write(formatted.Data) + if err != nil { + a.l.Log(logger.Fatal, "unable to encode", "error", err.Error()) + } + formatted.Data = b.Bytes() default: a.l.Log(logger.Error, "codec conversion failed, audio has remained original codec", "error", err.Error()) } diff --git a/revid/audio-input_test.go b/revid/audio-input_test.go index 5f057679..871cc92e 100644 --- a/revid/audio-input_test.go +++ b/revid/audio-input_test.go @@ -3,7 +3,6 @@ package revid import ( "bytes" "errors" - "io/ioutil" "testing" "time" @@ -92,10 +91,11 @@ func TestAudio(t *testing.T) { ac := &AudioConfig{ SampleRate: 8000, Channels: 1, - RecPeriod: 1, + RecPeriod: 0.5, BitDepth: 16, Codec: ADPCM, } + n := 2 // Number of periods to wait while recording. // Skip if there are no suitable devices to test with. err := checkDevice(ac) @@ -113,9 +113,7 @@ func TestAudio(t *testing.T) { if err != nil { t.Error(err) } - num := 3 // How many 'ac.RecPeriod's to record. go codecutil.LexBytes(dst, ai, time.Duration(ac.RecPeriod*float64(time.Second)), ai.ChunkSize()) - time.Sleep(time.Millisecond * 1000 * time.Duration(num)) + time.Sleep(time.Millisecond * 1000 * time.Duration(n)) ai.Stop() - err = ioutil.WriteFile("./testout", dst.Bytes(), 0644) }