Skip to content

Commit 8add608

Browse files
committed
feat(sdp): mirror media direction in answer
Answer now reflects the offer's direction per RFC 3264 (sendonly↔recvonly, inactive/sendrecv unchanged) instead of always emitting sendrecv. AnswerMedia takes the direction as a parameter.
1 parent 40d3338 commit 8add608

2 files changed

Lines changed: 68 additions & 4 deletions

File tree

sdp/offer.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func OfferMedia(rtpListenerPort int, encrypted Encryption) (MediaDesc, *sdp.Medi
186186
}
187187

188188
// AnswerMedia creates a new SDP media description for an answer.
189-
func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *sdp.MediaDescription {
189+
func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile, dir sdp.Direction) *sdp.MediaDescription {
190190
// Static compiler check for frame duration hardcoded below.
191191
var _ = [1]struct{}{}[20*time.Millisecond-rtp.DefFrameDur]
192192

@@ -210,7 +210,7 @@ func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *
210210
}
211211
attrs = append(attrs, []sdp.Attribute{
212212
{Key: "ptime", Value: "20"},
213-
{Key: "sendrecv"},
213+
{Key: dir.String()},
214214
}...)
215215
return &sdp.MediaDescription{
216216
MediaName: sdp.MediaName{
@@ -223,6 +223,21 @@ func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *
223223
}
224224
}
225225

226+
func mirror(d sdp.Direction) sdp.Direction {
227+
switch d {
228+
case sdp.DirectionSendRecv:
229+
return sdp.DirectionSendRecv
230+
case sdp.DirectionSendOnly:
231+
return sdp.DirectionRecvOnly
232+
case sdp.DirectionRecvOnly:
233+
return sdp.DirectionSendOnly
234+
case sdp.DirectionInactive:
235+
return sdp.DirectionInactive
236+
default:
237+
return sdp.DirectionSendRecv
238+
}
239+
}
240+
226241
type Description struct {
227242
SDP sdp.SessionDescription
228243
Addr netip.AddrPort
@@ -306,7 +321,8 @@ func (d *Offer) Answer(publicIp netip.Addr, rtpListenerPort int, enc Encryption)
306321
return nil, nil, ErrNoCommonCrypto
307322
}
308323

309-
mediaDesc := AnswerMedia(rtpListenerPort, audio, sprof)
324+
dir := mirror(d.MediaDesc.Direction)
325+
mediaDesc := AnswerMedia(rtpListenerPort, audio, sprof, dir)
310326
answer := sdp.SessionDescription{
311327
Version: 0,
312328
Origin: sdp.Origin{
@@ -402,7 +418,7 @@ func buildLocalSDP(sessionID uint64, local netip.AddrPort, audio *AudioConfig, s
402418
}
403419
addrStr := local.Addr().String()
404420
portVal := int(local.Port())
405-
mediaDesc := AnswerMedia(portVal, audio, sprof)
421+
mediaDesc := AnswerMedia(portVal, audio, sprof, sdp.DirectionSendRecv)
406422
s := &sdp.SessionDescription{
407423
Version: 0,
408424
Origin: sdp.Origin{

sdp/offer_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,3 +879,51 @@ a=inactive
879879
})
880880
}
881881
}
882+
883+
func TestAnswerMirrorsDirection(t *testing.T) {
884+
g := media.GlobalCodecs()
885+
886+
const offer = `v=0
887+
o=- 1 1 IN IP4 203.0.113.5
888+
s=LiveKit
889+
c=IN IP4 203.0.113.5
890+
t=0 0
891+
m=audio 10000 RTP/AVP 0
892+
a=rtpmap:0 PCMU/8000
893+
`
894+
895+
tests := []struct {
896+
name string
897+
dir string // direction in the offer
898+
want sdp.Direction // expected mirrored direction in the answer
899+
}{
900+
{name: "sendonly mirrors to recvonly", dir: "a=sendonly\n", want: sdp.DirectionRecvOnly},
901+
{name: "recvonly mirrors to sendonly", dir: "a=recvonly\n", want: sdp.DirectionSendOnly},
902+
{name: "inactive stays inactive", dir: "a=inactive\n", want: sdp.DirectionInactive},
903+
{name: "sendrecv stays sendrecv", dir: "a=sendrecv\n", want: sdp.DirectionSendRecv},
904+
}
905+
906+
for _, test := range tests {
907+
t.Run(test.name, func(t *testing.T) {
908+
o, err := ParseOfferWith(g, []byte(offer+test.dir))
909+
require.NoError(t, err)
910+
911+
answer, _, err := o.Answer(netip.MustParseAddr("203.0.113.9"), 20000, EncryptionNone)
912+
require.NoError(t, err)
913+
914+
require.Equal(t, test.want, answerDirection(t, answer.SDP.MediaDescriptions[0]))
915+
})
916+
}
917+
}
918+
919+
// answerDirection returns the direction attribute set on a media description.
920+
func answerDirection(t *testing.T, m *sdp.MediaDescription) sdp.Direction {
921+
t.Helper()
922+
for _, a := range m.Attributes {
923+
if dir, err := sdp.NewDirection(a.Key); err == nil {
924+
return dir
925+
}
926+
}
927+
t.Fatal("no direction attribute in answer")
928+
return 0
929+
}

0 commit comments

Comments
 (0)