revid: now using AVDevice implementations

Also renamed constructors of devices to reduce stutter e.g. raspivid.NewRaspivid to
raspivid.New
This commit is contained in:
Saxon 2019-11-12 16:04:07 +10:30
parent 7da3778485
commit dec39a3636
9 changed files with 51 additions and 104 deletions

View File

@ -94,7 +94,7 @@ type Logger interface {
type OpenError error
// NewALSA initializes and returns an ALSA device which has its logger set as the given logger.
func NewALSA(l Logger) *ALSA { return &ALSA{l: l} }
func New(l Logger) *ALSA { return &ALSA{l: l} }
// Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary. If fields are not valid,
@ -185,10 +185,11 @@ func (d *ALSA) Start() error {
// Stop will stop recording audio and close the device.
// Once an ALSA device has been stopped it cannot be started again. This is likely to change in future.
func (d *ALSA) Stop() {
func (d *ALSA) Stop() error {
d.mu.Lock()
d.mode = stopped
d.mu.Unlock()
return nil
}
// ChunkSize returns the number of bytes written to the ringbuffer per d.RecPeriod.

View File

@ -49,7 +49,7 @@ func TestDevice(t *testing.T) {
// Create a new ALSA device, start, read/lex, and then stop it.
l := logger.New(logger.Debug, os.Stderr, true)
ai := NewALSA(l)
ai := New(l)
err := ai.Set(c)
// If there was an error opening the device, skip this test.
if _, ok := err.(OpenError); ok {

View File

@ -41,7 +41,7 @@ type AVFile struct {
}
// NewAVFile returns a new AVFile.
func NewAVFile() *AVFile { return &AVFile{} }
func New() *AVFile { return &AVFile{} }
// Set simply sets the AVFile's config to the passed config.
func (m *AVFile) Set(c config.Config) error {

View File

@ -95,7 +95,7 @@ type GeoVision struct {
}
// NewGeoVision returns a new GeoVision.
func NewGeoVision(l avconfig.Logger) *GeoVision { return &GeoVision{log: l} }
func New(l avconfig.Logger) *GeoVision { return &GeoVision{log: l} }
// Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary using config to control the

View File

@ -111,8 +111,8 @@ type Raspivid struct {
log config.Logger
}
// NewRaspivid returns a new Raspivid.
func NewRaspivid(l config.Logger) *Raspivid { return &Raspivid{log: l} }
// New returns a new Raspivid.
func New(l config.Logger) *Raspivid { return &Raspivid{log: l} }
// Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary. If fields are not valid,

View File

@ -65,8 +65,8 @@ type Webcam struct {
cmd *exec.Cmd
}
// NewWebcam returns a new Webcam.
func NewWebcam(l config.Logger) *Webcam {
// New returns a new Webcam.
func New(l config.Logger) *Webcam {
return &Webcam{log: l}
}

View File

@ -1,53 +0,0 @@
/*
LICENSE
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
This is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package revid
import (
"time"
"bitbucket.org/ausocean/av/device/alsa"
"bitbucket.org/ausocean/utils/logger"
)
// startAudioDevice is used to start capturing audio from an ALSA audio device and processing it.
// It returns a function that can be used to stop the device and any errors that occur.
func (r *Revid) startAudioDevice() (func() error, error) {
// Create audio device.
ai := alsa.NewALSA(r.cfg.Logger)
err := ai.Set(r.cfg)
if err != nil {
r.cfg.Logger.Log(logger.Fatal, pkg+"failed to setup ALSA device", "error", err.Error())
}
// Start ALSA audio device
err = ai.Start()
if err != nil {
r.cfg.Logger.Log(logger.Fatal, pkg+"failed to start ALSA device", "error", err.Error())
}
// Process output from ALSA audio device.
r.cfg.ChunkSize = ai.ChunkSize()
r.wg.Add(1)
go r.processFrom(ai, time.Duration(float64(time.Second)/r.cfg.WriteRate))
return func() error {
ai.Stop()
return nil
}, nil
}

View File

@ -1,25 +0,0 @@
/*
LICENSE
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
This is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
// startAudioDevice is used to start capturing audio from an audio device
// TODO: Implement on Windows.
package revid
func (r *Revid) startAudioDevice() (func() error, error) {
panic("Audio not implemented on Windows")
}

View File

@ -44,6 +44,12 @@ import (
"bitbucket.org/ausocean/av/codec/mjpeg"
"bitbucket.org/ausocean/av/container/flv"
"bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/device"
"bitbucket.org/ausocean/av/device/audio"
"bitbucket.org/ausocean/av/device/file"
"bitbucket.org/ausocean/av/device/geovision"
"bitbucket.org/ausocean/av/device/raspivid"
"bitbucket.org/ausocean/av/device/webcam"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/iot/pi/netsender"
"bitbucket.org/ausocean/utils/ioext"
@ -89,9 +95,8 @@ type Revid struct {
// ns holds the netsender.Sender responsible for HTTP.
ns *netsender.Sender
// setupInput holds the current approach to setting up
// the input stream. It returns a function used for cleaning up, and any errors.
setupInput func() (func() error, error)
// input will capture audio or video from which we can read data.
input device.AVDevice
// closeInput holds the cleanup function return from setupInput and is called
// in Revid.Stop().
@ -314,20 +319,25 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
switch r.cfg.Input {
case config.InputRaspivid:
r.setupInput = r.startRaspivid
r.input = raspivid.New(r.cfg.Logger)
switch r.cfg.InputCodec {
case codecutil.H264:
r.lexTo = h264.Lex
case codecutil.MJPEG:
r.lexTo = mjpeg.Lex
}
case config.InputV4L:
r.setupInput = r.startV4L
r.input = webcam.New(r.cfg.Logger)
r.lexTo = h264.Lex
case config.InputFile:
r.setupInput = r.setupInputForFile
r.input = file.New()
case config.InputRTSP:
r.setupInput = r.startRTSPCamera
r.input = geovision.New(r.cfg.Logger)
switch r.cfg.InputCodec {
case codecutil.H264:
r.lexTo = h264.NewExtractor().Extract
@ -336,11 +346,13 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
case codecutil.MJPEG:
panic("not implemented")
}
case config.InputAudio:
mts.Meta.Add("sampleRate", strconv.Itoa(r.cfg.SampleRate))
mts.Meta.Add("channels", strconv.Itoa(r.cfg.Channels))
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.cfg.RecPeriod))
mts.Meta.Add("bitDepth", strconv.Itoa(r.cfg.BitDepth))
switch r.cfg.InputCodec {
case codecutil.PCM:
mts.Meta.Add("codec", "pcm")
@ -349,8 +361,17 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
default:
r.cfg.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
}
r.setupInput = r.startAudioDevice
r.lexTo = codecutil.NewByteLexer(&r.cfg.ChunkSize).Lex
r.input = audio.New(r.cfg.Logger)
cs := r.input.(*audio.ALSA).ChunkSize()
r.lexTo = codecutil.NewByteLexer(&cs).Lex
}
// Configure the input device. We know that defaults are set, so no need to
// return error, but we should log.
err := r.input.Set(r.cfg)
if err != nil {
r.cfg.Logger.Log(logger.Warning, pkg+"errors from configuring input device", "errors", err)
}
return nil
@ -375,10 +396,15 @@ func (r *Revid) Start() error {
r.Stop()
return err
}
r.closeInput, err = r.setupInput()
err = r.input.Start()
if err != nil {
return err
return fmt.Errorf("could not start input device: %w", err)
}
r.wg.Add(1)
go r.processFrom(r.input, 0)
r.running = true
return nil
}
@ -396,15 +422,13 @@ func (r *Revid) Stop() {
r.mu.Lock()
defer r.mu.Unlock()
if r.closeInput != nil {
err := r.closeInput()
if err != nil {
r.cfg.Logger.Log(logger.Error, pkg+"could not close input", "error", err.Error())
}
err := r.input.Stop()
if err != nil {
r.cfg.Logger.Log(logger.Error, pkg+"could not stop input", "error", err.Error())
}
r.cfg.Logger.Log(logger.Info, pkg+"closing pipeline")
err := r.encoders.Close()
err = r.encoders.Close()
if err != nil {
r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error())
}