前面一章讲述了客户端和Janus之间是如何通过信令完成sdp交互的,信令虽然看起来都懂,但初始接触webrtc的话,对sdp生成,ice作用和过程等都一头雾水,刚开始我也是花了不少时间来理解这个过程,当初信令都正确,但是其他web订阅端就是看不到我这个windows下SDK发布的媒体流。
发布过程的create, attach, join信令过程都很简单也很好理解,需要注意的就是join信令,需要设置自己的角色为 publisher
join成功后,会收到janus告知当前room都有些谁在发布视频流,就不多说了,现在是关键一步,告知janus自己的sdp, 所以发信令前需呀先获取自己的sdp,幸好如何生成sdp,webrtc都帮我们做了,只要调用webrtc::PeerConnectionInterface::CreateOffer(CreateSessionDescriptionObserver* observer,const MediaConstraintsInterface* constraints);接口即可在回调接口CreateSessionDescriptionObserver*的函数void OnSuccess(SessionDescriptionInterface* desc)里收到SDP, 当然,在调用CreateOffer前,需要先初始化PeerConnection,添加自己的音频视频,下面是关键代码:
bool myRtcConnection::InitializePeerConnection()
{
//RTC_DCHECK(peer_connection_factory_.get() == NULL);
if (peer_connection_factory_.get() != NULL)
return true;
peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(
webrtc::CreateBuiltinAudioEncoderFactory(),
webrtc::CreateBuiltinAudioDecoderFactory());
if (!peer_connection_factory_.get()) {
//rtcobserver_->MessageBox("Error", "Failed to initialize PeerConnectionFactory", true);
DeletePeerConnection();
return false;
}
if (!CreatePeerConnection(DTLS_ON)) {
//rtcobserver_->MessageBox("Error", "CreatePeerConnection failed", true);
DeletePeerConnection();
}
//AddStreams();
return peer_connection_.get() != NULL;
}
bool myRtcConnection::PublishStream()
{
if (InitializePeerConnection()) {
AddStreams();
peer_connection_->CreateOffer(this, NULL);
return true;
}
else {
return false;
}
}
具体的函数在webrtc的peerconnection_client里都有,我们在CreateOffer的回调里发送sdp给Janus:
void myRtcConnection::OnSuccess(webrtc::SessionDescriptionInterface* desc)
{
peer_connection_->SetLocalDescription(DummySetSessionDescriptionObserver::Create(), desc);
SdpType type = desc->GetType();
std::string sdp;
desc->ToString(&sdp);
///////send sdp to janus
janus_websocket_->configure(handleid_, sdp);
}
注意发布端的sdp type是offer,同步进行的是ice过程,ice过程是什么时候触发的呢?答案是CreatePeerConnection的时候触发的,看下CreatePeerConnection函数定义:
bool myRtcConnection::CreatePeerConnection(bool dtls) {
RTC_DCHECK(peer_connection_factory_.get() != NULL);
webrtc::PeerConnectionInterface::RTCConfiguration config;
webrtc::PeerConnectionInterface::IceServer server;
server.uri = GetPeerConnectionString();
config.servers.push_back(server);
webrtc::FakeConstraints constraints;
if (dtls) {
constraints.AddOptional(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
"true");
}
else {
constraints.AddOptional(webrtc::MediaConstraintsInterface::kEnableDtlsSrtp,
"false");
}
peer_connection_ = peer_connection_factory_->CreatePeerConnection(
config, &constraints, NULL, NULL, this);
return peer_connection_.get() != NULL;
}
如上代码CreatePeerConnection的时候,传入了this指针,因为myRtcConnection实现了PeerConnectionObserver,这个接口是ice过程的回调接口,我们在回调接口收到ice时,通过信令发送给Janus即可:
void myRtcConnection::OnIceCandidate(const webrtc::IceCandidateInterface* candidate)
{
RTC_LOG(INFO) << __FUNCTION__ << " " << candidate->sdp_mline_index();
std::string sdp;
if (!candidate->ToString(&sdp)) {
RTC_LOG(LS_ERROR) << "Failed to serialize candidate";
return;
}
std::string sdp_mid = candidate->sdp_mid();
int sdp_mlineindex = candidate->sdp_mline_index();
//////send candicate to janus
janus_websocket_->send_candidate(handleid_, sdp_mlineindex, sdp_mid, sdp);
}
对应的信令过程:
需要注意的是,我们需要在ice完成的时候发一条ice结束信令给janus:
这是什么时候发送呢?
void myRtcConnection::OnIceGatheringChange(
webrtc::PeerConnectionInterface::IceGatheringState new_state)
{
switch (new_state)
{
case webrtc::PeerConnectionInterface::kIceGatheringNew:
break;
case webrtc::PeerConnectionInterface::kIceGatheringGathering:
break;
case webrtc::PeerConnectionInterface::kIceGatheringComplete:
{
janus_websocket_->send_candidate_complete(handleid_);
}
break;
default:
break;
}
}
通过上述过程,我们就把自己的sdp和ice信息发送给了janus服务器,但是只是把自己的发给对方还不够,还需要获取jansu的sdp和ice信息,所以我们需要吃力websocket收到的sdp和ice信息,将这些信息告诉webrtc:
void myRtcConnection::onJanusPeerSdp(std::string sdptype, std::string sdp)
{
webrtc::SdpParseError error;
rtc::Optional<SdpType> type_maybe = webrtc::SdpTypeFromString(sdptype);
if (!type_maybe) {
RTC_LOG(LS_ERROR) << "Unknown SDP type: " << sdptype;
return;
}
SdpType type = *type_maybe;
if (!InitializePeerConnection())
{
///onError callback
return;
}
std::unique_ptr<webrtc::SessionDescriptionInterface> session_description = webrtc::CreateSessionDescription(type, sdp, &error);
if (!session_description) {
RTC_LOG(WARNING) << "Can't parse received session description message. "
<< "SdpParseError was: " << error.description;
return;
}
RTC_LOG(INFO) << " Received session description :" << sdp;
peer_connection_->SetRemoteDescription(DummySetSessionDescriptionObserver::Create(), session_description.release());
}
最重要的就是调用SetRemoteDescription告知webrtc, ice也一样:
void myRtcConnection::onJanusIceCandidate( std::string sdp, std::string sdp_mid, int sdp_mlineindex)
{
webrtc::SdpParseError error;
std::unique_ptr<webrtc::IceCandidateInterface> candidate(
webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));
if (!candidate.get()) {
RTC_LOG(WARNING) << "Can't parse received candidate message. "
<< "SdpParseError was: " << error.description;
return;
}
if (!peer_connection_->AddIceCandidate(candidate.get())) {
RTC_LOG(WARNING) << "Failed to apply the received candidate";
return;
}
}
到这里,webrtc就可以开始向对方发媒体流数据了,在janus的web测试端可以看到您发布的视频流了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
原创文章,转载须帮忙同时转载下面的小广告 :),作者的小程序作品,已申请专利和证爱、证爱树、证爱林、证爱网等一系列商标保护, 玩法专利申请时间还在蚂蚁森林上线之前。
浪漫是女性的刚需,记录浪漫推荐用证爱树情侣相册、证爱树家庭相册,是由证爱网出品的基于相册功能的情侣情感养成免费小游戏,模式为全球首创,情侣线下在一起互动越多,成长越块,而且每次互动都能记录成证爱树的一片树叶,永久保存,帮助您成为浪漫大师,让感情永久保鲜。