created av/device package and sub packages raspivid, geovision, webcam and file

av/device/device.go now contains the AVDevice interface and implementations of this
interface, namely, raspivid, geovision, webcam and file are contained in the packages
av/device/raspivid, av/device/geovision, av/device/webcam and av/device/file
respctively. config.go and testing was also moved to a new package called config.go in
order to remove would be circular dependency between AVDevice implementations and revid.
Modifications were made elsewhere expecting config.Config to be part of the revid package.
This commit is contained in:
Saxon 2019-11-06 17:27:10 +10:30
parent 9a93e92b50
commit 57d73a8d0a
19 changed files with 549 additions and 362 deletions

View File

@ -1,6 +1,6 @@
/* /*
NAME NAME
revid-cli - command line interface for Revid. revid-cli - command line interface for revid.
DESCRIPTION DESCRIPTION
See Readme.md See Readme.md
@ -42,6 +42,7 @@ import (
"bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/container/mts/meta"
"bitbucket.org/ausocean/av/revid" "bitbucket.org/ausocean/av/revid"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/iot/pi/netsender"
"bitbucket.org/ausocean/iot/pi/sds" "bitbucket.org/ausocean/iot/pi/sds"
"bitbucket.org/ausocean/iot/pi/smartlogger" "bitbucket.org/ausocean/iot/pi/smartlogger"
@ -102,8 +103,8 @@ func main() {
// handleFlags parses command line flags and returns a revid configuration // handleFlags parses command line flags and returns a revid configuration
// based on them. // based on them.
func handleFlags() revid.Config { func handleFlags() config.Config {
var cfg revid.Config var cfg config.Config
var ( var (
cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`") cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
@ -128,8 +129,8 @@ func handleFlags() revid.Config {
rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)") rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)")
brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ") brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")") exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(config.ExposureModes[:], ",")+")")
autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")") autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(config.AutoWhiteBalanceModes[:], ",")+")")
// Audio specific flags. // Audio specific flags.
sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio")
@ -179,15 +180,15 @@ func handleFlags() revid.Config {
switch *inputPtr { switch *inputPtr {
case "Raspivid": case "Raspivid":
cfg.Input = revid.InputRaspivid cfg.Input = config.InputRaspivid
case "v4l": case "v4l":
cfg.Input = revid.InputV4L cfg.Input = config.InputV4L
case "File": case "File":
cfg.Input = revid.InputFile cfg.Input = config.InputFile
case "Audio": case "Audio":
cfg.Input = revid.InputAudio cfg.Input = config.InputAudio
case "RTSP": case "RTSP":
cfg.Input = revid.InputRTSP cfg.Input = config.InputRTSP
case "": case "":
default: default:
log.Log(logger.Error, pkg+"bad input argument") log.Log(logger.Error, pkg+"bad input argument")
@ -214,13 +215,13 @@ func handleFlags() revid.Config {
for _, o := range outputs { for _, o := range outputs {
switch o { switch o {
case "File": case "File":
cfg.Outputs = append(cfg.Outputs, revid.OutputFile) cfg.Outputs = append(cfg.Outputs, config.OutputFile)
case "Http": case "Http":
cfg.Outputs = append(cfg.Outputs, revid.OutputHTTP) cfg.Outputs = append(cfg.Outputs, config.OutputHTTP)
case "Rtmp": case "Rtmp":
cfg.Outputs = append(cfg.Outputs, revid.OutputRTMP) cfg.Outputs = append(cfg.Outputs, config.OutputRTMP)
case "Rtp": case "Rtp":
cfg.Outputs = append(cfg.Outputs, revid.OutputRTP) cfg.Outputs = append(cfg.Outputs, config.OutputRTP)
case "": case "":
default: default:
log.Log(logger.Error, pkg+"bad output argument", "arg", o) log.Log(logger.Error, pkg+"bad output argument", "arg", o)
@ -258,7 +259,7 @@ func handleFlags() revid.Config {
} }
// initialize then run the main NetSender client // initialize then run the main NetSender client
func run(cfg revid.Config) { func run(cfg config.Config) {
log.Log(logger.Info, pkg+"running in NetSender mode") log.Log(logger.Info, pkg+"running in NetSender mode")
var rv *revid.Revid var rv *revid.Revid

64
device/device.go Normal file
View File

@ -0,0 +1,64 @@
/*
DESCRIPTION
device.go provides AVDevice, and interface that describes a configurable
audio or video device that can be started and stopped from which data may
be obtained.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
It 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 device
import (
"fmt"
"io"
"bitbucket.org/ausocean/av/revid/config"
)
// AVDevice describes a configurable audio or video device from which media data
// can be obtained. AVDevice is an io.Reader.
type AVDevice interface {
io.Reader
// Set allows for configuration of the AVDevice using a Config struct. All,
// some or none of the fields of the Config struct may be used for configuration
// by an implementation. An implementation should specify what fields are
// considered.
Set(c config.Config) error
// Start will start the AVDevice capturing media data; after which the Read
// method may be called to obtain the data. The format of the data may differ
// and should be specified by the implementation.
Start() error
// Stop will stop the AVDevice from capturing media data. From this point
// Reads will no longer be successful.
Stop() error
}
// multiError implements the built in error interface. multiError is used here
// to collect multi errors during validation of configruation parameters for o
// AVDevices.
type MultiError []error
func (me MultiError) Error() string {
return fmt.Sprintf("%v", me)
}

View File

@ -22,27 +22,29 @@ LICENSE
in gpl.txt. If not, see http://www.gnu.org/licenses. in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package file
import ( import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"bitbucket.org/ausocean/av/revid/config"
) )
// AVFile is an implementation of the AVDevice interface for a file containg // AVFile is an implementation of the AVDevice interface for a file containg
// audio or video data. // audio or video data.
type AVFile struct { type AVFile struct {
f io.ReadCloser f io.ReadCloser
cfg Config cfg config.Config
} }
// NewAVFile returns a new AVFile. // NewAVFile returns a new AVFile.
func NewAVFile() *AVFile { return &AVFile{} } func NewAVFile() *AVFile { return &AVFile{} }
// Set simply sets the AVFile's config to the passed config. // Set simply sets the AVFile's config to the passed config.
func (m *AVFile) Set(c Config) error { func (m *AVFile) Set(c config.Config) error {
m.cfg = c m.cfg = c
return nil return nil
} }

View File

@ -23,32 +23,53 @@ LICENSE
in gpl.txt. If not, see http://www.gnu.org/licenses. in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package geovision
import ( import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"strconv"
"strings"
"time" "time"
"bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/av/input/gvctrl" "bitbucket.org/ausocean/av/device"
"bitbucket.org/ausocean/av/device/geovision/gvctrl"
"bitbucket.org/ausocean/av/protocol/rtcp" "bitbucket.org/ausocean/av/protocol/rtcp"
"bitbucket.org/ausocean/av/protocol/rtp" "bitbucket.org/ausocean/av/protocol/rtp"
"bitbucket.org/ausocean/av/protocol/rtsp" "bitbucket.org/ausocean/av/protocol/rtsp"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
// Indicate package when logging.
const pkg = "geovision: "
// Constants for real time clients.
const (
rtpPort = 60000
rtcpPort = 60001
defaultServerRTCPPort = 17301
)
// TODO: remove this when gvctrl has configurable user and pass.
const (
ipCamUser = "admin"
ipCamPass = "admin"
)
// Configuration defaults. // Configuration defaults.
const ( const (
defaultGVCameraIP = "192.168.1.50" defaultCameraIP = "192.168.1.50"
defaultGVCodec = codecutil.H264 defaultCodec = codecutil.H264
defaultGVHeight = 720 defaultHeight = 720
defaultGVFrameRate = 25 defaultFrameRate = 25
defaultGVBitrate = 400 defaultBitrate = 400
defaultGVVBRBitrate = 400 defaultVBRBitrate = 400
defaultGVMinFrames = 100 defaultMinFrames = 100
defaultGVVBRQuality = "standard" defaultVBRQuality = config.QualityStandard
defaultCameraChan = 2
) )
// Configuration field errors. // Configuration field errors.
@ -66,56 +87,56 @@ var (
// IP camera. This has been designed to implement the GV-BX4700-8F in particular. // IP camera. This has been designed to implement the GV-BX4700-8F in particular.
// Any other models are untested. // Any other models are untested.
type GeoVision struct { type GeoVision struct {
cfg Config cfg config.Config
log Logger log config.Logger
rtpClt *rtp.Client rtpClt *rtp.Client
rtspClt *rtsp.Client rtspClt *rtsp.Client
rtcpClt *rtcp.Client rtcpClt *rtcp.Client
} }
// NewGeoVision returns a new GeoVision. // NewGeoVision returns a new GeoVision.
func NewGeoVision(l Logger) *GeoVision { return &GeoVision{log: l} } func NewGeoVision(l config.Logger) *GeoVision { return &GeoVision{log: l} }
// Set will take a Config struct, check the validity of the relevant fields // Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary using gvctrl to control the // and then performs any configuration necessary using gvctrl to control the
// GeoVision web interface. If fields are not valid, an error is added to the // GeoVision web interface. If fields are not valid, an error is added to the
// multiError and a default value is used for that particular field. // multiError and a default value is used for that particular field.
func (g *GeoVision) Set(c Config) error { func (g *GeoVision) Set(c config.Config) error {
var errs multiError var errs device.MultiError
if c.CameraIP == "" { if c.CameraIP == "" {
errs = append(errs, errGVBadCameraIP) errs = append(errs, errGVBadCameraIP)
c.CameraIP = defaultGVCameraIP c.CameraIP = defaultCameraIP
} }
switch c.InputCodec { switch c.InputCodec {
case codecutil.H264, codecutil.H265, codecutil.MJPEG: case codecutil.H264, codecutil.H265, codecutil.MJPEG:
default: default:
errs = append(errs, errGVBadCodec) errs = append(errs, errGVBadCodec)
c.InputCodec = defaultGVCodec c.InputCodec = defaultCodec
} }
if c.Height <= 0 { if c.Height <= 0 {
errs = append(errs, errGVBadHeight) errs = append(errs, errGVBadHeight)
c.Height = defaultGVHeight c.Height = defaultHeight
} }
if c.FrameRate <= 0 { if c.FrameRate <= 0 {
errs = append(errs, errGVBadFrameRate) errs = append(errs, errGVBadFrameRate)
c.FrameRate = defaultGVFrameRate c.FrameRate = defaultFrameRate
} }
if c.Bitrate <= 0 { if c.Bitrate <= 0 {
errs = append(errs, errGVBadBitrate) errs = append(errs, errGVBadBitrate)
c.Bitrate = defaultGVBitrate c.Bitrate = defaultBitrate
} }
if c.MinFrames <= 0 { if c.MinFrames <= 0 {
errs = append(errs, errGVBadMinFrames) errs = append(errs, errGVBadMinFrames)
c.MinFrames = defaultGVMinFrames c.MinFrames = defaultMinFrames
} }
switch c.VBRQuality { switch c.VBRQuality {
case qualityStandard, qualityFair, qualityGood, qualityGreat, qualityExcellent: case config.QualityStandard, config.QualityFair, config.QualityGood, config.QualityGreat, config.QualityExcellent:
default: default:
errs = append(errs, errGVBadVBRQuality) errs = append(errs, errGVBadVBRQuality)
c.VBRQuality = defaultVBRQuality c.VBRQuality = defaultVBRQuality
@ -147,12 +168,12 @@ func (g *GeoVision) Set(c Config) error {
gvctrl.FrameRate(int(g.cfg.FrameRate)), gvctrl.FrameRate(int(g.cfg.FrameRate)),
gvctrl.VariableBitrate(g.cfg.VBR), gvctrl.VariableBitrate(g.cfg.VBR),
gvctrl.VBRQuality( gvctrl.VBRQuality(
map[quality]gvctrl.Quality{ map[config.Quality]gvctrl.Quality{
qualityStandard: gvctrl.QualityStandard, config.QualityStandard: gvctrl.QualityStandard,
qualityFair: gvctrl.QualityFair, config.QualityFair: gvctrl.QualityFair,
qualityGood: gvctrl.QualityGood, config.QualityGood: gvctrl.QualityGood,
qualityGreat: gvctrl.QualityGreat, config.QualityGreat: gvctrl.QualityGreat,
qualityExcellent: gvctrl.QualityExcellent, config.QualityExcellent: gvctrl.QualityExcellent,
}[g.cfg.VBRQuality], }[g.cfg.VBRQuality],
), ),
gvctrl.VBRBitrate(g.cfg.VBRBitrate), gvctrl.VBRBitrate(g.cfg.VBRBitrate),
@ -167,7 +188,7 @@ func (g *GeoVision) Set(c Config) error {
const setupDelay = 5 * time.Second const setupDelay = 5 * time.Second
time.Sleep(setupDelay) time.Sleep(setupDelay)
return multiError(errs) return errs
} }
// Start uses an RTSP client to communicate with the GeoVision RTSP server and // Start uses an RTSP client to communicate with the GeoVision RTSP server and
@ -277,3 +298,32 @@ func (g *GeoVision) Read(p []byte) (int, error) {
} }
return 0, errors.New("cannot read, GeoVision not streaming") return 0, errors.New("cannot read, GeoVision not streaming")
} }
// formAddrs is a helper function to form the addresses for the RTP client,
// RTCP client, and the RTSP server's RTCP addr using the local, remote addresses
// of the RTSP conn, and the SETUP method response.
func formAddrs(local, remote *net.TCPAddr, setupResp rtsp.Response) (rtpCltAddr, rtcpCltAddr, rtcpSvrAddr string, err error) {
svrRTCPPort, err := parseSvrRTCPPort(setupResp)
if err != nil {
return "", "", "", err
}
rtpCltAddr = strings.Split(local.String(), ":")[0] + ":" + strconv.Itoa(rtpPort)
rtcpCltAddr = strings.Split(local.String(), ":")[0] + ":" + strconv.Itoa(rtcpPort)
rtcpSvrAddr = strings.Split(remote.String(), ":")[0] + ":" + strconv.Itoa(svrRTCPPort)
return
}
// parseServerRTCPPort is a helper function to get the RTSP server's RTCP port.
func parseSvrRTCPPort(resp rtsp.Response) (int, error) {
transport := resp.Header.Get("Transport")
for _, p := range strings.Split(transport, ";") {
if strings.Contains(p, "server_port") {
port, err := strconv.Atoi(strings.Split(p, "-")[1])
if err != nil {
return 0, err
}
return port, nil
}
}
return 0, errors.New("SETUP response did not provide RTCP port")
}

View File

@ -31,7 +31,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"bitbucket.org/ausocean/av/input/gvctrl" "bitbucket.org/ausocean/av/device/geovision/gvctrl"
) )
func main() { func main() {

View File

@ -22,7 +22,7 @@ LICENSE
in gpl.txt. If not, see http://www.gnu.org/licenses. in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package raspivid
import ( import (
"errors" "errors"
@ -32,9 +32,14 @@ import (
"strings" "strings"
"bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/av/device"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
// To indicate package when logging.
const pkg = "raspivid: "
// Raspivid configuration defaults. // Raspivid configuration defaults.
const ( const (
defaultRaspividCodec = codecutil.H264 defaultRaspividCodec = codecutil.H264
@ -67,23 +72,53 @@ var (
errBadQuantization = errors.New("quantization bad or unset, defaulting") errBadQuantization = errors.New("quantization bad or unset, defaulting")
) )
// Possible modes for raspivid --exposure parameter.
var ExposureModes = [...]string{
"auto",
"night",
"nightpreview",
"backlight",
"spotlight",
"sports",
"snow",
"beach",
"verylong",
"fixedfps",
"antishake",
"fireworks",
}
// Possible modes for raspivid --awb parameter.
var AutoWhiteBalanceModes = [...]string{
"off",
"auto",
"sun",
"cloud",
"shade",
"tungsten",
"fluorescent",
"incandescent",
"flash",
"horizon",
}
// Raspivid is an implementation of AVDevice that provides control over the // Raspivid is an implementation of AVDevice that provides control over the
// raspivid command to allow reading of data from a Raspberry Pi camera. // raspivid command to allow reading of data from a Raspberry Pi camera.
type Raspivid struct { type Raspivid struct {
cfg Config cfg config.Config
cmd *exec.Cmd cmd *exec.Cmd
out io.ReadCloser out io.ReadCloser
log Logger log config.Logger
} }
// NewRaspivid returns a new Raspivid. // NewRaspivid returns a new Raspivid.
func NewRaspivid(l Logger) *Raspivid { return &Raspivid{log: l} } func NewRaspivid(l config.Logger) *Raspivid { return &Raspivid{log: l} }
// Set will take a Config struct, check the validity of the relevant fields // Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary. If fields are not valid, // and then performs any configuration necessary. If fields are not valid,
// an error is added to the multiError and a default value is used. // an error is added to the multiError and a default value is used.
func (r *Raspivid) Set(c Config) error { func (r *Raspivid) Set(c config.Config) error {
var errs []error var errs device.MultiError
switch c.InputCodec { switch c.InputCodec {
case codecutil.H264, codecutil.MJPEG: case codecutil.H264, codecutil.MJPEG:
default: default:
@ -140,7 +175,7 @@ func (r *Raspivid) Set(c Config) error {
c.Saturation = defaultRaspividSaturation c.Saturation = defaultRaspividSaturation
} }
if c.Exposure == "" || !stringInSlice(c.Exposure, ExposureModes[:]) { if c.Exposure == "" || !stringInSlice(c.Exposure, config.ExposureModes[:]) {
errs = append(errs, errBadExposure) errs = append(errs, errBadExposure)
c.Exposure = defaultRaspividExposure c.Exposure = defaultRaspividExposure
} }
@ -151,7 +186,7 @@ func (r *Raspivid) Set(c Config) error {
} }
r.cfg = c r.cfg = c
return multiError(errs) return errs
} }
// Start will prepare the arguments for the raspivid command using the // Start will prepare the arguments for the raspivid command using the
@ -237,3 +272,13 @@ func (r *Raspivid) Stop() error {
} }
return r.out.Close() return r.out.Close()
} }
// stringInSlice returns true if want is in slice.
func stringInSlice(want string, slice []string) bool {
for _, s := range slice {
if s == want {
return true
}
}
return false
}

View File

@ -22,7 +22,7 @@ LICENSE
in gpl.txt. If not, see http://www.gnu.org/licenses. in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package webcam
import ( import (
"errors" "errors"
@ -31,66 +31,71 @@ import (
"os/exec" "os/exec"
"strings" "strings"
"bitbucket.org/ausocean/av/device"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
// Used to indicate package in logging.
const pkg = "webcam: "
// Configuration defaults. // Configuration defaults.
const ( const (
defaultWebcamInputPath = "/dev/video0" defaultInputPath = "/dev/video0"
defaultWebcamFrameRate = 25 defaultFrameRate = 25
defaultWebcamBitrate = 400 defaultBitrate = 400
defaultWebcamWidth = 1280 defaultWidth = 1280
defaultWebcamHeight = 720 defaultHeight = 720
) )
// Configuration field errors. // Configuration field errors.
var ( var (
errWebcamBadFrameRate = errors.New("frame rate bad or unset, defaulting") errBadFrameRate = errors.New("frame rate bad or unset, defaulting")
errWebcamBadBitrate = errors.New("bitrate bad or unset, defaulting") errBadBitrate = errors.New("bitrate bad or unset, defaulting")
errWebcamWidth = errors.New("width bad or unset, defaulting") errBadWidth = errors.New("width bad or unset, defaulting")
errWebcamHeight = errors.New("height bad or unset, defaulting") errBadHeight = errors.New("height bad or unset, defaulting")
) )
// Webcam is an implementation of the AVDevice interface for a Webcam. Webcam // Webcam is an implementation of the AVDevice interface for a Webcam. Webcam
// uses an ffmpeg process to pipe the video data from the webcam. // uses an ffmpeg process to pipe the video data from the webcam.
type Webcam struct { type Webcam struct {
out io.ReadCloser out io.ReadCloser
log Logger log config.Logger
cfg Config cfg config.Config
cmd *exec.Cmd cmd *exec.Cmd
} }
// NewWebcam returns a new Webcam. // NewWebcam returns a new Webcam.
func NewWebcam(l Logger) *Webcam { func NewWebcam(l config.Logger) *Webcam {
return &Webcam{log: l} return &Webcam{log: l}
} }
// Set will validate the relevant fields of the given Config struct and assign // Set will validate the relevant fields of the given Config struct and assign
// the struct to the Webcam's Config. If fields are not valid, an error is // the struct to the Webcam's Config. If fields are not valid, an error is
// added to the multiError and a default value is used. // added to the multiError and a default value is used.
func (w *Webcam) Set(c Config) error { func (w *Webcam) Set(c config.Config) error {
var errs []error var errs device.MultiError
if c.Width == 0 { if c.Width == 0 {
errs = append(errs, errBadWidth) errs = append(errs, errBadWidth)
c.Width = defaultWebcamWidth c.Width = defaultWidth
} }
if c.Height == 0 { if c.Height == 0 {
errs = append(errs, errBadHeight) errs = append(errs, errBadHeight)
c.Height = defaultWebcamHeight c.Height = defaultHeight
} }
if c.FrameRate == 0 { if c.FrameRate == 0 {
errs = append(errs, errBadFrameRate) errs = append(errs, errBadFrameRate)
c.FrameRate = defaultWebcamFrameRate c.FrameRate = defaultFrameRate
} }
if c.Bitrate <= 0 { if c.Bitrate <= 0 {
errs = append(errs, errBadBitrate) errs = append(errs, errBadBitrate)
c.Bitrate = defaultWebcamBitrate c.Bitrate = defaultBitrate
} }
w.cfg = c w.cfg = c
return multiError(errs) return errs
} }
// Start will build the required arguments for ffmpeg and then execute the // Start will build the required arguments for ffmpeg and then execute the

5
go.mod
View File

@ -4,15 +4,16 @@ go 1.13
require ( require (
bitbucket.org/ausocean/iot v1.2.7 bitbucket.org/ausocean/iot v1.2.7
bitbucket.org/ausocean/test v0.0.0-20190821085226-7a524f2344ba bitbucket.org/ausocean/test v0.0.0-20190821085226-7a524f2344ba // indirect
bitbucket.org/ausocean/utils v1.2.10 bitbucket.org/ausocean/utils v1.2.10
github.com/BurntSushi/toml v0.3.1 // indirect github.com/BurntSushi/toml v0.3.1 // indirect
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884
github.com/go-delve/delve v1.3.2
github.com/mewkiz/flac v1.0.5 github.com/mewkiz/flac v1.0.5
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
gocv.io/x/gocv v0.21.0 gocv.io/x/gocv v0.21.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect
) )

49
go.sum
View File

@ -1,17 +1,6 @@
bitbucket.org/ausocean/av v0.0.0-20190416003121-6ee286e98874/go.mod h1:DxZEprrNNQ2slHKAQVUHryDaWc5CbjxyHAvomhzg+AE=
bitbucket.org/ausocean/av/input/gvctrl v0.0.0-20191017223116-ce6c12cce8cd h1:L99pvZZtdy3v54ym6GswYi8SOGgz+4Tr8hiIOI+nSiQ=
bitbucket.org/ausocean/av/input/gvctrl v0.0.0-20191017223116-ce6c12cce8cd/go.mod h1:Hg522DOVaj23J7CIxknCxmNsLGdg1iZ+Td1FDcTOdLQ=
bitbucket.org/ausocean/iot v1.2.4/go.mod h1:5HVLgPHccW2PxS7WDUQO6sKWMgk3Vfze/7d5bHs8EWU=
bitbucket.org/ausocean/iot v1.2.6 h1:KAAY1KZDbyOpoKajT1dM8BawupHiW9hUOelseSV1Ptc=
bitbucket.org/ausocean/iot v1.2.6/go.mod h1:71AYHh8yGZ8XyzDBskwIWMF+8E8ORagXpXE24wlhoE0=
bitbucket.org/ausocean/iot v1.2.7 h1:dZgrmVtuXnzHgybDthn0bYgAJms9euTONXBsqsx9g5M= bitbucket.org/ausocean/iot v1.2.7 h1:dZgrmVtuXnzHgybDthn0bYgAJms9euTONXBsqsx9g5M=
bitbucket.org/ausocean/iot v1.2.7/go.mod h1:aAWgPo2f8sD2OPmxae1E5/iD9+tKY/iW4pcQMQXUvHM= bitbucket.org/ausocean/iot v1.2.7/go.mod h1:aAWgPo2f8sD2OPmxae1E5/iD9+tKY/iW4pcQMQXUvHM=
bitbucket.org/ausocean/test v0.0.0-20190821085226-7a524f2344ba/go.mod h1:MbKtu9Pu8l3hiVGX6ep8S1VwAVY5uCbifCFOYsm914w= bitbucket.org/ausocean/test v0.0.0-20190821085226-7a524f2344ba/go.mod h1:MbKtu9Pu8l3hiVGX6ep8S1VwAVY5uCbifCFOYsm914w=
bitbucket.org/ausocean/utils v0.0.0-20190408050157-66d3b4d4041e/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.6/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.8 h1:hyxAIqYBqjqCguG+6A/kKyrAihyeUt2LziZg6CH0gLU=
bitbucket.org/ausocean/utils v1.2.8/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.9 h1:g45C6KCNvCLOGFv+ZnmDbQOOdnwpIsvzuNOD141CTVI=
bitbucket.org/ausocean/utils v1.2.9/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.9/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
bitbucket.org/ausocean/utils v1.2.10 h1:JTS7n+K+0o/FQFWKjdGgA1ElZ4TQu9aHX3wTJXOayXw= bitbucket.org/ausocean/utils v1.2.10 h1:JTS7n+K+0o/FQFWKjdGgA1ElZ4TQu9aHX3wTJXOayXw=
bitbucket.org/ausocean/utils v1.2.10/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.10/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
@ -24,28 +13,50 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8= github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/cosiner/argv v0.0.0-20170225145430-13bacc38a0a5/go.mod h1:p/NrK5tF6ICIly4qwEDsf6VDirFiWWz0FenfYBwJaKQ=
github.com/cpuguy83/go-md2man v1.0.8/go.mod h1:N6JayAiVKtlHSnuTCeuLSQVs75hb8q+dYQLjr7cDsKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c= github.com/go-audio/aiff v0.0.0-20180403003018-6c3a8a6aff12/go.mod h1:AMSAp6W1zd0koOdX6QDgGIuBDTUvLa2SLQtm7d9eM3c=
github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/audio v0.0.0-20180206231410-b697a35b5608/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+Y2yzMYxq0GJFUsRRESI0P1gZ2ig= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 h1:4sGU+UABMMsRJyD+Y2yzMYxq0GJFUsRRESI0P1gZ2ig=
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs= github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw=
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks= github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
github.com/go-delve/delve v1.3.2 h1:K8VjV+Q2YnBYlPq0ctjrvc9h7h03wXszlszzfGW5Tog=
github.com/go-delve/delve v1.3.2/go.mod h1:LLw6qJfIsRK9WcwV2IRRqsdlgrqzOeuGrQOCOIhDpt8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs= github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs=
github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ= github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59G2E/qabUkHvEwOIJxDK0CJ7CRjA= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21 h1:Hc1iKlyxNHp3CV59G2E/qabUkHvEwOIJxDK0CJ7CRjA=
github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s= github.com/mattetti/audio v0.0.0-20180912171649-01576cde1f21/go.mod h1:LlQmBGkOuV/SKzEDXBPKauvN2UqCgzXO2XjecTGj40s=
github.com/mattn/go-colorable v0.0.0-20170327083344-ded68f7a9561/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc= github.com/mewkiz/flac v1.0.5 h1:dHGW/2kf+/KZ2GGqSVayNEhL9pluKn/rr/h/QqD9Ogc=
github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs= github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/peterh/liner v0.0.0-20170317030525-88609521dc4b/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v0.0.0-20170413231811-06b906832ed0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v0.0.0-20180428102519-11635eb403ff/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sirupsen/logrus v0.0.0-20180523074243-ea8897e79973/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/spf13/cobra v0.0.0-20170417170307-b6cb39589372/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v0.0.0-20170417173400-9e4c21054fa1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
@ -53,6 +64,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0=
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw= github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw=
github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0= github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0=
go.starlark.net v0.0.0-20190702223751-32f345186213/go.mod h1:c1/X6cHgvdXj6pUlmWKMkuqRnW4K8x2vwt6JAaaircg=
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
@ -60,12 +72,25 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
gocv.io/x/gocv v0.21.0 h1:dVjagrupZrfCRY0qPEaYWgoNMRpBel6GYDH4mvQOK8Y=
gocv.io/x/gocv v0.21.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs= gocv.io/x/gocv v0.21.0/go.mod h1:Rar2PS6DV+T4FL+PM535EImD/h13hGVaHhnCu1xarBs=
golang.org/x/arch v0.0.0-20171004143515-077ac972c2e4/go.mod h1:cYlCBUl1MsqxdiKgmc4uh7TxZfWSFLOGSRR090WDxt8=
golang.org/x/crypto v0.0.0-20180614174826-fd5f17ee7299/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181120060634-fc4f04983f62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -34,40 +34,40 @@ import (
func (r *Revid) startAudioDevice() (func() error, error) { func (r *Revid) startAudioDevice() (func() error, error) {
// Create audio device. // Create audio device.
ac := &audio.Config{ ac := &audio.Config{
SampleRate: r.config.SampleRate, SampleRate: r.cfg.SampleRate,
Channels: r.config.Channels, Channels: r.cfg.Channels,
RecPeriod: r.config.RecPeriod, RecPeriod: r.cfg.RecPeriod,
BitDepth: r.config.BitDepth, BitDepth: r.cfg.BitDepth,
Codec: r.config.InputCodec, Codec: r.cfg.InputCodec,
} }
mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate)) mts.Meta.Add("sampleRate", strconv.Itoa(r.cfg.SampleRate))
mts.Meta.Add("channels", strconv.Itoa(r.config.Channels)) mts.Meta.Add("channels", strconv.Itoa(r.cfg.Channels))
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod)) mts.Meta.Add("period", fmt.Sprintf("%.6f", r.cfg.RecPeriod))
mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth)) mts.Meta.Add("bitDepth", strconv.Itoa(r.cfg.BitDepth))
switch r.config.InputCodec { switch r.cfg.InputCodec {
case codecutil.PCM: case codecutil.PCM:
mts.Meta.Add("codec", "pcm") mts.Meta.Add("codec", "pcm")
case codecutil.ADPCM: case codecutil.ADPCM:
mts.Meta.Add("codec", "adpcm") mts.Meta.Add("codec", "adpcm")
default: default:
r.config.Logger.Log(logger.Fatal, pkg+"no audio codec set in config") r.cfg.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
} }
ai, err := audio.NewDevice(ac, r.config.Logger) ai, err := audio.NewDevice(ac, r.cfg.Logger)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error()) r.cfg.Logger.Log(logger.Fatal, pkg+"failed to create audio device", "error", err.Error())
} }
// Start audio device // Start audio device
err = ai.Start() err = ai.Start()
if err != nil { if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error()) r.cfg.Logger.Log(logger.Fatal, pkg+"failed to start audio device", "error", err.Error())
} }
// Process output from audio device. // Process output from audio device.
r.config.ChunkSize = ai.ChunkSize() r.cfg.ChunkSize = ai.ChunkSize()
r.wg.Add(1) r.wg.Add(1)
go r.processFrom(ai, time.Duration(float64(time.Second)/r.config.WriteRate)) go r.processFrom(ai, time.Duration(float64(time.Second)/r.cfg.WriteRate))
return func() error { return func() error {
ai.Stop() ai.Stop()
return nil return nil

View File

@ -23,7 +23,7 @@ LICENSE
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package config
import ( import (
"errors" "errors"
@ -63,17 +63,24 @@ var AutoWhiteBalanceModes = [...]string{
"horizon", "horizon",
} }
const pkg = "config: "
type Logger interface {
SetLevel(int8)
Log(level int8, message string, params ...interface{})
}
// quality represents video quality. // quality represents video quality.
type quality int type Quality int
// The different video qualities that can be used for variable bitrate when // The different video qualities that can be used for variable bitrate when
// using the GeoVision camera. // using the GeoVision camera.
const ( const (
qualityStandard quality = iota QualityStandard Quality = iota
qualityFair QualityFair
qualityGood QualityGood
qualityGreat QualityGreat
qualityExcellent QualityExcellent
) )
// Enums to define inputs, outputs and codecs. // Enums to define inputs, outputs and codecs.
@ -115,7 +122,7 @@ const (
defaultRtpAddr = "localhost:6970" defaultRtpAddr = "localhost:6970"
defaultCameraIP = "192.168.1.50" defaultCameraIP = "192.168.1.50"
defaultVBR = false defaultVBR = false
defaultVBRQuality = qualityStandard defaultVBRQuality = QualityStandard
defaultBurstPeriod = 10 // Seconds defaultBurstPeriod = 10 // Seconds
defaultVBRBitrate = 500 // kbps defaultVBRBitrate = 500 // kbps
defaultCameraChan = 2 defaultCameraChan = 2
@ -236,7 +243,7 @@ type Config struct {
// VBRQuality describes the general quality of video from the GeoVision camera // VBRQuality describes the general quality of video from the GeoVision camera
// under variable bitrate. VBRQuality can be one 5 consts defined: // under variable bitrate. VBRQuality can be one 5 consts defined:
// qualityStandard, qualityFair, qualityGood, qualityGreat and qualityExcellent. // qualityStandard, qualityFair, qualityGood, qualityGreat and qualityExcellent.
VBRQuality quality VBRQuality Quality
// VBRBitrate describes maximal bitrate for the GeoVision camera when under // VBRBitrate describes maximal bitrate for the GeoVision camera when under
// variable bitrate. // variable bitrate.
@ -518,7 +525,7 @@ func (c *Config) Validate() error {
} }
switch c.VBRQuality { switch c.VBRQuality {
case qualityStandard, qualityFair, qualityGood, qualityGreat, qualityExcellent: case QualityStandard, QualityFair, QualityGood, QualityGreat, QualityExcellent:
default: default:
c.Logger.Log(logger.Info, pkg+"VBRQuality bad or unset, defaulting", "VBRQuality", defaultVBRQuality) c.Logger.Log(logger.Info, pkg+"VBRQuality bad or unset, defaulting", "VBRQuality", defaultVBRQuality)
c.VBRQuality = defaultVBRQuality c.VBRQuality = defaultVBRQuality

View File

@ -22,7 +22,7 @@ LICENSE
in gpl.txt. If not, see http://www.gnu.org/licenses. in gpl.txt. If not, see http://www.gnu.org/licenses.
*/ */
package revid package config
import ( import (
"fmt" "fmt"

View File

@ -40,10 +40,11 @@ import (
"time" "time"
"bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/av/input/gvctrl" "bitbucket.org/ausocean/av/device/geovision/gvctrl"
"bitbucket.org/ausocean/av/protocol/rtcp" "bitbucket.org/ausocean/av/protocol/rtcp"
"bitbucket.org/ausocean/av/protocol/rtp" "bitbucket.org/ausocean/av/protocol/rtp"
"bitbucket.org/ausocean/av/protocol/rtsp" "bitbucket.org/ausocean/av/protocol/rtsp"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
@ -53,84 +54,61 @@ const (
ipCamPass = "admin" ipCamPass = "admin"
) )
// AVDevice describes a configurable audio or video device from which media data // Constants for real time clients.
// can be obtained. AVDevice is an io.Reader. const (
type AVDevice interface { rtpPort = 60000
io.Reader rtcpPort = 60001
defaultServerRTCPPort = 17301
// Set allows for configuration of the AVDevice using a Config struct. All, )
// some or none of the fields of the Config struct may be used for configuration
// by an implementation. An implementation should specify what fields are
// considered.
Set(c Config) error
// Start will start the AVDevice capturing media data; after which the Read
// method may be called to obtain the data. The format of the data may differ
// and should be specified by the implementation.
Start() error
// Stop will stop the AVDevice from capturing media data. From this point
// Reads will no longer be successful.
Stop() error
}
// multiError implements the built in error interface. multiError is used here
// to collect multi errors during validation of configruation parameters for o
// AVDevices.
type multiError []error
func (me multiError) Error() string {
return fmt.Sprintf("%v", me)
}
// startRaspivid sets up things for input from raspivid i.e. starts // startRaspivid sets up things for input from raspivid i.e. starts
// a raspivid process and pipes it's data output. // a raspivid process and pipes it's data output.
func (r *Revid) startRaspivid() (func() error, error) { func (r *Revid) startRaspivid() (func() error, error) {
r.config.Logger.Log(logger.Info, pkg+"starting raspivid") r.cfg.Logger.Log(logger.Info, pkg+"starting raspivid")
const disabled = "0" const disabled = "0"
args := []string{ args := []string{
"--output", "-", "--output", "-",
"--nopreview", "--nopreview",
"--timeout", disabled, "--timeout", disabled,
"--width", fmt.Sprint(r.config.Width), "--width", fmt.Sprint(r.cfg.Width),
"--height", fmt.Sprint(r.config.Height), "--height", fmt.Sprint(r.cfg.Height),
"--bitrate", fmt.Sprint(r.config.Bitrate * 1000), // Convert from kbps to bps. "--bitrate", fmt.Sprint(r.cfg.Bitrate * 1000), // Convert from kbps to bps.
"--framerate", fmt.Sprint(r.config.FrameRate), "--framerate", fmt.Sprint(r.cfg.FrameRate),
"--rotation", fmt.Sprint(r.config.Rotation), "--rotation", fmt.Sprint(r.cfg.Rotation),
"--brightness", fmt.Sprint(r.config.Brightness), "--brightness", fmt.Sprint(r.cfg.Brightness),
"--saturation", fmt.Sprint(r.config.Saturation), "--saturation", fmt.Sprint(r.cfg.Saturation),
"--exposure", fmt.Sprint(r.config.Exposure), "--exposure", fmt.Sprint(r.cfg.Exposure),
"--awb", fmt.Sprint(r.config.AutoWhiteBalance), "--awb", fmt.Sprint(r.cfg.AutoWhiteBalance),
} }
if r.config.FlipHorizontal { if r.cfg.FlipHorizontal {
args = append(args, "--hflip") args = append(args, "--hflip")
} }
if r.config.FlipVertical { if r.cfg.FlipVertical {
args = append(args, "--vflip") args = append(args, "--vflip")
} }
if r.config.FlipHorizontal { if r.cfg.FlipHorizontal {
args = append(args, "--hflip") args = append(args, "--hflip")
} }
switch r.config.InputCodec { switch r.cfg.InputCodec {
default: default:
return nil, fmt.Errorf("revid: invalid input codec: %v", r.config.InputCodec) return nil, fmt.Errorf("revid: invalid input codec: %v", r.cfg.InputCodec)
case codecutil.H264: case codecutil.H264:
args = append(args, args = append(args,
"--codec", "H264", "--codec", "H264",
"--inline", "--inline",
"--intra", fmt.Sprint(r.config.MinFrames), "--intra", fmt.Sprint(r.cfg.MinFrames),
) )
if r.config.VBR { if r.cfg.VBR {
args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization))
} }
case codecutil.MJPEG: case codecutil.MJPEG:
args = append(args, "--codec", "MJPEG") args = append(args, "--codec", "MJPEG")
} }
r.config.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " ")) r.cfg.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " "))
r.cmd = exec.Command("raspivid", args...) r.cmd = exec.Command("raspivid", args...)
stdout, err := r.cmd.StdoutPipe() stdout, err := r.cmd.StdoutPipe()
@ -151,28 +129,28 @@ func (r *Revid) startRaspivid() (func() error, error) {
func (r *Revid) startV4L() (func() error, error) { func (r *Revid) startV4L() (func() error, error) {
const defaultVideo = "/dev/video0" const defaultVideo = "/dev/video0"
r.config.Logger.Log(logger.Info, pkg+"starting webcam") r.cfg.Logger.Log(logger.Info, pkg+"starting webcam")
if r.config.InputPath == "" { if r.cfg.InputPath == "" {
r.config.Logger.Log(logger.Info, pkg+"using default video device", "device", defaultVideo) r.cfg.Logger.Log(logger.Info, pkg+"using default video device", "device", defaultVideo)
r.config.InputPath = defaultVideo r.cfg.InputPath = defaultVideo
} }
args := []string{ args := []string{
"-i", r.config.InputPath, "-i", r.cfg.InputPath,
"-f", "h264", "-f", "h264",
"-r", fmt.Sprint(r.config.FrameRate), "-r", fmt.Sprint(r.cfg.FrameRate),
} }
br := r.config.Bitrate * 1000 br := r.cfg.Bitrate * 1000
args = append(args, args = append(args,
"-b:v", fmt.Sprint(br), "-b:v", fmt.Sprint(br),
"-maxrate", fmt.Sprint(br), "-maxrate", fmt.Sprint(br),
"-bufsize", fmt.Sprint(br/2), "-bufsize", fmt.Sprint(br/2),
"-s", fmt.Sprintf("%dx%d", r.config.Width, r.config.Height), "-s", fmt.Sprintf("%dx%d", r.cfg.Width, r.cfg.Height),
"-", "-",
) )
r.config.Logger.Log(logger.Info, pkg+"ffmpeg args", "args", strings.Join(args, " ")) r.cfg.Logger.Log(logger.Info, pkg+"ffmpeg args", "args", strings.Join(args, " "))
r.cmd = exec.Command("ffmpeg", args...) r.cmd = exec.Command("ffmpeg", args...)
stdout, err := r.cmd.StdoutPipe() stdout, err := r.cmd.StdoutPipe()
@ -182,7 +160,7 @@ func (r *Revid) startV4L() (func() error, error) {
err = r.cmd.Start() err = r.cmd.Start()
if err != nil { if err != nil {
r.config.Logger.Log(logger.Fatal, pkg+"cannot start webcam", "error", err.Error()) r.cfg.Logger.Log(logger.Fatal, pkg+"cannot start webcam", "error", err.Error())
return nil, nil return nil, nil
} }
@ -194,9 +172,9 @@ func (r *Revid) startV4L() (func() error, error) {
// setupInputForFile sets up input from file and starts the revid.processFrom // setupInputForFile sets up input from file and starts the revid.processFrom
// routine. // routine.
func (r *Revid) setupInputForFile() (func() error, error) { func (r *Revid) setupInputForFile() (func() error, error) {
f, err := os.Open(r.config.InputPath) f, err := os.Open(r.cfg.InputPath)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Error, err.Error()) r.cfg.Logger.Log(logger.Error, err.Error())
r.Stop() r.Stop()
return nil, err return nil, err
} }
@ -214,92 +192,92 @@ func (r *Revid) setupInputForFile() (func() error, error) {
// TODO(saxon): this function should really be startGeoVision. It's much too // TODO(saxon): this function should really be startGeoVision. It's much too
// specific to be called startRTSPCamera. // specific to be called startRTSPCamera.
func (r *Revid) startRTSPCamera() (func() error, error) { func (r *Revid) startRTSPCamera() (func() error, error) {
r.config.Logger.Log(logger.Info, pkg+"starting geovision...") r.cfg.Logger.Log(logger.Info, pkg+"starting geovision...")
err := gvctrl.Set( err := gvctrl.Set(
r.config.CameraIP, r.cfg.CameraIP,
gvctrl.Channel(r.config.CameraChan), gvctrl.Channel(r.cfg.CameraChan),
gvctrl.CodecOut( gvctrl.CodecOut(
map[uint8]gvctrl.Codec{ map[uint8]gvctrl.Codec{
codecutil.H264: gvctrl.CodecH264, codecutil.H264: gvctrl.CodecH264,
codecutil.H265: gvctrl.CodecH265, codecutil.H265: gvctrl.CodecH265,
codecutil.MJPEG: gvctrl.CodecMJPEG, codecutil.MJPEG: gvctrl.CodecMJPEG,
}[r.config.InputCodec], }[r.cfg.InputCodec],
), ),
gvctrl.Height(int(r.config.Height)), gvctrl.Height(int(r.cfg.Height)),
gvctrl.FrameRate(int(r.config.FrameRate)), gvctrl.FrameRate(int(r.cfg.FrameRate)),
gvctrl.VariableBitrate(r.config.VBR), gvctrl.VariableBitrate(r.cfg.VBR),
gvctrl.VBRQuality( gvctrl.VBRQuality(
map[quality]gvctrl.Quality{ map[config.Quality]gvctrl.Quality{
qualityStandard: gvctrl.QualityStandard, config.QualityStandard: gvctrl.QualityStandard,
qualityFair: gvctrl.QualityFair, config.QualityFair: gvctrl.QualityFair,
qualityGood: gvctrl.QualityGood, config.QualityGood: gvctrl.QualityGood,
qualityGreat: gvctrl.QualityGreat, config.QualityGreat: gvctrl.QualityGreat,
qualityExcellent: gvctrl.QualityExcellent, config.QualityExcellent: gvctrl.QualityExcellent,
}[r.config.VBRQuality], }[r.cfg.VBRQuality],
), ),
gvctrl.VBRBitrate(r.config.VBRBitrate), gvctrl.VBRBitrate(r.cfg.VBRBitrate),
gvctrl.CBRBitrate(int(r.config.Bitrate)), gvctrl.CBRBitrate(int(r.cfg.Bitrate)),
gvctrl.Refresh(float64(r.config.MinFrames)/float64(r.config.FrameRate)), gvctrl.Refresh(float64(r.cfg.MinFrames)/float64(r.cfg.FrameRate)),
) )
if err != nil { if err != nil {
return nil, fmt.Errorf("could not set IPCamera settings: %w", err) return nil, fmt.Errorf("could not set IPCamera settings: %w", err)
} }
r.config.Logger.Log(logger.Info, pkg+"completed geovision configuration") r.cfg.Logger.Log(logger.Info, pkg+"completed geovision configuration")
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
rtspClt, local, remote, err := rtsp.NewClient("rtsp://" + ipCamUser + ":" + ipCamPass + "@" + r.config.CameraIP + ":8554/" + "CH002.sdp") rtspClt, local, remote, err := rtsp.NewClient("rtsp://" + ipCamUser + ":" + ipCamPass + "@" + r.cfg.CameraIP + ":8554/" + "CH002.sdp")
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Info, pkg+"created RTSP client") r.cfg.Logger.Log(logger.Info, pkg+"created RTSP client")
resp, err := rtspClt.Options() resp, err := rtspClt.Options()
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Debug, pkg+"RTSP OPTIONS response", "response", resp.String()) r.cfg.Logger.Log(logger.Debug, pkg+"RTSP OPTIONS response", "response", resp.String())
resp, err = rtspClt.Describe() resp, err = rtspClt.Describe()
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Debug, pkg+"RTSP DESCRIBE response", "response", resp.String()) r.cfg.Logger.Log(logger.Debug, pkg+"RTSP DESCRIBE response", "response", resp.String())
resp, err = rtspClt.Setup("track1", fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort)) resp, err = rtspClt.Setup("track1", fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Debug, pkg+"RTSP SETUP response", "response", resp.String()) r.cfg.Logger.Log(logger.Debug, pkg+"RTSP SETUP response", "response", resp.String())
rtpCltAddr, rtcpCltAddr, rtcpSvrAddr, err := formAddrs(local, remote, *resp) rtpCltAddr, rtcpCltAddr, rtcpSvrAddr, err := formAddrs(local, remote, *resp)
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Info, pkg+"RTSP session setup complete") r.cfg.Logger.Log(logger.Info, pkg+"RTSP session setup complete")
rtpClt, err := rtp.NewClient(rtpCltAddr) rtpClt, err := rtp.NewClient(rtpCltAddr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
rtcpClt, err := rtcp.NewClient(rtcpCltAddr, rtcpSvrAddr, rtpClt, r.config.Logger.Log) rtcpClt, err := rtcp.NewClient(rtcpCltAddr, rtcpSvrAddr, rtpClt, r.cfg.Logger.Log)
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Info, pkg+"RTCP and RTP clients created") r.cfg.Logger.Log(logger.Info, pkg+"RTCP and RTP clients created")
// Check errors from RTCP client until it has stopped running. // Check errors from RTCP client until it has stopped running.
go func() { go func() {
for { for {
err, ok := <-rtcpClt.Err() err, ok := <-rtcpClt.Err()
if ok { if ok {
r.config.Logger.Log(logger.Warning, pkg+"RTCP error", "error", err.Error()) r.cfg.Logger.Log(logger.Warning, pkg+"RTCP error", "error", err.Error())
} else { } else {
return return
} }
@ -309,20 +287,20 @@ func (r *Revid) startRTSPCamera() (func() error, error) {
// Start the RTCP client. // Start the RTCP client.
rtcpClt.Start() rtcpClt.Start()
r.config.Logger.Log(logger.Info, pkg+"RTCP client started") r.cfg.Logger.Log(logger.Info, pkg+"RTCP client started")
// Start reading data from the RTP client. // Start reading data from the RTP client.
r.wg.Add(1) r.wg.Add(1)
go r.processFrom(rtpClt, time.Second/time.Duration(r.config.FrameRate)) go r.processFrom(rtpClt, time.Second/time.Duration(r.cfg.FrameRate))
r.config.Logger.Log(logger.Info, pkg+"started input processor") r.cfg.Logger.Log(logger.Info, pkg+"started input processor")
resp, err = rtspClt.Play() resp, err = rtspClt.Play()
if err != nil { if err != nil {
return nil, err return nil, err
} }
r.config.Logger.Log(logger.Debug, pkg+"RTSP server PLAY response", "response", resp.String()) r.cfg.Logger.Log(logger.Debug, pkg+"RTSP server PLAY response", "response", resp.String())
r.config.Logger.Log(logger.Info, pkg+"play requested, now receiving stream") r.cfg.Logger.Log(logger.Info, pkg+"play requested, now receiving stream")
return func() error { return func() error {
err := rtpClt.Close() err := rtpClt.Close()
@ -337,7 +315,7 @@ func (r *Revid) startRTSPCamera() (func() error, error) {
rtcpClt.Stop() rtcpClt.Stop()
r.config.Logger.Log(logger.Info, pkg+"RTP, RTSP and RTCP clients stopped and closed") r.cfg.Logger.Log(logger.Info, pkg+"RTP, RTSP and RTCP clients stopped and closed")
return nil return nil
}, nil }, nil
} }
@ -375,6 +353,6 @@ func parseSvrRTCPPort(resp rtsp.Response) (int, error) {
// then send individual access units to revid's encoders. // then send individual access units to revid's encoders.
func (r *Revid) processFrom(read io.Reader, delay time.Duration) { func (r *Revid) processFrom(read io.Reader, delay time.Duration) {
r.err <- r.lexTo(r.encoders, read, delay) r.err <- r.lexTo(r.encoders, read, delay)
r.config.Logger.Log(logger.Info, pkg+"finished lexing") r.cfg.Logger.Log(logger.Info, pkg+"finished lexing")
r.wg.Done() r.wg.Done()
} }

View File

@ -44,24 +44,32 @@ import (
"bitbucket.org/ausocean/av/codec/mjpeg" "bitbucket.org/ausocean/av/codec/mjpeg"
"bitbucket.org/ausocean/av/container/flv" "bitbucket.org/ausocean/av/container/flv"
"bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/iot/pi/netsender"
"bitbucket.org/ausocean/utils/ioext" "bitbucket.org/ausocean/utils/ioext"
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
"bitbucket.org/ausocean/utils/ring" "bitbucket.org/ausocean/utils/ring"
) )
// Ring buffer defaults.
const (
// MTS ring buffer defaults.
defaultMTSRBSize = 1000
defaultMTSRBElementSize = 100000
defaultMTSRBWriteTimeout = 5
// RTMP ring buffer defaults.
defaultRTMPRBSize = 1000
defaultRTMPRBElementSize = 300000
defaultRTMPRBWriteTimeout = 5
)
// RTMP connection properties. // RTMP connection properties.
const ( const (
rtmpConnectionMaxTries = 5 rtmpConnectionMaxTries = 5
rtmpConnectionTimeout = 10 rtmpConnectionTimeout = 10
) )
const (
rtpPort = 60000
rtcpPort = 60001
defaultServerRTCPPort = 17301
)
const pkg = "revid: " const pkg = "revid: "
type Logger interface { type Logger interface {
@ -76,7 +84,7 @@ type Revid struct {
// For historical reasons it also handles logging. // For historical reasons it also handles logging.
// FIXME(kortschak): The relationship of concerns // FIXME(kortschak): The relationship of concerns
// in config/ns is weird. // in config/ns is weird.
config Config cfg config.Config
// ns holds the netsender.Sender responsible for HTTP. // ns holds the netsender.Sender responsible for HTTP.
ns *netsender.Sender ns *netsender.Sender
@ -116,13 +124,13 @@ type Revid struct {
// New returns a pointer to a new Revid with the desired configuration, and/or // New returns a pointer to a new Revid with the desired configuration, and/or
// an error if construction of the new instance was not successful. // an error if construction of the new instance was not successful.
func New(c Config, ns *netsender.Sender) (*Revid, error) { func New(c config.Config, ns *netsender.Sender) (*Revid, error) {
r := Revid{ns: ns, err: make(chan error)} r := Revid{ns: ns, err: make(chan error)}
err := r.setConfig(c) err := r.setConfig(c)
if err != nil { if err != nil {
return nil, fmt.Errorf("could not set config, failed with error: %v", err) return nil, fmt.Errorf("could not set config, failed with error: %v", err)
} }
r.config.Logger.SetLevel(c.LogLevel) r.cfg.Logger.SetLevel(c.LogLevel)
go r.handleErrors() go r.handleErrors()
return &r, nil return &r, nil
} }
@ -130,8 +138,8 @@ func New(c Config, ns *netsender.Sender) (*Revid, error) {
// Config returns a copy of revids current config. // Config returns a copy of revids current config.
// //
// Config is not safe for concurrent use. // Config is not safe for concurrent use.
func (r *Revid) Config() Config { func (r *Revid) Config() config.Config {
return r.config return r.cfg
} }
// TODO(Saxon): put more thought into error severity and how to handle these. // TODO(Saxon): put more thought into error severity and how to handle these.
@ -139,7 +147,7 @@ func (r *Revid) handleErrors() {
for { for {
err := <-r.err err := <-r.err
if err != nil { if err != nil {
r.config.Logger.Log(logger.Error, pkg+"async error", "error", err.Error()) r.cfg.Logger.Log(logger.Error, pkg+"async error", "error", err.Error())
} }
} }
} }
@ -154,45 +162,45 @@ func (r *Revid) Bitrate() int {
// reset swaps the current config of a Revid with the passed // reset swaps the current config of a Revid with the passed
// configuration; checking validity and returning errors if not valid. It then // configuration; checking validity and returning errors if not valid. It then
// sets up the data pipeline accordingly to this configuration. // sets up the data pipeline accordingly to this configuration.
func (r *Revid) reset(config Config) error { func (r *Revid) reset(c config.Config) error {
err := r.setConfig(config) err := r.setConfig(c)
if err != nil { if err != nil {
return err return err
} }
r.config.Logger.SetLevel(config.LogLevel) r.cfg.Logger.SetLevel(c.LogLevel)
err = r.setupPipeline( err = r.setupPipeline(
func(dst io.WriteCloser, fps float64) (io.WriteCloser, error) { func(dst io.WriteCloser, fps float64) (io.WriteCloser, error) {
var st int var st int
var encOptions []func(*mts.Encoder) error var encOptions []func(*mts.Encoder) error
switch r.config.Input { switch r.cfg.Input {
case InputRaspivid: case config.InputRaspivid:
switch r.config.InputCodec { switch r.cfg.InputCodec {
case codecutil.H264: case codecutil.H264:
st = mts.EncodeH264 st = mts.EncodeH264
case codecutil.MJPEG: case codecutil.MJPEG:
st = mts.EncodeMJPEG st = mts.EncodeMJPEG
encOptions = append(encOptions, mts.PacketBasedPSI(int(r.config.MinFrames))) encOptions = append(encOptions, mts.PacketBasedPSI(int(r.cfg.MinFrames)))
default: default:
panic("unknown input codec for raspivid input") panic("unknown input codec for raspivid input")
} }
case InputFile, InputV4L: case config.InputFile, config.InputV4L:
st = mts.EncodeH264 st = mts.EncodeH264
case InputRTSP: case config.InputRTSP:
switch r.config.InputCodec { switch r.cfg.InputCodec {
case codecutil.H265: case codecutil.H265:
st = mts.EncodeH265 st = mts.EncodeH265
case codecutil.H264: case codecutil.H264:
st = mts.EncodeH264 st = mts.EncodeH264
case codecutil.MJPEG: case codecutil.MJPEG:
st = mts.EncodeMJPEG st = mts.EncodeMJPEG
encOptions = append(encOptions, mts.PacketBasedPSI(int(r.config.MinFrames))) encOptions = append(encOptions, mts.PacketBasedPSI(int(r.cfg.MinFrames)))
default: default:
panic("unknown input codec for RTSP input") panic("unknown input codec for RTSP input")
} }
case InputAudio: case config.InputAudio:
st = mts.EncodeAudio st = mts.EncodeAudio
default: default:
panic("unknown input type") panic("unknown input type")
@ -215,13 +223,13 @@ func (r *Revid) reset(config Config) error {
// setConfig takes a config, checks it's validity and then replaces the current // setConfig takes a config, checks it's validity and then replaces the current
// revid config. // revid config.
func (r *Revid) setConfig(config Config) error { func (r *Revid) setConfig(config config.Config) error {
r.config.Logger = config.Logger r.cfg.Logger = config.Logger
err := config.Validate() err := config.Validate()
if err != nil { if err != nil {
return errors.New("Config struct is bad: " + err.Error()) return errors.New("Config struct is bad: " + err.Error())
} }
r.config = config r.cfg = config
return nil return nil
} }
@ -244,38 +252,38 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
// to mtsSenders if the output requires MPEGTS encoding, or flvSenders if the // to mtsSenders if the output requires MPEGTS encoding, or flvSenders if the
// output requires FLV encoding. // output requires FLV encoding.
var w io.WriteCloser var w io.WriteCloser
for _, out := range r.config.Outputs { for _, out := range r.cfg.Outputs {
switch out { switch out {
case OutputHTTP: case config.OutputHTTP:
w = newMtsSender( w = newMtsSender(
newHttpSender(r.ns, r.config.Logger.Log), newHttpSender(r.ns, r.cfg.Logger.Log),
r.config.Logger.Log, r.cfg.Logger.Log,
ring.NewBuffer(r.config.MTSRBSize, r.config.MTSRBElementSize, time.Duration(r.config.MTSRBWriteTimeout)*time.Second), ring.NewBuffer(r.cfg.MTSRBSize, r.cfg.MTSRBElementSize, time.Duration(r.cfg.MTSRBWriteTimeout)*time.Second),
r.config.ClipDuration, r.cfg.ClipDuration,
) )
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case OutputRTP: case config.OutputRTP:
w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) w, err := newRtpSender(r.cfg.RTPAddress, r.cfg.Logger.Log, r.cfg.FrameRate)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error()) r.cfg.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error())
} }
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case OutputFile: case config.OutputFile:
w, err := newFileSender(r.config.OutputPath) w, err := newFileSender(r.cfg.OutputPath)
if err != nil { if err != nil {
return err return err
} }
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case OutputRTMP: case config.OutputRTMP:
w, err := newRtmpSender( w, err := newRtmpSender(
r.config.RTMPURL, r.cfg.RTMPURL,
rtmpConnectionTimeout, rtmpConnectionTimeout,
rtmpConnectionMaxTries, rtmpConnectionMaxTries,
ring.NewBuffer(r.config.RTMPRBSize, r.config.RTMPRBElementSize, time.Duration(r.config.RTMPRBWriteTimeout)*time.Second), ring.NewBuffer(r.cfg.RTMPRBSize, r.cfg.RTMPRBElementSize, time.Duration(r.cfg.RTMPRBWriteTimeout)*time.Second),
r.config.Logger.Log, r.cfg.Logger.Log,
) )
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error()) r.cfg.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error())
} }
flvSenders = append(flvSenders, w) flvSenders = append(flvSenders, w)
} }
@ -286,7 +294,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
// as a destination. // as a destination.
if len(mtsSenders) != 0 { if len(mtsSenders) != 0 {
mw := multiWriter(mtsSenders...) mw := multiWriter(mtsSenders...)
e, _ := mtsEnc(mw, r.config.WriteRate) e, _ := mtsEnc(mw, r.cfg.WriteRate)
encoders = append(encoders, e) encoders = append(encoders, e)
} }
@ -295,7 +303,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
// as a destination. // as a destination.
if len(flvSenders) != 0 { if len(flvSenders) != 0 {
mw := multiWriter(flvSenders...) mw := multiWriter(flvSenders...)
e, err := flvEnc(mw, int(r.config.FrameRate)) e, err := flvEnc(mw, int(r.cfg.FrameRate))
if err != nil { if err != nil {
return err return err
} }
@ -304,23 +312,23 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
r.encoders = multiWriter(encoders...) r.encoders = multiWriter(encoders...)
switch r.config.Input { switch r.cfg.Input {
case InputRaspivid: case config.InputRaspivid:
r.setupInput = r.startRaspivid r.setupInput = r.startRaspivid
switch r.config.InputCodec { switch r.cfg.InputCodec {
case codecutil.H264: case codecutil.H264:
r.lexTo = h264.Lex r.lexTo = h264.Lex
case codecutil.MJPEG: case codecutil.MJPEG:
r.lexTo = mjpeg.Lex r.lexTo = mjpeg.Lex
} }
case InputV4L: case config.InputV4L:
r.setupInput = r.startV4L r.setupInput = r.startV4L
r.lexTo = h264.Lex r.lexTo = h264.Lex
case InputFile: case config.InputFile:
r.setupInput = r.setupInputForFile r.setupInput = r.setupInputForFile
case InputRTSP: case config.InputRTSP:
r.setupInput = r.startRTSPCamera r.setupInput = r.startRTSPCamera
switch r.config.InputCodec { switch r.cfg.InputCodec {
case codecutil.H264: case codecutil.H264:
r.lexTo = h264.NewExtractor().Extract r.lexTo = h264.NewExtractor().Extract
case codecutil.H265: case codecutil.H265:
@ -328,9 +336,9 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
case codecutil.MJPEG: case codecutil.MJPEG:
panic("not implemented") panic("not implemented")
} }
case InputAudio: case config.InputAudio:
r.setupInput = r.startAudioDevice r.setupInput = r.startAudioDevice
r.lexTo = codecutil.NewByteLexer(&r.config.ChunkSize).Lex r.lexTo = codecutil.NewByteLexer(&r.cfg.ChunkSize).Lex
} }
return nil return nil
@ -342,15 +350,15 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
// Start is safe for concurrent use. // Start is safe for concurrent use.
func (r *Revid) Start() error { func (r *Revid) Start() error {
if r.IsRunning() { if r.IsRunning() {
r.config.Logger.Log(logger.Warning, pkg+"start called, but revid already running") r.cfg.Logger.Log(logger.Warning, pkg+"start called, but revid already running")
return nil return nil
} }
r.mu.Lock() r.mu.Lock()
defer r.mu.Unlock() defer r.mu.Unlock()
r.config.Logger.Log(logger.Info, pkg+"starting Revid") r.cfg.Logger.Log(logger.Info, pkg+"starting Revid")
err := r.reset(r.config) err := r.reset(r.cfg)
if err != nil { if err != nil {
r.Stop() r.Stop()
return err return err
@ -369,7 +377,7 @@ func (r *Revid) Start() error {
// Stop is safe for concurrent use. // Stop is safe for concurrent use.
func (r *Revid) Stop() { func (r *Revid) Stop() {
if !r.IsRunning() { if !r.IsRunning() {
r.config.Logger.Log(logger.Warning, pkg+"stop called but revid isn't running") r.cfg.Logger.Log(logger.Warning, pkg+"stop called but revid isn't running")
return return
} }
@ -379,26 +387,26 @@ func (r *Revid) Stop() {
if r.closeInput != nil { if r.closeInput != nil {
err := r.closeInput() err := r.closeInput()
if err != nil { if err != nil {
r.config.Logger.Log(logger.Error, pkg+"could not close input", "error", err.Error()) r.cfg.Logger.Log(logger.Error, pkg+"could not close input", "error", err.Error())
} }
} }
r.config.Logger.Log(logger.Info, pkg+"closing pipeline") r.cfg.Logger.Log(logger.Info, pkg+"closing pipeline")
err := r.encoders.Close() err := r.encoders.Close()
if err != nil { if err != nil {
r.config.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error()) r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error())
} }
r.config.Logger.Log(logger.Info, pkg+"closed pipeline") r.cfg.Logger.Log(logger.Info, pkg+"closed pipeline")
if r.cmd != nil && r.cmd.Process != nil { if r.cmd != nil && r.cmd.Process != nil {
r.config.Logger.Log(logger.Info, pkg+"killing input proccess") r.cfg.Logger.Log(logger.Info, pkg+"killing input proccess")
r.cmd.Process.Kill() r.cmd.Process.Kill()
} }
r.config.Logger.Log(logger.Info, pkg+"waiting for routines to close") r.cfg.Logger.Log(logger.Info, pkg+"waiting for routines to close")
r.wg.Wait() r.wg.Wait()
r.config.Logger.Log(logger.Info, pkg+"revid stopped") r.cfg.Logger.Log(logger.Info, pkg+"revid stopped")
r.running = false r.running = false
} }
@ -423,239 +431,239 @@ func (r *Revid) Update(vars map[string]string) error {
for key, value := range vars { for key, value := range vars {
switch key { switch key {
case "Input": case "Input":
v, ok := map[string]uint8{"raspivid": InputRaspivid, "rtsp": InputRTSP}[strings.ToLower(value)] v, ok := map[string]uint8{"raspivid": config.InputRaspivid, "rtsp": config.InputRTSP}[strings.ToLower(value)]
if !ok { if !ok {
r.config.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value)
break break
} }
r.config.Input = v r.cfg.Input = v
case "Saturation": case "Saturation":
s, err := strconv.ParseInt(value, 10, 0) s, err := strconv.ParseInt(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid saturation param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid saturation param", "value", value)
break break
} }
r.config.Saturation = int(s) r.cfg.Saturation = int(s)
case "Brightness": case "Brightness":
b, err := strconv.ParseUint(value, 10, 0) b, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid brightness param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid brightness param", "value", value)
break break
} }
r.config.Brightness = uint(b) r.cfg.Brightness = uint(b)
case "Exposure": case "Exposure":
r.config.Exposure = value r.cfg.Exposure = value
case "AutoWhiteBalance": case "AutoWhiteBalance":
r.config.AutoWhiteBalance = value r.cfg.AutoWhiteBalance = value
case "InputCodec": case "InputCodec":
switch value { switch value {
case "H264": case "H264":
r.config.InputCodec = codecutil.H264 r.cfg.InputCodec = codecutil.H264
case "MJPEG": case "MJPEG":
r.config.InputCodec = codecutil.MJPEG r.cfg.InputCodec = codecutil.MJPEG
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid InputCodec variable value", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid InputCodec variable value", "value", value)
} }
case "Output": case "Output":
outputs := strings.Split(value, ",") outputs := strings.Split(value, ",")
r.config.Outputs = make([]uint8, len(outputs)) r.cfg.Outputs = make([]uint8, len(outputs))
for i, output := range outputs { for i, output := range outputs {
switch output { switch output {
case "File": case "File":
r.config.Outputs[i] = OutputFile r.cfg.Outputs[i] = config.OutputFile
case "Http": case "Http":
r.config.Outputs[i] = OutputHTTP r.cfg.Outputs[i] = config.OutputHTTP
case "Rtmp": case "Rtmp":
r.config.Outputs[i] = OutputRTMP r.cfg.Outputs[i] = config.OutputRTMP
case "Rtp": case "Rtp":
r.config.Outputs[i] = OutputRTP r.cfg.Outputs[i] = config.OutputRTP
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value)
continue continue
} }
} }
case "RtmpUrl": case "RtmpUrl":
r.config.RTMPURL = value r.cfg.RTMPURL = value
case "RtpAddress": case "RtpAddress":
r.config.RTPAddress = value r.cfg.RTPAddress = value
case "Bitrate": case "Bitrate":
v, err := strconv.ParseUint(value, 10, 0) v, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid framerate param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid framerate param", "value", value)
break break
} }
r.config.Bitrate = uint(v) r.cfg.Bitrate = uint(v)
case "OutputPath": case "OutputPath":
r.config.OutputPath = value r.cfg.OutputPath = value
case "InputPath": case "InputPath":
r.config.InputPath = value r.cfg.InputPath = value
case "Height": case "Height":
h, err := strconv.ParseUint(value, 10, 0) h, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid height param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid height param", "value", value)
break break
} }
r.config.Height = uint(h) r.cfg.Height = uint(h)
case "Width": case "Width":
w, err := strconv.ParseUint(value, 10, 0) w, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid width param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid width param", "value", value)
break break
} }
r.config.Width = uint(w) r.cfg.Width = uint(w)
case "FrameRate": case "FrameRate":
v, err := strconv.ParseUint(value, 10, 0) v, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid framerate param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid framerate param", "value", value)
break break
} }
r.config.FrameRate = uint(v) r.cfg.FrameRate = uint(v)
case "Rotation": case "Rotation":
v, err := strconv.ParseUint(value, 10, 0) v, err := strconv.ParseUint(value, 10, 0)
if err != nil || v > 359 { if err != nil || v > 359 {
r.config.Logger.Log(logger.Warning, pkg+"invalid rotation param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid rotation param", "value", value)
break break
} }
r.config.Rotation = uint(v) r.cfg.Rotation = uint(v)
case "HttpAddress": case "HttpAddress":
r.config.HTTPAddress = value r.cfg.HTTPAddress = value
case "Quantization": case "Quantization":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", v) r.cfg.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", v)
break break
} }
r.config.Quantization = uint(v) r.cfg.Quantization = uint(v)
case "MinFrames": case "MinFrames":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid MinFrames param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid MinFrames param", "value", value)
break break
} }
r.config.MinFrames = uint(v) r.cfg.MinFrames = uint(v)
case "ClipDuration": case "ClipDuration":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid ClipDuration param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid ClipDuration param", "value", value)
break break
} }
r.config.ClipDuration = time.Duration(v) * time.Second r.cfg.ClipDuration = time.Duration(v) * time.Second
case "HorizontalFlip": case "HorizontalFlip":
switch strings.ToLower(value) { switch strings.ToLower(value) {
case "true": case "true":
r.config.FlipHorizontal = true r.cfg.FlipHorizontal = true
case "false": case "false":
r.config.FlipHorizontal = false r.cfg.FlipHorizontal = false
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid HorizontalFlip param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid HorizontalFlip param", "value", value)
} }
case "VerticalFlip": case "VerticalFlip":
switch strings.ToLower(value) { switch strings.ToLower(value) {
case "true": case "true":
r.config.FlipVertical = true r.cfg.FlipVertical = true
case "false": case "false":
r.config.FlipVertical = false r.cfg.FlipVertical = false
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid VerticalFlip param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid VerticalFlip param", "value", value)
} }
case "BurstPeriod": case "BurstPeriod":
v, err := strconv.ParseUint(value, 10, 0) v, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid BurstPeriod param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid BurstPeriod param", "value", value)
break break
} }
r.config.BurstPeriod = uint(v) r.cfg.BurstPeriod = uint(v)
case "Logging": case "Logging":
switch value { switch value {
case "Debug": case "Debug":
r.config.LogLevel = logger.Debug r.cfg.LogLevel = logger.Debug
case "Info": case "Info":
r.config.LogLevel = logger.Info r.cfg.LogLevel = logger.Info
case "Warning": case "Warning":
r.config.LogLevel = logger.Warning r.cfg.LogLevel = logger.Warning
case "Error": case "Error":
r.config.LogLevel = logger.Error r.cfg.LogLevel = logger.Error
case "Fatal": case "Fatal":
r.config.LogLevel = logger.Fatal r.cfg.LogLevel = logger.Fatal
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid Logging param", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid Logging param", "value", value)
} }
case "RTMPRBSize": case "RTMPRBSize":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v < 0 { if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBSize var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid RTMPRBSize var", "value", value)
break break
} }
r.config.RTMPRBSize = v r.cfg.RTMPRBSize = v
case "RTMPRBElementSize": case "RTMPRBElementSize":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v < 0 { if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBElementSize var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid RTMPRBElementSize var", "value", value)
break break
} }
r.config.RTMPRBElementSize = v r.cfg.RTMPRBElementSize = v
case "RTMPRBWriteTimeout": case "RTMPRBWriteTimeout":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v <= 0 { if err != nil || v <= 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid RTMPRBWriteTimeout var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid RTMPRBWriteTimeout var", "value", value)
break break
} }
r.config.RTMPRBWriteTimeout = v r.cfg.RTMPRBWriteTimeout = v
case "MTSRBSize": case "MTSRBSize":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v < 0 { if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBSize var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid MTSRBSize var", "value", value)
break break
} }
r.config.MTSRBSize = v r.cfg.MTSRBSize = v
case "MTSRBElementSize": case "MTSRBElementSize":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v < 0 { if err != nil || v < 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBElementSize var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid MTSRBElementSize var", "value", value)
break break
} }
r.config.MTSRBElementSize = v r.cfg.MTSRBElementSize = v
case "MTSRBWriteTimeout": case "MTSRBWriteTimeout":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v <= 0 { if err != nil || v <= 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid MTSRBWriteTimeout var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid MTSRBWriteTimeout var", "value", value)
break break
} }
r.config.MTSRBWriteTimeout = v r.cfg.MTSRBWriteTimeout = v
case "VBR": case "VBR":
v, ok := map[string]bool{"true": true, "false": false}[strings.ToLower(value)] v, ok := map[string]bool{"true": true, "false": false}[strings.ToLower(value)]
if !ok { if !ok {
r.config.Logger.Log(logger.Warning, pkg+"invalid VBR var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid VBR var", "value", value)
break break
} }
r.config.VBR = v r.cfg.VBR = v
case "VBRQuality": case "VBRQuality":
v, ok := map[string]quality{"standard": qualityStandard, "fair": qualityFair, "good": qualityGood, "great": qualityGreat, "excellent": qualityExcellent}[strings.ToLower(value)] v, ok := map[string]config.Quality{"standard": config.QualityStandard, "fair": config.QualityFair, "good": config.QualityGood, "great": config.QualityGreat, "excellent": config.QualityExcellent}[strings.ToLower(value)]
if !ok { if !ok {
r.config.Logger.Log(logger.Warning, pkg+"invalid VBRQuality var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid VBRQuality var", "value", value)
break break
} }
r.config.VBRQuality = v r.cfg.VBRQuality = v
case "VBRBitrate": case "VBRBitrate":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || v <= 0 { if err != nil || v <= 0 {
r.config.Logger.Log(logger.Warning, pkg+"invalid VBRBitrate var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid VBRBitrate var", "value", value)
break break
} }
r.config.VBRBitrate = v r.cfg.VBRBitrate = v
case "CameraChan": case "CameraChan":
v, err := strconv.Atoi(value) v, err := strconv.Atoi(value)
if err != nil || (v != 1 && v != 2) { if err != nil || (v != 1 && v != 2) {
r.config.Logger.Log(logger.Warning, pkg+"invalid CameraChan var", "value", value) r.cfg.Logger.Log(logger.Warning, pkg+"invalid CameraChan var", "value", value)
break break
} }
r.config.CameraChan = v r.cfg.CameraChan = v
} }
} }
r.config.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.config)) r.cfg.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.cfg))
return nil return nil
} }

