av/revid/config.go

378 lines
10 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 revid
import (
"errors"
"bitbucket.org/ausocean/utils/logger"
)
// Config provides parameters relevant to a revid instance. A new config must
// be passed to the constructor.
type Config struct {
Logger Logger
LogLevel int8
// IO
Input uint8
InputCodec uint8
Outputs []uint8
RtmpMethod uint8
Packetization uint8
Quantize bool // Determines whether input to revid will have constant or variable bitrate.
RtmpUrl string
RTSPURL string
Bitrate uint
OutputPath string
InputPath string
HttpAddress string
Quantization uint
IntraRefreshPeriod uint
RtpAddress string
SendRetry bool
WriteRate float64 // How many times a second revid encoders will be written to.
// Video
Height uint
Width uint
FrameRate uint
FramesPerClip uint
BurstPeriod uint
// Transformation
FlipHorizontal bool
FlipVertical bool
Rotation uint
// Color correction
Brightness uint
Saturation int
Exposure string
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.
}
// 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 for config struct
const (
NothingDefined = iota
Raspivid
V4L
H264Codec
Audio
File
Http
H264
Mjpeg
PCM
ADPCM
None
Mpegts
Ffmpeg
Flv
LibRtmp
QuantizationOn
QuantizationOff
Yes
No
Rtmp
FfmpegRtmp
Udp
MpegtsRtp
Rtp
RTSP
)
// Default config settings
const (
defaultInput = Raspivid
defaultOutput = Http
defaultPacketization = Flv
defaultFrameRate = 25
defaultWriteRate = 25
defaultWidth = 1280
defaultHeight = 720
defaultIntraRefreshPeriod = 100
defaultTimeout = 0
defaultQuantization = 40
defaultBitrate = 400000
defaultQuantizationMode = QuantizationOff
defaultFramesPerClip = 1
httpFramesPerClip = 560
defaultInputCodec = H264
defaultVerbosity = logger.Error
defaultRtpAddr = "localhost:6970"
defaultBurstPeriod = 10 // Seconds
defaultRotation = 0 // Degrees
defaultBrightness = 50
defaultExposure = "auto"
defaultAutoWhiteBalance = "auto"
defaultAudioInputCodec = ADPCM
defaultSampleRate = 48000
defaultBitDepth = 16
defaultChannels = 1
defaultRecPeriod = 1.0
)
// 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 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 Mjpeg:
if c.Quantization > 0 || c.Bitrate == 0 {
return errors.New("bad bitrate or quantization for mjpeg input")
}
case PCM, ADPCM:
case NothingDefined:
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
}
default:
return errors.New("bad input codec defined in config")
}
if c.Outputs == nil {
c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput)
c.Outputs = append(c.Outputs, defaultOutput)
c.Packetization = defaultPacketization
} else {
for i, o := range c.Outputs {
switch o {
case File:
case Udp:
case Rtmp, FfmpegRtmp:
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
c.Packetization = Flv
c.SendRetry = true
case Http, Rtp:
c.Logger.Log(logger.Info, pkg+"defaulting frames per clip for http out", "framesPerClip", httpFramesPerClip)
c.FramesPerClip = httpFramesPerClip
c.Packetization = Mpegts
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
}