Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions sdp/offer.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func OfferMedia(rtpListenerPort int, encrypted Encryption) (MediaDesc, *sdp.Medi
}

// AnswerMedia creates a new SDP media description for an answer.
func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *sdp.MediaDescription {
func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile, dir sdp.Direction) *sdp.MediaDescription {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe also allow passing empty direction and default to sendrecv?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since Direction is iota+1, the zero value already represents "unset", so I'll take it by value and default 0 → sendrecv.

// Static compiler check for frame duration hardcoded below.
var _ = [1]struct{}{}[20*time.Millisecond-rtp.DefFrameDur]

Expand All @@ -210,7 +210,7 @@ func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *
}
attrs = append(attrs, []sdp.Attribute{
{Key: "ptime", Value: "20"},
{Key: "sendrecv"},
{Key: dir.String()},
}...)
return &sdp.MediaDescription{
MediaName: sdp.MediaName{
Expand All @@ -223,6 +223,21 @@ func AnswerMedia(rtpListenerPort int, audio *AudioConfig, crypt *srtp.Profile) *
}
}

func mirror(d sdp.Direction) sdp.Direction {
switch d {
case sdp.DirectionSendRecv:
return sdp.DirectionSendRecv
case sdp.DirectionSendOnly:
return sdp.DirectionRecvOnly
case sdp.DirectionRecvOnly:
return sdp.DirectionSendOnly
case sdp.DirectionInactive:
return sdp.DirectionInactive
default:
return sdp.DirectionSendRecv
}
}

type Description struct {
SDP sdp.SessionDescription
Addr netip.AddrPort
Expand Down Expand Up @@ -306,7 +321,8 @@ func (d *Offer) Answer(publicIp netip.Addr, rtpListenerPort int, enc Encryption)
return nil, nil, ErrNoCommonCrypto
}

mediaDesc := AnswerMedia(rtpListenerPort, audio, sprof)
dir := mirror(d.MediaDesc.Direction)
mediaDesc := AnswerMedia(rtpListenerPort, audio, sprof, dir)
answer := sdp.SessionDescription{
Version: 0,
Origin: sdp.Origin{
Expand Down Expand Up @@ -402,7 +418,7 @@ func buildLocalSDP(sessionID uint64, local netip.AddrPort, audio *AudioConfig, s
}
addrStr := local.Addr().String()
portVal := int(local.Port())
mediaDesc := AnswerMedia(portVal, audio, sprof)
mediaDesc := AnswerMedia(portVal, audio, sprof, sdp.DirectionSendRecv)
s := &sdp.SessionDescription{
Version: 0,
Origin: sdp.Origin{
Expand Down
48 changes: 48 additions & 0 deletions sdp/offer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -879,3 +879,51 @@ a=inactive
})
}
}

func TestAnswerMirrorsDirection(t *testing.T) {
g := media.GlobalCodecs()

const offer = `v=0
o=- 1 1 IN IP4 203.0.113.5
s=LiveKit
c=IN IP4 203.0.113.5
t=0 0
m=audio 10000 RTP/AVP 0
a=rtpmap:0 PCMU/8000
`

tests := []struct {
name string
dir string // direction in the offer
want sdp.Direction // expected mirrored direction in the answer
}{
{name: "sendonly mirrors to recvonly", dir: "a=sendonly\n", want: sdp.DirectionRecvOnly},
{name: "recvonly mirrors to sendonly", dir: "a=recvonly\n", want: sdp.DirectionSendOnly},
{name: "inactive stays inactive", dir: "a=inactive\n", want: sdp.DirectionInactive},
{name: "sendrecv stays sendrecv", dir: "a=sendrecv\n", want: sdp.DirectionSendRecv},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
o, err := ParseOfferWith(g, []byte(offer+test.dir))
require.NoError(t, err)

answer, _, err := o.Answer(netip.MustParseAddr("203.0.113.9"), 20000, EncryptionNone)
require.NoError(t, err)

require.Equal(t, test.want, answerDirection(t, answer.SDP.MediaDescriptions[0]))
})
}
}

// answerDirection returns the direction attribute set on a media description.
func answerDirection(t *testing.T, m *sdp.MediaDescription) sdp.Direction {
t.Helper()
for _, a := range m.Attributes {
if dir, err := sdp.NewDirection(a.Key); err == nil {
return dir
}
}
t.Fatal("no direction attribute in answer")
return 0
}
Loading