View File

@ -35,6 +35,7 @@ import (
"runtime" "runtime"
"testing" "testing"
"bitbucket.org/ausocean/av/revid/config"
"bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/iot/pi/netsender"
) )
@ -56,9 +57,9 @@ func TestRaspivid(t *testing.T) {
t.Errorf("netsender.New failed with error %v", err) t.Errorf("netsender.New failed with error %v", err)
} }
var c Config var c config.Config
c.Logger = &logger c.Logger = &logger
c.Input = InputRaspivid c.Input = config.InputRaspivid
rv, err := New(c, ns) rv, err := New(c, ns)
if err != nil { if err != nil {
@ -148,7 +149,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
encoders []encoder encoders []encoder
}{ }{
{ {
outputs: []uint8{OutputHTTP}, outputs: []uint8{config.OutputHTTP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -157,7 +158,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{OutputRTMP}, outputs: []uint8{config.OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: flvEncoderStr, encoderType: flvEncoderStr,
@ -166,7 +167,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{OutputRTP}, outputs: []uint8{config.OutputRTP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -175,7 +176,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{OutputHTTP, OutputRTMP}, outputs: []uint8{config.OutputHTTP, config.OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -188,7 +189,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{OutputHTTP, OutputRTP, OutputRTMP}, outputs: []uint8{config.OutputHTTP, config.OutputRTP, config.OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -201,7 +202,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{OutputRTP, OutputRTMP}, outputs: []uint8{config.OutputRTP, config.OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -215,7 +216,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
} }
rv, err := New(Config{Logger: &testLogger{}}, nil) rv, err := New(config.Config{Logger: &testLogger{}}, nil)
if err != nil { if err != nil {
t.Fatalf("unexpected err: %v", err) t.Fatalf("unexpected err: %v", err)
} }
@ -224,7 +225,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
for testNum, test := range tests { for testNum, test := range tests {
// Create a new config and reset revid with it. // Create a new config and reset revid with it.
const dummyURL = "rtmp://dummy" const dummyURL = "rtmp://dummy"
c := Config{Logger: &testLogger{}, Outputs: test.outputs, RTMPURL: dummyURL} c := config.Config{Logger: &testLogger{}, Outputs: test.outputs, RTMPURL: dummyURL}
err := rv.setConfig(c) err := rv.setConfig(c)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v for test %v", err, testNum) t.Fatalf("unexpected error: %v for test %v", err, testNum)