|
|
@ -14,12 +14,12 @@ import ( |
|
|
|
"sync" |
|
|
|
"sync" |
|
|
|
"time" |
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
|
|
"github.com/aler9/gortsplib/v2/pkg/format" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/formats" |
|
|
|
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtph264" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtph264" |
|
|
|
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpvp8" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp8" |
|
|
|
"github.com/aler9/gortsplib/v2/pkg/formatdecenc/rtpvp9" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/formats/rtpvp9" |
|
|
|
"github.com/aler9/gortsplib/v2/pkg/media" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/media" |
|
|
|
"github.com/aler9/gortsplib/v2/pkg/ringbuffer" |
|
|
|
"github.com/bluenviron/gortsplib/v3/pkg/ringbuffer" |
|
|
|
"github.com/google/uuid" |
|
|
|
"github.com/google/uuid" |
|
|
|
"github.com/pion/ice/v2" |
|
|
|
"github.com/pion/ice/v2" |
|
|
|
"github.com/pion/interceptor" |
|
|
|
"github.com/pion/interceptor" |
|
|
@ -64,7 +64,7 @@ func newPeerConnection(configuration webrtc.Configuration, |
|
|
|
|
|
|
|
|
|
|
|
type webRTCTrack struct { |
|
|
|
type webRTCTrack struct { |
|
|
|
media *media.Media |
|
|
|
media *media.Media |
|
|
|
format format.Format |
|
|
|
format formats.Format |
|
|
|
webRTCTrack *webrtc.TrackLocalStaticRTP |
|
|
|
webRTCTrack *webrtc.TrackLocalStaticRTP |
|
|
|
cb func(formatprocessor.Unit, context.Context, chan error) |
|
|
|
cb func(formatprocessor.Unit, context.Context, chan error) |
|
|
|
} |
|
|
|
} |
|
|
@ -306,11 +306,31 @@ func (c *webRTCConn) runInner(ctx context.Context) error { |
|
|
|
path.readerRemove(pathReaderRemoveReq{author: c}) |
|
|
|
path.readerRemove(pathReaderRemoveReq{author: c}) |
|
|
|
}() |
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
tracks, err := c.allocateTracks(res.stream.medias()) |
|
|
|
var tracks []*webRTCTrack |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
videoTrack, err := c.createVideoTrack(res.stream.medias()) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if videoTrack != nil { |
|
|
|
|
|
|
|
tracks = append(tracks, videoTrack) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
audioTrack, err := c.createAudioTrack(res.stream.medias()) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if audioTrack != nil { |
|
|
|
|
|
|
|
tracks = append(tracks, audioTrack) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if tracks == nil { |
|
|
|
|
|
|
|
return fmt.Errorf( |
|
|
|
|
|
|
|
"the stream doesn't contain any supported codec (which are currently VP9, VP8, H264, Opus, G722, G711)") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
err = c.wsconn.WriteJSON(c.genICEServers()) |
|
|
|
err = c.wsconn.WriteJSON(c.genICEServers()) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return err |
|
|
|
return err |
|
|
@ -538,10 +558,8 @@ outer: |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *webRTCConn) allocateTracks(medias media.Medias) ([]*webRTCTrack, error) { |
|
|
|
func (c *webRTCConn) createVideoTrack(medias media.Medias) (*webRTCTrack, error) { |
|
|
|
var ret []*webRTCTrack |
|
|
|
var vp9Format *formats.VP9 |
|
|
|
|
|
|
|
|
|
|
|
var vp9Format *format.VP9 |
|
|
|
|
|
|
|
vp9Media := medias.FindFormat(&vp9Format) |
|
|
|
vp9Media := medias.FindFormat(&vp9Format) |
|
|
|
|
|
|
|
|
|
|
|
if vp9Format != nil { |
|
|
|
if vp9Format != nil { |
|
|
@ -563,7 +581,7 @@ func (c *webRTCConn) allocateTracks(medias media.Medias) ([]*webRTCTrack, error) |
|
|
|
} |
|
|
|
} |
|
|
|
encoder.Init() |
|
|
|
encoder.Init() |
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
return &webRTCTrack{ |
|
|
|
media: vp9Media, |
|
|
|
media: vp9Media, |
|
|
|
format: vp9Format, |
|
|
|
format: vp9Format, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
@ -583,122 +601,121 @@ func (c *webRTCConn) allocateTracks(medias media.Medias) ([]*webRTCTrack, error) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var vp8Format *format.VP8 |
|
|
|
var vp8Format *formats.VP8 |
|
|
|
|
|
|
|
vp8Media := medias.FindFormat(&vp8Format) |
|
|
|
if vp9Format == nil { |
|
|
|
|
|
|
|
vp8Media := medias.FindFormat(&vp8Format) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if vp8Format != nil { |
|
|
|
if vp8Format != nil { |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
MimeType: webrtc.MimeTypeVP8, |
|
|
|
MimeType: webrtc.MimeTypeVP8, |
|
|
|
ClockRate: uint32(vp8Format.ClockRate()), |
|
|
|
ClockRate: uint32(vp8Format.ClockRate()), |
|
|
|
}, |
|
|
|
}, |
|
|
|
"vp8", |
|
|
|
"vp8", |
|
|
|
"rtspss", |
|
|
|
"rtspss", |
|
|
|
) |
|
|
|
) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
encoder := &rtpvp8.Encoder{ |
|
|
|
encoder := &rtpvp8.Encoder{ |
|
|
|
PayloadType: 96, |
|
|
|
PayloadType: 96, |
|
|
|
PayloadMaxSize: webrtcPayloadMaxSize, |
|
|
|
PayloadMaxSize: webrtcPayloadMaxSize, |
|
|
|
} |
|
|
|
} |
|
|
|
encoder.Init() |
|
|
|
encoder.Init() |
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
return &webRTCTrack{ |
|
|
|
media: vp8Media, |
|
|
|
media: vp8Media, |
|
|
|
format: vp8Format, |
|
|
|
format: vp8Format, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
tunit := unit.(*formatprocessor.UnitVP8) |
|
|
|
tunit := unit.(*formatprocessor.UnitVP8) |
|
|
|
|
|
|
|
|
|
|
|
if tunit.Frame == nil { |
|
|
|
if tunit.Frame == nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
packets, err := encoder.Encode(tunit.Frame, tunit.PTS) |
|
|
|
packets, err := encoder.Encode(tunit.Frame, tunit.PTS) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for _, pkt := range packets { |
|
|
|
for _, pkt := range packets { |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}, nil |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if vp9Format == nil && vp8Format == nil { |
|
|
|
var h264Format *formats.H264 |
|
|
|
var h264Format *format.H264 |
|
|
|
h264Media := medias.FindFormat(&h264Format) |
|
|
|
h264Media := medias.FindFormat(&h264Format) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if h264Format != nil { |
|
|
|
if h264Format != nil { |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
MimeType: webrtc.MimeTypeH264, |
|
|
|
MimeType: webrtc.MimeTypeH264, |
|
|
|
ClockRate: uint32(h264Format.ClockRate()), |
|
|
|
ClockRate: uint32(h264Format.ClockRate()), |
|
|
|
}, |
|
|
|
}, |
|
|
|
"h264", |
|
|
|
"h264", |
|
|
|
"rtspss", |
|
|
|
"rtspss", |
|
|
|
) |
|
|
|
) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
encoder := &rtph264.Encoder{ |
|
|
|
encoder := &rtph264.Encoder{ |
|
|
|
PayloadType: 96, |
|
|
|
PayloadType: 96, |
|
|
|
PayloadMaxSize: webrtcPayloadMaxSize, |
|
|
|
PayloadMaxSize: webrtcPayloadMaxSize, |
|
|
|
} |
|
|
|
} |
|
|
|
encoder.Init() |
|
|
|
encoder.Init() |
|
|
|
|
|
|
|
|
|
|
|
var lastPTS time.Duration |
|
|
|
var lastPTS time.Duration |
|
|
|
firstNALUReceived := false |
|
|
|
firstNALUReceived := false |
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
return &webRTCTrack{ |
|
|
|
media: h264Media, |
|
|
|
media: h264Media, |
|
|
|
format: h264Format, |
|
|
|
format: h264Format, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
tunit := unit.(*formatprocessor.UnitH264) |
|
|
|
tunit := unit.(*formatprocessor.UnitH264) |
|
|
|
|
|
|
|
|
|
|
|
if tunit.AU == nil { |
|
|
|
if tunit.AU == nil { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if !firstNALUReceived { |
|
|
|
if !firstNALUReceived { |
|
|
|
firstNALUReceived = true |
|
|
|
firstNALUReceived = true |
|
|
|
lastPTS = tunit.PTS |
|
|
|
lastPTS = tunit.PTS |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
if tunit.PTS < lastPTS { |
|
|
|
if tunit.PTS < lastPTS { |
|
|
|
select { |
|
|
|
select { |
|
|
|
case writeError <- fmt.Errorf("WebRTC doesn't support H264 streams with B-frames"): |
|
|
|
case writeError <- fmt.Errorf("WebRTC doesn't support H264 streams with B-frames"): |
|
|
|
case <-ctx.Done(): |
|
|
|
case <-ctx.Done(): |
|
|
|
} |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
lastPTS = tunit.PTS |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
packets, err := encoder.Encode(tunit.AU, tunit.PTS) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
lastPTS = tunit.PTS |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
for _, pkt := range packets { |
|
|
|
packets, err := encoder.Encode(tunit.AU, tunit.PTS) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
if err != nil { |
|
|
|
} |
|
|
|
return |
|
|
|
}, |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
for _, pkt := range packets { |
|
|
|
|
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var opusFormat *format.Opus |
|
|
|
return nil, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (c *webRTCConn) createAudioTrack(medias media.Medias) (*webRTCTrack, error) { |
|
|
|
|
|
|
|
var opusFormat *formats.Opus |
|
|
|
opusMedia := medias.FindFormat(&opusFormat) |
|
|
|
opusMedia := medias.FindFormat(&opusFormat) |
|
|
|
|
|
|
|
|
|
|
|
if opusFormat != nil { |
|
|
|
if opusFormat != nil { |
|
|
@ -714,7 +731,7 @@ func (c *webRTCConn) allocateTracks(medias media.Medias) ([]*webRTCTrack, error) |
|
|
|
return nil, err |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
return &webRTCTrack{ |
|
|
|
media: opusMedia, |
|
|
|
media: opusMedia, |
|
|
|
format: opusFormat, |
|
|
|
format: opusFormat, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
@ -723,84 +740,73 @@ func (c *webRTCConn) allocateTracks(medias media.Medias) ([]*webRTCTrack, error) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}) |
|
|
|
}, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var g722Format *format.G722 |
|
|
|
var g722Format *formats.G722 |
|
|
|
|
|
|
|
g722Media := medias.FindFormat(&g722Format) |
|
|
|
if opusFormat == nil { |
|
|
|
|
|
|
|
g722Media := medias.FindFormat(&g722Format) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if g722Format != nil { |
|
|
|
|
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
|
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
|
|
|
|
MimeType: webrtc.MimeTypeG722, |
|
|
|
|
|
|
|
ClockRate: uint32(g722Format.ClockRate()), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
"g722", |
|
|
|
|
|
|
|
"rtspss", |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
if g722Format != nil { |
|
|
|
media: g722Media, |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
format: g722Format, |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
MimeType: webrtc.MimeTypeG722, |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
ClockRate: uint32(g722Format.ClockRate()), |
|
|
|
for _, pkt := range unit.GetRTPPackets() { |
|
|
|
}, |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
"g722", |
|
|
|
} |
|
|
|
"rtspss", |
|
|
|
}, |
|
|
|
) |
|
|
|
}) |
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var g711Format *format.G711 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if opusFormat == nil && g722Format == nil { |
|
|
|
return &webRTCTrack{ |
|
|
|
g711Media := medias.FindFormat(&g711Format) |
|
|
|
media: g722Media, |
|
|
|
|
|
|
|
format: g722Format, |
|
|
|
|
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
|
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
|
|
|
|
for _, pkt := range unit.GetRTPPackets() { |
|
|
|
|
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if g711Format != nil { |
|
|
|
var g711Format *formats.G711 |
|
|
|
var mtyp string |
|
|
|
g711Media := medias.FindFormat(&g711Format) |
|
|
|
if g711Format.MULaw { |
|
|
|
|
|
|
|
mtyp = webrtc.MimeTypePCMU |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
mtyp = webrtc.MimeTypePCMA |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
if g711Format != nil { |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
var mtyp string |
|
|
|
MimeType: mtyp, |
|
|
|
if g711Format.MULaw { |
|
|
|
ClockRate: uint32(g711Format.ClockRate()), |
|
|
|
mtyp = webrtc.MimeTypePCMU |
|
|
|
}, |
|
|
|
} else { |
|
|
|
"g711", |
|
|
|
mtyp = webrtc.MimeTypePCMA |
|
|
|
"rtspss", |
|
|
|
} |
|
|
|
) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ret = append(ret, &webRTCTrack{ |
|
|
|
webRTCTrak, err := webrtc.NewTrackLocalStaticRTP( |
|
|
|
media: g711Media, |
|
|
|
webrtc.RTPCodecCapability{ |
|
|
|
format: g711Format, |
|
|
|
MimeType: mtyp, |
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
ClockRate: uint32(g711Format.ClockRate()), |
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
}, |
|
|
|
for _, pkt := range unit.GetRTPPackets() { |
|
|
|
"g711", |
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
"rtspss", |
|
|
|
} |
|
|
|
) |
|
|
|
}, |
|
|
|
if err != nil { |
|
|
|
}) |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ret == nil { |
|
|
|
return &webRTCTrack{ |
|
|
|
return nil, fmt.Errorf( |
|
|
|
media: g711Media, |
|
|
|
"the stream doesn't contain any supported codec (which are currently VP9, VP8, H264, Opus, G722, G711)") |
|
|
|
format: g711Format, |
|
|
|
|
|
|
|
webRTCTrack: webRTCTrak, |
|
|
|
|
|
|
|
cb: func(unit formatprocessor.Unit, ctx context.Context, writeError chan error) { |
|
|
|
|
|
|
|
for _, pkt := range unit.GetRTPPackets() { |
|
|
|
|
|
|
|
webRTCTrak.WriteRTP(pkt) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return ret, nil |
|
|
|
return nil, nil |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (c *webRTCConn) genICEServers() []webrtc.ICEServer { |
|
|
|
func (c *webRTCConn) genICEServers() []webrtc.ICEServer { |
|
|
|