/* DESCRIPTION webcam.go provides an implementation of AVDevice for webcams. AUTHORS Saxon A. Nelson-Milton LICENSE Copyright (C) 2019 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 in gpl.txt. If not, see http://www.gnu.org/licenses. */ package revid import ( "errors" "fmt" "io" "os/exec" "strings" "bitbucket.org/ausocean/utils/logger" ) const ( defaultWebcamInputPath = "/dev/video0" defaultWebcamFrameRate = 25 defaultWebcamBitrate = 400 defaultWebcamWidth = 1280 defaultWebcamHeight = 720 ) var ( errWebcamBadFrameRate = errors.New("frame rate bad or unset, defaulting") errWebcamBadBitrate = errors.New("bitrate bad or unset, defaulting") errWebcamWidth = errors.New("width bad or unset, defaulting") errWebcamHeight = errors.New("height bad or unset, defaulting") ) type Webcam struct { out io.ReadCloser log Logger cfg Config cmd *exec.Cmd } func NewWebcam(l Logger) *Webcam { return &Webcam{log: l} } func (w *Webcam) Set(c Config) error { var errs []error if c.Width == 0 { errs = append(errs, errBadWidth) c.Width = raspividDefaultWidth } if c.Height == 0 { errs = append(errs, errBadHeight) c.Height = raspividDefaultHeight } if c.FrameRate == 0 { errs = append(errs, errBadFrameRate) c.FrameRate = raspividDefaultFramerate } if c.Bitrate <= 0 { errs = append(errs, errBadBitrate) c.Bitrate = raspividDefaultBitrate } w.cfg = c return multiError(errs) } func (w *Webcam) Start() error { args := []string{ "-i", w.cfg.InputPath, "-f", "h264", "-r", fmt.Sprint(w.cfg.FrameRate), } br := w.cfg.Bitrate * 1000 args = append(args, "-b:v", fmt.Sprint(br), "-maxrate", fmt.Sprint(br), "-bufsize", fmt.Sprint(br/2), "-s", fmt.Sprintf("%dx%d", w.cfg.Width, w.cfg.Height), "-", ) w.log.Log(logger.Info, pkg+"ffmpeg args", "args", strings.Join(args, " ")) w.cmd = exec.Command("ffmpeg", args...) var err error w.out, err = w.cmd.StdoutPipe() if err != nil { return fmt.Errorf("failed to create pipe: %w", err) } err = w.cmd.Start() if err != nil { return fmt.Errorf("failed to start ffmpeg: %w", err) } return nil } func (w *Webcam) Stop() error { if w.cmd == nil || w.cmd.Process == nil { return errors.New("raspivid process was never started") } err := w.cmd.Process.Kill() if err != nil { return fmt.Errorf("could not kill raspivid process: %w", err) } return w.out.Close() } func (w *Webcam) Read(p []byte) (int, error) { return w.out.Read(p) }