/* 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 revid import ( "errors" "time" "bitbucket.org/ausocean/av/codec/codecutil" "bitbucket.org/ausocean/utils/logger" ) // Possible modes for raspivid --exposure parameter. var ExposureModes = [...]string{ "auto", "night", "nightpreview", "backlight", "spotlight", "sports", "snow", "beach", "verylong", "fixedfps", "antishake", "fireworks", } // Possible modes for raspivid --awb parameter. var AutoWhiteBalanceModes = [...]string{ "off", "auto", "sun", "cloud", "shade", "tungsten", "fluorescent", "incandescent", "flash", "horizon", } // Enums to define inputs, outputs and codecs. const ( // Indicates no option has been set. NothingDefined = iota // Input/Output. File // Inputs. Raspivid V4L RTSP Audio // Outputs. RTMP RTP HTTP MPEGTS // Codecs. H264 H265 MJPEG ) // Default config settings const ( // General revid defaults. defaultInput = Raspivid defaultOutput = HTTP defaultFrameRate = 25 defaultWriteRate = 25 defaultTimeout = 0 defaultInputCodec = codecutil.H264 defaultVerbosity = logger.Error defaultRtpAddr = "localhost:6970" defaultBurstPeriod = 10 // Seconds // Raspivid video defaults. defaultBrightness = 50 defaultExposure = "auto" defaultAutoWhiteBalance = "auto" defaultRotation = 0 // Degrees defaultWidth = 1280 defaultHeight = 720 defaultMinFrames = 100 defaultClipDuration = 0 defaultQuantization = 30 defaultBitrate = 400000 // Audio defaults. defaultAudioInputCodec = codecutil.ADPCM defaultSampleRate = 48000 defaultBitDepth = 16 defaultChannels = 1 defaultRecPeriod = 1.0 // Ringbuffer defaults. defaultMTSRBSize = 1000 defaultMTSRBElementSize = 100000 defaultRTMPRBSize = 500 defaultRTMPRBElementSize = 200000 ) // 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: // Raspivid: // Read data from a Raspberry Pi Camera. // V4l: // Read from webcam. // File: // Location must be specified in InputPath field. // RTSP: // RTSPURL must 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: // File: // Location must be defined by the OutputPath field. MPEG-TS packetization // is used. // HTTP: // Destination is defined by the sh field located in /etc/netsender.conf. // MPEGT-TS packetization is used. // RTMP: // Destination URL must be defined in the RtmpUrl field. FLV packetization // is used. // RTP: // 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 // RTSPURL specifies the RTSP server URL for RTSP input. This must be defined // when Input is RTSP. RTSPURL 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 // Quantization defines the quantization level, which may be a value between // 0-40. This will only take effect if the Quantize field is true and if we // are using Raspivid input. Quantization uint // 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 // 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. ChunkSize int // ChunkSize is the size of the chunks in the audio.Device's ringbuffer. 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 input bitrate for Raspivid input. FlipHorizontal bool // FlipHorizontal flips video horizontally for Raspivid input. FlipVertical bool // FlipVertial flips video vertically for Raspivid input. // Ring buffer sizes. RTMPRBSize int // The number of elements in the RTMP sender ringbuffer. RTMPRBElementSize int // The element size in bytes of the RTMP sender RingBuffer. MTSRBSize int // The number of elements in the MTS sender ringbuffer. MTSRBElementSize int // The element size in bytes of the MTS sender RingBuffer. } // 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: case logger.Info: case logger.Warning: case logger.Error: case logger.Fatal: default: c.LogLevel = defaultVerbosity c.Logger.Log(logger.Info, pkg+"bad LogLevel mode defined, defaulting", "LogLevel", defaultVerbosity) } switch c.Input { case Raspivid, V4L, File, Audio, RTSP: 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 Audio: 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 c.Logger.Log(logger.Info, pkg+"defaulting quantization", "quantization", defaultQuantization) c.Quantization = defaultQuantization } } if c.Outputs == nil { c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput) c.Outputs = append(c.Outputs, defaultOutput) } var haveRTMPOut bool for i, o := range c.Outputs { switch o { case File: case RTMP: haveRTMPOut = true if c.Bitrate == 0 { c.Bitrate = defaultBitrate } c.Quantization = 0 if c.RTMPURL == "" { c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") c.Outputs[i] = HTTP haveRTMPOut = false } fallthrough case HTTP, RTP: if !haveRTMPOut { c.Bitrate = 0 if c.Quantization == 0 { c.Quantization = defaultQuantization } } 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 } if c.Rotation > 359 { c.Logger.Log(logger.Warning, pkg+"bad rotate angle, defaulting", "angle", defaultRotation) c.Rotation = defaultRotation } if c.Width == 0 { c.Logger.Log(logger.Info, pkg+"no width defined, defaulting", "width", defaultWidth) c.Width = defaultWidth } if c.Height == 0 { c.Logger.Log(logger.Info, pkg+"no height defined, defaulting", "height", defaultHeight) c.Height = defaultHeight } if c.FrameRate == 0 { c.Logger.Log(logger.Info, pkg+"no frame rate defined, defaulting", "fps", defaultFrameRate) c.FrameRate = defaultFrameRate } if c.SampleRate == 0 { c.Logger.Log(logger.Info, pkg+"no sample rate defined, defaulting", "sampleRate", defaultSampleRate) c.SampleRate = defaultSampleRate } if c.Channels == 0 { c.Logger.Log(logger.Info, pkg+"no number of channels defined, defaulting", "Channels", defaultChannels) c.Channels = defaultChannels } if c.BitDepth == 0 { c.Logger.Log(logger.Info, pkg+"no bit depth defined, defaulting", "BitDepth", defaultBitDepth) c.BitDepth = defaultBitDepth } if c.RecPeriod == 0 { c.Logger.Log(logger.Info, pkg+"no record period defined, defaulting", "recPeriod", defaultRecPeriod) c.RecPeriod = defaultRecPeriod } if c.WriteRate == 0 { c.Logger.Log(logger.Info, pkg+"no write rate defined, defaulting", "writeRate", defaultWriteRate) c.WriteRate = defaultWriteRate } if c.Bitrate < 0 { return errors.New("invalid bitrate") } if c.MinFrames == 0 { c.Logger.Log(logger.Info, pkg+"no min period defined, defaulting", "MinFrames", defaultMinFrames) c.MinFrames = defaultMinFrames } else if c.MinFrames < 0 { return errors.New("refresh period is less than 0") } 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.Quantization != 0 && (c.Quantization < 10 || c.Quantization > 40) { return errInvalidQuantization } if c.RTPAddress == "" { c.RTPAddress = defaultRtpAddr } switch { case c.Brightness == 0: c.Logger.Log(logger.Info, pkg+"brightness undefined, defaulting", "brightness", defaultBrightness) c.Brightness = defaultBrightness case c.Brightness < 0 || c.Brightness > 100: return errors.New(pkg + "bad brightness defined in config") } if c.Saturation < -100 || c.Saturation > 100 { return errors.New(pkg + "bad saturation setting in config") } switch { case c.Exposure == "": c.Logger.Log(logger.Info, pkg+"exposure undefined, defaulting", "exposure", defaultExposure) c.Exposure = defaultExposure case !stringInSlice(c.Exposure, ExposureModes[:]): return errors.New(pkg + "bad exposure setting in config") } switch { case c.AutoWhiteBalance == "": c.Logger.Log(logger.Info, pkg+"auto white balance undefined, defaulting", "autoWhiteBalance", defaultAutoWhiteBalance) c.AutoWhiteBalance = defaultAutoWhiteBalance case !stringInSlice(c.AutoWhiteBalance, AutoWhiteBalanceModes[:]): return errors.New(pkg + "bad auto white balance setting in config") } 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.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 } return 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 }