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:
Saxon Milton 2019-11-13 06:03:58 +00:00
commit 715585501e
16 changed files with 188 additions and 756 deletions

View File

@ -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")

View File

@ -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.

View File

@ -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 {

View File

@ -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))
}

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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()
}

View File

@ -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()
}