/* NAME Config.go AUTHORS Saxon A. Nelson-Milton Trek Hopton LICENSE Config.go 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 config import ( "errors" "fmt" "os/exec" "time" "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/utils/logger" ) const pkg = "config: " type Logger interface { SetLevel(int8) Log(level int8, message string, params ...interface{}) } // Enums to define inputs, outputs and codecs. const ( // Indicates no option has been set. NothingDefined = iota // Input/Output. InputFile InputRaspivid InputV4L InputRTSP InputAudio // Outputs. OutputAudio OutputRTMP OutputRTP OutputHTTP OutputMPEGTS OutputFile // Codecs. H264 H265 MJPEG ) // Default config settings const ( // General revid defaults. defaultInput = InputRaspivid defaultOutput = OutputHTTP defaultTimeout = 0 defaultInputCodec = codecutil.H264 defaultVerbosity = logger.Error defaultRtpAddr = "localhost:6970" defaultCameraIP = "192.168.1.50" defaultBurstPeriod = 10 // Seconds defaultMinFrames = 100 defaultFrameRate = 25 defaultWriteRate = 25 defaultClipDuration = 0 defaultAudioInputCodec = codecutil.ADPCM defaultPSITime = 2 defaultMotionInterval = 5 defaultFileFPS = 0 // Ring buffer defaults. defaultRBMaxElements = 10000 defaultRBCapacity = 200000000 // bytes (200MB) defaultRBWriteTimeout = 5 // Motion filter parameter defaults. defaultMinFPS = 1.0 // KNN filter parameter defaults. defaultKNNMinArea = 25.0 defaultKNNThreshold = 300 defaultKNNHistory = 300 defaultKNNKernel = 9 // MOG filter parameter defaults. defaultMOGMinArea = 25.0 defaultMOGThreshold = 20.0 defaultMOGHistory = 500 ) // Quality represents video quality. 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 ) // The different media filters. const ( FilterNoOp = iota FilterMOG FilterVariableFPS FilterKNN ) // OS names const raspian = "Raspbian GNU/Linux" // Config provides parameters relevant to a revid instance. A new config must // be passed to the constructor. Default values for these fields are defined // as consts above. type Config struct { // LogLevel is the revid logging verbosity level. // Valid values are defined by enums from the logger package: logger.Debug, // logger.Info, logger.Warning logger.Error, logger.Fatal. LogLevel int8 // Input defines the input data source. // // Valid values are defined by enums: // InputRaspivid: // Read data from a Raspberry Pi Camera. // InputV4l: // Read from webcam. // InputFile: // Location must be specified in InputPath field. // InputRTSP: // CameraIP should also be defined. Input uint8 // InputCodec defines the input codec we wish to use, and therefore defines the // lexer for use in the pipeline. This defaults to H264, but H265 is also a // valid option if we expect this from the input. InputCodec uint8 // Outputs define the outputs we wish to output data too. // // Valid outputs are defined by enums: // OutputFile: // Location must be defined by the OutputPath field. MPEG-TS packetization // is used. // OutputHTTP: // Destination is defined by the sh field located in /etc/netsender.conf. // MPEGT-TS packetization is used. // OutputRTMP: // Destination URL must be defined in the RtmpUrl field. FLV packetization // is used. // OutputRTP: // Destination is defined by RtpAddr field, otherwise it will default to // localhost:6970. MPEGT-TS packetization is used. Outputs []uint8 // RTMPURL specifies the Rtmp output destination URL. This must be defined if // RTMP is to be used as an output. RTMPURL string // CameraIP is the IP address of the camera in the case of the input camera // being an IP camera. CameraIP string // OutputPath defines the output destination for File output. This must be // defined if File output is to be used. OutputPath string // InputPath defines the input file location for File Input. This must be // defined if File input is to be used. InputPath string // FrameRate defines the input frame rate if configurable by the chosen input. // Raspivid input supports custom framerate. FrameRate uint // WriteRate is how many times a second revid encoders will be written to. WriteRate float64 // HTTPAddress defines a custom HTTP destination if we do not wish to use that // defined in /etc/netsender.conf. HTTPAddress string // CBR indicates whether we wish to use constant or variable bitrate. If CBR // is true then we will use constant bitrate, and variable bitrate otherwise. // In the case of the Pi camera, variable bitrate quality is controlled by // the Quantization parameter below. In the case of the GeoVision camera, // variable bitrate quality is controlled by firstly the VBRQuality parameter // and second the VBRBitrate parameter. CBR bool // Quantization defines the quantization level, which will determine variable // bitrate quality in the case of input from the Pi Camera. Quantization uint // 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 // VBRBitrate describes maximal bitrate for the GeoVision camera when under // variable bitrate. VBRBitrate int // This is the channel we're using for the GeoVision camera. CameraChan int // MinFrames defines the frequency of key NAL units SPS, PPS and IDR in // number of NAL units. This will also determine the frequency of PSI if the // output container is MPEG-TS. If ClipDuration is less than MinFrames, // ClipDuration will default to MinFrames. MinFrames uint // ClipDuration is the duration of MTS data that is sent using HTTP or RTP // output. This defaults to 0, therefore MinFrames will determine the length of // clips by default. ClipDuration time.Duration // Logger holds an implementation of the Logger interface as defined in revid.go. // This must be set for revid to work correctly. Logger Logger // Brightness and saturation define the brightness and saturation levels for // Raspivid input. Brightness uint Saturation int // Exposure defines the exposure mode used by the Raspivid input. Valid modes // are defined in the exported []string ExposureModes defined at the start // of the file. Exposure string // ShowWindows enables or disables the display of windows used for debugging // motion filters. ShowWindows bool // AutoWhiteBalance defines the auto white balance mode used by Raspivid input. // Valid modes are defined in the exported []string AutoWhiteBalanceModes // defined at the start of the file. AutoWhiteBalance string // Audio SampleRate int // Samples a second (Hz). RecPeriod float64 // How many seconds to record at a time. Channels int // Number of audio channels, 1 for mono, 2 for stereo. BitDepth int // Sample bit depth. RTPAddress string // RTPAddress defines the RTP output destination. BurstPeriod uint // BurstPeriod defines the revid burst period in seconds. Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input. Height uint // Height defines the input video height Raspivid input. Width uint // Width defines the input video width Raspivid input. Bitrate uint // Bitrate specifies the bitrate for constant bitrate in kbps. HorizontalFlip bool // HorizontalFlip flips video horizontally for Raspivid input. VerticalFlip bool // VerticalFlip flips video vertically for Raspivid input. Filters []int // Defines the methods of filtering to be used in between lexing and encoding. MotionInterval int // Sets the number of frames that are held before the filter is used (on the nth frame) PSITime int // Sets the time between a packet being sent. // Ring buffer parameters. RBMaxElements int // The maximum possible number of elements in ring buffer. RBCapacity int // The total number of bytes available for the ring buffer. RBWriteTimeout int // The ringbuffer write timeout in seconds. // Motion filter parameters. MinFPS float64 // The reduced framerate of the video when there is no motion. // KNN filter parameters. KNNMinArea float64 // Used to ignore small areas of motion detection. KNNThreshold float64 // Intensity value from the KNN motion detection algorithm that is considered motion. KNNHistory uint // Length of KNN filter's history KNNKernel uint // Size of kernel used for filling holes and removing noise. // MOG filter parameters. 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 // Defines the rate at which frames from a file source are processed. FileFPS int } // TypeData contains information about all of the variables that // can be set over the web. It is a psuedo const. var TypeData = map[string]string{ "AutoWhiteBalance": "enum:off,auto,sun,cloud,shade,tungsten,fluorescent,incandescent,flash,horizon", "BitDepth": "int", "Brightness": "uint", "BurstPeriod": "uint", "CameraChan": "int", "CameraIP": "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", "HorizontalFlip": "bool", "HTTPAddress": "string", "Input": "enum:raspivid,rtsp,v4l,file", "InputCodec": "enum:H264,MJPEG", "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,Loop", "MOGHistory": "uint", "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", "Rotation": "uint", "RTMPURL": "string", "RTPAddress": "string", "Saturation": "int", "ShowWindows": "bool", "VBRBitrate": "int", "VBRQuality": "enum:standard,fair,good,great,excellent", "VerticalFlip": "bool", "Width": "uint", } // Validation errors. var ( errInvalidQuantization = errors.New("invalid quantization") ) // Validate checks for any errors in the config fields and defaults settings // if particular parameters have not been defined. func (c *Config) Validate() error { switch c.LogLevel { case logger.Debug, logger.Info, logger.Warning, logger.Error, logger.Fatal: default: c.LogLevel = defaultVerbosity c.Logger.Log(logger.Info, pkg+"bad LogLevel mode defined, defaulting", "LogLevel", defaultVerbosity) } if c.CameraIP == "" { c.Logger.Log(logger.Info, pkg+"no CameraIP defined, defaulting", "CameraIP", defaultCameraIP) c.CameraIP = defaultCameraIP } switch c.Input { case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP: case NothingDefined: c.Logger.Log(logger.Info, pkg+"no input type defined, defaulting", "input", defaultInput) c.Input = defaultInput default: return errors.New("bad input type defined in config") } switch c.InputCodec { case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM: default: switch c.Input { case OutputAudio: c.Logger.Log(logger.Info, pkg+"input is audio but no codec defined, defaulting", "inputCodec", defaultAudioInputCodec) c.InputCodec = defaultAudioInputCodec default: c.Logger.Log(logger.Info, pkg+"no input codec defined, defaulting", "inputCodec", defaultInputCodec) c.InputCodec = defaultInputCodec } } if c.Outputs == nil { c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput) c.Outputs = append(c.Outputs, defaultOutput) } for i, o := range c.Outputs { switch o { case OutputFile, OutputHTTP, OutputRTP: case OutputRTMP: if c.RTMPURL == "" { c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") c.Outputs[i] = OutputHTTP } default: return errors.New("bad output type defined in config") } } if c.BurstPeriod == 0 { c.Logger.Log(logger.Info, pkg+"no burst period defined, defaulting", "burstPeriod", defaultBurstPeriod) c.BurstPeriod = defaultBurstPeriod } const maxMinFrames = 1000 switch { case c.MinFrames == 0: c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinFrames", defaultMinFrames) c.MinFrames = defaultMinFrames case c.MinFrames < 0: return errors.New("refresh period is less than 0") case c.MinFrames > maxMinFrames: return errors.New("refresh period is greater than 1000") } if c.FrameRate <= 0 { c.Logger.Log(logger.Info, pkg+"frame rate bad or unset, defaulting", "FrameRate", defaultFrameRate) c.FrameRate = defaultFrameRate } if c.WriteRate <= 0 { c.Logger.Log(logger.Info, pkg+"write rate bad or unset, defaulting", "writeRate", defaultWriteRate) c.WriteRate = defaultWriteRate } if c.ClipDuration == 0 { c.Logger.Log(logger.Info, pkg+"no clip duration defined, defaulting", "ClipDuration", defaultClipDuration) c.ClipDuration = defaultClipDuration } else if c.ClipDuration < 0 { return errors.New("clip duration is less than 0") } if c.RTPAddress == "" { c.RTPAddress = defaultRtpAddr } if c.RBMaxElements <= 0 { c.Logger.Log(logger.Info, pkg+"RBMaxElements bad or unset, defaulting", "RBMaxElements", defaultRBMaxElements) c.RBMaxElements = defaultRBMaxElements } if c.RBCapacity <= 0 { c.Logger.Log(logger.Info, pkg+"RBCapacity bad or unset, defaulting", "RBCapacity", defaultRBCapacity) c.RBCapacity = defaultRBCapacity } if c.RBWriteTimeout <= 0 { c.Logger.Log(logger.Info, pkg+"RBWriteTimeout bad or unset, defaulting", "RBWriteTimeout", defaultRBWriteTimeout) c.RBWriteTimeout = defaultRBWriteTimeout } if c.PSITime <= 0 { c.Logger.Log(logger.Info, pkg+"PSITime bad or unset, defaulting", "PSITime", defaultPSITime) c.PSITime = defaultPSITime } if c.MotionInterval <= 0 { c.Logger.Log(logger.Info, pkg+"MotionInterval bad or unset, defaulting", "MotionInterval", defaultMotionInterval) c.MotionInterval = defaultMotionInterval } if c.MinFPS <= 0 { c.Logger.Log(logger.Info, pkg+"MinFPS bad or unset, defaulting", "MinFPS", defaultMinFPS) c.MinFPS = defaultMinFPS } if c.KNNMinArea <= 0 { c.Logger.Log(logger.Info, pkg+"KNNMinArea bad or unset, defaulting", "KNNMinArea", defaultKNNMinArea) c.KNNMinArea = defaultKNNMinArea } if c.KNNThreshold <= 0 { c.Logger.Log(logger.Info, pkg+"KNNThreshold bad or unset, defaulting", "KNNThreshold", defaultKNNThreshold) c.KNNThreshold = defaultKNNThreshold } if c.KNNHistory == 0 { c.Logger.Log(logger.Info, pkg+"KNNHistory bad or unset, defaulting", "KNNHistory", defaultKNNHistory) c.KNNHistory = defaultKNNHistory } if c.KNNKernel <= 0 { c.Logger.Log(logger.Info, pkg+"KNNKernel bad or unset, defaulting", "KNNKernel", defaultKNNKernel) c.KNNKernel = defaultKNNKernel } if c.MOGMinArea <= 0 { c.Logger.Log(logger.Info, pkg+"MOGMinArea bad or unset, defaulting", "MOGMinArea", defaultMOGMinArea) c.MOGMinArea = defaultMOGMinArea } if c.MOGThreshold <= 0 { c.Logger.Log(logger.Info, pkg+"MOGThreshold bad or unset, defaulting", "MOGThreshold", defaultMOGThreshold) c.MOGThreshold = defaultMOGThreshold } if c.MOGHistory == 0 { c.Logger.Log(logger.Info, pkg+"MOGHistory bad or unset, defaulting", "MOGHistory", defaultMOGHistory) c.MOGHistory = defaultMOGHistory } if c.ShowWindows { os, err := osName() if err != nil { return err } if os == raspian { c.Logger.Log(logger.Info, "ShowWindows disabled on Raspbian GNU/Linux") c.ShowWindows = false } } 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 } func osName() (string, error) { out, err := exec.Command("grep", "^NAME=", "/etc/os-release").Output() if err != nil { return "", fmt.Errorf("could not get OS name: %w", err) } return string(out)[6 : len(out)-2], nil } // 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 }