RTSP & WebRTC
real-time-stream-protocal and web real-time communication
就我的理解,WebRTC 支持渲染 RTSP 流,但是在这基础上增加了协商握手的过程,也就是 signaling。我之前不理解为什么浏览器不直接支持 RTSP,其实是因为流协议还未标准化的缘故。
协商握手:peerToPeer,规范不会要求你通过什么方式握手,只要能拿到一串 sdp(session description)字符并设置即可。
sdp 里具体有什么呢?关于音像文件的 address, timing, codec。codec (a blend word derived from "coder-decoder") is a program, algorithm, or device that encodes or decodes a data stream.
video Codecs Supported: H264; Audio Codecs Supported: pcm alaw and pcm mulaw
所以总流程相当于:
// 1.
const pc = new RTCPeerConnection();
pc.onnegotiationneeded = async () => {
// 3. 这一段来启动本端
let offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 4. 通过某种方式拿到另一端的 sdp 并设置
$.post(
'../receiver/' + suuid,
{
suuid: suuid,
data: btoa(pc.localDescription.sdp),
},function (data) {
pc.setRemoteDescription(
new RTCSessionDescription({
type: 'answer',
sdp: atob(data),
}),
);
},
);
}
//....以上行为称为 Signaling
pc.ontrack = function (event) {
// 5. event 为 WebRTCTrackEvent 其 track 字段给 stream 对象 add 上,
// 显然一个 stream 可以接受好多 track
let stream = new MediaStream();
stream.addTrack(event.track);
// 6. video 标签可以播放此 stream 了
videoNode.srcObject = stream;
};
// 2. 不明应设
pc.addTransceiver('video', { direction: 'sendrecv',});
pc.addTransceiver('audio', { direction: 'sendrecv',});
RTSPToWebRTC
- go 服务向远端 RTSP 的请求过程
conn, err := net.DialTimeout("tcp", client.pURL.Host, client.options.DialTimeout)
client.conn = conn
client.connRW = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
// 接下来通过 TCP 连接,go 服务向远端连续发出了好几个请求,解读应当是遵循 rtsp 协议的
// round 1: options
2022/06/23 11:06:38 [RTSP/1.0 200 OK
CSeq: 1
Date: Thu, Jun 23 2022 03:01:37 GMT
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER
]
2022/06/23 11:07:21 [DESCRIBE rtsp://192.168.2.53:8557/h264 RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: Lavf58.76.100
]
// round 2: DESCRIBE
2022/06/23 11:07:21 [DESCRIBE rtsp://192.168.2.53:8557/h264 RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: Lavf58.76.100
]
2022/06/23 11:09:21 [RTSP/1.0 200 OK
CSeq: 2
Date: Thu, Jun 23 2022 03:07:27 GMT
Content-Base: rtsp://192.168.2.53:8557/h264/
Content-Type: application/sdp
Content-Length: 691
v=0
o=- 18278359 1 IN IP4 192.168.2.53
s=RTSP/RTP stream from VZ Smart-IPC
a=rtpmap:8 PCMA/8000/1
a=fmtp:8 complaw=al
i=h264
t=0 0... // 后面这一堆数据就是 sdp
// round 3: track1 setup
2022/06/23 11:11:56 [SETUP rtsp://192.168.2.53:8557/h264/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP/TCP;unicast;interleaved=0-1
User-Agent: Lavf58.76.100
]
2022/06/23 11:13:25 [RTSP/1.0 200 OK
CSeq: 3
Date: Thu, Jun 23 2022 03:11:57 GMT
Transport: RTP/AVP/TCP;unicast;destination=192.168.2.128;source=192.168.2.53;interleaved=0-1
Session: 25E22894;timeout=60
]
// round 4: track2 setup
2022/06/23 11:14:27 [SETUP rtsp://192.168.2.53:8557/h264/track2 RTSP/1.0
CSeq: 4
Transport: RTP/AVP/TCP;unicast;interleaved=2-3
User-Agent: Lavf58.76.100
Session: 25E22894
]
2022/06/23 11:20:23 [RTSP/1.0 200 OK
CSeq: 4
Date: Thu, Jun 23 2022 03:20:24 GMT
Transport: RTP/AVP/TCP;unicast;destination=192.168.2.128;source=192.168.2.53;interleaved=2-3
Session: 629DD327;timeout=60
]
// round 5: play
022/06/23 11:20:23 [PLAY rtsp://192.168.2.53:8557/h264/ RTSP/1.0
CSeq: 5
User-Agent: Lavf58.76.100
Session: 629DD327
]
2022/06/23 11:20:23 [$0��� ����<�X�������� ��(none)$0���}���<ۗ�J�����}�(none)RTSP/1.0 200 OK
CSeq: 5
Date: Thu, Jun 23 2022 03:20:24 GMT
Range: npt=0.000-
Session: 629DD327
RTP-Info: url=rtsp://192.168.2.53:8557/h264/track1;seq=46978;rtptime=2279775039,url=rtsp://192.168.2.53:8557/h264/track2;seq=23770;rtptime=1252527567,url=rtsp://192.168.2.53:8557/h264/track3;seq=0;rtptime=0
推荐看以下这篇博客:我第一次真正理解了 text based protocol!!
https://csrgxtu.github.io/2015/04/08/An-Introduction-To-RTSP/
- toWebRTC
这一段越看越晕…我不理解为什么 rtsp 已经有很完善的握手和传递过程了,浏览器还要再制定一个 web 版的协议?
但是 go 里有一点我看明白啦 —> 中间服务器向远端拿来的数据会缓存进通道里**make**(chan *av.Packet, 3000)
,然后这个通道一有数据,会再吐出来分发给各个消费者:
// 从远端拿来存
client.OutgoingProxyQueue <- &content
// 分发
select {
case packetAV := <-RTSPClient.OutgoingPacketQueue:
if AudioOnly || packetAV.IsKeyFrame {
keyTest.Reset(20 * time.Second)
}
Config.cast(name, *packetAV)
}
}
func (element *ConfigST) cast(uuid string, pck av.Packet) {
element.mutex.Lock()
defer element.mutex.Unlock()
for _, v := range element.Streams[uuid].Cl {
if len(v.c) < cap(v.c) {
v.c <- pck
}
}
}
Ref
https://webrtc.org/getting-started/turn-server?hl=en
https://codelabs.developers.google.com/codelabs/webrtc-web#0
chrome://webrtc-internals/