av/cmd/revid-cli/main.go

490 lines
12 KiB
Go

/*
NAME
RevidCLI.go
DESCRIPTION
See Readme.md
AUTHORS
Saxon A. Nelson-Milton <saxon@ausocean.org>
Jack Richardson <jack@ausocean.org>
LICENSE
RevidCLI.go is Copyright (C) 2017-2018 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
along with revid in gpl.txt. If not, see [GNU licenses](http://www.gnu.org/licenses).
*/
package main
import (
"errors"
"flag"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"time"
"bitbucket.org/ausocean/av/revid"
"bitbucket.org/ausocean/iot/pi/netsender"
"bitbucket.org/ausocean/utils/smartlogger"
linuxproc "github.com/c9s/goprocinfo/linux"
)
const (
// progName is the program name for logging purposes.
progName = "revid-cli"
// Logging is set to INFO level.
loggerVerbosity = 3
)
// Indexes for configFlags
const (
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
prepTime = 20 * time.Second
)
const (
cpuUsage = 20
cpuTemp = 21
revidBitrate = 23
)
// Globals
var (
revidInst *revid.Revid
config revid.Config
)
func main() {
flagNames := [noOfConfigFlags]struct{ name, description string }{
{"Input", "The input type"},
{"InputCodec", "The codec of the input"},
{"Output", "The output type"},
{"RtmpMethod", "The method used to send over rtmp (ffmpeg or librtmp)"},
{"Packetization", "The method of data packetisation"},
{"QuantizationMode", "The level of quantization"},
{"Verbosity", "Verbosity on or off"},
{"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"},
{"Timeout", "Http timeout in seconds"},
{"IntraRefreshPeriod", "The IntraRefreshPeriod i.e. how many keyframes we send"},
{"VerticalFlip", "Flip video vertically"},
{"HorizontalFlip", "Flip video horizontally"},
}
// Create the configFlags based on the flagNames array
configFlags := make([](*string), noOfConfigFlags)
for i, f := range &flagNames {
configFlags[i] = flag.String(f.name, "", f.description)
}
// Do we want a netsender session
useNetsender := flag.Bool("NetSender", false, "Are we checking vars through netsender?")
// 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()
switch *configFlags[inputPtr] {
case "Raspivid":
config.Input = revid.Raspivid
case "Rtp":
config.Input = revid.Rtp
case "File":
config.Input = revid.File
case "":
default:
fmt.Println("Bad input argument!")
}
switch *configFlags[inputCodecPtr] {
case "H264Codec":
config.InputCodec = revid.H264Codec
case "":
default:
fmt.Println("Bad input codec argument!")
}
switch *configFlags[outputPtr] {
case "File":
config.Output = revid.File
case "Http":
config.Output = revid.Http
case "NativeRtmp":
config.Output = revid.NativeRtmp
case "FfmpegRtmp":
config.Output = revid.FfmpegRtmp
case "":
default:
fmt.Println("Bad output argument!")
}
switch *configFlags[rtmpMethodPtr] {
case "Ffmpeg":
config.RtmpMethod = revid.Ffmpeg
case "LibRtmp":
config.RtmpMethod = revid.LibRtmp
case "":
default:
fmt.Println("Bad rtmp method argument!")
}
switch *configFlags[packetizationPtr] {
case "None":
config.Packetization = revid.None
case "Rtp":
config.Packetization = revid.Rtp
case "Flv":
config.Packetization = revid.Flv
case "":
default:
fmt.Println("Bad packetization argument!")
}
switch *configFlags[quantizationModePtr] {
case "QuantizationOn":
config.QuantizationMode = revid.QuantizationOn
case "QuantizationOff":
config.QuantizationMode = revid.QuantizationOff
case "":
default:
fmt.Println("Bad quantization mode argument!")
}
switch *configFlags[verbosityPtr] {
case "No":
config.Verbosity = revid.No
case "Yes":
config.Verbosity = revid.Yes
case "":
default:
fmt.Println("Bad verbosity argument!")
}
switch *configFlags[horizontalFlipPtr] {
case "No":
config.HorizontalFlip = revid.No
case "Yes":
config.HorizontalFlip = revid.Yes
case "":
config.HorizontalFlip = revid.No
default:
fmt.Println("Bad horizontal flip option!")
}
switch *configFlags[verticalFlipPtr] {
case "No":
config.VerticalFlip = revid.No
case "Yes":
config.VerticalFlip = revid.Yes
case "":
config.VerticalFlip = revid.No
default:
fmt.Println("Bad vertical flip option!")
}
config.FramesPerClip = *configFlags[framesPerClipPtr]
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]
var ns netsender.Netsender
var vs int
if *useNetsender {
// initialize NetSender and use NetSender's logger
config.Logger = netsender.GetLogger()
var err error
ns, err = netsender.NewNetsender(false, revidReportActions)
if err != nil {
l := smartlogger.New(loggerVerbosity, smartlogger.File, "/var/log/netsender/")
l.Log(progName, "Error", err.Error()) // TODO(kortschak): Make this "Fatal" when that exists.
os.Exit(1)
}
vs = ns.GetVarSum()
} else {
// alternatively, instantiate our own logger
config.Logger = smartlogger.New(loggerVerbosity, smartlogger.File, "/var/log/netsender/")
}
time.Sleep(prepTime)
startRevid()
paused := false
// loop in NetSender mode
for *useNetsender {
if err := sendTo(ns); err != nil {
config.Logger.Log(progName, "Error", err.Error())
time.Sleep(netSendRetryTime)
continue
}
if vs != ns.GetVarSum() {
// vars changed
vars, err := ns.GetVars()
if err != nil {
config.Logger.Log(progName, "Error", err.Error())
time.Sleep(netSendRetryTime)
continue
}
vs = ns.GetVarSum()
if vars["mode"] == "Paused" {
if !paused {
config.Logger.Log(progName, "Info", "Pausing revid")
stopRevid()
paused = true
}
} else {
updateRevid(vars, !paused)
if paused {
paused = false
}
}
}
sleepTime, _ := strconv.Atoi(ns.GetConfigParam("mp"))
time.Sleep(time.Duration(sleepTime) * time.Second)
}
// If we're not running a netsender session then we run revid for the amount
// of time the user defined or the default 2 days
time.Sleep(*runDurationPtr)
stopRevid()
}
// sendTo handles NetReceiver configuration and sends requested data to the cloud.
func sendTo(ns netsender.Netsender) error {
if !ns.IsConfigured() {
err := ns.Config()
if err != nil {
return err
}
}
inputs := strings.Split(ns.GetConfigParam("ip"), ",")
_, reconfig, err := ns.Send(netsender.RequestPoll, inputs, nil)
if err != nil {
return err
}
if reconfig {
return ns.Config()
}
return nil
}
// wrappers for stopping and starting revid
func startRevid() {
createRevidInstance()
revidInst.Start()
}
func createRevidInstance() {
// Try to create the revid instance with the given config
var err error
for revidInst, err = revid.New(config); err != nil; {
// If the config does have a logger, use it to output error, otherwise
// just output to std output
if config.Logger != nil {
config.Logger.Log(progName, "FATAL ERROR", err.Error())
} else {
fmt.Printf("FATAL ERROR: %v", err.Error())
}
}
}
func stopRevid() {
revidInst.Stop()
// FIXME(kortschak): Is this waiting on completion of work?
// Use a wait group and Wait method if it is.
time.Sleep(revidStopTime)
}
func updateRevid(vars map[string]string, stop bool) {
if stop {
stopRevid()
}
//look through the vars and update revid where needed
for key, value := range vars {
switch key {
case "FramesPerClip":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.FramesPerClip = value
}
case "RtmpUrl":
config.RtmpUrl = value
case "Bitrate":
revidInst.Log(revid.Debug, fmt.Sprintf("The value: %v", value))
asInt, err := strconv.Atoi(value)
revidInst.Log(revid.Debug, fmt.Sprintf("Bitrate as integer: %v", asInt))
if asInt > 0 && err == nil {
revidInst.Log(revid.Debug, "Updating bitrate")
config.Bitrate = value
}
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
}
case "Width":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Width = value
}
case "FrameRate":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.FrameRate = value
}
case "HttpAddress":
config.HttpAddress = value
case "Quantization":
asInt, err := strconv.Atoi(value)
if asInt > 0 && err == nil {
config.Quantization = value
}
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
}
case "VerticalFlip":
switch value {
case "Yes":
config.VerticalFlip = revid.Yes
case "No":
config.VerticalFlip = revid.No
}
}
}
startRevid()
}
func revidReportActions(pin int) (int, error) {
switch {
//function to measure temp of cpu
case pin == cpuTemp:
var out []byte
var err error
var val float64
if out, err = exec.Command("/opt/vc/bin/vcgencmd", "measure_temp").Output(); err != nil {
return -1, errors.New("CPU Temp Read Err: " + err.Error())
}
if val, err = strconv.ParseFloat(string(out[5:len(out)-3]), 32); err != nil {
return -1, errors.New("CPU Temp Read Err: " + err.Error())
}
return int(val), nil
//function to measure usage of cpu
case pin == cpuUsage:
stat, err := linuxproc.ReadStat("/proc/stat")
if err != nil {
return -1, errors.New("CPU Uage Read Err: " + err.Error())
}
total1 := stat.CPUStatAll.User + stat.CPUStatAll.Nice + stat.CPUStatAll.System +
stat.CPUStatAll.Idle + stat.CPUStatAll.IOWait + stat.CPUStatAll.IRQ +
stat.CPUStatAll.SoftIRQ + stat.CPUStatAll.Steal + stat.CPUStatAll.Guest +
stat.CPUStatAll.GuestNice
idle1 := stat.CPUStatAll.Idle
time.Sleep(time.Millisecond * 1000)
stat, err = linuxproc.ReadStat("/proc/stat")
if err != nil {
return -1, errors.New("CPU Usage Read Err: " + err.Error())
}
total2 := stat.CPUStatAll.User + stat.CPUStatAll.Nice + stat.CPUStatAll.System +
stat.CPUStatAll.Idle + stat.CPUStatAll.IOWait + stat.CPUStatAll.IRQ +
stat.CPUStatAll.SoftIRQ + stat.CPUStatAll.Steal + stat.CPUStatAll.Guest +
stat.CPUStatAll.GuestNice
idle2 := stat.CPUStatAll.Idle
return int((1.0 - (float64(idle2-idle1) / float64(total2-total1))) * 100), nil
case pin == revidBitrate:
return int(revidInst.GetBitrate()), nil
default:
return -1, errors.New("External pin" + strconv.Itoa(pin) + " not defined")
}
}