|
@ -12,9 +12,7 @@ import ( |
|
|
|
|
|
|
|
|
"github.com/Monibuca/engine/v3" |
|
|
"github.com/Monibuca/engine/v3" |
|
|
"github.com/Monibuca/utils/v3" |
|
|
"github.com/Monibuca/utils/v3" |
|
|
"github.com/Monibuca/utils/v3/codec" |
|
|
|
|
|
"github.com/pion/rtcp" |
|
|
"github.com/pion/rtcp" |
|
|
"github.com/pion/rtp" |
|
|
|
|
|
. "github.com/pion/webrtc/v3" |
|
|
. "github.com/pion/webrtc/v3" |
|
|
"github.com/pion/webrtc/v3/pkg/media" |
|
|
"github.com/pion/webrtc/v3/pkg/media" |
|
|
) |
|
|
) |
|
@ -86,7 +84,6 @@ func init() { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
type WebRTC struct { |
|
|
type WebRTC struct { |
|
|
engine.Publisher |
|
|
|
|
|
*PeerConnection |
|
|
*PeerConnection |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -116,6 +113,7 @@ func (rtc *WebRTC) Publish(streamPath string) bool { |
|
|
// },
|
|
|
// },
|
|
|
// },
|
|
|
// },
|
|
|
}) |
|
|
}) |
|
|
|
|
|
rtc.PeerConnection = peerConnection |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
utils.Println(err) |
|
|
utils.Println(err) |
|
|
return false |
|
|
return false |
|
@ -129,21 +127,21 @@ func (rtc *WebRTC) Publish(streamPath string) bool { |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
return false |
|
|
return false |
|
|
} |
|
|
} |
|
|
|
|
|
stream := &engine.Stream{ |
|
|
|
|
|
Type: "WebRTC", |
|
|
|
|
|
StreamPath: streamPath, |
|
|
|
|
|
} |
|
|
|
|
|
if stream.Publish() { |
|
|
peerConnection.OnICEConnectionStateChange(func(connectionState ICEConnectionState) { |
|
|
peerConnection.OnICEConnectionStateChange(func(connectionState ICEConnectionState) { |
|
|
utils.Printf("%s Connection State has changed %s ", streamPath, connectionState.String()) |
|
|
utils.Printf("%s Connection State has changed %s ", streamPath, connectionState.String()) |
|
|
switch connectionState { |
|
|
switch connectionState { |
|
|
case ICEConnectionStateDisconnected, ICEConnectionStateFailed: |
|
|
case ICEConnectionStateDisconnected, ICEConnectionStateFailed: |
|
|
if rtc.Stream != nil { |
|
|
stream.Close() |
|
|
rtc.Stream.Close() |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
rtc.PeerConnection = peerConnection |
|
|
|
|
|
if rtc.Publish(streamPath) { |
|
|
|
|
|
//f, _ := os.OpenFile("resource/live/rtc.h264", os.O_TRUNC|os.O_WRONLY, 0666)
|
|
|
//f, _ := os.OpenFile("resource/live/rtc.h264", os.O_TRUNC|os.O_WRONLY, 0666)
|
|
|
rtc.Stream.Type = "WebRTC" |
|
|
|
|
|
peerConnection.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) { |
|
|
peerConnection.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) { |
|
|
defer rtc.Stream.Close() |
|
|
defer stream.Close() |
|
|
go func() { |
|
|
go func() { |
|
|
ticker := time.NewTicker(time.Second * 2) |
|
|
ticker := time.NewTicker(time.Second * 2) |
|
|
select { |
|
|
select { |
|
@ -151,48 +149,34 @@ func (rtc *WebRTC) Publish(streamPath string) bool { |
|
|
if rtcpErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil { |
|
|
if rtcpErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); rtcpErr != nil { |
|
|
fmt.Println(rtcpErr) |
|
|
fmt.Println(rtcpErr) |
|
|
} |
|
|
} |
|
|
case <-rtc.Done(): |
|
|
case <-stream.Done(): |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
}() |
|
|
}() |
|
|
var etrack engine.Track |
|
|
if codec := track.Codec(); track.Kind() == RTPCodecTypeAudio { |
|
|
codec := track.Codec() |
|
|
var at *engine.RTPAudio |
|
|
if track.Kind() == RTPCodecTypeAudio { |
|
|
|
|
|
switch codec.MimeType { |
|
|
switch codec.MimeType { |
|
|
case MimeTypePCMA: |
|
|
case MimeTypePCMA: |
|
|
at := engine.NewAudioTrack() |
|
|
at = stream.NewRTPAudio(7) |
|
|
at.SoundFormat = 7 |
|
|
|
|
|
at.Channels = byte(codec.Channels) |
|
|
at.Channels = byte(codec.Channels) |
|
|
rtc.SetOriginAT(at) |
|
|
|
|
|
etrack = at |
|
|
|
|
|
case MimeTypePCMU: |
|
|
case MimeTypePCMU: |
|
|
at := engine.NewAudioTrack() |
|
|
at = stream.NewRTPAudio(8) |
|
|
at.SoundFormat = 8 |
|
|
|
|
|
at.Channels = byte(codec.Channels) |
|
|
at.Channels = byte(codec.Channels) |
|
|
rtc.SetOriginAT(at) |
|
|
|
|
|
etrack = at |
|
|
|
|
|
default: |
|
|
default: |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
vt := engine.NewVideoTrack() |
|
|
|
|
|
vt.CodecID = 7 |
|
|
|
|
|
rtc.SetOriginVT(vt) |
|
|
|
|
|
etrack = vt |
|
|
|
|
|
} |
|
|
|
|
|
var pack rtp.Packet |
|
|
|
|
|
b := make([]byte, 1460) |
|
|
b := make([]byte, 1460) |
|
|
for { |
|
|
for i, _, err := track.Read(b); err == nil; i, _, err = track.Read(b) { |
|
|
i, _, err := track.Read(b) |
|
|
at.Push(b[:i]) |
|
|
if err != nil { |
|
|
|
|
|
return |
|
|
|
|
|
} |
|
|
} |
|
|
if err = pack.Unmarshal(b[:i]); err != nil { |
|
|
} else { |
|
|
return |
|
|
vt := stream.NewRTPVideo(7) |
|
|
|
|
|
b := make([]byte, 1460) |
|
|
|
|
|
for i, _, err := track.Read(b); err == nil; i, _, err = track.Read(b) { |
|
|
|
|
|
vt.Push(b[:i]) |
|
|
} |
|
|
} |
|
|
etrack.PushRTP(pack) |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
}) |
|
|
}) |
|
|
} else { |
|
|
} else { |
|
|
return false |
|
|
return false |
|
@ -271,9 +255,11 @@ func run() { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
vt := sub.WaitVideoTrack("h264") |
|
|
vt := sub.WaitVideoTrack("h264") |
|
|
|
|
|
at := sub.WaitAudioTrack("pcma", "pcmu") |
|
|
|
|
|
var rtpSender *RTPSender |
|
|
if vt != nil { |
|
|
if vt != nil { |
|
|
pli := "42001f" |
|
|
pli := "42001f" |
|
|
pli = fmt.Sprintf("%x", vt.SPS[1:4]) |
|
|
pli = fmt.Sprintf("%x", vt.ExtraData.NALUs[0][1:4]) |
|
|
if !strings.Contains(offer.SDP, pli) { |
|
|
if !strings.Contains(offer.SDP, pli) { |
|
|
pli = reg_level.FindAllStringSubmatch(offer.SDP, -1)[0][1] |
|
|
pli = reg_level.FindAllStringSubmatch(offer.SDP, -1)[0][1] |
|
|
} |
|
|
} |
|
@ -281,35 +267,55 @@ func run() { |
|
|
if videoTrack, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, "video", "m7s"); err != nil { |
|
|
if videoTrack, err = NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=" + pli}, "video", "m7s"); err != nil { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
if _, err = rtc.AddTrack(videoTrack); err != nil { |
|
|
rtpSender, err = rtc.AddTrack(videoTrack) |
|
|
|
|
|
if err != nil { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
var lastTimeStampV uint32 |
|
|
var lastTimeStampV uint32 |
|
|
sub.OnVideo = func(pack engine.VideoPack) { |
|
|
sub.OnVideo = func(pack engine.VideoPack) { |
|
|
var s uint32 |
|
|
var s uint32 = 40 |
|
|
if lastTimeStampV > 0 { |
|
|
if lastTimeStampV > 0 { |
|
|
s = pack.Timestamp - lastTimeStampV |
|
|
s = pack.Timestamp - lastTimeStampV |
|
|
} |
|
|
} |
|
|
lastTimeStampV = pack.Timestamp |
|
|
lastTimeStampV = pack.Timestamp |
|
|
if pack.NalType == codec.NALU_IDR_Picture { |
|
|
if pack.IDR { |
|
|
videoTrack.WriteSample(media.Sample{ |
|
|
err = videoTrack.WriteSample(media.Sample{ |
|
|
Data: vt.SPS, |
|
|
Data: vt.ExtraData.NALUs[0], |
|
|
}) |
|
|
}) |
|
|
videoTrack.WriteSample(media.Sample{ |
|
|
err = videoTrack.WriteSample(media.Sample{ |
|
|
Data: vt.PPS, |
|
|
Data: vt.ExtraData.NALUs[1], |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
videoTrack.WriteSample(media.Sample{ |
|
|
for _, nalu := range pack.NALUs { |
|
|
Data: pack.Payload, |
|
|
err = videoTrack.WriteSample(media.Sample{ |
|
|
|
|
|
Data: nalu, |
|
|
Duration: time.Millisecond * time.Duration(s), |
|
|
Duration: time.Millisecond * time.Duration(s), |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
at := sub.GetAudioTrack("pcma", "pcmu") |
|
|
go func() { |
|
|
|
|
|
rtcpBuf := make([]byte, 1500) |
|
|
|
|
|
for { |
|
|
|
|
|
if n, _, rtcpErr := rtpSender.Read(rtcpBuf); rtcpErr != nil { |
|
|
|
|
|
|
|
|
|
|
|
return |
|
|
|
|
|
} else { |
|
|
|
|
|
if p, err := rtcp.Unmarshal(rtcpBuf[:n]); err == nil { |
|
|
|
|
|
for _, pp := range p { |
|
|
|
|
|
switch pp.(type) { |
|
|
|
|
|
case *rtcp.PictureLossIndication: |
|
|
|
|
|
fmt.Println("PictureLossIndication") |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}() |
|
|
|
|
|
} |
|
|
if at != nil { |
|
|
if at != nil { |
|
|
var audioTrack *TrackLocalStaticSample |
|
|
var audioTrack *TrackLocalStaticSample |
|
|
audioMimeType := MimeTypePCMA |
|
|
audioMimeType := MimeTypePCMA |
|
|
if at.SoundFormat == 8 { |
|
|
if at.CodecID == 8 { |
|
|
audioMimeType = MimeTypePCMU |
|
|
audioMimeType = MimeTypePCMU |
|
|
} |
|
|
} |
|
|
if audioTrack, err = NewTrackLocalStaticSample(RTPCodecCapability{audioMimeType, 8000, 0, "", nil}, "audio", "m7s"); err != nil { |
|
|
if audioTrack, err = NewTrackLocalStaticSample(RTPCodecCapability{audioMimeType, 8000, 0, "", nil}, "audio", "m7s"); err != nil { |
|
@ -326,28 +332,29 @@ func run() { |
|
|
} |
|
|
} |
|
|
lastTimeStampA = pack.Timestamp |
|
|
lastTimeStampA = pack.Timestamp |
|
|
audioTrack.WriteSample(media.Sample{ |
|
|
audioTrack.WriteSample(media.Sample{ |
|
|
Data: pack.Payload, Duration: time.Millisecond * time.Duration(s), |
|
|
Data: pack.Raw, Duration: time.Millisecond * time.Duration(s), |
|
|
}) |
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if bytes, err := rtc.GetAnswer(); err == nil { |
|
|
if bytes, err := rtc.GetAnswer(); err == nil { |
|
|
w.Write(bytes) |
|
|
w.Write(bytes) |
|
|
rtc.OnICEConnectionStateChange(func(connectionState ICEConnectionState) { |
|
|
rtc.OnConnectionStateChange(func(pcs PeerConnectionState) { |
|
|
utils.Printf("%s Connection State has changed %s ", streamPath, connectionState.String()) |
|
|
utils.Printf("%s Connection State has changed %s ", streamPath, pcs.String()) |
|
|
switch connectionState { |
|
|
switch pcs { |
|
|
case ICEConnectionStateDisconnected, ICEConnectionStateFailed: |
|
|
case PeerConnectionStateConnected: |
|
|
sub.Close() |
|
|
|
|
|
rtc.PeerConnection.Close() |
|
|
|
|
|
case ICEConnectionStateConnected: |
|
|
|
|
|
if at != nil { |
|
|
if at != nil { |
|
|
go sub.PlayAudio(at) |
|
|
go sub.PlayAudio(at) |
|
|
} |
|
|
} |
|
|
if vt != nil { |
|
|
if vt != nil { |
|
|
go sub.PlayVideo(vt) |
|
|
go sub.PlayVideo(vt) |
|
|
} |
|
|
} |
|
|
|
|
|
case PeerConnectionStateDisconnected, PeerConnectionStateFailed: |
|
|
|
|
|
sub.Close() |
|
|
|
|
|
rtc.PeerConnection.Close() |
|
|
} |
|
|
} |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
} else { |
|
|
} else { |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|