/* 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" "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 ( defaultInput = Raspivid defaultOutput = HTTP defaultFrameRate = 25 defaultWriteRate = 25 defaultWidth = 1280 defaultHeight = 720 defaultIntraRefreshPeriod = 100 defaultTimeout = 0 defaultQuantization = 40 defaultBitrate = 400000 defaultFramesPerClip = 1 httpFramesPerClip = 560 defaultInputCodec = codecutil.H264 defaultVerbosity = logger.Error defaultRtpAddr = "localhost:6970" defaultBurstPeriod = 10 // Seconds defaultRotation = 0 // Degrees defaultBrightness = 50 defaultExposure = "auto" defaultAutoWhiteBalance = "auto" defaultAudioInputCodec = codecutil.ADPCM defaultSampleRate = 48000 defaultBitDepth = 16 defaultChannels = 1 defaultRecPeriod = 1.0 ) // 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 // Quantize specifies whether the input to revid will have constant or variable // bitrate, if configurable with the chosen input. Raspivid supports quantization. Quantize bool // FramesPerClip defines the number of packetization units to pack into a clip // per HTTP send. FramesPerClip uint // 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 // IntraRefreshPeriod defines the frequency of video parameter NAL units for // Raspivid input. IntraRefreshPeriod uint // 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. 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. } // Validate checks for any errors in the config fields and defaults settings // if particular parameters have not been defined. func (c *Config) Validate(r *Revid) 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: // FIXME(kortschak): This is not really what we want. // Configuration really needs to be rethought here. if c.Quantize && c.Quantization == 0 { c.Quantization = defaultQuantization } if (c.Bitrate > 0 && c.Quantize) || (c.Bitrate == 0 && !c.Quantize) { return errors.New("bad bitrate and quantization combination for H264 input") } case codecutil.MJPEG: if c.Quantization > 0 || c.Bitrate == 0 { return errors.New("bad bitrate or quantization for mjpeg input") } case 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) } else { for i, o := range c.Outputs { switch o { case File: case RTMP: if c.RTMPURL == "" { c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") c.Outputs[i] = HTTP // FIXME(kortschak): Does this want the same line as below? // c.FramesPerClip = httpFramesPerClip break } c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for rtmp out", "framesPerClip", defaultFramesPerClip) c.FramesPerClip = defaultFramesPerClip case HTTP, RTP: c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out", "framesPerClip", httpFramesPerClip) c.FramesPerClip = httpFramesPerClip 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.FramesPerClip < 1 { c.Logger.Log(logger.Info, pkg+"no FramesPerClip defined, defaulting", "framesPerClip", defaultFramesPerClip) c.FramesPerClip = defaultFramesPerClip } 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 { c.Logger.Log(logger.Info, pkg+"no bitrate defined, defaulting", "bitrate", defaultBitrate) c.Bitrate = defaultBitrate } if c.IntraRefreshPeriod == 0 { c.Logger.Log(logger.Info, pkg+"no intra refresh defined, defaulting", "intraRefresh", defaultIntraRefreshPeriod) c.IntraRefreshPeriod = defaultIntraRefreshPeriod } if c.Quantization == 0 { c.Logger.Log(logger.Info, pkg+"no quantization defined, defaulting", "quantization", defaultQuantization) c.Quantization = defaultQuantization } else if c.Quantization > 51 { return errors.New("quantisation is over threshold") } 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") } 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 }