/* NAME RevidCLI.go DESCRIPTION See Readme.md AUTHORS Saxon A. Nelson-Milton Jack Richardson 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) 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") } }