revid: added raspivid.go file to hold Raspivid implementation of AVDevice interface

Wrote consts for default values, wrote global errors, wrote multiError type (might move)
wrote Set method.
This commit is contained in:
Saxon 2019-11-01 21:45:46 +10:30
parent 20bf962fa3
commit 924858c1c0
6 changed files with 194 additions and 49 deletions

View File

@ -82,19 +82,19 @@ const (
NothingDefined = iota NothingDefined = iota
// Input/Output. // Input/Output.
File InputFile
InputRaspivid
// Inputs. InputV4L
Raspivid InputRTSP
V4L InputAudio
RTSP
Audio
// Outputs. // Outputs.
RTMP OutputAudio
RTP OutputRTMP
HTTP OutputRTP
MPEGTS OutputHTTP
OutputMPEGTS
OutputFile
// Codecs. // Codecs.
H264 H264
@ -105,8 +105,8 @@ const (
// Default config settings // Default config settings
const ( const (
// General revid defaults. // General revid defaults.
defaultInput = Raspivid defaultInput = InputRaspivid
defaultOutput = HTTP defaultOutput = OutputHTTP
defaultFrameRate = 25 defaultFrameRate = 25
defaultWriteRate = 25 defaultWriteRate = 25
defaultTimeout = 0 defaultTimeout = 0
@ -327,7 +327,7 @@ func (c *Config) Validate() error {
} }
switch c.Input { switch c.Input {
case Raspivid, V4L, File, Audio, RTSP: case InputRaspivid, InputV4L, InputFile, InputAudio, InputRTSP:
case NothingDefined: case NothingDefined:
c.Logger.Log(logger.Info, pkg+"no input type defined, defaulting", "input", defaultInput) c.Logger.Log(logger.Info, pkg+"no input type defined, defaulting", "input", defaultInput)
c.Input = defaultInput c.Input = defaultInput
@ -339,7 +339,7 @@ func (c *Config) Validate() error {
case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM: case codecutil.H264, codecutil.MJPEG, codecutil.PCM, codecutil.ADPCM:
default: default:
switch c.Input { switch c.Input {
case Audio: case OutputAudio:
c.Logger.Log(logger.Info, pkg+"input is audio but no codec defined, defaulting", "inputCodec", defaultAudioInputCodec) c.Logger.Log(logger.Info, pkg+"input is audio but no codec defined, defaulting", "inputCodec", defaultAudioInputCodec)
c.InputCodec = defaultAudioInputCodec c.InputCodec = defaultAudioInputCodec
default: default:
@ -358,8 +358,8 @@ func (c *Config) Validate() error {
var haveRTMPOut bool var haveRTMPOut bool
for i, o := range c.Outputs { for i, o := range c.Outputs {
switch o { switch o {
case File: case OutputFile:
case RTMP: case OutputRTMP:
haveRTMPOut = true haveRTMPOut = true
if c.Bitrate == 0 { if c.Bitrate == 0 {
c.Bitrate = defaultBitrate c.Bitrate = defaultBitrate
@ -367,11 +367,11 @@ func (c *Config) Validate() error {
c.Quantization = 0 c.Quantization = 0
if c.RTMPURL == "" { if c.RTMPURL == "" {
c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP")
c.Outputs[i] = HTTP c.Outputs[i] = OutputHTTP
haveRTMPOut = false haveRTMPOut = false
} }
fallthrough fallthrough
case HTTP, RTP: case OutputHTTP, OutputRTP:
if !haveRTMPOut { if !haveRTMPOut {
c.Bitrate = 0 c.Bitrate = 0
if c.Quantization == 0 { if c.Quantization == 0 {

View File

@ -41,7 +41,7 @@ func TestValidate(t *testing.T) {
err error err error
}{ }{
{ {
in: Config{Outputs: []uint8{HTTP}, Logger: &dumbLogger{}}, in: Config{Outputs: []uint8{OutputHTTP}, Logger: &dumbLogger{}},
check: func(c Config) error { check: func(c Config) error {
if c.Bitrate != 0 { if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)
@ -53,7 +53,7 @@ func TestValidate(t *testing.T) {
}, },
}, },
{ {
in: Config{Outputs: []uint8{RTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}}, in: Config{Outputs: []uint8{OutputRTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}},
check: func(c Config) error { check: func(c Config) error {
if c.Bitrate != defaultBitrate { if c.Bitrate != defaultBitrate {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate) return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate)
@ -65,12 +65,12 @@ func TestValidate(t *testing.T) {
}, },
}, },
{ {
in: Config{Outputs: []uint8{HTTP}, Quantization: 50, Logger: &dumbLogger{}}, in: Config{Outputs: []uint8{OutputHTTP}, Quantization: 50, Logger: &dumbLogger{}},
check: func(c Config) error { return nil }, check: func(c Config) error { return nil },
err: errInvalidQuantization, err: errInvalidQuantization,
}, },
{ {
in: Config{Outputs: []uint8{HTTP}, Quantization: 20, Logger: &dumbLogger{}}, in: Config{Outputs: []uint8{OutputHTTP}, Quantization: 20, Logger: &dumbLogger{}},
check: func(c Config) error { check: func(c Config) error {
if c.Bitrate != 0 { if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0) return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)

View File

@ -55,9 +55,9 @@ const (
type AVDevice interface { type AVDevice interface {
io.Reader io.Reader
Set(c Config) error
Start() error Start() error
Stop() error Stop() error
Set() error
} }
// startRaspivid sets up things for input from raspivid i.e. starts // startRaspivid sets up things for input from raspivid i.e. starts

145
revid/raspivid.go Normal file
View File

@ -0,0 +1,145 @@
/*
DESCRIPTION
raspivid.go provides an implementation of the AVDevice interface for raspivid.
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2017-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 revid
import (
"errors"
"fmt"
"bitbucket.org/ausocean/av/codec/codecutil"
)
// Raspivid AVDevice configuration defaults.
const (
raspividDefaultCodec = codecutil.H264
raspividDefaultRotation = 0
raspividDefaultWidth = 1280
raspividDefaultHeight = 720
raspividDefaultBrightness = 50
raspividDefaultSaturation = 0
raspividDefaultExposure = "auto"
raspividDefaultAutoWhiteBalance = "auto"
raspividDefaultMinFrames = 100
raspividDefaultQuantization = 30
raspividDefaultBitrate = 48000
raspividDefaultFramerate = 25
)
type Raspivid struct {
c Config
}
var (
errBadCodec = errors.New("codec bad or unset, defaulting")
errBadRotation = errors.New("rotation bad or unset, defaulting")
errBadWidth = errors.New("width bad or unset, defaulting")
errBadHeight = errors.New("height bad or unset, defaulting")
errBadFrameRate = errors.New("framerate bad or unset, defaulting")
errBadBitrate = errors.New("bitrate bad or unset, defaulting")
errBadMinFrames = errors.New("min frames bad or unset, defaulting")
errBadSaturation = errors.New("saturation bad or unset, defaulting")
errBadBrightness = errors.New("brightness bad or unset, defaulting")
errBadExposure = errors.New("exposure bad or unset, defaulting")
errBadAutoWhiteBalance = errors.New("auto white balance bad or unset, defaulting")
errBadQuantization = errors.New("quantization bad or unset, defaulting")
)
type multiError []error
func (me multiError) Error() string {
return fmt.Sprintf("%v", me)
}
func (r *Raspivid) Set(c Config) error {
var errs []error
switch c.InputCodec {
case codecutil.H264, codecutil.MJPEG:
default:
c.InputCodec = raspividDefaultCodec
errs = append(errs, errBadCodec)
}
if c.Rotation > 359 {
c.Rotation = raspividDefaultRotation
errs = append(errs, errBadRotation)
}
if c.Width == 0 {
c.Width = raspividDefaultWidth
errs = append(errs, errBadWidth)
}
if c.Height == 0 {
c.Height = raspividDefaultHeight
errs = append(errs, errBadHeight)
}
if c.FrameRate == 0 {
c.FrameRate = raspividDefaultFramerate
errs = append(errs, errBadFrameRate)
}
if c.VBR {
c.Bitrate = 0
if c.Quantization < 10 || c.Quantization > 40 {
errs = append(errs, errBadQuantization)
c.Quantization = raspividDefaultQuantization
}
} else {
c.Quantization = 0
if c.Bitrate <= 0 {
errs = append(errs, errBadBitrate)
c.Bitrate = raspividDefaultBitrate
}
}
if c.MinFrames <= 0 {
errs = append(errs, errBadMinFrames)
c.MinFrames = raspividDefaultMinFrames
}
if c.Brightness <= 0 || c.Brightness > 100 {
errs = append(errs, errBadBrightness)
c.Brightness = raspividDefaultBrightness
}
if c.Saturation < -100 || c.Saturation > 100 {
errs = append(errs, errBadSaturation)
c.Saturation = raspividDefaultSaturation
}
if c.Exposure == "" || !stringInSlice(c.Exposure, ExposureModes[:]) {
errs = append(errs, errBadExposure)
c.Exposure = raspividDefaultExposure
}
if c.AutoWhiteBalance == "" || !stringInSlice(c.AutoWhiteBalance, AutoWhiteBalanceModes[:]) {
errs = append(errs, errBadAutoWhiteBalance)
c.AutoWhiteBalance = raspividDefaultAutoWhiteBalance
}
r.c = c
return multiError(errs)
}

View File

@ -168,7 +168,7 @@ func (r *Revid) reset(config Config) error {
var encOptions []func(*mts.Encoder) error var encOptions []func(*mts.Encoder) error
switch r.config.Input { switch r.config.Input {
case Raspivid: case InputRaspivid:
switch r.config.InputCodec { switch r.config.InputCodec {
case codecutil.H264: case codecutil.H264:
st = mts.EncodeH264 st = mts.EncodeH264
@ -178,9 +178,9 @@ func (r *Revid) reset(config Config) error {
default: default:
panic("unknown input codec for raspivid input") panic("unknown input codec for raspivid input")
} }
case File, V4L: case InputFile, InputV4L:
st = mts.EncodeH264 st = mts.EncodeH264
case RTSP: case InputRTSP:
switch r.config.InputCodec { switch r.config.InputCodec {
case codecutil.H265: case codecutil.H265:
st = mts.EncodeH265 st = mts.EncodeH265
@ -192,7 +192,7 @@ func (r *Revid) reset(config Config) error {
default: default:
panic("unknown input codec for RTSP input") panic("unknown input codec for RTSP input")
} }
case Audio: case InputAudio:
st = mts.EncodeAudio st = mts.EncodeAudio
default: default:
panic("unknown input type") panic("unknown input type")
@ -246,7 +246,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
var w io.WriteCloser var w io.WriteCloser
for _, out := range r.config.Outputs { for _, out := range r.config.Outputs {
switch out { switch out {
case HTTP: case OutputHTTP:
w = newMtsSender( w = newMtsSender(
newHttpSender(r.ns, r.config.Logger.Log), newHttpSender(r.ns, r.config.Logger.Log),
r.config.Logger.Log, r.config.Logger.Log,
@ -254,19 +254,19 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
r.config.ClipDuration, r.config.ClipDuration,
) )
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case RTP: case OutputRTP:
w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate) w, err := newRtpSender(r.config.RTPAddress, r.config.Logger.Log, r.config.FrameRate)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error()) r.config.Logger.Log(logger.Warning, pkg+"rtp connect error", "error", err.Error())
} }
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case File: case OutputFile:
w, err := newFileSender(r.config.OutputPath) w, err := newFileSender(r.config.OutputPath)
if err != nil { if err != nil {
return err return err
} }
mtsSenders = append(mtsSenders, w) mtsSenders = append(mtsSenders, w)
case RTMP: case OutputRTMP:
w, err := newRtmpSender( w, err := newRtmpSender(
r.config.RTMPURL, r.config.RTMPURL,
rtmpConnectionTimeout, rtmpConnectionTimeout,
@ -305,7 +305,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
r.encoders = multiWriter(encoders...) r.encoders = multiWriter(encoders...)
switch r.config.Input { switch r.config.Input {
case Raspivid: case InputRaspivid:
r.setupInput = r.startRaspivid r.setupInput = r.startRaspivid
switch r.config.InputCodec { switch r.config.InputCodec {
case codecutil.H264: case codecutil.H264:
@ -313,12 +313,12 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
case codecutil.MJPEG: case codecutil.MJPEG:
r.lexTo = mjpeg.Lex r.lexTo = mjpeg.Lex
} }
case V4L: case InputV4L:
r.setupInput = r.startV4L r.setupInput = r.startV4L
r.lexTo = h264.Lex r.lexTo = h264.Lex
case File: case InputFile:
r.setupInput = r.setupInputForFile r.setupInput = r.setupInputForFile
case RTSP: case InputRTSP:
r.setupInput = r.startRTSPCamera r.setupInput = r.startRTSPCamera
switch r.config.InputCodec { switch r.config.InputCodec {
case codecutil.H264: case codecutil.H264:
@ -328,7 +328,7 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
case codecutil.MJPEG: case codecutil.MJPEG:
panic("not implemented") panic("not implemented")
} }
case Audio: case InputAudio:
r.setupInput = r.startAudioDevice r.setupInput = r.startAudioDevice
r.lexTo = codecutil.NewByteLexer(&r.config.ChunkSize).Lex r.lexTo = codecutil.NewByteLexer(&r.config.ChunkSize).Lex
} }
@ -423,7 +423,7 @@ func (r *Revid) Update(vars map[string]string) error {
for key, value := range vars { for key, value := range vars {
switch key { switch key {
case "Input": case "Input":
v, ok := map[string]uint8{"raspivid": Raspivid, "rtsp": RTSP}[strings.ToLower(value)] v, ok := map[string]uint8{"raspivid": InputRaspivid, "rtsp": InputRTSP}[strings.ToLower(value)]
if !ok { if !ok {
r.config.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value)
break break
@ -463,13 +463,13 @@ func (r *Revid) Update(vars map[string]string) error {
for i, output := range outputs { for i, output := range outputs {
switch output { switch output {
case "File": case "File":
r.config.Outputs[i] = File r.config.Outputs[i] = OutputFile
case "Http": case "Http":
r.config.Outputs[i] = HTTP r.config.Outputs[i] = OutputHTTP
case "Rtmp": case "Rtmp":
r.config.Outputs[i] = RTMP r.config.Outputs[i] = OutputRTMP
case "Rtp": case "Rtp":
r.config.Outputs[i] = RTP r.config.Outputs[i] = OutputRTP
default: default:
r.config.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid output param", "value", value)
continue continue

View File

@ -58,7 +58,7 @@ func TestRaspivid(t *testing.T) {
var c Config var c Config
c.Logger = &logger c.Logger = &logger
c.Input = Raspivid c.Input = InputRaspivid
rv, err := New(c, ns) rv, err := New(c, ns)
if err != nil { if err != nil {
@ -148,7 +148,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
encoders []encoder encoders []encoder
}{ }{
{ {
outputs: []uint8{HTTP}, outputs: []uint8{OutputHTTP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -157,7 +157,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{RTMP}, outputs: []uint8{OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: flvEncoderStr, encoderType: flvEncoderStr,
@ -166,7 +166,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{RTP}, outputs: []uint8{OutputRTP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -175,7 +175,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{HTTP, RTMP}, outputs: []uint8{OutputHTTP, OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -188,7 +188,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{HTTP, RTP, RTMP}, outputs: []uint8{OutputHTTP, OutputRTP, OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,
@ -201,7 +201,7 @@ func TestResetEncoderSenderSetup(t *testing.T) {
}, },
}, },
{ {
outputs: []uint8{RTP, RTMP}, outputs: []uint8{OutputRTP, OutputRTMP},
encoders: []encoder{ encoders: []encoder{
{ {
encoderType: mtsEncoderStr, encoderType: mtsEncoderStr,