allow setting additional properties of streams through description.Stream

This commit is contained in:
aler9
2023-08-16 19:02:49 +02:00
committed by Alessandro Ros
parent 4e000eb2dd
commit cdbecb1f5d
54 changed files with 943 additions and 893 deletions

128
client.go
View File

@@ -23,10 +23,10 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/bytecounter" "github.com/bluenviron/gortsplib/v4/pkg/bytecounter"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime" "github.com/bluenviron/gortsplib/v4/pkg/rtptime"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
@@ -91,7 +91,7 @@ func findBaseURL(sd *sdp.SessionDescription, res *base.Response, u *url.URL) (*u
return u, nil return u, nil
} }
func resetMediaControls(ms media.Medias) { func resetMediaControls(ms []*description.Media) {
for i, media := range ms { for i, media := range ms {
media.Control = "trackID=" + strconv.FormatInt(int64(i), 10) media.Control = "trackID=" + strconv.FormatInt(int64(i), 10)
} }
@@ -148,14 +148,14 @@ type describeReq struct {
} }
type announceReq struct { type announceReq struct {
url *url.URL url *url.URL
medias media.Medias desc *description.Session
res chan clientRes res chan clientRes
} }
type setupReq struct { type setupReq struct {
media *media.Media
baseURL *url.URL baseURL *url.URL
media *description.Media
rtpPort int rtpPort int
rtcpPort int rtcpPort int
res chan clientRes res chan clientRes
@@ -175,10 +175,9 @@ type pauseReq struct {
} }
type clientRes struct { type clientRes struct {
medias media.Medias sd *description.Session // describe only
baseURL *url.URL res *base.Response
res *base.Response err error
err error
} }
// ClientOnRequestFunc is the prototype of Client.OnRequest. // ClientOnRequestFunc is the prototype of Client.OnRequest.
@@ -200,13 +199,13 @@ type ClientOnDecodeErrorFunc func(err error)
type OnPacketRTPFunc func(*rtp.Packet) type OnPacketRTPFunc func(*rtp.Packet)
// OnPacketRTPAnyFunc is the prototype of the callback passed to OnPacketRTP(Any). // OnPacketRTPAnyFunc is the prototype of the callback passed to OnPacketRTP(Any).
type OnPacketRTPAnyFunc func(*media.Media, format.Format, *rtp.Packet) type OnPacketRTPAnyFunc func(*description.Media, format.Format, *rtp.Packet)
// OnPacketRTCPFunc is the prototype of the callback passed to OnPacketRTCP(). // OnPacketRTCPFunc is the prototype of the callback passed to OnPacketRTCP().
type OnPacketRTCPFunc func(rtcp.Packet) type OnPacketRTCPFunc func(rtcp.Packet)
// OnPacketRTCPAnyFunc is the prototype of the callback passed to OnPacketRTCPAny(). // OnPacketRTCPAnyFunc is the prototype of the callback passed to OnPacketRTCPAny().
type OnPacketRTCPAnyFunc func(*media.Media, rtcp.Packet) type OnPacketRTCPAnyFunc func(*description.Media, rtcp.Packet)
// Client is a RTSP client. // Client is a RTSP client.
type Client struct { type Client struct {
@@ -306,7 +305,7 @@ type Client struct {
lastDescribeURL *url.URL lastDescribeURL *url.URL
baseURL *url.URL baseURL *url.URL
effectiveTransport *Transport effectiveTransport *Transport
medias map[*media.Media]*clientMedia medias map[*description.Media]*clientMedia
tcpCallbackByChannel map[int]readFunc tcpCallbackByChannel map[int]readFunc
lastRange *headers.Range lastRange *headers.Range
checkTimeoutTimer *time.Timer checkTimeoutTimer *time.Timer
@@ -442,7 +441,7 @@ func (c *Client) Start(scheme string, host string) error {
} }
// StartRecording connects to the address and starts publishing given media. // StartRecording connects to the address and starts publishing given media.
func (c *Client) StartRecording(address string, medias media.Medias) error { func (c *Client) StartRecording(address string, desc *description.Session) error {
u, err := url.Parse(address) u, err := url.Parse(address)
if err != nil { if err != nil {
return err return err
@@ -453,13 +452,13 @@ func (c *Client) StartRecording(address string, medias media.Medias) error {
return err return err
} }
_, err = c.Announce(u, medias) _, err = c.Announce(u, desc)
if err != nil { if err != nil {
c.Close() c.Close()
return err return err
} }
err = c.SetupAll(u, medias) err = c.SetupAll(u, desc.Medias)
if err != nil { if err != nil {
c.Close() c.Close()
return err return err
@@ -505,11 +504,11 @@ func (c *Client) runInner() error {
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.describe: case req := <-c.describe:
medias, baseURL, res, err := c.doDescribe(req.url) sd, res, err := c.doDescribe(req.url)
req.res <- clientRes{medias: medias, baseURL: baseURL, res: res, err: err} req.res <- clientRes{sd: sd, res: res, err: err}
case req := <-c.announce: case req := <-c.announce:
res, err := c.doAnnounce(req.url, req.medias) res, err := c.doAnnounce(req.url, req.desc)
req.res <- clientRes{res: res, err: err} req.res <- clientRes{res: res, err: err}
case req := <-c.setup: case req := <-c.setup:
@@ -624,7 +623,7 @@ func (c *Client) trySwitchingProtocol() error {
c.connURL = prevConnURL c.connURL = prevConnURL
// some Hikvision cameras require a describe before a setup // some Hikvision cameras require a describe before a setup
_, _, _, err := c.doDescribe(c.lastDescribeURL) _, _, err := c.doDescribe(c.lastDescribeURL)
if err != nil { if err != nil {
return err return err
} }
@@ -649,7 +648,7 @@ func (c *Client) trySwitchingProtocol() error {
return nil return nil
} }
func (c *Client) trySwitchingProtocol2(medi *media.Media, baseURL *url.URL) (*base.Response, error) { func (c *Client) trySwitchingProtocol2(medi *description.Media, baseURL *url.URL) (*base.Response, error) {
c.OnTransportSwitch(fmt.Errorf("switching to TCP because server requested it")) c.OnTransportSwitch(fmt.Errorf("switching to TCP because server requested it"))
prevConnURL := c.connURL prevConnURL := c.connURL
@@ -661,7 +660,7 @@ func (c *Client) trySwitchingProtocol2(medi *media.Media, baseURL *url.URL) (*ba
c.connURL = prevConnURL c.connURL = prevConnURL
// some Hikvision cameras require a describe before a setup // some Hikvision cameras require a describe before a setup
_, _, _, err := c.doDescribe(c.lastDescribeURL) _, _, err := c.doDescribe(c.lastDescribeURL)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -970,7 +969,7 @@ func (c *Client) doOptions(u *url.URL) (*base.Response, error) {
return res, nil return res, nil
} }
// Options writes an OPTIONS request and reads a response. // Options sends an OPTIONS request.
func (c *Client) Options(u *url.URL) (*base.Response, error) { func (c *Client) Options(u *url.URL) (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
select { select {
@@ -983,14 +982,14 @@ func (c *Client) Options(u *url.URL) (*base.Response, error) {
} }
} }
func (c *Client) doDescribe(u *url.URL) (media.Medias, *url.URL, *base.Response, error) { func (c *Client) doDescribe(u *url.URL) (*description.Session, *base.Response, error) {
err := c.checkState(map[clientState]struct{}{ err := c.checkState(map[clientState]struct{}{
clientStateInitial: {}, clientStateInitial: {},
clientStatePrePlay: {}, clientStatePrePlay: {},
clientStatePreRecord: {}, clientStatePreRecord: {},
}) })
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
res, err := c.do(&base.Request{ res, err := c.do(&base.Request{
@@ -1001,7 +1000,7 @@ func (c *Client) doDescribe(u *url.URL) (media.Medias, *url.URL, *base.Response,
}, },
}, false, false) }, false, false)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
if res.StatusCode != base.StatusOK { if res.StatusCode != base.StatusOK {
@@ -1013,7 +1012,7 @@ func (c *Client) doDescribe(u *url.URL) (media.Medias, *url.URL, *base.Response,
ru, err := url.Parse(res.Header["Location"][0]) ru, err := url.Parse(res.Header["Location"][0])
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
if u.User != nil { if u.User != nil {
@@ -1028,57 +1027,58 @@ func (c *Client) doDescribe(u *url.URL) (media.Medias, *url.URL, *base.Response,
return c.doDescribe(ru) return c.doDescribe(ru)
} }
return nil, nil, res, liberrors.ErrClientBadStatusCode{Code: res.StatusCode, Message: res.StatusMessage} return nil, res, liberrors.ErrClientBadStatusCode{Code: res.StatusCode, Message: res.StatusMessage}
} }
ct, ok := res.Header["Content-Type"] ct, ok := res.Header["Content-Type"]
if !ok || len(ct) != 1 { if !ok || len(ct) != 1 {
return nil, nil, nil, liberrors.ErrClientContentTypeMissing{} return nil, nil, liberrors.ErrClientContentTypeMissing{}
} }
// strip encoding information from Content-Type header // strip encoding information from Content-Type header
ct = base.HeaderValue{strings.Split(ct[0], ";")[0]} ct = base.HeaderValue{strings.Split(ct[0], ";")[0]}
if ct[0] != "application/sdp" { if ct[0] != "application/sdp" {
return nil, nil, nil, liberrors.ErrClientContentTypeUnsupported{CT: ct} return nil, nil, liberrors.ErrClientContentTypeUnsupported{CT: ct}
} }
var sd sdp.SessionDescription var ssd sdp.SessionDescription
err = sd.Unmarshal(res.Body) err = ssd.Unmarshal(res.Body)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
var medias media.Medias var desc description.Session
err = medias.Unmarshal(sd.MediaDescriptions) err = desc.Unmarshal(&ssd)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
baseURL, err := findBaseURL(&sd, res, u) baseURL, err := findBaseURL(&ssd, res, u)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, err
} }
desc.BaseURL = baseURL
c.lastDescribeURL = u c.lastDescribeURL = u
return medias, baseURL, res, nil return &desc, res, nil
} }
// Describe writes a DESCRIBE request and reads a Response. // Describe sends a DESCRIBE request.
func (c *Client) Describe(u *url.URL) (media.Medias, *url.URL, *base.Response, error) { func (c *Client) Describe(u *url.URL) (*description.Session, *base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
select { select {
case c.describe <- describeReq{url: u, res: cres}: case c.describe <- describeReq{url: u, res: cres}:
res := <-cres res := <-cres
return res.medias, res.baseURL, res.res, res.err return res.sd, res.res, res.err
case <-c.ctx.Done(): case <-c.ctx.Done():
return nil, nil, nil, liberrors.ErrClientTerminated{} return nil, nil, liberrors.ErrClientTerminated{}
} }
} }
func (c *Client) doAnnounce(u *url.URL, medias media.Medias) (*base.Response, error) { func (c *Client) doAnnounce(u *url.URL, desc *description.Session) (*base.Response, error) {
err := c.checkState(map[clientState]struct{}{ err := c.checkState(map[clientState]struct{}{
clientStateInitial: {}, clientStateInitial: {},
}) })
@@ -1086,9 +1086,9 @@ func (c *Client) doAnnounce(u *url.URL, medias media.Medias) (*base.Response, er
return nil, err return nil, err
} }
resetMediaControls(medias) resetMediaControls(desc.Medias)
byts, err := medias.Marshal(false).Marshal() byts, err := desc.Marshal(false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1117,11 +1117,11 @@ func (c *Client) doAnnounce(u *url.URL, medias media.Medias) (*base.Response, er
return res, nil return res, nil
} }
// Announce writes an ANNOUNCE request and reads a Response. // Announce sends an ANNOUNCE request.
func (c *Client) Announce(u *url.URL, medias media.Medias) (*base.Response, error) { func (c *Client) Announce(u *url.URL, desc *description.Session) (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
select { select {
case c.announce <- announceReq{url: u, medias: medias, res: cres}: case c.announce <- announceReq{url: u, desc: desc, res: cres}:
res := <-cres res := <-cres
return res.res, res.err return res.res, res.err
@@ -1132,7 +1132,7 @@ func (c *Client) Announce(u *url.URL, medias media.Medias) (*base.Response, erro
func (c *Client) doSetup( func (c *Client) doSetup(
baseURL *url.URL, baseURL *url.URL,
medi *media.Media, medi *description.Media,
rtpPort int, rtpPort int,
rtcpPort int, rtcpPort int,
) (*base.Response, error) { ) (*base.Response, error) {
@@ -1384,7 +1384,7 @@ func (c *Client) doSetup(
} }
if c.medias == nil { if c.medias == nil {
c.medias = make(map[*media.Media]*clientMedia) c.medias = make(map[*description.Media]*clientMedia)
} }
c.medias[medi] = cm c.medias[medi] = cm
@@ -1417,20 +1417,20 @@ func (c *Client) findFreeChannelPair() int {
} }
} }
// Setup writes a SETUP request and reads a Response. // Setup sends a SETUP request.
// rtpPort and rtcpPort are used only if transport is UDP. // rtpPort and rtcpPort are used only if transport is UDP.
// if rtpPort and rtcpPort are zero, they are chosen automatically. // if rtpPort and rtcpPort are zero, they are chosen automatically.
func (c *Client) Setup( func (c *Client) Setup(
baseURL *url.URL, baseURL *url.URL,
media *media.Media, media *description.Media,
rtpPort int, rtpPort int,
rtcpPort int, rtcpPort int,
) (*base.Response, error) { ) (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
select { select {
case c.setup <- setupReq{ case c.setup <- setupReq{
media: media,
baseURL: baseURL, baseURL: baseURL,
media: media,
rtpPort: rtpPort, rtpPort: rtpPort,
rtcpPort: rtcpPort, rtcpPort: rtcpPort,
res: cres, res: cres,
@@ -1444,7 +1444,7 @@ func (c *Client) Setup(
} }
// SetupAll setups all the given medias. // SetupAll setups all the given medias.
func (c *Client) SetupAll(baseURL *url.URL, medias media.Medias) error { func (c *Client) SetupAll(baseURL *url.URL, medias []*description.Media) error {
for _, m := range medias { for _, m := range medias {
_, err := c.Setup(baseURL, m, 0, 0) _, err := c.Setup(baseURL, m, 0, 0)
if err != nil { if err != nil {
@@ -1509,7 +1509,7 @@ func (c *Client) doPlay(ra *headers.Range) (*base.Response, error) {
return res, nil return res, nil
} }
// Play writes a PLAY request and reads a Response. // Play sends a PLAY request.
// This can be called only after Setup(). // This can be called only after Setup().
func (c *Client) Play(ra *headers.Range) (*base.Response, error) { func (c *Client) Play(ra *headers.Range) (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
@@ -1551,7 +1551,7 @@ func (c *Client) doRecord() (*base.Response, error) {
return nil, nil return nil, nil
} }
// Record writes a RECORD request and reads a Response. // Record sends a RECORD request.
// This can be called only after Announce() and Setup(). // This can be called only after Announce() and Setup().
func (c *Client) Record() (*base.Response, error) { func (c *Client) Record() (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
@@ -1601,7 +1601,7 @@ func (c *Client) doPause() (*base.Response, error) {
return res, nil return res, nil
} }
// Pause writes a PAUSE request and reads a Response. // Pause sends a PAUSE request.
// This can be called only after Play() or Record(). // This can be called only after Play() or Record().
func (c *Client) Pause() (*base.Response, error) { func (c *Client) Pause() (*base.Response, error) {
cres := make(chan clientRes) cres := make(chan clientRes)
@@ -1648,26 +1648,26 @@ func (c *Client) OnPacketRTCPAny(cb OnPacketRTCPAnyFunc) {
} }
// OnPacketRTP sets the callback that is called when a RTP packet is read. // OnPacketRTP sets the callback that is called when a RTP packet is read.
func (c *Client) OnPacketRTP(medi *media.Media, forma format.Format, cb OnPacketRTPFunc) { func (c *Client) OnPacketRTP(medi *description.Media, forma format.Format, cb OnPacketRTPFunc) {
cm := c.medias[medi] cm := c.medias[medi]
ct := cm.formats[forma.PayloadType()] ct := cm.formats[forma.PayloadType()]
ct.onPacketRTP = cb ct.onPacketRTP = cb
} }
// OnPacketRTCP sets the callback that is called when a RTCP packet is read. // OnPacketRTCP sets the callback that is called when a RTCP packet is read.
func (c *Client) OnPacketRTCP(medi *media.Media, cb OnPacketRTCPFunc) { func (c *Client) OnPacketRTCP(medi *description.Media, cb OnPacketRTCPFunc) {
cm := c.medias[medi] cm := c.medias[medi]
cm.onPacketRTCP = cb cm.onPacketRTCP = cb
} }
// WritePacketRTP writes a RTP packet to the server. // WritePacketRTP writes a RTP packet to the server.
func (c *Client) WritePacketRTP(medi *media.Media, pkt *rtp.Packet) error { func (c *Client) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error {
return c.WritePacketRTPWithNTP(medi, pkt, c.timeNow()) return c.WritePacketRTPWithNTP(medi, pkt, c.timeNow())
} }
// WritePacketRTPWithNTP writes a RTP packet to the server. // WritePacketRTPWithNTP writes a RTP packet to the server.
// ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports. // ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports.
func (c *Client) WritePacketRTPWithNTP(medi *media.Media, pkt *rtp.Packet, ntp time.Time) error { func (c *Client) WritePacketRTPWithNTP(medi *description.Media, pkt *rtp.Packet, ntp time.Time) error {
byts := make([]byte, c.MaxPacketSize) byts := make([]byte, c.MaxPacketSize)
n, err := pkt.MarshalTo(byts) n, err := pkt.MarshalTo(byts)
if err != nil { if err != nil {
@@ -1688,7 +1688,7 @@ func (c *Client) WritePacketRTPWithNTP(medi *media.Media, pkt *rtp.Packet, ntp t
} }
// WritePacketRTCP writes a RTCP packet to the server. // WritePacketRTCP writes a RTCP packet to the server.
func (c *Client) WritePacketRTCP(medi *media.Media, pkt rtcp.Packet) error { func (c *Client) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error {
byts, err := pkt.Marshal() byts, err := pkt.Marshal()
if err != nil { if err != nil {
return err return err
@@ -1713,7 +1713,7 @@ func (c *Client) PacketPTS(forma format.Format, pkt *rtp.Packet) (time.Duration,
// PacketNTP returns the NTP timestamp of an incoming RTP packet. // PacketNTP returns the NTP timestamp of an incoming RTP packet.
// The NTP timestamp is computed from sender reports. // The NTP timestamp is computed from sender reports.
func (c *Client) PacketNTP(medi *media.Media, pkt *rtp.Packet) (time.Time, bool) { func (c *Client) PacketNTP(medi *description.Media, pkt *rtp.Packet) (time.Time, bool) {
cm := c.medias[medi] cm := c.medias[medi]
ct := cm.formats[pkt.PayloadType] ct := cm.formats[pkt.PayloadType]
return ct.rtcpReceiver.PacketNTP(pkt.Timestamp) return ct.rtcpReceiver.PacketNTP(pkt.Timestamp)

View File

@@ -9,12 +9,12 @@ import (
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
) )
type clientMedia struct { type clientMedia struct {
c *Client c *Client
media *media.Media media *description.Media
formats map[uint8]*clientFormat formats map[uint8]*clientFormat
tcpChannel int tcpChannel int
udpRTPListener *clientUDPListener udpRTPListener *clientUDPListener
@@ -71,7 +71,7 @@ func (cm *clientMedia) allocateUDPListeners(multicast bool, rtpAddress string, r
return err return err
} }
func (cm *clientMedia) setMedia(medi *media.Media) { func (cm *clientMedia) setMedia(medi *description.Media) {
cm.media = medi cm.media = medi
cm.formats = make(map[uint8]*clientFormat) cm.formats = make(map[uint8]*clientFormat)

View File

@@ -12,15 +12,16 @@ import (
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
psdp "github.com/pion/sdp/v3"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
"github.com/bluenviron/gortsplib/v4/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
) )
@@ -29,8 +30,34 @@ func ipPtr(v net.IP) *net.IP {
return &v return &v
} }
func mustMarshalMedias(medias media.Medias) []byte { func mediasToSDP(medias []*description.Media) []byte {
byts, err := medias.Marshal(false).Marshal() resetMediaControls(medias)
sout := &psdp.SessionDescription{
SessionName: psdp.SessionName("Stream"),
Origin: psdp.Origin{
Username: "-",
NetworkType: "IN",
AddressType: "IP4",
UnicastAddress: "127.0.0.1",
},
// required by Darwin Streaming Server
ConnectionInformation: &psdp.ConnectionInformation{
NetworkType: "IN",
AddressType: "IP4",
Address: &psdp.Address{Address: "0.0.0.0"},
},
TimeDescriptions: []psdp.TimeDescription{
{Timing: psdp.Timing{StartTime: 0, StopTime: 0}},
},
MediaDescriptions: make([]*psdp.MediaDescription, len(medias)),
}
for i, media := range medias {
sout.MediaDescriptions[i] = media.Marshal()
}
byts, err := sout.Marshal()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -53,7 +80,7 @@ func mustMarshalPacketRTCP(pkt rtcp.Packet) []byte {
return byts return byts
} }
func readAll(c *Client, ur string, cb func(*media.Media, format.Format, *rtp.Packet)) error { func readAll(c *Client, ur string, cb func(*description.Media, format.Format, *rtp.Packet)) error {
u, err := url.Parse(ur) u, err := url.Parse(ur)
if err != nil { if err != nil {
return err return err
@@ -64,13 +91,13 @@ func readAll(c *Client, ur string, cb func(*media.Media, format.Format, *rtp.Pac
return err return err
} }
medias, baseURL, _, err := c.Describe(u) sd, _, err := c.Describe(u)
if err != nil { if err != nil {
c.Close() c.Close()
return err return err
} }
err = c.SetupAll(baseURL, medias) err = c.SetupAll(sd.BaseURL, sd.Medias)
if err != nil { if err != nil {
c.Close() c.Close()
return err return err
@@ -92,8 +119,8 @@ func readAll(c *Client, ur string, cb func(*media.Media, format.Format, *rtp.Pac
func TestClientPlayFormats(t *testing.T) { func TestClientPlayFormats(t *testing.T) {
media1 := testH264Media media1 := testH264Media
media2 := &media.Media{ media2 := &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []format.Format{&format.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
@@ -107,8 +134,8 @@ func TestClientPlayFormats(t *testing.T) {
}}, }},
} }
media3 := &media.Media{ media3 := &description.Media{
Type: media.TypeAudio, Type: description.MediaTypeAudio,
Formats: []format.Format{&format.MPEG4Audio{ Formats: []format.Format{&format.MPEG4Audio{
PayloadTyp: 96, PayloadTyp: 96,
Config: &mpeg4audio.Config{ Config: &mpeg4audio.Config{
@@ -157,8 +184,7 @@ func TestClientPlayFormats(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
medias := media.Medias{media1, media2, media3} medias := []*description.Media{media1, media2, media3}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -166,7 +192,7 @@ func TestClientPlayFormats(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -293,17 +319,16 @@ func TestClientPlay(t *testing.T) {
err = forma.Init() err = forma.Init()
require.NoError(t, err) require.NoError(t, err)
medias := media.Medias{ medias := []*description.Media{
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
} }
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -311,7 +336,7 @@ func TestClientPlay(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{scheme + "://" + listenIP + ":8554/test/stream?param=value/"}, "Content-Base": base.HeaderValue{scheme + "://" + listenIP + ":8554/test/stream?param=value/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -505,13 +530,13 @@ func TestClientPlay(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) sd, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
err = c.SetupAll(baseURL, medias) err = c.SetupAll(sd.BaseURL, sd.Medias)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
require.Equal(t, &testRTPPacket, pkt) require.Equal(t, &testRTPPacket, pkt)
err := c.WritePacketRTCP(medi, &testRTCPPacket) err := c.WritePacketRTCP(medi, &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
@@ -569,17 +594,16 @@ func TestClientPlayPartial(t *testing.T) {
err = forma.Init() err = forma.Init()
require.NoError(t, err) require.NoError(t, err)
medias := media.Medias{ medias := []*description.Media{
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
} }
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -587,7 +611,7 @@ func TestClientPlayPartial(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://" + listenIP + ":8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://" + listenIP + ":8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -661,15 +685,15 @@ func TestClientPlayPartial(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) sd, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
_, err = c.Setup(baseURL, medias[1], 0, 0) _, err = c.Setup(sd.BaseURL, sd.Medias[1], 0, 0)
require.NoError(t, err) require.NoError(t, err)
c.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
require.Equal(t, medias[1], medi) require.Equal(t, sd.Medias[1], medi)
require.Equal(t, medias[1].Formats[0], forma) require.Equal(t, sd.Medias[1].Formats[0], forma)
require.Equal(t, &testRTPPacket, pkt) require.Equal(t, &testRTPPacket, pkt)
close(packetRecv) close(packetRecv)
}) })
@@ -721,8 +745,7 @@ func TestClientPlayContentBase(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
switch ca { switch ca {
case "absent": case "absent":
@@ -731,12 +754,12 @@ func TestClientPlayContentBase(t *testing.T) {
Header: base.Header{ Header: base.Header{
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
case "inside control attribute": case "inside control attribute":
body := string(mustMarshalMedias(medias)) body := string(mediasToSDP(medias))
body = strings.Replace(body, "t=0 0", "t=0 0\r\na=control:rtsp://localhost:8554/teststream", 1) body = strings.Replace(body, "t=0 0", "t=0 0\r\na=control:rtsp://localhost:8554/teststream", 1)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
@@ -851,8 +874,7 @@ func TestClientPlayAnyPort(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -860,7 +882,7 @@ func TestClientPlayAnyPort(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -950,9 +972,9 @@ func TestClientPlayAnyPort(t *testing.T) {
AnyPortEnable: true, AnyPortEnable: true,
} }
var med *media.Media var med *description.Media
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
require.Equal(t, &testRTPPacket, pkt) require.Equal(t, &testRTPPacket, pkt)
med = medi med = medi
close(packetRecv) close(packetRecv)
@@ -1007,8 +1029,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -1016,7 +1037,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1080,7 +1101,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(packetRecv) close(packetRecv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1100,8 +1121,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
go func() { go func() {
defer close(serverDone) defer close(serverDone)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
func() { func() {
nconn, err := l.Accept() nconn, err := l.Accept()
@@ -1135,7 +1155,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1204,7 +1224,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1260,7 +1280,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(packetRecv) close(packetRecv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1280,8 +1300,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
go func() { go func() {
defer close(serverDone) defer close(serverDone)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
func() { func() {
nconn, err := l.Accept() nconn, err := l.Accept()
@@ -1333,7 +1352,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1415,7 +1434,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1501,7 +1520,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
} }
err = readAll(&c, "rtsp://myuser:mypass@localhost:8554/teststream", err = readAll(&c, "rtsp://myuser:mypass@localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(packetRecv) close(packetRecv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1548,8 +1567,7 @@ func TestClientPlayDifferentInterleavedIDs(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -1557,7 +1575,7 @@ func TestClientPlayDifferentInterleavedIDs(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1624,7 +1642,7 @@ func TestClientPlayDifferentInterleavedIDs(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(packetRecv) close(packetRecv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1743,8 +1761,7 @@ func TestClientPlayRedirect(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
} }
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -1752,7 +1769,7 @@ func TestClientPlayRedirect(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1812,7 +1829,7 @@ func TestClientPlayRedirect(t *testing.T) {
ru = "rtsp://testusr:testpwd@localhost:8554/path1" ru = "rtsp://testusr:testpwd@localhost:8554/path1"
} }
err = readAll(&c, ru, err = readAll(&c, ru,
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(packetRecv) close(packetRecv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -1907,8 +1924,7 @@ func TestClientPlayPause(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -1916,7 +1932,7 @@ func TestClientPlayPause(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2014,7 +2030,7 @@ func TestClientPlayPause(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
if atomic.SwapInt32(&firstFrame, 1) == 0 { if atomic.SwapInt32(&firstFrame, 1) == 0 {
close(packetRecv) close(packetRecv)
} }
@@ -2075,8 +2091,7 @@ func TestClientPlayRTCPReport(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2084,7 +2099,7 @@ func TestClientPlayRTCPReport(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2249,8 +2264,7 @@ func TestClientPlayErrorTimeout(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2258,7 +2272,7 @@ func TestClientPlayErrorTimeout(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2396,8 +2410,7 @@ func TestClientPlayIgnoreTCPInvalidMedia(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2405,7 +2418,7 @@ func TestClientPlayIgnoreTCPInvalidMedia(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2475,7 +2488,7 @@ func TestClientPlayIgnoreTCPInvalidMedia(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
close(recv) close(recv)
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2519,8 +2532,7 @@ func TestClientPlaySeek(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2528,7 +2540,7 @@ func TestClientPlaySeek(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2625,10 +2637,10 @@ func TestClientPlaySeek(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
medias, baseURL, _, err := c.Describe(u) sd, _, err := c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
err = c.SetupAll(baseURL, medias) err = c.SetupAll(sd.BaseURL, sd.Medias)
require.NoError(t, err) require.NoError(t, err)
_, err = c.Play(&headers.Range{ _, err = c.Play(&headers.Range{
@@ -2683,8 +2695,7 @@ func TestClientPlayKeepaliveFromSession(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2692,7 +2703,7 @@ func TestClientPlayKeepaliveFromSession(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2803,8 +2814,7 @@ func TestClientPlayDifferentSource(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/test/stream?param=value"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/test/stream?param=value"), req.URL)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2812,7 +2822,7 @@ func TestClientPlayDifferentSource(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/test/stream?param=value/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/test/stream?param=value/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -2888,7 +2898,7 @@ func TestClientPlayDifferentSource(t *testing.T) {
} }
err = readAll(&c, "rtsp://localhost:8554/test/stream?param=value", err = readAll(&c, "rtsp://localhost:8554/test/stream?param=value",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
require.Equal(t, &testRTPPacket, pkt) require.Equal(t, &testRTPPacket, pkt)
close(packetRecv) close(packetRecv)
}) })
@@ -2953,14 +2963,13 @@ func TestClientPlayDecodeErrors(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{&media.Media{ medias := []*description.Media{{
Type: media.TypeApplication, Type: description.MediaTypeApplication,
Formats: []format.Format{&format.Generic{ Formats: []format.Format{&format.Generic{
PayloadTyp: 97, PayloadTyp: 97,
RTPMa: "private/90000", RTPMa: "private/90000",
}}, }},
}} }}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -2968,7 +2977,7 @@ func TestClientPlayDecodeErrors(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/stream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/stream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -3217,8 +3226,7 @@ func TestClientPlayPacketNTP(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -3226,7 +3234,7 @@ func TestClientPlayPacketNTP(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
@@ -3341,7 +3349,7 @@ func TestClientPlayPacketNTP(t *testing.T) {
first := false first := false
err = readAll(&c, "rtsp://localhost:8554/teststream", err = readAll(&c, "rtsp://localhost:8554/teststream",
func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
if !first { if !first {
first = true first = true
} else { } else {

View File

@@ -15,15 +15,15 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
var testH264Media = &media.Media{ var testH264Media = &description.Media{
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04}, SPS: []byte{0x01, 0x02, 0x03, 0x04},
@@ -65,7 +65,7 @@ func ntpTimeGoToRTCP(v time.Time) uint64 {
return (s/1000000000)<<32 | (s % 1000000000) return (s/1000000000)<<32 | (s % 1000000000)
} }
func record(c *Client, ur string, medias media.Medias, cb func(*media.Media, rtcp.Packet)) error { func record(c *Client, ur string, medias []*description.Media, cb func(*description.Media, rtcp.Packet)) error {
u, err := url.Parse(ur) u, err := url.Parse(ur)
if err != nil { if err != nil {
return err return err
@@ -76,7 +76,7 @@ func record(c *Client, ur string, medias media.Medias, cb func(*media.Media, rtc
return err return err
} }
_, err = c.Announce(u, medias) _, err = c.Announce(u, &description.Session{Medias: medias})
if err != nil { if err != nil {
c.Close() c.Close()
return err return err
@@ -285,10 +285,10 @@ func TestClientRecordSerial(t *testing.T) {
} }
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, scheme+"://localhost:8554/teststream", medias, err = record(&c, scheme+"://localhost:8554/teststream", medias,
func(medi *media.Media, pkt rtcp.Packet) { func(medi *description.Media, pkt rtcp.Packet) {
require.Equal(t, &testRTCPPacket, pkt) require.Equal(t, &testRTCPPacket, pkt)
close(recvDone) close(recvDone)
}) })
@@ -440,7 +440,7 @@ func TestClientRecordParallel(t *testing.T) {
defer func() { <-writerDone }() defer func() { <-writerDone }()
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, scheme+"://localhost:8554/teststream", medias, nil) err = record(&c, scheme+"://localhost:8554/teststream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -592,7 +592,7 @@ func TestClientRecordPauseSerial(t *testing.T) {
} }
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, "rtsp://localhost:8554/teststream", medias, nil) err = record(&c, "rtsp://localhost:8554/teststream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -722,7 +722,7 @@ func TestClientRecordPauseParallel(t *testing.T) {
} }
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, "rtsp://localhost:8554/teststream", medias, nil) err = record(&c, "rtsp://localhost:8554/teststream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -861,7 +861,7 @@ func TestClientRecordAutomaticProtocol(t *testing.T) {
c := Client{} c := Client{}
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, "rtsp://localhost:8554/teststream", medias, nil) err = record(&c, "rtsp://localhost:8554/teststream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -1041,7 +1041,7 @@ func TestClientRecordDecodeErrors(t *testing.T) {
}, },
} }
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
err = record(&c, "rtsp://localhost:8554/stream", medias, nil) err = record(&c, "rtsp://localhost:8554/stream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -1207,7 +1207,7 @@ func TestClientRecordRTCPReport(t *testing.T) {
} }
medi := testH264Media medi := testH264Media
medias := media.Medias{medi} medias := []*description.Media{medi}
err = record(&c, "rtsp://localhost:8554/teststream", medias, nil) err = record(&c, "rtsp://localhost:8554/teststream", medias, nil)
require.NoError(t, err) require.NoError(t, err)
@@ -1345,10 +1345,10 @@ func TestClientRecordIgnoreTCPRTPPackets(t *testing.T) {
}(), }(),
} }
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
err = record(&c, "rtsp://localhost:8554/teststream", medias, err = record(&c, "rtsp://localhost:8554/teststream", medias,
func(medi *media.Media, pkt rtcp.Packet) { func(medi *description.Media, pkt rtcp.Packet) {
close(rtcpReceived) close(rtcpReceived)
}) })
require.NoError(t, err) require.NoError(t, err)

View File

@@ -11,7 +11,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
@@ -134,8 +134,7 @@ func TestClientSession(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, base.HeaderValue{"123456"}, req.Header["Session"]) require.Equal(t, base.HeaderValue{"123456"}, req.Header["Session"])
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -143,7 +142,7 @@ func TestClientSession(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
"Session": base.HeaderValue{"123456"}, "Session": base.HeaderValue{"123456"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -157,7 +156,7 @@ func TestClientSession(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
_, _, _, err = c.Describe(u) _, _, err = c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -212,15 +211,14 @@ func TestClientAuth(t *testing.T) {
err = auth.Validate(req, "myuser", "mypass", nil, nil, "IPCAM", nonce) err = auth.Validate(req, "myuser", "mypass", nil, nil, "IPCAM", nonce)
require.NoError(t, err) require.NoError(t, err)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
Header: base.Header{ Header: base.Header{
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -234,7 +232,7 @@ func TestClientAuth(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
_, _, _, err = c.Describe(u) _, _, err = c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -272,7 +270,7 @@ func TestClientDescribeCharset(t *testing.T) {
require.Equal(t, base.Describe, req.Method) require.Equal(t, base.Describe, req.Method)
require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL) require.Equal(t, mustParseURL("rtsp://localhost:8554/teststream"), req.URL)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
err = conn.WriteResponse(&base.Response{ err = conn.WriteResponse(&base.Response{
StatusCode: base.StatusOK, StatusCode: base.StatusOK,
@@ -280,7 +278,7 @@ func TestClientDescribeCharset(t *testing.T) {
"Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"}, "Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"},
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"}, "Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -294,7 +292,7 @@ func TestClientDescribeCharset(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer c.Close() defer c.Close()
_, _, _, err = c.Describe(u) _, _, err = c.Describe(u)
require.NoError(t, err) require.NoError(t, err)
} }
@@ -312,7 +310,7 @@ func TestClientClose(t *testing.T) {
_, err = c.Options(u) _, err = c.Options(u)
require.EqualError(t, err, "terminated") require.EqualError(t, err, "terminated")
_, _, _, err = c.Describe(u) _, _, err = c.Describe(u)
require.EqualError(t, err, "terminated") require.EqualError(t, err, "terminated")
_, err = c.Announce(u, nil) _, err = c.Announce(u, nil)

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -35,16 +35,18 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a G711 format // create a description that contains a G711 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeAudio, Medias: []*description.Media{{
Formats: []format.Format{&format.G711{}}, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.G711{}},
}},
} }
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start recording the media // connect to the server and start recording
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -59,7 +61,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -35,16 +35,18 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a G722 format // create a description that contains a G722 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeAudio, Medias: []*description.Media{{
Formats: []format.Format{&format.G722{}}, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.G722{}},
}},
} }
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start recording the media // connect to the server and start recording
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -59,7 +61,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -37,18 +37,20 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a H264 format // create a stream description that contains a H264 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.H264{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.H264{
PacketizationMode: 1, PayloadTyp: 96,
PacketizationMode: 1,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -63,7 +65,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -36,17 +36,19 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a H265 format // create a description that contains a H265 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.H265{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.H265{
PayloadTyp: 96,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -61,7 +63,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -35,21 +35,23 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a LPCM format // create a description that contains a LPCM format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeAudio, Medias: []*description.Media{{
Formats: []format.Format{&format.LPCM{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.LPCM{
BitDepth: 16, PayloadTyp: 96,
SampleRate: 44100, BitDepth: 16,
ChannelCount: 1, SampleRate: 44100,
ChannelCount: 1,
}},
}}, }},
} }
c := gortsplib.Client{} c := gortsplib.Client{}
// connect to the server and start recording the media // connect to the server and start recording
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -64,7 +66,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -35,15 +35,17 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a M-JPEG format // create a description that contains a M-JPEG format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.MJPEG{}}, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.MJPEG{}},
}},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -58,7 +60,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio" "github.com/bluenviron/mediacommon/pkg/codecs/mpeg4audio"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -36,25 +36,27 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a MPEG-4 audio format // create a description that contains a MPEG-4 audio format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeAudio, Medias: []*description.Media{{
Formats: []format.Format{&format.MPEG4Audio{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.MPEG4Audio{
Config: &mpeg4audio.Config{ PayloadTyp: 96,
Type: mpeg4audio.ObjectTypeAACLC, Config: &mpeg4audio.Config{
SampleRate: 48000, Type: mpeg4audio.ObjectTypeAACLC,
ChannelCount: 2, SampleRate: 48000,
}, ChannelCount: 2,
SizeLength: 13, },
IndexLength: 3, SizeLength: 13,
IndexDeltaLength: 3, IndexLength: 3,
IndexDeltaLength: 3,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -69,7 +71,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -35,19 +35,20 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a Opus format // create a description that contains a Opus format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeAudio, Medias: []*description.Media{{
Formats: []format.Format{&format.Opus{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.Opus{
IsStereo: false, PayloadTyp: 96,
IsStereo: false,
}},
}}, }},
} }
// connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
// connect to the server and start recording the media
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -62,7 +63,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -36,17 +36,19 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a VP8 format // create a description that contains a VP8 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.VP8{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.VP8{
PayloadTyp: 96,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -61,7 +63,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"net" "net"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -36,17 +36,19 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a VP9 format // create a description that contains a VP9 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.VP9{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.VP9{
PayloadTyp: 96,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -61,7 +63,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -37,12 +37,14 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a H264 media // create a stream description that contains a H264 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.H264{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.H264{
PacketizationMode: 1, PayloadTyp: 96,
PacketizationMode: 1,
}},
}}, }},
} }
@@ -56,8 +58,8 @@ func main() {
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
} }
// connect to the server and start recording the media // connect to the server and start recording
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -72,7 +74,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
err = c.WritePacketRTP(medi, &pkt) err = c.WritePacketRTP(desc.Medias[0], &pkt)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -38,18 +38,20 @@ func main() {
} }
log.Println("stream connected") log.Println("stream connected")
// create a media that contains a H264 format // create a stream description that contains a H264 format
medi := &media.Media{ desc := &description.Session{
Type: media.TypeVideo, Medias: []*description.Media{{
Formats: []format.Format{&format.H264{ Type: description.MediaTypeVideo,
PayloadTyp: 96, Formats: []format.Format{&format.H264{
PacketizationMode: 1, PayloadTyp: 96,
PacketizationMode: 1,
}},
}}, }},
} }
// connect to the server and start recording the media // connect to the server and start recording
c := gortsplib.Client{} c := gortsplib.Client{}
err = c.StartRecording("rtsp://localhost:8554/mystream", media.Medias{medi}) err = c.StartRecording("rtsp://localhost:8554/mystream", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -66,7 +68,7 @@ func main() {
} }
// route RTP packet to the server // route RTP packet to the server
c.WritePacketRTP(medi, &pkt) c.WritePacketRTP(desc.Medias[0], &pkt)
// read another RTP packet from source // read another RTP packet from source
n, _, err = pc.ReadFrom(buf) n, _, err = pc.ReadFrom(buf)

View File

@@ -25,10 +25,10 @@ func main() {
} }
defer c.Close() defer c.Close()
medias, _, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Printf("available medias: %v\n", medias) log.Printf("available medias: %v\n", desc.Medias)
} }

View File

@@ -31,14 +31,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the G711 media and format // find the G711 media and format
var forma *format.G711 var forma *format.G711
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -50,7 +50,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -31,14 +31,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the G722 media and format // find the G722 media and format
var forma *format.G722 var forma *format.G722
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -50,7 +50,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -58,14 +58,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H264 media and format // find the H264 media and format
var forma *format.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -92,7 +92,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -32,14 +32,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H264 media and format // find the H264 media and format
var forma *format.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -57,7 +57,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -35,14 +35,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H264 media and format // find the H264 media and format
var forma *format.H264 var forma *format.H264
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -69,7 +69,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -32,14 +32,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the H265 media and format // find the H265 media and format
var forma *format.H265 var forma *format.H265
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -51,7 +51,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -31,14 +31,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the LPCM media and format // find the LPCM media and format
var forma *format.LPCM var forma *format.LPCM
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -50,7 +50,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -35,14 +35,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the M-JPEG media and format // find the M-JPEG media and format
var forma *format.MJPEG var forma *format.MJPEG
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -54,7 +54,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -11,7 +11,7 @@ import (
// This example shows how to // This example shows how to
// 1. connect to a RTSP server // 1. connect to a RTSP server
// 2. check if there's an MPEG4-audio media // 2. check if there's an MPEG-4 audio media
// 3. save the content of the media into a file in MPEG-TS format // 3. save the content of the media into a file in MPEG-TS format
func main() { func main() {
@@ -31,32 +31,32 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the MPEG4-audio media and format // find the MPEG-4 audio media and format
var forma *format.MPEG4Audio var forma *format.MPEG4Audio
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
// setup RTP/MPEG4-audio -> MPEG4-audio decoder // setup RTP/MPEG-4 audio -> MPEG-4 audio decoder
rtpDec, err := forma.CreateDecoder() rtpDec, err := forma.CreateDecoder()
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup MPEG4-audio -> MPEG-TS muxer // setup MPEG-4 audio -> MPEG-TS muxer
mpegtsMuxer, err := newMPEGTSMuxer(forma.Config) mpegtsMuxer, err := newMPEGTSMuxer(forma.Config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -31,14 +31,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the MPEG4-audio media and format // find the MPEG4-audio media and format
var forma *format.MPEG4Audio var forma *format.MPEG4Audio
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -50,7 +50,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -31,14 +31,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the Opus media and format // find the Opus media and format
var forma *format.Opus var forma *format.Opus
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -50,7 +50,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -32,14 +32,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the VP8 media and format // find the VP8 media and format
var forma *format.VP8 var forma *format.VP8
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -51,7 +51,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -32,14 +32,14 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// find the VP9 media and format // find the VP9 media and format
var forma *format.VP9 var forma *format.VP9
medi := medias.FindFormat(&forma) medi := desc.FindFormat(&forma)
if medi == nil { if medi == nil {
panic("media not found") panic("media not found")
} }
@@ -51,7 +51,7 @@ func main() {
} }
// setup a single media // setup a single media
_, err = c.Setup(baseURL, medi, 0, 0) _, err = c.Setup(desc.BaseURL, medi, 0, 0)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View File

@@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -41,24 +41,24 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup all medias // setup all medias
err = c.SetupAll(baseURL, medias) err = c.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// called when a RTP packet arrives // called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi) log.Printf("RTP packet from media %v\n", medi)
}) })
// called when a RTCP packet arrives // called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) { c.OnPacketRTCPAny(func(medi *description.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt) log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
}) })

View File

@@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -35,24 +35,24 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup all medias // setup all medias
err = c.SetupAll(baseURL, medias) err = c.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// called when a RTP packet arrives // called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi) log.Printf("RTP packet from media %v\n", medi)
}) })
// called when a RTCP packet arrives // called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) { c.OnPacketRTCPAny(func(medi *description.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt) log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
}) })

View File

@@ -4,8 +4,8 @@ import (
"log" "log"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -31,31 +31,31 @@ func main() {
defer reader.Close() defer reader.Close()
// find published medias // find published medias
medias, baseURL, _, err := reader.Describe(sourceURL) desc, _, err := reader.Describe(sourceURL)
if err != nil { if err != nil {
panic(err) panic(err)
} }
log.Printf("republishing %d medias", len(medias)) log.Printf("republishing %d medias", len(desc.Medias))
// setup all medias // setup all medias
// this must be called before StartRecording(), since it overrides the control attribute. // this must be called before StartRecording(), since it overrides the control attribute.
err = reader.SetupAll(baseURL, medias) err = reader.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// connect to the server and start recording the same medias // connect to the server and start recording the same medias
publisher := gortsplib.Client{} publisher := gortsplib.Client{}
err = publisher.StartRecording("rtsp://localhost:8554/mystream2", medias) err = publisher.StartRecording("rtsp://localhost:8554/mystream2", desc)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer publisher.Close() defer publisher.Close()
// read RTP packets from the reader and route them to the publisher // read RTP packets from the reader and route them to the publisher
reader.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { reader.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
publisher.WritePacketRTP(medi, pkt) publisher.WritePacketRTP(desc.Medias[0], pkt)
}) })
// start playing // start playing

View File

@@ -4,8 +4,8 @@ import (
"log" "log"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
@@ -32,24 +32,24 @@ func main() {
defer c.Close() defer c.Close()
// find published medias // find published medias
medias, baseURL, _, err := c.Describe(u) desc, _, err := c.Describe(u)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// setup all medias // setup all medias
err = c.SetupAll(baseURL, medias) err = c.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
panic(err) panic(err)
} }
// called when a RTP packet arrives // called when a RTP packet arrives
c.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { c.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
log.Printf("RTP packet from media %v\n", medi) log.Printf("RTP packet from media %v\n", medi)
}) })
// called when a RTCP packet arrives // called when a RTCP packet arrives
c.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) { c.OnPacketRTCPAny(func(medi *description.Media, pkt rtcp.Packet) {
log.Printf("RTCP packet from media %v, type %T\n", medi, pkt) log.Printf("RTCP packet from media %v, type %T\n", medi, pkt)
}) })

View File

@@ -5,8 +5,8 @@ import (
"time" "time"
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
"github.com/pion/rtp" "github.com/pion/rtp"
) )
@@ -57,24 +57,24 @@ func (c *client) read() error {
defer rc.Close() defer rc.Close()
// find published medias // find published medias
medias, baseURL, _, err := rc.Describe(u) desc, _, err := rc.Describe(u)
if err != nil { if err != nil {
return err return err
} }
// setup all medias // setup all medias
err = rc.SetupAll(baseURL, medias) err = rc.SetupAll(desc.BaseURL, desc.Medias)
if err != nil { if err != nil {
return err return err
} }
stream := c.s.setStreamReady(medias) stream := c.s.setStreamReady(desc)
defer c.s.setStreamUnready() defer c.s.setStreamUnready()
log.Printf("stream is ready and can be read from the server at rtsp://localhost:8554/stream\n") log.Printf("stream is ready and can be read from the server at rtsp://localhost:8554/stream\n")
// called when a RTP packet arrives // called when a RTP packet arrives
rc.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { rc.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
// route incoming packets to the server stream // route incoming packets to the server stream
stream.WritePacketRTP(medi, pkt) stream.WritePacketRTP(medi, pkt)
}) })

View File

@@ -6,7 +6,7 @@ import (
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
) )
type server struct { type server struct {
@@ -99,10 +99,10 @@ func (s *server) OnPlay(ctx *gortsplib.ServerHandlerOnPlayCtx) (*base.Response,
}, nil }, nil
} }
func (s *server) setStreamReady(medias media.Medias) *gortsplib.ServerStream { func (s *server) setStreamReady(desc *description.Session) *gortsplib.ServerStream {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
s.stream = gortsplib.NewServerStream(s.s, medias) s.stream = gortsplib.NewServerStream(s.s, desc)
return s.stream return s.stream
} }

View File

@@ -9,9 +9,9 @@ import (
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base" "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/format"
"github.com/bluenviron/gortsplib/v4/pkg/format/rtph264" "github.com/bluenviron/gortsplib/v4/pkg/format/rtph264"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
// This example shows how to // This example shows how to
@@ -23,7 +23,7 @@ type serverHandler struct {
s *gortsplib.Server s *gortsplib.Server
mutex sync.Mutex mutex sync.Mutex
publisher *gortsplib.ServerSession publisher *gortsplib.ServerSession
media *media.Media media *description.Media
format *format.H264 format *format.H264
rtpDec *rtph264.Decoder rtpDec *rtph264.Decoder
mpegtsMuxer *mpegtsMuxer mpegtsMuxer *mpegtsMuxer
@@ -69,7 +69,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
// find the H264 media and format // find the H264 media and format
var forma *format.H264 var forma *format.H264
medi := ctx.Medias.FindFormat(&forma) medi := ctx.Description.FindFormat(&forma)
if medi == nil { if medi == nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,

View File

@@ -9,8 +9,8 @@ import (
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base" "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/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
// This example shows how to // This example shows how to
@@ -89,7 +89,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
} }
// create the stream and save the publisher // create the stream and save the publisher
sh.stream = gortsplib.NewServerStream(sh.s, ctx.Medias) sh.stream = gortsplib.NewServerStream(sh.s, ctx.Description)
sh.publisher = ctx.Session sh.publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -127,7 +127,7 @@ func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*bas
log.Printf("record request") log.Printf("record request")
// called when receiving a RTP packet // called when receiving a RTP packet
ctx.Session.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
// route the RTP packet to all readers // route the RTP packet to all readers
sh.stream.WritePacketRTP(medi, pkt) sh.stream.WritePacketRTP(medi, pkt)
}) })

View File

@@ -8,8 +8,8 @@ import (
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base" "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/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
// This example shows how to // This example shows how to
@@ -88,7 +88,7 @@ func (sh *serverHandler) OnAnnounce(ctx *gortsplib.ServerHandlerOnAnnounceCtx) (
} }
// create the stream and save the publisher // create the stream and save the publisher
sh.stream = gortsplib.NewServerStream(sh.s, ctx.Medias) sh.stream = gortsplib.NewServerStream(sh.s, ctx.Description)
sh.publisher = ctx.Session sh.publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -126,7 +126,7 @@ func (sh *serverHandler) OnRecord(ctx *gortsplib.ServerHandlerOnRecordCtx) (*bas
log.Printf("record request") log.Printf("record request")
// called when receiving a RTP packet // called when receiving a RTP packet
ctx.Session.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
// route the RTP packet to all readers // route the RTP packet to all readers
sh.stream.WritePacketRTP(medi, pkt) sh.stream.WritePacketRTP(medi, pkt)
}) })

View File

@@ -19,8 +19,8 @@ import (
"github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4"
"github.com/bluenviron/gortsplib/v4/pkg/base" "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/format"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
var serverCert = []byte(`-----BEGIN CERTIFICATE----- var serverCert = []byte(`-----BEGIN CERTIFICATE-----
@@ -329,7 +329,7 @@ func TestServerRecordRead(t *testing.T) {
}, fmt.Errorf("someone is already publishing") }, fmt.Errorf("someone is already publishing")
} }
stream = gortsplib.NewServerStream(s, ctx.Medias) stream = gortsplib.NewServerStream(s, ctx.Description)
publisher = ctx.Session publisher = ctx.Session
return &base.Response{ return &base.Response{
@@ -386,7 +386,7 @@ func TestServerRecordRead(t *testing.T) {
}, fmt.Errorf("invalid query (%s)", ctx.Query) }, fmt.Errorf("invalid query (%s)", ctx.Query)
} }
ctx.Session.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
stream.WritePacketRTP(medi, pkt) stream.WritePacketRTP(medi, pkt)
}) })

View File

@@ -1,5 +1,5 @@
// Package media contains the media stream definition. // Package description contains objects to describe streams.
package media package description
import ( import (
"fmt" "fmt"
@@ -26,17 +26,17 @@ func getControlAttribute(attributes []psdp.Attribute) string {
return "" return ""
} }
func getDirection(attributes []psdp.Attribute) Direction { func getDirection(attributes []psdp.Attribute) MediaDirection {
for _, attr := range attributes { for _, attr := range attributes {
switch attr.Key { switch attr.Key {
case "sendonly": case "sendonly":
return DirectionSendonly return MediaDirectionSendonly
case "recvonly": case "recvonly":
return DirectionRecvonly return MediaDirectionRecvonly
case "sendrecv": case "sendrecv":
return DirectionSendrecv return MediaDirectionSendrecv
} }
} }
return "" return ""
@@ -92,34 +92,34 @@ func sortedKeys(fmtp map[string]string) []string {
return keys return keys
} }
// Direction is the direction of a media stream. // MediaDirection is the direction of a media stream.
type Direction string type MediaDirection string
// standard directions. // standard directions.
const ( const (
DirectionSendonly Direction = "sendonly" MediaDirectionSendonly MediaDirection = "sendonly"
DirectionRecvonly Direction = "recvonly" MediaDirectionRecvonly MediaDirection = "recvonly"
DirectionSendrecv Direction = "sendrecv" MediaDirectionSendrecv MediaDirection = "sendrecv"
) )
// Type is the type of a media stream. // MediaType is the type of a media stream.
type Type string type MediaType string
// standard media stream types. // standard media stream types.
const ( const (
TypeVideo Type = "video" MediaTypeVideo MediaType = "video"
TypeAudio Type = "audio" MediaTypeAudio MediaType = "audio"
TypeApplication Type = "application" MediaTypeApplication MediaType = "application"
) )
// Media is a media stream. // Media is a media stream.
// It contains one or more formats. // It contains one or more formats.
type Media struct { type Media struct {
// Media type. // Media type.
Type Type Type MediaType
// Direction of the stream. // Direction of the stream.
Direction Direction Direction MediaDirection
// Control attribute. // Control attribute.
Control string Control string
@@ -128,8 +128,9 @@ type Media struct {
Formats []format.Format Formats []format.Format
} }
func (m *Media) unmarshal(md *psdp.MediaDescription) error { // Unmarshal decodes the media from the SDP format.
m.Type = Type(md.MediaName.Media) func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
m.Type = MediaType(md.MediaName.Media)
m.Direction = getDirection(md.Attributes) m.Direction = getDirection(md.Attributes)
m.Control = getControlAttribute(md.Attributes) m.Control = getControlAttribute(md.Attributes)

View File

@@ -1,4 +1,4 @@
package media package description
import ( import (
"testing" "testing"
@@ -133,11 +133,11 @@ func TestMediaURL(t *testing.T) {
err := sd.Unmarshal(ca.sdp) err := sd.Unmarshal(ca.sdp)
require.NoError(t, err) require.NoError(t, err)
var medias Medias var media Media
err = medias.Unmarshal(sd.MediaDescriptions) err = media.Unmarshal(sd.MediaDescriptions[0])
require.NoError(t, err) require.NoError(t, err)
ur, err := medias[0].URL(ca.baseURL) ur, err := media.URL(ca.baseURL)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ca.ur, ur) require.Equal(t, ca.ur, ur)
}) })

View File

@@ -1,4 +1,4 @@
package media package description
import ( import (
"fmt" "fmt"
@@ -6,29 +6,48 @@ import (
psdp "github.com/pion/sdp/v3" psdp "github.com/pion/sdp/v3"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url"
) )
// Medias is a list of media streams. // Session is the description of a RTSP session.
type Medias []*Media type Session struct {
// base URL of the stream (read only).
BaseURL *url.URL
// Unmarshal decodes medias from the SDP format. // available media streams.
func (ms *Medias) Unmarshal(mds []*psdp.MediaDescription) error { Medias []*Media
*ms = make(Medias, len(mds)) }
for i, md := range mds { // FindFormat finds a certain format among all the formats in all the medias of the stream.
// If the format is found, it is inserted into forma, and its media is returned.
func (d *Session) FindFormat(forma interface{}) *Media {
for _, media := range d.Medias {
ok := media.FindFormat(forma)
if ok {
return media
}
}
return nil
}
// Unmarshal decodes the description from SDP.
func (d *Session) Unmarshal(ssd *sdp.SessionDescription) error {
d.Medias = make([]*Media, len(ssd.MediaDescriptions))
for i, md := range ssd.MediaDescriptions {
var m Media var m Media
err := m.unmarshal(md) err := m.Unmarshal(md)
if err != nil { if err != nil {
return fmt.Errorf("media %d is invalid: %v", i+1, err) return fmt.Errorf("media %d is invalid: %v", i+1, err)
} }
(*ms)[i] = &m d.Medias[i] = &m
} }
return nil return nil
} }
// Marshal encodes the medias in SDP format. // Marshal encodes the description in SDP.
func (ms Medias) Marshal(multicast bool) *sdp.SessionDescription { func (d Session) Marshal(multicast bool) ([]byte, error) {
var address string var address string
if multicast { if multicast {
address = "224.1.0.0" address = "224.1.0.0"
@@ -37,14 +56,14 @@ func (ms Medias) Marshal(multicast bool) *sdp.SessionDescription {
} }
sout := &sdp.SessionDescription{ sout := &sdp.SessionDescription{
SessionName: psdp.SessionName("Stream"), SessionName: psdp.SessionName("Session"),
Origin: psdp.Origin{ Origin: psdp.Origin{
Username: "-", Username: "-",
NetworkType: "IN", NetworkType: "IN",
AddressType: "IP4", AddressType: "IP4",
UnicastAddress: "127.0.0.1", UnicastAddress: "127.0.0.1",
}, },
// required by Darwin Streaming Server // required by Darwin Sessioning Server
ConnectionInformation: &psdp.ConnectionInformation{ ConnectionInformation: &psdp.ConnectionInformation{
NetworkType: "IN", NetworkType: "IN",
AddressType: "IP4", AddressType: "IP4",
@@ -53,24 +72,12 @@ func (ms Medias) Marshal(multicast bool) *sdp.SessionDescription {
TimeDescriptions: []psdp.TimeDescription{ TimeDescriptions: []psdp.TimeDescription{
{Timing: psdp.Timing{StartTime: 0, StopTime: 0}}, {Timing: psdp.Timing{StartTime: 0, StopTime: 0}},
}, },
MediaDescriptions: make([]*psdp.MediaDescription, len(ms)), MediaDescriptions: make([]*psdp.MediaDescription, len(d.Medias)),
} }
for i, media := range ms { for i, media := range d.Medias {
sout.MediaDescriptions[i] = media.Marshal() sout.MediaDescriptions[i] = media.Marshal()
} }
return sout return sout.Marshal()
}
// FindFormat finds a certain format among all the formats in all the medias.
// If the format is found, it is inserted into forma, and its media is returned.
func (ms Medias) FindFormat(forma interface{}) *Media {
for _, media := range ms {
ok := media.FindFormat(forma)
if ok {
return media
}
}
return nil
} }

View File

@@ -1,4 +1,4 @@
package media package description
import ( import (
"testing" "testing"
@@ -9,11 +9,11 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
) )
var casesMedias = []struct { var casesSession = []struct {
name string name string
in string in string
out string out string
medias Medias desc Session
}{ }{
{ {
"one format for each media, absolute", "one format for each media, absolute",
@@ -43,7 +43,7 @@ var casesMedias = []struct {
"b=AS:8\r\n", "b=AS:8\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 97\r\n" + "m=video 0 RTP/AVP 97\r\n" +
@@ -56,30 +56,32 @@ var casesMedias = []struct {
"a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" +
"m=application 0 RTP/AVP 107\r\n" + "m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n", "a=control\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "video", {
Control: "rtsp://10.0.100.50/profile5/media.smp/trackID=v", Type: "video",
Formats: []format.Format{&format.H264{ Control: "rtsp://10.0.100.50/profile5/media.smp/trackID=v",
PayloadTyp: 97, Formats: []format.Format{&format.H264{
PacketizationMode: 1, PayloadTyp: 97,
SPS: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a}, PacketizationMode: 1,
PPS: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c}, SPS: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
}}, PPS: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
}, }},
{ },
Type: "audio", {
Direction: DirectionRecvonly, Type: "audio",
Control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a", Direction: MediaDirectionRecvonly,
Formats: []format.Format{&format.G711{ Control: "rtsp://10.0.100.50/profile5/media.smp/trackID=a",
MULaw: true, Formats: []format.Format{&format.G711{
}}, MULaw: true,
}, }},
{ },
Type: "application", {
Formats: []format.Format{&format.Generic{ Type: "application",
PayloadTyp: 107, Formats: []format.Format{&format.Generic{
}}, PayloadTyp: 107,
}},
},
}, },
}, },
}, },
@@ -110,7 +112,7 @@ var casesMedias = []struct {
"b=AS:8\r\n", "b=AS:8\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 97\r\n" + "m=video 0 RTP/AVP 97\r\n" +
@@ -123,30 +125,32 @@ var casesMedias = []struct {
"a=rtpmap:0 PCMU/8000\r\n" + "a=rtpmap:0 PCMU/8000\r\n" +
"m=application 0 RTP/AVP 107\r\n" + "m=application 0 RTP/AVP 107\r\n" +
"a=control\r\n", "a=control\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "video", {
Control: "trackID=1", Type: "video",
Formats: []format.Format{&format.H264{ Control: "trackID=1",
PayloadTyp: 97, Formats: []format.Format{&format.H264{
PacketizationMode: 1, PayloadTyp: 97,
SPS: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a}, PacketizationMode: 1,
PPS: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c}, SPS: []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
}}, PPS: []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
}, }},
{ },
Type: "audio", {
Direction: DirectionRecvonly, Type: "audio",
Control: "trackID=2", Direction: MediaDirectionRecvonly,
Formats: []format.Format{&format.G711{ Control: "trackID=2",
MULaw: true, Formats: []format.Format{&format.G711{
}}, MULaw: true,
}, }},
{ },
Type: "application", {
Formats: []format.Format{&format.Generic{ Type: "application",
PayloadTyp: 107, Formats: []format.Format{&format.Generic{
}}, PayloadTyp: 107,
}},
},
}, },
}, },
}, },
@@ -157,7 +161,7 @@ var casesMedias = []struct {
"s=-\r\n" + "s=-\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"a=group:BUNDLE audio video\r\n" + "a=group:BUNDLE audio video\r\n" +
"a=msid-semantic: WMS mediaStreamLocal\r\n" + "a=msid-semantic: WMS mediaSessionLocal\r\n" +
"m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" + "m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"a=rtcp:9 IN IP4 0.0.0.0\r\n" + "a=rtcp:9 IN IP4 0.0.0.0\r\n" +
@@ -190,8 +194,8 @@ var casesMedias = []struct {
"a=rtpmap:113 telephone-event/16000\r\n" + "a=rtpmap:113 telephone-event/16000\r\n" +
"a=rtpmap:126 telephone-event/8000\r\n" + "a=rtpmap:126 telephone-event/8000\r\n" +
"a=ssrc:3754810229 cname:CvU1TYqkVsjj5XOt\r\n" + "a=ssrc:3754810229 cname:CvU1TYqkVsjj5XOt\r\n" +
"a=ssrc:3754810229 msid:mediaStreamLocal 101\r\n" + "a=ssrc:3754810229 msid:mediaSessionLocal 101\r\n" +
"a=ssrc:3754810229 mslabel:mediaStreamLocal\r\n" + "a=ssrc:3754810229 mslabel:mediaSessionLocal\r\n" +
"a=ssrc:3754810229 label:101\r\n" + "a=ssrc:3754810229 label:101\r\n" +
"m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125\r\n" + "m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 127 124 125\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
@@ -245,16 +249,16 @@ var casesMedias = []struct {
"a=rtpmap:125 ulpfec/90000\r\n" + "a=rtpmap:125 ulpfec/90000\r\n" +
"a=ssrc-group:FID 2712436124 1733091158\r\n" + "a=ssrc-group:FID 2712436124 1733091158\r\n" +
"a=ssrc:2712436124 cname:CvU1TYqkVsjj5XOt\r\n" + "a=ssrc:2712436124 cname:CvU1TYqkVsjj5XOt\r\n" +
"a=ssrc:2712436124 msid:mediaStreamLocal 100\r\n" + "a=ssrc:2712436124 msid:mediaSessionLocal 100\r\n" +
"a=ssrc:2712436124 mslabel:mediaStreamLocal\r\n" + "a=ssrc:2712436124 mslabel:mediaSessionLocal\r\n" +
"a=ssrc:2712436124 label:100\r\n" + "a=ssrc:2712436124 label:100\r\n" +
"a=ssrc:1733091158 cname:CvU1TYqkVsjj5XOt\r\n" + "a=ssrc:1733091158 cname:CvU1TYqkVsjj5XOt\r\n" +
"a=ssrc:1733091158 msid:mediaStreamLocal 100\r\n" + "a=ssrc:1733091158 msid:mediaSessionLocal 100\r\n" +
"a=ssrc:1733091158 mslabel:mediaStreamLocal\r\n" + "a=ssrc:1733091158 mslabel:mediaSessionLocal\r\n" +
"a=ssrc:1733091158 label:100\r\n", "a=ssrc:1733091158 label:100\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=audio 0 RTP/AVP 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" + "m=audio 0 RTP/AVP 111 103 104 9 102 0 8 106 105 13 110 112 113 126\r\n" +
@@ -291,129 +295,131 @@ var casesMedias = []struct {
"a=rtpmap:127 red/90000\r\n" + "a=rtpmap:127 red/90000\r\n" +
"a=rtpmap:124 rtx/90000\r\n" + "a=rtpmap:124 rtx/90000\r\n" +
"a=fmtp:124 apt=127\r\na=rtpmap:125 ulpfec/90000\r\n", "a=fmtp:124 apt=127\r\na=rtpmap:125 ulpfec/90000\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "audio", {
Direction: DirectionSendonly, Type: "audio",
Formats: []format.Format{ Direction: MediaDirectionSendonly,
&format.Opus{ Formats: []format.Format{
PayloadTyp: 111, &format.Opus{
IsStereo: false, PayloadTyp: 111,
}, IsStereo: false,
&format.Generic{ },
PayloadTyp: 103, &format.Generic{
RTPMa: "ISAC/16000", PayloadTyp: 103,
ClockRat: 16000, RTPMa: "ISAC/16000",
}, ClockRat: 16000,
&format.Generic{ },
PayloadTyp: 104, &format.Generic{
RTPMa: "ISAC/32000", PayloadTyp: 104,
ClockRat: 32000, RTPMa: "ISAC/32000",
}, ClockRat: 32000,
&format.G722{}, },
&format.Generic{ &format.G722{},
PayloadTyp: 102, &format.Generic{
RTPMa: "ILBC/8000", PayloadTyp: 102,
ClockRat: 8000, RTPMa: "ILBC/8000",
}, ClockRat: 8000,
&format.G711{ },
MULaw: true, &format.G711{
}, MULaw: true,
&format.G711{ },
MULaw: false, &format.G711{
}, MULaw: false,
&format.Generic{ },
PayloadTyp: 106, &format.Generic{
RTPMa: "CN/32000", PayloadTyp: 106,
ClockRat: 32000, RTPMa: "CN/32000",
}, ClockRat: 32000,
&format.Generic{ },
PayloadTyp: 105, &format.Generic{
RTPMa: "CN/16000", PayloadTyp: 105,
ClockRat: 16000, RTPMa: "CN/16000",
}, ClockRat: 16000,
&format.Generic{ },
PayloadTyp: 13, &format.Generic{
RTPMa: "CN/8000", PayloadTyp: 13,
ClockRat: 8000, RTPMa: "CN/8000",
}, ClockRat: 8000,
&format.Generic{ },
PayloadTyp: 110, &format.Generic{
RTPMa: "telephone-event/48000", PayloadTyp: 110,
ClockRat: 48000, RTPMa: "telephone-event/48000",
}, ClockRat: 48000,
&format.Generic{ },
PayloadTyp: 112, &format.Generic{
RTPMa: "telephone-event/32000", PayloadTyp: 112,
ClockRat: 32000, RTPMa: "telephone-event/32000",
}, ClockRat: 32000,
&format.Generic{ },
PayloadTyp: 113, &format.Generic{
RTPMa: "telephone-event/16000", PayloadTyp: 113,
ClockRat: 16000, RTPMa: "telephone-event/16000",
}, ClockRat: 16000,
&format.Generic{ },
PayloadTyp: 126, &format.Generic{
RTPMa: "telephone-event/8000", PayloadTyp: 126,
ClockRat: 8000, RTPMa: "telephone-event/8000",
ClockRat: 8000,
},
}, },
}, },
}, {
{ Type: "video",
Type: "video", Direction: MediaDirectionSendonly,
Direction: DirectionSendonly, Formats: []format.Format{
Formats: []format.Format{ &format.VP8{
&format.VP8{ PayloadTyp: 96,
PayloadTyp: 96,
},
&format.Generic{
PayloadTyp: 97,
RTPMa: "rtx/90000",
FMT: map[string]string{
"apt": "96",
}, },
ClockRat: 90000, &format.Generic{
}, PayloadTyp: 97,
&format.VP9{ RTPMa: "rtx/90000",
PayloadTyp: 98, FMT: map[string]string{
}, "apt": "96",
&format.Generic{ },
PayloadTyp: 99, ClockRat: 90000,
RTPMa: "rtx/90000",
FMT: map[string]string{
"apt": "98",
}, },
ClockRat: 90000, &format.VP9{
}, PayloadTyp: 98,
&format.H264{
PayloadTyp: 100,
PacketizationMode: 1,
},
&format.Generic{
PayloadTyp: 101,
RTPMa: "rtx/90000",
FMT: map[string]string{
"apt": "100",
}, },
ClockRat: 90000, &format.Generic{
}, PayloadTyp: 99,
&format.Generic{ RTPMa: "rtx/90000",
PayloadTyp: 127, FMT: map[string]string{
RTPMa: "red/90000", "apt": "98",
ClockRat: 90000, },
}, ClockRat: 90000,
&format.Generic{ },
PayloadTyp: 124, &format.H264{
RTPMa: "rtx/90000", PayloadTyp: 100,
FMT: map[string]string{ PacketizationMode: 1,
"apt": "127", },
&format.Generic{
PayloadTyp: 101,
RTPMa: "rtx/90000",
FMT: map[string]string{
"apt": "100",
},
ClockRat: 90000,
},
&format.Generic{
PayloadTyp: 127,
RTPMa: "red/90000",
ClockRat: 90000,
},
&format.Generic{
PayloadTyp: 124,
RTPMa: "rtx/90000",
FMT: map[string]string{
"apt": "127",
},
ClockRat: 90000,
},
&format.Generic{
PayloadTyp: 125,
RTPMa: "ulpfec/90000",
ClockRat: 90000,
}, },
ClockRat: 90000,
},
&format.Generic{
PayloadTyp: 125,
RTPMa: "ulpfec/90000",
ClockRat: 90000,
}, },
}, },
}, },
@@ -433,7 +439,7 @@ var casesMedias = []struct {
"sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==\r\n", "sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 96 98\r\n" + "m=video 0 RTP/AVP 96 98\r\n" +
@@ -442,22 +448,24 @@ var casesMedias = []struct {
"a=fmtp:96 packetization-mode=1; profile-level-id=4D002A; " + "a=fmtp:96 packetization-mode=1; profile-level-id=4D002A; " +
"sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==\r\n" + "sprop-parameter-sets=Z00AKp2oHgCJ+WbgICAgQA==,aO48gA==\r\n" +
"a=rtpmap:98 MetaData\r\n", "a=rtpmap:98 MetaData\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "video", {
Formats: []format.Format{ Type: "video",
&format.H264{ Formats: []format.Format{
PayloadTyp: 96, &format.H264{
SPS: []byte{ PayloadTyp: 96,
0x67, 0x4d, 0x00, 0x2a, 0x9d, 0xa8, 0x1e, 0x00, SPS: []byte{
0x89, 0xf9, 0x66, 0xe0, 0x20, 0x20, 0x20, 0x40, 0x67, 0x4d, 0x00, 0x2a, 0x9d, 0xa8, 0x1e, 0x00,
0x89, 0xf9, 0x66, 0xe0, 0x20, 0x20, 0x20, 0x40,
},
PPS: []byte{0x68, 0xee, 0x3c, 0x80},
PacketizationMode: 1,
},
&format.Generic{
PayloadTyp: 98,
RTPMa: "MetaData",
}, },
PPS: []byte{0x68, 0xee, 0x3c, 0x80},
PacketizationMode: 1,
},
&format.Generic{
PayloadTyp: 98,
RTPMa: "MetaData",
}, },
}, },
}, },
@@ -480,7 +488,7 @@ var casesMedias = []struct {
"a=sendonly\r\n", "a=sendonly\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 26\r\n" + "m=video 0 RTP/AVP 26\r\n" +
@@ -495,24 +503,26 @@ var casesMedias = []struct {
"a=control:rtsp://192.168.0.1/audioback\r\n" + "a=control:rtsp://192.168.0.1/audioback\r\n" +
"a=sendonly\r\n" + "a=sendonly\r\n" +
"a=rtpmap:0 PCMU/8000\r\n", "a=rtpmap:0 PCMU/8000\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "video", {
Direction: DirectionRecvonly, Type: "video",
Control: "rtsp://192.168.0.1/video", Direction: MediaDirectionRecvonly,
Formats: []format.Format{&format.MJPEG{}}, Control: "rtsp://192.168.0.1/video",
}, Formats: []format.Format{&format.MJPEG{}},
{ },
Type: "audio", {
Direction: DirectionRecvonly, Type: "audio",
Control: "rtsp://192.168.0.1/audio", Direction: MediaDirectionRecvonly,
Formats: []format.Format{&format.G711{MULaw: true}}, Control: "rtsp://192.168.0.1/audio",
}, Formats: []format.Format{&format.G711{MULaw: true}},
{ },
Type: "audio", {
Direction: DirectionSendonly, Type: "audio",
Control: "rtsp://192.168.0.1/audioback", Direction: MediaDirectionSendonly,
Formats: []format.Format{&format.G711{MULaw: true}}, Control: "rtsp://192.168.0.1/audioback",
Formats: []format.Format{&format.G711{MULaw: true}},
},
}, },
}, },
}, },
@@ -526,20 +536,22 @@ var casesMedias = []struct {
"a=rtpmap:95 TP-LINK/90000\r\n", "a=rtpmap:95 TP-LINK/90000\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=application/TP-LINK 0 RTP/AVP 95\r\n" + "m=application/TP-LINK 0 RTP/AVP 95\r\n" +
"a=control\r\n" + "a=control\r\n" +
"a=rtpmap:95 TP-LINK/90000\r\n", "a=rtpmap:95 TP-LINK/90000\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "application/TP-LINK", {
Formats: []format.Format{&format.Generic{ Type: "application/TP-LINK",
PayloadTyp: 95, Formats: []format.Format{&format.Generic{
RTPMa: "TP-LINK/90000", PayloadTyp: 95,
ClockRat: 90000, RTPMa: "TP-LINK/90000",
}}, ClockRat: 90000,
}},
},
}, },
}, },
}, },
@@ -554,20 +566,22 @@ var casesMedias = []struct {
"a=rtpmap:95 MERCURY/90000\n", "a=rtpmap:95 MERCURY/90000\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=application/MERCURY 0 RTP/AVP 95\r\n" + "m=application/MERCURY 0 RTP/AVP 95\r\n" +
"a=control\r\n" + "a=control\r\n" +
"a=rtpmap:95 MERCURY/90000\r\n", "a=rtpmap:95 MERCURY/90000\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "application/MERCURY", {
Formats: []format.Format{&format.Generic{ Type: "application/MERCURY",
PayloadTyp: 95, Formats: []format.Format{&format.Generic{
RTPMa: "MERCURY/90000", PayloadTyp: 95,
ClockRat: 90000, RTPMa: "MERCURY/90000",
}}, ClockRat: 90000,
}},
},
}, },
}, },
}, },
@@ -582,20 +596,22 @@ var casesMedias = []struct {
"a=fmtp:96 packetization-mode=1\r\n", "a=fmtp:96 packetization-mode=1\r\n",
"v=0\r\n" + "v=0\r\n" +
"o=- 0 0 IN IP4 127.0.0.1\r\n" + "o=- 0 0 IN IP4 127.0.0.1\r\n" +
"s=Stream\r\n" + "s=Session\r\n" +
"c=IN IP4 0.0.0.0\r\n" + "c=IN IP4 0.0.0.0\r\n" +
"t=0 0\r\n" + "t=0 0\r\n" +
"m=video 0 RTP/AVP 96\r\n" + "m=video 0 RTP/AVP 96\r\n" +
"a=control\r\n" + "a=control\r\n" +
"a=rtpmap:96 H264/90000\r\n" + "a=rtpmap:96 H264/90000\r\n" +
"a=fmtp:96 packetization-mode=1\r\n", "a=fmtp:96 packetization-mode=1\r\n",
Medias{ Session{
{ Medias: []*Media{
Type: "video", {
Formats: []format.Format{ Type: "video",
&format.H264{ Formats: []format.Format{
PayloadTyp: 96, &format.H264{
PacketizationMode: 1, PayloadTyp: 96,
PacketizationMode: 1,
},
}, },
}, },
}, },
@@ -603,22 +619,22 @@ var casesMedias = []struct {
}, },
} }
func TestMediasUnmarshal(t *testing.T) { func TestSessionUnmarshal(t *testing.T) {
for _, ca := range casesMedias { for _, ca := range casesSession {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
var sdp sdp.SessionDescription var sdp sdp.SessionDescription
err := sdp.Unmarshal([]byte(ca.in)) err := sdp.Unmarshal([]byte(ca.in))
require.NoError(t, err) require.NoError(t, err)
var medias Medias var desc Session
err = medias.Unmarshal(sdp.MediaDescriptions) err = desc.Unmarshal(&sdp)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ca.medias, medias) require.Equal(t, ca.desc, desc)
}) })
} }
} }
func TestMediasUnmarshalErrors(t *testing.T) { func TestSessionUnmarshalErrors(t *testing.T) {
for _, ca := range []struct { for _, ca := range []struct {
name string name string
sdp string sdp string
@@ -646,24 +662,24 @@ func TestMediasUnmarshalErrors(t *testing.T) {
err := sd.Unmarshal([]byte(ca.sdp)) err := sd.Unmarshal([]byte(ca.sdp))
require.NoError(t, err) require.NoError(t, err)
var medias Medias var desc Session
err = medias.Unmarshal(sd.MediaDescriptions) err = desc.Unmarshal(&sd)
require.EqualError(t, err, ca.err) require.EqualError(t, err, ca.err)
}) })
} }
} }
func TestMediasMarshal(t *testing.T) { func TestSessionMarshal(t *testing.T) {
for _, ca := range casesMedias { for _, ca := range casesSession {
t.Run(ca.name, func(t *testing.T) { t.Run(ca.name, func(t *testing.T) {
byts, err := ca.medias.Marshal(false).Marshal() byts, err := ca.desc.Marshal(false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, ca.out, string(byts)) require.Equal(t, ca.out, string(byts))
}) })
} }
} }
func TestMediasFindFormat(t *testing.T) { func TestSessionFindFormat(t *testing.T) {
tr := &format.Generic{ tr := &format.Generic{
PayloadTyp: 97, PayloadTyp: 97,
RTPMa: "rtx/90000", RTPMa: "rtx/90000",
@@ -674,7 +690,7 @@ func TestMediasFindFormat(t *testing.T) {
} }
md := &Media{ md := &Media{
Type: TypeVideo, Type: MediaTypeVideo,
Formats: []format.Format{ Formats: []format.Format{
&format.VP8{ &format.VP8{
PayloadTyp: 96, PayloadTyp: 96,
@@ -686,21 +702,23 @@ func TestMediasFindFormat(t *testing.T) {
}, },
} }
ms := Medias{ desc := &Session{
{ Medias: []*Media{
Type: TypeAudio, {
Formats: []format.Format{ Type: MediaTypeAudio,
&format.Opus{ Formats: []format.Format{
PayloadTyp: 111, &format.Opus{
IsStereo: true, PayloadTyp: 111,
IsStereo: true,
},
}, },
}, },
md,
}, },
md,
} }
var forma *format.Generic var forma *format.Generic
me := ms.FindFormat(&forma) me := desc.FindFormat(&forma)
require.Equal(t, md, me) require.Equal(t, md, me)
require.Equal(t, tr, forma) require.Equal(t, tr, forma)
} }

View File

@@ -12,8 +12,8 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/bytecounter" "github.com/bluenviron/gortsplib/v4/pkg/bytecounter"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
@@ -24,14 +24,13 @@ func getSessionID(header base.Header) string {
return "" return ""
} }
func mediasForSDP( func streamDescCopyForServer(d *description.Session, contentBase *url.URL) *description.Session {
medias media.Medias, out := &description.Session{
contentBase *url.URL, Medias: make([]*description.Media, len(d.Medias)),
) media.Medias { }
newMedias := make(media.Medias, len(medias))
for i, medi := range medias { for i, medi := range d.Medias {
mc := &media.Media{ mc := &description.Media{
Type: medi.Type, Type: medi.Type,
// Direction: skipped for the moment // Direction: skipped for the moment
Formats: medi.Formats, Formats: medi.Formats,
@@ -47,10 +46,10 @@ func mediasForSDP(
u, _ := mc.URL(contentBase) u, _ := mc.URL(contentBase)
mc.Control = u.String() mc.Control = u.String()
newMedias[i] = mc out.Medias[i] = mc
} }
return newMedias return out
} }
type readReq struct { type readReq struct {
@@ -294,7 +293,7 @@ func (sc *ServerConn) handleRequestInner(req *base.Request) (*base.Response, err
} }
if stream != nil { if stream != nil {
byts, _ := mediasForSDP(stream.medias, req.URL).Marshal(multicast).Marshal() byts, _ := streamDescCopyForServer(stream.desc, req.URL).Marshal(multicast)
res.Body = byts res.Body = byts
} }
} }

View File

@@ -2,7 +2,7 @@ package gortsplib
import ( import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
) )
// ServerHandler is the interface implemented by all the server handlers. // ServerHandler is the interface implemented by all the server handlers.
@@ -83,12 +83,12 @@ type ServerHandlerOnDescribe interface {
// ServerHandlerOnAnnounceCtx is the context of OnAnnounce. // ServerHandlerOnAnnounceCtx is the context of OnAnnounce.
type ServerHandlerOnAnnounceCtx struct { type ServerHandlerOnAnnounceCtx struct {
Session *ServerSession Session *ServerSession
Conn *ServerConn Conn *ServerConn
Request *base.Request Request *base.Request
Path string Path string
Query string Query string
Medias media.Medias Description *description.Session
} }
// ServerHandlerOnAnnounce can be implemented by a ServerHandler. // ServerHandlerOnAnnounce can be implemented by a ServerHandler.

View File

@@ -19,9 +19,9 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
) )
@@ -254,11 +254,13 @@ func TestServerPlayPath(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{ stream = NewServerStream(s, &description.Session{
testH264Media, Medias: []*description.Media{
testH264Media, testH264Media,
testH264Media, testH264Media,
testH264Media, testH264Media,
testH264Media,
},
}) })
defer stream.Close() defer stream.Close()
@@ -337,7 +339,7 @@ func TestServerPlaySetupErrors(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
if ca == "closed stream" { if ca == "closed stream" {
stream.Close() stream.Close()
} else { } else {
@@ -463,7 +465,7 @@ func TestServerPlaySetupErrorSameUDPPortsAndIP(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
@@ -553,17 +555,17 @@ func TestServerPlay(t *testing.T) {
// send RTCP packets directly to the session. // send RTCP packets directly to the session.
// these are sent after the response, only if onPlay returns StatusOK. // these are sent after the response, only if onPlay returns StatusOK.
if transport != "multicast" { if transport != "multicast" {
err := ctx.Session.WritePacketRTCP(stream.Medias()[0], &testRTCPPacket) err := ctx.Session.WritePacketRTCP(stream.Description().Medias[0], &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
} }
ctx.Session.OnPacketRTCPAny(func(medi *media.Media, pkt rtcp.Packet) { ctx.Session.OnPacketRTCPAny(func(medi *description.Media, pkt rtcp.Packet) {
// ignore multicast loopback // ignore multicast loopback
if transport == "multicast" && atomic.AddUint64(&counter, 1) <= 1 { if transport == "multicast" && atomic.AddUint64(&counter, 1) <= 1 {
return return
} }
require.Equal(t, stream.Medias()[0], medi) require.Equal(t, stream.Description().Medias[0], medi)
require.Equal(t, &testRTCPPacket, pkt) require.Equal(t, &testRTCPPacket, pkt)
close(framesReceived) close(framesReceived)
}) })
@@ -573,9 +575,9 @@ func TestServerPlay(t *testing.T) {
// ServerStream.WritePacket*() // ServerStream.WritePacket*()
go func() { go func() {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
err := stream.WritePacketRTCP(stream.Medias()[0], &testRTCPPacket) err := stream.WritePacketRTCP(stream.Description().Medias[0], &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
err = stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err = stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -612,7 +614,7 @@ func TestServerPlay(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", listenIP+":8554") nconn, err := net.Dial("tcp", listenIP+":8554")
@@ -890,7 +892,7 @@ func TestServerPlayDecodeErrors(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1014,7 +1016,7 @@ func TestServerPlayRTCPReport(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1066,7 +1068,7 @@ func TestServerPlayRTCPReport(t *testing.T) {
curTimeMutex.Unlock() curTimeMutex.Unlock()
err = stream.WritePacketRTPWithNTP( err = stream.WritePacketRTPWithNTP(
stream.Medias()[0], stream.Description().Medias[0],
&rtp.Packet{ &rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 2, Version: 2,
@@ -1138,7 +1140,7 @@ func TestServerPlayVLCMulticast(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", listenIP+":8554") nconn, err := net.Dial("tcp", listenIP+":8554")
@@ -1189,7 +1191,7 @@ func TestServerPlayTCPResponseBeforeFrames(t *testing.T) {
go func() { go func() {
defer close(writerDone) defer close(writerDone)
err := stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
ti := time.NewTicker(50 * time.Millisecond) ti := time.NewTicker(50 * time.Millisecond)
@@ -1198,7 +1200,7 @@ func TestServerPlayTCPResponseBeforeFrames(t *testing.T) {
for { for {
select { select {
case <-ti.C: case <-ti.C:
err := stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
case <-writerTerminate: case <-writerTerminate:
return return
@@ -1219,7 +1221,7 @@ func TestServerPlayTCPResponseBeforeFrames(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1282,7 +1284,7 @@ func TestServerPlayPlayPlay(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1347,7 +1349,7 @@ func TestServerPlayPlayPausePlay(t *testing.T) {
for { for {
select { select {
case <-ti.C: case <-ti.C:
err := stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
case <-writerTerminate: case <-writerTerminate:
return return
@@ -1373,7 +1375,7 @@ func TestServerPlayPlayPausePlay(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1436,7 +1438,7 @@ func TestServerPlayPlayPausePause(t *testing.T) {
for { for {
select { select {
case <-ti.C: case <-ti.C:
err := stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
case <-writerTerminate: case <-writerTerminate:
return return
@@ -1461,7 +1463,7 @@ func TestServerPlayPlayPausePause(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1547,7 +1549,7 @@ func TestServerPlayTimeout(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1636,7 +1638,7 @@ func TestServerPlayWithoutTeardown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1714,7 +1716,7 @@ func TestServerPlayUDPChangeConn(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
sxID := "" sxID := ""
@@ -1786,9 +1788,9 @@ func TestServerPlayPartialMedias(t *testing.T) {
onPlay: func(ctx *ServerHandlerOnPlayCtx) (*base.Response, error) { onPlay: func(ctx *ServerHandlerOnPlayCtx) (*base.Response, error) {
go func() { go func() {
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
err := stream.WritePacketRTP(stream.Medias()[0], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[0], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
err = stream.WritePacketRTP(stream.Medias()[1], &testRTPPacket) err = stream.WritePacketRTP(stream.Description().Medias[1], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
}() }()
@@ -1804,7 +1806,7 @@ func TestServerPlayPartialMedias(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media, testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media, testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1928,19 +1930,21 @@ func TestServerPlayAdditionalInfos(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{ stream = NewServerStream(s, &description.Session{
&media.Media{ Medias: []*description.Media{
Type: "application", {
Formats: []format.Format{forma}, Type: "application",
}, Formats: []format.Format{forma},
&media.Media{ },
Type: "application", {
Formats: []format.Format{forma}, Type: "application",
Formats: []format.Format{forma},
},
}, },
}) })
defer stream.Close() defer stream.Close()
err = stream.WritePacketRTP(stream.Medias()[0], &rtp.Packet{ err = stream.WritePacketRTP(stream.Description().Medias[0], &rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 2, Version: 2,
PayloadType: 96, PayloadType: 96,
@@ -1970,7 +1974,7 @@ func TestServerPlayAdditionalInfos(t *testing.T) {
nil, nil,
}, ssrcs) }, ssrcs)
err = stream.WritePacketRTP(stream.Medias()[1], &rtp.Packet{ err = stream.WritePacketRTP(stream.Description().Medias[1], &rtp.Packet{
Header: rtp.Header{ Header: rtp.Header{
Version: 2, Version: 2,
PayloadType: 96, PayloadType: 96,
@@ -2045,14 +2049,16 @@ func TestServerPlayNoInterleavedIDs(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{ stream = NewServerStream(s, &description.Session{
&media.Media{ Medias: []*description.Media{
Type: "application", {
Formats: []format.Format{forma}, Type: "application",
}, Formats: []format.Format{forma},
&media.Media{ },
Type: "application", {
Formats: []format.Format{forma}, Type: "application",
Formats: []format.Format{forma},
},
}, },
}) })
defer stream.Close() defer stream.Close()
@@ -2089,7 +2095,7 @@ func TestServerPlayNoInterleavedIDs(t *testing.T) {
doPlay(t, conn, "rtsp://localhost:8554/teststream", session) doPlay(t, conn, "rtsp://localhost:8554/teststream", session)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
err := stream.WritePacketRTP(stream.Medias()[i], &testRTPPacket) err := stream.WritePacketRTP(stream.Description().Medias[i], &testRTPPacket)
require.NoError(t, err) require.NoError(t, err)
f, err := conn.ReadInterleavedFrame() f, err := conn.ReadInterleavedFrame()

View File

@@ -15,13 +15,13 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/format" "github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
) )
func doAnnounce(t *testing.T, conn *conn.Conn, u string, medias media.Medias) { func doAnnounce(t *testing.T, conn *conn.Conn, u string, medias []*description.Media) {
res, err := writeReqReadRes(conn, base.Request{ res, err := writeReqReadRes(conn, base.Request{
Method: base.Announce, Method: base.Announce,
URL: mustParseURL(u), URL: mustParseURL(u),
@@ -29,7 +29,7 @@ func doAnnounce(t *testing.T, conn *conn.Conn, u string, medias media.Medias) {
"CSeq": base.HeaderValue{"1"}, "CSeq": base.HeaderValue{"1"},
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
}) })
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, base.StatusOK, res.StatusCode) require.Equal(t, base.StatusOK, res.StatusCode)
@@ -224,7 +224,7 @@ func TestServerRecordPath(t *testing.T) {
Handler: &testServerHandler{ Handler: &testServerHandler{
onAnnounce: func(ctx *ServerHandlerOnAnnounceCtx) (*base.Response, error) { onAnnounce: func(ctx *ServerHandlerOnAnnounceCtx) (*base.Response, error) {
// make sure that media URLs are not overridden by NewServerStream() // make sure that media URLs are not overridden by NewServerStream()
stream := NewServerStream(s, ctx.Medias) stream := NewServerStream(s, ctx.Description)
defer stream.Close() defer stream.Close()
return &base.Response{ return &base.Response{
@@ -344,8 +344,7 @@ func TestServerRecordErrorSetupMediaTwice(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -438,17 +437,16 @@ func TestServerRecordErrorRecordPartialMedias(t *testing.T) {
err = forma.Init() err = forma.Init()
require.NoError(t, err) require.NoError(t, err)
medias := media.Medias{ medias := []*description.Media{
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
&media.Media{ {
Type: "application", Type: "application",
Formats: []format.Format{forma}, Formats: []format.Format{forma},
}, },
} }
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -523,25 +521,25 @@ func TestServerRecord(t *testing.T) {
onRecord: func(ctx *ServerHandlerOnRecordCtx) (*base.Response, error) { onRecord: func(ctx *ServerHandlerOnRecordCtx) (*base.Response, error) {
// queue sending of RTCP packets. // queue sending of RTCP packets.
// these are sent after the response, only if onRecord returns StatusOK. // these are sent after the response, only if onRecord returns StatusOK.
err := ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedMedias()[0], &testRTCPPacket) err := ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedDescription().Medias[0], &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
err = ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedMedias()[1], &testRTCPPacket) err = ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedDescription().Medias[1], &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
for i := 0; i < 2; i++ { for i := 0; i < 2; i++ {
ctx.Session.OnPacketRTP( ctx.Session.OnPacketRTP(
ctx.Session.AnnouncedMedias()[i], ctx.Session.AnnouncedDescription().Medias[i],
ctx.Session.AnnouncedMedias()[i].Formats[0], ctx.Session.AnnouncedDescription().Medias[i].Formats[0],
func(pkt *rtp.Packet) { func(pkt *rtp.Packet) {
require.Equal(t, &testRTPPacket, pkt) require.Equal(t, &testRTPPacket, pkt)
}) })
ci := i ci := i
ctx.Session.OnPacketRTCP( ctx.Session.OnPacketRTCP(
ctx.Session.AnnouncedMedias()[i], ctx.Session.AnnouncedDescription().Medias[i],
func(pkt rtcp.Packet) { func(pkt rtcp.Packet) {
require.Equal(t, &testRTCPPacket, pkt) require.Equal(t, &testRTCPPacket, pkt)
err := ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedMedias()[ci], &testRTCPPacket) err := ctx.Session.WritePacketRTCP(ctx.Session.AnnouncedDescription().Medias[ci], &testRTCPPacket)
require.NoError(t, err) require.NoError(t, err)
}) })
} }
@@ -583,9 +581,9 @@ func TestServerRecord(t *testing.T) {
<-nconnOpened <-nconnOpened
medias := media.Medias{ medias := []*description.Media{
&media.Media{ {
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04}, SPS: []byte{0x01, 0x02, 0x03, 0x04},
@@ -593,8 +591,8 @@ func TestServerRecord(t *testing.T) {
PacketizationMode: 1, PacketizationMode: 1,
}}, }},
}, },
&media.Media{ {
Type: media.TypeVideo, Type: description.MediaTypeVideo,
Formats: []format.Format{&format.H264{ Formats: []format.Format{&format.H264{
PayloadTyp: 96, PayloadTyp: 96,
SPS: []byte{0x01, 0x02, 0x03, 0x04}, SPS: []byte{0x01, 0x02, 0x03, 0x04},
@@ -603,7 +601,6 @@ func TestServerRecord(t *testing.T) {
}}, }},
}, },
} }
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -766,8 +763,7 @@ func TestServerRecordErrorInvalidProtocol(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -833,8 +829,7 @@ func TestServerRecordRTCPReport(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -974,8 +969,7 @@ func TestServerRecordTimeout(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -1063,8 +1057,7 @@ func TestServerRecordWithoutTeardown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -1142,8 +1135,7 @@ func TestServerRecordUDPChangeConn(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -1273,14 +1265,13 @@ func TestServerRecordDecodeErrors(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{&media.Media{ medias := []*description.Media{{
Type: media.TypeApplication, Type: description.MediaTypeApplication,
Formats: []format.Format{&format.Generic{ Formats: []format.Format{&format.Generic{
PayloadTyp: 97, PayloadTyp: 97,
RTPMa: "private/90000", RTPMa: "private/90000",
}}, }},
}} }}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)
@@ -1438,7 +1429,7 @@ func TestServerRecordPacketNTP(t *testing.T) {
}, nil, nil }, nil, nil
}, },
onRecord: func(ctx *ServerHandlerOnRecordCtx) (*base.Response, error) { onRecord: func(ctx *ServerHandlerOnRecordCtx) (*base.Response, error) {
ctx.Session.OnPacketRTPAny(func(medi *media.Media, forma format.Format, pkt *rtp.Packet) { ctx.Session.OnPacketRTPAny(func(medi *description.Media, forma format.Format, pkt *rtp.Packet) {
if !first { if !first {
first = true first = true
} else { } else {
@@ -1468,8 +1459,7 @@ func TestServerRecordPacketNTP(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
resetMediaControls(medias)
doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias) doAnnounce(t, conn, "rtsp://localhost:8554/teststream", medias)

View File

@@ -14,10 +14,10 @@ import (
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4/pkg/base" "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/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/media"
"github.com/bluenviron/gortsplib/v4/pkg/rtptime" "github.com/bluenviron/gortsplib/v4/pkg/rtptime"
"github.com/bluenviron/gortsplib/v4/pkg/sdp" "github.com/bluenviron/gortsplib/v4/pkg/sdp"
"github.com/bluenviron/gortsplib/v4/pkg/url" "github.com/bluenviron/gortsplib/v4/pkg/url"
@@ -58,7 +58,7 @@ func serverParseURLForPlay(u *url.URL) (string, string, string, error) {
return path, query, trackID, nil return path, query, trackID, nil
} }
func findMediaByURL(medias media.Medias, baseURL *url.URL, u *url.URL) *media.Media { func findMediaByURL(medias []*description.Media, baseURL *url.URL, u *url.URL) *description.Media {
for _, media := range medias { for _, media := range medias {
u1, err := media.URL(baseURL) u1, err := media.URL(baseURL)
if err == nil && u1.String() == u.String() { if err == nil && u1.String() == u.String() {
@@ -69,9 +69,9 @@ func findMediaByURL(medias media.Medias, baseURL *url.URL, u *url.URL) *media.Me
return nil return nil
} }
func findMediaByTrackID(st *ServerStream, trackID string) *media.Media { func findMediaByTrackID(medias []*description.Media, trackID string) *description.Media {
if trackID == "" { if trackID == "" {
return st.medias[0] return medias[0]
} }
tmp, err := strconv.ParseUint(trackID, 10, 31) tmp, err := strconv.ParseUint(trackID, 10, 31)
@@ -80,11 +80,11 @@ func findMediaByTrackID(st *ServerStream, trackID string) *media.Media {
} }
id := int(tmp) id := int(tmp)
if len(st.medias) <= id { if len(medias) <= id {
return nil return nil
} }
return st.medias[id] return medias[id]
} }
func findFirstSupportedTransportHeader(s *Server, tsh headers.Transports) *headers.Transport { func findFirstSupportedTransportHeader(s *Server, tsh headers.Transports) *headers.Transport {
@@ -144,7 +144,7 @@ type ServerSession struct {
userData interface{} userData interface{}
conns map[*ServerConn]struct{} conns map[*ServerConn]struct{}
state ServerSessionState state ServerSessionState
setuppedMedias map[*media.Media]*serverSessionMedia setuppedMedias map[*description.Media]*serverSessionMedia
setuppedMediasOrdered []*serverSessionMedia setuppedMediasOrdered []*serverSessionMedia
tcpCallbackByChannel map[int]readFunc tcpCallbackByChannel map[int]readFunc
setuppedTransport *Transport setuppedTransport *Transport
@@ -153,8 +153,8 @@ type ServerSession struct {
setuppedQuery string setuppedQuery string
lastRequestTime time.Time lastRequestTime time.Time
tcpConn *ServerConn tcpConn *ServerConn
announcedMedias media.Medias // publish announcedDesc *description.Session // publish
udpLastPacketTime *int64 // publish udpLastPacketTime *int64 // publish
udpCheckStreamTimer *time.Timer udpCheckStreamTimer *time.Timer
writer asyncProcessor writer asyncProcessor
timeDecoder *rtptime.GlobalDecoder timeDecoder *rtptime.GlobalDecoder
@@ -221,14 +221,14 @@ func (ss *ServerSession) SetuppedTransport() *Transport {
return ss.setuppedTransport return ss.setuppedTransport
} }
// AnnouncedMedias returns the announced medias. // AnnouncedDescription returns the announced stream description.
func (ss *ServerSession) AnnouncedMedias() media.Medias { func (ss *ServerSession) AnnouncedDescription() *description.Session {
return ss.announcedMedias return ss.announcedDesc
} }
// SetuppedMedias returns the setupped medias. // SetuppedMedias returns the setupped medias.
func (ss *ServerSession) SetuppedMedias() media.Medias { func (ss *ServerSession) SetuppedMedias() []*description.Media {
ret := make(media.Medias, len(ss.setuppedMedias)) ret := make([]*description.Media, len(ss.setuppedMedias))
for i, sm := range ss.setuppedMediasOrdered { for i, sm := range ss.setuppedMediasOrdered {
ret[i] = sm.media ret[i] = sm.media
} }
@@ -511,23 +511,23 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
}, liberrors.ErrServerContentTypeUnsupported{CT: ct} }, liberrors.ErrServerContentTypeUnsupported{CT: ct}
} }
var sd sdp.SessionDescription var ssd sdp.SessionDescription
err = sd.Unmarshal(req.Body) err = ssd.Unmarshal(req.Body)
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,
}, liberrors.ErrServerSDPInvalid{Err: err} }, liberrors.ErrServerSDPInvalid{Err: err}
} }
var medias media.Medias var desc description.Session
err = medias.Unmarshal(sd.MediaDescriptions) err = desc.Unmarshal(&ssd)
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,
}, liberrors.ErrServerSDPInvalid{Err: err} }, liberrors.ErrServerSDPInvalid{Err: err}
} }
for _, medi := range medias { for _, medi := range desc.Medias {
mediURL, err := medi.URL(req.URL) mediURL, err := medi.URL(req.URL)
if err != nil { if err != nil {
return &base.Response{ return &base.Response{
@@ -551,12 +551,12 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
} }
res, err := ss.s.Handler.(ServerHandlerOnAnnounce).OnAnnounce(&ServerHandlerOnAnnounceCtx{ res, err := ss.s.Handler.(ServerHandlerOnAnnounce).OnAnnounce(&ServerHandlerOnAnnounceCtx{
Session: ss, Session: ss,
Conn: sc, Conn: sc,
Request: req, Request: req,
Path: path, Path: path,
Query: query, Query: query,
Medias: medias, Description: &desc,
}) })
if res.StatusCode != base.StatusOK { if res.StatusCode != base.StatusOK {
@@ -566,7 +566,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
ss.state = ServerSessionStatePreRecord ss.state = ServerSessionStatePreRecord
ss.setuppedPath = &path ss.setuppedPath = &path
ss.setuppedQuery = query ss.setuppedQuery = query
ss.announcedMedias = medias ss.announcedDesc = &desc
return res, err return res, err
@@ -706,10 +706,10 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
return res, err return res, err
} }
var medi *media.Media var medi *description.Media
switch ss.state { switch ss.state {
case ServerSessionStateInitial, ServerSessionStatePrePlay: // play case ServerSessionStateInitial, ServerSessionStatePrePlay: // play
medi = findMediaByTrackID(stream, trackID) medi = findMediaByTrackID(stream.desc.Medias, trackID)
default: // record default: // record
baseURL := &url.URL{ baseURL := &url.URL{
Scheme: req.URL.Scheme, Scheme: req.URL.Scheme,
@@ -724,7 +724,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
baseURL.Path += "/" baseURL.Path += "/"
} }
medi = findMediaByURL(ss.announcedMedias, baseURL, req.URL) medi = findMediaByURL(ss.announcedDesc.Medias, baseURL, req.URL)
} }
if medi == nil { if medi == nil {
@@ -819,7 +819,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
} }
if ss.setuppedMedias == nil { if ss.setuppedMedias == nil {
ss.setuppedMedias = make(map[*media.Media]*serverSessionMedia) ss.setuppedMedias = make(map[*description.Media]*serverSessionMedia)
} }
ss.setuppedMedias[medi] = sm ss.setuppedMedias[medi] = sm
ss.setuppedMediasOrdered = append(ss.setuppedMediasOrdered, sm) ss.setuppedMediasOrdered = append(ss.setuppedMediasOrdered, sm)
@@ -934,7 +934,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
}, err }, err
} }
if len(ss.setuppedMedias) != len(ss.announcedMedias) { if len(ss.setuppedMedias) != len(ss.announcedDesc.Medias) {
return &base.Response{ return &base.Response{
StatusCode: base.StatusBadRequest, StatusCode: base.StatusBadRequest,
}, liberrors.ErrServerNotAllAnnouncedMediasSetup{} }, liberrors.ErrServerNotAllAnnouncedMediasSetup{}
@@ -1148,25 +1148,25 @@ func (ss *ServerSession) OnPacketRTCPAny(cb OnPacketRTCPAnyFunc) {
} }
// OnPacketRTP sets the callback that is called when a RTP packet is read. // OnPacketRTP sets the callback that is called when a RTP packet is read.
func (ss *ServerSession) OnPacketRTP(medi *media.Media, forma format.Format, cb OnPacketRTPFunc) { func (ss *ServerSession) OnPacketRTP(medi *description.Media, forma format.Format, cb OnPacketRTPFunc) {
sm := ss.setuppedMedias[medi] sm := ss.setuppedMedias[medi]
st := sm.formats[forma.PayloadType()] st := sm.formats[forma.PayloadType()]
st.onPacketRTP = cb st.onPacketRTP = cb
} }
// OnPacketRTCP sets the callback that is called when a RTCP packet is read. // OnPacketRTCP sets the callback that is called when a RTCP packet is read.
func (ss *ServerSession) OnPacketRTCP(medi *media.Media, cb OnPacketRTCPFunc) { func (ss *ServerSession) OnPacketRTCP(medi *description.Media, cb OnPacketRTCPFunc) {
sm := ss.setuppedMedias[medi] sm := ss.setuppedMedias[medi]
sm.onPacketRTCP = cb sm.onPacketRTCP = cb
} }
func (ss *ServerSession) writePacketRTP(medi *media.Media, byts []byte) { func (ss *ServerSession) writePacketRTP(medi *description.Media, byts []byte) {
sm := ss.setuppedMedias[medi] sm := ss.setuppedMedias[medi]
sm.writePacketRTP(byts) sm.writePacketRTP(byts)
} }
// WritePacketRTP writes a RTP packet to the session. // WritePacketRTP writes a RTP packet to the session.
func (ss *ServerSession) WritePacketRTP(medi *media.Media, pkt *rtp.Packet) error { func (ss *ServerSession) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error {
byts := make([]byte, ss.s.MaxPacketSize) byts := make([]byte, ss.s.MaxPacketSize)
n, err := pkt.MarshalTo(byts) n, err := pkt.MarshalTo(byts)
if err != nil { if err != nil {
@@ -1178,13 +1178,13 @@ func (ss *ServerSession) WritePacketRTP(medi *media.Media, pkt *rtp.Packet) erro
return nil return nil
} }
func (ss *ServerSession) writePacketRTCP(medi *media.Media, byts []byte) { func (ss *ServerSession) writePacketRTCP(medi *description.Media, byts []byte) {
sm := ss.setuppedMedias[medi] sm := ss.setuppedMedias[medi]
sm.writePacketRTCP(byts) sm.writePacketRTCP(byts)
} }
// WritePacketRTCP writes a RTCP packet to the session. // WritePacketRTCP writes a RTCP packet to the session.
func (ss *ServerSession) WritePacketRTCP(medi *media.Media, pkt rtcp.Packet) error { func (ss *ServerSession) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error {
byts, err := pkt.Marshal() byts, err := pkt.Marshal()
if err != nil { if err != nil {
return err return err
@@ -1202,7 +1202,7 @@ func (ss *ServerSession) PacketPTS(forma format.Format, pkt *rtp.Packet) (time.D
// PacketNTP returns the NTP timestamp of an incoming RTP packet. // PacketNTP returns the NTP timestamp of an incoming RTP packet.
// The NTP timestamp is computed from sender reports. // The NTP timestamp is computed from sender reports.
func (ss *ServerSession) PacketNTP(medi *media.Media, pkt *rtp.Packet) (time.Time, bool) { func (ss *ServerSession) PacketNTP(medi *description.Media, pkt *rtp.Packet) (time.Time, bool) {
sm := ss.setuppedMedias[medi] sm := ss.setuppedMedias[medi]
sf := sm.formats[pkt.PayloadType] sf := sm.formats[pkt.PayloadType]
return sf.rtcpReceiver.PacketNTP(pkt.Timestamp) return sf.rtcpReceiver.PacketNTP(pkt.Timestamp)

View File

@@ -10,12 +10,12 @@ import (
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
) )
type serverSessionMedia struct { type serverSessionMedia struct {
ss *ServerSession ss *ServerSession
media *media.Media media *description.Media
tcpChannel int tcpChannel int
udpRTPReadPort int udpRTPReadPort int
udpRTPWriteAddr *net.UDPAddr udpRTPWriteAddr *net.UDPAddr
@@ -30,7 +30,7 @@ type serverSessionMedia struct {
onPacketRTCP OnPacketRTCPFunc onPacketRTCP OnPacketRTCPFunc
} }
func newServerSessionMedia(ss *ServerSession, medi *media.Media) *serverSessionMedia { func newServerSessionMedia(ss *ServerSession, medi *description.Media) *serverSessionMedia {
sm := &serverSessionMedia{ sm := &serverSessionMedia{
ss: ss, ss: ss,
media: medi, media: medi,

View File

@@ -8,9 +8,9 @@ import (
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/liberrors" "github.com/bluenviron/gortsplib/v4/pkg/liberrors"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
func firstFormat(formats map[uint8]*serverStreamFormat) *serverStreamFormat { func firstFormat(formats map[uint8]*serverStreamFormat) *serverStreamFormat {
@@ -29,27 +29,27 @@ func firstFormat(formats map[uint8]*serverStreamFormat) *serverStreamFormat {
// - allocating multicast listeners // - allocating multicast listeners
// - gathering infos about the stream in order to generate SSRC and RTP-Info // - gathering infos about the stream in order to generate SSRC and RTP-Info
type ServerStream struct { type ServerStream struct {
s *Server s *Server
medias media.Medias desc *description.Session
mutex sync.RWMutex mutex sync.RWMutex
activeUnicastReaders map[*ServerSession]struct{} activeUnicastReaders map[*ServerSession]struct{}
readers map[*ServerSession]struct{} readers map[*ServerSession]struct{}
streamMedias map[*media.Media]*serverStreamMedia streamMedias map[*description.Media]*serverStreamMedia
closed bool closed bool
} }
// NewServerStream allocates a ServerStream. // NewServerStream allocates a ServerStream.
func NewServerStream(s *Server, medias media.Medias) *ServerStream { func NewServerStream(s *Server, desc *description.Session) *ServerStream {
st := &ServerStream{ st := &ServerStream{
s: s, s: s,
medias: medias, desc: desc,
activeUnicastReaders: make(map[*ServerSession]struct{}), activeUnicastReaders: make(map[*ServerSession]struct{}),
readers: make(map[*ServerSession]struct{}), readers: make(map[*ServerSession]struct{}),
} }
st.streamMedias = make(map[*media.Media]*serverStreamMedia, len(medias)) st.streamMedias = make(map[*description.Media]*serverStreamMedia, len(desc.Medias))
for i, medi := range medias { for i, medi := range desc.Medias {
st.streamMedias[medi] = newServerStreamMedia(st, medi, i) st.streamMedias[medi] = newServerStreamMedia(st, medi, i)
} }
@@ -71,12 +71,12 @@ func (st *ServerStream) Close() {
} }
} }
// Medias returns the medias of the stream. // Description returns the description of the stream.
func (st *ServerStream) Medias() media.Medias { func (st *ServerStream) Description() *description.Session {
return st.medias return st.desc
} }
func (st *ServerStream) senderSSRC(medi *media.Media) (uint32, bool) { func (st *ServerStream) senderSSRC(medi *description.Media) (uint32, bool) {
st.mutex.Lock() st.mutex.Lock()
defer st.mutex.Unlock() defer st.mutex.Unlock()
@@ -92,7 +92,7 @@ func (st *ServerStream) senderSSRC(medi *media.Media) (uint32, bool) {
return firstFormat(sm.formats).rtcpSender.SenderSSRC() return firstFormat(sm.formats).rtcpSender.SenderSSRC()
} }
func (st *ServerStream) rtpInfoEntry(medi *media.Media, now time.Time) *headers.RTPInfoEntry { func (st *ServerStream) rtpInfoEntry(medi *description.Media, now time.Time) *headers.RTPInfoEntry {
st.mutex.Lock() st.mutex.Lock()
defer st.mutex.Unlock() defer st.mutex.Unlock()
@@ -233,13 +233,13 @@ func (st *ServerStream) readerSetInactive(ss *ServerSession) {
} }
// WritePacketRTP writes a RTP packet to all the readers of the stream. // WritePacketRTP writes a RTP packet to all the readers of the stream.
func (st *ServerStream) WritePacketRTP(medi *media.Media, pkt *rtp.Packet) error { func (st *ServerStream) WritePacketRTP(medi *description.Media, pkt *rtp.Packet) error {
return st.WritePacketRTPWithNTP(medi, pkt, st.s.timeNow()) return st.WritePacketRTPWithNTP(medi, pkt, st.s.timeNow())
} }
// WritePacketRTPWithNTP writes a RTP packet to all the readers of the stream. // WritePacketRTPWithNTP writes a RTP packet to all the readers of the stream.
// ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports. // ntp is the absolute time of the packet, and is sent with periodic RTCP sender reports.
func (st *ServerStream) WritePacketRTPWithNTP(medi *media.Media, pkt *rtp.Packet, ntp time.Time) error { func (st *ServerStream) WritePacketRTPWithNTP(medi *description.Media, pkt *rtp.Packet, ntp time.Time) error {
byts := make([]byte, st.s.MaxPacketSize) byts := make([]byte, st.s.MaxPacketSize)
n, err := pkt.MarshalTo(byts) n, err := pkt.MarshalTo(byts)
if err != nil { if err != nil {
@@ -260,7 +260,7 @@ func (st *ServerStream) WritePacketRTPWithNTP(medi *media.Media, pkt *rtp.Packet
} }
// WritePacketRTCP writes a RTCP packet to all the readers of the stream. // WritePacketRTCP writes a RTCP packet to all the readers of the stream.
func (st *ServerStream) WritePacketRTCP(medi *media.Media, pkt rtcp.Packet) error { func (st *ServerStream) WritePacketRTCP(medi *description.Media, pkt rtcp.Packet) error {
byts, err := pkt.Marshal() byts, err := pkt.Marshal()
if err != nil { if err != nil {
return err return err

View File

@@ -6,19 +6,19 @@ import (
"github.com/pion/rtcp" "github.com/pion/rtcp"
"github.com/pion/rtp" "github.com/pion/rtp"
"github.com/bluenviron/gortsplib/v4/pkg/media" "github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/rtcpsender" "github.com/bluenviron/gortsplib/v4/pkg/rtcpsender"
) )
type serverStreamMedia struct { type serverStreamMedia struct {
st *ServerStream st *ServerStream
media *media.Media media *description.Media
trackID int trackID int
formats map[uint8]*serverStreamFormat formats map[uint8]*serverStreamFormat
multicastWriter *serverMulticastWriter multicastWriter *serverMulticastWriter
} }
func newServerStreamMedia(st *ServerStream, medi *media.Media, trackID int) *serverStreamMedia { func newServerStreamMedia(st *ServerStream, medi *description.Media, trackID int) *serverStreamMedia {
sm := &serverStreamMedia{ sm := &serverStreamMedia{
st: st, st: st,
media: medi, media: medi,

View File

@@ -11,8 +11,8 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/auth" "github.com/bluenviron/gortsplib/v4/pkg/auth"
"github.com/bluenviron/gortsplib/v4/pkg/base" "github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/conn" "github.com/bluenviron/gortsplib/v4/pkg/conn"
"github.com/bluenviron/gortsplib/v4/pkg/description"
"github.com/bluenviron/gortsplib/v4/pkg/headers" "github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/media"
) )
var serverCert = []byte(`-----BEGIN CERTIFICATE----- var serverCert = []byte(`-----BEGIN CERTIFICATE-----
@@ -353,7 +353,7 @@ func TestServerErrorMethodNotImplemented(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream := NewServerStream(s, media.Medias{testH264Media}) stream := NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
h.stream = stream h.stream = stream
@@ -452,7 +452,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn1, err := net.Dial("tcp", "localhost:8554") nconn1, err := net.Dial("tcp", "localhost:8554")
@@ -545,7 +545,7 @@ func TestServerErrorTCPOneConnTwoSessions(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -620,7 +620,7 @@ func TestServerSetupMultipleTransports(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -731,7 +731,7 @@ func TestServerGetSetParameter(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -881,7 +881,7 @@ func TestServerSessionClose(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -962,7 +962,7 @@ func TestServerSessionAutoClose(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1030,7 +1030,7 @@ func TestServerSessionTeardown(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
defer s.Close() defer s.Close()
stream = NewServerStream(s, media.Medias{testH264Media}) stream = NewServerStream(s, &description.Session{Medias: []*description.Media{testH264Media}})
defer stream.Close() defer stream.Close()
nconn, err := net.Dial("tcp", "localhost:8554") nconn, err := net.Dial("tcp", "localhost:8554")
@@ -1104,7 +1104,7 @@ func TestServerAuth(t *testing.T) {
defer nconn.Close() defer nconn.Close()
conn := conn.NewConn(nconn) conn := conn.NewConn(nconn)
medias := media.Medias{testH264Media} medias := []*description.Media{testH264Media}
req := base.Request{ req := base.Request{
Method: base.Announce, Method: base.Announce,
@@ -1113,7 +1113,7 @@ func TestServerAuth(t *testing.T) {
"CSeq": base.HeaderValue{"1"}, "CSeq": base.HeaderValue{"1"},
"Content-Type": base.HeaderValue{"application/sdp"}, "Content-Type": base.HeaderValue{"application/sdp"},
}, },
Body: mustMarshalMedias(medias), Body: mediasToSDP(medias),
} }
res, err := writeReqReadRes(conn, req) res, err := writeReqReadRes(conn, req)