/* DESCRIPTION slate.go houses vidforward slate functionality including the slate writing routine and HTTP request handlers for receiving of new slate videos. AUTHORS Saxon A. Nelson-Milton LICENSE Copyright (C) 2022 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. */ package main import ( "errors" "fmt" "io" "time" "bitbucket.org/ausocean/av/codec/h264" "bitbucket.org/ausocean/av/device/file" "bitbucket.org/ausocean/utils/logging" ) func writeSlateAndCheckErrors(dst io.Writer, signalCh chan struct{}, log logging.Logger) error { // Also create an errCh that will be used to communicate errors from the // writeSlate routine. errCh := make(chan error) go writeSlate(dst, errCh, signalCh, log) // We'll watch out for any errors that happen within a 5 second window. This // will indicate something seriously wrong with init, like a missing file etc. const startupWindowDuration = 5 * time.Second startupWindow := time.NewTimer(startupWindowDuration) select { // If this triggers first, we're all good. case <-startupWindow.C: log.Debug("out of error window") // We consider any errors after this either to be normal i.e. as a result // of stopping the slate input, or something that can not be handled, and // only logged, therefore we can close the error channel errCh now. // This will also let the routine know that errors can no longer be sent // down errCh. close(errCh) // This means we got a slate error pretty early and need to let caller know. case err := <-errCh: return fmt.Errorf("could not write slate image: %w", err) } return nil } // writeSlate is a routine that employs a file input device and h264 lexer to // write a h264 encoded slate image to the provided revid pipeline. func writeSlate(dst io.Writer, errCh chan error, exitSignal chan struct{}, log logging.Logger) { log.Info("writing slate") const ( // This is temporary and will eventually be part of a broadcast configuration // where the remote vidforward API user can provide the slate image. slateFileName = "slate.h264" // Assume 25fps until this becomes configurable. slateFrameRate = 25 loopSetting = true frameDelay = time.Second / slateFrameRate ) fileInput := file.NewWith(log, slateFileName, loopSetting) err := fileInput.Start() if err != nil { errCh <- err return } // This will wait for a signal from the provided slateExitSignal (or from a // timeout) to stop writing the slate by "Stopping" the file input which will // terminate the Lex function. go func() { slateTimeoutTimer := time.NewTimer(24 * time.Hour) select { case <-slateTimeoutTimer.C: log.Warning("slate timeout") case <-exitSignal: log.Info("slate exit signal") } log.Info("stopping file input") fileInput.Stop() }() // Begin lexing the slate file and send frames to rv pipeline. We'll stay in // here until file input closes or there's an unexpected error. err = h264.Lex(dst, fileInput, frameDelay) // If we get to this point, it means that the we've finished lexing for some // reason; let's figure out why. select { // The only reason we'd get a receive on errCh from this side is if its been // closed. This means that we've exceeded the "startup error" period, and that // either the error is normal from stopping the input, or we can no longer inform // the caller and just need to log the problem. case <-errCh: if errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) { log.Debug("got expected error", "error", err) return } log.Error("got unexpected error", "error", err) // This means that a problem occured pretty early in lexing. default: log.Error("unexpected error during lex startup", "error", err) errCh <- err } log.Error("finished writing slate") }