mirror of
https://github.com/aler9/gortsplib
synced 2025-09-28 20:12:18 +08:00
allow setting additional properties of streams through description.Stream
This commit is contained in:
128
client.go
128
client.go
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
@@ -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)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
|
@@ -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)
|
||||||
})
|
})
|
@@ -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
|
|
||||||
}
|
}
|
@@ -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)
|
||||||
}
|
}
|
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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.
|
||||||
|
@@ -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()
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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,
|
||||||
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user