/* 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 // MTS ring buffer defaults. defaultMTSRBSize = 100 defaultMTSRBElementSize = 300000 defaultMTSRBWriteTimeout = 5 // RTMP ring buffer defaults. defaultRTMPRBSize = 100 defaultRTMPRBElementSize = 300000 defaultRTMPRBWriteTimeout = 5 ) // 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. PSITime int // Sets the time between a packet being sent. // RTMP ring buffer parameters. RTMPRBSize int // The number of elements in the RTMP sender ringbuffer. RTMPRBElementSize int // The element size in bytes of the RTMP sender RingBuffer. RTMPRBWriteTimeout int // The ringbuffer write timeout in seconds. // MTS ring buffer parameters. MTSRBSize int // The number of elements in the MTS sender ringbuffer. MTSRBElementSize int // The element size in bytes of the MTS sender RingBuffer. MTSRBWriteTimeout int // The ringbuffer write timeout in seconds. } // 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", "CBR": "bool", "ClipDuration": "uint", "Exposure": "enum:auto,night,nightpreview,backlight,spotlight,sports,snow,beach,verylong,fixedfps,antishake,fireworks", "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", "logging": "enum:Debug,Info,Warning,Error,Fatal", "MinFrames": "uint", "MTSRBElementSize": "int", "MTSRBSize": "int", "MTSRBWriteTimeout": "int", "OutputPath": "string", "Output": "enum:File,Http,Rtmp,Rtp", "Outputs": "enums:File,Http,Rtmp,Rtp", "Quantization": "uint", "Rotation": "uint", "RTMPRBElementSize": "int", "RTMPRBSize": "int", "RTMPRBWriteTimeout": "int", "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.RTMPRBSize <= 0 { c.Logger.Log(logger.Info, pkg+"RTMPRBSize bad or unset, defaulting", "RTMPRBSize", defaultRTMPRBSize) c.RTMPRBSize = defaultRTMPRBSize } if c.RTMPRBElementSize <= 0 { c.Logger.Log(logger.Info, pkg+"RTMPRBElementSize bad or unset, defaulting", "RTMPRBElementSize", defaultRTMPRBElementSize) c.RTMPRBElementSize = defaultRTMPRBElementSize } if c.RTMPRBWriteTimeout <= 0 { c.Logger.Log(logger.Info, pkg+"RTMPRBWriteTimeout bad or unset, defaulting", "RTMPRBWriteTimeout", defaultRTMPRBWriteTimeout) c.RTMPRBWriteTimeout = defaultRTMPRBWriteTimeout } if c.MTSRBSize <= 0 { c.Logger.Log(logger.Info, pkg+"MTSRBSize bad or unset, defaulting", "MTSRBSize", defaultMTSRBSize) c.MTSRBSize = defaultMTSRBSize } if c.MTSRBElementSize <= 0 { c.Logger.Log(logger.Info, pkg+"MTSRBElementSize bad or unset, defaulting", "MTSRBElementSize", defaultMTSRBElementSize) c.MTSRBElementSize = defaultMTSRBElementSize } if c.MTSRBWriteTimeout <= 0 { c.Logger.Log(logger.Info, pkg+"MTSRBWriteTimeout bad or unset, defaulting", "MTSRBWriteTimeout", defaultMTSRBWriteTimeout) c.MTSRBWriteTimeout = defaultMTSRBWriteTimeout } if c.PSITime <= 0 { c.Logger.Log(logger.Info, pkg+"PSITime bad or unset, defaulting", "PSITime", defaultPSITime) c.PSITime = defaultPSITime } 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 } } 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 }