av/revid/geovision.go

246 lines
6.5 KiB
Go

/*
DESCRIPTION
geovision.go
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
LICENSE
Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean)
It is free software: you can redistribute it and/or modify them
under the terms of the GNU General Public License as published by the
Free Software Foundation, either version 3 of the License, or (at your
option) any later version.
It is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
in gpl.txt. If not, see http://www.gnu.org/licenses.
*/
package revid
import (
"errors"
"fmt"
"time"
"bitbucket.org/ausocean/av/codec/codecutil"
"bitbucket.org/ausocean/av/input/gvctrl"
"bitbucket.org/ausocean/av/protocol/rtcp"
"bitbucket.org/ausocean/av/protocol/rtp"
"bitbucket.org/ausocean/av/protocol/rtsp"
"bitbucket.org/ausocean/utils/logger"
)
const (
gvDefaultCameraIP = "192.168.1.50"
gvDefaultCodec = codecutil.H264
gvDefaultHeight = 720
gvDefaultFrameRate = 25
gvDefaultBitrate = 400
gvDefaultVBRBitrate = 400
gvDefaultMinFrames = 100
gvDefaultVBRQuality = "standard"
)
var (
errGVBadCameraIP = errors.New("camera IP bad or unset, defaulting")
errGVBadCodec = errors.New("codec bad or unset, defaulting")
errGVBadFrameRate = errors.New("frame rate bad or unset, defaulting")
errGVBadBitrate = errors.New("bitrate bad or unset, defaulting")
errGVBadVBRQuality = errors.New("VBR quality bad or unset, defaulting")
errGVBadHeight = errors.New("height bad or unset, defaulting")
errGVBadMinFrames = errors.New("min frames bad or unset, defaulting")
)
type GeoVision struct {
cfg Config
log Logger
}
func NewGeovision(l Logger) *GeoVision { return &GeoVision{log: l} }
func (g *GeoVision) Set(c Config) error {
var errs multiError
if c.CameraIP == "" {
errs = append(errs, errGVBadCameraIP)
c.CameraIP = gvDefaultCameraIP
}
switch c.InputCodec {
case codecutil.H264, codecutil.H265, codecutil.MJPEG:
default:
errs = append(errs, errGVBadCodec)
c.InputCodec = gvDefaultCodec
}
if c.Height <= 0 {
errs = append(errs, errGVBadHeight)
c.Height = gvDefaultHeight
}
if c.FrameRate <= 0 {
errs = append(errs, errGVBadFrameRate)
c.FrameRate = gvDefaultFrameRate
}
if c.Bitrate <= 0 {
errs = append(errs, errGVBadBitrate)
c.Bitrate = gvDefaultBitrate
}
if c.MinFrames <= 0 {
errs = append(errs, errGVBadMinFrames)
c.MinFrames = gvDefaultMinFrames
}
switch c.VBRQuality {
case qualityStandard, qualityFair, qualityGood, qualityGreat, qualityExcellent:
default:
errs = append(errs, errGVBadVBRQuality)
c.VBRQuality = defaultVBRQuality
}
if c.VBRBitrate <= 0 {
errs = append(errs, errGVBadVBRQuality)
c.VBRBitrate = defaultVBRBitrate
}
if c.CameraChan != 1 && c.CameraChan != 2 {
errs = append(errs, errGVBadVBRQuality)
c.CameraChan = defaultCameraChan
}
g.cfg = c
err := gvctrl.Set(
g.cfg.CameraIP,
gvctrl.Channel(g.cfg.CameraChan),
gvctrl.CodecOut(
map[uint8]gvctrl.Codec{
codecutil.H264: gvctrl.CodecH264,
codecutil.H265: gvctrl.CodecH265,
codecutil.MJPEG: gvctrl.CodecMJPEG,
}[g.cfg.InputCodec],
),
gvctrl.Height(int(g.cfg.Height)),
gvctrl.FrameRate(int(g.cfg.FrameRate)),
gvctrl.VariableBitrate(g.cfg.VBR),
gvctrl.VBRQuality(
map[quality]gvctrl.Quality{
qualityStandard: gvctrl.QualityStandard,
qualityFair: gvctrl.QualityFair,
qualityGood: gvctrl.QualityGood,
qualityGreat: gvctrl.QualityGreat,
qualityExcellent: gvctrl.QualityExcellent,
}[g.cfg.VBRQuality],
),
gvctrl.VBRBitrate(g.cfg.VBRBitrate),
gvctrl.CBRBitrate(int(g.cfg.Bitrate)),
gvctrl.Refresh(float64(g.cfg.MinFrames)/float64(g.cfg.FrameRate)),
)
if err != nil {
return fmt.Errorf("could not set IPCamera settings: %w", err)
}
// Give the camera some time to change it's configuration.
const setupDelay = 5 * time.Second
time.Sleep(setupDelay)
return multiError(errs)
}
func (g *GeoVision) Start() error {
rtspClt, local, remote, err := rtsp.NewClient("rtsp://" + ipCamUser + ":" + ipCamPass + "@" + g.cfg.CameraIP + ":8554/" + "CH002.sdp")
if err != nil {
return fmt.Errorf("could not create RTSP client: %w", err)
}
g.log.Log(logger.Info, pkg+"created RTSP client")
resp, err := rtspClt.Options()
if err != nil {
return fmt.Errorf("options request unsuccessful: %w", err)
}
g.log.Log(logger.Debug, pkg+"RTSP OPTIONS response", "response", resp.String())
resp, err = rtspClt.Describe()
if err != nil {
return fmt.Errorf("describe request unsuccessful: %w", err)
}
g.log.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 fmt.Errorf("setup request unsuccessful: %w", err)
}
g.log.Log(logger.Debug, pkg+"RTSP SETUP response", "response", resp.String())
rtpCltAddr, rtcpCltAddr, rtcpSvrAddr, err := formAddrs(local, remote, *resp)
if err != nil {
return fmt.Errorf("could not format addresses: %w", err)
}
g.log.Log(logger.Info, pkg+"RTSP session setup complete")
rtpClt, err := rtp.NewClient(rtpCltAddr)
if err != nil {
return fmt.Errorf("could not create RTP client: %w", err)
}
rtcpClt, err := rtcp.NewClient(rtcpCltAddr, rtcpSvrAddr, rtpClt, g.log.Log)
if err != nil {
return fmt.Errorf("could not create RTCP client: %w", err)
}
g.log.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 {
g.log.Log(logger.Warning, pkg+"RTCP error", "error", err.Error())
} else {
return
}
}
}()
// Start the RTCP client.
rtcpClt.Start()
g.log.Log(logger.Info, pkg+"RTCP client started")
g.log.Log(logger.Info, pkg+"started input processor")
resp, err = rtspClt.Play()
if err != nil {
return fmt.Errorf("play request unsuccessful: %w", err)
}
g.log.Log(logger.Debug, pkg+"RTSP server PLAY response", "response", resp.String())
g.log.Log(logger.Info, pkg+"play requested, now receiving stream")
return nil
}
/*
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()
g.log.Log(logger.Info, pkg+"RTP, RTSP and RTCP clients stopped and closed")*/