diff --git a/client.go b/client.go index 96f61ea9..7e93a54c 100644 --- a/client.go +++ b/client.go @@ -1142,7 +1142,7 @@ func (c *Client) doAnnounce(u *base.URL, tracks Tracks) (*base.Response, error) Header: base.Header{ "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }, false) if err != nil { return nil, err diff --git a/client_read_test.go b/client_read_test.go index 1f2f3731..f4c83ef9 100644 --- a/client_read_test.go +++ b/client_read_test.go @@ -76,7 +76,7 @@ func TestClientReadTracks(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -237,7 +237,7 @@ func TestClientRead(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{scheme + "://" + listenIP + ":8554/test/stream?param=value/"}, }, - Body: Tracks{track}.Write(), + Body: Tracks{track}.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -490,7 +490,7 @@ func TestClientReadNonStandardFrameSize(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: Tracks{track}.Write(), + Body: Tracks{track}.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -591,7 +591,7 @@ func TestClientReadPartial(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://" + listenIP + ":8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -728,7 +728,7 @@ func TestClientReadNoContentBase(t *testing.T) { Header: base.Header{ "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -840,7 +840,7 @@ func TestClientReadAnyPort(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -968,7 +968,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1098,7 +1098,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1286,7 +1286,7 @@ func TestClientReadDifferentInterleavedIDs(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1443,7 +1443,7 @@ func TestClientReadRedirect(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1600,7 +1600,7 @@ func TestClientReadPause(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1768,7 +1768,7 @@ func TestClientReadRTCPReport(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -1942,7 +1942,7 @@ func TestClientReadErrorTimeout(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -2096,7 +2096,7 @@ func TestClientReadIgnoreTCPInvalidTrack(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) @@ -2221,7 +2221,7 @@ func TestClientReadSeek(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) diff --git a/client_test.go b/client_test.go index 320828f0..31a8fcbd 100644 --- a/client_test.go +++ b/client_test.go @@ -80,7 +80,7 @@ func TestClientSession(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp"}, "Session": base.HeaderValue{"123456"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) }() @@ -161,7 +161,7 @@ func TestClientAuth(t *testing.T) { Header: base.Header{ "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }.Write(bconn.Writer) require.NoError(t, err) }() @@ -225,7 +225,7 @@ func TestClientDescribeCharset(t *testing.T) { "Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, }, - Body: Tracks{track1}.Write(), + Body: Tracks{track1}.Write(false), }.Write(bconn.Writer) require.NoError(t, err) }() diff --git a/server_publish_test.go b/server_publish_test.go index b2947e9d..9132819f 100644 --- a/server_publish_test.go +++ b/server_publish_test.go @@ -372,7 +372,7 @@ func TestServerPublishErrorSetupDifferentPaths(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -455,7 +455,7 @@ func TestServerPublishErrorSetupTrackTwice(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -558,7 +558,7 @@ func TestServerPublishErrorRecordPartialTracks(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -704,7 +704,7 @@ func TestServerPublish(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -892,7 +892,7 @@ func TestServerPublishNonStandardFrameSize(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: Tracks{track}.Write(), + Body: Tracks{track}.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -996,7 +996,7 @@ func TestServerPublishErrorInvalidProtocol(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -1098,7 +1098,7 @@ func TestServerPublishRTCPReport(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -1258,7 +1258,7 @@ func TestServerPublishTimeout(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -1386,7 +1386,7 @@ func TestServerPublishWithoutTeardown(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) @@ -1506,7 +1506,7 @@ func TestServerPublishUDPChangeConn(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) diff --git a/server_read_test.go b/server_read_test.go index ad4e54c6..16b378e2 100644 --- a/server_read_test.go +++ b/server_read_test.go @@ -11,6 +11,7 @@ import ( "time" "github.com/pion/rtp" + psdp "github.com/pion/sdp/v3" "github.com/stretchr/testify/require" "golang.org/x/net/ipv4" @@ -570,6 +571,54 @@ func TestServerRead(t *testing.T) { } } +func TestServerReadVLCMulticast(t *testing.T) { + track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) + require.NoError(t, err) + + stream := NewServerStream(Tracks{track}) + + listenIP := multicastCapableIP(t) + + s := &Server{ + Handler: &testServerHandler{ + onDescribe: func(ctx *ServerHandlerOnDescribeCtx) (*base.Response, *ServerStream, error) { + return &base.Response{ + StatusCode: base.StatusOK, + }, stream, nil + }, + }, + RTSPAddress: listenIP + ":8554", + MulticastIPRange: "224.1.0.0/16", + MulticastRTPPort: 8000, + MulticastRTCPPort: 8001, + } + + err = s.Start() + require.NoError(t, err) + defer s.Close() + + nconn, err := net.Dial("tcp", listenIP+":8554") + require.NoError(t, err) + bconn := bufio.NewReadWriter(bufio.NewReader(nconn), bufio.NewWriter(nconn)) + defer nconn.Close() + + res, err := writeReqReadRes(bconn, base.Request{ + Method: base.Describe, + URL: mustParseURL("rtsp://" + listenIP + ":8554/teststream?vlcmulticast"), + Header: base.Header{ + "CSeq": base.HeaderValue{"1"}, + }, + }) + require.NoError(t, err) + require.Equal(t, base.StatusOK, res.StatusCode) + + var desc psdp.SessionDescription + err = desc.Unmarshal(res.Body) + require.NoError(t, err) + + require.Equal(t, "224.1.0.0", desc.ConnectionInformation.Address.Address) +} + func TestServerReadNonStandardFrameSize(t *testing.T) { track, err := NewTrackH264(96, &TrackConfigH264{[]byte{0x01, 0x02, 0x03, 0x04}, []byte{0x01, 0x02, 0x03, 0x04}}) require.NoError(t, err) diff --git a/server_test.go b/server_test.go index c2cb51d3..71c98628 100644 --- a/server_test.go +++ b/server_test.go @@ -1171,7 +1171,7 @@ func TestServerErrorInvalidPath(t *testing.T) { "CSeq": base.HeaderValue{"1"}, "Content-Type": base.HeaderValue{"application/sdp"}, }, - Body: tracks.Write(), + Body: tracks.Write(false), }) require.NoError(t, err) require.Equal(t, base.StatusOK, res.StatusCode) diff --git a/serverconn.go b/serverconn.go index 993f0915..2e466428 100644 --- a/serverconn.go +++ b/serverconn.go @@ -5,6 +5,7 @@ import ( "context" "crypto/tls" "net" + "net/url" "strings" "time" @@ -377,8 +378,20 @@ func (sc *ServerConn) handleRequest(req *base.Request) (*base.Response, error) { res.Header["Content-Base"] = base.HeaderValue{req.URL.String() + "/"} res.Header["Content-Type"] = base.HeaderValue{"application/sdp"} + // VLC uses multicast if the SDP contains a multicast address. + // therefore, we introduce a special query (vlcmulticast) that allows + // to return a SDP that contains a multicast address. + multicast := false + if sc.s.MulticastIPRange != "" { + if q, err := url.ParseQuery(query); err == nil { + if _, ok := q["vlcmulticast"]; ok { + multicast = true + } + } + } + if stream != nil { - res.Body = stream.Tracks().Write() + res.Body = stream.Tracks().Write(multicast) } } diff --git a/track.go b/track.go index 4360d379..ed18ee5a 100644 --- a/track.go +++ b/track.go @@ -128,7 +128,7 @@ type Tracks []*Track // ReadTracks decodes tracks from SDP. func ReadTracks(byts []byte) (Tracks, error) { - desc := sdp.SessionDescription{} + var desc sdp.SessionDescription err := desc.Unmarshal(byts) if err != nil { return nil, err @@ -192,7 +192,12 @@ func cloneAndClearTracks(ts Tracks) Tracks { } // Write encodes tracks into SDP. -func (ts Tracks) Write() []byte { +func (ts Tracks) Write(multicast bool) []byte { + address := "0.0.0.0" + if multicast { + address = "224.1.0.0" + } + sout := &sdp.SessionDescription{ SessionName: psdp.SessionName("Stream"), Origin: psdp.Origin{ @@ -205,7 +210,7 @@ func (ts Tracks) Write() []byte { ConnectionInformation: &psdp.ConnectionInformation{ NetworkType: "IN", AddressType: "IP4", - Address: &psdp.Address{Address: "0.0.0.0"}, + Address: &psdp.Address{Address: address}, }, TimeDescriptions: []psdp.TimeDescription{ {Timing: psdp.Timing{0, 0}}, //nolint:govet