diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 454bde10..cc31c69c 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -218,13 +218,13 @@ func handleFlags() config.Config { switch *inputCodecPtr { case "H264": - cfg.InputCodec = codecutil.H264 + cfg.InputCodec = codecutil.CodecH264 case "PCM": - cfg.InputCodec = codecutil.PCM + cfg.InputCodec = codecutil.CodecPCM case "ADPCM": - cfg.InputCodec = codecutil.ADPCM + cfg.InputCodec = codecutil.CodecADPCM case "MJPEG": - cfg.InputCodec = codecutil.MJPEG + cfg.InputCodec = codecutil.CodecMJPEG default: log.Log(logger.Error, pkg+"bad input codec argument") } diff --git a/cmd/rv/main.go b/cmd/rv/main.go new file mode 100644 index 00000000..e7e479fe --- /dev/null +++ b/cmd/rv/main.go @@ -0,0 +1,233 @@ +/* +NAME + revid-cli - command line interface for revid. + +DESCRIPTION + See Readme.md + +AUTHORS + Saxon A. Nelson-Milton + Jack Richardson + Trek Hopton + +LICENSE + revid-cli is Copyright (C) 2017-2018 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 + along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +// revid-cli is a command line interface for revid. +package main + +import ( + "fmt" + "io" + "os" + "runtime/pprof" + "strconv" + "time" + + "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/netlogger" + "bitbucket.org/ausocean/iot/pi/netsender" + "bitbucket.org/ausocean/iot/pi/sds" + "bitbucket.org/ausocean/utils/logger" + "gopkg.in/natefinch/lumberjack.v2" +) + +// Copyright information prefixed to all metadata. +const ( + metaPreambleKey = "copyright" + metaPreambleData = "ausocean.org/license/content2019" +) + +// Logging configuration. +const ( + logPath = "/var/log/netsender/netsender.log" + logMaxSize = 500 // MB + logMaxBackup = 10 + logMaxAge = 28 // days + logVerbosity = logger.Info + logSuppress = true +) + +// Revid modes. +const ( + modeNormal = "Normal" + modePaused = "Paused" + modeBurst = "Burst" +) + +// Misc consts. +const ( + netSendRetryTime = 5 * time.Second + defaultSleepTime = 60 // Seconds + profilePath = "rv.prof" + pkg = "revid-cli:" +) + +// canProfile is set to false with revid-cli is built with "-tags profile". +var canProfile = true + +func main() { + mts.Meta = meta.NewWith([][2]string{{metaPreambleKey, metaPreambleData}}) + + fileLog := &lumberjack.Logger{ + Filename: logPath, + MaxSize: logMaxSize, + MaxBackups: logMaxBackup, + MaxAge: logMaxAge, + } + + netLog := netlogger.New() + + log := logger.New(logVerbosity, io.MultiWriter(fileLog, netLog), logSuppress) + + if canProfile { + profile(log) + defer pprof.StopCPUProfile() + } + + var rv *revid.Revid + + ns, err := netsender.New(log, nil, readPin(rv), nil, config.TypeData) + if err != nil { + log.Log(logger.Fatal, pkg+"could not initialise netsender client: "+err.Error()) + } + + run(rv, ns, log, netLog) +} + +func run(rv *revid.Revid, ns *netsender.Sender, l *logger.Logger, nl *netlogger.Logger) { + var vs int + for { + err := ns.Run() + if err != nil { + l.Log(logger.Warning, pkg+"Run Failed. Retrying...", "error", err.Error()) + time.Sleep(netSendRetryTime) + continue + } + + err = nl.Send(ns) + if err != nil { + l.Log(logger.Warning, pkg+"Logs could not be sent", "error", err.Error()) + } + + // If var sum hasn't changed we continue. + newVs := ns.VarSum() + if vs == newVs { + sleep(ns, l) + continue + } + vs = newVs + + vars, err := ns.Vars() + if err != nil { + l.Log(logger.Error, pkg+"netSender failed to get vars", "error", err.Error()) + time.Sleep(netSendRetryTime) + continue + } + + if rv == nil { + rv, err = revid.New(config.Config{Logger: l}, ns) + if err != nil { + l.Log(logger.Warning, pkg+"could not initialise revid", "error", err.Error()) + sleep(ns, l) + continue + } + } + + err = rv.Update(vars) + if err != nil { + l.Log(logger.Warning, pkg+"Couldn't update revid", "error", err.Error()) + sleep(ns, l) + continue + } + + switch ns.Mode() { + case modePaused: + rv.Stop() + case modeNormal: + err = rv.Start() + if err != nil { + l.Log(logger.Warning, pkg+"could not start revid", "error", err.Error()) + ns.SetMode(modePaused, &vs) + sleep(ns, l) + continue + } + case modeBurst: + err = burst(l, rv, ns) + if err != nil { + l.Log(logger.Warning, pkg+"could not start burst", "error", err.Error()) + ns.SetMode(modePaused, &vs) + sleep(ns, l) + continue + } + ns.SetMode(modePaused, &vs) + } + } +} + +func profile(l *logger.Logger) { + f, err := os.Create(profilePath) + if err != nil { + l.Log(logger.Fatal, pkg+"could not create CPU profile", "error", err.Error()) + } + if err := pprof.StartCPUProfile(f); err != nil { + l.Log(logger.Fatal, pkg+"could not start CPU profile", "error", err.Error()) + } +} + +func sleep(ns *netsender.Sender, l *logger.Logger) { + t, err := strconv.Atoi(ns.Param("mp")) + if err != nil { + l.Log(logger.Error, pkg+"could not get sleep time, using default", "error", err) + t = defaultSleepTime + } + time.Sleep(time.Duration(t) * time.Second) +} + +func readPin(rv *revid.Revid) func(pin *netsender.Pin) error { + return func(pin *netsender.Pin) error { + switch { + case pin.Name == "X23": + pin.Value = -1 + if rv != nil { + pin.Value = rv.Bitrate() + } + case pin.Name[0] == 'X': + return sds.ReadSystem(pin) + default: + pin.Value = -1 + } + return nil + } +} + +func burst(l *logger.Logger, r *revid.Revid, s *netsender.Sender) error { + l.Log(logger.Info, pkg+"starting burst") + + err := r.Start() + if err != nil { + return fmt.Errorf("could not start revid: %w", err) + } + + time.Sleep(time.Duration(r.Config().BurstPeriod) * time.Second) + l.Log(logger.Info, pkg+"stopping burst") + r.Stop() + return nil +} diff --git a/cmd/rv/profile.go b/cmd/rv/profile.go new file mode 100644 index 00000000..7b6874d5 --- /dev/null +++ b/cmd/rv/profile.go @@ -0,0 +1,36 @@ +// +build profile + +/* +NAME + revid-cli - command line interface for Revid. + +DESCRIPTION + See Readme.md + +AUTHORS + Dan Kortschak + +LICENSE + revid-cli is Copyright (C) 2017-2018 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 + along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package main + +import _ "net/http/pprof" + +func init() { + canProfile = false +} diff --git a/cmd/rv/run.sh b/cmd/rv/run.sh new file mode 100644 index 00000000..d1c9dced --- /dev/null +++ b/cmd/rv/run.sh @@ -0,0 +1,4 @@ +#!/bin/sh +REVIDPATH=$HOME/go/src/bitbucket.org/ausocean/av/cmd/revid-cli +cd $REVIDPATH +sudo "PATH=$PATH:$REVIDPATH" ./revid-cli -NetSender & diff --git a/cmd/rv/rv b/cmd/rv/rv new file mode 100755 index 00000000..05d16bfc Binary files /dev/null and b/cmd/rv/rv differ diff --git a/cmd/rv/rv.prof b/cmd/rv/rv.prof new file mode 100644 index 00000000..e69de29b diff --git a/cmd/rv/upgrade.sh b/cmd/rv/upgrade.sh new file mode 100644 index 00000000..46cfac2a --- /dev/null +++ b/cmd/rv/upgrade.sh @@ -0,0 +1,56 @@ +#!/bin/bash +# All-purpose upgrade script. +# Upgrades source(s) to given Git tag, runs make in each directory, +# and write tags to tags.conf upon success, exiting 0. +# NB: Customize SrcDirs as needed to reflect dependencies. +Usage="Usage: upgrade.sh [-d] tag" +BaseDir=$GOPATH/src/bitbucket.org/ausocean +VarDir=/var/netsender +LogFile=/var/log/netsender/stream.log +SrcDirs=($BaseDir/utils $BaseDir/iot $BaseDir/av) +if [ "$1" == "-d" ]; then + set -x + GitFlags="" + NewTag="$2" +else + # capture stdout and stderr + exec 2> $LogFile + exec 1>&2 + GitFlags="--quiet" + NewTag="$1" +fi +if [ -z "$GOPATH" ]; then + echo "Error: GOPATH not defined" + exit 1 +fi +if [ -z "$NewTag" ]; then + echo "$Usage" + exit 1 +fi +for dir in ${SrcDirs[@]}; do + pushd $dir + if [ ! "$?" == 0 ]; then + exit 1 + fi + git fetch $GitFlags --depth=1 origin refs/tags/$NewTag:refs/tags/$NewTag + if [ ! "$?" == 0 ]; then + exit 1 + fi + git checkout $GitFlags --force tags/$NewTag + if [ ! "$?" == 0 ]; then + exit 1 + fi + if [ -e Makefile ]; then + make + if [ ! "$?" == 0 ]; then + exit 1 + fi + fi + popd +done +if [ ! -d "$VarDir" ]; then + echo "Error: $VarDir does not exit." + exit 1 +fi +git tag > "$VarDir/tags.conf" +exit $? diff --git a/codec/codecutil/list.go b/codec/codecutil/list.go index c270eb6b..9642cfbc 100644 --- a/codec/codecutil/list.go +++ b/codec/codecutil/list.go @@ -30,11 +30,12 @@ const numCodecs = 5 // A global list containing all available codecs for reference in any application. // When adding or removing a codec from this list, the numCodecs const must be updated. const ( - PCM = iota - ADPCM - H264 - H265 - MJPEG + CodecUndef = iota + CodecPCM + CodecADPCM + CodecH264 + CodecH265 + CodecMJPEG ) // IsValid recieves an int representing a codec and checks if it is valid. diff --git a/codec/pcm/pcm.go b/codec/pcm/pcm.go index 98619f81..3c6fbb5d 100644 --- a/codec/pcm/pcm.go +++ b/codec/pcm/pcm.go @@ -70,7 +70,7 @@ type Buffer struct { // DataSize takes audio attributes describing audio data and returns the size of that data. func DataSize(rate, channels, bitDepth int, period float64, codec uint8) int { s := int(float64(channels) * float64(rate) * float64(bitDepth/8) * period) - if codec == codecutil.ADPCM { + if codec == codecutil.CodecADPCM { s = adpcm.EncBytes(s) } return s diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 5f83d016..8d53b13f 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -421,8 +421,8 @@ func (d *ALSA) formatBuffer() pcm.Buffer { } switch d.Codec { - case codecutil.PCM: - case codecutil.ADPCM: + case codecutil.CodecPCM: + case codecutil.CodecADPCM: b := bytes.NewBuffer(make([]byte, 0, adpcm.EncBytes(len(formatted.Data)))) enc := adpcm.NewEncoder(b) _, err = enc.Write(formatted.Data) diff --git a/device/alsa/alsa_test.go b/device/alsa/alsa_test.go index 44e54e38..9d529b98 100644 --- a/device/alsa/alsa_test.go +++ b/device/alsa/alsa_test.go @@ -44,7 +44,7 @@ func TestDevice(t *testing.T) { Channels: 1, RecPeriod: 0.3, BitDepth: 16, - InputCodec: codecutil.ADPCM, + InputCodec: codecutil.CodecADPCM, } n := 2 // Number of periods to wait while recording. diff --git a/device/geovision/geovision.go b/device/geovision/geovision.go index d09054ad..1df72a3c 100644 --- a/device/geovision/geovision.go +++ b/device/geovision/geovision.go @@ -63,7 +63,7 @@ const ( // Configuration defaults. const ( defaultCameraIP = "192.168.1.50" - defaultCodec = codecutil.H264 + defaultCodec = codecutil.CodecH264 defaultHeight = 720 defaultFrameRate = 25 defaultBitrate = 400 @@ -115,7 +115,7 @@ func (g *GeoVision) Set(c avconfig.Config) error { } switch c.InputCodec { - case codecutil.H264, codecutil.H265, codecutil.MJPEG: + case codecutil.CodecH264, codecutil.CodecH265, codecutil.CodecMJPEG: default: errs = append(errs, errGVBadCodec) c.InputCodec = defaultCodec @@ -171,9 +171,9 @@ func (g *GeoVision) Set(c avconfig.Config) error { gvconfig.Channel(g.cfg.CameraChan), gvconfig.CodecOut( map[uint8]gvconfig.Codec{ - codecutil.H264: gvconfig.CodecH264, - codecutil.H265: gvconfig.CodecH265, - codecutil.MJPEG: gvconfig.CodecMJPEG, + codecutil.CodecH264: gvconfig.CodecH264, + codecutil.CodecH265: gvconfig.CodecH265, + codecutil.CodecMJPEG: gvconfig.CodecMJPEG, }[g.cfg.InputCodec], ), gvconfig.Height(int(g.cfg.Height)), diff --git a/device/raspivid/raspivid.go b/device/raspivid/raspivid.go index cbae8c83..b0496320 100644 --- a/device/raspivid/raspivid.go +++ b/device/raspivid/raspivid.go @@ -44,7 +44,7 @@ const pkg = "raspivid: " // Raspivid configuration defaults. const ( - defaultRaspividCodec = codecutil.H264 + defaultRaspividCodec = codecutil.CodecH264 defaultRaspividRotation = 0 defaultRaspividWidth = 1280 defaultRaspividHeight = 720 @@ -133,7 +133,7 @@ func (r *Raspivid) Name() string { func (r *Raspivid) Set(c config.Config) error { var errs device.MultiError switch c.InputCodec { - case codecutil.H264, codecutil.MJPEG: + case codecutil.CodecH264, codecutil.CodecMJPEG: default: c.InputCodec = defaultRaspividCodec errs = append(errs, errBadCodec) @@ -236,7 +236,7 @@ func (r *Raspivid) Start() error { switch r.cfg.InputCodec { default: return fmt.Errorf("revid: invalid input codec: %v", r.cfg.InputCodec) - case codecutil.H264: + case codecutil.CodecH264: args = append(args, "--codec", "H264", "--inline", @@ -245,7 +245,7 @@ func (r *Raspivid) Start() error { if !r.cfg.CBR { args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization)) } - case codecutil.MJPEG: + case codecutil.CodecMJPEG: args = append(args, "--codec", "MJPEG") } r.cfg.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " ")) diff --git a/device/webcam/webcam.go b/device/webcam/webcam.go index fbb3a91b..f13ef62b 100644 --- a/device/webcam/webcam.go +++ b/device/webcam/webcam.go @@ -131,13 +131,13 @@ func (w *Webcam) Start() error { switch w.cfg.InputCodec { default: return fmt.Errorf("revid: invalid input codec: %v", w.cfg.InputCodec) - case codecutil.H264: + case codecutil.CodecH264: args = append(args, "-f", "h264", "-maxrate", fmt.Sprint(br), "-bufsize", fmt.Sprint(br/2), ) - case codecutil.MJPEG: + case codecutil.CodecMJPEG: args = append(args, "-f", "mjpeg", ) @@ -182,10 +182,12 @@ func (w *Webcam) Start() error { } }() + w.cfg.Logger.Log(logger.Info, "starting webcam") err = w.cmd.Start() if err != nil { return fmt.Errorf("failed to start ffmpeg: %w", err) } + w.cfg.Logger.Log(logger.Info, "webcam started") return nil } diff --git a/revid/audio_linux.go b/revid/audio_linux.go index 9ce5c2bc..eb87fd16 100644 --- a/revid/audio_linux.go +++ b/revid/audio_linux.go @@ -42,9 +42,9 @@ func (r *Revid) setupAudio() error { mts.Meta.Add("bitDepth", strconv.Itoa(r.cfg.BitDepth)) switch r.cfg.InputCodec { - case codecutil.PCM: + case codecutil.CodecPCM: mts.Meta.Add("codec", "pcm") - case codecutil.ADPCM: + case codecutil.CodecADPCM: mts.Meta.Add("codec", "adpcm") default: r.cfg.Logger.Log(logger.Fatal, pkg+"no audio codec set in config") diff --git a/revid/config/config.go b/revid/config/config.go index 0192b1bd..d86360f3 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -74,7 +74,7 @@ const ( defaultInput = InputRaspivid defaultOutput = OutputHTTP defaultTimeout = 0 - defaultInputCodec = codecutil.H264 + defaultInputCodec = codecutil.CodecH264 defaultVerbosity = logger.Error defaultRtpAddr = "localhost:6970" defaultCameraIP = "192.168.1.50" @@ -83,7 +83,7 @@ const ( defaultFrameRate = 25 defaultWriteRate = 25 defaultClipDuration = 0 - defaultAudioInputCodec = codecutil.ADPCM + defaultAudioInputCodec = codecutil.CodecADPCM defaultPSITime = 2 defaultMotionInterval = 5 defaultFileFPS = 0 @@ -392,7 +392,7 @@ func (c *Config) Validate() error { } switch c.InputCodec { - case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM: + case codecutil.CodecH264, codecutil.CodecMJPEG, codecutil.CodecPCM, codecutil.CodecADPCM: default: switch c.Input { case OutputAudio: diff --git a/revid/revid.go b/revid/revid.go index 5eb5b7bf..cb32b2b8 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -176,9 +176,9 @@ func (r *Revid) reset(c config.Config) error { switch r.cfg.Input { case config.InputRaspivid: switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: st = mts.EncodeH264 - case codecutil.MJPEG: + case codecutil.CodecMJPEG: st = mts.EncodeMJPEG encOptions = append(encOptions, mts.TimeBasedPSI(time.Duration(r.cfg.PSITime)*time.Second)) r.cfg.CBR = true @@ -187,22 +187,22 @@ func (r *Revid) reset(c config.Config) error { } case config.InputFile, config.InputV4L: switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: st = mts.EncodeH264 - case codecutil.MJPEG: + case codecutil.CodecMJPEG: st = mts.EncodeMJPEG encOptions = append(encOptions, mts.TimeBasedPSI(time.Duration(r.cfg.PSITime)*time.Second)) r.cfg.CBR = true default: - panic("unknown input codec for v4l or input file input") + panic(fmt.Sprintf("unknown input codec %d for v4l or input file input", r.cfg.InputCodec)) } case config.InputRTSP: switch r.cfg.InputCodec { - case codecutil.H265: + case codecutil.CodecH265: st = mts.EncodeH265 - case codecutil.H264: + case codecutil.CodecH264: st = mts.EncodeH264 - case codecutil.MJPEG: + case codecutil.CodecMJPEG: st = mts.EncodeMJPEG encOptions = append(encOptions, mts.TimeBasedPSI(time.Duration(r.cfg.PSITime)*time.Second)) r.cfg.CBR = true @@ -361,27 +361,27 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.input = raspivid.New(r.cfg.Logger) switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: r.lexTo = h264.Lex - case codecutil.MJPEG: + case codecutil.CodecMJPEG: r.lexTo = mjpeg.Lex } case config.InputV4L: r.input = webcam.New(r.cfg.Logger) switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: r.lexTo = h264.Lex - case codecutil.MJPEG: + case codecutil.CodecMJPEG: r.lexTo = mjpeg.Lex } case config.InputFile: r.input = file.New() switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: r.lexTo = h264.Lex - case codecutil.MJPEG: + case codecutil.CodecMJPEG: r.lexTo = mjpeg.Lex } @@ -389,11 +389,11 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.input = geovision.New(r.cfg.Logger) switch r.cfg.InputCodec { - case codecutil.H264: + case codecutil.CodecH264: r.lexTo = h264.NewExtractor().Extract - case codecutil.H265: + case codecutil.CodecH265: r.lexTo = h265.NewLexer(false).Lex - case codecutil.MJPEG: + case codecutil.CodecMJPEG: r.lexTo = mjpeg.NewExtractor().Extract } @@ -540,9 +540,9 @@ func (r *Revid) Update(vars map[string]string) error { case "InputCodec": switch value { case "H264": - r.cfg.InputCodec = codecutil.H264 + r.cfg.InputCodec = codecutil.CodecH264 case "MJPEG": - r.cfg.InputCodec = codecutil.MJPEG + r.cfg.InputCodec = codecutil.CodecMJPEG default: r.cfg.Logger.Log(logger.Warning, pkg+"invalid InputCodec variable value", "value", value) }