diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index c76343e4..8a0fbe01 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -1,6 +1,6 @@ /* NAME - revid-cli - command line interface for Revid. + revid-cli - command line interface for revid. DESCRIPTION See Readme.md @@ -42,6 +42,7 @@ import ( "bitbucket.org/ausocean/av/container/mts" "bitbucket.org/ausocean/av/container/mts/meta" "bitbucket.org/ausocean/av/revid" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/iot/pi/sds" "bitbucket.org/ausocean/iot/pi/smartlogger" @@ -102,8 +103,8 @@ func main() { // handleFlags parses command line flags and returns a revid configuration // based on them. -func handleFlags() revid.Config { - var cfg revid.Config +func handleFlags() config.Config { + var cfg config.Config var ( 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)") brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ") saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") - exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(revid.ExposureModes[:], ",")+")") - autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(revid.AutoWhiteBalanceModes[:], ",")+")") + exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(config.ExposureModes[:], ",")+")") + autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(config.AutoWhiteBalanceModes[:], ",")+")") // Audio specific flags. sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") @@ -179,15 +180,15 @@ func handleFlags() revid.Config { switch *inputPtr { case "Raspivid": - cfg.Input = revid.InputRaspivid + cfg.Input = config.InputRaspivid case "v4l": - cfg.Input = revid.InputV4L + cfg.Input = config.InputV4L case "File": - cfg.Input = revid.InputFile + cfg.Input = config.InputFile case "Audio": - cfg.Input = revid.InputAudio + cfg.Input = config.InputAudio case "RTSP": - cfg.Input = revid.InputRTSP + cfg.Input = config.InputRTSP case "": default: log.Log(logger.Error, pkg+"bad input argument") @@ -214,13 +215,13 @@ func handleFlags() revid.Config { for _, o := range outputs { switch o { case "File": - cfg.Outputs = append(cfg.Outputs, revid.OutputFile) + cfg.Outputs = append(cfg.Outputs, config.OutputFile) case "Http": - cfg.Outputs = append(cfg.Outputs, revid.OutputHTTP) + cfg.Outputs = append(cfg.Outputs, config.OutputHTTP) case "Rtmp": - cfg.Outputs = append(cfg.Outputs, revid.OutputRTMP) + cfg.Outputs = append(cfg.Outputs, config.OutputRTMP) case "Rtp": - cfg.Outputs = append(cfg.Outputs, revid.OutputRTP) + cfg.Outputs = append(cfg.Outputs, config.OutputRTP) case "": default: 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 -func run(cfg revid.Config) { +func run(cfg config.Config) { log.Log(logger.Info, pkg+"running in NetSender mode") var rv *revid.Revid diff --git a/device/device.go b/device/device.go new file mode 100644 index 00000000..1801be20 --- /dev/null +++ b/device/device.go @@ -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 + +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) +} diff --git a/revid/file.go b/device/file/file.go similarity index 93% rename from revid/file.go rename to device/file/file.go index d19ef69f..8f23c408 100644 --- a/revid/file.go +++ b/device/file/file.go @@ -22,27 +22,29 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package file import ( "errors" "fmt" "io" "os" + + "bitbucket.org/ausocean/av/revid/config" ) // AVFile is an implementation of the AVDevice interface for a file containg // audio or video data. type AVFile struct { f io.ReadCloser - cfg Config + cfg config.Config } // NewAVFile returns a new AVFile. func NewAVFile() *AVFile { return &AVFile{} } // 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 return nil } diff --git a/revid/geovision.go b/device/geovision/geovision.go similarity index 72% rename from revid/geovision.go rename to device/geovision/geovision.go index 6c11d1f9..6eaf6949 100644 --- a/revid/geovision.go +++ b/device/geovision/geovision.go @@ -23,32 +23,53 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package geovision import ( "errors" "fmt" "net" + "strconv" + "strings" "time" "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/rtp" "bitbucket.org/ausocean/av/protocol/rtsp" + "bitbucket.org/ausocean/av/revid/config" "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. const ( - defaultGVCameraIP = "192.168.1.50" - defaultGVCodec = codecutil.H264 - defaultGVHeight = 720 - defaultGVFrameRate = 25 - defaultGVBitrate = 400 - defaultGVVBRBitrate = 400 - defaultGVMinFrames = 100 - defaultGVVBRQuality = "standard" + defaultCameraIP = "192.168.1.50" + defaultCodec = codecutil.H264 + defaultHeight = 720 + defaultFrameRate = 25 + defaultBitrate = 400 + defaultVBRBitrate = 400 + defaultMinFrames = 100 + defaultVBRQuality = config.QualityStandard + defaultCameraChan = 2 ) // Configuration field errors. @@ -66,56 +87,56 @@ var ( // IP camera. This has been designed to implement the GV-BX4700-8F in particular. // Any other models are untested. type GeoVision struct { - cfg Config - log Logger + cfg config.Config + log config.Logger rtpClt *rtp.Client rtspClt *rtsp.Client rtcpClt *rtcp.Client } // 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 // 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 // multiError and a default value is used for that particular field. -func (g *GeoVision) Set(c Config) error { - var errs multiError +func (g *GeoVision) Set(c config.Config) error { + var errs device.MultiError if c.CameraIP == "" { errs = append(errs, errGVBadCameraIP) - c.CameraIP = defaultGVCameraIP + c.CameraIP = defaultCameraIP } switch c.InputCodec { case codecutil.H264, codecutil.H265, codecutil.MJPEG: default: errs = append(errs, errGVBadCodec) - c.InputCodec = defaultGVCodec + c.InputCodec = defaultCodec } if c.Height <= 0 { errs = append(errs, errGVBadHeight) - c.Height = defaultGVHeight + c.Height = defaultHeight } if c.FrameRate <= 0 { errs = append(errs, errGVBadFrameRate) - c.FrameRate = defaultGVFrameRate + c.FrameRate = defaultFrameRate } if c.Bitrate <= 0 { errs = append(errs, errGVBadBitrate) - c.Bitrate = defaultGVBitrate + c.Bitrate = defaultBitrate } if c.MinFrames <= 0 { errs = append(errs, errGVBadMinFrames) - c.MinFrames = defaultGVMinFrames + c.MinFrames = defaultMinFrames } switch c.VBRQuality { - case qualityStandard, qualityFair, qualityGood, qualityGreat, qualityExcellent: + case config.QualityStandard, config.QualityFair, config.QualityGood, config.QualityGreat, config.QualityExcellent: default: errs = append(errs, errGVBadVBRQuality) c.VBRQuality = defaultVBRQuality @@ -147,12 +168,12 @@ func (g *GeoVision) Set(c Config) error { gvctrl.FrameRate(int(g.cfg.FrameRate)), gvctrl.VariableBitrate(g.cfg.VBR), gvctrl.VBRQuality( - map[quality]gvctrl.Quality{ - qualityStandard: gvctrl.QualityStandard, - qualityFair: gvctrl.QualityFair, - qualityGood: gvctrl.QualityGood, - qualityGreat: gvctrl.QualityGreat, - qualityExcellent: gvctrl.QualityExcellent, + map[config.Quality]gvctrl.Quality{ + config.QualityStandard: gvctrl.QualityStandard, + config.QualityFair: gvctrl.QualityFair, + config.QualityGood: gvctrl.QualityGood, + config.QualityGreat: gvctrl.QualityGreat, + config.QualityExcellent: gvctrl.QualityExcellent, }[g.cfg.VBRQuality], ), gvctrl.VBRBitrate(g.cfg.VBRBitrate), @@ -167,7 +188,7 @@ func (g *GeoVision) Set(c Config) error { const setupDelay = 5 * time.Second time.Sleep(setupDelay) - return multiError(errs) + return errs } // 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") } + +// 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") +} diff --git a/input/gvctrl/gvctrl-cli/main.go b/device/geovision/gvctrl/gvctrl-cli/main.go similarity index 98% rename from input/gvctrl/gvctrl-cli/main.go rename to device/geovision/gvctrl/gvctrl-cli/main.go index 8c7d5d18..8e5eb942 100644 --- a/input/gvctrl/gvctrl-cli/main.go +++ b/device/geovision/gvctrl/gvctrl-cli/main.go @@ -31,7 +31,7 @@ import ( "fmt" "strconv" - "bitbucket.org/ausocean/av/input/gvctrl" + "bitbucket.org/ausocean/av/device/geovision/gvctrl" ) func main() { diff --git a/input/gvctrl/gvctrl.go b/device/geovision/gvctrl/gvctrl.go similarity index 100% rename from input/gvctrl/gvctrl.go rename to device/geovision/gvctrl/gvctrl.go diff --git a/input/gvctrl/gvctrl_test.go b/device/geovision/gvctrl/gvctrl_test.go similarity index 100% rename from input/gvctrl/gvctrl_test.go rename to device/geovision/gvctrl/gvctrl_test.go diff --git a/input/gvctrl/request.go b/device/geovision/gvctrl/request.go similarity index 100% rename from input/gvctrl/request.go rename to device/geovision/gvctrl/request.go diff --git a/input/gvctrl/utils.go b/device/geovision/gvctrl/utils.go similarity index 100% rename from input/gvctrl/utils.go rename to device/geovision/gvctrl/utils.go diff --git a/revid/raspivid.go b/device/raspivid/raspivid.go similarity index 86% rename from revid/raspivid.go rename to device/raspivid/raspivid.go index 43f94cf3..59f1a7e9 100644 --- a/revid/raspivid.go +++ b/device/raspivid/raspivid.go @@ -22,7 +22,7 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package raspivid import ( "errors" @@ -32,9 +32,14 @@ import ( "strings" "bitbucket.org/ausocean/av/codec/codecutil" + "bitbucket.org/ausocean/av/device" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" ) +// To indicate package when logging. +const pkg = "raspivid: " + // Raspivid configuration defaults. const ( defaultRaspividCodec = codecutil.H264 @@ -67,23 +72,53 @@ var ( 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 command to allow reading of data from a Raspberry Pi camera. type Raspivid struct { - cfg Config + cfg config.Config cmd *exec.Cmd out io.ReadCloser - log Logger + log config.Logger } // 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 // and then performs any configuration necessary. If fields are not valid, // an error is added to the multiError and a default value is used. -func (r *Raspivid) Set(c Config) error { - var errs []error +func (r *Raspivid) Set(c config.Config) error { + var errs device.MultiError switch c.InputCodec { case codecutil.H264, codecutil.MJPEG: default: @@ -140,7 +175,7 @@ func (r *Raspivid) Set(c Config) error { c.Saturation = defaultRaspividSaturation } - if c.Exposure == "" || !stringInSlice(c.Exposure, ExposureModes[:]) { + if c.Exposure == "" || !stringInSlice(c.Exposure, config.ExposureModes[:]) { errs = append(errs, errBadExposure) c.Exposure = defaultRaspividExposure } @@ -151,7 +186,7 @@ func (r *Raspivid) Set(c Config) error { } r.cfg = c - return multiError(errs) + return errs } // Start will prepare the arguments for the raspivid command using the @@ -237,3 +272,13 @@ func (r *Raspivid) Stop() error { } 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 +} diff --git a/revid/webcam.go b/device/webcam/webcam.go similarity index 78% rename from revid/webcam.go rename to device/webcam/webcam.go index 34dceb41..13a1ad32 100644 --- a/revid/webcam.go +++ b/device/webcam/webcam.go @@ -22,7 +22,7 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package webcam import ( "errors" @@ -31,66 +31,71 @@ import ( "os/exec" "strings" + "bitbucket.org/ausocean/av/device" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" ) +// Used to indicate package in logging. +const pkg = "webcam: " + // Configuration defaults. const ( - defaultWebcamInputPath = "/dev/video0" - defaultWebcamFrameRate = 25 - defaultWebcamBitrate = 400 - defaultWebcamWidth = 1280 - defaultWebcamHeight = 720 + defaultInputPath = "/dev/video0" + defaultFrameRate = 25 + defaultBitrate = 400 + defaultWidth = 1280 + defaultHeight = 720 ) // Configuration field errors. var ( - errWebcamBadFrameRate = errors.New("frame rate bad or unset, defaulting") - errWebcamBadBitrate = errors.New("bitrate bad or unset, defaulting") - errWebcamWidth = errors.New("width bad or unset, defaulting") - errWebcamHeight = errors.New("height bad or unset, defaulting") + errBadFrameRate = errors.New("frame rate bad or unset, defaulting") + errBadBitrate = errors.New("bitrate bad or unset, defaulting") + errBadWidth = errors.New("width bad or unset, defaulting") + errBadHeight = errors.New("height bad or unset, defaulting") ) // Webcam is an implementation of the AVDevice interface for a Webcam. Webcam // uses an ffmpeg process to pipe the video data from the webcam. type Webcam struct { out io.ReadCloser - log Logger - cfg Config + log config.Logger + cfg config.Config cmd *exec.Cmd } // NewWebcam returns a new Webcam. -func NewWebcam(l Logger) *Webcam { +func NewWebcam(l config.Logger) *Webcam { return &Webcam{log: l} } // 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 // added to the multiError and a default value is used. -func (w *Webcam) Set(c Config) error { - var errs []error +func (w *Webcam) Set(c config.Config) error { + var errs device.MultiError if c.Width == 0 { errs = append(errs, errBadWidth) - c.Width = defaultWebcamWidth + c.Width = defaultWidth } if c.Height == 0 { errs = append(errs, errBadHeight) - c.Height = defaultWebcamHeight + c.Height = defaultHeight } if c.FrameRate == 0 { errs = append(errs, errBadFrameRate) - c.FrameRate = defaultWebcamFrameRate + c.FrameRate = defaultFrameRate } if c.Bitrate <= 0 { errs = append(errs, errBadBitrate) - c.Bitrate = defaultWebcamBitrate + c.Bitrate = defaultBitrate } w.cfg = c - return multiError(errs) + return errs } // Start will build the required arguments for ffmpeg and then execute the diff --git a/go.mod b/go.mod index 922e40a9..1b29004b 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,16 @@ go 1.13 require ( 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 github.com/BurntSushi/toml v0.3.1 // indirect github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480 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/pkg/errors v0.8.1 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 ) diff --git a/go.sum b/go.sum index 231895b1..1770d71f 100644 --- a/go.sum +++ b/go.sum @@ -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/go.mod h1:aAWgPo2f8sD2OPmxae1E5/iD9+tKY/iW4pcQMQXUvHM= 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.10 h1:JTS7n+K+0o/FQFWKjdGgA1ElZ4TQu9aHX3wTJXOayXw= 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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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/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/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/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/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/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/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/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/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/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/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/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 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/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw= 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/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 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.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 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= +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-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/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/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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/revid/audio_linux.go b/revid/audio_linux.go index 831ef077..ad3a3e5d 100644 --- a/revid/audio_linux.go +++ b/revid/audio_linux.go @@ -34,40 +34,40 @@ import ( func (r *Revid) startAudioDevice() (func() error, error) { // Create audio device. ac := &audio.Config{ - SampleRate: r.config.SampleRate, - Channels: r.config.Channels, - RecPeriod: r.config.RecPeriod, - BitDepth: r.config.BitDepth, - Codec: r.config.InputCodec, + SampleRate: r.cfg.SampleRate, + Channels: r.cfg.Channels, + RecPeriod: r.cfg.RecPeriod, + BitDepth: r.cfg.BitDepth, + Codec: r.cfg.InputCodec, } - mts.Meta.Add("sampleRate", strconv.Itoa(r.config.SampleRate)) - mts.Meta.Add("channels", strconv.Itoa(r.config.Channels)) - mts.Meta.Add("period", fmt.Sprintf("%.6f", r.config.RecPeriod)) - mts.Meta.Add("bitDepth", strconv.Itoa(r.config.BitDepth)) - switch r.config.InputCodec { + 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") case codecutil.ADPCM: mts.Meta.Add("codec", "adpcm") 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 { - 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 err = ai.Start() 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. - r.config.ChunkSize = ai.ChunkSize() + r.cfg.ChunkSize = ai.ChunkSize() 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 { ai.Stop() return nil diff --git a/revid/config.go b/revid/config/config.go similarity index 97% rename from revid/config.go rename to revid/config/config.go index 619e8be6..22dd3543 100644 --- a/revid/config.go +++ b/revid/config/config.go @@ -23,7 +23,7 @@ LICENSE along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package config import ( "errors" @@ -63,17 +63,24 @@ var AutoWhiteBalanceModes = [...]string{ "horizon", } +const pkg = "config: " + +type Logger interface { + SetLevel(int8) + Log(level int8, message string, params ...interface{}) +} + // quality represents video quality. -type quality int +type Quality int // The different video qualities that can be used for variable bitrate when // using the GeoVision camera. const ( - qualityStandard quality = iota - qualityFair - qualityGood - qualityGreat - qualityExcellent + QualityStandard Quality = iota + QualityFair + QualityGood + QualityGreat + QualityExcellent ) // Enums to define inputs, outputs and codecs. @@ -115,7 +122,7 @@ const ( defaultRtpAddr = "localhost:6970" defaultCameraIP = "192.168.1.50" defaultVBR = false - defaultVBRQuality = qualityStandard + defaultVBRQuality = QualityStandard defaultBurstPeriod = 10 // Seconds defaultVBRBitrate = 500 // kbps defaultCameraChan = 2 @@ -236,7 +243,7 @@ type Config struct { // VBRQuality describes the general quality of video from the GeoVision camera // under variable bitrate. VBRQuality can be one 5 consts defined: // qualityStandard, qualityFair, qualityGood, qualityGreat and qualityExcellent. - VBRQuality quality + VBRQuality Quality // VBRBitrate describes maximal bitrate for the GeoVision camera when under // variable bitrate. @@ -518,7 +525,7 @@ func (c *Config) Validate() error { } switch c.VBRQuality { - case qualityStandard, qualityFair, qualityGood, qualityGreat, qualityExcellent: + case QualityStandard, QualityFair, QualityGood, QualityGreat, QualityExcellent: default: c.Logger.Log(logger.Info, pkg+"VBRQuality bad or unset, defaulting", "VBRQuality", defaultVBRQuality) c.VBRQuality = defaultVBRQuality diff --git a/revid/config_test.go b/revid/config/config_test.go similarity index 99% rename from revid/config_test.go rename to revid/config/config_test.go index 6f121078..90973c25 100644 --- a/revid/config_test.go +++ b/revid/config/config_test.go @@ -22,7 +22,7 @@ LICENSE in gpl.txt. If not, see http://www.gnu.org/licenses. */ -package revid +package config import ( "fmt" diff --git a/revid/inputs.go b/revid/inputs.go index bae92032..a31e0201 100644 --- a/revid/inputs.go +++ b/revid/inputs.go @@ -40,10 +40,11 @@ import ( "time" "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/rtp" "bitbucket.org/ausocean/av/protocol/rtsp" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/utils/logger" ) @@ -53,84 +54,61 @@ const ( ipCamPass = "admin" ) -// 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) 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) -} +// Constants for real time clients. +const ( + rtpPort = 60000 + rtcpPort = 60001 + defaultServerRTCPPort = 17301 +) // startRaspivid sets up things for input from raspivid i.e. starts // a raspivid process and pipes it's data output. 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" args := []string{ "--output", "-", "--nopreview", "--timeout", disabled, - "--width", fmt.Sprint(r.config.Width), - "--height", fmt.Sprint(r.config.Height), - "--bitrate", fmt.Sprint(r.config.Bitrate * 1000), // Convert from kbps to bps. - "--framerate", fmt.Sprint(r.config.FrameRate), - "--rotation", fmt.Sprint(r.config.Rotation), - "--brightness", fmt.Sprint(r.config.Brightness), - "--saturation", fmt.Sprint(r.config.Saturation), - "--exposure", fmt.Sprint(r.config.Exposure), - "--awb", fmt.Sprint(r.config.AutoWhiteBalance), + "--width", fmt.Sprint(r.cfg.Width), + "--height", fmt.Sprint(r.cfg.Height), + "--bitrate", fmt.Sprint(r.cfg.Bitrate * 1000), // Convert from kbps to bps. + "--framerate", fmt.Sprint(r.cfg.FrameRate), + "--rotation", fmt.Sprint(r.cfg.Rotation), + "--brightness", fmt.Sprint(r.cfg.Brightness), + "--saturation", fmt.Sprint(r.cfg.Saturation), + "--exposure", fmt.Sprint(r.cfg.Exposure), + "--awb", fmt.Sprint(r.cfg.AutoWhiteBalance), } - if r.config.FlipHorizontal { + if r.cfg.FlipHorizontal { args = append(args, "--hflip") } - if r.config.FlipVertical { + if r.cfg.FlipVertical { args = append(args, "--vflip") } - if r.config.FlipHorizontal { + if r.cfg.FlipHorizontal { args = append(args, "--hflip") } - switch r.config.InputCodec { + switch r.cfg.InputCodec { 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: args = append(args, "--codec", "H264", "--inline", - "--intra", fmt.Sprint(r.config.MinFrames), + "--intra", fmt.Sprint(r.cfg.MinFrames), ) - if r.config.VBR { - args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) + if r.cfg.VBR { + args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization)) } case codecutil.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...) stdout, err := r.cmd.StdoutPipe() @@ -151,28 +129,28 @@ func (r *Revid) startRaspivid() (func() error, error) { func (r *Revid) startV4L() (func() error, error) { const defaultVideo = "/dev/video0" - r.config.Logger.Log(logger.Info, pkg+"starting webcam") - if r.config.InputPath == "" { - r.config.Logger.Log(logger.Info, pkg+"using default video device", "device", defaultVideo) - r.config.InputPath = defaultVideo + r.cfg.Logger.Log(logger.Info, pkg+"starting webcam") + if r.cfg.InputPath == "" { + r.cfg.Logger.Log(logger.Info, pkg+"using default video device", "device", defaultVideo) + r.cfg.InputPath = defaultVideo } args := []string{ - "-i", r.config.InputPath, + "-i", r.cfg.InputPath, "-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, "-b:v", fmt.Sprint(br), "-maxrate", fmt.Sprint(br), "-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...) stdout, err := r.cmd.StdoutPipe() @@ -182,7 +160,7 @@ func (r *Revid) startV4L() (func() error, error) { err = r.cmd.Start() 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 } @@ -194,9 +172,9 @@ func (r *Revid) startV4L() (func() error, error) { // setupInputForFile sets up input from file and starts the revid.processFrom // routine. func (r *Revid) setupInputForFile() (func() error, error) { - f, err := os.Open(r.config.InputPath) + f, err := os.Open(r.cfg.InputPath) if err != nil { - r.config.Logger.Log(logger.Error, err.Error()) + r.cfg.Logger.Log(logger.Error, err.Error()) r.Stop() 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 // specific to be called startRTSPCamera. 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( - r.config.CameraIP, - gvctrl.Channel(r.config.CameraChan), + r.cfg.CameraIP, + gvctrl.Channel(r.cfg.CameraChan), gvctrl.CodecOut( map[uint8]gvctrl.Codec{ codecutil.H264: gvctrl.CodecH264, codecutil.H265: gvctrl.CodecH265, codecutil.MJPEG: gvctrl.CodecMJPEG, - }[r.config.InputCodec], + }[r.cfg.InputCodec], ), - gvctrl.Height(int(r.config.Height)), - gvctrl.FrameRate(int(r.config.FrameRate)), - gvctrl.VariableBitrate(r.config.VBR), + gvctrl.Height(int(r.cfg.Height)), + gvctrl.FrameRate(int(r.cfg.FrameRate)), + gvctrl.VariableBitrate(r.cfg.VBR), gvctrl.VBRQuality( - map[quality]gvctrl.Quality{ - qualityStandard: gvctrl.QualityStandard, - qualityFair: gvctrl.QualityFair, - qualityGood: gvctrl.QualityGood, - qualityGreat: gvctrl.QualityGreat, - qualityExcellent: gvctrl.QualityExcellent, - }[r.config.VBRQuality], + map[config.Quality]gvctrl.Quality{ + config.QualityStandard: gvctrl.QualityStandard, + config.QualityFair: gvctrl.QualityFair, + config.QualityGood: gvctrl.QualityGood, + config.QualityGreat: gvctrl.QualityGreat, + config.QualityExcellent: gvctrl.QualityExcellent, + }[r.cfg.VBRQuality], ), - gvctrl.VBRBitrate(r.config.VBRBitrate), - gvctrl.CBRBitrate(int(r.config.Bitrate)), - gvctrl.Refresh(float64(r.config.MinFrames)/float64(r.config.FrameRate)), + gvctrl.VBRBitrate(r.cfg.VBRBitrate), + gvctrl.CBRBitrate(int(r.cfg.Bitrate)), + gvctrl.Refresh(float64(r.cfg.MinFrames)/float64(r.cfg.FrameRate)), ) if err != nil { 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) - 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 { 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() if err != nil { 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() if err != nil { 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)) if err != nil { 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) if err != nil { 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) if err != nil { 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 { 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. go func() { for { err, ok := <-rtcpClt.Err() 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 { return } @@ -309,20 +287,20 @@ func (r *Revid) startRTSPCamera() (func() error, error) { // Start the RTCP client. 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. 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() if err != nil { return nil, err } - r.config.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.Debug, pkg+"RTSP server PLAY response", "response", resp.String()) + r.cfg.Logger.Log(logger.Info, pkg+"play requested, now receiving stream") return func() error { err := rtpClt.Close() @@ -337,7 +315,7 @@ func (r *Revid) startRTSPCamera() (func() error, error) { 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 }, nil } @@ -375,6 +353,6 @@ func parseSvrRTCPPort(resp rtsp.Response) (int, error) { // then send individual access units to revid's encoders. func (r *Revid) processFrom(read io.Reader, delay time.Duration) { 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() } diff --git a/revid/revid.go b/revid/revid.go index fd6239d3..1f7c213f 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -44,24 +44,32 @@ import ( "bitbucket.org/ausocean/av/codec/mjpeg" "bitbucket.org/ausocean/av/container/flv" "bitbucket.org/ausocean/av/container/mts" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/utils/ioext" "bitbucket.org/ausocean/utils/logger" "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. const ( rtmpConnectionMaxTries = 5 rtmpConnectionTimeout = 10 ) -const ( - rtpPort = 60000 - rtcpPort = 60001 - defaultServerRTCPPort = 17301 -) - const pkg = "revid: " type Logger interface { @@ -76,7 +84,7 @@ type Revid struct { // For historical reasons it also handles logging. // FIXME(kortschak): The relationship of concerns // in config/ns is weird. - config Config + cfg config.Config // ns holds the netsender.Sender responsible for HTTP. ns *netsender.Sender @@ -116,13 +124,13 @@ type Revid struct { // 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. -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)} err := r.setConfig(c) if err != nil { 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() 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 is not safe for concurrent use. -func (r *Revid) Config() Config { - return r.config +func (r *Revid) Config() config.Config { + return r.cfg } // TODO(Saxon): put more thought into error severity and how to handle these. @@ -139,7 +147,7 @@ func (r *Revid) handleErrors() { for { err := <-r.err 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 // configuration; checking validity and returning errors if not valid. It then // sets up the data pipeline accordingly to this configuration. -func (r *Revid) reset(config Config) error { - err := r.setConfig(config) +func (r *Revid) reset(c config.Config) error { + err := r.setConfig(c) if err != nil { return err } - r.config.Logger.SetLevel(config.LogLevel) + r.cfg.Logger.SetLevel(c.LogLevel) err = r.setupPipeline( func(dst io.WriteCloser, fps float64) (io.WriteCloser, error) { var st int var encOptions []func(*mts.Encoder) error - switch r.config.Input { - case InputRaspivid: - switch r.config.InputCodec { + switch r.cfg.Input { + case config.InputRaspivid: + switch r.cfg.InputCodec { case codecutil.H264: st = mts.EncodeH264 case codecutil.MJPEG: st = mts.EncodeMJPEG - encOptions = append(encOptions, mts.PacketBasedPSI(int(r.config.MinFrames))) + encOptions = append(encOptions, mts.PacketBasedPSI(int(r.cfg.MinFrames))) default: panic("unknown input codec for raspivid input") } - case InputFile, InputV4L: + case config.InputFile, config.InputV4L: st = mts.EncodeH264 - case InputRTSP: - switch r.config.InputCodec { + case config.InputRTSP: + switch r.cfg.InputCodec { case codecutil.H265: st = mts.EncodeH265 case codecutil.H264: st = mts.EncodeH264 case codecutil.MJPEG: st = mts.EncodeMJPEG - encOptions = append(encOptions, mts.PacketBasedPSI(int(r.config.MinFrames))) + encOptions = append(encOptions, mts.PacketBasedPSI(int(r.cfg.MinFrames))) default: panic("unknown input codec for RTSP input") } - case InputAudio: + case config.InputAudio: st = mts.EncodeAudio default: 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 // revid config. -func (r *Revid) setConfig(config Config) error { - r.config.Logger = config.Logger +func (r *Revid) setConfig(config config.Config) error { + r.cfg.Logger = config.Logger err := config.Validate() if err != nil { return errors.New("Config struct is bad: " + err.Error()) } - r.config = config + r.cfg = config 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 // output requires FLV encoding. var w io.WriteCloser - for _, out := range r.config.Outputs { + for _, out := range r.cfg.Outputs { switch out { - case OutputHTTP: + case config.OutputHTTP: w = newMtsSender( - newHttpSender(r.ns, r.config.Logger.Log), - r.config.Logger.Log, - ring.NewBuffer(r.config.MTSRBSize, r.config.MTSRBElementSize, time.Duration(r.config.MTSRBWriteTimeout)*time.Second), - r.config.ClipDuration, + newHttpSender(r.ns, r.cfg.Logger.Log), + r.cfg.Logger.Log, + ring.NewBuffer(r.cfg.MTSRBSize, r.cfg.MTSRBElementSize, time.Duration(r.cfg.MTSRBWriteTimeout)*time.Second), + r.cfg.ClipDuration, ) mtsSenders = append(mtsSenders, w) - case OutputRTP: - w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) + case config.OutputRTP: + w, err := newRtpSender(r.cfg.RTPAddress, r.cfg.Logger.Log, r.cfg.FrameRate) 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) - case OutputFile: - w, err := newFileSender(r.config.OutputPath) + case config.OutputFile: + w, err := newFileSender(r.cfg.OutputPath) if err != nil { return err } mtsSenders = append(mtsSenders, w) - case OutputRTMP: + case config.OutputRTMP: w, err := newRtmpSender( - r.config.RTMPURL, + r.cfg.RTMPURL, rtmpConnectionTimeout, rtmpConnectionMaxTries, - ring.NewBuffer(r.config.RTMPRBSize, r.config.RTMPRBElementSize, time.Duration(r.config.RTMPRBWriteTimeout)*time.Second), - r.config.Logger.Log, + ring.NewBuffer(r.cfg.RTMPRBSize, r.cfg.RTMPRBElementSize, time.Duration(r.cfg.RTMPRBWriteTimeout)*time.Second), + r.cfg.Logger.Log, ) 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) } @@ -286,7 +294,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. // as a destination. if len(mtsSenders) != 0 { mw := multiWriter(mtsSenders...) - e, _ := mtsEnc(mw, r.config.WriteRate) + e, _ := mtsEnc(mw, r.cfg.WriteRate) encoders = append(encoders, e) } @@ -295,7 +303,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. // as a destination. if len(flvSenders) != 0 { mw := multiWriter(flvSenders...) - e, err := flvEnc(mw, int(r.config.FrameRate)) + e, err := flvEnc(mw, int(r.cfg.FrameRate)) if err != nil { return err } @@ -304,23 +312,23 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.encoders = multiWriter(encoders...) - switch r.config.Input { - case InputRaspivid: + switch r.cfg.Input { + case config.InputRaspivid: r.setupInput = r.startRaspivid - switch r.config.InputCodec { + switch r.cfg.InputCodec { case codecutil.H264: r.lexTo = h264.Lex case codecutil.MJPEG: r.lexTo = mjpeg.Lex } - case InputV4L: + case config.InputV4L: r.setupInput = r.startV4L r.lexTo = h264.Lex - case InputFile: + case config.InputFile: r.setupInput = r.setupInputForFile - case InputRTSP: + case config.InputRTSP: r.setupInput = r.startRTSPCamera - switch r.config.InputCodec { + switch r.cfg.InputCodec { case codecutil.H264: r.lexTo = h264.NewExtractor().Extract case codecutil.H265: @@ -328,9 +336,9 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. case codecutil.MJPEG: panic("not implemented") } - case InputAudio: + case config.InputAudio: r.setupInput = r.startAudioDevice - r.lexTo = codecutil.NewByteLexer(&r.config.ChunkSize).Lex + r.lexTo = codecutil.NewByteLexer(&r.cfg.ChunkSize).Lex } return nil @@ -342,15 +350,15 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. // Start is safe for concurrent use. func (r *Revid) Start() error { 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 } r.mu.Lock() defer r.mu.Unlock() - r.config.Logger.Log(logger.Info, pkg+"starting Revid") - err := r.reset(r.config) + r.cfg.Logger.Log(logger.Info, pkg+"starting Revid") + err := r.reset(r.cfg) if err != nil { r.Stop() return err @@ -369,7 +377,7 @@ func (r *Revid) Start() error { // Stop is safe for concurrent use. func (r *Revid) Stop() { 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 } @@ -379,26 +387,26 @@ func (r *Revid) Stop() { if r.closeInput != nil { err := r.closeInput() 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() 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 { - 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.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.config.Logger.Log(logger.Info, pkg+"revid stopped") + r.cfg.Logger.Log(logger.Info, pkg+"revid stopped") r.running = false } @@ -423,239 +431,239 @@ func (r *Revid) Update(vars map[string]string) error { for key, value := range vars { switch key { 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 { - 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 } - r.config.Input = v + r.cfg.Input = v case "Saturation": s, err := strconv.ParseInt(value, 10, 0) 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 } - r.config.Saturation = int(s) + r.cfg.Saturation = int(s) case "Brightness": b, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.Brightness = uint(b) + r.cfg.Brightness = uint(b) case "Exposure": - r.config.Exposure = value + r.cfg.Exposure = value case "AutoWhiteBalance": - r.config.AutoWhiteBalance = value + r.cfg.AutoWhiteBalance = value case "InputCodec": switch value { case "H264": - r.config.InputCodec = codecutil.H264 + r.cfg.InputCodec = codecutil.H264 case "MJPEG": - r.config.InputCodec = codecutil.MJPEG + r.cfg.InputCodec = codecutil.MJPEG 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": outputs := strings.Split(value, ",") - r.config.Outputs = make([]uint8, len(outputs)) + r.cfg.Outputs = make([]uint8, len(outputs)) for i, output := range outputs { switch output { case "File": - r.config.Outputs[i] = OutputFile + r.cfg.Outputs[i] = config.OutputFile case "Http": - r.config.Outputs[i] = OutputHTTP + r.cfg.Outputs[i] = config.OutputHTTP case "Rtmp": - r.config.Outputs[i] = OutputRTMP + r.cfg.Outputs[i] = config.OutputRTMP case "Rtp": - r.config.Outputs[i] = OutputRTP + r.cfg.Outputs[i] = config.OutputRTP 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 } } case "RtmpUrl": - r.config.RTMPURL = value + r.cfg.RTMPURL = value case "RtpAddress": - r.config.RTPAddress = value + r.cfg.RTPAddress = value case "Bitrate": v, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.Bitrate = uint(v) + r.cfg.Bitrate = uint(v) case "OutputPath": - r.config.OutputPath = value + r.cfg.OutputPath = value case "InputPath": - r.config.InputPath = value + r.cfg.InputPath = value case "Height": h, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.Height = uint(h) + r.cfg.Height = uint(h) case "Width": w, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.Width = uint(w) + r.cfg.Width = uint(w) case "FrameRate": v, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.FrameRate = uint(v) + r.cfg.FrameRate = uint(v) case "Rotation": v, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.Rotation = uint(v) + r.cfg.Rotation = uint(v) case "HttpAddress": - r.config.HTTPAddress = value + r.cfg.HTTPAddress = value case "Quantization": v, err := strconv.Atoi(value) 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 } - r.config.Quantization = uint(v) + r.cfg.Quantization = uint(v) case "MinFrames": v, err := strconv.Atoi(value) 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 } - r.config.MinFrames = uint(v) + r.cfg.MinFrames = uint(v) case "ClipDuration": v, err := strconv.Atoi(value) 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 } - r.config.ClipDuration = time.Duration(v) * time.Second + r.cfg.ClipDuration = time.Duration(v) * time.Second case "HorizontalFlip": switch strings.ToLower(value) { case "true": - r.config.FlipHorizontal = true + r.cfg.FlipHorizontal = true case "false": - r.config.FlipHorizontal = false + r.cfg.FlipHorizontal = false 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": switch strings.ToLower(value) { case "true": - r.config.FlipVertical = true + r.cfg.FlipVertical = true case "false": - r.config.FlipVertical = false + r.cfg.FlipVertical = false 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": v, err := strconv.ParseUint(value, 10, 0) 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 } - r.config.BurstPeriod = uint(v) + r.cfg.BurstPeriod = uint(v) case "Logging": switch value { case "Debug": - r.config.LogLevel = logger.Debug + r.cfg.LogLevel = logger.Debug case "Info": - r.config.LogLevel = logger.Info + r.cfg.LogLevel = logger.Info case "Warning": - r.config.LogLevel = logger.Warning + r.cfg.LogLevel = logger.Warning case "Error": - r.config.LogLevel = logger.Error + r.cfg.LogLevel = logger.Error case "Fatal": - r.config.LogLevel = logger.Fatal + r.cfg.LogLevel = logger.Fatal 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": v, err := strconv.Atoi(value) 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 } - r.config.RTMPRBSize = v + r.cfg.RTMPRBSize = v case "RTMPRBElementSize": v, err := strconv.Atoi(value) 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 } - r.config.RTMPRBElementSize = v + r.cfg.RTMPRBElementSize = v case "RTMPRBWriteTimeout": v, err := strconv.Atoi(value) 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 } - r.config.RTMPRBWriteTimeout = v + r.cfg.RTMPRBWriteTimeout = v case "MTSRBSize": v, err := strconv.Atoi(value) 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 } - r.config.MTSRBSize = v + r.cfg.MTSRBSize = v case "MTSRBElementSize": v, err := strconv.Atoi(value) 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 } - r.config.MTSRBElementSize = v + r.cfg.MTSRBElementSize = v case "MTSRBWriteTimeout": v, err := strconv.Atoi(value) 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 } - r.config.MTSRBWriteTimeout = v + r.cfg.MTSRBWriteTimeout = v case "VBR": v, ok := map[string]bool{"true": true, "false": false}[strings.ToLower(value)] 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 } - r.config.VBR = v + r.cfg.VBR = v 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 { - 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 } - r.config.VBRQuality = v + r.cfg.VBRQuality = v case "VBRBitrate": v, err := strconv.Atoi(value) 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 } - r.config.VBRBitrate = v + r.cfg.VBRBitrate = v case "CameraChan": v, err := strconv.Atoi(value) 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 } - 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 } diff --git a/revid/revid_test.go b/revid/revid_test.go index a7e91323..3fbdb7f7 100644 --- a/revid/revid_test.go +++ b/revid/revid_test.go @@ -35,6 +35,7 @@ import ( "runtime" "testing" + "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/iot/pi/netsender" ) @@ -56,9 +57,9 @@ func TestRaspivid(t *testing.T) { t.Errorf("netsender.New failed with error %v", err) } - var c Config + var c config.Config c.Logger = &logger - c.Input = InputRaspivid + c.Input = config.InputRaspivid rv, err := New(c, ns) if err != nil { @@ -148,7 +149,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { encoders []encoder }{ { - outputs: []uint8{OutputHTTP}, + outputs: []uint8{config.OutputHTTP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -157,7 +158,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{OutputRTMP}, + outputs: []uint8{config.OutputRTMP}, encoders: []encoder{ { encoderType: flvEncoderStr, @@ -166,7 +167,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{OutputRTP}, + outputs: []uint8{config.OutputRTP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -175,7 +176,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{OutputHTTP, OutputRTMP}, + outputs: []uint8{config.OutputHTTP, config.OutputRTMP}, encoders: []encoder{ { 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{ { encoderType: mtsEncoderStr, @@ -201,7 +202,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{OutputRTP, OutputRTMP}, + outputs: []uint8{config.OutputRTP, config.OutputRTMP}, encoders: []encoder{ { 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 { t.Fatalf("unexpected err: %v", err) } @@ -224,7 +225,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { for testNum, test := range tests { // Create a new config and reset revid with it. 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) if err != nil { t.Fatalf("unexpected error: %v for test %v", err, testNum)