From 9a0fa098798f5ce6dbf925eaf4c681726f176c13 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 20 Jan 2020 17:40:45 +1030 Subject: [PATCH 01/20] revid/senders.go,revid.go: Added bitrate calculations to revid. --- revid/revid.go | 12 +++++++---- revid/senders.go | 53 ++++++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/revid/revid.go b/revid/revid.go index 8b1b42da..5cd0fbfc 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -7,6 +7,7 @@ AUTHORS Alan Noble Dan Kortschak Trek Hopton + Scott Barnard LICENSE revid is Copyright (C) 2017-2020 the Australian Ocean Lab (AusOcean) @@ -52,6 +53,7 @@ import ( "bitbucket.org/ausocean/av/filter" "bitbucket.org/ausocean/av/revid/config" "bitbucket.org/ausocean/iot/pi/netsender" + "bitbucket.org/ausocean/utils/bitrate" "bitbucket.org/ausocean/utils/ioext" "bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/vring" @@ -115,6 +117,9 @@ type Revid struct { // err will channel errors from revid routines to the handle errors routine. err chan error + + // bitrate is used for bitrate calculations. + bitrate bitrate.Calculator } // New returns a pointer to a new Revid with the desired configuration, and/or @@ -148,10 +153,8 @@ func (r *Revid) handleErrors() { } // Bitrate returns the result of the most recent bitrate check. -// -// TODO: get this working again. func (r *Revid) Bitrate() int { - return -1 + return r.bitrate.Bitrate() } // reset swaps the current config of a Revid with the passed @@ -266,7 +269,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. return fmt.Errorf("could not initialise MTS ring buffer: %w", err) } w = newMtsSender( - newHttpSender(r.ns, r.cfg.Logger.Log), + newHttpSender(r.ns, r.cfg.Logger.Log, r.bitrate.Report), r.cfg.Logger.Log, rb, r.cfg.ClipDuration, @@ -295,6 +298,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. rtmpConnectionMaxTries, rb, r.cfg.Logger.Log, + r.bitrate.Report, ) if err != nil { r.cfg.Logger.Log(logger.Warning, pkg+"rtmp connect error", "error", err.Error()) diff --git a/revid/senders.go b/revid/senders.go index 4871ddb5..216b191b 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -59,21 +59,27 @@ const ( // httpSender provides an implemntation of io.Writer to perform sends to a http // destination. type httpSender struct { - client *netsender.Sender - log func(lvl int8, msg string, args ...interface{}) + client *netsender.Sender + log func(lvl int8, msg string, args ...interface{}) + reportSent func(sent int) } // newHttpSender returns a pointer to a new httpSender. -func newHttpSender(ns *netsender.Sender, log func(lvl int8, msg string, args ...interface{})) *httpSender { +func newHttpSender(ns *netsender.Sender, log func(lvl int8, msg string, args ...interface{}), reportSent func(sent int)) *httpSender { return &httpSender{ - client: ns, - log: log, + client: ns, + log: log, + reportSent: reportSent, } } // Write implements io.Writer. func (s *httpSender) Write(d []byte) (int, error) { - return len(d), httpSend(d, s.client, s.log) + err := httpSend(d, s.client, s.log) + if err == nil { + s.reportSent(len(d)) + } + return len(d), err } func (s *httpSender) Close() error { return nil } @@ -268,17 +274,18 @@ func (s *mtsSender) Close() error { // rtmpSender implements loadSender for a native RTMP destination. type rtmpSender struct { - conn *rtmp.Conn - url string - timeout uint - retries int - log func(lvl int8, msg string, args ...interface{}) - ring *vring.Buffer - done chan struct{} - wg sync.WaitGroup + conn *rtmp.Conn + url string + timeout uint + retries int + log func(lvl int8, msg string, args ...interface{}) + ring *vring.Buffer + done chan struct{} + wg sync.WaitGroup + reportSent func(sent int) } -func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log func(lvl int8, msg string, args ...interface{})) (*rtmpSender, error) { +func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log func(lvl int8, msg string, args ...interface{}), reportSent func(sent int)) (*rtmpSender, error) { var conn *rtmp.Conn var err error for n := 0; n < retries; n++ { @@ -292,13 +299,14 @@ func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log } } s := &rtmpSender{ - conn: conn, - url: url, - timeout: timeout, - retries: retries, - log: log, - ring: rb, - done: make(chan struct{}), + conn: conn, + url: url, + timeout: timeout, + retries: retries, + log: log, + ring: rb, + done: make(chan struct{}), + reportSent: reportSent, } s.wg.Add(1) go s.output() @@ -364,6 +372,7 @@ func (s *rtmpSender) Write(d []byte) (int, error) { if err != nil { s.log(logger.Warning, pkg+"rtmpSender: ring buffer write error", "error", err.Error()) } + s.reportSent(len(d)) return len(d), nil } From cdd74c7a22625e59caf47b18510aa5d7f2c2e484 Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 22 Jan 2020 15:26:14 +1030 Subject: [PATCH 02/20] revid/senders.go: reportSent -> report --- revid/senders.go | 54 ++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/revid/senders.go b/revid/senders.go index 216b191b..6e74d519 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -59,17 +59,17 @@ const ( // httpSender provides an implemntation of io.Writer to perform sends to a http // destination. type httpSender struct { - client *netsender.Sender - log func(lvl int8, msg string, args ...interface{}) - reportSent func(sent int) + client *netsender.Sender + log func(lvl int8, msg string, args ...interface{}) + report func(sent int) } // newHttpSender returns a pointer to a new httpSender. -func newHttpSender(ns *netsender.Sender, log func(lvl int8, msg string, args ...interface{}), reportSent func(sent int)) *httpSender { +func newHttpSender(ns *netsender.Sender, log func(lvl int8, msg string, args ...interface{}), report func(sent int)) *httpSender { return &httpSender{ - client: ns, - log: log, - reportSent: reportSent, + client: ns, + log: log, + report: report, } } @@ -77,7 +77,7 @@ func newHttpSender(ns *netsender.Sender, log func(lvl int8, msg string, args ... func (s *httpSender) Write(d []byte) (int, error) { err := httpSend(d, s.client, s.log) if err == nil { - s.reportSent(len(d)) + s.report(len(d)) } return len(d), err } @@ -274,18 +274,18 @@ func (s *mtsSender) Close() error { // rtmpSender implements loadSender for a native RTMP destination. type rtmpSender struct { - conn *rtmp.Conn - url string - timeout uint - retries int - log func(lvl int8, msg string, args ...interface{}) - ring *vring.Buffer - done chan struct{} - wg sync.WaitGroup - reportSent func(sent int) + conn *rtmp.Conn + url string + timeout uint + retries int + log func(lvl int8, msg string, args ...interface{}) + ring *vring.Buffer + done chan struct{} + wg sync.WaitGroup + report func(sent int) } -func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log func(lvl int8, msg string, args ...interface{}), reportSent func(sent int)) (*rtmpSender, error) { +func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log func(lvl int8, msg string, args ...interface{}), report func(sent int)) (*rtmpSender, error) { var conn *rtmp.Conn var err error for n := 0; n < retries; n++ { @@ -299,14 +299,14 @@ func newRtmpSender(url string, timeout uint, retries int, rb *vring.Buffer, log } } s := &rtmpSender{ - conn: conn, - url: url, - timeout: timeout, - retries: retries, - log: log, - ring: rb, - done: make(chan struct{}), - reportSent: reportSent, + conn: conn, + url: url, + timeout: timeout, + retries: retries, + log: log, + ring: rb, + done: make(chan struct{}), + report: report, } s.wg.Add(1) go s.output() @@ -372,7 +372,7 @@ func (s *rtmpSender) Write(d []byte) (int, error) { if err != nil { s.log(logger.Warning, pkg+"rtmpSender: ring buffer write error", "error", err.Error()) } - s.reportSent(len(d)) + s.report(len(d)) return len(d), nil } From 1876fe9dea14bbd950f00608565517b9608df5ab Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 23 Jan 2020 10:07:28 +1030 Subject: [PATCH 03/20] Added bitrate reporting to RTP sender. --- revid/revid.go | 2 +- revid/senders.go | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/revid/revid.go b/revid/revid.go index 5cd0fbfc..1c95f05a 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -276,7 +276,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. ) mtsSenders = append(mtsSenders, w) case config.OutputRTP: - w, err := newRtpSender(r.cfg.RTPAddress, r.cfg.Logger.Log, r.cfg.FrameRate) + w, err := newRtpSender(r.cfg.RTPAddress, r.cfg.Logger.Log, r.cfg.FrameRate, r.bitrate.Report) if err != nil { r.cfg.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error()) } diff --git a/revid/senders.go b/revid/senders.go index 6e74d519..4929bd40 100644 --- a/revid/senders.go +++ b/revid/senders.go @@ -413,9 +413,10 @@ type rtpSender struct { log func(lvl int8, msg string, args ...interface{}) encoder *rtp.Encoder data []byte + report func(sent int) } -func newRtpSender(addr string, log func(lvl int8, msg string, args ...interface{}), fps uint) (*rtpSender, error) { +func newRtpSender(addr string, log func(lvl int8, msg string, args ...interface{}), fps uint, report func(sent int)) (*rtpSender, error) { conn, err := net.Dial("udp", addr) if err != nil { return nil, err @@ -423,6 +424,7 @@ func newRtpSender(addr string, log func(lvl int8, msg string, args ...interface{ s := &rtpSender{ log: log, encoder: rtp.NewEncoder(conn, int(fps)), + report: report, } return s, nil } @@ -435,6 +437,7 @@ func (s *rtpSender) Write(d []byte) (int, error) { if err != nil { s.log(logger.Warning, pkg+"rtpSender: write error", err.Error()) } + s.report(len(d)) return len(d), nil } From 7419bdff7ad579a373b3983a9c7128ddd47d7ae1 Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 23 Jan 2020 14:28:21 +1030 Subject: [PATCH 04/20] Using utils v1.2.13 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2ed4c734..dfa6f093 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.13 require ( bitbucket.org/ausocean/iot v1.2.13 - bitbucket.org/ausocean/utils v1.2.12 + bitbucket.org/ausocean/utils v1.2.13 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 diff --git a/go.sum b/go.sum index ef1fb9ff..d74fe617 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,8 @@ bitbucket.org/ausocean/utils v1.2.11 h1:zA0FOaPjN960ryp8PKCkV5y50uWBYrIxCVnXjwbv bitbucket.org/ausocean/utils v1.2.11/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= bitbucket.org/ausocean/utils v1.2.12 h1:VnskjWTDM475TnQRhBQE0cNp9D6Y6OELrd4UkD2VVIQ= bitbucket.org/ausocean/utils v1.2.12/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= +bitbucket.org/ausocean/utils v1.2.13 h1:tUaIywtoMc1+zl1GCVQokX4mL5X7LNHX5O51AgAPrWA= +bitbucket.org/ausocean/utils v1.2.13/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXShkLA3/kpxXb6LJpH+ekU2krbzw= From 99b931f948f875cb1b61d95aab8dc4670c9fd2e8 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 24 Jan 2020 19:21:18 +1030 Subject: [PATCH 05/20] revid & cmd/revid-cli: added loop mode so that input may be restarted after completion Loop flag has been added to command line flags and in turn sets the Loop field that has been added to the config.Config struct. mode variable now also checked to see if value set to Loop, in which case revid config.Config.Loop = true. Revid.processFrom modified so that when input source has completed Revid.cfg.Loop is checked and input restarted if true. --- cmd/revid-cli/main.go | 3 +++ revid/config/config.go | 3 +++ revid/revid.go | 43 +++++++++++++++++++++++++++++++----------- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index bced0a4f..fc4d863f 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -122,6 +122,7 @@ func handleFlags() config.Config { httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") + loopPtr = flag.Bool("Loop", false, "Loop video if EOF encountered: true, false") bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") heightPtr = flag.Uint("Height", 0, "Height in pixels") widthPtr = flag.Uint("Width", 0, "Width in pixels") @@ -179,6 +180,8 @@ func handleFlags() config.Config { } } + cfg.Loop = *loopPtr + switch *inputPtr { case "Raspivid": cfg.Input = config.InputRaspivid diff --git a/revid/config/config.go b/revid/config/config.go index 194faf33..47a8eaf9 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -297,6 +297,9 @@ type Config struct { MOGMinArea float64 // Used to ignore small areas of motion detection. MOGThreshold float64 // Intensity value from the KNN motion detection algorithm that is considered motion. MOGHistory uint // Length of MOG filter's history + + // If true will restart reading of input after an io.EOF. + Loop bool } // TypeData contains information about all of the variables that diff --git a/revid/revid.go b/revid/revid.go index 8b1b42da..9c414480 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -428,13 +428,8 @@ func (r *Revid) Start() error { return err } - err = r.input.Start() - if err != nil { - return fmt.Errorf("could not start input device: %w", err) - } - r.wg.Add(1) - go r.processFrom(r.input, 0) + go r.processFrom(r.input, (1000/25)*time.Millisecond) r.running = true return nil @@ -845,6 +840,11 @@ func (r *Revid) Update(vars map[string]string) error { break } r.cfg.MOGHistory = uint(v) + case "mode": + r.cfg.Loop = false + if value == "Loop" { + r.cfg.Loop = true + } } } r.cfg.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.cfg)) @@ -853,14 +853,35 @@ func (r *Revid) Update(vars map[string]string) error { // processFrom is run as a routine to read from a input data source, lex and // then send individual access units to revid's encoders. -func (r *Revid) processFrom(read io.Reader, delay time.Duration) { - err := r.lexTo(r.filters[0], read, delay) - r.cfg.Logger.Log(logger.Debug, pkg+"finished lexing") +func (r *Revid) processFrom(in device.AVDevice, delay time.Duration) { +restart: + err := in.Start() + if err != nil { + r.err <- fmt.Errorf("could not start input device: %w", err) + r.wg.Done() + return + } + + // Lex data from input device, in, until finished or an error is encountered. + // For a continuous source e.g. a camera or microphone, we should remain + // in this call indefinitely unless in.Stop() is called and an io.EOF is forced. + err = r.lexTo(r.filters[0], in, delay) switch err { - case nil: // Do nothing. - case io.EOF: // TODO: handle this depending on loop mode. + case nil, io.EOF, io.ErrUnexpectedEOF: default: r.err <- err } + + err = in.Stop() + if err != nil { + r.err <- fmt.Errorf("could not stop input source: %w", err) + } + + if r.cfg.Loop { + r.cfg.Logger.Log(logger.Info, pkg+"looping input") + goto restart + } + + r.cfg.Logger.Log(logger.Info, pkg+"finished lexing") r.wg.Done() } From 0f5aaf6cb5afac81fbb1d1b871d4d08313db3681 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 24 Jan 2020 20:05:43 +1030 Subject: [PATCH 06/20] revid & cmd/revid-cli: added InputFPS config.Config field The InputFPS field can control rate at which we lex frames from the input source. This has not been a useful feature until now; we now want to simulate realtime input device using file input. This requires firstly the Loop mode, and now also realistic input rate. --- cmd/revid-cli/main.go | 5 +++-- revid/config/config.go | 14 +++++++++++++- revid/revid.go | 5 ++++- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index fc4d863f..d0b32e9f 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -133,6 +133,7 @@ func handleFlags() config.Config { saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(raspivid.ExposureModes[:], ",")+")") autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(raspivid.AutoWhiteBalanceModes[:], ",")+")") + inputFPSPtr = flag.Int("InputFPS", 0, "Input source processing FPS") // Audio specific flags. sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") @@ -180,8 +181,6 @@ func handleFlags() config.Config { } } - cfg.Loop = *loopPtr - switch *inputPtr { case "Raspivid": cfg.Input = config.InputRaspivid @@ -238,6 +237,8 @@ func handleFlags() config.Config { netsender.ConfigFile = *configFilePtr } + cfg.InputFPS = *inputFPSPtr + cfg.Loop = *loopPtr cfg.CameraIP = *cameraIPPtr cfg.Rotation = *rotationPtr cfg.HorizontalFlip = *horizontalFlipPtr diff --git a/revid/config/config.go b/revid/config/config.go index 47a8eaf9..8ee31ee5 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -86,6 +86,7 @@ const ( defaultAudioInputCodec = codecutil.ADPCM defaultPSITime = 2 defaultMotionInterval = 5 + defaultInputFPS = 0 // Ring buffer defaults. defaultRBMaxElements = 10000 @@ -300,6 +301,10 @@ type Config struct { // If true will restart reading of input after an io.EOF. Loop bool + + // Defines the rate at which an input source is processed. If reading + // from a realtime source, InputFPS is not necessary and should be 0 (default). + InputFPS int } // TypeData contains information about all of the variables that @@ -321,15 +326,17 @@ var TypeData = map[string]string{ "HTTPAddress": "string", "Input": "enum:raspivid,rtsp,v4l,file", "InputCodec": "enum:H264,MJPEG", + "InputFPS": "int", "InputPath": "string", "KNNHistory": "uint", "KNNKernel": "float", "KNNMinArea": "float", "KNNThreshold": "float", "logging": "enum:Debug,Info,Warning,Error,Fatal", + "Loop": "bool", "MinFPS": "float", "MinFrames": "uint", - "mode": "enum:Normal,Paused,Burst", + "mode": "enum:Normal,Paused,Burst,Loop", "MOGHistory": "uint", "MOGMinArea": "float", "MOGThreshold": "float", @@ -524,6 +531,11 @@ func (c *Config) Validate() error { } } + if c.InputFPS <= 0 { + c.Logger.Log(logger.Info, pkg+"InputFPS bad or unset, defaulting", "InputFPS", defaultInputFPS) + c.InputFPS = defaultInputFPS + } + return nil } diff --git a/revid/revid.go b/revid/revid.go index 9c414480..b442e34a 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -428,8 +428,11 @@ func (r *Revid) Start() error { return err } + // Calculate delay between frames based on InputFPS. + d := time.Duration(1000/r.cfg.InputFPS) * time.Millisecond + r.wg.Add(1) - go r.processFrom(r.input, (1000/25)*time.Millisecond) + go r.processFrom(r.input, d) r.running = true return nil From 4e7e5ebca3cd5fa95f448dbf3f3baf7cb7788639 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 24 Jan 2020 20:15:40 +1030 Subject: [PATCH 07/20] revid/revid.go: cleaned up prcoessFrom (added deger for waitgroup.Done and added info log) --- revid/revid.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/revid/revid.go b/revid/revid.go index b442e34a..7aca384e 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -857,17 +857,19 @@ func (r *Revid) Update(vars map[string]string) error { // processFrom is run as a routine to read from a input data source, lex and // then send individual access units to revid's encoders. func (r *Revid) processFrom(in device.AVDevice, delay time.Duration) { + defer r.wg.Done() + restart: err := in.Start() if err != nil { r.err <- fmt.Errorf("could not start input device: %w", err) - r.wg.Done() return } // Lex data from input device, in, until finished or an error is encountered. // For a continuous source e.g. a camera or microphone, we should remain // in this call indefinitely unless in.Stop() is called and an io.EOF is forced. + r.cfg.Logger.Log(logger.Info, pkg+"lexing") err = r.lexTo(r.filters[0], in, delay) switch err { case nil, io.EOF, io.ErrUnexpectedEOF: @@ -886,5 +888,4 @@ restart: } r.cfg.Logger.Log(logger.Info, pkg+"finished lexing") - r.wg.Done() } From 0e14c0a056966b82a269d0124b3914cf7e666570 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 24 Jan 2020 20:24:23 +1030 Subject: [PATCH 08/20] cmd/revid-cli: improved commentfor Loop flag description --- cmd/revid-cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index d0b32e9f..3d03916b 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -122,7 +122,7 @@ func handleFlags() config.Config { httpAddressPtr = flag.String("HttpAddress", "", "Destination address of http posts") verticalFlipPtr = flag.Bool("VerticalFlip", false, "Flip video vertically: Yes, No") horizontalFlipPtr = flag.Bool("HorizontalFlip", false, "Flip video horizontally: Yes, No") - loopPtr = flag.Bool("Loop", false, "Loop video if EOF encountered: true, false") + loopPtr = flag.Bool("Loop", false, "Loop input source on completion (true/false)") bitratePtr = flag.Uint("Bitrate", 0, "Bitrate of recorded video") heightPtr = flag.Uint("Height", 0, "Height in pixels") widthPtr = flag.Uint("Width", 0, "Width in pixels") From 64754f7e0f33a6ae37000ed732a4f70d9a2f2771 Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 24 Jan 2020 21:25:27 +1030 Subject: [PATCH 09/20] revid: use loop in processFrom instead of goto --- revid/revid.go | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/revid/revid.go b/revid/revid.go index 7aca384e..3d9d26c5 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -859,32 +859,28 @@ func (r *Revid) Update(vars map[string]string) error { func (r *Revid) processFrom(in device.AVDevice, delay time.Duration) { defer r.wg.Done() -restart: - err := in.Start() - if err != nil { - r.err <- fmt.Errorf("could not start input device: %w", err) - return - } + for l := true; l; l = r.cfg.Loop { + err := in.Start() + if err != nil { + r.err <- fmt.Errorf("could not start input device: %w", err) + return + } - // Lex data from input device, in, until finished or an error is encountered. - // For a continuous source e.g. a camera or microphone, we should remain - // in this call indefinitely unless in.Stop() is called and an io.EOF is forced. - r.cfg.Logger.Log(logger.Info, pkg+"lexing") - err = r.lexTo(r.filters[0], in, delay) - switch err { - case nil, io.EOF, io.ErrUnexpectedEOF: - default: - r.err <- err - } + // Lex data from input device, in, until finished or an error is encountered. + // For a continuous source e.g. a camera or microphone, we should remain + // in this call indefinitely unless in.Stop() is called and an io.EOF is forced. + r.cfg.Logger.Log(logger.Info, pkg+"lexing") + err = r.lexTo(r.filters[0], in, delay) + switch err { + case nil, io.EOF, io.ErrUnexpectedEOF: + default: + r.err <- err + } - err = in.Stop() - if err != nil { - r.err <- fmt.Errorf("could not stop input source: %w", err) - } - - if r.cfg.Loop { - r.cfg.Logger.Log(logger.Info, pkg+"looping input") - goto restart + err = in.Stop() + if err != nil { + r.err <- fmt.Errorf("could not stop input source: %w", err) + } } r.cfg.Logger.Log(logger.Info, pkg+"finished lexing") From f82ab422463dcc7db3fccc335011bc0b0754212c Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 25 Jan 2020 09:39:42 +1030 Subject: [PATCH 10/20] revid & cmd/revid-cli: InputFPS field now FileFPS and added check so that only used with File input --- cmd/revid-cli/main.go | 2 +- revid/config/config.go | 15 +++++++-------- revid/revid.go | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 3d03916b..5db8e9a1 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -133,7 +133,7 @@ func handleFlags() config.Config { saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)") exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(raspivid.ExposureModes[:], ",")+")") autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(raspivid.AutoWhiteBalanceModes[:], ",")+")") - inputFPSPtr = flag.Int("InputFPS", 0, "Input source processing FPS") + fileFPSPtr = flag.Int("FileFPS", 0, "File source frame processing FPS") // Audio specific flags. sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio") diff --git a/revid/config/config.go b/revid/config/config.go index 8ee31ee5..b70a01b3 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -86,7 +86,7 @@ const ( defaultAudioInputCodec = codecutil.ADPCM defaultPSITime = 2 defaultMotionInterval = 5 - defaultInputFPS = 0 + defaultFileFPS = 0 // Ring buffer defaults. defaultRBMaxElements = 10000 @@ -302,9 +302,8 @@ type Config struct { // If true will restart reading of input after an io.EOF. Loop bool - // Defines the rate at which an input source is processed. If reading - // from a realtime source, InputFPS is not necessary and should be 0 (default). - InputFPS int + // Defines the rate at which frames from a file source are processed. + FileFPS int } // TypeData contains information about all of the variables that @@ -319,6 +318,7 @@ var TypeData = map[string]string{ "CBR": "bool", "ClipDuration": "uint", "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", + "FileFPS": "int", "Filters": "enums:NoOp,MOG,VariableFPS,KNN", "FrameRate": "uint", "Height": "uint", @@ -326,7 +326,6 @@ var TypeData = map[string]string{ "HTTPAddress": "string", "Input": "enum:raspivid,rtsp,v4l,file", "InputCodec": "enum:H264,MJPEG", - "InputFPS": "int", "InputPath": "string", "KNNHistory": "uint", "KNNKernel": "float", @@ -531,9 +530,9 @@ func (c *Config) Validate() error { } } - if c.InputFPS <= 0 { - c.Logger.Log(logger.Info, pkg+"InputFPS bad or unset, defaulting", "InputFPS", defaultInputFPS) - c.InputFPS = defaultInputFPS + if c.FileFPS <= 0 || (c.FileFPS > 0 && c.Input != InputFile) { + c.Logger.Log(logger.Info, pkg+"FileFPS bad or unset, defaulting", "FileFPS", defaultFileFPS) + c.FileFPS = defaultFileFPS } return nil diff --git a/revid/revid.go b/revid/revid.go index 3d9d26c5..5d8c81d0 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -429,7 +429,7 @@ func (r *Revid) Start() error { } // Calculate delay between frames based on InputFPS. - d := time.Duration(1000/r.cfg.InputFPS) * time.Millisecond + d := time.Duration(1000/r.cfg.FileFPS) * time.Millisecond r.wg.Add(1) go r.processFrom(r.input, d) From a0a99e17dbf62ca7534b869475fdddf14f0af1c5 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 25 Jan 2020 09:42:32 +1030 Subject: [PATCH 11/20] revid/revid.go: logging unexpected EOF from input source --- revid/revid.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/revid/revid.go b/revid/revid.go index 5d8c81d0..aa94daf0 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -872,7 +872,9 @@ func (r *Revid) processFrom(in device.AVDevice, delay time.Duration) { r.cfg.Logger.Log(logger.Info, pkg+"lexing") err = r.lexTo(r.filters[0], in, delay) switch err { - case nil, io.EOF, io.ErrUnexpectedEOF: + case nil, io.EOF: + case io.ErrUnexpectedEOF: + r.cfg.Logger.Log(logger.Info, pkg+"unexpected EOF from input") default: r.err <- err } From dcafcbf69ebc83b500eb4a6edcb649b849188744 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 25 Jan 2020 09:53:21 +1030 Subject: [PATCH 12/20] cmd/revid-cli: fixed build error --- cmd/revid-cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 5db8e9a1..abc049ed 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -237,7 +237,7 @@ func handleFlags() config.Config { netsender.ConfigFile = *configFilePtr } - cfg.InputFPS = *inputFPSPtr + cfg.FileFPS = *fileFPSPtr cfg.Loop = *loopPtr cfg.CameraIP = *cameraIPPtr cfg.Rotation = *rotationPtr From cfd3f0fc3c62d961ac906fe81f789717dd1e8a68 Mon Sep 17 00:00:00 2001 From: Saxon Date: Sat, 25 Jan 2020 10:15:27 +1030 Subject: [PATCH 13/20] revid: consider when r.cfg.FileFPS ==0 --- revid/revid.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/revid/revid.go b/revid/revid.go index aa94daf0..94bd4a6f 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -428,8 +428,11 @@ func (r *Revid) Start() error { return err } - // Calculate delay between frames based on InputFPS. - d := time.Duration(1000/r.cfg.FileFPS) * time.Millisecond + // Calculate delay between frames based on FileFPS. + d := time.Duration(0) + if r.cfg.FileFPS != 0 { + d = time.Duration(1000/r.cfg.FileFPS) * time.Millisecond + } r.wg.Add(1) go r.processFrom(r.input, d) From ced8727c070b2077366ab4329e2e639a6923098a Mon Sep 17 00:00:00 2001 From: Scott Date: Wed, 22 Jan 2020 12:06:14 +1030 Subject: [PATCH 14/20] filter: create a simple difference motion filter using gocv --- filter/difference.go | 114 +++++++++++++++++++++++++++++++++++++++++ revid/config/config.go | 12 +++-- revid/revid.go | 11 +++- 3 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 filter/difference.go diff --git a/filter/difference.go b/filter/difference.go new file mode 100644 index 00000000..97df94b9 --- /dev/null +++ b/filter/difference.go @@ -0,0 +1,114 @@ +// +build !circleci + +/* +DESCRIPTION + A filter that detects motion and discards frames without motion. The + algorithm calculates the absolute difference for each pixel between + two frames, then finds the mean. If the mean is above a given threshold, + then it is considered motion. + +AUTHORS + Scott Barnard + +LICENSE + difference.go is Copyright (C) 2020 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 filter + +import ( + "fmt" + "image" + "image/color" + "io" + + "gocv.io/x/gocv" +) + +// DiffFilter is a filter that provides basic motion detection. DiffFilter calculates +// the absolute difference for each pixel between two frames, then finds the mean. If +// the mean is above a given threshold, then it is considered motion. +type DiffFilter struct { + dst io.WriteCloser + thresh float64 + prev gocv.Mat + debug bool + windows []*gocv.Window +} + +// NewDiffFilter returns a pointer to a new DiffFilter. +func NewDiffFilter(dst io.WriteCloser, debug bool, threshold float64) *DiffFilter { + var windows []*gocv.Window + if debug { + windows = []*gocv.Window{gocv.NewWindow("Diff: Bounding boxes"), gocv.NewWindow("Diff: Motion")} + } + return &DiffFilter{dst, threshold, gocv.NewMat(), debug, windows} +} + +// Implements io.Closer. +// Close frees resources used by gocv, because it has to be done manually, due to +// it using c-go. +func (d *DiffFilter) Close() error { + d.prev.Close() + for _, window := range d.windows { + window.Close() + } + return nil +} + +// Implements io.Writer. +// Write applies the motion filter to the video stream. Only frames with motion +// are written to the destination encoder, frames without are discarded. +func (d *DiffFilter) Write(f []byte) (int, error) { + if d.prev.Empty() { + d.prev, _ = gocv.IMDecode(f, gocv.IMReadColor) + return 0, nil + } + + img, _ := gocv.IMDecode(f, gocv.IMReadColor) + defer img.Close() + + imgDelta := gocv.NewMat() + defer imgDelta.Close() + + // Seperate foreground and background. + gocv.AbsDiff(img, d.prev, &imgDelta) + gocv.CvtColor(imgDelta, &imgDelta, gocv.ColorBGRToGray) + + mean := imgDelta.Mean().Val1 + + // Update History + d.prev = img.Clone() + + // Draw debug information. + if d.debug { + if mean >= d.thresh { + gocv.PutText(&img, fmt.Sprintf("motion - mean:%f", mean), image.Pt(32, 32), gocv.FontHersheyPlain, 2.0, color.RGBA{255, 0, 0, 0}, 2) + } + + d.windows[0].IMShow(img) + d.windows[1].IMShow(imgDelta) + d.windows[0].WaitKey(1) + } + + // Don't write to destination if there is no motion. + if mean < d.thresh { + return 0, nil + } + + // Write to destination. + return d.dst.Write(f) +} diff --git a/revid/config/config.go b/revid/config/config.go index b70a01b3..0192b1bd 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -127,6 +127,7 @@ const ( FilterMOG FilterVariableFPS FilterKNN + FilterDifference ) // OS names @@ -304,6 +305,8 @@ type Config struct { // Defines the rate at which frames from a file source are processed. FileFPS int + // Difference filter parameters. + DiffThreshold float64 // Intensity value from the Difference motion detection algorithm that is considered motion. } // TypeData contains information about all of the variables that @@ -317,9 +320,10 @@ var TypeData = map[string]string{ "CameraIP": "string", "CBR": "bool", "ClipDuration": "uint", + "DiffThreshold": "float", "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", "FileFPS": "int", - "Filters": "enums:NoOp,MOG,VariableFPS,KNN", + "Filters": "enums:NoOp,MOG,VariableFPS,KNN,Difference", "FrameRate": "uint", "Height": "uint", "HorizontalFlip": "bool", @@ -340,13 +344,13 @@ var TypeData = map[string]string{ "MOGMinArea": "float", "MOGThreshold": "float", "MotionInterval": "int", - "RBCapacity": "uint", - "RBMaxElements": "uint", - "RBWriteTimeout": "uint", "Output": "enum:File,Http,Rtmp,Rtp", "OutputPath": "string", "Outputs": "enums:File,Http,Rtmp,Rtp", "Quantization": "uint", + "RBCapacity": "uint", + "RBMaxElements": "uint", + "RBWriteTimeout": "uint", "Rotation": "uint", "RTMPURL": "string", "RTPAddress": "string", diff --git a/revid/revid.go b/revid/revid.go index 94bd4a6f..fd7614f1 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -342,6 +342,8 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.filters[i] = filter.NewVariableFPSFilter(dst, r.cfg.MinFPS, filter.NewMOGFilter(dst, r.cfg.MOGMinArea, r.cfg.MOGThreshold, int(r.cfg.MOGHistory), r.cfg.ShowWindows, r.cfg.MotionInterval)) case config.FilterKNN: r.filters[i] = filter.NewKNNFilter(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval) + case config.FilterDifference: + r.filters[i] = filter.NewDiffFilter(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold) default: panic("Undefined Filter") } @@ -663,7 +665,7 @@ func (r *Revid) Update(vars map[string]string) error { } case "Filters": filters := strings.Split(value, ",") - m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN} + m := map[string]int{"NoOp": config.FilterNoOp, "MOG": config.FilterMOG, "VariableFPS": config.FilterVariableFPS, "KNN": config.FilterKNN, "Difference": config.FilterDifference} r.cfg.Filters = make([]int, len(filters)) for i, filter := range filters { v, ok := m[filter] @@ -811,6 +813,13 @@ func (r *Revid) Update(vars map[string]string) error { break } r.cfg.KNNThreshold = v + case "DiffThreshold": + v, err := strconv.ParseFloat(value, 64) + if err != nil { + r.cfg.Logger.Log(logger.Warning, pkg+"invalid DiffThreshold var", "value", value) + break + } + r.cfg.DiffThreshold = v case "KNNKernel": v, err := strconv.Atoi(value) if err != nil { From 458933babb95bb105a7f6ffc7a39b509787ea27f Mon Sep 17 00:00:00 2001 From: Scott Date: Thu, 23 Jan 2020 14:03:08 +1030 Subject: [PATCH 15/20] filter: create function for satisfying circleci tests --- filter/filters_circleci.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/filter/filters_circleci.go b/filter/filters_circleci.go index 374e8b0a..d4c427d7 100644 --- a/filter/filters_circleci.go +++ b/filter/filters_circleci.go @@ -36,6 +36,12 @@ func NewMOGFilter(dst io.WriteCloser, area, threshold float64, history int, debu return &NoOp{dst: dst} } +// NewKNNFilter returns a pointer to a new NoOp struct for testing purposes only. func NewKNNFilter(dst io.WriteCloser, area, threshold float64, history, kernelSize int, debug bool, hf int) *NoOp { return &NoOp{dst: dst} } + +// NewDiffFilter returns a pointer to a new NoOp struct for testing purposes only. +func NewDiffFilter(dst io.WriteCloser, debug bool, threshold float64) *NoOp { + return &NoOp{dst: dst} +} From e0fa47490671fdbca93d70023e6c25cb97ca568d Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 24 Jan 2020 16:05:35 +1030 Subject: [PATCH 16/20] =?UTF-8?q?filter:=20DiffFilter=20=E2=86=92=20Differ?= =?UTF-8?q?ence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- filter/difference.go | 14 +++++++------- filter/filters_circleci.go | 4 ++-- revid/revid.go | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/filter/difference.go b/filter/difference.go index 97df94b9..00de000a 100644 --- a/filter/difference.go +++ b/filter/difference.go @@ -38,10 +38,10 @@ import ( "gocv.io/x/gocv" ) -// DiffFilter is a filter that provides basic motion detection. DiffFilter calculates +// Difference is a filter that provides basic motion detection. Difference calculates // the absolute difference for each pixel between two frames, then finds the mean. If // the mean is above a given threshold, then it is considered motion. -type DiffFilter struct { +type Difference struct { dst io.WriteCloser thresh float64 prev gocv.Mat @@ -49,19 +49,19 @@ type DiffFilter struct { windows []*gocv.Window } -// NewDiffFilter returns a pointer to a new DiffFilter. -func NewDiffFilter(dst io.WriteCloser, debug bool, threshold float64) *DiffFilter { +// NewDifference returns a pointer to a new Difference struct. +func NewDifference(dst io.WriteCloser, debug bool, threshold float64) *Difference { var windows []*gocv.Window if debug { windows = []*gocv.Window{gocv.NewWindow("Diff: Bounding boxes"), gocv.NewWindow("Diff: Motion")} } - return &DiffFilter{dst, threshold, gocv.NewMat(), debug, windows} + return &Difference{dst, threshold, gocv.NewMat(), debug, windows} } // Implements io.Closer. // Close frees resources used by gocv, because it has to be done manually, due to // it using c-go. -func (d *DiffFilter) Close() error { +func (d *Difference) Close() error { d.prev.Close() for _, window := range d.windows { window.Close() @@ -72,7 +72,7 @@ func (d *DiffFilter) Close() error { // Implements io.Writer. // Write applies the motion filter to the video stream. Only frames with motion // are written to the destination encoder, frames without are discarded. -func (d *DiffFilter) Write(f []byte) (int, error) { +func (d *Difference) Write(f []byte) (int, error) { if d.prev.Empty() { d.prev, _ = gocv.IMDecode(f, gocv.IMReadColor) return 0, nil diff --git a/filter/filters_circleci.go b/filter/filters_circleci.go index d4c427d7..aaca83f6 100644 --- a/filter/filters_circleci.go +++ b/filter/filters_circleci.go @@ -41,7 +41,7 @@ func NewKNNFilter(dst io.WriteCloser, area, threshold float64, history, kernelSi return &NoOp{dst: dst} } -// NewDiffFilter returns a pointer to a new NoOp struct for testing purposes only. -func NewDiffFilter(dst io.WriteCloser, debug bool, threshold float64) *NoOp { +// NewDiffference returns a pointer to a new NoOp struct for testing purposes only. +func NewDifference(dst io.WriteCloser, debug bool, threshold float64) *NoOp { return &NoOp{dst: dst} } diff --git a/revid/revid.go b/revid/revid.go index fd7614f1..b5f31a6e 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -343,7 +343,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. case config.FilterKNN: r.filters[i] = filter.NewKNNFilter(dst, r.cfg.KNNMinArea, r.cfg.KNNThreshold, int(r.cfg.KNNHistory), int(r.cfg.KNNKernel), r.cfg.ShowWindows, r.cfg.MotionInterval) case config.FilterDifference: - r.filters[i] = filter.NewDiffFilter(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold) + r.filters[i] = filter.NewDifference(dst, r.cfg.ShowWindows, r.cfg.DiffThreshold) default: panic("Undefined Filter") } From b15b649151d394d25d4313d8f66182595ada3d8b Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 24 Jan 2020 16:10:08 +1030 Subject: [PATCH 17/20] filter: check for errors after decoding --- filter/difference.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/filter/difference.go b/filter/difference.go index 00de000a..581cc8bf 100644 --- a/filter/difference.go +++ b/filter/difference.go @@ -74,12 +74,16 @@ func (d *Difference) Close() error { // are written to the destination encoder, frames without are discarded. func (d *Difference) Write(f []byte) (int, error) { if d.prev.Empty() { - d.prev, _ = gocv.IMDecode(f, gocv.IMReadColor) - return 0, nil + var err error + d.prev, err = gocv.IMDecode(f, gocv.IMReadColor) + return 0, err } - img, _ := gocv.IMDecode(f, gocv.IMReadColor) + img, err := gocv.IMDecode(f, gocv.IMReadColor) defer img.Close() + if err != nil { + return 0, err + } imgDelta := gocv.NewMat() defer imgDelta.Close() From fb43e9214ae5fb11d67e92ea4e2c1cec0ee9d40f Mon Sep 17 00:00:00 2001 From: Scott Date: Fri, 24 Jan 2020 16:29:06 +1030 Subject: [PATCH 18/20] filter: change formatting of code --- filter/difference.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/filter/difference.go b/filter/difference.go index 581cc8bf..4504c6ef 100644 --- a/filter/difference.go +++ b/filter/difference.go @@ -100,7 +100,15 @@ func (d *Difference) Write(f []byte) (int, error) { // Draw debug information. if d.debug { if mean >= d.thresh { - gocv.PutText(&img, fmt.Sprintf("motion - mean:%f", mean), image.Pt(32, 32), gocv.FontHersheyPlain, 2.0, color.RGBA{255, 0, 0, 0}, 2) + gocv.PutText( + &img, + fmt.Sprintf("motion - mean:%f", mean), + image.Pt(32, 32), + gocv.FontHersheyPlain, + 2.0, + color.RGBA{255, 0, 0, 0}, + 2, + ) } d.windows[0].IMShow(img) From a111f214279966885d1af40766dabd0ee9867453 Mon Sep 17 00:00:00 2001 From: Scott Date: Mon, 27 Jan 2020 11:23:56 +1030 Subject: [PATCH 19/20] filter: return length from the filter's Write method --- filter/difference.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/filter/difference.go b/filter/difference.go index 4504c6ef..d01cf0a7 100644 --- a/filter/difference.go +++ b/filter/difference.go @@ -76,7 +76,10 @@ func (d *Difference) Write(f []byte) (int, error) { if d.prev.Empty() { var err error d.prev, err = gocv.IMDecode(f, gocv.IMReadColor) - return 0, err + if err != nil { + return 0, err + } + return len(f), nil } img, err := gocv.IMDecode(f, gocv.IMReadColor) @@ -94,7 +97,7 @@ func (d *Difference) Write(f []byte) (int, error) { mean := imgDelta.Mean().Val1 - // Update History + // Update History. d.prev = img.Clone() // Draw debug information. @@ -118,7 +121,7 @@ func (d *Difference) Write(f []byte) (int, error) { // Don't write to destination if there is no motion. if mean < d.thresh { - return 0, nil + return len(f), nil } // Write to destination. From cae7e55723f7ce5ed032218354165f56e4354b4a Mon Sep 17 00:00:00 2001 From: Saxon Date: Mon, 27 Jan 2020 13:54:43 +1030 Subject: [PATCH 20/20] revid/revid.go: added handling for FileFPS var from vidgrind --- revid/revid.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/revid/revid.go b/revid/revid.go index 5e3a035d..5eb5b7bf 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -859,6 +859,13 @@ func (r *Revid) Update(vars map[string]string) error { break } r.cfg.MOGHistory = uint(v) + case "FileFPS": + v, err := strconv.Atoi(value) + if err != nil { + r.cfg.Logger.Log(logger.Warning, pkg+"invalid FileFPS var", "value", value) + break + } + r.cfg.FileFPS = v case "mode": r.cfg.Loop = false if value == "Loop" {