av/revid/config.go

477 lines
14 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"
"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:
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)
}
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
}