revid: avoid monomorphic interface

This commit is contained in:
Dan Kortschak 2018-06-09 11:31:21 +09:30
parent 709de3f119
commit 3881cb9712
3 changed files with 98 additions and 113 deletions

View File

@ -95,7 +95,7 @@ const (
// Globals
var (
revidInst revid.Revid
revidInst *revid.Revid
config revid.Config
)
@ -341,7 +341,7 @@ func startRevid() {
func createRevidInstance() {
// Try to create the revid instance with the given config
var err error
for revidInst, err = revid.NewRevid(config); err != nil; {
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 {

View File

@ -104,46 +104,46 @@ const (
// Validate checks for any errors in the config fields and defaults settings
// if particular parameters have not been defined.
func (config *Config) Validate(r *revid) error {
switch config.Verbosity {
func (c *Config) Validate(r *Revid) error {
switch c.Verbosity {
case Yes:
case No:
case NothingDefined:
config.Verbosity = Yes
c.Verbosity = Yes
r.Log(Warning, "No verbosity mode defined, defaulting to no Verbosity!")
default:
return errors.New("Bad Verbosity defined in config!")
}
switch config.QuantizationMode {
switch c.QuantizationMode {
case QuantizationOn:
case QuantizationOff:
case NothingDefined:
r.Log(Warning, "No quantization mode defined, defaulting to QuantizationOff!")
config.QuantizationMode = QuantizationOff
c.QuantizationMode = QuantizationOff
default:
return errors.New("Bad QuantizationMode defined in config!")
}
switch config.Input {
switch c.Input {
case Rtp:
case Raspivid:
case File:
case NothingDefined:
r.Log(Warning, "No input type defined, defaulting to raspivid!")
config.Input = defaultInput
c.Input = defaultInput
default:
return errors.New("Bad input type defined in config!")
}
switch config.InputCodec {
switch c.InputCodec {
case H264:
if config.Bitrate != "" && config.Quantization != "" {
bitrate, err := strconv.Atoi(config.Bitrate)
if c.Bitrate != "" && c.Quantization != "" {
bitrate, err := strconv.Atoi(c.Bitrate)
if err != nil {
return errors.New("Something is wrong with bitrate in conig!")
}
quantization, err := strconv.Atoi(config.Quantization)
quantization, err := strconv.Atoi(c.Quantization)
if err != nil {
return errors.New("Something is wrong with quantization in config!")
}
@ -152,141 +152,141 @@ func (config *Config) Validate(r *revid) error {
}
}
case Mjpeg:
if config.Quantization != "" {
quantization, err := strconv.Atoi(config.Quantization)
if c.Quantization != "" {
quantization, err := strconv.Atoi(c.Quantization)
if err != nil {
return errors.New("Something is wrong with quantization in config!")
}
if quantization > 0 || config.Bitrate == "" {
if quantization > 0 || c.Bitrate == "" {
return errors.New("Bad bitrate or quantization for mjpeg input!")
}
}
case NothingDefined:
r.Log(Warning, "No input codec defined, defaulting to h264!")
config.InputCodec = H264
c.InputCodec = H264
r.Log(Warning, "Defaulting bitrate to 0 and quantization to 35!")
config.Quantization = defaultQuantization
c.Quantization = defaultQuantization
default:
return errors.New("Bad input codec defined in config!")
}
switch config.Output {
switch c.Output {
case Http:
case File:
case NativeRtmp, FfmpegRtmp:
if config.RtmpUrl == "" {
if c.RtmpUrl == "" {
r.Log(Info, "No RTMP URL: falling back to HTTP")
config.Output = Http
c.Output = Http
break
}
r.Log(Info, "Defaulting frames per clip to 1 for rtmp output!")
config.FramesPerClip = "1"
c.FramesPerClip = "1"
case NothingDefined:
r.Log(Warning, "No output defined, defaulting to httpOut!")
config.Output = defaultOutput
c.Output = defaultOutput
default:
return errors.New("Bad output type defined in config!")
}
switch config.Packetization {
switch c.Packetization {
case None:
case Mpegts:
case Flv:
case NothingDefined:
r.Log(Warning, "No packetization option defined, defaulting to none!")
config.Packetization = Flv
c.Packetization = Flv
default:
return errors.New("Bad packetization option defined in config!")
}
switch config.HorizontalFlip {
switch c.HorizontalFlip {
case Yes:
case No:
case NothingDefined:
r.Log(Warning, "No horizontal flip option defined, defaulting to not flipped!")
config.HorizontalFlip = defaultHorizontalFlip
c.HorizontalFlip = defaultHorizontalFlip
default:
return errors.New("Bad horizontal flip option defined in config!")
}
switch config.VerticalFlip {
switch c.VerticalFlip {
case Yes:
case No:
case NothingDefined:
r.Log(Warning, "No vertical flip option defined, defaulting to not flipped!")
config.VerticalFlip = defaultVerticalFlip
c.VerticalFlip = defaultVerticalFlip
default:
return errors.New("Bad vertical flip option defined in config!")
}
if config.FramesPerClip == "" {
if c.FramesPerClip == "" {
r.Log(Warning, "No FramesPerClip defined defined, defaulting to 1!")
config.Width = defaultFramesPerClip
c.Width = defaultFramesPerClip
} else {
if integer, err := strconv.Atoi(config.FramesPerClip); integer <= 0 || err != nil {
if integer, err := strconv.Atoi(c.FramesPerClip); integer <= 0 || err != nil {
return errors.New("Bad width defined in config!")
}
}
if config.Width == "" {
if c.Width == "" {
r.Log(Warning, "No width defined, defaulting to 1280!")
config.Width = defaultWidth
c.Width = defaultWidth
} else {
if integer, err := strconv.Atoi(config.Width); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.Width); integer < 0 || err != nil {
return errors.New("Bad width defined in config!")
}
}
if config.Height == "" {
if c.Height == "" {
r.Log(Warning, "No height defined, defaulting to 720!")
config.Height = defaultHeight
c.Height = defaultHeight
} else {
if integer, err := strconv.Atoi(config.Height); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.Height); integer < 0 || err != nil {
return errors.New("Bad height defined in config!")
}
}
if config.FrameRate == "" {
if c.FrameRate == "" {
r.Log(Warning, "No frame rate defined, defaulting to 25!")
config.FrameRate = defaultFrameRate
c.FrameRate = defaultFrameRate
} else {
if integer, err := strconv.Atoi(config.FrameRate); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.FrameRate); integer < 0 || err != nil {
return errors.New("Bad frame rate defined in config!")
}
}
if config.Bitrate == "" {
if c.Bitrate == "" {
r.Log(Warning, "No bitrate defined, defaulting!")
config.Bitrate = defaultBitrate
c.Bitrate = defaultBitrate
} else {
if integer, err := strconv.Atoi(config.Bitrate); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.Bitrate); integer < 0 || err != nil {
return errors.New("Bad bitrate defined in config!")
}
}
if config.Timeout == "" {
if c.Timeout == "" {
r.Log(Warning, "No timeout defined, defaulting to 0!")
config.Timeout = defaultTimeout
c.Timeout = defaultTimeout
} else {
if integer, err := strconv.Atoi(config.Timeout); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.Timeout); integer < 0 || err != nil {
return errors.New("Bad timeout defined in config!")
}
}
if config.IntraRefreshPeriod == "" {
if c.IntraRefreshPeriod == "" {
r.Log(Warning, "No intra refresh defined, defaulting to 100!")
config.IntraRefreshPeriod = defaultIntraRefreshPeriod
c.IntraRefreshPeriod = defaultIntraRefreshPeriod
} else {
if integer, err := strconv.Atoi(config.IntraRefreshPeriod); integer < 0 || err != nil {
if integer, err := strconv.Atoi(c.IntraRefreshPeriod); integer < 0 || err != nil {
return errors.New("Bad intra refresh defined in config!")
}
}
if config.Quantization == "" {
if c.Quantization == "" {
r.Log(Warning, "No quantization defined, defaulting to 35!")
config.Quantization = defaultQuantization
c.Quantization = defaultQuantization
} else {
if integer, err := strconv.Atoi(config.Quantization); integer < 0 || integer > 51 || err != nil {
if integer, err := strconv.Atoi(c.Quantization); integer < 0 || integer > 51 || err != nil {
return errors.New("Bad quantization defined in config!")
}
}

View File

@ -86,18 +86,7 @@ const (
// Revid provides methods to control a revid session; providing methods
// to start, stop and change the state of an instance using the Config struct.
type Revid interface {
Start()
Stop()
changeState(newconfig Config) error
GetConfigRef() *Config
Log(logType, m string)
IsRunning() bool
GetBitrate() int64
}
// The revid struct provides fields to describe the state of a Revid.
type revid struct {
type Revid struct {
ffmpegPath string
tempDir string
ringBuffer *ring.Buffer
@ -122,38 +111,34 @@ type revid struct {
currentBitrate int64
}
// NewRevid returns a pointer to a new revid with the desired
// NewRevid returns a pointer to a new Revid with the desired
// configuration, and/or an error if construction of the new instant was not
// successful.
func NewRevid(config Config) (r *revid, err error) {
r = new(revid)
r.mutex = sync.Mutex{}
r.sendMutex = sync.Mutex{}
r.ringBuffer = ring.NewBuffer(ringBufferSize, ringBufferElementSize, writeTimeout)
err = r.changeState(config)
func New(c Config) (*Revid, error) {
var r Revid
err := r.reset(c)
if err != nil {
r = nil
return
return nil, err
}
r.ringBuffer = ring.NewBuffer(ringBufferSize, ringBufferElementSize, writeTimeout)
r.outputChan = make(chan []byte, outputChanSize)
r.isRunning = false
return
return &r, nil
}
// Returns the currently saved bitrate from the most recent bitrate check
// check bitrate output delay in consts for this period
func (r *revid) GetBitrate() int64 {
func (r *Revid) GetBitrate() int64 {
return r.currentBitrate
}
// GetConfigRef returns a pointer to the revidInst's Config struct object
func (r *revid) GetConfigRef() *Config {
func (r *Revid) GetConfigRef() *Config {
return &r.config
}
// changeState swaps the current config of a revid with the passed
// reset swaps the current config of a Revid with the passed
// configuration; checking validity and returning errors if not valid.
func (r *revid) changeState(config Config) error {
func (r *Revid) reset(config Config) error {
r.config.Logger = config.Logger
err := config.Validate(r)
if err != nil {
@ -214,25 +199,25 @@ noPacketizationSetup:
return nil
}
// ChangeConfig changes the current configuration of the revid instance.
func (r *revid) ChangeConfig(config Config) (err error) {
// ChangeConfig changes the current configuration of the Revid instance.
func (r *Revid) ChangeConfig(c Config) error {
// FIXME(kortschak): This is reimplemented in cmd/revid-cli/main.go.
// The implementation in the command is used and this is not.
// Decide on one or the other.
r.Stop()
r, err = NewRevid(config)
r, err := New(c)
if err != nil {
return
return err
}
r.Start()
return
return err
}
// Log takes a logtype and message and tries to send this information to the
// logger provided in the revid config - if there is one, otherwise the message
// is sent to stdout
func (r *revid) Log(logType, m string) {
func (r *Revid) Log(logType, m string) {
if r.config.Verbosity == Yes {
if r.config.Logger != nil {
r.config.Logger.Log("revid", logType, m)
@ -243,17 +228,17 @@ func (r *revid) Log(logType, m string) {
}
// IsRunning returns true if the revid is currently running and false otherwise
func (r *revid) IsRunning() bool {
func (r *Revid) IsRunning() bool {
return r.isRunning
}
// Start invokes a revid to start processing video from a defined input
// Start invokes a Revid to start processing video from a defined input
// and packetising (if theres packetization) to a defined output.
func (r *revid) Start() {
func (r *Revid) Start() {
r.mutex.Lock()
defer r.mutex.Unlock()
if r.isRunning {
r.Log(Warning, "revid.Start() called but revid already running!")
r.Log(Warning, "Revid.Start() called but revid already running!")
return
}
r.Log(Info, "Starting Revid!")
@ -279,12 +264,12 @@ func (r *revid) Start() {
}
// Stop halts any processing of video data from a camera or file
func (r *revid) Stop() {
func (r *Revid) Stop() {
r.mutex.Lock()
defer r.mutex.Unlock()
if !r.isRunning {
r.Log(Warning, "revid.Stop() called but revid not running!")
r.Log(Warning, "Revid.Stop() called but revid not running!")
return
}
@ -315,18 +300,18 @@ func (r *revid) Stop() {
// getFrameNoPacketization gets a frame directly from the revid output chan
// as we don't need to go through the generator with no packetization settings
func (r *revid) getFrameNoPacketization() []byte {
func (r *Revid) getFrameNoPacketization() []byte {
return <-r.outputChan
}
// getFramePacketization gets a frame from the generators output chan - the
// the generator being an mpegts or flv generator depending on the config
func (r *revid) getFramePacketization() []byte {
func (r *Revid) getFramePacketization() []byte {
return <-(r.generator.GetOutputChan())
}
// flushDataPacketization removes data from the revid inst's coutput chan
func (r *revid) flushData() {
// flushDataPacketization removes data from the Revid inst's coutput chan
func (r *Revid) flushData() {
switch r.config.Packetization {
case Flv:
for {
@ -342,7 +327,7 @@ done:
// packClips takes data segments; whether that be tsPackets or mjpeg frames and
// packs them into clips consisting of the amount frames specified in the config
func (r *revid) packClips() {
func (r *Revid) packClips() {
clipSize := 0
packetCount := 0
for r.isRunning {
@ -384,7 +369,7 @@ func (r *revid) packClips() {
// outputClips takes the clips produced in the packClips method and outputs them
// to the desired output defined in the revid config
func (r *revid) outputClips() {
func (r *Revid) outputClips() {
now := time.Now()
prevTime := now
bytes := 0
@ -458,7 +443,7 @@ func (r *revid) outputClips() {
}
// senClipToFile writes the passed clip to a file
func (r *revid) sendClipToFile(clip *ring.Chunk) error {
func (r *Revid) sendClipToFile(clip *ring.Chunk) error {
r.sendMutex.Lock()
_, err := clip.WriteTo(r.outputFile)
r.sendMutex.Unlock()
@ -466,7 +451,7 @@ func (r *revid) sendClipToFile(clip *ring.Chunk) error {
}
// sendClipToHTTP takes a clip and an output url and posts through http.
func (r *revid) sendClipToHTTP(clip *ring.Chunk) error {
func (r *Revid) sendClipToHTTP(clip *ring.Chunk) error {
defer r.sendMutex.Unlock()
r.sendMutex.Lock()
@ -479,7 +464,7 @@ func (r *revid) sendClipToHTTP(clip *ring.Chunk) error {
// use a method value for dispatching the sendClip work.
// So to save work in this case, sendClip should be made
// a proper method with a behaviour switch based on a
// revid field so that we can prepare these bytes only
// Revid field so that we can prepare these bytes only
// once for each clip (reusing a buffer field? or tt
// might be work using a sync.Pool for the bodies).
post := bytes.NewBuffer(make([]byte, 0, clip.Len()))
@ -507,7 +492,7 @@ func (r *revid) sendClipToHTTP(clip *ring.Chunk) error {
// sendClipToFfmpegRtmp sends the clip over the current rtmp connection using
// an ffmpeg process.
func (r *revid) sendClipToFfmpegRtmp(clip *ring.Chunk) error {
func (r *Revid) sendClipToFfmpegRtmp(clip *ring.Chunk) error {
r.sendMutex.Lock()
_, err := clip.WriteTo(r.ffmpegStdin)
r.sendMutex.Unlock()
@ -516,7 +501,7 @@ func (r *revid) sendClipToFfmpegRtmp(clip *ring.Chunk) error {
// sendClipToLibRtmp send the clip over the current rtmp connection using the
// c based librtmp library
func (r *revid) sendClipToLibRtmp(clip *ring.Chunk) error {
func (r *Revid) sendClipToLibRtmp(clip *ring.Chunk) error {
r.sendMutex.Lock()
_, err := clip.WriteTo(r.rtmpInst)
r.sendMutex.Unlock()
@ -524,7 +509,7 @@ func (r *revid) sendClipToLibRtmp(clip *ring.Chunk) error {
}
// setupOutputForFfmpegRtmp sets up output to rtmp using an ffmpeg process
func (r *revid) setupOutputForFfmpegRtmp() error {
func (r *Revid) setupOutputForFfmpegRtmp() error {
r.ffmpegCmd = exec.Command(ffmpegPath,
"-f", "h264",
"-r", r.config.FrameRate,
@ -558,7 +543,7 @@ func (r *revid) setupOutputForFfmpegRtmp() error {
// setupOutputForLibRtmp sets up rtmp output using the wrapper for the c based
// librtmp library - makes connection and starts comms etc.
func (r *revid) setupOutputForLibRtmp() error {
func (r *Revid) setupOutputForLibRtmp() error {
r.rtmpInst = rtmp.NewSession(r.config.RtmpUrl, rtmpConnectionTimout)
err := r.rtmpInst.StartSession()
for noOfTries := 0; err != nil && noOfTries < rtmpConnectionMaxTries; noOfTries++ {
@ -575,14 +560,14 @@ func (r *revid) setupOutputForLibRtmp() error {
}
// setupOutputForFile sets up an output file to output data to
func (r *revid) setupOutputForFile() (err error) {
func (r *Revid) setupOutputForFile() (err error) {
r.outputFile, err = os.Create(r.config.OutputFileName)
return
}
// setupInputForRaspivid sets up things for input from raspivid i.e. starts
// a raspivid process and pipes it's data output.
func (r *revid) setupInputForRaspivid() error {
func (r *Revid) setupInputForRaspivid() error {
r.Log(Info, "Starting raspivid!")
switch r.config.InputCodec {
case H264:
@ -642,7 +627,7 @@ func (r *revid) setupInputForRaspivid() error {
}
// setupInputForFile sets things up for getting input from a file
func (r *revid) setupInputForFile() error {
func (r *Revid) setupInputForFile() error {
fps, _ := strconv.Atoi(r.config.FrameRate)
r.parser.SetDelay(uint(float64(1000) / float64(fps)))
r.readFile()
@ -652,7 +637,7 @@ func (r *revid) setupInputForFile() error {
// testRtmp is useful to check robustness of connections. Intended to be run as
// goroutine. After every 'delayTime' the rtmp connection is ended and then
// restarted
func (r *revid) testRtmp(delayTime uint) {
func (r *Revid) testRtmp(delayTime uint) {
for {
time.Sleep(time.Duration(delayTime) * time.Millisecond)
r.rtmpInst.Close()
@ -660,9 +645,9 @@ func (r *revid) testRtmp(delayTime uint) {
}
}
// readCamera reads data from the defined camera while the revid is running.
// readCamera reads data from the defined camera while the Revid is running.
// TODO: use ringbuffer here instead of allocating mem every time!
func (r *revid) readCamera() {
func (r *Revid) readCamera() {
r.Log(Info, "Reading camera data!")
for r.isRunning {
data := make([]byte, 1)
@ -679,8 +664,8 @@ func (r *revid) readCamera() {
r.Log(Info, "Not trying to read from camera anymore!")
}
// readFile reads data from the defined file while the revid is running.
func (r *revid) readFile() error {
// readFile reads data from the defined file while the Revid is running.
func (r *Revid) readFile() error {
var err error
r.inputFile, err = os.Open(r.config.InputFileName)
if err != nil {