From 924858c1c0527d3409f638063243fa00792a6a4c Mon Sep 17 00:00:00 2001 From: Saxon Date: Fri, 1 Nov 2019 21:45:46 +1030 Subject: [PATCH] revid: added raspivid.go file to hold Raspivid implementation of AVDevice interface Wrote consts for default values, wrote global errors, wrote multiError type (might move) wrote Set method. --- revid/config.go | 38 ++++++------ revid/config_test.go | 8 +-- revid/inputs.go | 2 +- revid/raspivid.go | 145 +++++++++++++++++++++++++++++++++++++++++++ revid/revid.go | 36 +++++------ revid/revid_test.go | 14 ++--- 6 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 revid/raspivid.go diff --git a/revid/config.go b/revid/config.go index 7be75475..619e8be6 100644 --- a/revid/config.go +++ b/revid/config.go @@ -82,19 +82,19 @@ const ( NothingDefined = iota // Input/Output. - File - - // Inputs. - Raspivid - V4L - RTSP - Audio + InputFile + InputRaspivid + InputV4L + InputRTSP + InputAudio // Outputs. - RTMP - RTP - HTTP - MPEGTS + OutputAudio + OutputRTMP + OutputRTP + OutputHTTP + OutputMPEGTS + OutputFile // Codecs. H264 @@ -105,8 +105,8 @@ const ( // Default config settings const ( // General revid defaults. - defaultInput = Raspivid - defaultOutput = HTTP + defaultInput = InputRaspivid + defaultOutput = OutputHTTP defaultFrameRate = 25 defaultWriteRate = 25 defaultTimeout = 0 @@ -327,7 +327,7 @@ func (c *Config) Validate() error { } switch c.Input { - case Raspivid, V4L, File, Audio, RTSP: + case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP: case NothingDefined: c.Logger.Log(logger.Info, pkg+"no input type defined, defaulting", "input", defaultInput) c.Input = defaultInput @@ -339,7 +339,7 @@ func (c *Config) Validate() error { case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM: default: switch c.Input { - case Audio: + case OutputAudio: c.Logger.Log(logger.Info, pkg+"input is audio but no codec defined, defaulting", "inputCodec", defaultAudioInputCodec) c.InputCodec = defaultAudioInputCodec default: @@ -358,8 +358,8 @@ func (c *Config) Validate() error { var haveRTMPOut bool for i, o := range c.Outputs { switch o { - case File: - case RTMP: + case OutputFile: + case OutputRTMP: haveRTMPOut = true if c.Bitrate == 0 { c.Bitrate = defaultBitrate @@ -367,11 +367,11 @@ func (c *Config) Validate() error { c.Quantization = 0 if c.RTMPURL == "" { c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") - c.Outputs[i] = HTTP + c.Outputs[i] = OutputHTTP haveRTMPOut = false } fallthrough - case HTTP, RTP: + case OutputHTTP, OutputRTP: if !haveRTMPOut { c.Bitrate = 0 if c.Quantization == 0 { diff --git a/revid/config_test.go b/revid/config_test.go index eb248c93..6f121078 100644 --- a/revid/config_test.go +++ b/revid/config_test.go @@ -41,7 +41,7 @@ func TestValidate(t *testing.T) { err error }{ { - in: Config{Outputs: []uint8{HTTP}, Logger: &dumbLogger{}}, + in: Config{Outputs: []uint8{OutputHTTP}, Logger: &dumbLogger{}}, check: func(c Config) error { if c.Bitrate != 0 { return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) @@ -53,7 +53,7 @@ func TestValidate(t *testing.T) { }, }, { - in: Config{Outputs: []uint8{RTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}}, + in: Config{Outputs: []uint8{OutputRTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}}, check: func(c Config) error { if c.Bitrate != defaultBitrate { return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate) @@ -65,12 +65,12 @@ func TestValidate(t *testing.T) { }, }, { - in: Config{Outputs: []uint8{HTTP}, Quantization: 50, Logger: &dumbLogger{}}, + in: Config{Outputs: []uint8{OutputHTTP}, Quantization: 50, Logger: &dumbLogger{}}, check: func(c Config) error { return nil }, err: errInvalidQuantization, }, { - in: Config{Outputs: []uint8{HTTP}, Quantization: 20, Logger: &dumbLogger{}}, + in: Config{Outputs: []uint8{OutputHTTP}, Quantization: 20, Logger: &dumbLogger{}}, check: func(c Config) error { if c.Bitrate != 0 { return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) diff --git a/revid/inputs.go b/revid/inputs.go index 66706e03..775cf8ce 100644 --- a/revid/inputs.go +++ b/revid/inputs.go @@ -55,9 +55,9 @@ const ( type AVDevice interface { io.Reader + Set(c Config) error Start() error Stop() error - Set() error } // startRaspivid sets up things for input from raspivid i.e. starts diff --git a/revid/raspivid.go b/revid/raspivid.go new file mode 100644 index 00000000..f467b727 --- /dev/null +++ b/revid/raspivid.go @@ -0,0 +1,145 @@ +/* +DESCRIPTION + raspivid.go provides an implementation of the AVDevice interface for raspivid. + +AUTHORS + Saxon A. Nelson-Milton + +LICENSE + Copyright (C) 2017-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 revid + +import ( + "errors" + "fmt" + + "bitbucket.org/ausocean/av/codec/codecutil" +) + +// Raspivid AVDevice configuration defaults. +const ( + raspividDefaultCodec = codecutil.H264 + raspividDefaultRotation = 0 + raspividDefaultWidth = 1280 + raspividDefaultHeight = 720 + raspividDefaultBrightness = 50 + raspividDefaultSaturation = 0 + raspividDefaultExposure = "auto" + raspividDefaultAutoWhiteBalance = "auto" + raspividDefaultMinFrames = 100 + raspividDefaultQuantization = 30 + raspividDefaultBitrate = 48000 + raspividDefaultFramerate = 25 +) + +type Raspivid struct { + c Config +} + +var ( + errBadCodec = errors.New("codec bad or unset, defaulting") + errBadRotation = errors.New("rotation bad or unset, defaulting") + errBadWidth = errors.New("width bad or unset, defaulting") + errBadHeight = errors.New("height bad or unset, defaulting") + errBadFrameRate = errors.New("framerate bad or unset, defaulting") + errBadBitrate = errors.New("bitrate bad or unset, defaulting") + errBadMinFrames = errors.New("min frames bad or unset, defaulting") + errBadSaturation = errors.New("saturation bad or unset, defaulting") + errBadBrightness = errors.New("brightness bad or unset, defaulting") + errBadExposure = errors.New("exposure bad or unset, defaulting") + errBadAutoWhiteBalance = errors.New("auto white balance bad or unset, defaulting") + errBadQuantization = errors.New("quantization bad or unset, defaulting") +) + +type multiError []error + +func (me multiError) Error() string { + return fmt.Sprintf("%v", me) +} + +func (r *Raspivid) Set(c Config) error { + var errs []error + switch c.InputCodec { + case codecutil.H264, codecutil.MJPEG: + default: + c.InputCodec = raspividDefaultCodec + errs = append(errs, errBadCodec) + } + + if c.Rotation > 359 { + c.Rotation = raspividDefaultRotation + errs = append(errs, errBadRotation) + } + + if c.Width == 0 { + c.Width = raspividDefaultWidth + errs = append(errs, errBadWidth) + } + + if c.Height == 0 { + c.Height = raspividDefaultHeight + errs = append(errs, errBadHeight) + } + + if c.FrameRate == 0 { + c.FrameRate = raspividDefaultFramerate + errs = append(errs, errBadFrameRate) + } + + if c.VBR { + c.Bitrate = 0 + if c.Quantization < 10 || c.Quantization > 40 { + errs = append(errs, errBadQuantization) + c.Quantization = raspividDefaultQuantization + } + } else { + c.Quantization = 0 + if c.Bitrate <= 0 { + errs = append(errs, errBadBitrate) + c.Bitrate = raspividDefaultBitrate + } + } + + if c.MinFrames <= 0 { + errs = append(errs, errBadMinFrames) + c.MinFrames = raspividDefaultMinFrames + } + + if c.Brightness <= 0 || c.Brightness > 100 { + errs = append(errs, errBadBrightness) + c.Brightness = raspividDefaultBrightness + } + + if c.Saturation < -100 || c.Saturation > 100 { + errs = append(errs, errBadSaturation) + c.Saturation = raspividDefaultSaturation + } + + if c.Exposure == "" || !stringInSlice(c.Exposure, ExposureModes[:]) { + errs = append(errs, errBadExposure) + c.Exposure = raspividDefaultExposure + } + + if c.AutoWhiteBalance == "" || !stringInSlice(c.AutoWhiteBalance, AutoWhiteBalanceModes[:]) { + errs = append(errs, errBadAutoWhiteBalance) + c.AutoWhiteBalance = raspividDefaultAutoWhiteBalance + } + + r.c = c + return multiError(errs) +} diff --git a/revid/revid.go b/revid/revid.go index 0fbf2fe6..fd6239d3 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -168,7 +168,7 @@ func (r *Revid) reset(config Config) error { var encOptions []func(*mts.Encoder) error switch r.config.Input { - case Raspivid: + case InputRaspivid: switch r.config.InputCodec { case codecutil.H264: st = mts.EncodeH264 @@ -178,9 +178,9 @@ func (r *Revid) reset(config Config) error { default: panic("unknown input codec for raspivid input") } - case File, V4L: + case InputFile, InputV4L: st = mts.EncodeH264 - case RTSP: + case InputRTSP: switch r.config.InputCodec { case codecutil.H265: st = mts.EncodeH265 @@ -192,7 +192,7 @@ func (r *Revid) reset(config Config) error { default: panic("unknown input codec for RTSP input") } - case Audio: + case InputAudio: st = mts.EncodeAudio default: panic("unknown input type") @@ -246,7 +246,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. var w io.WriteCloser for _, out := range r.config.Outputs { switch out { - case HTTP: + case OutputHTTP: w = newMtsSender( newHttpSender(r.ns, r.config.Logger.Log), r.config.Logger.Log, @@ -254,19 +254,19 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.config.ClipDuration, ) mtsSenders = append(mtsSenders, w) - case RTP: + case OutputRTP: w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) if err != nil { r.config.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error()) } mtsSenders = append(mtsSenders, w) - case File: + case OutputFile: w, err := newFileSender(r.config.OutputPath) if err != nil { return err } mtsSenders = append(mtsSenders, w) - case RTMP: + case OutputRTMP: w, err := newRtmpSender( r.config.RTMPURL, rtmpConnectionTimeout, @@ -305,7 +305,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. r.encoders = multiWriter(encoders...) switch r.config.Input { - case Raspivid: + case InputRaspivid: r.setupInput = r.startRaspivid switch r.config.InputCodec { case codecutil.H264: @@ -313,12 +313,12 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. case codecutil.MJPEG: r.lexTo = mjpeg.Lex } - case V4L: + case InputV4L: r.setupInput = r.startV4L r.lexTo = h264.Lex - case File: + case InputFile: r.setupInput = r.setupInputForFile - case RTSP: + case InputRTSP: r.setupInput = r.startRTSPCamera switch r.config.InputCodec { case codecutil.H264: @@ -328,7 +328,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io. case codecutil.MJPEG: panic("not implemented") } - case Audio: + case InputAudio: r.setupInput = r.startAudioDevice r.lexTo = codecutil.NewByteLexer(&r.config.ChunkSize).Lex } @@ -423,7 +423,7 @@ func (r *Revid) Update(vars map[string]string) error { for key, value := range vars { switch key { case "Input": - v, ok := map[string]uint8{"raspivid": Raspivid, "rtsp": RTSP}[strings.ToLower(value)] + v, ok := map[string]uint8{"raspivid": InputRaspivid, "rtsp": InputRTSP}[strings.ToLower(value)] if !ok { r.config.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value) break @@ -463,13 +463,13 @@ func (r *Revid) Update(vars map[string]string) error { for i, output := range outputs { switch output { case "File": - r.config.Outputs[i] = File + r.config.Outputs[i] = OutputFile case "Http": - r.config.Outputs[i] = HTTP + r.config.Outputs[i] = OutputHTTP case "Rtmp": - r.config.Outputs[i] = RTMP + r.config.Outputs[i] = OutputRTMP case "Rtp": - r.config.Outputs[i] = RTP + r.config.Outputs[i] = OutputRTP default: r.config.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value) continue diff --git a/revid/revid_test.go b/revid/revid_test.go index 8ab2e62f..a7e91323 100644 --- a/revid/revid_test.go +++ b/revid/revid_test.go @@ -58,7 +58,7 @@ func TestRaspivid(t *testing.T) { var c Config c.Logger = &logger - c.Input = Raspivid + c.Input = InputRaspivid rv, err := New(c, ns) if err != nil { @@ -148,7 +148,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { encoders []encoder }{ { - outputs: []uint8{HTTP}, + outputs: []uint8{OutputHTTP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -157,7 +157,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{RTMP}, + outputs: []uint8{OutputRTMP}, encoders: []encoder{ { encoderType: flvEncoderStr, @@ -166,7 +166,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{RTP}, + outputs: []uint8{OutputRTP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -175,7 +175,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{HTTP, RTMP}, + outputs: []uint8{OutputHTTP, OutputRTMP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -188,7 +188,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{HTTP, RTP, RTMP}, + outputs: []uint8{OutputHTTP, OutputRTP, OutputRTMP}, encoders: []encoder{ { encoderType: mtsEncoderStr, @@ -201,7 +201,7 @@ func TestResetEncoderSenderSetup(t *testing.T) { }, }, { - outputs: []uint8{RTP, RTMP}, + outputs: []uint8{OutputRTP, OutputRTMP}, encoders: []encoder{ { encoderType: mtsEncoderStr,