mirror of
				https://github.com/aler9/rtsp-simple-server
				synced 2025-10-31 19:13:22 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			396 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package webrtc
 | |
| 
 | |
| import (
 | |
| 	"sync/atomic"
 | |
| 	"time"
 | |
| 
 | |
| 	"github.com/bluenviron/gortsplib/v4/pkg/rtcpreceiver"
 | |
| 	"github.com/pion/rtcp"
 | |
| 	"github.com/pion/rtp"
 | |
| 	"github.com/pion/webrtc/v4"
 | |
| 
 | |
| 	"github.com/bluenviron/mediamtx/internal/counterdumper"
 | |
| 	"github.com/bluenviron/mediamtx/internal/logger"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	keyFrameInterval  = 2 * time.Second
 | |
| 	mimeTypeMultiopus = "audio/multiopus"
 | |
| 	mimeTypeL16       = "audio/L16"
 | |
| )
 | |
| 
 | |
| var incomingVideoCodecs = []webrtc.RTPCodecParameters{
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeAV1,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "profile=1",
 | |
| 		},
 | |
| 		PayloadType: 96,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypeAV1,
 | |
| 			ClockRate: 90000,
 | |
| 		},
 | |
| 		PayloadType: 97,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeVP9,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "profile-id=3",
 | |
| 		},
 | |
| 		PayloadType: 98,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeVP9,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "profile-id=2",
 | |
| 		},
 | |
| 		PayloadType: 99,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeVP9,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "profile-id=1",
 | |
| 		},
 | |
| 		PayloadType: 100,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeVP9,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "profile-id=0",
 | |
| 		},
 | |
| 		PayloadType: 101,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypeVP8,
 | |
| 			ClockRate: 90000,
 | |
| 		},
 | |
| 		PayloadType: 102,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeH265,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "level-id=93;profile-id=2;tier-flag=0;tx-mode=SRST",
 | |
| 		},
 | |
| 		PayloadType: 103,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeH265,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "level-id=93;profile-id=1;tier-flag=0;tx-mode=SRST",
 | |
| 		},
 | |
| 		PayloadType: 104,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeH264,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
 | |
| 		},
 | |
| 		PayloadType: 105,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeH264,
 | |
| 			ClockRate:   90000,
 | |
| 			SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
 | |
| 		},
 | |
| 		PayloadType: 106,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| var incomingAudioCodecs = []webrtc.RTPCodecParameters{
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    3,
 | |
| 			SDPFmtpLine: "channel_mapping=0,2,1;num_streams=2;coupled_streams=1",
 | |
| 		},
 | |
| 		PayloadType: 112,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    4,
 | |
| 			SDPFmtpLine: "channel_mapping=0,1,2,3;num_streams=2;coupled_streams=2",
 | |
| 		},
 | |
| 		PayloadType: 113,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    5,
 | |
| 			SDPFmtpLine: "channel_mapping=0,4,1,2,3;num_streams=3;coupled_streams=2",
 | |
| 		},
 | |
| 		PayloadType: 114,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    6,
 | |
| 			SDPFmtpLine: "channel_mapping=0,4,1,2,3,5;num_streams=4;coupled_streams=2",
 | |
| 		},
 | |
| 		PayloadType: 115,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    7,
 | |
| 			SDPFmtpLine: "channel_mapping=0,4,1,2,3,5,6;num_streams=4;coupled_streams=4",
 | |
| 		},
 | |
| 		PayloadType: 116,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    mimeTypeMultiopus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    8,
 | |
| 			SDPFmtpLine: "channel_mapping=0,6,1,4,5,2,3,7;num_streams=5;coupled_streams=4",
 | |
| 		},
 | |
| 		PayloadType: 117,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:    webrtc.MimeTypeOpus,
 | |
| 			ClockRate:   48000,
 | |
| 			Channels:    2,
 | |
| 			SDPFmtpLine: "minptime=10;useinbandfec=1;stereo=1;sprop-stereo=1",
 | |
| 		},
 | |
| 		PayloadType: 111,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypeG722,
 | |
