av/cmd/revid-cli/main.go

489 lines
12 KiB
Go
Raw Normal View History

2018-04-19 10:03:12 +03:00
/*
NAME
2018-06-27 01:20:05 +03:00
revid-cli - command line interface for Revid.
2018-04-19 10:03:12 +03:00
DESCRIPTION
See Readme.md
AUTHORS
2018-06-20 08:08:34 +03:00
Saxon A. Nelson-Milton <saxon@ausocean.org>
Jack Richardson <jack@ausocean.org>
2018-04-19 10:03:12 +03:00
LICENSE
2018-06-27 01:20:05 +03:00
revid-cli is Copyright (C) 2017-2018 the Australian Ocean Lab (AusOcean)
2018-04-19 10:03:12 +03:00
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
2018-06-20 08:08:34 +03:00
for more details.
2018-04-19 10:03:12 +03:00
You should have received a copy of the GNU General Public License
2018-06-20 08:08:34 +03:00
along with revid in gpl.txt. If not, see http://www.gnu.org/licenses.
2018-04-19 10:03:12 +03:00
*/
2018-07-05 14:24:10 +03:00
package main
import (
"flag"
2018-05-06 11:38:45 +03:00
"fmt"
2018-07-05 14:24:10 +03:00
"log"
"os"
2018-07-05 14:24:10 +03:00
"runtime/pprof"
"strconv"
2018-04-19 11:19:36 +03:00
"time"
2018-04-19 08:58:16 +03:00
2018-04-19 11:19:36 +03:00
"bitbucket.org/ausocean/av/revid"
2018-05-03 06:51:28 +03:00
"bitbucket.org/ausocean/iot/pi/netsender"
2018-05-24 06:35:29 +03:00
"bitbucket.org/ausocean/utils/smartlogger"
)
const (
// progName is the program name for logging purposes.
progName = "revid-cli"
// Logging is set to INFO level.
loggerVerbosity = 3
)
2018-04-19 11:11:18 +03:00
// Indexes for configFlags
const (
2018-06-07 14:50:57 +03:00
inputPtr = iota
inputCodecPtr
outputPtr
rtmpMethodPtr
packetizationPtr
quantizationModePtr
verbosityPtr
framesPerClipPtr
rtmpUrlPtr
bitratePtr
outputFileNamePtr
inputFileNamePtr
heightPtr
widthPtr
frameRatePtr
httpAddressPtr
quantizationPtr
timeoutPtr
intraRefreshPeriodPtr
verticalFlipPtr
horizontalFlipPtr
noOfConfigFlags
)
// Other misc consts
const (
netSendRetryTime = 5 * time.Second
defaultRunDuration = 24 * time.Hour
revidStopTime = 5 * time.Second
)
2018-07-05 14:24:10 +03:00
// canProfile is set to false with revid-cli is built with "-tags profile".
var canProfile = true
// Globals
var (
rv *revid.Revid
config revid.Config
2018-04-19 08:58:16 +03:00
)
func main() {
2018-07-05 14:24:10 +03:00
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`")
2018-06-07 14:50:57 +03:00
flagNames := [noOfConfigFlags]struct{ name, description string }{
{"Input", "The input type: Raspivid, File"},
{"InputCodec", "The codec of the input: H264, Mjpeg"},
{"Output", "The output type: Http, Rtmp, File"},
{"RtmpMethod", "The method used to send over rtmp: Ffmpeg, Librtmp"},
// NOTE: we add rtp here when we have this functionality
{"Packetization", "The method of data packetisation: Flv, Mpegts, None"},
{"QuantizationMode", "Whether quantization if on or off (variable bitrate): On, Off"},
{"Verbosity", "Verbosity: On, Off"},
2018-05-06 11:38:45 +03:00
{"FramesPerClip", "Number of frames per clip sent"},
{"RtmpUrl", "Url of rtmp endpoint"},
{"Bitrate", "Bitrate of recorded video"},
{"OutputFileName", "The directory of the output file"},
{"InputFileName", "The directory of the input file"},
{"Height", "Height in pixels"},
{"Width", "Width in pixels"},
{"FrameRate", "Frame rate of captured video"},
{"HttpAddress", "Destination address of http posts"},
{"Quantization", "Desired quantization value: 0-40"},
2018-05-06 11:38:45 +03:00
{"Timeout", "Http timeout in seconds"},
{"IntraRefreshPeriod", "The IntraRefreshPeriod i.e. how many keyframes we send"},
{"VerticalFlip", "Flip video vertically: Yes, No"},
{"HorizontalFlip", "Flip video horizontally: Yes, No"},
}
2018-04-19 11:11:18 +03:00
// Create the configFlags based on the flagNames array
2018-05-06 11:38:45 +03:00
configFlags := make([](*string), noOfConfigFlags)
2018-06-07 14:50:57 +03:00
for i, f := range &flagNames {
configFlags[i] = flag.String(f.name, "", f.description)
}
2018-04-19 10:03:12 +03:00
// Do we want a netsender session
useNetsender := flag.Bool("NetSender", false, "Are we checking vars through netsender?")
2018-04-19 10:03:12 +03:00
// User might also want to define how long revid runs for
runDurationPtr := flag.Duration("runDuration", defaultRunDuration, "How long do you want revid to run for?")
flag.Parse()
2018-07-05 14:24:10 +03:00
if *cpuprofile != "" {
if canProfile {
f, err := os.Create(*cpuprofile)
if err != nil {
log.Fatal("could not create CPU profile: ", err)
}
if err := pprof.StartCPUProfile(f); err != nil {
log.Fatal("could not start CPU profile: ", err)
}
defer pprof.StopCPUProfile()
} else {
fmt.Fprintln(os.Stderr, "Ignoring cpuprofile flag - http/pprof built in.")
}
}
switch *configFlags[inputPtr] {
case "Raspivid":
2018-04-19 11:16:26 +03:00
config.Input = revid.Raspivid
case "File":
2018-04-19 11:16:26 +03:00
config.Input = revid.File
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad input argument!")
}
switch *configFlags[inputCodecPtr] {
case "H264Codec":
2018-04-19 11:16:26 +03:00
config.InputCodec = revid.H264Codec
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad input codec argument!")
}
switch *configFlags[outputPtr] {
case "File":
2018-04-19 11:16:26 +03:00
config.Output = revid.File
case "Http":
2018-04-19 11:16:26 +03:00
config.Output = revid.Http
case "Rtmp":
config.Output = revid.Rtmp
2018-04-19 12:39:48 +03:00
case "FfmpegRtmp":
config.Output = revid.FfmpegRtmp
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad output argument!")
}
switch *configFlags[rtmpMethodPtr] {
case "Ffmpeg":
2018-04-19 11:16:26 +03:00
config.RtmpMethod = revid.Ffmpeg
case "LibRtmp":
2018-04-19 11:16:26 +03:00
config.RtmpMethod = revid.LibRtmp
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad rtmp method argument!")
}
2018-04-19 11:24:44 +03:00
switch *configFlags[packetizationPtr] {
case "None":
2018-04-19 11:19:36 +03:00
config.Packetization = revid.None
case "Mpegts":
config.Packetization = revid.Mpegts
case "Flv":
2018-04-19 11:19:36 +03:00
config.Packetization = revid.Flv
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad packetization argument!")
}
2018-04-19 11:19:36 +03:00
switch *configFlags[quantizationModePtr] {
case "QuantizationOn":
2018-04-19 11:24:44 +03:00
config.QuantizationMode = revid.QuantizationOn
case "QuantizationOff":
2018-04-19 11:24:44 +03:00
config.QuantizationMode = revid.QuantizationOff
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad quantization mode argument!")
}
switch *configFlags[verbosityPtr] {
case "No":
2018-04-19 11:16:26 +03:00
config.Verbosity = revid.No
case "Yes":
2018-04-19 11:16:26 +03:00
config.Verbosity = revid.Yes
2018-04-19 12:37:25 +03:00
case "":
default:
fmt.Println("Bad verbosity argument!")
}
2018-05-03 15:36:17 +03:00
switch *configFlags[horizontalFlipPtr] {
2018-05-03 11:32:36 +03:00
case "No":
2018-05-05 06:45:45 +03:00
config.HorizontalFlip = revid.No
2018-05-03 11:32:36 +03:00
case "Yes":
2018-05-05 06:45:45 +03:00
config.HorizontalFlip = revid.Yes
2018-05-03 11:32:36 +03:00
case "":
2018-05-06 10:18:17 +03:00
config.HorizontalFlip = revid.No
2018-05-03 11:32:36 +03:00
default:
2018-05-03 15:36:17 +03:00
fmt.Println("Bad horizontal flip option!")
}
switch *configFlags[verticalFlipPtr] {
case "No":
2018-05-05 06:45:45 +03:00
config.VerticalFlip = revid.No
2018-05-03 15:36:17 +03:00
case "Yes":
config.VerticalFlip = revid.Yes
case "":
2018-05-06 10:18:17 +03:00
config.VerticalFlip = revid.No
2018-05-03 15:36:17 +03:00
default:
fmt.Println("Bad vertical flip option!")
2018-05-03 11:32:36 +03:00
}
2018-06-20 08:08:34 +03:00
fpc, err := strconv.Atoi(*configFlags[framesPerClipPtr])
if err == nil && fpc > 0 {
config.FramesPerClip = fpc
}
2018-05-06 11:38:45 +03:00
config.RtmpUrl = *configFlags[rtmpUrlPtr]
config.Bitrate = *configFlags[bitratePtr]
config.OutputFileName = *configFlags[outputFileNamePtr]
config.InputFileName = *configFlags[inputFileNamePtr]
config.Height = *configFlags[heightPtr]
config.Width = *configFlags[widthPtr]
config.FrameRate = *configFlags[frameRatePtr]
config.HttpAddress = *configFlags[httpAddressPtr]
config.Quantization = *configFlags[quantizationPtr]
config.Timeout = *configFlags[timeoutPtr]
config.IntraRefreshPeriod = *configFlags[intraRefreshPeriodPtr]
if !*useNetsender {
// instantiate our own logger
2018-05-30 06:15:37 +03:00
config.Logger = smartlogger.New(loggerVerbosity, smartlogger.File, "/var/log/netsender/")
// run revid for the specified duration
startRevid(nil)
time.Sleep(*runDurationPtr)
stopRevid()
return
2018-04-20 05:20:33 +03:00
}
2018-04-20 09:53:51 +03:00
err = run()
if err != nil {
config.Logger.Log(progName, "Error", err.Error()) // TODO(kortschak): Make this "Fatal" when that exists.
os.Exit(1)
}
}
// initialize then run the main NetSender client
func run() error {
// initialize NetSender and use NetSender's logger
//config.Logger = netsender.Logger()
config.Logger.Log(progName, "Info", "Running in NetSender mode")
var ns netsender.Sender
err := ns.Init(nil, nil, nil, nil)
if err != nil {
return err
}
vars, _ := ns.Vars()
vs := ns.VarSum()
paused := false
if vars["mode"] == "Paused" {
paused = true
2018-06-22 13:39:03 +03:00
}
2018-06-28 09:31:15 +03:00
if !paused {
err = updateRevid(&ns, vars, false)
if err != nil {
return err
}
}
for {
if err := send(&ns); err != nil {
config.Logger.Log(progName, "Warning", err.Error())
time.Sleep(netSendRetryTime)
continue
}
if vs != ns.VarSum() {
2018-06-07 14:50:57 +03:00
// vars changed
vars, err := ns.Vars()
if err != nil {
config.Logger.Log(progName, "Warning", err.Error())
time.Sleep(netSendRetryTime)
continue
}
vs = ns.VarSum()
if vars["mode"] == "Paused" {
if !paused {
config.Logger.Log(progName, "Info", "Pausing revid")
stopRevid()
paused = true
}
} else {
err = updateRevid(&ns, vars, !paused)
if err != nil {
return err
}
if paused {
paused = false
}
}
}
2018-06-25 03:40:37 +03:00
sleepTime, _ := strconv.Atoi(ns.Param("mp"))
2018-04-19 08:58:16 +03:00
time.Sleep(time.Duration(sleepTime) * time.Second)
}
}
// send implements our main NetSender client and handles NetReceiver configuration
// (as distinct from httpSender which just sends video data).
func send(ns *netsender.Sender) error {
// populate input values, if any
2018-06-25 03:40:37 +03:00
inputs := netsender.MakePins(ns.Param("ip"), "X")
2018-06-28 09:43:06 +03:00
if rv != nil {
for i, pin := range inputs {
if pin.Name == "X23" {
inputs[i].Value = rv.Bitrate()
}
}
}
_, reconfig, err := ns.Send(netsender.RequestPoll, inputs)
if err != nil {
return err
}
if reconfig {
return ns.Config()
}
return nil
}
// wrappers for stopping and starting revid
func startRevid(ns *netsender.Sender) (err error) {
rv, err = revid.New(config, ns)
if err != nil {
return err
}
rv.Start()
return nil
}
func stopRevid() {
rv.Stop()
2018-06-07 14:50:57 +03:00
// FIXME(kortschak): Is this waiting on completion of work?
// Use a wait group and Wait method if it is.
time.Sleep(revidStopTime)
}
func updateRevid(ns *netsender.Sender, vars map[string]string, stop bool) error {
if stop {
stopRevid()
}
2018-04-20 11:23:59 +03:00
//look through the vars and update revid where needed
2018-04-19 08:58:16 +03:00
for key, value := range vars {
switch key {
case "Output":
switch value {
case "File":
config.Output = revid.File
case "Http":
config.Output = revid.Http
case "Rtmp":
config.Output = revid.Rtmp
case "FfmpegRtmp":
config.Output = revid.FfmpegRtmp
default:
rv.Log(revid.Warning, "Invalid Output param: "+value)
continue
}
2018-05-06 11:38:45 +03:00
case "FramesPerClip":
2018-06-20 08:08:34 +03:00
fpc, err := strconv.Atoi(value)
if fpc > 0 && err == nil {
config.FramesPerClip = fpc
} else {
rv.Log(revid.Warning, "Invalid FramesPerClip param: "+value)
2018-05-06 11:38:45 +03:00
}
case "RtmpUrl":
config.RtmpUrl = value
case "Bitrate":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Bitrate = value
} else {
rv.Log(revid.Warning, "Invalid Bitrate param: "+value)
2018-05-06 11:38:45 +03:00
}
case "OutputFileName":
config.OutputFileName = value
case "InputFileName":
config.InputFileName = value
case "Height":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Height = value
} else {
rv.Log(revid.Warning, "Invalid Height param: "+value)
2018-05-06 11:38:45 +03:00
}
case "Width":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Width = value
} else {
rv.Log(revid.Warning, "Invalid Width param: "+value)
2018-05-06 11:38:45 +03:00
}
case "FrameRate":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.FrameRate = value
} else {
rv.Log(revid.Warning, "Invalid FrameRate param: "+value)
2018-05-06 11:38:45 +03:00
}
case "HttpAddress":
config.HttpAddress = value
case "Quantization":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Quantization = value
} else {
rv.Log(revid.Warning, "Invalid Quantization param: "+value)
2018-05-06 11:38:45 +03:00
}
case "Timeout":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Timeout = value
}
case "IntraRefreshPeriod":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.IntraRefreshPeriod = value
}
case "HorizontalFlip":
switch value {
case "Yes":
config.HorizontalFlip = revid.Yes
case "No":
config.HorizontalFlip = revid.No
default:
rv.Log(revid.Warning, "Invalid HorizontalFlip param: "+value)
2018-05-06 11:38:45 +03:00
}
case "VerticalFlip":
switch value {
case "Yes":
config.VerticalFlip = revid.Yes
case "No":
config.VerticalFlip = revid.No
default:
rv.Log(revid.Warning, "Invalid VerticalFlip param: "+value)
2018-05-06 11:38:45 +03:00
}
default:
2018-04-19 08:58:16 +03:00
}
}
return startRevid(ns)
2018-04-19 08:58:16 +03:00
}