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
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

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.
*/
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
}

View File

@ -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")
}

View File

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

View File

@ -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
}

View File

@ -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

5
go.mod
View File

@ -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
)

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/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=

View File

@ -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

View File

@ -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

View File

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

View File

@ -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()
}

View File

@ -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
}

View File

@ -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)