22 changed files with 685 additions and 664 deletions
@ -0,0 +1,155 @@
@@ -0,0 +1,155 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"net" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/bluenviron/gortsplib/v4/pkg/headers" |
||||
"github.com/bluenviron/mediamtx/internal/conf" |
||||
"github.com/bluenviron/mediamtx/internal/defs" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
type testHTTPAuthenticator struct { |
||||
*http.Server |
||||
} |
||||
|
||||
func (ts *testHTTPAuthenticator) initialize(t *testing.T, protocol string, action string) { |
||||
firstReceived := false |
||||
|
||||
ts.Server = &http.Server{ |
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
require.Equal(t, http.MethodPost, r.Method) |
||||
require.Equal(t, "/auth", r.URL.Path) |
||||
|
||||
var in struct { |
||||
IP string `json:"ip"` |
||||
User string `json:"user"` |
||||
Password string `json:"password"` |
||||
Path string `json:"path"` |
||||
Protocol string `json:"protocol"` |
||||
ID string `json:"id"` |
||||
Action string `json:"action"` |
||||
Query string `json:"query"` |
||||
} |
||||
err := json.NewDecoder(r.Body).Decode(&in) |
||||
require.NoError(t, err) |
||||
|
||||
var user string |
||||
if action == "publish" { |
||||
user = "testpublisher" |
||||
} else { |
||||
user = "testreader" |
||||
} |
||||
|
||||
if in.IP != "127.0.0.1" || |
||||
in.User != user || |
||||
in.Password != "testpass" || |
||||
in.Path != "teststream" || |
||||
in.Protocol != protocol || |
||||
(firstReceived && in.ID == "") || |
||||
in.Action != action || |
||||
(in.Query != "user=testreader&pass=testpass¶m=value" && |
||||
in.Query != "user=testpublisher&pass=testpass¶m=value" && |
||||
in.Query != "param=value") { |
||||
w.WriteHeader(http.StatusBadRequest) |
||||
return |
||||
} |
||||
|
||||
firstReceived = true |
||||
}), |
||||
} |
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:9120") |
||||
require.NoError(t, err) |
||||
|
||||
go ts.Server.Serve(ln) |
||||
} |
||||
|
||||
func (ts *testHTTPAuthenticator) close() { |
||||
ts.Server.Shutdown(context.Background()) |
||||
} |
||||
|
||||
func TestAuthSha256(t *testing.T) { |
||||
err := doAuthentication( |
||||
"", |
||||
conf.AuthMethods{headers.AuthBasic}, |
||||
&conf.Path{ |
||||
PublishUser: conf.Credential("sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ="), |
||||
PublishPass: conf.Credential("sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w="), |
||||
}, |
||||
defs.PathAccessRequest{ |
||||
Name: "mypath", |
||||
Query: "", |
||||
Publish: true, |
||||
SkipAuth: false, |
||||
IP: net.ParseIP("127.0.0.1"), |
||||
User: "testuser", |
||||
Pass: "testpass", |
||||
Proto: defs.AuthProtocolRTSP, |
||||
ID: nil, |
||||
RTSPRequest: nil, |
||||
RTSPBaseURL: nil, |
||||
RTSPNonce: "", |
||||
}, |
||||
) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
func TestAuthArgon2(t *testing.T) { |
||||
err := doAuthentication( |
||||
"", |
||||
conf.AuthMethods{headers.AuthBasic}, |
||||
&conf.Path{ |
||||
PublishUser: conf.Credential( |
||||
"argon2:$argon2id$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58"), |
||||
PublishPass: conf.Credential( |
||||
"argon2:$argon2i$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$/mrZ42TiTv1mcPnpMUera5oi0SFYbbyueAbdx5sUvWo"), |
||||
}, |
||||
defs.PathAccessRequest{ |
||||
Name: "mypath", |
||||
Query: "", |
||||
Publish: true, |
||||
SkipAuth: false, |
||||
IP: net.ParseIP("127.0.0.1"), |
||||
User: "testuser", |
||||
Pass: "testpass", |
||||
Proto: defs.AuthProtocolRTSP, |
||||
ID: nil, |
||||
RTSPRequest: nil, |
||||
RTSPBaseURL: nil, |
||||
RTSPNonce: "", |
||||
}, |
||||
) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
func TestAuthExternal(t *testing.T) { |
||||
au := &testHTTPAuthenticator{} |
||||
au.initialize(t, "rtsp", "publish") |
||||
defer au.close() |
||||
|
||||
err := doAuthentication( |
||||
"http://127.0.0.1:9120/auth", |
||||
conf.AuthMethods{headers.AuthBasic}, |
||||
&conf.Path{}, |
||||
defs.PathAccessRequest{ |
||||
Name: "teststream", |
||||
Query: "param=value", |
||||
Publish: true, |
||||
SkipAuth: false, |
||||
IP: net.ParseIP("127.0.0.1"), |
||||
User: "testpublisher", |
||||
Pass: "testpass", |
||||
Proto: defs.AuthProtocolRTSP, |
||||
ID: nil, |
||||
RTSPRequest: nil, |
||||
RTSPBaseURL: nil, |
||||
RTSPNonce: "", |
||||
}, |
||||
) |
||||
require.NoError(t, err) |
||||
} |
@ -1,441 +0,0 @@
@@ -1,441 +0,0 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"net" |
||||
"net/http" |
||||
"testing" |
||||
|
||||
"github.com/bluenviron/gortsplib/v4" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/base" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/description" |
||||
"github.com/pion/rtp" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
type testHTTPAuthenticator struct { |
||||
*http.Server |
||||
} |
||||
|
||||
func newTestHTTPAuthenticator(t *testing.T, protocol string, action string) *testHTTPAuthenticator { |
||||
firstReceived := false |
||||
|
||||
ts := &testHTTPAuthenticator{} |
||||
|
||||
ts.Server = &http.Server{ |
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
||||
require.Equal(t, http.MethodPost, r.Method) |
||||
require.Equal(t, "/auth", r.URL.Path) |
||||
|
||||
var in struct { |
||||
IP string `json:"ip"` |
||||
User string `json:"user"` |
||||
Password string `json:"password"` |
||||
Path string `json:"path"` |
||||
Protocol string `json:"protocol"` |
||||
ID string `json:"id"` |
||||
Action string `json:"action"` |
||||
Query string `json:"query"` |
||||
} |
||||
err := json.NewDecoder(r.Body).Decode(&in) |
||||
require.NoError(t, err) |
||||
|
||||
var user string |
||||
if action == "publish" { |
||||
user = "testpublisher" |
||||
} else { |
||||
user = "testreader" |
||||
} |
||||
|
||||
if in.IP != "127.0.0.1" || |
||||
in.User != user || |
||||
in.Password != "testpass" || |
||||
in.Path != "teststream" || |
||||
in.Protocol != protocol || |
||||
(firstReceived && in.ID == "") || |
||||
in.Action != action || |
||||
(in.Query != "user=testreader&pass=testpass¶m=value" && |
||||
in.Query != "user=testpublisher&pass=testpass¶m=value" && |
||||
in.Query != "param=value") { |
||||
w.WriteHeader(http.StatusBadRequest) |
||||
return |
||||
} |
||||
|
||||
firstReceived = true |
||||
}), |
||||
} |
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:9120") |
||||
require.NoError(t, err) |
||||
|
||||
go ts.Server.Serve(ln) |
||||
|
||||
return ts |
||||
} |
||||
|
||||
func (ts *testHTTPAuthenticator) close() { |
||||
ts.Server.Shutdown(context.Background()) |
||||
} |
||||
|
||||
func TestRTSPServer(t *testing.T) { |
||||
for _, auth := range []string{ |
||||
"none", |
||||
"internal", |
||||
"external", |
||||
} { |
||||
t.Run("auth_"+auth, func(t *testing.T) { |
||||
var conf string |
||||
|
||||
switch auth { |
||||
case "none": |
||||
conf = "paths:\n" + |
||||
" all_others:\n" |
||||
|
||||
case "internal": |
||||
conf = "rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" publishUser: testpublisher\n" + |
||||
" publishPass: testpass\n" + |
||||
" publishIPs: [127.0.0.0/16]\n" + |
||||
" readUser: testreader\n" + |
||||
" readPass: testpass\n" + |
||||
" readIPs: [127.0.0.0/16]\n" |
||||
|
||||
case "external": |
||||
conf = "externalAuthenticationURL: http://localhost:9120/auth\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" |
||||
} |
||||
|
||||
p, ok := newInstance(conf) |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
var a *testHTTPAuthenticator |
||||
if auth == "external" { |
||||
a = newTestHTTPAuthenticator(t, "rtsp", "publish") |
||||
} |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
source := gortsplib.Client{} |
||||
|
||||
err := source.StartRecording( |
||||
"rtsp://testpublisher:testpass@127.0.0.1:8554/teststream?param=value", |
||||
&description.Session{Medias: []*description.Media{medi}}) |
||||
require.NoError(t, err) |
||||
defer source.Close() |
||||
|
||||
if auth == "external" { |
||||
a.close() |
||||
a = newTestHTTPAuthenticator(t, "rtsp", "read") |
||||
defer a.close() |
||||
} |
||||
|
||||
reader := gortsplib.Client{} |
||||
|
||||
u, err := base.ParseURL("rtsp://testreader:testpass@127.0.0.1:8554/teststream?param=value") |
||||
require.NoError(t, err) |
||||
|
||||
err = reader.Start(u.Scheme, u.Host) |
||||
require.NoError(t, err) |
||||
defer reader.Close() |
||||
|
||||
desc, _, err := reader.Describe(u) |
||||
require.NoError(t, err) |
||||
|
||||
err = reader.SetupAll(desc.BaseURL, desc.Medias) |
||||
require.NoError(t, err) |
||||
|
||||
_, err = reader.Play(nil) |
||||
require.NoError(t, err) |
||||
}) |
||||
} |
||||
} |
||||
|
||||
func TestRTSPServerAuthHashedSHA256(t *testing.T) { |
||||
p, ok := newInstance( |
||||
"rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" publishUser: sha256:rl3rgi4NcZkpAEcacZnQ2VuOfJ0FxAqCRaKB/SwdZoQ=\n" + |
||||
" publishPass: sha256:E9JJ8stBJ7QM+nV4ZoUCeHk/gU3tPFh/5YieiJp6n2w=\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
source := gortsplib.Client{} |
||||
|
||||
err := source.StartRecording( |
||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream", |
||||
&description.Session{Medias: []*description.Media{medi}}) |
||||
require.NoError(t, err) |
||||
defer source.Close() |
||||
} |
||||
|
||||
func TestRTSPServerAuthHashedArgon2(t *testing.T) { |
||||
p, ok := newInstance( |
||||
"rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" publishUser: argon2:$argon2id$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$Ux/LWeTgJQPyfMMJo1myR64+o8rALHoPmlE1i/TR+58\n" + |
||||
" publishPass: argon2:$argon2i$v=19$m=4096,t=3,p=1$MTIzNDU2Nzg$/mrZ42TiTv1mcPnpMUera5oi0SFYbbyueAbdx5sUvWo\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
source := gortsplib.Client{} |
||||
|
||||
err := source.StartRecording( |
||||
"rtsp://testuser:testpass@127.0.0.1:8554/test/stream", |
||||
&description.Session{Medias: []*description.Media{medi}}) |
||||
require.NoError(t, err) |
||||
defer source.Close() |
||||
} |
||||
|
||||
func TestRTSPServerAuthFail(t *testing.T) { |
||||
for _, ca := range []struct { |
||||
name string |
||||
user string |
||||
pass string |
||||
}{ |
||||
{ |
||||
"wronguser", |
||||
"test1user", |
||||
"testpass", |
||||
}, |
||||
{ |
||||
"wrongpass", |
||||
"testuser", |
||||
"test1pass", |
||||
}, |
||||
{ |
||||
"wrongboth", |
||||
"test1user", |
||||
"test1pass", |
||||
}, |
||||
} { |
||||
t.Run("publish_"+ca.name, func(t *testing.T) { |
||||
p, ok := newInstance("rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" publishUser: testuser\n" + |
||||
" publishPass: testpass\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
c := gortsplib.Client{} |
||||
|
||||
err := c.StartRecording( |
||||
"rtsp://"+ca.user+":"+ca.pass+"@localhost:8554/test/stream", |
||||
&description.Session{Medias: []*description.Media{medi}}, |
||||
) |
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") |
||||
}) |
||||
} |
||||
|
||||
for _, ca := range []struct { |
||||
name string |
||||
user string |
||||
pass string |
||||
}{ |
||||
{ |
||||
"wronguser", |
||||
"test1user", |
||||
"testpass", |
||||
}, |
||||
{ |
||||
"wrongpass", |
||||
"testuser", |
||||
"test1pass", |
||||
}, |
||||
{ |
||||
"wrongboth", |
||||
"test1user", |
||||
"test1pass", |
||||
}, |
||||
} { |
||||
t.Run("read_"+ca.name, func(t *testing.T) { |
||||
p, ok := newInstance("rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" readUser: testuser\n" + |
||||
" readPass: testpass\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
c := gortsplib.Client{} |
||||
|
||||
u, err := base.ParseURL("rtsp://" + ca.user + ":" + ca.pass + "@localhost:8554/test/stream") |
||||
require.NoError(t, err) |
||||
|
||||
err = c.Start(u.Scheme, u.Host) |
||||
require.NoError(t, err) |
||||
defer c.Close() |
||||
|
||||
_, _, err = c.Describe(u) |
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") |
||||
}) |
||||
} |
||||
|
||||
t.Run("ip", func(t *testing.T) { |
||||
p, ok := newInstance("rtmp: no\n" + |
||||
"hls: no\n" + |
||||
"webrtc: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" + |
||||
" publishIPs: [128.0.0.1/32]\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
c := gortsplib.Client{} |
||||
|
||||
err := c.StartRecording( |
||||
"rtsp://localhost:8554/test/stream", |
||||
&description.Session{Medias: []*description.Media{medi}}, |
||||
) |
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") |
||||
}) |
||||
|
||||
t.Run("external", func(t *testing.T) { |
||||
p, ok := newInstance("externalAuthenticationURL: http://localhost:9120/auth\n" + |
||||
"paths:\n" + |
||||
" all_others:\n") |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
a := newTestHTTPAuthenticator(t, "rtsp", "publish") |
||||
defer a.close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
c := gortsplib.Client{} |
||||
|
||||
err := c.StartRecording( |
||||
"rtsp://testpublisher2:testpass@localhost:8554/teststream?param=value", |
||||
&description.Session{Medias: []*description.Media{medi}}, |
||||
) |
||||
require.EqualError(t, err, "bad status code: 401 (Unauthorized)") |
||||
}) |
||||
} |
||||
|
||||
func TestRTSPServerPublisherOverride(t *testing.T) { |
||||
for _, ca := range []string{ |
||||
"enabled", |
||||
"disabled", |
||||
} { |
||||
t.Run(ca, func(t *testing.T) { |
||||
conf := "rtmp: no\n" + |
||||
"paths:\n" + |
||||
" all_others:\n" |
||||
|
||||
if ca == "disabled" { |
||||
conf += " overridePublisher: no\n" |
||||
} |
||||
|
||||
p, ok := newInstance(conf) |
||||
require.Equal(t, true, ok) |
||||
defer p.Close() |
||||
|
||||
medi := testMediaH264 |
||||
|
||||
s1 := gortsplib.Client{} |
||||
|
||||
err := s1.StartRecording("rtsp://localhost:8554/teststream", |
||||
&description.Session{Medias: []*description.Media{medi}}) |
||||
require.NoError(t, err) |
||||
defer s1.Close() |
||||
|
||||
s2 := gortsplib.Client{} |
||||
|
||||
err = s2.StartRecording("rtsp://localhost:8554/teststream", |
||||
&description.Session{Medias: []*description.Media{medi}}) |
||||
if ca == "enabled" { |
||||
require.NoError(t, err) |
||||
defer s2.Close() |
||||
} else { |
||||
require.Error(t, err) |
||||
} |
||||
|
||||
frameRecv := make(chan struct{}) |
||||
|
||||
c := gortsplib.Client{} |
||||
|
||||
u, err := base.ParseURL("rtsp://localhost:8554/teststream") |
||||
require.NoError(t, err) |
||||
|
||||
err = c.Start(u.Scheme, u.Host) |
||||
require.NoError(t, err) |
||||
defer c.Close() |
||||
|
||||
desc, _, err := c.Describe(u) |
||||
require.NoError(t, err) |
||||
|
||||
err = c.SetupAll(desc.BaseURL, desc.Medias) |
||||
require.NoError(t, err) |
||||
|
||||
c.OnPacketRTP(desc.Medias[0], desc.Medias[0].Formats[0], func(pkt *rtp.Packet) { |
||||
if ca == "enabled" { |
||||
require.Equal(t, []byte{5, 15, 16, 17, 18}, pkt.Payload) |
||||
} else { |
||||
require.Equal(t, []byte{5, 11, 12, 13, 14}, pkt.Payload) |
||||
} |
||||
close(frameRecv) |
||||
}) |
||||
|
||||
_, err = c.Play(nil) |
||||
require.NoError(t, err) |
||||
|
||||
if ca == "enabled" { |
||||
err := s1.Wait() |
||||
require.EqualError(t, err, "EOF") |
||||
|
||||
err = s2.WritePacketRTP(medi, &rtp.Packet{ |
||||
Header: rtp.Header{ |
||||
Version: 0x02, |
||||
PayloadType: 96, |
||||
SequenceNumber: 57899, |
||||
Timestamp: 345234345, |
||||
SSRC: 978651231, |
||||
Marker: true, |
||||
}, |
||||
Payload: []byte{5, 15, 16, 17, 18}, |
||||
}) |
||||
require.NoError(t, err) |
||||
} else { |
||||
err = s1.WritePacketRTP(medi, &rtp.Packet{ |
||||
Header: rtp.Header{ |
||||
Version: 0x02, |
||||
PayloadType: 96, |
||||
SequenceNumber: 57899, |
||||
Timestamp: 345234345, |
||||
SSRC: 978651231, |
||||
Marker: true, |
||||
}, |
||||
Payload: []byte{5, 11, 12, 13, 14}, |
||||
}) |
||||
require.NoError(t, err) |
||||
} |
||||
|
||||
<-frameRecv |
||||
}) |
||||
} |
||||
} |
@ -1,14 +0,0 @@
@@ -1,14 +0,0 @@
|
||||
package defs |
||||
|
||||
import ( |
||||
"github.com/bluenviron/mediamtx/internal/conf" |
||||
"github.com/bluenviron/mediamtx/internal/stream" |
||||
) |
||||
|
||||
// PathManager is a path manager.
|
||||
type PathManager interface { |
||||
FindPathConf(req PathFindPathConfReq) (*conf.Path, error) |
||||
Describe(req PathDescribeReq) PathDescribeRes |
||||
AddPublisher(req PathAddPublisherReq) (Path, error) |
||||
AddReader(req PathAddReaderReq) (Path, *stream.Stream, error) |
||||
} |
@ -0,0 +1,264 @@
@@ -0,0 +1,264 @@
|
||||
package rtsp |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
|
||||
"github.com/bluenviron/gortsplib/v4" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/base" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/description" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/format" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/headers" |
||||
"github.com/bluenviron/mediamtx/internal/asyncwriter" |
||||
"github.com/bluenviron/mediamtx/internal/conf" |
||||
"github.com/bluenviron/mediamtx/internal/defs" |
||||
"github.com/bluenviron/mediamtx/internal/externalcmd" |
||||
"github.com/bluenviron/mediamtx/internal/stream" |
||||
"github.com/bluenviron/mediamtx/internal/test" |
||||
"github.com/bluenviron/mediamtx/internal/unit" |
||||
"github.com/pion/rtp" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
type dummyPath struct { |
||||
stream *stream.Stream |
||||
streamCreated chan struct{} |
||||
} |
||||
|
||||
func (p *dummyPath) Name() string { |
||||
return "teststream" |
||||
} |
||||
|
||||
func (p *dummyPath) SafeConf() *conf.Path { |
||||
return &conf.Path{} |
||||
} |
||||
|
||||
func (p *dummyPath) ExternalCmdEnv() externalcmd.Environment { |
||||
return externalcmd.Environment{} |
||||
} |
||||
|
||||
func (p *dummyPath) StartPublisher(req defs.PathStartPublisherReq) (*stream.Stream, error) { |
||||
var err error |
||||
p.stream, err = stream.New( |
||||
1460, |
||||
req.Desc, |
||||
true, |
||||
test.NilLogger{}, |
||||
) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
close(p.streamCreated) |
||||
return p.stream, nil |
||||
} |
||||
|
||||
func (p *dummyPath) StopPublisher(_ defs.PathStopPublisherReq) { |
||||
} |
||||
|
||||
func (p *dummyPath) RemovePublisher(_ defs.PathRemovePublisherReq) { |
||||
} |
||||
|
||||
func (p *dummyPath) RemoveReader(_ defs.PathRemoveReaderReq) { |
||||
} |
||||
|
||||
type dummyPathManager struct { |
||||
path *dummyPath |
||||
} |
||||
|
||||
func (pm *dummyPathManager) Describe(_ defs.PathDescribeReq) defs.PathDescribeRes { |
||||
return defs.PathDescribeRes{ |
||||
Path: pm.path, |
||||
Stream: pm.path.stream, |
||||
Redirect: "", |
||||
Err: nil, |
||||
} |
||||
} |
||||
|
||||
func (pm *dummyPathManager) AddPublisher(_ defs.PathAddPublisherReq) (defs.Path, error) { |
||||
return pm.path, nil |
||||
} |
||||
|
||||
func (pm *dummyPathManager) AddReader(_ defs.PathAddReaderReq) (defs.Path, *stream.Stream, error) { |
||||
return pm.path, pm.path.stream, nil |
||||
} |
||||
|
||||
func TestServerPublish(t *testing.T) { |
||||
path := &dummyPath{ |
||||
streamCreated: make(chan struct{}), |
||||
} |
||||
|
||||
pathManager := &dummyPathManager{path: path} |
||||
|
||||
s := &Server{ |
||||
Address: "127.0.0.1:8557", |
||||
AuthMethods: []headers.AuthMethod{headers.AuthBasic}, |
||||
ReadTimeout: conf.StringDuration(10 * time.Second), |
||||
WriteTimeout: conf.StringDuration(10 * time.Second), |
||||
WriteQueueSize: 512, |
||||
UseUDP: false, |
||||
UseMulticast: false, |
||||
RTPAddress: "", |
||||
RTCPAddress: "", |
||||
MulticastIPRange: "", |
||||
MulticastRTPPort: 0, |
||||
MulticastRTCPPort: 0, |
||||
IsTLS: false, |
||||
ServerCert: "", |
||||
ServerKey: "", |
||||
RTSPAddress: "", |
||||
Protocols: map[conf.Protocol]struct{}{conf.Protocol(gortsplib.TransportTCP): {}}, |
||||
RunOnConnect: "", |
||||
RunOnConnectRestart: false, |
||||
RunOnDisconnect: "", |
||||
ExternalCmdPool: nil, |
||||
PathManager: pathManager, |
||||
Parent: &test.NilLogger{}, |
||||
} |
||||
err := s.Initialize() |
||||
require.NoError(t, err) |
||||
defer s.Close() |
||||
|
||||
source := gortsplib.Client{} |
||||
|
||||
media0 := test.UniqueMediaH264() |
||||
|
||||
err = source.StartRecording( |
||||
"rtsp://testpublisher:testpass@127.0.0.1:8557/teststream?param=value", |
||||
&description.Session{Medias: []*description.Media{media0}}) |
||||
require.NoError(t, err) |
||||
defer source.Close() |
||||
|
||||
<-path.streamCreated |
||||
|
||||
aw := asyncwriter.New(512, &test.NilLogger{}) |
||||
|
||||
recv := make(chan struct{}) |
||||
|
||||
path.stream.AddReader(aw, |
||||
path.stream.Desc().Medias[0], |
||||
path.stream.Desc().Medias[0].Formats[0], |
||||
func(u unit.Unit) error { |
||||
require.Equal(t, [][]byte{ |
||||
test.FormatH264.SPS, |
||||
test.FormatH264.PPS, |
||||
{5, 2, 3, 4}, |
||||
}, u.(*unit.H264).AU) |
||||
close(recv) |
||||
return nil |
||||
}) |
||||
|
||||
err = source.WritePacketRTP(media0, &rtp.Packet{ |
||||
Header: rtp.Header{ |
||||
Version: 2, |
||||
Marker: true, |
||||
PayloadType: 96, |
||||
SequenceNumber: 123, |
||||
Timestamp: 45343, |
||||
SSRC: 563423, |
||||
}, |
||||
Payload: []byte{5, 2, 3, 4}, |
||||
}) |
||||
require.NoError(t, err) |
||||
|
||||
aw.Start() |
||||
<-recv |
||||
aw.Stop() |
||||
} |
||||
|
||||
func TestServerRead(t *testing.T) { |
||||
desc := &description.Session{Medias: []*description.Media{test.MediaH264}} |
||||
|
||||
stream, err := stream.New( |
||||
1460, |
||||
desc, |
||||
true, |
||||
test.NilLogger{}, |
||||
) |
||||
require.NoError(t, err) |
||||
|
||||
path := &dummyPath{stream: stream} |
||||
|
||||
pathManager := &dummyPathManager{path: path} |
||||
|
||||
s := &Server{ |
||||
Address: "127.0.0.1:8557", |
||||
AuthMethods: []headers.AuthMethod{headers.AuthBasic}, |
||||
ReadTimeout: conf.StringDuration(10 * time.Second), |
||||
WriteTimeout: conf.StringDuration(10 * time.Second), |
||||
WriteQueueSize: 512, |
||||
UseUDP: false, |
||||
UseMulticast: false, |
||||
RTPAddress: "", |
||||
RTCPAddress: "", |
||||
MulticastIPRange: "", |
||||
MulticastRTPPort: 0, |
||||
MulticastRTCPPort: 0, |
||||
IsTLS: false, |
||||
ServerCert: "", |
||||
ServerKey: "", |
||||
RTSPAddress: "", |
||||
Protocols: map[conf.Protocol]struct{}{conf.Protocol(gortsplib.TransportTCP): {}}, |
||||
RunOnConnect: "", |
||||
RunOnConnectRestart: false, |
||||
RunOnDisconnect: "", |
||||
ExternalCmdPool: nil, |
||||
PathManager: pathManager, |
||||
Parent: &test.NilLogger{}, |
||||
} |
||||
err = s.Initialize() |
||||
require.NoError(t, err) |
||||
defer s.Close() |
||||
|
||||
reader := gortsplib.Client{} |
||||
|
||||
u, err := base.ParseURL("rtsp://testreader:testpass@127.0.0.1:8557/teststream?param=value") |
||||
require.NoError(t, err) |
||||
|
||||
err = reader.Start(u.Scheme, u.Host) |
||||
require.NoError(t, err) |
||||
defer reader.Close() |
||||
|
||||
desc2, _, err := reader.Describe(u) |
||||
require.NoError(t, err) |
||||
|
||||
err = reader.SetupAll(desc2.BaseURL, desc2.Medias) |
||||
require.NoError(t, err) |
||||
|
||||
recv := make(chan struct{}) |
||||
|
||||
reader.OnPacketRTPAny(func(m *description.Media, f format.Format, p *rtp.Packet) { |
||||
require.Equal(t, &rtp.Packet{ |
||||
Header: rtp.Header{ |
||||
Version: 2, |
||||
Marker: true, |
||||
PayloadType: 96, |
||||
SequenceNumber: p.SequenceNumber, |
||||
Timestamp: 0, |
||||
SSRC: p.SSRC, |
||||
CSRC: []uint32{}, |
||||
}, |
||||
Payload: []byte{ |
||||
0x18, 0x00, 0x19, 0x67, 0x42, 0xc0, 0x28, 0xd9, |
||||
0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, |
||||
0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, |
||||
0x3c, 0x60, 0xc9, 0x20, 0x00, 0x04, 0x08, 0x06, |
||||
0x07, 0x08, 0x00, 0x04, 0x05, 0x02, 0x03, 0x04, |
||||
}, |
||||
}, p) |
||||
close(recv) |
||||
}) |
||||
|
||||
_, err = reader.Play(nil) |
||||
require.NoError(t, err) |
||||
|
||||
stream.WriteUnit(desc.Medias[0], desc.Medias[0].Formats[0], &unit.H264{ |
||||
Base: unit.Base{ |
||||
NTP: time.Time{}, |
||||
}, |
||||
AU: [][]byte{ |
||||
{5, 2, 3, 4}, // IDR
|
||||
}, |
||||
}) |
||||
|
||||
<-recv |
||||
} |
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
package test |
||||
|
||||
import ( |
||||
"github.com/bluenviron/gortsplib/v4/pkg/description" |
||||
"github.com/bluenviron/gortsplib/v4/pkg/format" |
||||
) |
||||
|
||||
// MediaH264 is a test H264 media.
|
||||
var MediaH264 = UniqueMediaH264() |
||||
|
||||
// MediaMPEG4Audio is a test MPEG-4 audio media.
|
||||
var MediaMPEG4Audio = UniqueMediaMPEG4Audio() |
||||
|
||||
// UniqueMediaH264 is a test H264 media.
|
||||
func UniqueMediaH264() *description.Media { |
||||
return &description.Media{ |
||||
Type: description.MediaTypeVideo, |
||||
Formats: []format.Format{FormatH264}, |
||||
} |
||||
} |
||||
|
||||
// UniqueMediaMPEG4Audio is a test MPEG-4 audio media.
|
||||
func UniqueMediaMPEG4Audio() *description.Media { |
||||
return &description.Media{ |
||||
Type: description.MediaTypeAudio, |
||||
Formats: []format.Format{FormatMPEG4Audio}, |
||||
} |
||||
} |
Loading…
Reference in new issue