mirror of https://bitbucket.org/ausocean/av.git
Merged in revid-use-devices (pull request #280)
revid: using AVDevice implementations as revid input Approved-by: Alan Noble <anoble@gmail.com>
This commit is contained in:
commit
715585501e
|
@ -41,6 +41,7 @@ import (
|
|||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
"bitbucket.org/ausocean/av/container/mts"
|
||||
"bitbucket.org/ausocean/av/container/mts/meta"
|
||||
"bitbucket.org/ausocean/av/device/raspivid"
|
||||
"bitbucket.org/ausocean/av/revid"
|
||||
"bitbucket.org/ausocean/av/revid/config"
|
||||
"bitbucket.org/ausocean/iot/pi/netsender"
|
||||
|
@ -129,8 +130,8 @@ func handleFlags() config.Config {
|
|||
rotationPtr = flag.Uint("Rotation", 0, "Rotate video output. (0-359 degrees)")
|
||||
brightnessPtr = flag.Uint("Brightness", 50, "Set brightness. (0-100) ")
|
||||
saturationPtr = flag.Int("Saturation", 0, "Set Saturation. (100-100)")
|
||||
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(config.ExposureModes[:], ",")+")")
|
||||
autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(config.AutoWhiteBalanceModes[:], ",")+")")
|
||||
exposurePtr = flag.String("Exposure", "auto", "Set exposure mode. ("+strings.Join(raspivid.ExposureModes[:], ",")+")")
|
||||
autoWhiteBalancePtr = flag.String("Awb", "auto", "Set automatic white balance mode. ("+strings.Join(raspivid.AutoWhiteBalanceModes[:], ",")+")")
|
||||
|
||||
// Audio specific flags.
|
||||
sampleRatePtr = flag.Int("SampleRate", 48000, "Sample rate of recorded audio")
|
||||
|
|
|
@ -94,7 +94,7 @@ type Logger interface {
|
|||
type OpenError error
|
||||
|
||||
// NewALSA initializes and returns an ALSA device which has its logger set as the given logger.
|
||||
func NewALSA(l Logger) *ALSA { return &ALSA{l: l} }
|
||||
func New(l Logger) *ALSA { return &ALSA{l: l} }
|
||||
|
||||
// Set will take a Config struct, check the validity of the relevant fields
|
||||
// and then performs any configuration necessary. If fields are not valid,
|
||||
|
@ -185,10 +185,11 @@ func (d *ALSA) Start() error {
|
|||
|
||||
// Stop will stop recording audio and close the device.
|
||||
// Once an ALSA device has been stopped it cannot be started again. This is likely to change in future.
|
||||
func (d *ALSA) Stop() {
|
||||
func (d *ALSA) Stop() error {
|
||||
d.mu.Lock()
|
||||
d.mode = stopped
|
||||
d.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChunkSize returns the number of bytes written to the ringbuffer per d.RecPeriod.
|
||||
|
|
|
@ -49,7 +49,7 @@ func TestDevice(t *testing.T) {
|
|||
|
||||
// Create a new ALSA device, start, read/lex, and then stop it.
|
||||
l := logger.New(logger.Debug, os.Stderr, true)
|
||||
ai := NewALSA(l)
|
||||
ai := New(l)
|
||||
err := ai.Set(c)
|
||||
// If there was an error opening the device, skip this test.
|
||||
if _, ok := err.(OpenError); ok {
|
||||
|
|
|
@ -60,5 +60,5 @@ type AVDevice interface {
|
|||
type MultiError []error
|
||||
|
||||
func (me MultiError) Error() string {
|
||||
return fmt.Sprintf("%v", me)
|
||||
return fmt.Sprintf("%v", []error(me))
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ type AVFile struct {
|
|||
}
|
||||
|
||||
// NewAVFile returns a new AVFile.
|
||||
func NewAVFile() *AVFile { return &AVFile{} }
|
||||
func New() *AVFile { return &AVFile{} }
|
||||
|
||||
// Set simply sets the AVFile's config to the passed config.
|
||||
func (m *AVFile) Set(c config.Config) error {
|
||||
|
|
|
@ -95,7 +95,7 @@ type GeoVision struct {
|
|||
}
|
||||
|
||||
// NewGeoVision returns a new GeoVision.
|
||||
func NewGeoVision(l avconfig.Logger) *GeoVision { return &GeoVision{log: l} }
|
||||
func New(l avconfig.Logger) *GeoVision { return &GeoVision{log: l} }
|
||||
|
||||
// Set will take a Config struct, check the validity of the relevant fields
|
||||
// and then performs any configuration necessary using config to control the
|
||||
|
|
|
@ -111,8 +111,8 @@ type Raspivid struct {
|
|||
log config.Logger
|
||||
}
|
||||
|
||||
// NewRaspivid returns a new Raspivid.
|
||||
func NewRaspivid(l config.Logger) *Raspivid { return &Raspivid{log: l} }
|
||||
// New returns a new Raspivid.
|
||||
func New(l config.Logger) *Raspivid { return &Raspivid{log: l} }
|
||||
|
||||
// Set will take a Config struct, check the validity of the relevant fields
|
||||
// and then performs any configuration necessary. If fields are not valid,
|
||||
|
@ -175,7 +175,7 @@ func (r *Raspivid) Set(c config.Config) error {
|
|||
c.Saturation = defaultRaspividSaturation
|
||||
}
|
||||
|
||||
if c.Exposure == "" || !stringInSlice(c.Exposure, config.ExposureModes[:]) {
|
||||
if c.Exposure == "" || !stringInSlice(c.Exposure, ExposureModes[:]) {
|
||||
errs = append(errs, errBadExposure)
|
||||
c.Exposure = defaultRaspividExposure
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ var (
|
|||
errBadBitrate = errors.New("bitrate bad or unset, defaulting")
|
||||
errBadWidth = errors.New("width bad or unset, defaulting")
|
||||
errBadHeight = errors.New("height bad or unset, defaulting")
|
||||
errBadInputPath = errors.New("input path bad or unset, defaulting")
|
||||
)
|
||||
|
||||
// Webcam is an implementation of the AVDevice interface for a Webcam. Webcam
|
||||
|
@ -65,8 +66,8 @@ type Webcam struct {
|
|||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// NewWebcam returns a new Webcam.
|
||||
func NewWebcam(l config.Logger) *Webcam {
|
||||
// New returns a new Webcam.
|
||||
func New(l config.Logger) *Webcam {
|
||||
return &Webcam{log: l}
|
||||
}
|
||||
|
||||
|
@ -75,6 +76,12 @@ func NewWebcam(l config.Logger) *Webcam {
|
|||
// added to the multiError and a default value is used.
|
||||
func (w *Webcam) Set(c config.Config) error {
|
||||
var errs device.MultiError
|
||||
if c.InputPath == "" {
|
||||
const defaultInputPath = "/dev/video0"
|
||||
errs = append(errs, errBadInputPath)
|
||||
c.InputPath = defaultInputPath
|
||||
}
|
||||
|
||||
if c.Width == 0 {
|
||||
errs = append(errs, errBadWidth)
|
||||
c.Width = defaultWidth
|
||||
|
|
2
go.mod
2
go.mod
|
@ -5,7 +5,6 @@ go 1.13
|
|||
require (
|
||||
bitbucket.org/ausocean/iot v1.2.7
|
||||
bitbucket.org/ausocean/utils v1.2.10
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7
|
||||
github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480
|
||||
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884
|
||||
|
@ -13,6 +12,7 @@ require (
|
|||
github.com/mewkiz/flac v1.0.5
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e
|
||||
google.golang.org/api v0.13.0
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
|
|
79
go.sum
79
go.sum
|
@ -3,6 +3,10 @@ bitbucket.org/ausocean/iot v1.2.7/go.mod h1:aAWgPo2f8sD2OPmxae1E5/iD9+tKY/iW4pcQ
|
|||
bitbucket.org/ausocean/utils v1.2.9/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
|
||||
bitbucket.org/ausocean/utils v1.2.10 h1:JTS7n+K+0o/FQFWKjdGgA1ElZ4TQu9aHX3wTJXOayXw=
|
||||
bitbucket.org/ausocean/utils v1.2.10/go.mod h1:uXzX9z3PLemyURTMWRhVI8uLhPX4uuvaaO85v2hcob8=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Comcast/gots v0.0.0-20190305015453-8d56e473f0f7 h1:LdOc9B9Bj6LEsKiXShkLA3/kpxXb6LJpH+ekU2krbzw=
|
||||
|
@ -12,6 +16,7 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
|
|||
github.com/adrianmo/go-nmea v1.1.1-0.20190109062325-c448653979f7/go.mod h1:HHPxPAm2kmev+61qmkZh7xgZF/7qHtSpsWppip2Ipv8=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -22,7 +27,24 @@ github.com/go-audio/audio v0.0.0-20181013203223-7b2a6ca21480/go.mod h1:6uAu0+H2l
|
|||
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884 h1:2TaXIaVA4ff/MHHezOj83tCypALTFAcXOImcFWNa3jw=
|
||||
github.com/go-audio/wav v0.0.0-20181013172942-de841e69b884/go.mod h1:UiqzUyfX0zs3pJ/DPyvS5v8sN6s5bXPUDDIVA5v8dks=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/jacobsa/go-serial v0.0.0-20180131005756-15cf729a72d4/go.mod h1:2RvX5ZjVtsznNZPEt4xwJXNJrM3VTZoQf7V6gk0ysvs=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/kidoman/embd v0.0.0-20170508013040-d3d8c0c5c68d/go.mod h1:ACKj9jnzOzj1lw2ETilpFGK7L9dtJhAzT7T1OhAGtRQ=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
|
@ -46,6 +68,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e h1:3NIzz7weXhh3NToPgbtlQtKiVgerEaG4/nY2skGoGG0=
|
||||
github.com/yobert/alsa v0.0.0-20180630182551-d38d89fa843e/go.mod h1:CaowXBWOiSGWEpBBV8LoVnQTVPV4ycyviC9IBLj8dRw=
|
||||
github.com/yryz/ds18b20 v0.0.0-20180211073435-3cf383a40624/go.mod h1:MqFju5qeLDFh+S9PqxYT7TEla8xeW7bgGr/69q3oki0=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
|
@ -53,7 +77,59 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
|
|||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190305064518-30e92a19ae4a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
|
@ -62,3 +138,6 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXL
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
/*
|
||||
LICENSE
|
||||
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
This 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 (
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/device/alsa"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
// startAudioDevice is used to start capturing audio from an ALSA audio device and processing it.
|
||||
// It returns a function that can be used to stop the device and any errors that occur.
|
||||
func (r *Revid) startAudioDevice() (func() error, error) {
|
||||
// Create audio device.
|
||||
ai := alsa.NewALSA(r.cfg.Logger)
|
||||
|
||||
err := ai.Set(r.cfg)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Fatal, pkg+"failed to setup ALSA device", "error", err.Error())
|
||||
}
|
||||
|
||||
// Start ALSA audio device
|
||||
err = ai.Start()
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Fatal, pkg+"failed to start ALSA device", "error", err.Error())
|
||||
}
|
||||
|
||||
// Process output from ALSA audio device.
|
||||
r.cfg.ChunkSize = ai.ChunkSize()
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(ai, time.Duration(float64(time.Second)/r.cfg.WriteRate))
|
||||
return func() error {
|
||||
ai.Stop()
|
||||
return nil
|
||||
}, nil
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
LICENSE
|
||||
Copyright (C) 2019 the Australian Ocean Lab (AusOcean)
|
||||
|
||||
This 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.
|
||||
*/
|
||||
|
||||
// startAudioDevice is used to start capturing audio from an audio device
|
||||
// TODO: Implement on Windows.
|
||||
package revid
|
||||
|
||||
func (r *Revid) startAudioDevice() (func() error, error) {
|
||||
panic("Audio not implemented on Windows")
|
||||
}
|
|
@ -33,36 +33,6 @@ import (
|
|||
"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",
|
||||
}
|
||||
|
||||
const pkg = "config: "
|
||||
|
||||
type Logger interface {
|
||||
|
@ -70,19 +40,6 @@ type Logger interface {
|
|||
Log(level int8, message string, params ...interface{})
|
||||
}
|
||||
|
||||
// quality represents video quality.
|
||||
type Quality int
|
||||
|
||||
// The different video qualities that can be used for variable bitrate when
|
||||
// using the GeoVision camera.
|
||||
const (
|
||||
QualityStandard Quality = iota
|
||||
QualityFair
|
||||
QualityGood
|
||||
QualityGreat
|
||||
QualityExcellent
|
||||
)
|
||||
|
||||
// Enums to define inputs, outputs and codecs.
|
||||
const (
|
||||
// Indicates no option has been set.
|
||||
|
@ -114,37 +71,17 @@ const (
|
|||
// General revid defaults.
|
||||
defaultInput = InputRaspivid
|
||||
defaultOutput = OutputHTTP
|
||||
defaultFrameRate = 25
|
||||
defaultWriteRate = 25
|
||||
defaultTimeout = 0
|
||||
defaultInputCodec = codecutil.H264
|
||||
defaultVerbosity = logger.Error
|
||||
defaultRtpAddr = "localhost:6970"
|
||||
defaultCameraIP = "192.168.1.50"
|
||||
defaultVBR = false
|
||||
defaultVBRQuality = QualityStandard
|
||||
defaultBurstPeriod = 10 // Seconds
|
||||
defaultVBRBitrate = 500 // kbps
|
||||
defaultCameraChan = 2
|
||||
|
||||
// Raspivid video defaults.
|
||||
defaultBrightness = 50
|
||||
defaultExposure = "auto"
|
||||
defaultAutoWhiteBalance = "auto"
|
||||
defaultRotation = 0 // Degrees
|
||||
defaultWidth = 1280
|
||||
defaultHeight = 720
|
||||
defaultMinFrames = 100
|
||||
defaultFrameRate = 25
|
||||
defaultWriteRate = 25
|
||||
defaultClipDuration = 0
|
||||
defaultQuantization = 30
|
||||
defaultBitrate = 400
|
||||
|
||||
// Audio defaults.
|
||||
defaultAudioInputCodec = codecutil.ADPCM
|
||||
defaultSampleRate = 48000
|
||||
defaultBitDepth = 16
|
||||
defaultChannels = 1
|
||||
defaultRecPeriod = 1.0
|
||||
|
||||
// MTS ring buffer defaults.
|
||||
defaultMTSRBSize = 100
|
||||
|
@ -157,6 +94,19 @@ const (
|
|||
defaultRTMPRBWriteTimeout = 5
|
||||
)
|
||||
|
||||
// quality represents video quality.
|
||||
type Quality int
|
||||
|
||||
// The different video qualities that can be used for variable bitrate when
|
||||
// using the GeoVision camera.
|
||||
const (
|
||||
QualityStandard Quality = iota
|
||||
QualityFair
|
||||
QualityGood
|
||||
QualityGreat
|
||||
QualityExcellent
|
||||
)
|
||||
|
||||
// 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.
|
||||
|
@ -352,8 +302,6 @@ func (c *Config) Validate() error {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -362,28 +310,13 @@ func (c *Config) Validate() error {
|
|||
c.Outputs = append(c.Outputs, defaultOutput)
|
||||
}
|
||||
|
||||
var haveRTMPOut bool
|
||||
for i, o := range c.Outputs {
|
||||
switch o {
|
||||
case OutputFile:
|
||||
case OutputFile, OutputHTTP, OutputRTP:
|
||||
case OutputRTMP:
|
||||
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] = OutputHTTP
|
||||
haveRTMPOut = false
|
||||
}
|
||||
fallthrough
|
||||
case OutputHTTP, OutputRTP:
|
||||
if !haveRTMPOut {
|
||||
c.Bitrate = 0
|
||||
if c.Quantization == 0 {
|
||||
c.Quantization = defaultQuantization
|
||||
}
|
||||
}
|
||||
default:
|
||||
return errors.New("bad output type defined in config")
|
||||
|
@ -395,55 +328,6 @@ func (c *Config) Validate() error {
|
|||
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
|
||||
|
@ -451,6 +335,16 @@ func (c *Config) Validate() error {
|
|||
return errors.New("refresh period is less than 0")
|
||||
}
|
||||
|
||||
if c.FrameRate <= 0 {
|
||||
c.Logger.Log(logger.Info, pkg+"frame rate bad or unset, defaulting", "FrameRate", defaultFrameRate)
|
||||
c.FrameRate = defaultFrameRate
|
||||
}
|
||||
|
||||
if c.WriteRate <= 0 {
|
||||
c.Logger.Log(logger.Info, pkg+"write rate bad or unset, defaulting", "writeRate", defaultWriteRate)
|
||||
c.WriteRate = defaultWriteRate
|
||||
}
|
||||
|
||||
if c.ClipDuration == 0 {
|
||||
c.Logger.Log(logger.Info, pkg+"no clip duration defined, defaulting", "ClipDuration", defaultClipDuration)
|
||||
c.ClipDuration = defaultClipDuration
|
||||
|
@ -458,42 +352,10 @@ func (c *Config) Validate() error {
|
|||
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
|
||||
|
@ -524,23 +386,6 @@ func (c *Config) Validate() error {
|
|||
c.MTSRBWriteTimeout = defaultMTSRBWriteTimeout
|
||||
}
|
||||
|
||||
switch c.VBRQuality {
|
||||
case QualityStandard, QualityFair, QualityGood, QualityGreat, QualityExcellent:
|
||||
default:
|
||||
c.Logger.Log(logger.Info, pkg+"VBRQuality bad or unset, defaulting", "VBRQuality", defaultVBRQuality)
|
||||
c.VBRQuality = defaultVBRQuality
|
||||
}
|
||||
|
||||
if c.VBRBitrate <= 0 {
|
||||
c.Logger.Log(logger.Info, pkg+"VBRBitrate bad or unset, defaulting", "VBRBitrate", defaultVBRBitrate)
|
||||
c.VBRBitrate = defaultVBRBitrate
|
||||
}
|
||||
|
||||
if c.CameraChan != 1 && c.CameraChan != 2 {
|
||||
c.Logger.Log(logger.Info, pkg+"CamChan bad or unset, defaulting", "CamChan", defaultCameraChan)
|
||||
c.CameraChan = defaultCameraChan
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
/*
|
||||
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 config
|
||||
|
||||
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{OutputHTTP}, 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{OutputRTMP}, 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{OutputHTTP}, Quantization: 50, Logger: &dumbLogger{}},
|
||||
check: func(c Config) error { return nil },
|
||||
err: errInvalidQuantization,
|
||||
},
|
||||
{
|
||||
in: Config{Outputs: []uint8{OutputHTTP}, 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)
|
||||
}
|
||||
}
|
||||
}
|
358
revid/inputs.go
358
revid/inputs.go
|
@ -1,358 +0,0 @@
|
|||
/*
|
||||
DESCRIPTION
|
||||
inputs.go contains code for interfacing with various revid inputs to obtain
|
||||
input data streams.
|
||||
|
||||
AUTHORS
|
||||
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
||||
Alan Noble <alan@ausocean.org>
|
||||
Dan Kortschak <dan@ausocean.org>
|
||||
Trek Hopton <trek@ausocean.org>
|
||||
|
||||
LICENSE
|
||||
revid is 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"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/ausocean/av/codec/codecutil"
|
||||
gvconfig "bitbucket.org/ausocean/av/device/geovision/config"
|
||||
"bitbucket.org/ausocean/av/protocol/rtcp"
|
||||
"bitbucket.org/ausocean/av/protocol/rtp"
|
||||
"bitbucket.org/ausocean/av/protocol/rtsp"
|
||||
avconfig "bitbucket.org/ausocean/av/revid/config"
|
||||
"bitbucket.org/ausocean/utils/logger"
|
||||
)
|
||||
|
||||
// TODO: remove this when config has configurable user and pass.
|
||||
const (
|
||||
ipCamUser = "admin"
|
||||
ipCamPass = "admin"
|
||||
)
|
||||
|
||||
// Constants for real time clients.
|
||||
const (
|
||||
rtpPort = 60000
|
||||
rtcpPort = 60001
|
||||
defaultServerRTCPPort = 17301
|
||||
)
|
||||
|
||||
// startRaspivid sets up things for input from raspivid i.e. starts
|
||||
// a raspivid process and pipes it's data output.
|
||||
func (r *Revid) startRaspivid() (func() error, error) {
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"starting raspivid")
|
||||
|
||||
const disabled = "0"
|
||||
args := []string{
|
||||
"--output", "-",
|
||||
"--nopreview",
|
||||
"--timeout", disabled,
|
||||
"--width", fmt.Sprint(r.cfg.Width),
|
||||
"--height", fmt.Sprint(r.cfg.Height),
|
||||
"--bitrate", fmt.Sprint(r.cfg.Bitrate * 1000), // Convert from kbps to bps.
|
||||
"--framerate", fmt.Sprint(r.cfg.FrameRate),
|
||||
"--rotation", fmt.Sprint(r.cfg.Rotation),
|
||||
"--brightness", fmt.Sprint(r.cfg.Brightness),
|
||||
"--saturation", fmt.Sprint(r.cfg.Saturation),
|
||||
"--exposure", fmt.Sprint(r.cfg.Exposure),
|
||||
"--awb", fmt.Sprint(r.cfg.AutoWhiteBalance),
|
||||
}
|
||||
|
||||
if r.cfg.FlipHorizontal {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
if r.cfg.FlipVertical {
|
||||
args = append(args, "--vflip")
|
||||
}
|
||||
if r.cfg.FlipHorizontal {
|
||||
args = append(args, "--hflip")
|
||||
}
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
default:
|
||||
return nil, fmt.Errorf("revid: invalid input codec: %v", r.cfg.InputCodec)
|
||||
case codecutil.H264:
|
||||
args = append(args,
|
||||
"--codec", "H264",
|
||||
"--inline",
|
||||
"--intra", fmt.Sprint(r.cfg.MinFrames),
|
||||
)
|
||||
if r.cfg.VBR {
|
||||
args = append(args, "-qp", fmt.Sprint(r.cfg.Quantization))
|
||||
}
|
||||
case codecutil.MJPEG:
|
||||
args = append(args, "--codec", "MJPEG")
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"raspivid args", "raspividArgs", strings.Join(args, " "))
|
||||
r.cmd = exec.Command("raspivid", args...)
|
||||
|
||||
stdout, err := r.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = r.cmd.Start()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not start raspivid command: %w", err)
|
||||
}
|
||||
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(stdout, 0)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// startV4l sets up webcam input and starts the revid.processFrom routine.
|
||||
func (r *Revid) startV4L() (func() error, error) {
|
||||
const defaultVideo = "/dev/video0"
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"starting webcam")
|
||||
if r.cfg.InputPath == "" {
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"using default video device", "device", defaultVideo)
|
||||
r.cfg.InputPath = defaultVideo
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"-i", r.cfg.InputPath,
|
||||
"-f", "h264",
|
||||
"-r", fmt.Sprint(r.cfg.FrameRate),
|
||||
}
|
||||
|
||||
br := r.cfg.Bitrate * 1000
|
||||
args = append(args,
|
||||
"-b:v", fmt.Sprint(br),
|
||||
"-maxrate", fmt.Sprint(br),
|
||||
"-bufsize", fmt.Sprint(br/2),
|
||||
"-s", fmt.Sprintf("%dx%d", r.cfg.Width, r.cfg.Height),
|
||||
"-",
|
||||
)
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"ffmpeg args", "args", strings.Join(args, " "))
|
||||
r.cmd = exec.Command("ffmpeg", args...)
|
||||
|
||||
stdout, err := r.cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
err = r.cmd.Start()
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Fatal, pkg+"cannot start webcam", "error", err.Error())
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(stdout, time.Duration(0))
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// setupInputForFile sets up input from file and starts the revid.processFrom
|
||||
// routine.
|
||||
func (r *Revid) setupInputForFile() (func() error, error) {
|
||||
f, err := os.Open(r.cfg.InputPath)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Error, err.Error())
|
||||
r.Stop()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(kortschak): Maybe we want a context.Context-aware parser that we can stop.
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(f, 0)
|
||||
return func() error { return f.Close() }, nil
|
||||
}
|
||||
|
||||
// startRTSPCamera uses RTSP to request an RTP stream from an IP camera. An RTP
|
||||
// client is created from which RTP packets containing either h264/h265 can be
|
||||
// read by the selected lexer.
|
||||
//
|
||||
// TODO(saxon): this function should really be startGeoVision. It's much too
|
||||
// specific to be called startRTSPCamera.
|
||||
func (r *Revid) startRTSPCamera() (func() error, error) {
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"starting geovision...")
|
||||
|
||||
err := gvconfig.Set(
|
||||
r.cfg.CameraIP,
|
||||
gvconfig.Channel(r.cfg.CameraChan),
|
||||
gvconfig.CodecOut(
|
||||
map[uint8]gvconfig.Codec{
|
||||
codecutil.H264: gvconfig.CodecH264,
|
||||
codecutil.H265: gvconfig.CodecH265,
|
||||
codecutil.MJPEG: gvconfig.CodecMJPEG,
|
||||
}[r.cfg.InputCodec],
|
||||
),
|
||||
gvconfig.Height(int(r.cfg.Height)),
|
||||
gvconfig.FrameRate(int(r.cfg.FrameRate)),
|
||||
gvconfig.VariableBitrate(r.cfg.VBR),
|
||||
gvconfig.VBRQuality(
|
||||
map[avconfig.Quality]gvconfig.Quality{
|
||||
avconfig.QualityStandard: gvconfig.QualityStandard,
|
||||
avconfig.QualityFair: gvconfig.QualityFair,
|
||||
avconfig.QualityGood: gvconfig.QualityGood,
|
||||
avconfig.QualityGreat: gvconfig.QualityGreat,
|
||||
avconfig.QualityExcellent: gvconfig.QualityExcellent,
|
||||
}[r.cfg.VBRQuality],
|
||||
),
|
||||
gvconfig.VBRBitrate(r.cfg.VBRBitrate),
|
||||
gvconfig.CBRBitrate(int(r.cfg.Bitrate)),
|
||||
gvconfig.Refresh(float64(r.cfg.MinFrames)/float64(r.cfg.FrameRate)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not set IPCamera settings: %w", err)
|
||||
}
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"completed geovision configuration")
|
||||
|
||||
time.Sleep(5 * time.Second)
|
||||
|
||||
rtspClt, local, remote, err := rtsp.NewClient("rtsp://" + ipCamUser + ":" + ipCamPass + "@" + r.cfg.CameraIP + ":8554/" + "CH002.sdp")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"created RTSP client")
|
||||
|
||||
resp, err := rtspClt.Options()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Debug, pkg+"RTSP OPTIONS response", "response", resp.String())
|
||||
|
||||
resp, err = rtspClt.Describe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Debug, pkg+"RTSP DESCRIBE response", "response", resp.String())
|
||||
|
||||
resp, err = rtspClt.Setup("track1", fmt.Sprintf("RTP/AVP;unicast;client_port=%d-%d", rtpPort, rtcpPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Debug, pkg+"RTSP SETUP response", "response", resp.String())
|
||||
|
||||
rtpCltAddr, rtcpCltAddr, rtcpSvrAddr, err := formAddrs(local, remote, *resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"RTSP session setup complete")
|
||||
|
||||
rtpClt, err := rtp.NewClient(rtpCltAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rtcpClt, err := rtcp.NewClient(rtcpCltAddr, rtcpSvrAddr, rtpClt, r.cfg.Logger.Log)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"RTCP and RTP clients created")
|
||||
|
||||
// Check errors from RTCP client until it has stopped running.
|
||||
go func() {
|
||||
for {
|
||||
err, ok := <-rtcpClt.Err()
|
||||
if ok {
|
||||
r.cfg.Logger.Log(logger.Warning, pkg+"RTCP error", "error", err.Error())
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Start the RTCP client.
|
||||
rtcpClt.Start()
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"RTCP client started")
|
||||
|
||||
// Start reading data from the RTP client.
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(rtpClt, time.Second/time.Duration(r.cfg.FrameRate))
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"started input processor")
|
||||
|
||||
resp, err = rtspClt.Play()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Debug, pkg+"RTSP server PLAY response", "response", resp.String())
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"play requested, now receiving stream")
|
||||
|
||||
return func() error {
|
||||
err := rtpClt.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not close RTP client: %w", err)
|
||||
}
|
||||
|
||||
err = rtspClt.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not close RTSP client: %w", err)
|
||||
}
|
||||
|
||||
rtcpClt.Stop()
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"RTP, RTSP and RTCP clients stopped and closed")
|
||||
return nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// formAddrs is a helper function to form the addresses for the RTP client,
|
||||
// RTCP client, and the RTSP server's RTCP addr using the local, remote addresses
|
||||
// of the RTSP conn, and the SETUP method response.
|
||||
func formAddrs(local, remote *net.TCPAddr, setupResp rtsp.Response) (rtpCltAddr, rtcpCltAddr, rtcpSvrAddr string, err error) {
|
||||
svrRTCPPort, err := parseSvrRTCPPort(setupResp)
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
rtpCltAddr = strings.Split(local.String(), ":")[0] + ":" + strconv.Itoa(rtpPort)
|
||||
rtcpCltAddr = strings.Split(local.String(), ":")[0] + ":" + strconv.Itoa(rtcpPort)
|
||||
rtcpSvrAddr = strings.Split(remote.String(), ":")[0] + ":" + strconv.Itoa(svrRTCPPort)
|
||||
return
|
||||
}
|
||||
|
||||
// parseServerRTCPPort is a helper function to get the RTSP server's RTCP port.
|
||||
func parseSvrRTCPPort(resp rtsp.Response) (int, error) {
|
||||
transport := resp.Header.Get("Transport")
|
||||
for _, p := range strings.Split(transport, ";") {
|
||||
if strings.Contains(p, "server_port") {
|
||||
port, err := strconv.Atoi(strings.Split(p, "-")[1])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return port, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("SETUP response did not provide RTCP port")
|
||||
}
|
||||
|
||||
// processFrom is run as a routine to read from a input data source, lex and
|
||||
// then send individual access units to revid's encoders.
|
||||
func (r *Revid) processFrom(read io.Reader, delay time.Duration) {
|
||||
r.err <- r.lexTo(r.encoders, read, delay)
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"finished lexing")
|
||||
r.wg.Done()
|
||||
}
|
|
@ -44,6 +44,12 @@ import (
|
|||
"bitbucket.org/ausocean/av/codec/mjpeg"
|
||||
"bitbucket.org/ausocean/av/container/flv"
|
||||
"bitbucket.org/ausocean/av/container/mts"
|
||||
"bitbucket.org/ausocean/av/device"
|
||||
"bitbucket.org/ausocean/av/device/alsa"
|
||||
"bitbucket.org/ausocean/av/device/file"
|
||||
"bitbucket.org/ausocean/av/device/geovision"
|
||||
"bitbucket.org/ausocean/av/device/raspivid"
|
||||
"bitbucket.org/ausocean/av/device/webcam"
|
||||
"bitbucket.org/ausocean/av/revid/config"
|
||||
"bitbucket.org/ausocean/iot/pi/netsender"
|
||||
"bitbucket.org/ausocean/utils/ioext"
|
||||
|
@ -89,9 +95,8 @@ type Revid struct {
|
|||
// ns holds the netsender.Sender responsible for HTTP.
|
||||
ns *netsender.Sender
|
||||
|
||||
// setupInput holds the current approach to setting up
|
||||
// the input stream. It returns a function used for cleaning up, and any errors.
|
||||
setupInput func() (func() error, error)
|
||||
// input will capture audio or video from which we can read data.
|
||||
input device.AVDevice
|
||||
|
||||
// closeInput holds the cleanup function return from setupInput and is called
|
||||
// in Revid.Stop().
|
||||
|
@ -314,20 +319,25 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
|
||||
switch r.cfg.Input {
|
||||
case config.InputRaspivid:
|
||||
r.setupInput = r.startRaspivid
|
||||
r.input = raspivid.New(r.cfg.Logger)
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
case codecutil.H264:
|
||||
r.lexTo = h264.Lex
|
||||
case codecutil.MJPEG:
|
||||
r.lexTo = mjpeg.Lex
|
||||
}
|
||||
|
||||
case config.InputV4L:
|
||||
r.setupInput = r.startV4L
|
||||
r.input = webcam.New(r.cfg.Logger)
|
||||
r.lexTo = h264.Lex
|
||||
|
||||
case config.InputFile:
|
||||
r.setupInput = r.setupInputForFile
|
||||
r.input = file.New()
|
||||
|
||||
case config.InputRTSP:
|
||||
r.setupInput = r.startRTSPCamera
|
||||
r.input = geovision.New(r.cfg.Logger)
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
case codecutil.H264:
|
||||
r.lexTo = h264.NewExtractor().Extract
|
||||
|
@ -336,11 +346,13 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
case codecutil.MJPEG:
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
case config.InputAudio:
|
||||
mts.Meta.Add("sampleRate", strconv.Itoa(r.cfg.SampleRate))
|
||||
mts.Meta.Add("channels", strconv.Itoa(r.cfg.Channels))
|
||||
mts.Meta.Add("period", fmt.Sprintf("%.6f", r.cfg.RecPeriod))
|
||||
mts.Meta.Add("bitDepth", strconv.Itoa(r.cfg.BitDepth))
|
||||
|
||||
switch r.cfg.InputCodec {
|
||||
case codecutil.PCM:
|
||||
mts.Meta.Add("codec", "pcm")
|
||||
|
@ -349,8 +361,17 @@ func (r *Revid) setupPipeline(mtsEnc func(dst io.WriteCloser, rate float64) (io.
|
|||
default:
|
||||
r.cfg.Logger.Log(logger.Fatal, pkg+"no audio codec set in config")
|
||||
}
|
||||
r.setupInput = r.startAudioDevice
|
||||
r.lexTo = codecutil.NewByteLexer(&r.cfg.ChunkSize).Lex
|
||||
|
||||
r.input = alsa.New(r.cfg.Logger)
|
||||
cs := r.input.(*alsa.ALSA).ChunkSize()
|
||||
r.lexTo = codecutil.NewByteLexer(&cs).Lex
|
||||
}
|
||||
|
||||
// Configure the input device. We know that defaults are set, so no need to
|
||||
// return error, but we should log.
|
||||
err := r.input.Set(r.cfg)
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Warning, pkg+"errors from configuring input device", "errors", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -375,10 +396,15 @@ func (r *Revid) Start() error {
|
|||
r.Stop()
|
||||
return err
|
||||
}
|
||||
r.closeInput, err = r.setupInput()
|
||||
|
||||
err = r.input.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not start input device: %w", err)
|
||||
}
|
||||
|
||||
r.wg.Add(1)
|
||||
go r.processFrom(r.input, 0)
|
||||
|
||||
r.running = true
|
||||
return nil
|
||||
}
|
||||
|
@ -396,15 +422,13 @@ func (r *Revid) Stop() {
|
|||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if r.closeInput != nil {
|
||||
err := r.closeInput()
|
||||
err := r.input.Stop()
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Error, pkg+"could not close input", "error", err.Error())
|
||||
}
|
||||
r.cfg.Logger.Log(logger.Error, pkg+"could not stop input", "error", err.Error())
|
||||
}
|
||||
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"closing pipeline")
|
||||
err := r.encoders.Close()
|
||||
err = r.encoders.Close()
|
||||
if err != nil {
|
||||
r.cfg.Logger.Log(logger.Error, pkg+"failed to close pipeline", "error", err.Error())
|
||||
}
|
||||
|
@ -443,7 +467,7 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
for key, value := range vars {
|
||||
switch key {
|
||||
case "Input":
|
||||
v, ok := map[string]uint8{"raspivid": config.InputRaspivid, "rtsp": config.InputRTSP}[strings.ToLower(value)]
|
||||
v, ok := map[string]uint8{"raspivid": config.InputRaspivid, "rtsp": config.InputRTSP, "v4l": config.InputV4L}[strings.ToLower(value)]
|
||||
if !ok {
|
||||
r.cfg.Logger.Log(logger.Warning, pkg+"invalid input var", "value", value)
|
||||
break
|
||||
|
@ -679,3 +703,11 @@ func (r *Revid) Update(vars map[string]string) error {
|
|||
r.cfg.Logger.Log(logger.Info, pkg+"revid config changed", "config", fmt.Sprintf("%+v", r.cfg))
|
||||
return nil
|
||||
}
|
||||
|
||||
// processFrom is run as a routine to read from a input data source, lex and
|
||||
// then send individual access units to revid's encoders.
|
||||
func (r *Revid) processFrom(read io.Reader, delay time.Duration) {
|
||||
r.err <- r.lexTo(r.encoders, read, delay)
|
||||
r.cfg.Logger.Log(logger.Info, pkg+"finished lexing")
|
||||
r.wg.Done()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue