diff --git a/device/alsa/alsa.go b/device/alsa/alsa.go index 5f83d016..f08667f5 100644 --- a/device/alsa/alsa.go +++ b/device/alsa/alsa.go @@ -462,3 +462,8 @@ func nearestPowerOfTwo(n int) int { } return v } + +// IsRunning is used to determine if the ALSA device is running. +func (d *ALSA) IsRunning() bool { + return d.mode == running +} diff --git a/device/alsa/alsa_test.go b/device/alsa/alsa_test.go index 44e54e38..aeeb6ac9 100644 --- a/device/alsa/alsa_test.go +++ b/device/alsa/alsa_test.go @@ -4,9 +4,10 @@ NAME AUTHOR Trek Hopton + Scott Barnard LICENSE - This file is Copyright (C) 2019 the Australian Ocean Lab (AusOcean) + This file is Copyright (C) 2019-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 @@ -25,6 +26,7 @@ LICENSE package alsa import ( + "bytes" "io/ioutil" "os" "strconv" @@ -106,3 +108,47 @@ func TestNearestPowerOfTwo(t *testing.T) { }) } } + +func TestIsRunning(t *testing.T) { + const dur = 250 * time.Millisecond + const sampleRate = 1000 + const channels = 1 + const bitDepth = 16 + const recPeriod = 1 + + l := logger.New(logger.Debug, &bytes.Buffer{}, true) // Discard logs. + d := New(l) + + err := d.Set(config.Config{ + SampleRate: sampleRate, + Channels: channels, + BitDepth: bitDepth, + RecPeriod: recPeriod, + InputCodec: codecutil.ADPCM, + }) + if err != nil { + t.Skipf("could not set device: %w", err) + } + + err = d.Start() + if err != nil { + t.Fatalf("could not start device %w", err) + } + + time.Sleep(dur) + + if !d.IsRunning() { + t.Error("device isn't running, when it should be") + } + + err = d.Stop() + if err != nil { + t.Error(err.Error()) + } + + time.Sleep(dur) + + if d.IsRunning() { + t.Error("device is running, when it should not be") + } +} diff --git a/device/device.go b/device/device.go index a75ccb0a..efdc7043 100644 --- a/device/device.go +++ b/device/device.go @@ -55,6 +55,9 @@ type AVDevice interface { // Stop will stop the AVDevice from capturing media data. From this point // Reads will no longer be successful. Stop() error + + // IsRunning is used to determine if the device is running. + IsRunning() bool } // multiError implements the built in error interface. multiError is used here diff --git a/device/file/file.go b/device/file/file.go index 3d990b93..d068ace6 100644 --- a/device/file/file.go +++ b/device/file/file.go @@ -36,8 +36,9 @@ import ( // AVFile is an implementation of the AVDevice interface for a file containg // audio or video data. type AVFile struct { - f io.ReadCloser - cfg config.Config + f io.ReadCloser + cfg config.Config + isRunning bool } // NewAVFile returns a new AVFile. @@ -62,11 +63,19 @@ func (m *AVFile) Start() error { if err != nil { return fmt.Errorf("could not open media file: %w", err) } + m.isRunning = true return nil } // Stop will close the file such that any further reads will fail. -func (m *AVFile) Stop() error { return m.f.Close() } +func (m *AVFile) Stop() error { + err := m.f.Close() + if err == nil { + m.isRunning = false + return nil + } + return err +} // Read implements io.Reader. If start has not been called, or Start has been // called and Stop has since been called, an error is returned. @@ -76,3 +85,8 @@ func (m *AVFile) Read(p []byte) (int, error) { } return 0, errors.New("AV file is closed") } + +// IsRunning is used to determine if the AVFile device is running. +func (m *AVFile) IsRunning() bool { + return m.f != nil && m.isRunning +} diff --git a/device/file/file_test.go b/device/file/file_test.go new file mode 100644 index 00000000..6bf547f4 --- /dev/null +++ b/device/file/file_test.go @@ -0,0 +1,68 @@ +/* +DESCRIPTION + file_test.go tests the file AVDevice. + +AUTHORS + Scott Barnard + +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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package file + +import ( + "testing" + "time" + + "bitbucket.org/ausocean/av/revid/config" +) + +func TestIsRunning(t *testing.T) { + const dur = 250 * time.Millisecond + const path = "../../../test/test-data/av/input/motion-detection/mjpeg/school.mjpeg" + + d := New() + + err := d.Set(config.Config{ + InputPath: path, + }) + if err != nil { + t.Skipf("could not set device: %w", err) + } + + err = d.Start() + if err != nil { + t.Fatalf("could not start device %w", err) + } + + time.Sleep(dur) + + if !d.IsRunning() { + t.Error("device isn't running, when it should be") + } + + err = d.Stop() + if err != nil { + t.Error(err.Error()) + } + + time.Sleep(dur) + + if d.IsRunning() { + t.Error("device is running, when it should not be") + } +} diff --git a/device/geovision/geovision.go b/device/geovision/geovision.go index d09054ad..decf1e80 100644 --- a/device/geovision/geovision.go +++ b/device/geovision/geovision.go @@ -88,11 +88,12 @@ var ( // IP camera. This has been designed to implement the GV-BX4700-8F in particular. // Any other models are untested. type GeoVision struct { - cfg avconfig.Config - log avconfig.Logger - rtpClt *rtp.Client - rtspClt *rtsp.Client - rtcpClt *rtcp.Client + cfg avconfig.Config + log avconfig.Logger + rtpClt *rtp.Client + rtspClt *rtsp.Client + rtcpClt *rtcp.Client + isRunning bool } // NewGeoVision returns a new GeoVision. @@ -278,6 +279,7 @@ func (g *GeoVision) Start() error { } g.log.Log(logger.Debug, pkg+"RTSP server PLAY response", "response", resp.String()) g.log.Log(logger.Info, pkg+"play requested, now receiving stream") + g.isRunning = true return nil } @@ -299,6 +301,8 @@ func (g *GeoVision) Stop() error { g.log.Log(logger.Info, pkg+"RTP, RTSP and RTCP clients stopped and closed") + g.isRunning = false + return nil } @@ -339,3 +343,8 @@ func parseSvrRTCPPort(resp rtsp.Response) (int, error) { } return 0, errors.New("SETUP response did not provide RTCP port") } + +// IsRunning is used to determine if the geovision is running. +func (g *GeoVision) IsRunning() bool { + return g.isRunning +} diff --git a/device/geovision/geovision_test.go b/device/geovision/geovision_test.go new file mode 100644 index 00000000..784313e3 --- /dev/null +++ b/device/geovision/geovision_test.go @@ -0,0 +1,74 @@ +/* +DESCRIPTION + geovision_test.go tests the geovision AVDevice. + +AUTHORS + Scott Barnard + +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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ + +package geovision + +import ( + "bytes" + "testing" + "time" + + "bitbucket.org/ausocean/av/codec/codecutil" + "bitbucket.org/ausocean/av/revid/config" + "bitbucket.org/ausocean/utils/logger" +) + +func TestIsRunning(t *testing.T) { + const dur = 250 * time.Millisecond + const ip = "192.168.4.20" + + l := logger.New(logger.Debug, &bytes.Buffer{}, true) // Discard logs. + d := New(l) + + err := d.Set(config.Config{ + Logger: l, + InputCodec: codecutil.H264, + CameraIP: ip, + }) + if err != nil { + t.Skipf("could not set device: %w", err) + } + + err = d.Start() + if err != nil { + t.Fatalf("could not start device %w", err) + } + + time.Sleep(dur) + + if !d.IsRunning() { + t.Error("device isn't running, when it should be") + } + + err = d.Stop() + if err != nil { + t.Error(err.Error()) + } + + time.Sleep(dur) + + if d.IsRunning() { + t.Error("device is running, when it should not be") + } +} diff --git a/device/raspivid/raspivid.go b/device/raspivid/raspivid.go index cbae8c83..d47fc346 100644 --- a/device/raspivid/raspivid.go +++ b/device/raspivid/raspivid.go @@ -107,11 +107,12 @@ var AutoWhiteBalanceModes = [...]string{ // Raspivid is an implementation of AVDevice that provides control over the // raspivid command to allow reading of data from a Raspberry Pi camera. type Raspivid struct { - cfg config.Config - cmd *exec.Cmd - out io.ReadCloser - log config.Logger - done chan struct{} + cfg config.Config + cmd *exec.Cmd + out io.ReadCloser + log config.Logger + done chan struct{} + isRunning bool } // New returns a new Raspivid. @@ -287,6 +288,7 @@ func (r *Raspivid) Start() error { if err != nil { return fmt.Errorf("could not start raspivid command: %w", err) } + r.isRunning = true return nil } @@ -310,5 +312,11 @@ func (r *Raspivid) Stop() error { if err != nil { return fmt.Errorf("could not kill raspivid process: %w", err) } + r.isRunning = false return r.out.Close() } + +// IsRunning is used to determine if the pi's camera is running. +func (r *Raspivid) IsRunning() bool { + return r.isRunning +} diff --git a/device/raspivid/raspivid_test.go b/device/raspivid/raspivid_test.go new file mode 100644 index 00000000..a1822317 --- /dev/null +++ b/device/raspivid/raspivid_test.go @@ -0,0 +1,71 @@ +/* +DESCRIPTION + raspivid_test.go tests the raspivid AVDevice. + +AUTHORS + Scott Barnard + +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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ +package raspivid + +import ( + "bytes" + "testing" + "time" + + "bitbucket.org/ausocean/av/codec/codecutil" + "bitbucket.org/ausocean/av/revid/config" + "bitbucket.org/ausocean/utils/logger" +) + +func TestIsRunning(t *testing.T) { + const dur = 250 * time.Millisecond + + l := logger.New(logger.Debug, &bytes.Buffer{}, true) // Discard logs. + d := New(l) + + err := d.Set(config.Config{ + Logger: l, + InputCodec: codecutil.H264, + }) + if err != nil { + t.Skipf("could not set device: %w", err) + } + + err = d.Start() + if err != nil { + t.Fatalf("could not start device %w", err) + } + + time.Sleep(dur) + + if !d.IsRunning() { + t.Error("device isn't running, when it should be") + } + + err = d.Stop() + if err != nil { + t.Error(err.Error()) + } + + time.Sleep(dur) + + if d.IsRunning() { + t.Error("device is running, when it should not be") + } +} diff --git a/device/webcam/webcam.go b/device/webcam/webcam.go index 2d5e27e3..583a8158 100644 --- a/device/webcam/webcam.go +++ b/device/webcam/webcam.go @@ -62,11 +62,12 @@ var ( // Webcam is an implementation of the AVDevice interface for a Webcam. Webcam // uses an ffmpeg process to pipe the video data from the webcam. type Webcam struct { - out io.ReadCloser - log config.Logger - cfg config.Config - cmd *exec.Cmd - done chan struct{} + out io.ReadCloser + log config.Logger + cfg config.Config + cmd *exec.Cmd + done chan struct{} + isRunning bool } // New returns a new Webcam. @@ -161,6 +162,8 @@ func (w *Webcam) Start() error { return fmt.Errorf("could not pipe command error: %w", err) } + w.isRunning = true + go func() { for { select { @@ -202,7 +205,12 @@ func (w *Webcam) Stop() error { if err != nil { return fmt.Errorf("could not kill ffmpeg process: %w", err) } - return w.out.Close() + err = w.out.Close() + if err == nil { + w.isRunning = false + return nil + } + return err } // Read implements io.Reader. If the pipe is nil a read error is returned. @@ -212,3 +220,8 @@ func (w *Webcam) Read(p []byte) (int, error) { } return 0, errors.New("webcam not streaming") } + +// IsRunning is used to determine if the webcam is running. +func (w *Webcam) IsRunning() bool { + return w.isRunning +} diff --git a/device/webcam/webcam_test.go b/device/webcam/webcam_test.go new file mode 100644 index 00000000..45f8e205 --- /dev/null +++ b/device/webcam/webcam_test.go @@ -0,0 +1,71 @@ +/* +DESCRIPTION + webcam_test.go tests the webcam AVDevice. + +AUTHORS + Scott Barnard + +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 + in gpl.txt. If not, see http://www.gnu.org/licenses. +*/ +package webcam + +import ( + "bytes" + "testing" + "time" + + "bitbucket.org/ausocean/av/codec/codecutil" + "bitbucket.org/ausocean/av/revid/config" + "bitbucket.org/ausocean/utils/logger" +) + +func TestIsRunning(t *testing.T) { + const dur = 250 * time.Millisecond + + l := logger.New(logger.Debug, &bytes.Buffer{}, true) // Discard logs. + d := New(l) + + err := d.Set(config.Config{ + Logger: l, + InputCodec: codecutil.H264, + }) + if err != nil { + t.Skipf("could not set device: %w", err) + } + + err = d.Start() + if err != nil { + t.Fatalf("could not start device %w", err) + } + + time.Sleep(dur) + + if !d.IsRunning() { + t.Error("device isn't running, when it should be") + } + + err = d.Stop() + if err != nil { + t.Error(err.Error()) + } + + time.Sleep(dur) + + if d.IsRunning() { + t.Error("device is running, when it should not be") + } +}