mirror of
https://github.com/pion/webrtc.git
synced 2025-11-01 19:22:49 +08:00
Bolting on send side simulcast
Introduces AddEncoding method in RTP sender to add simulcast encodings. Added UTs for AddEncoding. Also modified the Simulcast send test to use the new API.
This commit is contained in:
@@ -142,6 +142,9 @@ var (
|
||||
// ErrRTPSenderNewTrackHasIncorrectKind indicates that the new track is of a different kind than the previous/original
|
||||
ErrRTPSenderNewTrackHasIncorrectKind = errors.New("new track must be of the same kind as previous")
|
||||
|
||||
// ErrRTPSenderNewTrackHasIncorrectEnvelope indicates that the new track has a different envelope than the previous/original
|
||||
ErrRTPSenderNewTrackHasIncorrectEnvelope = errors.New("new track must have the same envelope as previous")
|
||||
|
||||
// ErrUnbindFailed indicates that a TrackLocal was not able to be unbind
|
||||
ErrUnbindFailed = errors.New("failed to unbind TrackLocal from PeerConnection")
|
||||
|
||||
@@ -205,7 +208,13 @@ var (
|
||||
errRTPSenderTrackNil = errors.New("Track must not be nil")
|
||||
errRTPSenderDTLSTransportNil = errors.New("DTLSTransport must not be nil")
|
||||
errRTPSenderSendAlreadyCalled = errors.New("Send has already been called")
|
||||
errRTPSenderStopped = errors.New("Sender has already been stopped")
|
||||
errRTPSenderTrackRemoved = errors.New("Sender Track has been removed or replaced to nil")
|
||||
errRTPSenderRidNil = errors.New("Sender cannot add encoding as rid is empty")
|
||||
errRTPSenderNoBaseEncoding = errors.New("Sender cannot add encoding as there is no base track")
|
||||
errRTPSenderBaseEncodingMismatch = errors.New("Sender cannot add encoding as provided track does not match base track")
|
||||
errRTPSenderRIDCollision = errors.New("Sender cannot encoding due to RID collision")
|
||||
errRTPSenderNoTrackForRID = errors.New("Sender does not have track for RID")
|
||||
|
||||
errRTPTransceiverCannotChangeMid = errors.New("errRTPSenderTrackNil")
|
||||
errRTPTransceiverSetSendingInvalidState = errors.New("invalid state change in RTPTransceiver.setSending")
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(sender.ssrc), MediaSSRC: uint32(sender.ssrc)}}); routineErr != nil {
|
||||
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(sender.trackEncodings[0].ssrc), MediaSSRC: uint32(sender.trackEncodings[0].ssrc)}}); routineErr != nil {
|
||||
awaitRTCPSenderSend <- routineErr
|
||||
}
|
||||
|
||||
@@ -643,12 +643,12 @@ func TestAddTransceiverAddTrack_Reuse(t *testing.T) {
|
||||
track1, sender1 := addTrack()
|
||||
assert.Equal(t, 1, len(pc.GetTransceivers()))
|
||||
assert.Equal(t, sender1, tr.Sender())
|
||||
assert.Equal(t, track1, tr.Sender().track)
|
||||
assert.Equal(t, track1, tr.Sender().Track())
|
||||
require.NoError(t, pc.RemoveTrack(sender1))
|
||||
|
||||
track2, _ := addTrack()
|
||||
assert.Equal(t, 1, len(pc.GetTransceivers()))
|
||||
assert.Equal(t, track2, tr.Sender().track)
|
||||
assert.Equal(t, track2, tr.Sender().Track())
|
||||
|
||||
addTrack()
|
||||
assert.Equal(t, 2, len(pc.GetTransceivers()))
|
||||
@@ -1256,23 +1256,47 @@ func TestPeerConnection_Simulcast(t *testing.T) {
|
||||
pcOffer, pcAnswer, err := NewAPI(WithMediaEngine(m)).newPair(Configuration{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
vp8Writer, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2")
|
||||
vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("a"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = pcOffer.AddTrack(vp8Writer)
|
||||
sender, err := pcOffer.AddTrack(vp8WriterA)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, sender)
|
||||
|
||||
vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("b"))
|
||||
assert.NoError(t, err)
|
||||
err = sender.AddEncoding(vp8WriterB)
|
||||
assert.NoError(t, err)
|
||||
|
||||
vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID("c"))
|
||||
assert.NoError(t, err)
|
||||
err = sender.AddEncoding(vp8WriterC)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ridMap = map[string]int{}
|
||||
pcAnswer.OnTrack(onTrackHandler)
|
||||
|
||||
assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string {
|
||||
sessionDescription = strings.Split(sessionDescription, "a=end-of-candidates\r\n")[0]
|
||||
sessionDescription = filterSsrc(sessionDescription)
|
||||
for _, rid := range rids {
|
||||
sessionDescription += "a=" + sdpAttributeRid + ":" + rid + " send\r\n"
|
||||
parameters := sender.GetParameters()
|
||||
assert.Equal(t, "a", parameters.Encodings[0].RID)
|
||||
assert.Equal(t, "b", parameters.Encodings[1].RID)
|
||||
assert.Equal(t, "c", parameters.Encodings[2].RID)
|
||||
|
||||
var midID, ridID, rsidID uint8
|
||||
for _, extension := range parameters.HeaderExtensions {
|
||||
switch extension.URI {
|
||||
case sdp.SDESMidURI:
|
||||
midID = uint8(extension.ID)
|
||||
case sdp.SDESRTPStreamIDURI:
|
||||
ridID = uint8(extension.ID)
|
||||
case sdesRepairRTPStreamIDURI:
|
||||
rsidID = uint8(extension.ID)
|
||||
}
|
||||
return sessionDescription + "a=simulcast:send " + strings.Join(rids, ";") + "\r\n"
|
||||
}))
|
||||
}
|
||||
assert.NotZero(t, midID)
|
||||
assert.NotZero(t, ridID)
|
||||
assert.NotZero(t, rsidID)
|
||||
|
||||
assert.NoError(t, signalPair(pcOffer, pcAnswer))
|
||||
|
||||
for sequenceNumber := uint16(0); !ridsFullfilled(); sequenceNumber++ {
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
@@ -1284,17 +1308,26 @@ func TestPeerConnection_Simulcast(t *testing.T) {
|
||||
SequenceNumber: sequenceNumber,
|
||||
PayloadType: 96,
|
||||
}
|
||||
assert.NoError(t, header.SetExtension(1, []byte("0")))
|
||||
assert.NoError(t, header.SetExtension(midID, []byte("0")))
|
||||
|
||||
// Send RSID for first 10 packets
|
||||
if sequenceNumber >= 10 {
|
||||
assert.NoError(t, header.SetExtension(2, []byte(rid)))
|
||||
assert.NoError(t, header.SetExtension(ridID, []byte(rid)))
|
||||
} else {
|
||||
assert.NoError(t, header.SetExtension(3, []byte(rid)))
|
||||
assert.NoError(t, header.SetExtension(rsidID, []byte(rid)))
|
||||
header.SSRC += 10
|
||||
}
|
||||
|
||||
_, err := vp8Writer.bindings[0].writeStream.WriteRTP(header, []byte{0x00})
|
||||
var writer *TrackLocalStaticRTP
|
||||
switch rid {
|
||||
case "a":
|
||||
writer = vp8WriterA
|
||||
case "b":
|
||||
writer = vp8WriterB
|
||||
case "c":
|
||||
writer = vp8WriterC
|
||||
}
|
||||
_, err = writer.bindings[0].writeStream.WriteRTP(header, []byte{0x00})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,7 +371,7 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
|
||||
// Must have 3 media descriptions (2 video channels)
|
||||
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
|
||||
|
||||
assert.True(t, sdpMidHasSsrc(offer, "0", sender1.ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", sender1.ssrc, offer.SDP)
|
||||
assert.True(t, sdpMidHasSsrc(offer, "0", sender1.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", sender1.trackEncodings[0].ssrc, offer.SDP)
|
||||
|
||||
// Remove first track, must keep same number of media
|
||||
// descriptions and same track ssrc for mid 1 as previous
|
||||
@@ -382,7 +382,7 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
|
||||
|
||||
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
|
||||
|
||||
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", sender2.ssrc, offer.SDP)
|
||||
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP)
|
||||
|
||||
_, err = pcAnswer.CreateAnswer(nil)
|
||||
assert.Equal(t, err, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState})
|
||||
@@ -402,8 +402,8 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
|
||||
// We reuse the existing non-sending transceiver
|
||||
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
|
||||
|
||||
assert.True(t, sdpMidHasSsrc(offer, "0", sender3.ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", sender3.ssrc, offer.SDP)
|
||||
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", sender2.ssrc, offer.SDP)
|
||||
assert.True(t, sdpMidHasSsrc(offer, "0", sender3.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", sender3.trackEncodings[0].ssrc, offer.SDP)
|
||||
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP)
|
||||
|
||||
closePairNow(t, pcOffer, pcAnswer)
|
||||
}
|
||||
|
||||
239
rtpsender.go
239
rtpsender.go
@@ -4,6 +4,7 @@
|
||||
package webrtc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -12,23 +13,30 @@ import (
|
||||
"github.com/pion/randutil"
|
||||
"github.com/pion/rtcp"
|
||||
"github.com/pion/rtp"
|
||||
"github.com/pion/webrtc/v3/internal/util"
|
||||
)
|
||||
|
||||
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
||||
type RTPSender struct {
|
||||
type trackEncoding struct {
|
||||
track TrackLocal
|
||||
|
||||
srtpStream *srtpWriterFuture
|
||||
|
||||
rtcpInterceptor interceptor.RTCPReader
|
||||
streamInfo interceptor.StreamInfo
|
||||
|
||||
context TrackLocalContext
|
||||
|
||||
ssrc SSRC
|
||||
}
|
||||
|
||||
// RTPSender allows an application to control how a given Track is encoded and transmitted to a remote peer
|
||||
type RTPSender struct {
|
||||
trackEncodings []*trackEncoding
|
||||
|
||||
transport *DTLSTransport
|
||||
|
||||
payloadType PayloadType
|
||||
kind RTPCodecType
|
||||
ssrc SSRC
|
||||
|
||||
// nolint:godox
|
||||
// TODO(sgotti) remove this when in future we'll avoid replacing
|
||||
@@ -60,23 +68,15 @@ func (api *API) NewRTPSender(track TrackLocal, transport *DTLSTransport) (*RTPSe
|
||||
}
|
||||
|
||||
r := &RTPSender{
|
||||
track: track,
|
||||
transport: transport,
|
||||
api: api,
|
||||
sendCalled: make(chan struct{}),
|
||||
stopCalled: make(chan struct{}),
|
||||
ssrc: SSRC(randutil.NewMathRandomGenerator().Uint32()),
|
||||
id: id,
|
||||
srtpStream: &srtpWriterFuture{},
|
||||
kind: track.Kind(),
|
||||
}
|
||||
|
||||
r.srtpStream.rtpSender = r
|
||||
|
||||
r.rtcpInterceptor = r.api.interceptor.BindRTCPReader(interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
|
||||
n, err = r.srtpStream.Read(in)
|
||||
return n, a, err
|
||||
}))
|
||||
r.addEncoding(track)
|
||||
|
||||
return r, nil
|
||||
}
|
||||
@@ -108,24 +108,26 @@ func (r *RTPSender) Transport() *DTLSTransport {
|
||||
}
|
||||
|
||||
func (r *RTPSender) getParameters() RTPSendParameters {
|
||||
var encodings []RTPEncodingParameters
|
||||
for _, trackEncoding := range r.trackEncodings {
|
||||
var rid string
|
||||
if r.track != nil {
|
||||
rid = r.track.RID()
|
||||
if trackEncoding.track != nil {
|
||||
rid = trackEncoding.track.RID()
|
||||
}
|
||||
encodings = append(encodings, RTPEncodingParameters{
|
||||
RTPCodingParameters: RTPCodingParameters{
|
||||
RID: rid,
|
||||
SSRC: trackEncoding.ssrc,
|
||||
PayloadType: r.payloadType,
|
||||
},
|
||||
})
|
||||
}
|
||||
sendParameters := RTPSendParameters{
|
||||
RTPParameters: r.api.mediaEngine.getRTPParametersByKind(
|
||||
r.kind,
|
||||
[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
|
||||
),
|
||||
Encodings: []RTPEncodingParameters{
|
||||
{
|
||||
RTPCodingParameters: RTPCodingParameters{
|
||||
RID: rid,
|
||||
SSRC: r.ssrc,
|
||||
PayloadType: r.payloadType,
|
||||
},
|
||||
},
|
||||
},
|
||||
Encodings: encodings,
|
||||
}
|
||||
if r.rtpTransceiver != nil {
|
||||
sendParameters.Codecs = r.rtpTransceiver.getCodecs()
|
||||
@@ -143,11 +145,81 @@ func (r *RTPSender) GetParameters() RTPSendParameters {
|
||||
return r.getParameters()
|
||||
}
|
||||
|
||||
// AddEncoding adds an encoding to RTPSender. Used by simulcast senders.
|
||||
func (r *RTPSender) AddEncoding(track TrackLocal) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
if track == nil {
|
||||
return errRTPSenderTrackNil
|
||||
}
|
||||
|
||||
if track.RID() == "" {
|
||||
return errRTPSenderRidNil
|
||||
}
|
||||
|
||||
if r.hasStopped() {
|
||||
return errRTPSenderStopped
|
||||
}
|
||||
|
||||
if r.hasSent() {
|
||||
return errRTPSenderSendAlreadyCalled
|
||||
}
|
||||
|
||||
var refTrack TrackLocal
|
||||
if len(r.trackEncodings) != 0 {
|
||||
refTrack = r.trackEncodings[0].track
|
||||
}
|
||||
if refTrack == nil || refTrack.RID() == "" {
|
||||
return errRTPSenderNoBaseEncoding
|
||||
}
|
||||
|
||||
if refTrack.ID() != track.ID() || refTrack.StreamID() != track.StreamID() || refTrack.Kind() != track.Kind() {
|
||||
return errRTPSenderBaseEncodingMismatch
|
||||
}
|
||||
|
||||
for _, encoding := range r.trackEncodings {
|
||||
if encoding.track == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if encoding.track.RID() == track.RID() {
|
||||
return errRTPSenderRIDCollision
|
||||
}
|
||||
}
|
||||
|
||||
r.addEncoding(track)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RTPSender) addEncoding(track TrackLocal) {
|
||||
ssrc := SSRC(randutil.NewMathRandomGenerator().Uint32())
|
||||
trackEncoding := &trackEncoding{
|
||||
track: track,
|
||||
srtpStream: &srtpWriterFuture{ssrc: ssrc},
|
||||
ssrc: ssrc,
|
||||
}
|
||||
trackEncoding.srtpStream.rtpSender = r
|
||||
trackEncoding.rtcpInterceptor = r.api.interceptor.BindRTCPReader(
|
||||
interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
|
||||
n, err = trackEncoding.srtpStream.Read(in)
|
||||
return n, a, err
|
||||
}),
|
||||
)
|
||||
|
||||
r.trackEncodings = append(r.trackEncodings, trackEncoding)
|
||||
}
|
||||
|
||||
// Track returns the RTCRtpTransceiver track, or nil
|
||||
func (r *RTPSender) Track() TrackLocal {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.track
|
||||
|
||||
if len(r.trackEncodings) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return r.trackEncodings[0].track
|
||||
}
|
||||
|
||||
// ReplaceTrack replaces the track currently being used as the sender's source with a new TrackLocal.
|
||||
@@ -161,26 +233,38 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
||||
return ErrRTPSenderNewTrackHasIncorrectKind
|
||||
}
|
||||
|
||||
if r.hasSent() && r.track != nil {
|
||||
if err := r.track.Unbind(r.context); err != nil {
|
||||
// cannot replace simulcast envelope
|
||||
if track != nil && len(r.trackEncodings) > 1 {
|
||||
return ErrRTPSenderNewTrackHasIncorrectEnvelope
|
||||
}
|
||||
|
||||
var replacedTrack TrackLocal
|
||||
var context *TrackLocalContext
|
||||
if len(r.trackEncodings) != 0 {
|
||||
replacedTrack = r.trackEncodings[0].track
|
||||
context = &r.trackEncodings[0].context
|
||||
}
|
||||
if r.hasSent() && replacedTrack != nil {
|
||||
if err := replacedTrack.Unbind(*context); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.hasSent() || track == nil {
|
||||
r.track = track
|
||||
r.trackEncodings[0].track = track
|
||||
return nil
|
||||
}
|
||||
|
||||
codec, err := track.Bind(TrackLocalContext{
|
||||
id: r.context.id,
|
||||
id: context.id,
|
||||
params: r.api.mediaEngine.getRTPParametersByKind(track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||
ssrc: r.context.ssrc,
|
||||
writeStream: r.context.writeStream,
|
||||
ssrc: context.ssrc,
|
||||
writeStream: context.writeStream,
|
||||
rtcpInterceptor: context.rtcpInterceptor,
|
||||
})
|
||||
if err != nil {
|
||||
// Re-bind the original track
|
||||
if _, reBindErr := r.track.Bind(r.context); reBindErr != nil {
|
||||
if _, reBindErr := replacedTrack.Bind(*context); reBindErr != nil {
|
||||
return reBindErr
|
||||
}
|
||||
|
||||
@@ -189,10 +273,10 @@ func (r *RTPSender) ReplaceTrack(track TrackLocal) error {
|
||||
|
||||
// Codec has changed
|
||||
if r.payloadType != codec.PayloadType {
|
||||
r.context.params.Codecs = []RTPCodecParameters{codec}
|
||||
context.params.Codecs = []RTPCodecParameters{codec}
|
||||
}
|
||||
|
||||
r.track = track
|
||||
r.trackEncodings[0].track = track
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -204,29 +288,42 @@ func (r *RTPSender) Send(parameters RTPSendParameters) error {
|
||||
switch {
|
||||
case r.hasSent():
|
||||
return errRTPSenderSendAlreadyCalled
|
||||
case r.track == nil:
|
||||
case r.trackEncodings[0].track == nil:
|
||||
return errRTPSenderTrackRemoved
|
||||
}
|
||||
|
||||
for idx, trackEncoding := range r.trackEncodings {
|
||||
writeStream := &interceptorToTrackLocalWriter{}
|
||||
r.context = TrackLocalContext{
|
||||
trackEncoding.context = TrackLocalContext{
|
||||
id: r.id,
|
||||
params: r.api.mediaEngine.getRTPParametersByKind(r.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||
ssrc: parameters.Encodings[0].SSRC,
|
||||
params: r.api.mediaEngine.getRTPParametersByKind(trackEncoding.track.Kind(), []RTPTransceiverDirection{RTPTransceiverDirectionSendonly}),
|
||||
ssrc: parameters.Encodings[idx].SSRC,
|
||||
writeStream: writeStream,
|
||||
rtcpInterceptor: trackEncoding.rtcpInterceptor,
|
||||
}
|
||||
|
||||
codec, err := r.track.Bind(r.context)
|
||||
codec, err := trackEncoding.track.Bind(trackEncoding.context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.context.params.Codecs = []RTPCodecParameters{codec}
|
||||
trackEncoding.context.params.Codecs = []RTPCodecParameters{codec}
|
||||
|
||||
r.streamInfo = *createStreamInfo(r.id, parameters.Encodings[0].SSRC, codec.PayloadType, codec.RTPCodecCapability, parameters.HeaderExtensions)
|
||||
rtpInterceptor := r.api.interceptor.BindLocalStream(&r.streamInfo, interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
|
||||
return r.srtpStream.WriteRTP(header, payload)
|
||||
}))
|
||||
trackEncoding.streamInfo = *createStreamInfo(
|
||||
r.id,
|
||||
parameters.Encodings[idx].SSRC,
|
||||
codec.PayloadType,
|
||||
codec.RTPCodecCapability,
|
||||
parameters.HeaderExtensions,
|
||||
)
|
||||
srtpStream := trackEncoding.srtpStream
|
||||
rtpInterceptor := r.api.interceptor.BindLocalStream(
|
||||
&trackEncoding.streamInfo,
|
||||
interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
|
||||
return srtpStream.WriteRTP(header, payload)
|
||||
}),
|
||||
)
|
||||
writeStream.interceptor.Store(rtpInterceptor)
|
||||
}
|
||||
|
||||
close(r.sendCalled)
|
||||
return nil
|
||||
@@ -252,16 +349,20 @@ func (r *RTPSender) Stop() error {
|
||||
return err
|
||||
}
|
||||
|
||||
r.api.interceptor.UnbindLocalStream(&r.streamInfo)
|
||||
|
||||
return r.srtpStream.Close()
|
||||
errs := []error{}
|
||||
for _, trackEncoding := range r.trackEncodings {
|
||||
r.api.interceptor.UnbindLocalStream(&trackEncoding.streamInfo)
|
||||
errs = append(errs, trackEncoding.srtpStream.Close())
|
||||
}
|
||||
|
||||
// Read reads incoming RTCP for this RTPReceiver
|
||||
return util.FlattenErrs(errs)
|
||||
}
|
||||
|
||||
// Read reads incoming RTCP for this RTPSender
|
||||
func (r *RTPSender) Read(b []byte) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.sendCalled:
|
||||
return r.rtcpInterceptor.Read(b, a)
|
||||
return r.trackEncodings[0].rtcpInterceptor.Read(b, a)
|
||||
case <-r.stopCalled:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
@@ -283,10 +384,50 @@ func (r *RTPSender) ReadRTCP() ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
return pkts, attributes, nil
|
||||
}
|
||||
|
||||
// ReadSimulcast reads incoming RTCP for this RTPSender for given rid
|
||||
func (r *RTPSender) ReadSimulcast(b []byte, rid string) (n int, a interceptor.Attributes, err error) {
|
||||
select {
|
||||
case <-r.sendCalled:
|
||||
for _, t := range r.trackEncodings {
|
||||
if t.track != nil && t.track.RID() == rid {
|
||||
return t.rtcpInterceptor.Read(b, a)
|
||||
}
|
||||
}
|
||||
return 0, nil, fmt.Errorf("%w: %s", errRTPSenderNoTrackForRID, rid)
|
||||
case <-r.stopCalled:
|
||||
return 0, nil, io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// ReadSimulcastRTCP is a convenience method that wraps ReadSimulcast and unmarshal for you
|
||||
func (r *RTPSender) ReadSimulcastRTCP(rid string) ([]rtcp.Packet, interceptor.Attributes, error) {
|
||||
b := make([]byte, r.api.settingEngine.getReceiveMTU())
|
||||
i, attributes, err := r.ReadSimulcast(b, rid)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
pkts, err := rtcp.Unmarshal(b[:i])
|
||||
return pkts, attributes, err
|
||||
}
|
||||
|
||||
// SetReadDeadline sets the deadline for the Read operation.
|
||||
// Setting to zero means no deadline.
|
||||
func (r *RTPSender) SetReadDeadline(t time.Time) error {
|
||||
return r.srtpStream.SetReadDeadline(t)
|
||||
return r.trackEncodings[0].srtpStream.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// SetReadDeadlineSimulcast sets the max amount of time the RTCP stream for a given rid will block before returning. 0 is forever.
|
||||
func (r *RTPSender) SetReadDeadlineSimulcast(deadline time.Time, rid string) error {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
|
||||
for _, t := range r.trackEncodings {
|
||||
if t.track != nil && t.track.RID() == rid {
|
||||
return t.srtpStream.SetReadDeadline(deadline)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%w: %s", errRTPSenderNoTrackForRID, rid)
|
||||
}
|
||||
|
||||
// hasSent tells if data has been ever sent for this instance
|
||||
|
||||
@@ -117,7 +117,7 @@ func Test_RTPSender_GetParameters(t *testing.T) {
|
||||
parameters := rtpTransceiver.Sender().GetParameters()
|
||||
assert.NotEqual(t, 0, len(parameters.Codecs))
|
||||
assert.Equal(t, 1, len(parameters.Encodings))
|
||||
assert.Equal(t, rtpTransceiver.Sender().ssrc, parameters.Encodings[0].SSRC)
|
||||
assert.Equal(t, rtpTransceiver.Sender().trackEncodings[0].ssrc, parameters.Encodings[0].SSRC)
|
||||
assert.Equal(t, "", parameters.Encodings[0].RID)
|
||||
|
||||
closePairNow(t, offerer, answerer)
|
||||
@@ -340,3 +340,64 @@ func Test_RTPSender_Send_Track_Removed(t *testing.T) {
|
||||
|
||||
assert.NoError(t, peerConnection.Close())
|
||||
}
|
||||
|
||||
func Test_RTPSender_Add_Encoding(t *testing.T) {
|
||||
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||
assert.NoError(t, err)
|
||||
|
||||
peerConnection, err := NewPeerConnection(Configuration{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
rtpSender, err := peerConnection.AddTrack(track)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, errRTPSenderTrackNil, rtpSender.AddEncoding(nil))
|
||||
|
||||
track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderRidNil, rtpSender.AddEncoding(track1))
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("h"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderNoBaseEncoding, rtpSender.AddEncoding(track1))
|
||||
|
||||
track, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("q"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
rtpSender, err = peerConnection.AddTrack(track)
|
||||
assert.NoError(t, err)
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video1", "pion", WithRTPStreamID("h"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderBaseEncodingMismatch, rtpSender.AddEncoding(track1))
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1", WithRTPStreamID("h"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderBaseEncodingMismatch, rtpSender.AddEncoding(track1))
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeOpus}, "video", "pion", WithRTPStreamID("h"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderBaseEncodingMismatch, rtpSender.AddEncoding(track1))
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("q"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderRIDCollision, rtpSender.AddEncoding(track1))
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("h"))
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, rtpSender.AddEncoding(track1))
|
||||
|
||||
err = rtpSender.Send(rtpSender.GetParameters())
|
||||
assert.NoError(t, err)
|
||||
|
||||
track1, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("f"))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, errRTPSenderSendAlreadyCalled, rtpSender.AddEncoding(track1))
|
||||
|
||||
err = rtpSender.Stop()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, errRTPSenderStopped, rtpSender.AddEncoding(track1))
|
||||
|
||||
assert.NoError(t, peerConnection.Close())
|
||||
}
|
||||
|
||||
66
sdp.go
66
sdp.go
@@ -340,7 +340,60 @@ func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGathe
|
||||
}
|
||||
}
|
||||
|
||||
func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, mediaSection mediaSection) (bool, error) {
|
||||
func addSenderSDP(
|
||||
mediaSection mediaSection,
|
||||
isPlanB bool,
|
||||
media *sdp.MediaDescription,
|
||||
) {
|
||||
for _, mt := range mediaSection.transceivers {
|
||||
sender := mt.Sender()
|
||||
if sender == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
track := sender.Track()
|
||||
if track == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sendParameters := sender.GetParameters()
|
||||
for _, encoding := range sendParameters.Encodings {
|
||||
media = media.WithMediaSource(uint32(encoding.SSRC), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
|
||||
if !isPlanB {
|
||||
media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
|
||||
}
|
||||
}
|
||||
|
||||
if len(sendParameters.Encodings) > 1 {
|
||||
sendRids := make([]string, 0, len(sendParameters.Encodings))
|
||||
|
||||
for _, encoding := range sendParameters.Encodings {
|
||||
media.WithValueAttribute(sdpAttributeRid, encoding.RID+" send")
|
||||
sendRids = append(sendRids, encoding.RID)
|
||||
}
|
||||
// Simulcast
|
||||
media.WithValueAttribute("simulcast", "send "+strings.Join(sendRids, ";"))
|
||||
}
|
||||
|
||||
if !isPlanB {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addTransceiverSDP(
|
||||
d *sdp.SessionDescription,
|
||||
isPlanB bool,
|
||||
shouldAddCandidates bool,
|
||||
dtlsFingerprints []DTLSFingerprint,
|
||||
mediaEngine *MediaEngine,
|
||||
midValue string,
|
||||
iceParams ICEParameters,
|
||||
candidates []ICECandidate,
|
||||
dtlsRole sdp.ConnectionRole,
|
||||
iceGatheringState ICEGatheringState,
|
||||
mediaSection mediaSection,
|
||||
) (bool, error) {
|
||||
transceivers := mediaSection.transceivers
|
||||
if len(transceivers) < 1 {
|
||||
return false, errSDPZeroTransceivers
|
||||
@@ -410,16 +463,7 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB, shouldAddCandidates b
|
||||
media.WithValueAttribute("simulcast", "recv "+strings.Join(recvRids, ";"))
|
||||
}
|
||||
|
||||
for _, mt := range transceivers {
|
||||
if sender := mt.Sender(); sender != nil && sender.Track() != nil {
|
||||
track := sender.Track()
|
||||
media = media.WithMediaSource(uint32(sender.ssrc), track.StreamID() /* cname */, track.StreamID() /* streamLabel */, track.ID())
|
||||
if !isPlanB {
|
||||
media = media.WithPropertyAttribute("msid:" + track.StreamID() + " " + track.ID())
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
addSenderSDP(mediaSection, isPlanB, media)
|
||||
|
||||
media = media.WithPropertyAttribute(t.Direction().String())
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
// srtpWriterFuture blocks Read/Write calls until
|
||||
// the SRTP Session is available
|
||||
type srtpWriterFuture struct {
|
||||
ssrc SSRC
|
||||
rtpSender *RTPSender
|
||||
rtcpReadStream atomic.Value // *srtp.ReadStreamSRTCP
|
||||
rtpWriteStream atomic.Value // *srtp.WriteStreamSRTP
|
||||
@@ -52,7 +53,7 @@ func (s *srtpWriterFuture) init(returnWhenNoSRTP bool) error {
|
||||
return err
|
||||
}
|
||||
|
||||
rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(s.rtpSender.ssrc))
|
||||
rtcpReadStream, err := srtcpSession.OpenReadStream(uint32(s.ssrc))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package webrtc
|
||||
|
||||
import "github.com/pion/rtp"
|
||||
import (
|
||||
"github.com/pion/interceptor"
|
||||
"github.com/pion/rtp"
|
||||
)
|
||||
|
||||
// TrackLocalWriter is the Writer for outbound RTP Packets
|
||||
type TrackLocalWriter interface {
|
||||
@@ -18,6 +21,7 @@ type TrackLocalContext struct {
|
||||
params RTPParameters
|
||||
ssrc SSRC
|
||||
writeStream TrackLocalWriter
|
||||
rtcpInterceptor interceptor.RTCPReader
|
||||
}
|
||||
|
||||
// CodecParameters returns the negotiated RTPCodecParameters. These are the codecs supported by both
|
||||
@@ -49,6 +53,11 @@ func (t *TrackLocalContext) ID() string {
|
||||
return t.id
|
||||
}
|
||||
|
||||
// RTCPReader returns the RTCP interceptor for this TrackLocal. Used to read RTCP of this TrackLocal.
|
||||
func (t *TrackLocalContext) RTCPReader() interceptor.RTCPReader {
|
||||
return t.rtcpInterceptor
|
||||
}
|
||||
|
||||
// TrackLocal is an interface that controls how the user can send media
|
||||
// The user can provide their own TrackLocal implementations, or use
|
||||
// the implementations in pkg/media
|
||||
|
||||
Reference in New Issue
Block a user