| 			ClockRate: 8000,
 | |
| 		},
 | |
| 		PayloadType: 9,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypePCMU,
 | |
| 			ClockRate: 8000,
 | |
| 			Channels:  2,
 | |
| 		},
 | |
| 		PayloadType: 118,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypePCMA,
 | |
| 			ClockRate: 8000,
 | |
| 			Channels:  2,
 | |
| 		},
 | |
| 		PayloadType: 119,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypePCMU,
 | |
| 			ClockRate: 8000,
 | |
| 		},
 | |
| 		PayloadType: 0,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  webrtc.MimeTypePCMA,
 | |
| 			ClockRate: 8000,
 | |
| 		},
 | |
| 		PayloadType: 8,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  mimeTypeL16,
 | |
| 			ClockRate: 8000,
 | |
| 			Channels:  2,
 | |
| 		},
 | |
| 		PayloadType: 120,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  mimeTypeL16,
 | |
| 			ClockRate: 16000,
 | |
| 			Channels:  2,
 | |
| 		},
 | |
| 		PayloadType: 121,
 | |
| 	},
 | |
| 	{
 | |
| 		RTPCodecCapability: webrtc.RTPCodecCapability{
 | |
| 			MimeType:  mimeTypeL16,
 | |
| 			ClockRate: 48000,
 | |
| 			Channels:  2,
 | |
| 		},
 | |
| 		PayloadType: 122,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| // IncomingTrack is an incoming track.
 | |
| type IncomingTrack struct {
 | |
| 	OnPacketRTP func(*rtp.Packet, time.Time)
 | |
| 
 | |
| 	useAbsoluteTimestamp bool
 | |
| 	track                *webrtc.TrackRemote
 | |
| 	receiver             *webrtc.RTPReceiver
 | |
| 	writeRTCP            func([]rtcp.Packet) error
 | |
| 	log                  logger.Writer
 | |
| 	rtpPacketsReceived   *uint64
 | |
| 	rtpPacketsLost       *uint64
 | |
| 
 | |
| 	packetsLost  *counterdumper.CounterDumper
 | |
| 	rtcpReceiver *rtcpreceiver.RTCPReceiver
 | |
| }
 | |
| 
 | |
| func (t *IncomingTrack) initialize() {
 | |
| 	t.OnPacketRTP = func(*rtp.Packet, time.Time) {}
 | |
| }
 | |
| 
 | |
| // Codec returns the track codec.
 | |
| func (t *IncomingTrack) Codec() webrtc.RTPCodecParameters {
 | |
| 	return t.track.Codec()
 | |
| }
 | |
| 
 | |
| // ClockRate returns the clock rate. Needed by rtptime.GlobalDecoder
 | |
| func (t *IncomingTrack) ClockRate() int {
 | |
| 	return int(t.track.Codec().ClockRate)
 | |
| }
 | |
| 
 | |
| // PTSEqualsDTS returns whether PTS equals DTS. Needed by rtptime.GlobalDecoder
 | |
| func (*IncomingTrack) PTSEqualsDTS(*rtp.Packet) bool {
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func (t *IncomingTrack) start() {
 | |
| 	t.packetsLost = &counterdumper.CounterDumper{
 | |
| 		OnReport: func(val uint64) {
 | |
| 			t.log.Log(logger.Warn, "%d RTP %s lost",
 | |
| 				val,
 | |
| 				func() string {
 | |
| 					if val == 1 {
 | |
| 						return "packet"
 | |
| 					}
 | |
| 					return "packets"
 | |
| 				}())
 | |
| 		},
 | |
| 	}
 | |
| 	t.packetsLost.Start()
 | |
| 
 | |
| 	t.rtcpReceiver = &rtcpreceiver.RTCPReceiver{
 | |
| 		ClockRate:            int(t.track.Codec().ClockRate),
 | |
| 		UnrealiableTransport: true,
 | |
| 		Period:               1 * time.Second,
 | |
| 		WritePacketRTCP: func(p rtcp.Packet) {
 | |
| 			t.writeRTCP([]rtcp.Packet{p}) //nolint:errcheck
 | |
| 		},
 | |
| 	}
 | |
| 	err := t.rtcpReceiver.Initialize()
 | |
| 	if err != nil {
 | |
| 		panic(err)
 | |
| 	}
 | |
| 
 | |
| 	// read incoming RTCP packets.
 | |
| 	// incoming RTCP packets must always be read to make interceptors work.
 | |
| 	go func() {
 | |
| 		buf := make([]byte, 1500)
 | |
| 		for {
 | |
| 			n, _, err2 := t.receiver.Read(buf)
 | |
| 			if err2 != nil {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			pkts, err2 := rtcp.Unmarshal(buf[:n])
 | |
| 			if err2 != nil {
 | |
| 				panic(err2)
 | |
| 			}
 | |
| 
 | |
| 			for _, pkt := range pkts {
 | |
| 				if sr, ok := pkt.(*rtcp.SenderReport); ok {
 | |
| 					t.rtcpReceiver.ProcessSenderReport(sr, time.Now())
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| 
 | |
| 	// send period key frame requests
 | |
| 	if t.track.Kind() == webrtc.RTPCodecTypeVideo {
 | |
| 		go func() {
 | |
| 			keyframeTicker := time.NewTicker(keyFrameInterval)
 | |
| 			defer keyframeTicker.Stop()
 | |
| 
 | |
| 			for range keyframeTicker.C {
 | |
| 				err2 := t.writeRTCP([]rtcp.Packet{
 | |
| 					&rtcp.PictureLossIndication{
 | |
| 						MediaSSRC: uint32(t.track.SSRC()),
 | |
| 					},
 | |
| 				})
 | |
| 				if err2 != nil {
 | |
| 					return
 | |
| 				}
 | |
| 			}
 | |
| 		}()
 | |
| 	}
 | |
| 
 | |
| 	// read incoming RTP packets.
 | |
| 	go func() {
 | |
| 		for {
 | |
| 			pkt, _, err2 := t.track.ReadRTP()
 | |
| 			if err2 != nil {
 | |
| 				return
 | |
| 			}
 | |
| 
 | |
| 			packets, lost, err2 := t.rtcpReceiver.ProcessPacket2(pkt, time.Now(), true)
 | |
| 			if err2 != nil {
 | |
| 				t.log.Log(logger.Warn, err2.Error())
 | |
| 				continue
 | |
| 			}
 | |
| 			if lost != 0 {
 | |
| 				atomic.AddUint64(t.rtpPacketsLost, lost)
 | |
| 				t.packetsLost.Add(lost)
 | |
| 				// do not return
 | |
| 			}
 | |
| 
 | |
| 			atomic.AddUint64(t.rtpPacketsReceived, uint64(len(packets)))
 | |
| 
 | |
| 			var ntp time.Time
 | |
| 			if t.useAbsoluteTimestamp {
 | |
| 				var avail bool
 | |
| 				ntp, avail = t.rtcpReceiver.PacketNTP(pkt.Timestamp)
 | |
| 				if !avail {
 | |
| 					t.log.Log(logger.Warn, "received RTP packet without absolute time, skipping it")
 | |
| 					continue
 | |
| 				}
 | |
| 			} else {
 | |
| 				ntp = time.Now()
 | |
| 			}
 | |
| 
 | |
| 			for _, pkt := range packets {
 | |
| 				// sometimes Chrome sends empty RTP packets. ignore them.
 | |
| 				if len(pkt.Payload) == 0 {
 | |
| 					continue
 | |
| 				}
 | |
| 
 | |
| 				t.OnPacketRTP(pkt, ntp)
 | |
| 			}
 | |
| 		}
 | |
| 	}()
 | |
| }
 | |
| 
 | |
| func (t *IncomingTrack) close() {
 | |
| 	if t.packetsLost != nil {
 | |
| 		t.packetsLost.Stop()
 | |
| 	}
 | |
| 	if t.rtcpReceiver != nil {
 | |
| 		t.rtcpReceiver.Close()
 | |
| 	}
 | |
| }
 | 
