mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
server: support reading with VLC and multicast
This commit is contained in:
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
}()
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
11
track.go
11
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
|
||||
|
Reference in New Issue
Block a user