mirror of https://bitbucket.org/ausocean/av.git
device: add raspistill package housing release and testing implementations
This commit is contained in:
parent
60187c797d
commit
45c019a062
|
@ -98,7 +98,7 @@ func Lex(dst io.Writer, src io.Reader, delay time.Duration) error {
|
|||
|
||||
if nImg == 0 {
|
||||
<-tick
|
||||
Log.Debug("writing buf","len(buf)",len(buf))
|
||||
Log.Debug("writing buf", "len(buf)", len(buf))
|
||||
_, err = dst.Write(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -56,14 +56,14 @@ var jpegTests = []struct {
|
|||
input: []byte{0xff, 0xd8, 0xff, 0xd9},
|
||||
delay: 0,
|
||||
want: [][]byte{{0xff, 0xd8, 0xff, 0xd9}},
|
||||
err: io.ErrUnexpectedEOF,
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
{
|
||||
name: "null delayed",
|
||||
input: []byte{0xff, 0xd8, 0xff, 0xd9},
|
||||
delay: time.Millisecond,
|
||||
want: [][]byte{{0xff, 0xd8, 0xff, 0xd9}},
|
||||
err: io.ErrUnexpectedEOF,
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
{
|
||||
name: "full",
|
||||
|
@ -82,7 +82,7 @@ var jpegTests = []struct {
|
|||
{0xff, 0xd8, 'l', 'e', 'n', 'g', 't', 'h', 0xff, 0xd9},
|
||||
{0xff, 0xd8, 's', 'p', 'r', 'e', 'a', 'd', 0xff, 0xd9},
|
||||
},
|
||||
err: io.ErrUnexpectedEOF,
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
{
|
||||
name: "full delayed",
|
||||
|
@ -101,7 +101,7 @@ var jpegTests = []struct {
|
|||
{0xff, 0xd8, 'l', 'e', 'n', 'g', 't', 'h', 0xff, 0xd9},
|
||||
{0xff, 0xd8, 's', 'p', 'r', 'e', 'a', 'd', 0xff, 0xd9},
|
||||
},
|
||||
err: io.ErrUnexpectedEOF,
|
||||
err: io.ErrUnexpectedEOF,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const (
|
|||
H264SID = 27
|
||||
H265SID = 36
|
||||
MJPEGSID = 136
|
||||
JPEGSID = 137
|
||||
JPEGSID = 137
|
||||
PCMSID = 192
|
||||
ADPCMSID = 193
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -35,6 +35,11 @@ import (
|
|||
type Logger interface {
|
||||
SetLevel(int8)
|
||||
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.
|
||||
|
@ -45,6 +50,7 @@ const (
|
|||
// Input/Output.
|
||||
InputFile
|
||||
InputRaspivid
|
||||
InputRaspistill
|
||||
InputV4L
|
||||
InputRTSP
|
||||
InputAudio
|
||||
|
@ -55,10 +61,12 @@ const (
|
|||
OutputHTTP
|
||||
OutputMPEGTS
|
||||
OutputFile
|
||||
OutputFiles
|
||||
|
||||
// Codecs.
|
||||
H264
|
||||
H265
|
||||
MJPEG
|
||||
JPEG
|
||||
)
|
||||
|
||||
|
@ -142,7 +150,9 @@ type Config struct {
|
|||
//
|
||||
// Valid values are defined by enums:
|
||||
// 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:
|
||||
// Read from webcam.
|
||||
// InputFile:
|
||||
|
@ -198,7 +208,7 @@ type Config struct {
|
|||
// Outputs define the outputs we wish to output data too.
|
||||
//
|
||||
// Valid outputs are defined by enums:
|
||||
// OutputFile:
|
||||
// OutputFile & OutputFiles:
|
||||
// Location must be defined by the OutputPath field. MPEG-TS packetization
|
||||
// is used.
|
||||
// OutputHTTP:
|
||||
|
@ -212,18 +222,34 @@ type Config struct {
|
|||
// localhost:6970. MPEGT-TS packetization is used.
|
||||
Outputs []uint8
|
||||
|
||||
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.
|
||||
RBCapacity uint // The number of bytes the ring buffer will occupy.
|
||||
RBWriteTimeout uint // The ringbuffer write timeout in seconds.
|
||||
RecPeriod float64 // How many seconds to record at a time.
|
||||
Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input.
|
||||
RTMPURL string // RTMPURL specifies the Rtmp output destination URL. This must be defined if RTMP is to be used as an output.
|
||||
RTPAddress string // RTPAddress defines the RTP output destination.
|
||||
SampleRate uint // Samples a second (Hz).
|
||||
Saturation int
|
||||
Suppress bool // Holds logger suppression state.
|
||||
VBRBitrate uint // VBRBitrate describes maximal variable bitrate.
|
||||
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.
|
||||
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.
|
||||
RecPeriod float64 // How many seconds to record at a time.
|
||||
Rotation uint // Rotation defines the video rotation angle in degrees Raspivid input.
|
||||
RTMPURL string // RTMPURL specifies the Rtmp output destination URL. This must be defined if RTMP is to be used as an output.
|
||||
RTPAddress string // RTPAddress defines the RTP output destination.
|
||||
SampleRate uint // Samples a second (Hz).
|
||||
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.
|
||||
|
||||
// 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.
|
||||
|
||||
// VBRQuality describes the general quality of video from the GeoVision camera
|
||||
// under variable bitrate. VBRQuality can be one 5 consts defined:
|
||||
|
|
|
@ -35,28 +35,34 @@ import (
|
|||
|
||||
type dumbLogger struct{}
|
||||
|
||||
func (dl *dumbLogger) Log(l int8, m string, a ...interface{}) {}
|
||||
func (dl *dumbLogger) SetLevel(l int8) {}
|
||||
func (dl *dumbLogger) Log(l int8, m string, a ...interface{}) {}
|
||||
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) {
|
||||
dl := &dumbLogger{}
|
||||
|
||||
want := Config{
|
||||
Logger: dl,
|
||||
Input: defaultInput,
|
||||
Outputs: []uint8{defaultOutput},
|
||||
InputCodec: defaultInputCodec,
|
||||
RTPAddress: defaultRTPAddr,
|
||||
CameraIP: defaultCameraIP,
|
||||
BurstPeriod: defaultBurstPeriod,
|
||||
MinFrames: defaultMinFrames,
|
||||
FrameRate: defaultFrameRate,
|
||||
ClipDuration: defaultClipDuration,
|
||||
PSITime: defaultPSITime,
|
||||
FileFPS: defaultFileFPS,
|
||||
RBCapacity: defaultRBCapacity,
|
||||
RBWriteTimeout: defaultRBWriteTimeout,
|
||||
MinFPS: defaultMinFPS,
|
||||
Logger: dl,
|
||||
Input: defaultInput,
|
||||
Outputs: []uint8{defaultOutput},
|
||||
InputCodec: defaultInputCodec,
|
||||
RTPAddress: defaultRTPAddr,
|
||||
CameraIP: defaultCameraIP,
|
||||
BurstPeriod: defaultBurstPeriod,
|
||||
MinFrames: defaultMinFrames,
|
||||
FrameRate: defaultFrameRate,
|
||||
ClipDuration: defaultClipDuration,
|
||||
PSITime: defaultPSITime,
|
||||
FileFPS: defaultFileFPS,
|
||||
RBCapacity: defaultRBCapacity,
|
||||
RBStartElementSize: defaultRBStartElementSize,
|
||||
RBWriteTimeout: defaultRBWriteTimeout,
|
||||
MinFPS: defaultMinFPS,
|
||||
}
|
||||
|
||||
got := Config{Logger: dl}
|
||||
|
|
|
@ -37,64 +37,69 @@ import (
|
|||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
// Config map keys.
|
||||
// Config map Keys.
|
||||
const (
|
||||
KeyAutoWhiteBalance = "AutoWhiteBalance"
|
||||
KeyBitDepth = "BitDepth"
|
||||
KeyBitrate = "Bitrate"
|
||||
KeyBrightness = "Brightness"
|
||||
KeyBurstPeriod = "BurstPeriod"
|
||||
KeyCameraChan = "CameraChan"
|
||||
KeyCameraIP = "CameraIP"
|
||||
KeyCBR = "CBR"
|
||||
KeyClipDuration = "ClipDuration"
|
||||
KeyChannels = "Channels"
|
||||
KeyExposure = "Exposure"
|
||||
KeyFileFPS = "FileFPS"
|
||||
KeyFilters = "Filters"
|
||||
KeyFrameRate = "FrameRate"
|
||||
KeyHeight = "Height"
|
||||
KeyHorizontalFlip = "HorizontalFlip"
|
||||
KeyHTTPAddress = "HTTPAddress"
|
||||
KeyInput = "Input"
|
||||
KeyInputCodec = "InputCodec"
|
||||
KeyInputPath = "InputPath"
|
||||
KeyLogging = "logging"
|
||||
KeyLoop = "Loop"
|
||||
KeyMinFPS = "MinFPS"
|
||||
KeyMinFrames = "MinFrames"
|
||||
KeyMode = "mode"
|
||||
KeyMotionDownscaling = "MotionDownscaling"
|
||||
KeyMotionHistory = "MotionHistory"
|
||||
KeyMotionInterval = "MotionInterval"
|
||||
KeyMotionKernel = "MotionKernel"
|
||||
KeyMotionMinArea = "MotionMinArea"
|
||||
KeyMotionPadding = "MotionPadding"
|
||||
KeyMotionPixels = "MotionPixels"
|
||||
KeyMotionThreshold = "MotionThreshold"
|
||||
KeyOutput = "Output"
|
||||
KeyOutputPath = "OutputPath"
|
||||
KeyOutputs = "Outputs"
|
||||
KeyPSITime = "PSITime"
|
||||
KeyQuantization = "Quantization"
|
||||
KeyRBCapacity = "RBCapacity"
|
||||
KeyRBWriteTimeout = "RBWriteTimeout"
|
||||
KeyRecPeriod = "RecPeriod"
|
||||
KeyRotation = "Rotation"
|
||||
KeyRTMPURL = "RTMPURL"
|
||||
KeyRTPAddress = "RTPAddress"
|
||||
KeySampleRate = "SampleRate"
|
||||
KeySaturation = "Saturation"
|
||||
KeySuppress = "Suppress"
|
||||
KeyVBRBitrate = "VBRBitrate"
|
||||
KeyVBRQuality = "VBRQuality"
|
||||
KeyVerticalFlip = "VerticalFlip"
|
||||
KeyWidth = "Width"
|
||||
KeyAutoWhiteBalance = "AutoWhiteBalance"
|
||||
KeyBitDepth = "BitDepth"
|
||||
KeyBitrate = "Bitrate"
|
||||
KeyBrightness = "Brightness"
|
||||
KeyBurstPeriod = "BurstPeriod"
|
||||
KeyCameraChan = "CameraChan"
|
||||
KeyCameraIP = "CameraIP"
|
||||
KeyCBR = "CBR"
|
||||
KeyClipDuration = "ClipDuration"
|
||||
KeyChannels = "Channels"
|
||||
KeyExposure = "Exposure"
|
||||
KeyFileFPS = "FileFPS"
|
||||
KeyFilters = "Filters"
|
||||
KeyFrameRate = "FrameRate"
|
||||
KeyHeight = "Height"
|
||||
KeyHorizontalFlip = "HorizontalFlip"
|
||||
KeyHTTPAddress = "HTTPAddress"
|
||||
KeyInput = "Input"
|
||||
KeyInputCodec = "InputCodec"
|
||||
KeyInputPath = "InputPath"
|
||||
KeyLogging = "logging"
|
||||
KeyLoop = "Loop"
|
||||
KeyMinFPS = "MinFPS"
|
||||
KeyMinFrames = "MinFrames"
|
||||
KeyMode = "mode"
|
||||
KeyMotionDownscaling = "MotionDownscaling"
|
||||
KeyMotionHistory = "MotionHistory"
|
||||
KeyMotionInterval = "MotionInterval"
|
||||
KeyMotionKernel = "MotionKernel"
|
||||
KeyMotionMinArea = "MotionMinArea"
|
||||
KeyMotionPadding = "MotionPadding"
|
||||
KeyMotionPixels = "MotionPixels"
|
||||
KeyMotionThreshold = "MotionThreshold"
|
||||
KeyOutput = "Output"
|
||||
KeyOutputPath = "OutputPath"
|
||||
KeyOutputs = "Outputs"
|
||||
KeyPSITime = "PSITime"
|
||||
KeyQuantization = "Quantization"
|
||||
KeyRBCapacity = "RBCapacity"
|
||||
KeyRBStartElementSize = "RBStartElementSize"
|
||||
KeyRBWriteTimeout = "RBWriteTimeout"
|
||||
KeyRecPeriod = "RecPeriod"
|
||||
KeyRotation = "Rotation"
|
||||
KeyRTMPURL = "RTMPURL"
|
||||
KeyRTPAddress = "RTPAddress"
|
||||
KeySampleRate = "SampleRate"
|
||||
KeySaturation = "Saturation"
|
||||
KeySnapQuality = "SnapQuality"
|
||||
KeySuppress = "Suppress"
|
||||
KeyTimelapseDuration = "TimelapseDuration"
|
||||
KeyTimelapseInterval = "TimelapseInterval"
|
||||
KeyVBRBitrate = "VBRBitrate"
|
||||
KeyVBRQuality = "VBRQuality"
|
||||
KeyVerticalFlip = "VerticalFlip"
|
||||
KeyWidth = "Width"
|
||||
)
|
||||
|
||||
// Config map parameter types.
|
||||
const (
|
||||
typeString = "string"
|
||||
typeInt = "int"
|
||||
typeUint = "uint"
|
||||
typeBool = "bool"
|
||||
typeFloat = "float"
|
||||
|
@ -117,8 +122,9 @@ const (
|
|||
defaultFileFPS = 0
|
||||
|
||||
// Ring buffer defaults.
|
||||
defaultRBCapacity = 50000000 // => 50MB
|
||||
defaultRBWriteTimeout = 5 // Seconds.
|
||||
defaultRBCapacity = 50000000 // => 50MB
|
||||
defaultRBStartElementSize = 1000 // bytes
|
||||
defaultRBWriteTimeout = 5 // Seconds.
|
||||
|
||||
// Motion filter parameter defaults.
|
||||
defaultMinFPS = 1.0
|
||||
|
@ -264,24 +270,25 @@ var Variables = []struct {
|
|||
},
|
||||
{
|
||||
Name: KeyInput,
|
||||
Type_: "enum:raspivid,rtsp,v4l,file,audio",
|
||||
Type_: "enum:raspivid,raspistill,rtsp,v4l,file,audio",
|
||||
Update: func(c *Config, v string) {
|
||||
c.Input = parseEnum(
|
||||
KeyInput,
|
||||
v,
|
||||
map[string]uint8{
|
||||
"raspivid": InputRaspivid,
|
||||
"rtsp": InputRTSP,
|
||||
"v4l": InputV4L,
|
||||
"file": InputFile,
|
||||
"audio": InputAudio,
|
||||
"raspivid": InputRaspivid,
|
||||
"raspistill": InputRaspistill,
|
||||
"rtsp": InputRTSP,
|
||||
"v4l": InputV4L,
|
||||
"file": InputFile,
|
||||
"audio": InputAudio,
|
||||
},
|
||||
c,
|
||||
)
|
||||
},
|
||||
Validate: func(c *Config) {
|
||||
switch c.Input {
|
||||
case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP:
|
||||
case InputRaspivid, InputRaspistill, InputV4L, InputFile, InputAudio, InputRTSP:
|
||||
default:
|
||||
c.LogInvalidField(KeyInput, defaultInput)
|
||||
c.Input = defaultInput
|
||||
|
@ -290,7 +297,7 @@ var Variables = []struct {
|
|||
},
|
||||
{
|
||||
Name: KeyInputCodec,
|
||||
Type_: "enum:H264,H265,MJPEG,PCM,ADPCM",
|
||||
Type_: "enum:H264,H265,MJPEG,JPEG,PCM,ADPCM",
|
||||
Update: func(c *Config, v string) {
|
||||
c.InputCodec = parseEnum(
|
||||
KeyInputCodec,
|
||||
|
@ -299,6 +306,7 @@ var Variables = []struct {
|
|||
"h264": codecutil.H264,
|
||||
"h265": codecutil.H265,
|
||||
"mjpeg": codecutil.MJPEG,
|
||||
"jpeg": codecutil.JPEG,
|
||||
"pcm": codecutil.PCM,
|
||||
"adpcm": codecutil.ADPCM,
|
||||
},
|
||||
|
@ -307,7 +315,7 @@ var Variables = []struct {
|
|||
},
|
||||
Validate: func(c *Config) {
|
||||
switch c.InputCodec {
|
||||
case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM:
|
||||
case codecutil.H264, codecutil.MJPEG, codecutil.JPEG, codecutil.PCM, codecutil.ADPCM:
|
||||
default:
|
||||
c.LogInvalidField(KeyInputCodec, defaultInputCodec)
|
||||
c.InputCodec = defaultInputCodec
|
||||
|
@ -440,6 +448,8 @@ var Variables = []struct {
|
|||
switch strings.ToLower(v) {
|
||||
case "file":
|
||||
c.Outputs[0] = OutputFile
|
||||
case "files":
|
||||
c.Outputs[0] = OutputFiles
|
||||
case "http":
|
||||
c.Outputs[0] = OutputHTTP
|
||||
case "rtmp":
|
||||
|
@ -466,6 +476,8 @@ var Variables = []struct {
|
|||
switch strings.ToLower(output) {
|
||||
case "file":
|
||||
c.Outputs[i] = OutputFile
|
||||
case "files":
|
||||
c.Outputs[i] = OutputFiles
|
||||
case "http":
|
||||
c.Outputs[i] = OutputHTTP
|
||||
case "rtmp":
|
||||
|
@ -501,6 +513,14 @@ var Variables = []struct {
|
|||
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) },
|
||||
},
|
||||
{
|
||||
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,
|
||||
Type_: typeUint,
|
||||
|
@ -548,7 +568,7 @@ var Variables = []struct {
|
|||
},
|
||||
{
|
||||
Name: KeySaturation,
|
||||
Type_: "int",
|
||||
Type_: typeInt,
|
||||
Update: func(c *Config, v string) {
|
||||
_v, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
|
@ -557,6 +577,17 @@ var Variables = []struct {
|
|||
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,
|
||||
Type_: typeBool,
|
||||
|
@ -565,6 +596,28 @@ var Variables = []struct {
|
|||
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,
|
||||
Type_: typeUint,
|
||||
|
|
Loading…
Reference in New Issue