Merged in http-mode-vbr (pull request #239)

revid: variable bitrate default for HTTP and RTP mode

Approved-by: Alan Noble <anoble@gmail.com>
This commit is contained in:
Saxon Milton 2019-08-24 06:59:43 +00:00
commit 379b528bb2
4 changed files with 139 additions and 42 deletions

View File

@ -111,7 +111,6 @@ func handleFlags() revid.Config {
inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM") inputCodecPtr = flag.String("InputCodec", "H264", "The codec of the input: H264, Mjpeg, PCM, ADPCM")
inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP") inputPtr = flag.String("Input", "", "The input type: Raspivid, File, v4l, Audio, RTSP")
rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.") rtspURLPtr = flag.String("RTSPURL", "", "The URL for an RTSP server.")
quantizePtr = flag.Bool("Quantize", false, "Quantize input (non-variable bitrate)")
verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal") verbosityPtr = flag.String("Verbosity", "Info", "Verbosity: Debug, Info, Warning, Error, Fatal")
rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: <IP>:<port> (port is generally 6970-6999)") rtpAddrPtr = flag.String("RtpAddr", "", "Rtp destination address: <IP>:<port> (port is generally 6970-6999)")
logPathPtr = flag.String("LogPath", defaultLogPath, "The log path") logPathPtr = flag.String("LogPath", defaultLogPath, "The log path")
@ -235,7 +234,6 @@ func handleFlags() revid.Config {
} }
cfg.RTSPURL = *rtspURLPtr cfg.RTSPURL = *rtspURLPtr
cfg.Quantize = *quantizePtr
cfg.Rotation = *rotationPtr cfg.Rotation = *rotationPtr
cfg.FlipHorizontal = *horizontalFlipPtr cfg.FlipHorizontal = *horizontalFlipPtr
cfg.FlipVertical = *verticalFlipPtr cfg.FlipVertical = *verticalFlipPtr

View File

@ -109,7 +109,7 @@ const (
defaultWidth = 1280 defaultWidth = 1280
defaultHeight = 720 defaultHeight = 720
defaultIntraRefreshPeriod = 100 defaultIntraRefreshPeriod = 100
defaultQuantization = 40 defaultQuantization = 30
defaultBitrate = 400000 defaultBitrate = 400000
// Audio defaults. // Audio defaults.
@ -170,10 +170,6 @@ type Config struct {
// localhost:6970. MPEGT-TS packetization is used. // localhost:6970. MPEGT-TS packetization is used.
Outputs []uint8 Outputs []uint8
// Quantize specifies whether the input to revid will have constant or variable
// bitrate, if configurable with the chosen input. Raspivid supports quantization.
Quantize bool
// RTMPURL specifies the Rtmp output destination URL. This must be defined if // RTMPURL specifies the Rtmp output destination URL. This must be defined if
// RTMP is to be used as an output. // RTMP is to be used as an output.
RTMPURL string RTMPURL string
@ -252,9 +248,14 @@ type Config struct {
MTSRBElementSize int // The element size in bytes of 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 // Validate checks for any errors in the config fields and defaults settings
// if particular parameters have not been defined. // if particular parameters have not been defined.
func (c *Config) Validate(r *Revid) error { func (c *Config) Validate() error {
switch c.LogLevel { switch c.LogLevel {
case logger.Debug: case logger.Debug:
case logger.Info: case logger.Info:
@ -277,12 +278,6 @@ func (c *Config) Validate(r *Revid) error {
switch c.InputCodec { switch c.InputCodec {
case codecutil.H264: case codecutil.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
}
case codecutil.MJPEG: case codecutil.MJPEG:
if c.Quantization > 0 || c.Bitrate == 0 { if c.Quantization > 0 || c.Bitrate == 0 {
return errors.New("bad bitrate or quantization for mjpeg input") return errors.New("bad bitrate or quantization for mjpeg input")
@ -304,22 +299,33 @@ func (c *Config) Validate(r *Revid) error {
if c.Outputs == nil { if c.Outputs == nil {
c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput) c.Logger.Log(logger.Info, pkg+"no output defined, defaulting", "output", defaultOutput)
c.Outputs = append(c.Outputs, defaultOutput) c.Outputs = append(c.Outputs, defaultOutput)
} else { }
for i, o := range c.Outputs {
switch o { var haveRTMPOut bool
case File: for i, o := range c.Outputs {
case RTMP: switch o {
if c.RTMPURL == "" { case File:
c.Logger.Log(logger.Info, pkg+"no RTMP URL: falling back to HTTP") case RTMP:
c.Outputs[i] = HTTP haveRTMPOut = true
// FIXME(kortschak): Does this want the same line as below? if c.Bitrate == 0 {
// c.FramesPerClip = httpFramesPerClip c.Bitrate = defaultBitrate
break
}
case HTTP, RTP:
default:
return errors.New("bad output type defined in config")
} }
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")
} }
} }
@ -373,9 +379,8 @@ func (c *Config) Validate(r *Revid) error {
c.WriteRate = defaultWriteRate c.WriteRate = defaultWriteRate
} }
if c.Bitrate == 0 { if c.Bitrate < 0 {
c.Logger.Log(logger.Info, pkg+"no bitrate defined, defaulting", "bitrate", defaultBitrate) return errors.New("invalid bitrate")
c.Bitrate = defaultBitrate
} }
if c.IntraRefreshPeriod == 0 { if c.IntraRefreshPeriod == 0 {
@ -383,11 +388,8 @@ func (c *Config) Validate(r *Revid) error {
c.IntraRefreshPeriod = defaultIntraRefreshPeriod c.IntraRefreshPeriod = defaultIntraRefreshPeriod
} }
if c.Quantization == 0 { if c.Quantization != 0 && (c.Quantization < 10 || c.Quantization > 40) {
c.Logger.Log(logger.Info, pkg+"no quantization defined, defaulting", "quantization", defaultQuantization) return errInvalidQuantization
c.Quantization = defaultQuantization
} else if c.Quantization > 51 {
return errors.New("quantisation is over threshold")
} }
if c.RTPAddress == "" { if c.RTPAddress == "" {

97
revid/config_test.go Normal file
View File

@ -0,0 +1,97 @@
/*
DESCRIPTION
config_test.go provides testing for configuration functionality found in config.go.
AUTHORS
Saxon A. 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 revid
import (
"fmt"
"testing"
)
type dumbLogger struct{}
func (dl *dumbLogger) SetLevel(l int8) {}
func (dl *dumbLogger) Log(l int8, m string, p ...interface{}) {}
func TestValidate(t *testing.T) {
tests := []struct {
in Config
check func(c Config) error
err error
}{
{
in: Config{Outputs: []uint8{HTTP}, Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)
}
if c.Quantization != defaultQuantization {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, defaultQuantization)
}
return nil
},
},
{
in: Config{Outputs: []uint8{RTMP}, RTMPURL: "dummURL", Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != defaultBitrate {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, defaultBitrate)
}
if c.Quantization != 0 {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 0)
}
return nil
},
},
{
in: Config{Outputs: []uint8{HTTP}, Quantization: 50, Logger: &dumbLogger{}},
check: func(c Config) error { return nil },
err: errInvalidQuantization,
},
{
in: Config{Outputs: []uint8{HTTP}, Quantization: 20, Logger: &dumbLogger{}},
check: func(c Config) error {
if c.Bitrate != 0 {
return fmt.Errorf("did not get expected bitrate. Got: %v, Want: %v", c.Bitrate, 0)
}
if c.Quantization != 20 {
return fmt.Errorf("did not get expected quantization. Got: %v, Want: %v", c.Quantization, 20)
}
return nil
},
},
}
for i, test := range tests {
err := (&test.in).Validate()
if err != test.err {
t.Errorf("did not get expected error for test %d\nGot: %v\nWant: %v\n", i, err, test.err)
}
err = test.check(test.in)
if err != nil {
t.Errorf("test %d failed with err: %v", i, err)
}
}
}

View File

@ -197,7 +197,7 @@ func (r *Revid) reset(config Config) error {
// revid config. // revid config.
func (r *Revid) setConfig(config Config) error { func (r *Revid) setConfig(config Config) error {
r.config.Logger = config.Logger r.config.Logger = config.Logger
err := config.Validate(r) err := config.Validate()
if err != nil { if err != nil {
return errors.New("Config struct is bad: " + err.Error()) return errors.New("Config struct is bad: " + err.Error())
} }
@ -450,12 +450,12 @@ func (r *Revid) Update(vars map[string]string) error {
case "HttpAddress": case "HttpAddress":
r.config.HTTPAddress = value r.config.HTTPAddress = value
case "Quantization": case "Quantization":
q, err := strconv.ParseUint(value, 10, 0) v, err := strconv.Atoi(value)
if err != nil { if err != nil {
r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", value) r.config.Logger.Log(logger.Warning, pkg+"invalid quantization param", "value", v)
break break
} }
r.config.Quantization = uint(q) r.config.Quantization = uint(v)
case "IntraRefreshPeriod": case "IntraRefreshPeriod":
p, err := strconv.ParseUint(value, 10, 0) p, err := strconv.ParseUint(value, 10, 0)
if err != nil { if err != nil {
@ -579,7 +579,7 @@ func (r *Revid) startRaspivid() (func() error, error) {
"--inline", "--inline",
"--intra", fmt.Sprint(r.config.IntraRefreshPeriod), "--intra", fmt.Sprint(r.config.IntraRefreshPeriod),
) )
if r.config.Quantize { if r.config.Quantization != 0 {
args = append(args, "-qp", fmt.Sprint(r.config.Quantization)) args = append(args, "-qp", fmt.Sprint(r.config.Quantization))
} }
case codecutil.MJPEG: case codecutil.MJPEG: