2020-01-27 14:20:14 +03:00
|
|
|
/*
|
|
|
|
DESCRIPTION
|
|
|
|
rvcl is command line interface for revid. The user can provide configuration
|
|
|
|
by passing a JSON string directly, or by specifying a file containing the JSON.
|
|
|
|
|
|
|
|
AUTHORS
|
|
|
|
Saxon A. Nelson-Milton <saxon@ausocean.org>
|
|
|
|
Dan Kortschak <dan@ausocean.org>
|
|
|
|
Scott Barnard <scott@ausocean.org>
|
|
|
|
|
|
|
|
LICENSE
|
|
|
|
Copyright (C) 2020 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 http://www.gnu.org/licenses.
|
|
|
|
|
|
|
|
USAGE
|
|
|
|
User may providied path to a config JSON file using the 'config-file' flag.
|
|
|
|
For example, to stream a H.264 file to youtube we will need to create a file
|
|
|
|
containing configuration JSON:
|
|
|
|
{
|
|
|
|
"Input":"file",
|
|
|
|
"InputPath":"<path to h264 file>",
|
|
|
|
"Output":"rtmp",
|
|
|
|
"RtmpUrl":"<rtmp url>",
|
|
|
|
"FileFPS":"25"
|
|
|
|
}
|
|
|
|
|
|
|
|
Then, using rvcl we can stream:
|
|
|
|
|
|
|
|
./rvcl -config-file="config.json"
|
|
|
|
|
|
|
|
We can also use the 'config' flag to directly pass JSON as a string.
|
|
|
|
NB: Quotations must have backslashes, '\', proceeding them for rvcl to consider
|
|
|
|
the string valid JSON. For example, to play a file to youtube using the JSON
|
|
|
|
config described above, we would do the following:
|
|
|
|
|
|
|
|
./rvcl -config="{\"Input\":\"file\",\"InputPath\":\"<file path>\",
|
|
|
|
\"Output\":\"rtmp\",\"RtmpUrl\":\"<rtmp url>\",\"FileFPS\":\"25\"}"
|
|
|
|
|
|
|
|
rvcl passes given variables to revid i.e. all variables defined by revid are
|
|
|
|
valid. See revid config package i.e. bitbucket.org/ausocean/av/revid/config for
|
|
|
|
revid specific variables, and see AVDevice implementations i.e.
|
|
|
|
bitbucket.org/ausocean/av/device for input device specific variables.
|
|
|
|
*/
|
|
|
|
|
2020-04-23 06:43:37 +03:00
|
|
|
// Package rvcl is a command line interface for revid.
|
2020-01-27 14:20:14 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"flag"
|
|
|
|
"os"
|
2020-12-20 04:33:43 +03:00
|
|
|
"os/exec"
|
2020-01-27 14:20:14 +03:00
|
|
|
"runtime/pprof"
|
|
|
|
|
|
|
|
"bitbucket.org/ausocean/av/container/mts"
|
|
|
|
"bitbucket.org/ausocean/av/container/mts/meta"
|
|
|
|
"bitbucket.org/ausocean/av/revid"
|
|
|
|
"bitbucket.org/ausocean/av/revid/config"
|
|
|
|
"bitbucket.org/ausocean/iot/pi/netsender"
|
2022-05-27 09:12:52 +03:00
|
|
|
"bitbucket.org/ausocean/utils/logging"
|
2020-01-27 14:20:14 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Copyright information prefixed to all metadata.
|
|
|
|
const (
|
|
|
|
metaPreambleKey = "copyright"
|
|
|
|
metaPreambleData = "ausocean.org/license/content2019"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Logging configuration.
|
|
|
|
const (
|
2022-05-27 09:12:52 +03:00
|
|
|
logLevel = logging.Info
|
2020-12-20 04:33:43 +03:00
|
|
|
logSuppress = true
|
2020-01-27 14:20:14 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
// Misc consts.
|
|
|
|
const (
|
|
|
|
pkg = "rvcl: "
|
|
|
|
profilePath = "rvcl.prof"
|
|
|
|
)
|
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
// Netsender conf consts.
|
|
|
|
const (
|
|
|
|
cfgPath = "/etc/netsender.conf"
|
|
|
|
fMode = 0777
|
|
|
|
)
|
|
|
|
|
2020-12-21 03:48:35 +03:00
|
|
|
// Default config parameters.
|
|
|
|
const (
|
2020-12-21 04:21:21 +03:00
|
|
|
defaultInput = "File"
|
|
|
|
defaultInputPath = "../../../test/test-data/av/input/betterInput.h264"
|
|
|
|
defaultFileFPS = "25"
|
|
|
|
defaultOutput = "RTP"
|
2020-12-21 03:48:35 +03:00
|
|
|
defaultRTPAddress = "localhost:6970"
|
|
|
|
defaultLoop = "true"
|
|
|
|
)
|
|
|
|
|
2020-01-27 14:20:14 +03:00
|
|
|
// canProfile is set to false with revid-cli is built with "-tags profile".
|
|
|
|
var canProfile = false
|
|
|
|
|
|
|
|
// The logger that will be used throughout.
|
2022-05-27 09:12:52 +03:00
|
|
|
var log logging.Logger
|
2020-01-27 14:20:14 +03:00
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
// stdoutLogger provides an io.Writer for the purpose of capturing stdout from
|
|
|
|
// the VLC process and using the logger to capture and print to stdout of
|
|
|
|
// this process.
|
|
|
|
type stdoutLogger struct {
|
2022-05-27 09:12:52 +03:00
|
|
|
l logging.Logger
|
2020-12-20 04:33:43 +03:00
|
|
|
t string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sl *stdoutLogger) Write(d []byte) (int, error) {
|
|
|
|
sl.l.Info(sl.t + ": " + string(d))
|
|
|
|
return len(d), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// stderrLogger provides an io.Writer for the purpose of capturing stderr from
|
|
|
|
// the VLC process and using the logger to capture and print to stdout of
|
|
|
|
// this process.
|
|
|
|
type stderrLogger struct {
|
2022-05-27 09:12:52 +03:00
|
|
|
l logging.Logger
|
2020-12-20 04:33:43 +03:00
|
|
|
t string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sl *stderrLogger) Write(d []byte) (int, error) {
|
|
|
|
sl.l.Error(sl.t + ": " + string(d))
|
|
|
|
return len(d), nil
|
|
|
|
}
|
|
|
|
|
2020-01-27 14:20:14 +03:00
|
|
|
func main() {
|
|
|
|
mts.Meta = meta.NewWith([][2]string{{metaPreambleKey, metaPreambleData}})
|
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
// Create logger that methods will be called on by the netsender client and
|
|
|
|
// revid to log messages. Logs will go the lumberjack logger to handle file
|
|
|
|
// writing of messages.
|
2022-05-27 09:12:52 +03:00
|
|
|
log = logging.New(
|
2020-12-20 04:33:43 +03:00
|
|
|
logLevel,
|
|
|
|
os.Stdout,
|
|
|
|
logSuppress,
|
|
|
|
)
|
|
|
|
|
2020-01-27 14:20:14 +03:00
|
|
|
// If built with profile tag, we will start CPU profiling.
|
|
|
|
if canProfile {
|
|
|
|
profile()
|
|
|
|
defer pprof.StopCPUProfile()
|
|
|
|
}
|
|
|
|
|
|
|
|
// User can provide config through single JSON string flag, or through JSON file.
|
|
|
|
var (
|
|
|
|
configPtr = flag.String("config", "", "Provide configuration JSON to revid (see readme for further information).")
|
|
|
|
configFilePtr = flag.String("config-file", "", "Location of revid configuration file (see readme for further information).")
|
2020-12-21 03:48:35 +03:00
|
|
|
rtpAddrPtr = flag.String("rtp-addr", defaultRTPAddress, "RTP destination address (<ip>:<port>)(common port=6970)")
|
2020-01-27 14:20:14 +03:00
|
|
|
)
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
// Create config map according to flags or file, and panic if both are defined.
|
|
|
|
var (
|
|
|
|
cfg map[string]string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
switch {
|
2020-12-20 04:33:43 +03:00
|
|
|
// This doesn't make sense so panic.
|
|
|
|
case *configPtr != "" && *configFilePtr != "":
|
2020-01-27 14:20:14 +03:00
|
|
|
panic("cannot define both command-line config and file config")
|
2020-12-20 04:33:43 +03:00
|
|
|
|
|
|
|
// Decode JSON file to map.
|
|
|
|
case *configPtr != "":
|
2020-01-27 14:20:14 +03:00
|
|
|
err = json.Unmarshal([]byte(*configPtr), &cfg)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not decode JSON config", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
2020-12-20 04:33:43 +03:00
|
|
|
|
|
|
|
// Decode JSON string to map from command line flag.
|
|
|
|
case *configFilePtr != "":
|
2020-01-27 14:20:14 +03:00
|
|
|
f, err := os.Open(*configFilePtr)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not open config file", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
err = json.NewDecoder(f).Decode(&cfg)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not decode JSON config", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
// No config information has been provided; provide a default config map.
|
|
|
|
default:
|
|
|
|
cfg = map[string]string{
|
2020-12-21 03:48:35 +03:00
|
|
|
"Input": defaultInput,
|
|
|
|
"InputPath": defaultInputPath,
|
|
|
|
"FileFPS": defaultFileFPS,
|
|
|
|
"Output": defaultOutput,
|
2020-12-20 04:33:43 +03:00
|
|
|
"RTPAddress": *rtpAddrPtr,
|
2020-12-21 03:48:35 +03:00
|
|
|
"Loop": defaultLoop,
|
2020-12-20 04:33:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Info("got config", "config", cfg)
|
2020-01-27 14:20:14 +03:00
|
|
|
|
|
|
|
// Create a netsender client. This is used only for HTTP sending of media
|
|
|
|
// in this binary.
|
|
|
|
ns, err := netsender.New(log, nil, nil, nil, nil)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not initialise netsender client", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
// Create the revid client, responsible for media collection and processing.
|
|
|
|
log.Info("got creating revid client")
|
2020-01-27 14:20:14 +03:00
|
|
|
rv, err := revid.New(config.Config{Logger: log}, ns)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not create revid", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Configure revid with configuration map obtained through flags or file.
|
|
|
|
// If config is empty, defaults will be adopted by revid.
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Info("updating revid with config")
|
2020-01-27 14:20:14 +03:00
|
|
|
err = rv.Update(cfg)
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not update revid config", "error", err)
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Info("starting revid")
|
2020-01-27 14:20:14 +03:00
|
|
|
err = rv.Start()
|
|
|
|
if err != nil {
|
2020-12-20 04:33:43 +03:00
|
|
|
log.Fatal("could not start revid", "error", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If output is RTP, open up a VLC window to see stream.
|
|
|
|
if v, ok := cfg["Output"]; ok && v == "RTP" {
|
|
|
|
log.Info("opening vlc window")
|
|
|
|
cmd := exec.Command("vlc", "rtp://"+*rtpAddrPtr)
|
|
|
|
cmd.Stdout = &stdoutLogger{log, "VLC STDOUT"}
|
|
|
|
cmd.Stderr = &stderrLogger{log, "VLC STDERR"}
|
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatal("could not run vlc command", "error", err)
|
|
|
|
}
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Run indefinitely.
|
|
|
|
select {}
|
|
|
|
}
|
|
|
|
|
|
|
|
// profile creates a file to hold CPU profile metrics and begins CPU profiling.
|
|
|
|
func profile() {
|
|
|
|
f, err := os.Create(profilePath)
|
|
|
|
if err != nil {
|
2022-05-27 09:12:52 +03:00
|
|
|
log.Fatal( pkg+"could not create CPU profile", "error", err.Error())
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
if err := pprof.StartCPUProfile(f); err != nil {
|
2022-05-27 09:12:52 +03:00
|
|
|
log.Fatal( pkg+"could not start CPU profile", "error", err.Error())
|
2020-01-27 14:20:14 +03:00
|
|
|
}
|
|
|
|
}
|