webrtc datachannel的协商
ZLMediaKit支持webrtc接入,按正常流程,第一步需要协商出音视频通道。
但方案是只协商出datachannel,sdp如下:
web 发起offer:
v=0^M
o=- 7161281774595815373 2 IN IP4 127.0.0.1^M
s=-^M
t=0 0^M
a=group:BUNDLE 0^M
a=extmap-allow-mixed^M
a=msid-semantic: WMS^M
m=application 9 UDP/DTLS/SCTP webrtc-datachannel^M
c=IN IP4 0.0.0.0^M
a=ice-ufrag:2rgo^M
a=ice-pwd:UpLdvvTbYzdPeYmgLBiCD15D^M
a=ice-options:trickle^M
a=fingerprint:sha-256 25:31:BD:29:3C:FB:50:4E:F5:32:A8:61:7A:D2:75:E3:D0:BA:8E:F7:B8:D9:63:8B:AA:31:26:81:94:A9:D1:B3^M
a=setup:actpass^M
a=mid:0^M
a=sctp-port:5000^M
a=max-message-size:262144^M
ZLMediaKit响应的answer:
v=0
o=- 7161281774595815373 2 IN IP4 172.16.63.52
s=-
t=0 0
a=group:BUNDLE 0
a=msid-semantic: WMS
a=ice-lite
m=application 8000 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 172.16.63.52
a=rtcp:8000 IN IP4 172.16.63.52
a=ice-ufrag:zlm_1
a=ice-pwd:QXOSSrdX7wKA6v6OZWWnEcFy
a=ice-options:trickle
a=fingerprint:sha-256 47:99:1F:A6:6F:B0:0C:E8:1F:A3:C0:E2:CD:D7:9B:C9:A3:33:67:50:E4:74:2A:73:68:98:DC:09:F3:78:71:AB
a=setup:passive
a=mid:0
a=ice-lite
a=sctp-port:5000
a=candidate:udpcandidate 1 udp 120 172.16.63.52 8000 typ host
a=candidate:tcpcandidate 1 tcp 115 172.16.63.52 8000 typ host tcptype passive
可以看到sdp中只带一个m行,类型为webrtc-datachannel
,在offer的sdp中有一个表示可以发送最大发送消息长度的属性行 a=max-message-size:262144
。rtp包的长度不会大于mute,一般是在1500个字节一下,远远小于这个大小。
转发视频流
只有webrtc datachannel通道,ZLMediaKit不会转发视频流。需要修改逻辑,使其也能转发视频流。
编译安装usrcsctp库
ZLMediaKit中的datachannel的实现也是基于usrsctp库,所以首先需要编译安装usrsctp,步骤比较简单这里就不列举。
安装完usrsctp后,再执行ZLMediaKit的cmake时,会打印 -- WebRTC datachannel 功能已打开
。
逻辑修改
在ZLMediaKit中,WebRtcTransport
类负责处理webrtc接入的所有流程及媒体流的转发。
- sdp协商成功后,接下的流程就是进行DTLS的协商,
void WebRtcTransport::OnDtlsTransportConnected
就是DTLS协商成功后的回调。 - DTLS协商成功后,接下来就是收发媒体流(如果协商了音视频通道)和自定义消息(如果协商了datachannel通道)。
- 在前面一篇文章提到过,data channel通道基于DTLS,处理data channel消息的堆栈如下:
static int onRecvSctpData
SctpAssociation::ProcessSctpData
WebRtcTransport::OnDtlsTransportApplicationDataReceived
WebRtcTransportImp::OnDtlsTransportApplicationDataReceive
DtlsTransport::ProcessDtlsData
sctp消息都会到OnRecvSctpData
处理。
- usrsctp也有一个sctp连接成功的回调,
OnSctpAssociationConnected
,在这个回调中就表示可以发送sctp消息了,那么在该回调中添加如下转流逻辑
void WebRtcPlayer:OnSctpAssociationConnected(RTC::SctpAssociation *sctpAssociation) {auto playSrc = _play_src.lock();if(!playSrc){onShutdown(SockException(Err_shutdown, "rtsp media source was shutdown"));return ;}if (isOnlyDatachannel()) {playSrc->pause(false);_reader = playSrc->getRing()->attach(getPoller(), true);weak_ptr<WebRtcPlayer> weak_self = static_pointer_cast<WebRtcPlayer>(shared_from_this());weak_ptr<Session> weak_session = static_pointer_cast<Session>(getSession());_reader->setGetInfoCB([weak_session]() { return weak_session.lock(); });_reader->setReadCB([weak_self](const RtspMediaSource::RingDataType &pkt) {auto strong_self = weak_self.lock();if (!strong_self) {return;}size_t i = 0;pkt->for_each([&](const RtpPacket::Ptr &rtp){if (TrackVideo == rtp->type) {strong_self->SendSctpMessage((uint8_t*)rtp->data(),rtp->size());}});});_reader->setDetachCB([weak_self]() {auto strong_self = weak_self.lock();if (!strong_self) {return;}strong_self->onShutdown(SockException(Err_shutdown, "rtsp ring buffer detached"));});}
}
在只有datachannel通道时,才从src的RingBuffer
中获取一个RingReader
,开始转流。
优化发送缓存区大小
usrsctp的IO模式被设置成非阻塞IO,默认的发送缓存的大小为262144。如果缓存区满了,那么就会报错Resource temporarily unavailable
。
在码率和分辨率比较高时,RingBuffer
中缓存的GOP大小可能远远大于262144,在一开始转流时就填满了发送缓冲区,造成图像卡顿。
可以将缓存区的大小改大,在SctpAssociation::SctpAssociation
构造函数中加入如下代码:
//设置发送缓存区
//默认的buffer大小为262144,将buffer size设置为512000*5
uint32_t bufferSize = 512000*5;
if (usrsctp_setsockopt(this->socket, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(uint32_t))<0) {MS_THROW_ERROR("usrsctp_setsockopt(SO_SNDBUF) failed: %s", std::strerror(errno));
}
不过这样也还是避免不了缓存区被填满的情况,这也是使用webrtc data channel传输视频流的一个弊端。
具体代码看这里