diff --git a/cmd/revid-cli/main.go b/cmd/revid-cli/main.go index 8a0fbe01..ea031a42 100644 --- a/cmd/revid-cli/main.go +++ b/cmd/revid-cli/main.go @@ -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") diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 6c634556..f8595aca 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -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. diff --git a/device/alsa/alsa_test.go b/device/alsa/alsa_test.go index ba11d3f0..08be27a4 100644 --- a/device/alsa/alsa_test.go +++ b/device/alsa/alsa_test.go @@ -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 { diff --git a/device/device.go b/device/device.go index c66c45e3..08739416 100644 --- a/device/device.go +++ b/device/device.go @@ -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)) } diff --git a/device/file/file.go b/device/file/file.go index 8f23c408..14a25b3b 100644 --- a/device/file/file.go +++ b/device/file/file.go @@ -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 { diff --git a/device/geovision/geovision.go b/device/geovision/geovision.go index c51afb59..492a05fb 100644 --- a/device/geovision/geovision.go +++ b/device/geovision/geovision.go @@ -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 diff --git a/device/raspivid/raspivid.go b/device/raspivid/raspivid.go index 59f1a7e9..9502de4b 100644 --- a/device/raspivid/raspivid.go +++ b/device/raspivid/raspivid.go @@ -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 } diff --git a/device/webcam/webcam.go b/device/webcam/webcam.go index 13a1ad32..163e6c11 100644 --- a/device/webcam/webcam.go +++ b/device/webcam/webcam.go @@ -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 diff --git a/go.mod b/go.mod index c0ae53a4..c00cf273 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index bb222988..ab6b1cd9 100644 --- a/go.sum +++ b/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= diff --git a/revid/audio_linux.go b/revid/audio_linux.go deleted file mode 100644 index 2aa6fd84..00000000 --- a/revid/audio_linux.go +++ /dev/null @@ -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 -} diff --git a/revid/audio_windows.go b/revid/audio_windows.go deleted file mode 100644 index bdc61da8..00000000 --- a/revid/audio_windows.go +++ /dev/null @@ -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") -} diff --git a/revid/config/config.go b/revid/config/config.go index 6abc1ca1..88228867 100644 --- a/revid/config/config.go +++ b/revid/config/config.go @@ -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. @@ -112,39 +69,19 @@ const ( // Default config settings 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 - defaultClipDuration = 0 - defaultQuantization = 30 - defaultBitrate = 400 - - // Audio defaults. + defaultInput = InputRaspivid + defaultOutput = OutputHTTP + defaultTimeout = 0 + defaultInputCodec = codecutil.H264 + defaultVerbosity = logger.Error + defaultRtpAddr = "localhost:6970" + defaultCameraIP = "192.168.1.50" + defaultBurstPeriod = 10 // Seconds + defaultMinFrames = 100 + defaultFrameRate = 25 + defaultWriteRate = 25 + defaultClipDuration = 0 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 } diff --git a/revid/config/config_test.go b/revid/config/config_test.go deleted file mode 100644 index 90973c25..00000000 --- a/revid/config/config_test.go +++ /dev/null @@ -1,97 +0,0 @@ -/* -DESCRIPTION - config_test.go provides testing for configuration functionality found in config.go. - -AUTHORS - Saxon A. Nelson-Milton - -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) - } - } -} diff --git a/revid/inputs.go b/revid/inputs.go deleted file mode 100644 index 124d800b..00000000 --- a/revid/inputs.go +++ /dev/null @@ -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 - Alan Noble - Dan Kortschak - Trek Hopton - -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() -} diff --git a/revid/revid.go b/revid/revid.go index f9c48478..4885bc1b 100644 --- a/revid/revid.go +++ b/revid/revid.go @@ -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() - if err != nil { - r.cfg.Logger.Log(logger.Error, pkg+"could not close input", "error", err.Error()) - } + err := r.input.Stop() + if err != nil { + 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() +}