/* NAME RevidCLI.go DESCRIPTION See Readme.md AUTHORS Saxon A. Nelson-Milton Jack Richardson LICENSE RevidCLI.go is Copyright (C) 2017 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/exec" "strconv" "time" "bitbucket.org/ausocean/av/revid" "bitbucket.org/ausocean/iot/pi/netsender" "bitbucket.org/ausocean/utils/smartlogger" linuxproc "github.com/c9s/goprocinfo/linux" ) // Indexes for configFlags const ( inputPtr = 0 inputCodecPtr = 1 outputPtr = 2 rtmpMethodPtr = 3 packetizationPtr = 4 quantizationModePtr = 5 verbosityPtr = 6 framesPerClipPtr = 7 rtmpUrlPtr = 8 bitratePtr = 9 outputFileNamePtr = 10 inputFileNamePtr = 11 heightPtr = 12 widthPtr = 13 frameRatePtr = 14 httpAddressPtr = 15 quantizationPtr = 16 timeoutPtr = 17 intraRefreshPeriodPtr = 18 verticalFlipPtr = 19 horizontalFlipPtr = 20 ) // Other misc consts const ( netSendRetryTime = 5 sleepTime = 2 * 43200 defaultRunDuration = 2 * 43200 noOfConfigFlags = 21 revidStopTime = 5 prepTime = 20 loggerVerbosity = 3 ) const ( cpuUsage = 20 cpuTemp = 21 revidBitrate = 23 ) // Globals var ( varSum int = 0 vars = make(map[string]string) revidInst revid.Revid config revid.Config ) func main() { flagNames := [noOfConfigFlags][2]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 := 0; i < noOfConfigFlags; i++ { configFlags[i] = flag.String(flagNames[i][0], "", flagNames[i][1]) } // Do we want a netsender session netSenderFlagPtr := flag.Bool("NetSender", false, "Are we checking vars through netsender?") // User might also want to define how long revid runs for runDurationPtr := flag.Int("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] // Also give the config a logger object config.Logger = smartlogger.New(loggerVerbosity, smartlogger.File, "/var/log/netsender/") //init netsender if *netSenderFlagPtr { netsenderInit() } time.Sleep(time.Duration(prepTime) * time.Second) startRevid() paused := false // Is the netsender flag been used ? if so run this loop for *netSenderFlagPtr { varsChanged, err := netSend() if err != nil { config.Logger.Log("revidCLI","Error", err.Error()) time.Sleep(time.Duration(netSendRetryTime) * time.Second) continue } if varsChanged { if vars["mode"] == "Paused" { if !paused { config.Logger.Log("revidCLI","Info", "Pausing revid") stopRevid() paused = true } } else { updateRevid(!paused) if paused { paused = false } } } sleepTime, _ := strconv.Atoi(netsender.GetConfigParam("monPeriod")) 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(time.Duration(*runDurationPtr) * time.Second) stopRevid() } func netsenderInit() { //initialize netsender and assign function to check X pins config.Logger = netsender.GetLogger() netsender.Init(false) netsender.ExternalReader = revidReportActions } // netSend implements the NetSender client, and is called every monPeriod seconds. // It handles NetReceiver configuration and sends requested data to the cloud. // Finally, it updates vars and returns true when vars have changed func netSend() (bool, error) { var err error if !netsender.IsConfigured() { if err = netsender.GetConfig(); err != nil { return false, err } } inputs := netsender.SplitCSV(netsender.GetConfigParam("inputs")) var reconfig bool if _, reconfig, err = netsender.Send(netsender.RequestPoll, inputs); err != nil { return false, err } if reconfig { if err = netsender.GetConfig(); err != nil { return false, err } } if cloudVarSum := netsender.GetVarSum(); cloudVarSum != varSum { var newVars map[string]string if newVars, err = netsender.GetVars(); err != nil { return false, err } varSum = cloudVarSum for newVar, value := range newVars { if currentValue, varExists := vars[newVar]; !varExists || currentValue != value { vars[newVar] = value } } return true, nil } return false, nil } // wrappers for stopping and starting revid func startRevid() { createRevidInstance() revidInst.Start() } func stopRevid() { revidInst.Stop() time.Sleep(time.Duration(revidStopTime) * time.Second) } func updateRevid(stop bool) { if stop { stopRevid() } //look through var map 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") } } func createRevidInstance() { // Try to create the revid instance with the given config var err error for revidInst, err = revid.NewRevid(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("revidCLI","FATAL ERROR", err.Error()) } else { fmt.Printf("FATAL ERROR: %v", err.Error()) } } }