基于Webrtc和Janus的多人视频会议系统开发5 - 发布媒体流到Janus服务器

时间:2024-03-31 08:39:49

前面一章讲述了客户端和Janus之间是如何通过信令完成sdp交互的,信令虽然看起来都懂,但初始接触webrtc的话,对sdp生成,ice作用和过程等都一头雾水,刚开始我也是花了不少时间来理解这个过程,当初信令都正确,但是其他web订阅端就是看不到我这个windows下SDK发布的媒体流。

发布过程的create, attach, join信令过程都很简单也很好理解,需要注意的就是join信令,需要设置自己的角色为 publisher

基于Webrtc和Janus的多人视频会议系统开发5 - 发布媒体流到Janus服务器

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);

}

基于Webrtc和Janus的多人视频会议系统开发5 - 发布媒体流到Janus服务器

注意发布端的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);
}

对应的信令过程:

基于Webrtc和Janus的多人视频会议系统开发5 - 发布媒体流到Janus服务器

需要注意的是,我们需要在ice完成的时候发一条ice结束信令给janus:

基于Webrtc和Janus的多人视频会议系统开发5 - 发布媒体流到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测试端可以看到您发布的视频流了。

 

--------------------------------------------------------------------------------------------------------------------------------------------------------------

原创文章,转载须帮忙同时转载下面的小广告 :),作者的小程序作品,已申请专利和证爱、证爱树、证爱林、证爱网等一系列商标保护, 玩法专利申请时间还在蚂蚁森林上线之前。

浪漫是女性的刚需,记录浪漫推荐用证爱树情侣相册证爱树家庭相册,是由证爱网出品的基于相册功能的情侣情感养成免费小游戏,模式为全球首创,情侣线下在一起互动越多,成长越块,而且每次互动都能记录成证爱树的一片树叶,永久保存,帮助您成为浪漫大师,让感情永久保鲜。