mirror of https://bitbucket.org/ausocean/av.git
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:
parent
9a93e92b50
commit
57d73a8d0a
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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")
|
||||
}
|
|
@ -31,7 +31,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"bitbucket.org/ausocean/av/input/gvctrl"
|
||||
"bitbucket.org/ausocean/av/device/geovision/gvctrl"
|
||||
)
|
||||
|
||||
func main() {
|
|
@ -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
|
||||
}
|
|
@ -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
5
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
|
||||
)
|
||||
|
|
49
go.sum
49
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=
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -22,7 +22,7 @@ LICENSE
|
|||
in gpl.txt. If not, see http://www.gnu.org/licenses.
|
||||
*/
|
||||
|
||||
package revid
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
168
revid/inputs.go
168
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()
|
||||
}
|
||||
|
|
288
revid/revid.go
288
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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue