device: add raspistill package housing release and testing implementations

This commit is contained in:
Saxon Nelson-Milton 2021-01-20 11:49:01 +10:30
parent 60187c797d
commit 45c019a062
9 changed files with 643 additions and 101 deletions

View File

@ -98,7 +98,7 @@ func Lex(dst io.Writer, src io.Reader, delay time.Duration) error {
if nImg == 0 { if nImg == 0 {
<-tick <-tick
Log.Debug("writing buf","len(buf)",len(buf)) Log.Debug("writing buf", "len(buf)", len(buf))
_, err = dst.Write(buf) _, err = dst.Write(buf)
if err != nil { if err != nil {
return err return err

View File

@ -0,0 +1,137 @@
// +build !test
/*
DESCRIPTION
release.go provides implementations for the Raspistill struct for release
conditions, i.e. we're running on a raspberry pi with access to the actual
raspistill utility with a pi camera connected. The code here runs a raspistill
background process and reads captured images from the camera.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2019 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
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package raspistill
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os/exec"
"strings"
"bitbucket.org/ausocean/av/revid/config"
)
type raspistill struct {
cfg config.Config
cmd *exec.Cmd
out io.ReadCloser
log config.Logger
done chan struct{}
isRunning bool
}
func new(l config.Logger) raspistill {
return raspistill{
log: l,
done: make(chan struct{}),
}
}
func (r *Raspistill) stop() error {
if r.isRunning == false {
return nil
}
close(r.done)
if r.cmd == nil || r.cmd.Process == nil {
return errors.New("raspistill process was never started")
}
err := r.cmd.Process.Kill()
if err != nil {
return fmt.Errorf("could not kill raspistill process: %w", err)
}
r.isRunning = false
return r.out.Close()
}
func (r *Raspistill) start() error {
const disabled = "0"
args := []string{
"--output", "-",
"--nopreview",
"--width", fmt.Sprint(r.cfg.Width),
"--height", fmt.Sprint(r.cfg.Height),
"--rotation", fmt.Sprint(r.cfg.Rotation),
"--timeout", fmt.Sprint(r.cfg.TimelapseDuration),
"--timelapse", fmt.Sprint(r.cfg.TimelapseInterval),
"--quality", fmt.Sprint(r.cfg.SnapQuality),
}
r.log.Info(pkg+"raspistill args", "args", strings.Join(args, " "))
r.cmd = exec.Command("raspistill", args...)
var err error
r.out, err = r.cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("could not pipe command output: %w", err)
}
stderr, err := r.cmd.StderrPipe()
if err != nil {
return fmt.Errorf("could not pipe command error: %w", err)
}
go func() {
for {
select {
case <-r.done:
r.log.Info("raspistill.Stop() called, finished checking stderr")
return
default:
buf, err := ioutil.ReadAll(stderr)
if err != nil {
r.log.Error("could not read stderr", "error", err)
return
}
if len(buf) != 0 {
r.log.Error("error from raspistill stderr", "error", string(buf))
return
}
}
}
}()
err = r.cmd.Start()
if err != nil {
return fmt.Errorf("could not start raspistill process: %w", err)
}
r.isRunning = true
return nil
}
func (r *Raspistill) read(p []byte) (int, error) {
if r.out != nil {
return r.out.Read(p)
}
return 0, errNotStarted
}

View File

@ -0,0 +1,181 @@
// +build test
/*
DESCRIPTION
test.go provides test implementations of the raspistill methods when the
"test" build tag is specified. In this mode, raspistill simply provides
arbitrary loaded JPEG images when Raspistill.Read() is called.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2019 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
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package raspistill
import (
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"sync"
"time"
"bitbucket.org/ausocean/av/revid/config"
)
const (
noOfImages = 6
imgDir = "/go/src/bitbucket.org/ausocean/test/test-data/av/input/jpeg/"
jpgExt = ".jpg"
)
type raspistill struct {
log config.Logger
cfg config.Config
isRunning bool
images [noOfImages][]byte
imgCnt int
durTicker *time.Ticker
intvlTicker *time.Ticker
buf []byte
init bool
term chan struct{}
mu sync.Mutex
}
func new(l config.Logger) raspistill {
l.Debug("creating new test raspistill input")
r := raspistill{log: l}
// Get to the right file location of the JPEG images.
home, err := os.UserHomeDir()
if err != nil {
panic(fmt.Sprintf("could not get home directory: %v", err))
}
// Load the test images into the images slice.
// We expect the 6 images test images to be named 0.jpg through to 5.jpg.
r.log.Debug("loading test JPEG images")
for i, _ := range r.images {
path := imgDir + strconv.Itoa(i) + jpgExt
r.images[i], err = ioutil.ReadFile(home + imgDir + strconv.Itoa(i) + jpgExt)
if err != nil {
r.log.Fatal("error loading test image", "imageNum", i, "error", err)
}
r.log.Debug("image loaded", "path/name", path, "size", len(r.images[i]))
}
return r
}
// stop sets isRunning flag to false, indicating no further captures. Calls on
// Raspistill.read return error.
func (r *Raspistill) stop() error {
r.log.Debug("stopping test raspistill")
r.isRunning = false
return nil
}
// start creates the timelapse interval and duration tickers i.e. also starting
// them and sets isRunning flag to true indicating that raspistill is capturing.
func (r *Raspistill) start() error {
r.log.Debug("starting test raspistill")
r.durTicker = time.NewTicker(r.cfg.TimelapseDuration)
r.intvlTicker = time.NewTicker(r.cfg.TimelapseInterval)
r.term = make(chan struct{})
r.loadImg()
go r.capture()
r.setRunning(true)
return nil
}
func (r *Raspistill) loadImg() {
r.log.Debug("appending new image on to buffer and copying next image p", "imgCnt", r.imgCnt)
imgBytes := r.images[r.imgCnt%noOfImages]
if len(imgBytes) == 0 {
panic("length of image bytes should not be 0")
}
r.imgCnt++
r.mu.Lock()
r.buf = append(r.buf, imgBytes...)
r.log.Debug("added img to buf", "len(imgBytes)", len(imgBytes))
r.mu.Unlock()
}
func (r *Raspistill) capture() {
for {
select {
case t := <-r.intvlTicker.C:
r.log.Debug("got interval tick", "tick", t)
r.loadImg()
r.intvlTicker.Reset(r.cfg.TimelapseInterval)
case <-r.term:
r.setRunning(false)
return
case t := <-r.durTicker.C:
r.log.Debug("got duration tick, timelapse over", "tick", t)
r.buf = nil
return
}
}
}
// read blocks until either another timelapse interval has completed, in which
// case we provide the next jpeg to p, or, the timelapse duration has completed
// in which case we don't read and provide io.EOF error.
func (r *Raspistill) read(p []byte) (int, error) {
r.log.Debug("reading from test raspistill")
if !r.running() {
return 0, errNotStarted
}
r.mu.Lock()
defer r.mu.Unlock()
if r.buf == nil {
return 0, io.EOF
}
n := copy(p, r.buf)
r.log.Debug("copied", "p[:2]", p[:2], "p[n-2:n]", p[n-2:n])
r.buf = r.buf[n:]
return n, nil
}
func (r *Raspistill) setRunning(s bool) {
r.mu.Lock()
r.isRunning = s
r.mu.Unlock()
}
func (r *Raspistill) running() bool {
r.mu.Lock()
ir := r.isRunning
r.mu.Unlock()
return ir
}

View File

@ -0,0 +1,139 @@
/*
DESCRIPTION
raspistill.go provides an implementation of the AVDevice interface for the
raspistill raspberry pi camera interfacing utility. This allows for the
capture of single frames over time, i.e. a timelapse form of capture.
AUTHORS
Saxon Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2019 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
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
// Package raspistill rovides an implementation of the AVDevice interface for the
// raspistill raspberry pi camera interfacing utility. This allows for the
// capture of single frames over time, i.e. a timelapse form of capture.
package raspistill
import (
"errors"
"fmt"
"time"
"bitbucket.org/ausocean/av/device"
"bitbucket.org/ausocean/av/revid/config"
)
// To indicate package when logging.
const pkg = "raspistill: "
// Config field validation bounds.
const (
minTimelapseDuration = 30 * time.Second // s
maxTimelapseDuration = 86400 * time.Second // s = 24 hours
minTimelapseInterval = 1 * time.Second // s
maxTimelapseInterval = 86400 * time.Second // s = 24 hours
)
// Raspivid configuration defaults.
const (
defaultRotation = 0 // degrees
defaultWidth = 1280 // pixels
defaultHeight = 720 // pixels
defaultSnapQuality = 75 // %
defaultTimelapseDuration = maxTimelapseDuration // ms
defaultTimelapseInterval = 3600 * time.Second // ms
)
// Configuration errors.
var (
errBadRotation = fmt.Errorf("rotation bad or unset, defaulting to: %v", defaultRotation)
errBadWidth = fmt.Errorf("width bad or unset, defaulting to: %v", defaultWidth)
errBadHeight = fmt.Errorf("height bad or unset, defaulting to: %v", defaultHeight)
errBadSnapQuality = fmt.Errorf("SnapQuality bad or unset, defaulting to: %v", defaultSnapQuality)
errBadTimelapseDuration = fmt.Errorf("TimelapseDuration bad or unset, defaulting to: %v", defaultTimelapseDuration)
errBadTimelapseInterval = fmt.Errorf("TimelapseInterval bad or unset, defaulting to: %v", defaultTimelapseInterval)
)
// Misc erros.
var errNotStarted = errors.New("cannot read, raspistill not started")
// Raspistill is an implementation of AVDevice that provides control over the
// raspistill utility for using the raspberry pi camera for the capture of
// singular images.
type Raspistill struct{ raspistill }
// New returns a new Raspivid.
func New(l config.Logger) *Raspistill { return &Raspistill{raspistill: new(l)} }
// Start will prepare the arguments for the raspistill command using the
// configuration set using the Set method then call the raspistill command,
// piping the image output from which the Read method will read from.
func (r *Raspistill) Start() error { return r.start() }
// Read implements io.Reader. Calling read before Start has been called will
// result in 0 bytes read and an error.
func (r *Raspistill) Read(p []byte) (int, error) { return r.read(p) }
// Stop will terminate the raspistill process and close the output pipe.
func (r *Raspistill) Stop() error { return r.stop() }
// IsRunning is used to determine if the pi's camera is running.
func (r *Raspistill) IsRunning() bool { return r.isRunning }
// Name returns the name of the device.
func (r *Raspistill) Name() string { return "Raspistill" }
// Set will take a Config struct, check the validity of the relevant fields
// and then performs any configuration necessary. If fields are not valid,
// an error is added to the multiError and a default value is used.
func (r *Raspistill) Set(c config.Config) error {
var errs device.MultiError
if c.Rotation > 359 || c.Rotation < 0 {
c.Rotation = defaultRotation
errs = append(errs, errBadRotation)
}
if c.Width == 0 {
c.Width = defaultWidth
errs = append(errs, errBadWidth)
}
if c.Height == 0 {
c.Height = defaultHeight
errs = append(errs, errBadHeight)
}
if c.SnapQuality < 0 || c.SnapQuality > 100 {
c.SnapQuality = defaultSnapQuality
errs = append(errs, errBadSnapQuality)
}
if c.TimelapseDuration > maxTimelapseDuration || c.TimelapseDuration < minTimelapseDuration {
c.TimelapseDuration = defaultTimelapseDuration
errs = append(errs, errBadTimelapseDuration)
}
if c.TimelapseInterval > maxTimelapseInterval || c.TimelapseInterval < minTimelapseInterval {
c.TimelapseInterval = defaultTimelapseInterval
errs = append(errs, errBadTimelapseInterval)
}
r.cfg = c
return errs
}

View File

@ -35,6 +35,11 @@ import (
type Logger interface { type Logger interface {
SetLevel(int8) SetLevel(int8)
Log(level int8, message string, params ...interface{}) Log(level int8, message string, params ...interface{})
Debug(msg string, params ...interface{})
Info(msg string, params ...interface{})
Warning(msg string, params ...interface{})
Error(msg string, params ...interface{})
Fatal(msg string, params ...interface{})
} }
// Enums to define inputs, outputs and codecs. // Enums to define inputs, outputs and codecs.
@ -45,6 +50,7 @@ const (
// Input/Output. // Input/Output.
InputFile InputFile
InputRaspivid InputRaspivid
InputRaspistill
InputV4L InputV4L
InputRTSP InputRTSP
InputAudio InputAudio
@ -55,10 +61,12 @@ const (
OutputHTTP OutputHTTP
OutputMPEGTS OutputMPEGTS
OutputFile OutputFile
OutputFiles
// Codecs. // Codecs.
H264 H264
H265 H265
MJPEG
JPEG JPEG
) )
@ -142,7 +150,9 @@ type Config struct {
// //
// Valid values are defined by enums: // Valid values are defined by enums:
// InputRaspivid: // InputRaspivid:
// Read data from a Raspberry Pi Camera. // Use raspivid utility to capture video from Raspberry Pi Camera.
// InputRaspistill:
// Use raspistill utility to capture images from the Raspberry Pi Camera.
// InputV4l: // InputV4l:
// Read from webcam. // Read from webcam.
// InputFile: // InputFile:
@ -198,7 +208,7 @@ type Config struct {
// Outputs define the outputs we wish to output data too. // Outputs define the outputs we wish to output data too.
// //
// Valid outputs are defined by enums: // Valid outputs are defined by enums:
// OutputFile: // OutputFile & OutputFiles:
// Location must be defined by the OutputPath field. MPEG-TS packetization // Location must be defined by the OutputPath field. MPEG-TS packetization
// is used. // is used.
// OutputHTTP: // OutputHTTP:
@ -215,6 +225,7 @@ type Config struct {
PSITime uint // Sets the time between a packet being sent. PSITime uint // Sets the time between a packet being sent.
Quantization uint // Quantization defines the quantization level, which will determine variable bitrate quality in the case of input from the Pi Camera. Quantization uint // Quantization defines the quantization level, which will determine variable bitrate quality in the case of input from the Pi Camera.
RBCapacity uint // The number of bytes the ring buffer will occupy. RBCapacity uint // The number of bytes the ring buffer will occupy.
RBStartElementSize uint // The starting element size of the ring buffer from which element size will increase to accomodate frames.
RBWriteTimeout uint // The ringbuffer write timeout in seconds. RBWriteTimeout uint // The ringbuffer write timeout in seconds.
RecPeriod float64 // How many seconds to record at a time. RecPeriod float64 // How many seconds to record at a time.
Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input. Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input.
@ -222,7 +233,22 @@ type Config struct {
RTPAddress string // RTPAddress defines the RTP output destination. RTPAddress string // RTPAddress defines the RTP output destination.
SampleRate uint // Samples a second (Hz). SampleRate uint // Samples a second (Hz).
Saturation int Saturation int
// SnapQuality is a value 0-100 inclusive, controlling JPEG compression of the
// timelapse snaps. 100 represents minimal compression and 0 represents the most
// compression.
SnapQuality int
Suppress bool // Holds logger suppression state. Suppress bool // Holds logger suppression state.
// TimelapseInterval defines the interval between timelapse snaps when using
// raspistill input.
TimelapseInterval time.Duration
// TimelapseDuration defines the duration of timelapse i.e. duration over
// which all snaps are taken, when using raspistill input.
TimelapseDuration time.Duration
VBRBitrate uint // VBRBitrate describes maximal variable bitrate. VBRBitrate uint // VBRBitrate describes maximal variable bitrate.
// VBRQuality describes the general quality of video from the GeoVision camera // VBRQuality describes the general quality of video from the GeoVision camera

View File

@ -37,6 +37,11 @@ type dumbLogger struct{}
func (dl *dumbLogger) Log(l int8, m string, a ...interface{}) {} func (dl *dumbLogger) Log(l int8, m string, a ...interface{}) {}
func (dl *dumbLogger) SetLevel(l int8) {} func (dl *dumbLogger) SetLevel(l int8) {}
func (dl *dumbLogger) Debug(msg string, args ...interface{}) {}
func (dl *dumbLogger) Info(msg string, args ...interface{}) {}
func (dl *dumbLogger) Warning(msg string, args ...interface{}) {}
func (dl *dumbLogger) Error(msg string, args ...interface{}) {}
func (dl *dumbLogger) Fatal(msg string, args ...interface{}) {}
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
dl := &dumbLogger{} dl := &dumbLogger{}
@ -55,6 +60,7 @@ func TestValidate(t *testing.T) {
PSITime: defaultPSITime, PSITime: defaultPSITime,
FileFPS: defaultFileFPS, FileFPS: defaultFileFPS,
RBCapacity: defaultRBCapacity, RBCapacity: defaultRBCapacity,
RBStartElementSize: defaultRBStartElementSize,
RBWriteTimeout: defaultRBWriteTimeout, RBWriteTimeout: defaultRBWriteTimeout,
MinFPS: defaultMinFPS, MinFPS: defaultMinFPS,
} }

View File

@ -37,7 +37,7 @@ import (
"bitbucket.org/ausocean/utils/logger" "bitbucket.org/ausocean/utils/logger"
) )
// Config map keys. // Config map Keys.
const ( const (
KeyAutoWhiteBalance = "AutoWhiteBalance" KeyAutoWhiteBalance = "AutoWhiteBalance"
KeyBitDepth = "BitDepth" KeyBitDepth = "BitDepth"
@ -78,6 +78,7 @@ const (
KeyPSITime = "PSITime" KeyPSITime = "PSITime"
KeyQuantization = "Quantization" KeyQuantization = "Quantization"
KeyRBCapacity = "RBCapacity" KeyRBCapacity = "RBCapacity"
KeyRBStartElementSize = "RBStartElementSize"
KeyRBWriteTimeout = "RBWriteTimeout" KeyRBWriteTimeout = "RBWriteTimeout"
KeyRecPeriod = "RecPeriod" KeyRecPeriod = "RecPeriod"
KeyRotation = "Rotation" KeyRotation = "Rotation"
@ -85,7 +86,10 @@ const (
KeyRTPAddress = "RTPAddress" KeyRTPAddress = "RTPAddress"
KeySampleRate = "SampleRate" KeySampleRate = "SampleRate"
KeySaturation = "Saturation" KeySaturation = "Saturation"
KeySnapQuality = "SnapQuality"
KeySuppress = "Suppress" KeySuppress = "Suppress"
KeyTimelapseDuration = "TimelapseDuration"
KeyTimelapseInterval = "TimelapseInterval"
KeyVBRBitrate = "VBRBitrate" KeyVBRBitrate = "VBRBitrate"
KeyVBRQuality = "VBRQuality" KeyVBRQuality = "VBRQuality"
KeyVerticalFlip = "VerticalFlip" KeyVerticalFlip = "VerticalFlip"
@ -95,6 +99,7 @@ const (
// Config map parameter types. // Config map parameter types.
const ( const (
typeString = "string" typeString = "string"
typeInt = "int"
typeUint = "uint" typeUint = "uint"
typeBool = "bool" typeBool = "bool"
typeFloat = "float" typeFloat = "float"
@ -118,6 +123,7 @@ const (
// Ring buffer defaults. // Ring buffer defaults.
defaultRBCapacity = 50000000 // => 50MB defaultRBCapacity = 50000000 // => 50MB
defaultRBStartElementSize = 1000 // bytes
defaultRBWriteTimeout = 5 // Seconds. defaultRBWriteTimeout = 5 // Seconds.
// Motion filter parameter defaults. // Motion filter parameter defaults.
@ -264,13 +270,14 @@ var Variables = []struct {
}, },
{ {
Name: KeyInput, Name: KeyInput,
Type_: "enum:raspivid,rtsp,v4l,file,audio", Type_: "enum:raspivid,raspistill,rtsp,v4l,file,audio",
Update: func(c *Config, v string) { Update: func(c *Config, v string) {
c.Input = parseEnum( c.Input = parseEnum(
KeyInput, KeyInput,
v, v,
map[string]uint8{ map[string]uint8{
"raspivid": InputRaspivid, "raspivid": InputRaspivid,
"raspistill": InputRaspistill,
"rtsp": InputRTSP, "rtsp": InputRTSP,
"v4l": InputV4L, "v4l": InputV4L,
"file": InputFile, "file": InputFile,
@ -281,7 +288,7 @@ var Variables = []struct {
}, },
Validate: func(c *Config) { Validate: func(c *Config) {
switch c.Input { switch c.Input {
case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP: case InputRaspivid, InputRaspistill, InputV4L, InputFile, InputAudio, InputRTSP:
default: default:
c.LogInvalidField(KeyInput, defaultInput) c.LogInvalidField(KeyInput, defaultInput)
c.Input = defaultInput c.Input = defaultInput
@ -290,7 +297,7 @@ var Variables = []struct {
}, },
{ {
Name: KeyInputCodec, Name: KeyInputCodec,
Type_: "enum:H264,H265,MJPEG,PCM,ADPCM", Type_: "enum:H264,H265,MJPEG,JPEG,PCM,ADPCM",
Update: func(c *Config, v string) { Update: func(c *Config, v string) {
c.InputCodec = parseEnum( c.InputCodec = parseEnum(
KeyInputCodec, KeyInputCodec,
@ -299,6 +306,7 @@ var Variables = []struct {
"h264": codecutil.H264, "h264": codecutil.H264,
"h265": codecutil.H265, "h265": codecutil.H265,
"mjpeg": codecutil.MJPEG, "mjpeg": codecutil.MJPEG,
"jpeg": codecutil.JPEG,
"pcm": codecutil.PCM, "pcm": codecutil.PCM,
"adpcm": codecutil.ADPCM, "adpcm": codecutil.ADPCM,
}, },
@ -307,7 +315,7 @@ var Variables = []struct {
}, },
Validate: func(c *Config) { Validate: func(c *Config) {
switch c.InputCodec { switch c.InputCodec {
case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM: case codecutil.H264, codecutil.MJPEG, codecutil.JPEG, codecutil.PCM, codecutil.ADPCM:
default: default:
c.LogInvalidField(KeyInputCodec, defaultInputCodec) c.LogInvalidField(KeyInputCodec, defaultInputCodec)
c.InputCodec = defaultInputCodec c.InputCodec = defaultInputCodec
@ -440,6 +448,8 @@ var Variables = []struct {
switch strings.ToLower(v) { switch strings.ToLower(v) {
case "file": case "file":
c.Outputs[0] = OutputFile c.Outputs[0] = OutputFile
case "files":
c.Outputs[0] = OutputFiles
case "http": case "http":
c.Outputs[0] = OutputHTTP c.Outputs[0] = OutputHTTP
case "rtmp": case "rtmp":
@ -466,6 +476,8 @@ var Variables = []struct {
switch strings.ToLower(output) { switch strings.ToLower(output) {
case "file": case "file":
c.Outputs[i] = OutputFile c.Outputs[i] = OutputFile
case "files":
c.Outputs[i] = OutputFiles
case "http": case "http":
c.Outputs[i] = OutputHTTP c.Outputs[i] = OutputHTTP
case "rtmp": case "rtmp":
@ -501,6 +513,14 @@ var Variables = []struct {
Update: func(c *Config, v string) { c.RBCapacity = parseUint(KeyRBCapacity, v, c) }, Update: func(c *Config, v string) { c.RBCapacity = parseUint(KeyRBCapacity, v, c) },
Validate: func(c *Config) { c.RBCapacity = lessThanOrEqual(KeyRBCapacity, c.RBCapacity, 0, c, defaultRBCapacity) }, Validate: func(c *Config) { c.RBCapacity = lessThanOrEqual(KeyRBCapacity, c.RBCapacity, 0, c, defaultRBCapacity) },
}, },
{
Name: KeyRBStartElementSize,
Type_: typeUint,
Update: func(c *Config, v string) { c.RBStartElementSize = parseUint("RBStartElementSize", v, c) },
Validate: func(c *Config) {
c.RBStartElementSize = lessThanOrEqual("RBStartElementSize", c.RBStartElementSize, 0, c, defaultRBStartElementSize)
},
},
{ {
Name: KeyRBWriteTimeout, Name: KeyRBWriteTimeout,
Type_: typeUint, Type_: typeUint,
@ -548,7 +568,7 @@ var Variables = []struct {
}, },
{ {
Name: KeySaturation, Name: KeySaturation,
Type_: "int", Type_: typeInt,
Update: func(c *Config, v string) { Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v) _v, err := strconv.Atoi(v)
if err != nil { if err != nil {
@ -557,6 +577,17 @@ var Variables = []struct {
c.Saturation = _v c.Saturation = _v
}, },
}, },
{
Name: KeySnapQuality,
Type_: typeUint,
Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, "invalid SnapQuality param", "value", v)
}
c.SnapQuality = _v
},
},
{ {
Name: KeySuppress, Name: KeySuppress,
Type_: typeBool, Type_: typeBool,
@ -565,6 +596,28 @@ var Variables = []struct {
c.Logger.(*logger.Logger).SetSuppress(c.Suppress) c.Logger.(*logger.Logger).SetSuppress(c.Suppress)
}, },
}, },
{
Name: KeyTimelapseInterval,
Type_: typeUint,
Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, "invalid TimelapseInterval param", "value", v)
}
c.TimelapseInterval = time.Duration(_v) * time.Second
},
},
{
Name: KeyTimelapseDuration,
Type_: typeUint,
Update: func(c *Config, v string) {
_v, err := strconv.Atoi(v)
if err != nil {
c.Logger.Log(logger.Warning, "invalid TimelapseDuration param", "value", v)
}
c.TimelapseDuration = time.Duration(_v) * time.Second
},
},
{ {
Name: KeyVBRBitrate, Name: KeyVBRBitrate,
Type_: typeUint, Type_: typeUint,