Repository: bluenviron/gortsplib
Pull request #210 (2023-03-22 22:34:16)

server: use absolute RTSP URL as control attribute
this is necessary in order to make GStreamer's rtspsrc process query parameters correctly.


Patch:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
deleted file mode 100644
index 4a83b95e..00000000
--- a/.github/FUNDING.yml
+++ /dev/null
@@ -1,1 +0,0 @@
-custom: ["https://www.buymeacoffee.com/aler9"]
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..3938344a
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,6 @@
+version: 2
+updates:
+  - package-ecosystem: "gomod"
+    directory: "/"
+    schedule:
+      interval: "daily"
diff --git a/go.mod b/go.mod
index 5d13f39f..dbd4a76f 100644
--- a/go.mod
+++ b/go.mod
@@ -8,15 +8,15 @@ require (
 	github.com/pion/rtcp v1.2.10
 	github.com/pion/rtp v0.0.0-20230107162714-c3ea6851e25b
 	github.com/pion/sdp/v3 v3.0.6
-	github.com/stretchr/testify v1.7.1
-	golang.org/x/net v0.7.0
+	github.com/stretchr/testify v1.8.2
+	golang.org/x/net v0.8.0
 )
 
 require (
 	github.com/asticode/go-astikit v0.30.0 // indirect
-	github.com/davecgh/go-spew v1.1.0 // indirect
+	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/pion/randutil v0.1.0 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+	golang.org/x/sys v0.6.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 7201ccfe..0ace9630 100644
--- a/go.sum
+++ b/go.sum
@@ -2,8 +2,9 @@ github.com/asticode/go-astikit v0.30.0 h1:DkBkRQRIxYcknlaU7W7ksNfn4gMFsB0tqMJflx
 github.com/asticode/go-astikit v0.30.0/go.mod h1:h4ly7idim1tNhaVkdVBeXQZEE3L0xblP7fCWbgwipF0=
 github.com/asticode/go-astits v1.11.0 h1:GTHUXht0ZXAJXsVbsLIcyfHr1Bchi4QQwMARw2ZWAng=
 github.com/asticode/go-astits v1.11.0/go.mod h1:QSHmknZ51pf6KJdHKZHJTLlMegIrhega3LPWz3ND/iI=
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
@@ -18,15 +19,20 @@ github.com/pkg/profile v1.4.0/go.mod h1:NWz/XGvpEW1FyYQ7fCx4dqYBLlfTcE+A9FLAkNKq
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/pkg/media/media.go b/pkg/media/media.go
index a115d410..6e5f0190 100644
--- a/pkg/media/media.go
+++ b/pkg/media/media.go
@@ -155,7 +155,7 @@ func (m Media) Marshal() *psdp.MediaDescription {
 	return md
 }
 
-// URL returns the media URL.
+// URL returns the absolute URL of the media.
 func (m Media) URL(contentBase *url.URL) (*url.URL, error) {
 	if contentBase == nil {
 		return nil, fmt.Errorf("Content-Base header not provided")
diff --git a/scripts/mod-tidy.mk b/scripts/mod-tidy.mk
index 3bfcf76c..b02d72ad 100644
--- a/scripts/mod-tidy.mk
+++ b/scripts/mod-tidy.mk
@@ -1,3 +1,3 @@
 mod-tidy:
 	docker run --rm -it -v $(PWD):/s -w /s $(BASE_IMAGE) \
-	sh -c "apk add git && GOPROXY=direct go get && go mod tidy"
+	sh -c "apk add git && GOPROXY=direct go mod tidy"
diff --git a/server_conn.go b/server_conn.go
index 21ecca34..ac73ab83 100644
--- a/server_conn.go
+++ b/server_conn.go
@@ -25,15 +25,27 @@ func getSessionID(header base.Header) string {
 	return ""
 }
 
-func filterMedias(medias media.Medias, streamMedias map[*media.Media]*serverStreamMedia) media.Medias {
+func mediasForSDP(
+	medias media.Medias,
+	streamMedias map[*media.Media]*serverStreamMedia,
+	contentBase *url.URL,
+) media.Medias {
 	copy := make(media.Medias, len(medias))
 	for i, medi := range medias {
-		copy[i] = &media.Media{
+		mc := &media.Media{
 			Type: medi.Type,
 			// Direction: skipped for the moment
 			Formats: medi.Formats,
 			Control: "mediaUUID=" + streamMedias[medi].uuid.String(),
 		}
+
+		// always use the absolute URL of the track as control attribute, in order
+		// to support GStreamer's rtspsrc. When a relative control is used, GStreamer
+		// puts it between path and query, instead of appending it to the URL.
+		u, _ := mc.URL(contentBase)
+		mc.Control = u.String()
+
+		copy[i] = mc
 	}
 	return copy
 }
@@ -392,7 +404,7 @@ func (sc *ServerConn) handleRequest(req *base.Request) (*base.Response, error) {
 				}
 
 				if stream != nil {
-					byts, _ := filterMedias(stream.medias, stream.streamMedias).Marshal(multicast).Marshal()
+					byts, _ := mediasForSDP(stream.medias, stream.streamMedias, req.URL).Marshal(multicast).Marshal()
 					res.Body = byts
 				}
 			}


Test patch:
diff --git a/client_play_test.go b/client_play_test.go
index 34656096..9e828c48 100644
--- a/client_play_test.go
+++ b/client_play_test.go
@@ -23,15 +23,15 @@ import (
 	"github.com/aler9/gortsplib/v2/pkg/format"
 	"github.com/aler9/gortsplib/v2/pkg/headers"
 	"github.com/aler9/gortsplib/v2/pkg/media"
-	"github.com/aler9/gortsplib/v2/pkg/sdp"
 	"github.com/aler9/gortsplib/v2/pkg/url"
 )
 
-func mustMarshalSDP(sdp *sdp.SessionDescription) []byte {
-	byts, err := sdp.Marshal()
+func mustMarshalMedias(medias media.Medias) []byte {
+	byts, err := medias.Marshal(false).Marshal()
 	if err != nil {
 		panic(err)
 	}
+
 	return byts
 }
 
@@ -148,7 +148,7 @@ func TestClientPlayFormats(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -293,7 +293,7 @@ func TestClientPlay(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{scheme + "://" + listenIP + ":8554/test/stream?param=value/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -567,7 +567,7 @@ func TestClientPlayPartial(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://" + listenIP + ":8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -711,12 +711,12 @@ func TestClientPlayContentBase(t *testing.T) {
 						Header: base.Header{
 							"Content-Type": base.HeaderValue{"application/sdp"},
 						},
-						Body: mustMarshalSDP(medias.Marshal(false)),
+						Body: mustMarshalMedias(medias),
 					})
 					require.NoError(t, err)
 
 				case "inside control attribute":
-					body := string(mustMarshalSDP(medias.Marshal(false)))
+					body := string(mustMarshalMedias(medias))
 					body = strings.Replace(body, "t=0 0", "t=0 0\r\na=control:rtsp://localhost:8554/teststream", 1)
 
 					err = conn.WriteResponse(&base.Response{
@@ -840,7 +840,7 @@ func TestClientPlayAnyPort(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -994,7 +994,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
 					"Content-Type": base.HeaderValue{"application/sdp"},
 					"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 				},
-				Body: mustMarshalSDP(medias.Marshal(false)),
+				Body: mustMarshalMedias(medias),
 			})
 			require.NoError(t, err)
 
@@ -1109,7 +1109,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -1178,7 +1178,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -1302,7 +1302,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -1384,7 +1384,7 @@ func TestClientPlayAutomaticProtocol(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -1522,7 +1522,7 @@ func TestClientPlayDifferentInterleavedIDs(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -1717,7 +1717,7 @@ func TestClientPlayRedirect(t *testing.T) {
 							"Content-Type": base.HeaderValue{"application/sdp"},
 							"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 						},
-						Body: mustMarshalSDP(medias.Marshal(false)),
+						Body: mustMarshalMedias(medias),
 					})
 					require.NoError(t, err)
 
@@ -1878,7 +1878,7 @@ func TestClientPlayPause(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -2046,7 +2046,7 @@ func TestClientPlayRTCPReport(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -2224,7 +2224,7 @@ func TestClientPlayErrorTimeout(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
@@ -2370,7 +2370,7 @@ func TestClientPlayIgnoreTCPInvalidMedia(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -2493,7 +2493,7 @@ func TestClientPlaySeek(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -2657,7 +2657,7 @@ func TestClientPlayKeepaliveFromSession(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -2780,7 +2780,7 @@ func TestClientPlayDifferentSource(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/test/stream?param=value/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 
@@ -2933,7 +2933,7 @@ func TestClientPlayDecodeErrors(t *testing.T) {
 						"Content-Type": base.HeaderValue{"application/sdp"},
 						"Content-Base": base.HeaderValue{"rtsp://localhost:8554/stream/"},
 					},
-					Body: mustMarshalSDP(medias.Marshal(false)),
+					Body: mustMarshalMedias(medias),
 				})
 				require.NoError(t, err)
 
diff --git a/client_record_test.go b/client_record_test.go
index c9d798b1..3c63920c 100644
--- a/client_record_test.go
+++ b/client_record_test.go
@@ -170,7 +170,7 @@ func TestClientRecordSerial(t *testing.T) {
 				require.NoError(t, err)
 				require.Equal(t, base.Setup, req.Method)
 				require.Equal(t, mustParseURL(
-					scheme+"://localhost:8554/teststream/"+controlAttribute(desc.MediaDescriptions[0])), req.URL)
+					scheme+"://localhost:8554/teststream/"+relativeControlAttribute(desc.MediaDescriptions[0])), req.URL)
 
 				var inTH headers.Transport
 				err = inTH.Unmarshal(req.Header["Transport"])
diff --git a/client_test.go b/client_test.go
index 68ae72a3..2b9ab625 100644
--- a/client_test.go
+++ b/client_test.go
@@ -114,7 +114,7 @@ func TestClientSession(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp"},
 				"Session":      base.HeaderValue{"123456"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 	}()
@@ -190,7 +190,7 @@ func TestClientAuth(t *testing.T) {
 			Header: base.Header{
 				"Content-Type": base.HeaderValue{"application/sdp"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 	}()
@@ -250,7 +250,7 @@ func TestClientDescribeCharset(t *testing.T) {
 				"Content-Type": base.HeaderValue{"application/sdp; charset=utf-8"},
 				"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 	}()
diff --git a/pkg/media/medias_test.go b/pkg/media/medias_test.go
index 2baf33a8..670118ce 100644
--- a/pkg/media/medias_test.go
+++ b/pkg/media/medias_test.go
@@ -16,7 +16,7 @@ var casesMedias = []struct {
 	medias Medias
 }{
 	{
-		"one track for each media",
+		"one format for each media, absolute",
 		"v=0\r\n" +
 			"o=- 0 0 IN IP4 10.0.0.131\r\n" +
 			"s=Media Presentation\r\n" +
@@ -84,7 +84,74 @@ var casesMedias = []struct {
 		},
 	},
 	{
-		"multiple tracks for each media",
+		"one format for each media, relative",
+		"v=0\r\n" +
+			"o=- 0 0 IN IP4 10.0.0.131\r\n" +
+			"s=Media Presentation\r\n" +
+			"i=samsung\r\n" +
+			"c=IN IP4 0.0.0.0\r\n" +
+			"b=AS:2632\r\n" +
+			"t=0 0\r\n" +
+			"a=range:npt=now-\r\n" +
+			"m=video 42504 RTP/AVP 97\r\n" +
+			"b=AS:2560\r\n" +
+			"a=rtpmap:97 H264/90000\r\n" +
+			"a=control:trackID=1\r\n" +
+			"a=cliprect:0,0,1080,1920\r\n" +
+			"a=framesize:97 1920-1080\r\n" +
+			"a=framerate:30.0\r\n" +
+			"a=fmtp:97 packetization-mode=1;profile-level-id=640028;sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" +
+			"m=audio 42506 RTP/AVP 0\r\n" +
+			"b=AS:64\r\n" +
+			"a=rtpmap:0 PCMU/8000\r\n" +
+			"a=control:trackID=2\r\n" +
+			"a=recvonly\r\n" +
+			"m=application 42508 RTP/AVP 107\r\n" +
+			"b=AS:8\r\n",
+		"v=0\r\n" +
+			"o=- 0 0 IN IP4 127.0.0.1\r\n" +
+			"s=Stream\r\n" +
+			"c=IN IP4 0.0.0.0\r\n" +
+			"t=0 0\r\n" +
+			"m=video 0 RTP/AVP 97\r\n" +
+			"a=control:trackID=1\r\n" +
+			"a=rtpmap:97 H264/90000\r\n" +
+			"a=fmtp:97 packetization-mode=1; profile-level-id=640028; sprop-parameter-sets=Z2QAKKy0A8ARPyo=,aO4Bniw=\r\n" +
+			"m=audio 0 RTP/AVP 0\r\n" +
+			"a=control:trackID=2\r\n" +
+			"a=recvonly\r\n" +
+			"a=rtpmap:0 PCMU/8000\r\n" +
+			"m=application 0 RTP/AVP 107\r\n" +
+			"a=control\r\n",
+		Medias{
+			{
+				Type:    "video",
+				Control: "trackID=1",
+				Formats: []format.Format{&format.H264{
+					PayloadTyp:        97,
+					PacketizationMode: 1,
+					SPS:               []byte{0x67, 0x64, 0x00, 0x28, 0xac, 0xb4, 0x03, 0xc0, 0x11, 0x3f, 0x2a},
+					PPS:               []byte{0x68, 0xee, 0x01, 0x9e, 0x2c},
+				}},
+			},
+			{
+				Type:      "audio",
+				Direction: DirectionRecvonly,
+				Control:   "trackID=2",
+				Formats: []format.Format{&format.G711{
+					MULaw: true,
+				}},
+			},
+			{
+				Type: "application",
+				Formats: []format.Format{&format.Generic{
+					PayloadTyp: 107,
+				}},
+			},
+		},
+	},
+	{
+		"multiple formats for each media",
 		"v=0\r\n" +
 			"o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" +
 			"s=-\r\n" +
@@ -353,7 +420,7 @@ var casesMedias = []struct {
 		},
 	},
 	{
-		"multiple tracks for each media 2",
+		"multiple formats for each media 2",
 		"v=0\r\n" +
 			"o=- 4158123474391860926 2 IN IP4 127.0.0.1\r\n" +
 			"s=-\r\n" +
@@ -417,17 +484,14 @@ var casesMedias = []struct {
 			"c=IN IP4 0.0.0.0\r\n" +
 			"t=0 0\r\n" +
 			"m=video 0 RTP/AVP 26\r\n" +
-
 			"a=control:rtsp://192.168.0.1/video\r\n" +
 			"a=recvonly\r\n" +
 			"a=rtpmap:26 JPEG/90000\r\n" +
 			"m=audio 0 RTP/AVP 0\r\n" +
-
 			"a=control:rtsp://192.168.0.1/audio\r\n" +
 			"a=recvonly\r\n" +
 			"a=rtpmap:0 PCMU/8000\r\n" +
 			"m=audio 0 RTP/AVP 0\r\n" +
-
 			"a=control:rtsp://192.168.0.1/audioback\r\n" +
 			"a=sendonly\r\n" +
 			"a=rtpmap:0 PCMU/8000\r\n",
@@ -507,8 +571,7 @@ func TestMediasReadErrors(t *testing.T) {
 func TestMediasMarshal(t *testing.T) {
 	for _, ca := range casesMedias {
 		t.Run(ca.name, func(t *testing.T) {
-			sdp := ca.medias.Marshal(false)
-			byts, err := sdp.Marshal()
+			byts, err := ca.medias.Marshal(false).Marshal()
 			require.NoError(t, err)
 			require.Equal(t, ca.out, string(byts))
 		})
diff --git a/server_play_test.go b/server_play_test.go
index 26edbfe5..3dd91bde 100644
--- a/server_play_test.go
+++ b/server_play_test.go
@@ -52,7 +52,13 @@ func multicastCapableIP(t *testing.T) string {
 	return ""
 }
 
-func controlAttribute(md *psdp.MediaDescription) string {
+func relativeControlAttribute(md *psdp.MediaDescription) string {
+	v, _ := md.Attribute("control")
+	i := strings.Index(v, "mediaUUID=")
+	return v[i:]
+}
+
+func absoluteControlAttribute(md *psdp.MediaDescription) string {
 	v, _ := md.Attribute("control")
 	return v
 }
@@ -60,7 +66,7 @@ func controlAttribute(md *psdp.MediaDescription) string {
 func doDescribe(conn *conn.Conn) (*sdp.SessionDescription, error) {
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Describe,
-		URL:    mustParseURL("rtsp://localhost:8554/pa"),
+		URL:    mustParseURL("rtsp://localhost:8554/teststream"),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 		},
@@ -198,11 +204,10 @@ func TestServerPlayPath(t *testing.T) {
 				InterleavedIDs: &[2]int{0, 1},
 			}
 
-			v, _ := desc.MediaDescriptions[1].Attribute("control")
-
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL(strings.ReplaceAll(ca.setupURL, "[control]", "/"+v)),
+				URL: mustParseURL(strings.ReplaceAll(ca.setupURL, "[control]", "/"+
+					relativeControlAttribute(desc.MediaDescriptions[1]))),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": th.Marshal(),
@@ -301,7 +306,7 @@ func TestServerPlaySetupErrors(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": th.Marshal(),
@@ -320,7 +325,7 @@ func TestServerPlaySetupErrors(t *testing.T) {
 
 				res, err = writeReqReadRes(conn, base.Request{
 					Method: base.Setup,
-					URL:    mustParseURL("rtsp://localhost:8554/test12stream/" + controlAttribute(desc.MediaDescriptions[0])),
+					URL:    mustParseURL("rtsp://localhost:8554/test12stream/" + relativeControlAttribute(desc.MediaDescriptions[0])),
 					Header: base.Header{
 						"CSeq":      base.HeaderValue{"3"},
 						"Transport": th.Marshal(),
@@ -341,7 +346,7 @@ func TestServerPlaySetupErrors(t *testing.T) {
 
 				res, err = writeReqReadRes(conn, base.Request{
 					Method: base.Setup,
-					URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+					URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 					Header: base.Header{
 						"CSeq":      base.HeaderValue{"4"},
 						"Transport": th.Marshal(),
@@ -425,7 +430,7 @@ func TestServerPlaySetupErrorSameUDPPortsAndIP(t *testing.T) {
 
 		res, err := writeReqReadRes(conn, base.Request{
 			Method: base.Setup,
-			URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+			URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 			Header: base.Header{
 				"CSeq":      base.HeaderValue{"2"},
 				"Transport": inTH.Marshal(),
@@ -592,7 +597,7 @@ func TestServerPlay(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://" + listenIP + ":8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": inTH.Marshal(),
@@ -886,7 +891,7 @@ func TestServerPlayDecodeErrors(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": inTH.Marshal(),
@@ -1023,7 +1028,7 @@ func TestServerPlayRTCPReport(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": inTH.Marshal(),
@@ -1226,7 +1231,7 @@ func TestServerPlayTCPResponseBeforeFrames(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"2"},
 			"Transport": headers.Transport{
@@ -1306,7 +1311,7 @@ func TestServerPlayPlayPlay(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"2"},
 			"Transport": headers.Transport{
@@ -1424,7 +1429,7 @@ func TestServerPlayPlayPausePlay(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"2"},
 			"Transport": headers.Transport{
@@ -1549,7 +1554,7 @@ func TestServerPlayPlayPausePause(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"2"},
 			"Transport": headers.Transport{
@@ -1697,7 +1702,7 @@ func TestServerPlayTimeout(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": inTH.Marshal(),
@@ -1805,7 +1810,7 @@ func TestServerPlayWithoutTeardown(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq":      base.HeaderValue{"2"},
 					"Transport": inTH.Marshal(),
@@ -1899,7 +1904,7 @@ func TestServerPlayUDPChangeConn(t *testing.T) {
 
 		res, err := writeReqReadRes(conn, base.Request{
 			Method: base.Setup,
-			URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+			URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 			Header: base.Header{
 				"CSeq":      base.HeaderValue{"2"},
 				"Transport": inTH.Marshal(),
@@ -2003,7 +2008,7 @@ func TestServerPlayPartialMedias(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[1])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[1])),
 		Header: base.Header{
 			"CSeq":      base.HeaderValue{"2"},
 			"Transport": inTH.Marshal(),
@@ -2058,7 +2063,7 @@ func TestServerPlayAdditionalInfos(t *testing.T) {
 
 		res, err := writeReqReadRes(conn, base.Request{
 			Method: base.Setup,
-			URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+			URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 			Header: base.Header{
 				"CSeq":      base.HeaderValue{"2"},
 				"Transport": inTH.Marshal(),
@@ -2092,7 +2097,7 @@ func TestServerPlayAdditionalInfos(t *testing.T) {
 
 		res, err = writeReqReadRes(conn, base.Request{
 			Method: base.Setup,
-			URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[1])),
+			URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[1])),
 			Header: base.Header{
 				"CSeq":      base.HeaderValue{"3"},
 				"Transport": inTH.Marshal(),
diff --git a/server_record_test.go b/server_record_test.go
index 179ce2d9..e3172205 100644
--- a/server_record_test.go
+++ b/server_record_test.go
@@ -246,9 +246,7 @@ func TestServerRecordPath(t *testing.T) {
 				TimeDescriptions: []psdp.TimeDescription{
 					{Timing: psdp.Timing{0, 0}}, //nolint:govet
 				},
-				MediaDescriptions: []*psdp.MediaDescription{
-					media.Marshal(),
-				},
+				MediaDescriptions: []*psdp.MediaDescription{media.Marshal()},
 			}
 
 			byts, _ := sout.Marshal()
@@ -348,7 +346,7 @@ func TestServerRecordErrorSetupMediaTwice(t *testing.T) {
 			"CSeq":         base.HeaderValue{"1"},
 			"Content-Type": base.HeaderValue{"application/sdp"},
 		},
-		Body: mustMarshalSDP(medias.Marshal(false)),
+		Body: mustMarshalMedias(medias),
 	})
 	require.NoError(t, err)
 	require.Equal(t, base.StatusOK, res.StatusCode)
@@ -468,7 +466,7 @@ func TestServerRecordErrorRecordPartialMedias(t *testing.T) {
 			"CSeq":         base.HeaderValue{"1"},
 			"Content-Type": base.HeaderValue{"application/sdp"},
 		},
-		Body: mustMarshalSDP(medias.Marshal(false)),
+		Body: mustMarshalMedias(medias),
 	})
 	require.NoError(t, err)
 	require.Equal(t, base.StatusOK, res.StatusCode)
@@ -641,7 +639,7 @@ func TestServerRecord(t *testing.T) {
 					"CSeq":         base.HeaderValue{"1"},
 					"Content-Type": base.HeaderValue{"application/sdp"},
 				},
-				Body: mustMarshalSDP(medias.Marshal(false)),
+				Body: mustMarshalMedias(medias),
 			})
 			require.NoError(t, err)
 			require.Equal(t, base.StatusOK, res.StatusCode)
@@ -845,7 +843,7 @@ func TestServerRecordErrorInvalidProtocol(t *testing.T) {
 			"CSeq":         base.HeaderValue{"1"},
 			"Content-Type": base.HeaderValue{"application/sdp"},
 		},
-		Body: mustMarshalSDP(medias.Marshal(false)),
+		Body: mustMarshalMedias(medias),
 	})
 	require.NoError(t, err)
 	require.Equal(t, base.StatusOK, res.StatusCode)
@@ -946,7 +944,7 @@ func TestServerRecordRTCPReport(t *testing.T) {
 			"CSeq":         base.HeaderValue{"1"},
 			"Content-Type": base.HeaderValue{"application/sdp"},
 		},
-		Body: mustMarshalSDP(medias.Marshal(false)),
+		Body: mustMarshalMedias(medias),
 	})
 	require.NoError(t, err)
 	require.Equal(t, base.StatusOK, res.StatusCode)
@@ -1121,7 +1119,7 @@ func TestServerRecordTimeout(t *testing.T) {
 					"CSeq":         base.HeaderValue{"1"},
 					"Content-Type": base.HeaderValue{"application/sdp"},
 				},
-				Body: mustMarshalSDP(medias.Marshal(false)),
+				Body: mustMarshalMedias(medias),
 			})
 			require.NoError(t, err)
 			require.Equal(t, base.StatusOK, res.StatusCode)
@@ -1244,7 +1242,7 @@ func TestServerRecordWithoutTeardown(t *testing.T) {
 					"CSeq":         base.HeaderValue{"1"},
 					"Content-Type": base.HeaderValue{"application/sdp"},
 				},
-				Body: mustMarshalSDP(medias.Marshal(false)),
+				Body: mustMarshalMedias(medias),
 			})
 			require.NoError(t, err)
 			require.Equal(t, base.StatusOK, res.StatusCode)
@@ -1357,7 +1355,7 @@ func TestServerRecordUDPChangeConn(t *testing.T) {
 				"CSeq":         base.HeaderValue{"1"},
 				"Content-Type": base.HeaderValue{"application/sdp"},
 			},
-			Body: mustMarshalSDP(medias.Marshal(false)),
+			Body: mustMarshalMedias(medias),
 		})
 		require.NoError(t, err)
 		require.Equal(t, base.StatusOK, res.StatusCode)
@@ -1512,7 +1510,7 @@ func TestServerRecordDecodeErrors(t *testing.T) {
 					"CSeq":         base.HeaderValue{"1"},
 					"Content-Type": base.HeaderValue{"application/sdp"},
 				},
-				Body: mustMarshalSDP(medias.Marshal(false)),
+				Body: mustMarshalMedias(medias),
 			})
 			require.NoError(t, err)
 			require.Equal(t, base.StatusOK, res.StatusCode)
diff --git a/server_test.go b/server_test.go
index 7ba93246..bb0d2db0 100644
--- a/server_test.go
+++ b/server_test.go
@@ -359,7 +359,7 @@ func TestServerErrorMethodNotImplemented(t *testing.T) {
 			if ca == "inside session" {
 				res, err := writeReqReadRes(conn, base.Request{
 					Method: base.Setup,
-					URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+					URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 					Header: base.Header{
 						"CSeq": base.HeaderValue{"1"},
 						"Transport": headers.Transport{
@@ -391,7 +391,7 @@ func TestServerErrorMethodNotImplemented(t *testing.T) {
 
 			res, err := writeReqReadRes(conn, base.Request{
 				Method: base.SetParameter,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: headers,
 			})
 			require.NoError(t, err)
@@ -459,7 +459,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) {
 
 	res, err := writeReqReadRes(conn1, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc1.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc1.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 			"Transport": headers.Transport{
@@ -504,7 +504,7 @@ func TestServerErrorTCPTwoConnOneSession(t *testing.T) {
 
 	res, err = writeReqReadRes(conn2, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc2.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc2.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 			"Transport": headers.Transport{
@@ -570,7 +570,7 @@ func TestServerErrorTCPOneConnTwoSessions(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 			"Transport": headers.Transport{
@@ -607,7 +607,7 @@ func TestServerErrorTCPOneConnTwoSessions(t *testing.T) {
 
 	res, err = writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"3"},
 			"Transport": headers.Transport{
@@ -689,7 +689,7 @@ func TestServerSetupMultipleTransports(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq":      base.HeaderValue{"1"},
 			"Transport": inTHS.Marshal(),
@@ -776,7 +776,7 @@ func TestServerGetSetParameter(t *testing.T) {
 			if ca == "inside session" {
 				res, err := writeReqReadRes(conn, base.Request{
 					Method: base.Setup,
-					URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+					URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 					Header: base.Header{
 						"CSeq": base.HeaderValue{"2"},
 						"Transport": headers.Transport{
@@ -927,7 +927,7 @@ func TestServerSessionClose(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 			"Transport": headers.Transport{
@@ -1008,7 +1008,7 @@ func TestServerSessionAutoClose(t *testing.T) {
 
 			_, err = writeReqReadRes(conn, base.Request{
 				Method: base.Setup,
-				URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+				URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 				Header: base.Header{
 					"CSeq": base.HeaderValue{"1"},
 					"Transport": headers.Transport{
@@ -1068,7 +1068,7 @@ func TestServerSessionTeardown(t *testing.T) {
 
 	res, err := writeReqReadRes(conn, base.Request{
 		Method: base.Setup,
-		URL:    mustParseURL("rtsp://localhost:8554/teststream/" + controlAttribute(desc.MediaDescriptions[0])),
+		URL:    mustParseURL(absoluteControlAttribute(desc.MediaDescriptions[0])),
 		Header: base.Header{
 			"CSeq": base.HeaderValue{"1"},
 			"Transport": headers.Transport{
@@ -1156,7 +1156,7 @@ func TestServerAuth(t *testing.T) {
 			"CSeq":         base.HeaderValue{"1"},
 			"Content-Type": base.HeaderValue{"application/sdp"},
 		},
-		Body: mustMarshalSDP(medias.Marshal(false)),
+		Body: mustMarshalMedias(medias),
 	}
 
 	res, err := writeReqReadRes(conn, req)