diff --git a/Makefile b/Makefile index b094581..a56a723 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ run: test: docker compose stop test && docker compose down test && docker compose run --rm test -test-local: - ./scripts/local_run_test.sh +mac-test-local: + ./scripts/mac_local_run_test.sh html-local-coverage: go tool cover -html=coverage.out diff --git a/go.mod b/go.mod index 5ac26ee..a0818be 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/asticode/go-astits v1.11.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/pion/webrtc/v3 v3.1.47 + github.com/stretchr/testify v1.8.0 github.com/szatmary/gocaption v0.0.0-20220607192049-fdd59655f0c3 go.uber.org/fx v1.20.1 go.uber.org/zap v1.23.0 @@ -14,6 +15,7 @@ require ( require ( github.com/asticode/go-astikit v0.36.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/pion/datachannel v1.5.2 // indirect github.com/pion/dtls/v2 v2.1.5 // indirect @@ -31,10 +33,12 @@ require ( github.com/pion/transport v0.13.1 // indirect github.com/pion/turn/v2 v2.0.8 // indirect github.com/pion/udp v0.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/dig v1.17.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.2.0 // indirect golang.org/x/net v0.2.0 // indirect golang.org/x/sys v0.2.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 1bfc9ae..4206cb5 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,10 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -89,6 +91,7 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -96,6 +99,7 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/szatmary/gocaption v0.0.0-20220607192049-fdd59655f0c3 h1:j8SVIV6YZreqjOPGjxM48tB4XgS8oUZdgy0cyN7YrBg= github.com/szatmary/gocaption v0.0.0-20220607192049-fdd59655f0c3/go.mod h1:l9r7RYKHGLuHbXpKJhJgASvi8xT+Uqxnz9B26uVU73c= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -179,6 +183,7 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -188,3 +193,4 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/controllers/probers/srt_mpets.go b/internal/controllers/probers/srt_mpets.go index 9e973e3..aaf0582 100644 --- a/internal/controllers/probers/srt_mpets.go +++ b/internal/controllers/probers/srt_mpets.go @@ -42,10 +42,9 @@ func (c *SrtMpegTs) StreamInfo(req *entities.RequestParams) (map[entities.Codec] defer srtConnection.Close() streamInfoMap := map[entities.Codec]entities.Stream{} - inboundMpegTsPacket := make([]byte, c.c.SRTReadBufferSizeBytes) // probing mpeg-ts for N packets to find metadata - go c.fromSRTToWriterPipe(srtConnection, inboundMpegTsPacket, w, cancel) + go c.fromSRTToWriterPipe(srtConnection, w, cancel) c.l.Info("probing has starting demuxing") @@ -67,8 +66,12 @@ func (c *SrtMpegTs) StreamInfo(req *entities.RequestParams) (map[entities.Codec] } } -func (c *SrtMpegTs) fromSRTToWriterPipe(srtConnection *astisrt.Connection, inboundMpegTsPacket []byte, w *io.PipeWriter, cancel context.CancelFunc) { +func (c *SrtMpegTs) fromSRTToWriterPipe(srtConnection *astisrt.Connection, w *io.PipeWriter, cancel context.CancelFunc) { defer cancel() + defer w.Close() + defer srtConnection.Close() + + inboundMpegTsPacket := make([]byte, c.c.SRTReadBufferSizeBytes) c.l.Info("probing has started") for i := 1; i < c.c.ProbingSize; i++ { diff --git a/internal/controllers/probers/srt_mpets_test.go b/internal/controllers/probers/srt_mpets_test.go new file mode 100644 index 0000000..782542a --- /dev/null +++ b/internal/controllers/probers/srt_mpets_test.go @@ -0,0 +1,35 @@ +package probers_test + +import ( + "testing" + + "github.com/flavioribeiro/donut/internal/controllers/probers" + "github.com/flavioribeiro/donut/internal/entities" + "github.com/flavioribeiro/donut/internal/teststreaming" + "github.com/flavioribeiro/donut/internal/web" + "github.com/stretchr/testify/assert" + "go.uber.org/fx" + "go.uber.org/fx/fxtest" +) + +func TestSrtMpegTs_StreamInfo(t *testing.T) { + ffmpeg := teststreaming.FFMPEG_LIVE_SRT_MPEG_TS_H264_AAC + + defer ffmpeg.Stop() + ffmpeg.Start() + + var controller *probers.SrtMpegTs + fxtest.New(t, + web.Dependencies(false), + fx.Populate(&controller), + ) + + streams, err := controller.StreamInfo(&entities.RequestParams{ + SRTHost: ffmpeg.Output().Host, + SRTPort: uint16(ffmpeg.Output().Port), + SRTStreamID: "test_id", + }) + assert.Nil(t, err) + assert.NotNil(t, streams) + assert.Equal(t, ffmpeg.ExpectedStreams()[entities.H264], streams[entities.H264]) +} diff --git a/internal/entities/entities.go b/internal/entities/entities.go index df2b274..8ea4dcc 100644 --- a/internal/entities/entities.go +++ b/internal/entities/entities.go @@ -69,7 +69,7 @@ const ( const ( UnknownType MediaType = "unknownMediaType" VideoType MediaType = "video" - AudioTyp MediaType = "audio" + AudioType MediaType = "audio" ) type Stream struct { diff --git a/internal/entities/errors.go b/internal/entities/errors.go index cbcf78d..c452328 100644 --- a/internal/entities/errors.go +++ b/internal/entities/errors.go @@ -11,3 +11,4 @@ var ErrMissingSRTStreamID = errors.New("SRTStreamID must not be empty") var ErrMissingWebRTCSetup = errors.New("WebRTCController.SetupPeerConnection must be called first") var ErrMissingRemoteOffer = errors.New("nil offer, in order to connect one must pass a valid offer") var ErrMissingRequestParams = errors.New("RequestParams must not be nil") +var ErrMissingProcess = errors.New("there is no process running") diff --git a/internal/mapper/mapper.go b/internal/mapper/mapper.go index 10e8c49..082de66 100644 --- a/internal/mapper/mapper.go +++ b/internal/mapper/mapper.go @@ -31,7 +31,7 @@ func FromMpegTsStreamTypeToType(st astits.StreamType) entities.MediaType { return entities.VideoType } if st.IsAudio() { - return entities.AudioTyp + return entities.AudioType } return entities.UnknownType } diff --git a/internal/teststreaming/ffmpeg.go b/internal/teststreaming/ffmpeg.go new file mode 100644 index 0000000..60cdcf6 --- /dev/null +++ b/internal/teststreaming/ffmpeg.go @@ -0,0 +1,102 @@ +package teststreaming + +import ( + "log" + "os/exec" + "strings" + "time" + + "github.com/flavioribeiro/donut/internal/entities" +) + +const ( + ffmpeg_startup = 5 * time.Second +) + +type FFmpeg interface { + Start() error + Stop() error + ExpectedStreams() map[entities.Codec]entities.Stream + Output() FFmpegOutput +} +type FFmpegOutput struct { + Host string + Port int +} + +var FFMPEG_LIVE_SRT_MPEG_TS_H264_AAC = testFFmpeg{ + cmd: ` + -hide_banner -loglevel verbose + -re -f lavfi -i testsrc2=size=1280x720:rate=30,format=yuv420p + -f lavfi -i sine=frequency=1000:sample_rate=44100 + -c:v libx264 -preset veryfast -tune zerolatency -profile:v baseline + -b:v 1000k -bufsize 2000k -x264opts keyint=30:min-keyint=30:scenecut=-1 + -f mpegts srt://0.0.0.0:45678?mode=listener&smoother=live&transtype=live + `, + expectedStreams: map[entities.Codec]entities.Stream{ + entities.H264: entities.Stream{Codec: entities.H264, Type: entities.VideoType}, + entities.AAC: entities.Stream{Codec: entities.AAC, Type: entities.AudioType}, + }, + output: FFmpegOutput{Host: "127.0.0.1", Port: 45678}, +} + +type testFFmpeg struct { + cmd string + expectedStreams map[entities.Codec]entities.Stream + cmdExec *exec.Cmd + output FFmpegOutput +} + +func (t *testFFmpeg) Start() error { + t.cmdExec = exec.Command("ffmpeg", prepareFFmpegParameters(t.cmd)...) + // Useful for debugging + // t.cmdExec.Stdout = os.Stdout + // t.cmdExec.Stderr = os.Stderr + + go func() { + if err := t.cmdExec.Run(); err != nil { + if strings.Contains(err.Error(), "signal: killed") { + return + } + log.Fatalln("XXXXXXXXXXXX Error running ffmpeg XXXXXXXXXXXX", err.Error()) + return + } + }() + time.Sleep(ffmpeg_startup) + return nil +} + +func (t *testFFmpeg) Stop() error { + if t == nil || t.cmdExec == nil { + return entities.ErrMissingProcess + } + + if err := t.cmdExec.Process.Kill(); err != nil { + return err + } + return nil +} + +func (t *testFFmpeg) ExpectedStreams() map[entities.Codec]entities.Stream { + return t.expectedStreams +} + +func (t *testFFmpeg) Output() FFmpegOutput { + return t.output +} + +func prepareFFmpegParameters(cmd string) []string { + result := []string{} + + for _, item := range strings.Split(cmd, " ") { + item = strings.ReplaceAll(item, "\\", "") + item = strings.ReplaceAll(item, "\n", "") + item = strings.ReplaceAll(item, "\t", "") + item = strings.ReplaceAll(item, " ", "") + if item != "" { + result = append(result, item) + } + } + + return result +} diff --git a/scripts/mac_local_run_test.sh b/scripts/mac_local_run_test.sh new file mode 100755 index 0000000..4d4c2f0 --- /dev/null +++ b/scripts/mac_local_run_test.sh @@ -0,0 +1,19 @@ +if ! brew list srt &>/dev/null; then + echo "ERROR you must install srt" + echo "brew install srt" + exit 1 +fi + +if ! brew list ffmpeg &>/dev/null; then + echo "ERROR you must install ffmpeg" + echo "brew install ffmpeg" + exit 1 +fi + +export CGO_LDFLAGS="-L$(brew --prefix srt)/lib -lsrt" +export CGO_CFLAGS="-I$(brew --prefix srt)/include/" + +# testing with logging +# ref https://github.com/golang/go/issues/46959#issuecomment-1407594935 +# go test -v -p 1 ./... +go test -v ./... \ No newline at end of file