diff --git a/errors.go b/errors.go index 23d26200..31595b59 100644 --- a/errors.go +++ b/errors.go @@ -202,6 +202,7 @@ var ( errRTPTransceiverCannotChangeMid = errors.New("errRTPSenderTrackNil") errRTPTransceiverSetSendingInvalidState = errors.New("invalid state change in RTPTransceiver.setSending") + errRTPTransceiverCodecUnsupported = errors.New("unsupported codec type by this transceiver") errSCTPTransportDTLS = errors.New("DTLS not established") diff --git a/mediaengine.go b/mediaengine.go index c7384fe4..6c160358 100644 --- a/mediaengine.go +++ b/mediaengine.go @@ -6,6 +6,7 @@ import ( "fmt" "strconv" "strings" + "sync" "time" "github.com/pion/rtp" @@ -57,6 +58,8 @@ type MediaEngine struct { headerExtensions []mediaEngineHeaderExtension negotiatedHeaderExtensions map[int]mediaEngineHeaderExtension + + mu sync.RWMutex } // RegisterDefaultCodecs registers the default codecs supported by Pion WebRTC. @@ -196,6 +199,9 @@ func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParame // These are the list of codecs supported by this PeerConnection. // RegisterCodec is not safe for concurrent use. func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) error { + m.mu.Lock() + defer m.mu.Unlock() + codec.statsID = fmt.Sprintf("RTPCodec-%d", time.Now().UnixNano()) switch typ { case RTPCodecTypeAudio: @@ -211,6 +217,9 @@ func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType) // RegisterHeaderExtension adds a header extension to the MediaEngine // To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType, allowedDirections ...RTPTransceiverDirection) error { + m.mu.Lock() + defer m.mu.Unlock() + if m.negotiatedHeaderExtensions == nil { m.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{} } @@ -251,6 +260,9 @@ func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapabi // RegisterFeedback adds feedback mechanism to already registered codecs. func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) { + m.mu.Lock() + defer m.mu.Unlock() + switch typ { case RTPCodecTypeVideo: for i, v := range m.videoCodecs { @@ -268,6 +280,9 @@ func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType) // getHeaderExtensionID returns the negotiated ID for a header extension. // If the Header Extension isn't enabled ok will be false func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) { + m.mu.RLock() + defer m.mu.RUnlock() + if m.negotiatedHeaderExtensions == nil { return 0, false, false } @@ -284,6 +299,8 @@ func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapabilit // copy copies any user modifiable state of the MediaEngine // all internal state is reset func (m *MediaEngine) copy() *MediaEngine { + m.mu.Lock() + defer m.mu.Unlock() cloned := &MediaEngine{ videoCodecs: append([]RTPCodecParameters{}, m.videoCodecs...), audioCodecs: append([]RTPCodecParameters{}, m.audioCodecs...), @@ -296,12 +313,24 @@ func (m *MediaEngine) copy() *MediaEngine { } func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParameters, RTPCodecType, error) { - for _, codec := range m.negotiatedVideoCodecs { + m.mu.RLock() + defer m.mu.RUnlock() + + codecs := m.negotiatedVideoCodecs + if !m.negotiatedVideo { + codecs = m.videoCodecs + } + for _, codec := range codecs { if codec.PayloadType == payloadType { return codec, RTPCodecTypeVideo, nil } } - for _, codec := range m.negotiatedAudioCodecs { + + codecs = m.negotiatedAudioCodecs + if !m.negotiatedAudio { + codecs = m.audioCodecs + } + for _, codec := range codecs { if codec.PayloadType == payloadType { return codec, RTPCodecTypeAudio, nil } @@ -311,6 +340,9 @@ func (m *MediaEngine) getCodecByPayload(payloadType PayloadType) (RTPCodecParame } func (m *MediaEngine) collectStats(collector *statsReportCollector) { + m.mu.RLock() + defer m.mu.RUnlock() + statsLoop := func(codecs []RTPCodecParameters) { for _, codec := range codecs { collector.Collecting() @@ -418,6 +450,9 @@ func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType) // Update the MediaEngine from a remote description func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error { + m.mu.Lock() + defer m.mu.Unlock() + for _, media := range desc.MediaDescriptions { var typ RTPCodecType switch { @@ -478,6 +513,9 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e } func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters { + m.mu.RLock() + defer m.mu.RUnlock() + if typ == RTPCodecTypeVideo { if m.negotiatedVideo { return m.negotiatedVideoCodecs @@ -496,6 +534,9 @@ func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters { } func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters { + m.mu.RLock() + defer m.mu.RUnlock() + headerExtensions := make([]RTPHeaderExtensionParameter, 0) if m.negotiatedVideo && typ == RTPCodecTypeVideo || @@ -525,6 +566,8 @@ func (m *MediaEngine) getRTPParametersByPayloadType(payloadType PayloadType) (RT return RTPParameters{}, err } + m.mu.RLock() + defer m.mu.RUnlock() headerExtensions := make([]RTPHeaderExtensionParameter, 0) for id, e := range m.negotiatedHeaderExtensions { if e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo { diff --git a/peerconnection.go b/peerconnection.go index 1afb120c..8ac7ec61 100644 --- a/peerconnection.go +++ b/peerconnection.go @@ -1052,7 +1052,7 @@ func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) error { localDirection = RTPTransceiverDirectionSendonly } - t = newRTPTransceiver(receiver, nil, localDirection, kind) + t = newRTPTransceiver(receiver, nil, localDirection, kind, pc.api) pc.mu.Lock() pc.addRTPTransceiver(t) pc.mu.Unlock() @@ -1634,7 +1634,7 @@ func (pc *PeerConnection) newTransceiverFromTrack(direction RTPTransceiverDirect if err != nil { return } - return newRTPTransceiver(r, s, direction, track.Kind()), nil + return newRTPTransceiver(r, s, direction, track.Kind(), pc.api), nil } // AddTransceiverFromKind Create a new RtpTransceiver and adds it to the set of transceivers. @@ -1668,7 +1668,7 @@ func (pc *PeerConnection) AddTransceiverFromKind(kind RTPCodecType, init ...RTPT if err != nil { return nil, err } - t = newRTPTransceiver(receiver, nil, RTPTransceiverDirectionRecvonly, kind) + t = newRTPTransceiver(receiver, nil, RTPTransceiverDirectionRecvonly, kind, pc.api) default: return nil, errPeerConnAddTransceiverFromKindSupport } @@ -2208,7 +2208,7 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use t, localTransceivers = satisfyTypeAndDirection(kind, direction, localTransceivers) if t == nil { if len(mediaTransceivers) == 0 { - t = &RTPTransceiver{kind: kind} + t = &RTPTransceiver{kind: kind, api: pc.api, codecs: pc.api.mediaEngine.getCodecsByKind(kind)} t.setDirection(RTPTransceiverDirectionInactive) mediaTransceivers = append(mediaTransceivers, t) } diff --git a/rtpreceiver.go b/rtpreceiver.go index 446e8ae9..cec59339 100644 --- a/rtpreceiver.go +++ b/rtpreceiver.go @@ -38,6 +38,8 @@ type RTPReceiver struct { closed, received chan interface{} mu sync.RWMutex + tr *RTPTransceiver + // A reference to the associated api object api *API } @@ -60,6 +62,12 @@ func (api *API) NewRTPReceiver(kind RTPCodecType, transport *DTLSTransport) (*RT return r, nil } +func (r *RTPReceiver) setRTPTransceiver(tr *RTPTransceiver) { + r.mu.Lock() + defer r.mu.Unlock() + r.tr = tr +} + // Transport returns the currently-configured *DTLSTransport or nil // if one has not yet been configured func (r *RTPReceiver) Transport() *DTLSTransport { @@ -68,10 +76,18 @@ func (r *RTPReceiver) Transport() *DTLSTransport { return r.transport } +func (r *RTPReceiver) getParameters() RTPParameters { + parameters := r.api.mediaEngine.getRTPParametersByKind(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) + parameters.Codecs = r.tr.getCodecs() + return parameters +} + // GetParameters describes the current configuration for the encoding and // transmission of media on the receiver's track. func (r *RTPReceiver) GetParameters() RTPParameters { - return r.api.mediaEngine.getRTPParametersByKind(r.kind, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly}) + r.mu.RLock() + defer r.mu.RUnlock() + return r.getParameters() } // Track returns the RtpTransceiver TrackRemote @@ -119,7 +135,7 @@ func (r *RTPReceiver) Receive(parameters RTPReceiveParameters) error { ), } - globalParams := r.GetParameters() + globalParams := r.getParameters() codec := RTPCodecCapability{} if len(globalParams.Codecs) != 0 { codec = globalParams.Codecs[0].RTPCodecCapability diff --git a/rtpsender.go b/rtpsender.go index fb21d1bd..96cb926a 100644 --- a/rtpsender.go +++ b/rtpsender.go @@ -38,6 +38,8 @@ type RTPSender struct { api *API id string + tr *RTPTransceiver + mu sync.RWMutex sendCalled, stopCalled chan struct{} } @@ -88,6 +90,12 @@ func (r *RTPSender) setNegotiated() { r.negotiated = true } +func (r *RTPSender) setRTPTransceiver(tr *RTPTransceiver) { + r.mu.Lock() + defer r.mu.Unlock() + r.tr = tr +} + // Transport returns the currently-configured *DTLSTransport or nil // if one has not yet been configured func (r *RTPSender) Transport() *DTLSTransport { @@ -99,7 +107,7 @@ func (r *RTPSender) Transport() *DTLSTransport { // GetParameters describes the current configuration for the encoding and // transmission of media on the sender's track. func (r *RTPSender) GetParameters() RTPSendParameters { - return RTPSendParameters{ + sendParameters := RTPSendParameters{ RTPParameters: r.api.mediaEngine.getRTPParametersByKind( r.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}, @@ -113,6 +121,8 @@ func (r *RTPSender) GetParameters() RTPSendParameters { }, }, } + sendParameters.Codecs = r.tr.getCodecs() + return sendParameters } // Track returns the RTCRtpTransceiver track, or nil diff --git a/rtptransceiver.go b/rtptransceiver.go index 62291565..4d21b545 100644 --- a/rtptransceiver.go +++ b/rtptransceiver.go @@ -4,6 +4,7 @@ package webrtc import ( "fmt" + "sync" "sync/atomic" "github.com/pion/rtp" @@ -16,8 +17,13 @@ type RTPTransceiver struct { receiver atomic.Value // *RTPReceiver direction atomic.Value // RTPTransceiverDirection + codecs []RTPCodecParameters // User provided codecs via SetCodecPreferences + stopped bool kind RTPCodecType + + api *API + mu sync.RWMutex } func newRTPTransceiver( @@ -25,14 +31,51 @@ func newRTPTransceiver( sender *RTPSender, direction RTPTransceiverDirection, kind RTPCodecType, + api *API, ) *RTPTransceiver { - t := &RTPTransceiver{kind: kind} + t := &RTPTransceiver{kind: kind, api: api} t.setReceiver(receiver) t.setSender(sender) t.setDirection(direction) return t } +// SetCodecPreferences sets preferred list of supported codecs +// if codecs is empty or nil we reset to default from MediaEngine +func (t *RTPTransceiver) SetCodecPreferences(codecs []RTPCodecParameters) error { + t.mu.Lock() + defer t.mu.Unlock() + + for _, codec := range codecs { + if _, matchType := codecParametersFuzzySearch(codec, t.api.mediaEngine.getCodecsByKind(t.kind)); matchType == codecMatchNone { + return fmt.Errorf("%w %s", errRTPTransceiverCodecUnsupported, codec.MimeType) + } + } + + t.codecs = codecs + return nil +} + +// Codecs returns list of supported codecs +func (t *RTPTransceiver) getCodecs() []RTPCodecParameters { + t.mu.RLock() + defer t.mu.RUnlock() + + mediaEngineCodecs := t.api.mediaEngine.getCodecsByKind(t.kind) + if len(t.codecs) == 0 { + return mediaEngineCodecs + } + + filteredCodecs := []RTPCodecParameters{} + for _, codec := range t.codecs { + if c, matchType := codecParametersFuzzySearch(codec, mediaEngineCodecs); matchType != codecMatchNone { + filteredCodecs = append(filteredCodecs, c) + } + } + + return filteredCodecs +} + // Sender returns the RTPTransceiver's RTPSender if it has one func (t *RTPTransceiver) Sender() *RTPSender { if v := t.sender.Load(); v != nil { @@ -49,6 +92,14 @@ func (t *RTPTransceiver) SetSender(s *RTPSender, track TrackLocal) error { } func (t *RTPTransceiver) setSender(s *RTPSender) { + if s != nil { + s.setRTPTransceiver(t) + } + + if prevSender := t.Sender(); prevSender != nil { + prevSender.setRTPTransceiver(nil) + } + t.sender.Store(s) } @@ -106,6 +157,14 @@ func (t *RTPTransceiver) Stop() error { } func (t *RTPTransceiver) setReceiver(r *RTPReceiver) { + if r != nil { + r.setRTPTransceiver(t) + } + + if prevReceiver := t.Receiver(); prevReceiver != nil { + prevReceiver.setRTPTransceiver(nil) + } + t.receiver.Store(r) } diff --git a/rtptransceiver_test.go b/rtptransceiver_test.go new file mode 100644 index 00000000..f5d46337 --- /dev/null +++ b/rtptransceiver_test.go @@ -0,0 +1,133 @@ +// +build !js + +package webrtc + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RTPTransceiver_SetCodecPreferences(t *testing.T) { + me := &MediaEngine{} + api := NewAPI(WithMediaEngine(me)) + assert.NoError(t, me.RegisterDefaultCodecs()) + + me.pushCodecs(me.videoCodecs, RTPCodecTypeVideo) + me.pushCodecs(me.audioCodecs, RTPCodecTypeAudio) + + tr := RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs} + assert.EqualValues(t, me.videoCodecs, tr.getCodecs()) + + failTestCases := [][]RTPCodecParameters{ + { + { + RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil}, + PayloadType: 111, + }, + }, + { + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, + PayloadType: 96, + }, + { + RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 2, "minptime=10;useinbandfec=1", nil}, + PayloadType: 111, + }, + }, + } + + for _, testCase := range failTestCases { + assert.Error(t, tr.SetCodecPreferences(testCase), errRTPTransceiverCodecUnsupported) + } + + successTestCases := [][]RTPCodecParameters{ + { + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, + PayloadType: 96, + }, + }, + { + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, + PayloadType: 96, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=96", nil}, + PayloadType: 97, + }, + + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", nil}, + PayloadType: 98, + }, + { + RTPCodecCapability: RTPCodecCapability{"video/rtx", 90000, 0, "apt=98", nil}, + PayloadType: 99, + }, + }, + } + + for _, testCase := range successTestCases { + assert.NoError(t, tr.SetCodecPreferences(testCase)) + } + + assert.NoError(t, tr.SetCodecPreferences(nil)) + assert.NotEqual(t, 0, len(tr.getCodecs())) + + assert.NoError(t, tr.SetCodecPreferences([]RTPCodecParameters{})) + assert.NotEqual(t, 0, len(tr.getCodecs())) +} + +// Assert that SetCodecPreferences properly filters codecs and PayloadTypes are respected +func Test_RTPTransceiver_SetCodecPreferences_PayloadType(t *testing.T) { + testCodec := RTPCodecParameters{ + RTPCodecCapability: RTPCodecCapability{"video/testCodec", 90000, 0, "", nil}, + PayloadType: 50, + } + + m := &MediaEngine{} + assert.NoError(t, m.RegisterDefaultCodecs()) + + offerPC, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{}) + assert.NoError(t, err) + + assert.NoError(t, m.RegisterCodec(testCodec, RTPCodecTypeVideo)) + + answerPC, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{}) + assert.NoError(t, err) + + _, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo) + assert.NoError(t, err) + + answerTransceiver, err := answerPC.AddTransceiverFromKind(RTPCodecTypeVideo) + assert.NoError(t, err) + + assert.NoError(t, answerTransceiver.SetCodecPreferences([]RTPCodecParameters{ + testCodec, + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, + PayloadType: 51, + }, + })) + + offer, err := offerPC.CreateOffer(nil) + assert.NoError(t, err) + + assert.NoError(t, offerPC.SetLocalDescription(offer)) + assert.NoError(t, answerPC.SetRemoteDescription(offer)) + + answer, err := answerPC.CreateAnswer(nil) + assert.NoError(t, err) + + // VP8 with proper PayloadType + assert.NotEqual(t, -1, strings.Index(answer.SDP, "a=rtpmap:96 VP8/90000")) + + // testCodec is ignored since offerer doesn't support + assert.Equal(t, -1, strings.Index(answer.SDP, "testCodec")) + + closePairNow(t, offerPC, answerPC) +} diff --git a/sdp.go b/sdp.go index af4c3646..8aca0041 100644 --- a/sdp.go +++ b/sdp.go @@ -292,7 +292,7 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates b WithPropertyAttribute(sdp.AttrKeyRTCPMux). WithPropertyAttribute(sdp.AttrKeyRTCPRsize) - codecs := mediaEngine.getCodecsByKind(t.kind) + codecs := t.getCodecs() for _, codec := range codecs { name := strings.TrimPrefix(codec.MimeType, "audio/") name = strings.TrimPrefix(name, "video/") diff --git a/sdp_test.go b/sdp_test.go index 7bed5081..7a657bba 100644 --- a/sdp_test.go +++ b/sdp_test.go @@ -307,6 +307,8 @@ func TestMediaDescriptionFingerprints(t *testing.T) { engine := &MediaEngine{} assert.NoError(t, engine.RegisterDefaultCodecs()) + api := NewAPI(WithMediaEngine(engine)) + sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.NoError(t, err) @@ -317,13 +319,17 @@ func TestMediaDescriptionFingerprints(t *testing.T) { { id: "video", transceivers: []*RTPTransceiver{{ - kind: RTPCodecTypeVideo, + kind: RTPCodecTypeVideo, + api: api, + codecs: engine.getCodecsByKind(RTPCodecTypeVideo), }}, }, { id: "audio", transceivers: []*RTPTransceiver{{ - kind: RTPCodecTypeAudio, + kind: RTPCodecTypeAudio, + api: api, + codecs: engine.getCodecsByKind(RTPCodecTypeAudio), }}, }, { @@ -363,21 +369,22 @@ func TestMediaDescriptionFingerprints(t *testing.T) { func TestPopulateSDP(t *testing.T) { t.Run("Rid", func(t *testing.T) { - tr := &RTPTransceiver{kind: RTPCodecTypeVideo} + se := SettingEngine{} + + me := &MediaEngine{} + assert.NoError(t, me.RegisterDefaultCodecs()) + api := NewAPI(WithMediaEngine(me)) + + tr := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs} tr.setDirection(RTPTransceiverDirectionRecvonly) ridMap := map[string]string{ "ridkey": "some", } mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}, ridMap: ridMap}} - se := SettingEngine{} - - m := MediaEngine{} - assert.NoError(t, m.RegisterDefaultCodecs()) - d := &sdp.SessionDescription{} - offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, &m, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete) + offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete) assert.Nil(t, err) // Test contains rid map keys @@ -397,6 +404,50 @@ func TestPopulateSDP(t *testing.T) { } assert.Equal(t, true, found, "Rid key should be present") }) + t.Run("SetCodecPreferences", func(t *testing.T) { + se := SettingEngine{} + + me := &MediaEngine{} + assert.NoError(t, me.RegisterDefaultCodecs()) + api := NewAPI(WithMediaEngine(me)) + me.pushCodecs(me.videoCodecs, RTPCodecTypeVideo) + me.pushCodecs(me.audioCodecs, RTPCodecTypeAudio) + + tr := &RTPTransceiver{kind: RTPCodecTypeVideo, api: api, codecs: me.videoCodecs} + tr.setDirection(RTPTransceiverDirectionRecvonly) + codecErr := tr.SetCodecPreferences([]RTPCodecParameters{ + { + RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil}, + PayloadType: 96, + }, + }) + assert.NoError(t, codecErr) + + mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}}} + + d := &sdp.SessionDescription{} + + offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, me, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete) + assert.Nil(t, err) + + // Test codecs + foundVP8 := false + for _, desc := range offerSdp.MediaDescriptions { + if desc.MediaName.Media != "video" { + continue + } + for _, a := range desc.Attributes { + if strings.Contains(a.Key, "rtpmap") { + if a.Value == "98 VP9/90000" { + t.Fatal("vp9 should not be present in sdp") + } else if a.Value == "96 VP8/90000" { + foundVP8 = true + } + } + } + } + assert.Equal(t, true, foundVP8, "vp8 should be present in sdp") + }) } func TestGetRIDs(t *testing.T) {