av/revid/config/config.go

538 lines
17 KiB
Go

/*
NAME
Config.go
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
Trek Hopton <trek@ausocean.org>
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
// 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.
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
}
// 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",
"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",
"MinFPS": "float",
"MinFrames": "uint",
"mode": "enum:Normal,Paused,Burst",
"MOGHistory": "uint",
"MOGMinArea": "float",
"MOGThreshold": "float",
"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.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
}
}
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
}