WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

学习一个框架最好的入手点之一就是首先学会如何方便快捷的使用它达到我们想要的效果,然后再去逐步分析每一个过程的具体实现细节,这样思路会更加清晰一些。WebRTC为我们提供一个非常完整凝练的音视频互动案例作为入手分析点,下边我们来详细分析下这个实例demo。

一、源码目录

          WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第1张图片

二、结构流程WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第2张图片

           WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第3张图片

WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第4张图片

图中Conductor是该示例工程提供的核心业务类,整个WebRTC的使用都浓缩在这个类中。Conductor通过CreatePeerConnectionFactory方法创建了一个PeerConnectionFactoryInterface接口的实现对象,通过这个接口,可以创建关键的PeerConnectionInterface接口,PeerConnectionInterface接口是WebRTC的协议核心。此外,PeerConnectionFactoryInterface接口还提供了创建本地音视频流的功能接口,这个部分稍后再述。根据图中PeerConnectionInterface接口的成员方法可以看出,WebRTC通信流程的交互接口基本上都在这里面了,给Conductor的回调通知是通过PeerConnectionObserver接口来完成。

构建媒体流的过程基本上就是构建Video Track和Audio Track,并将其添加到Media Stream里。在peerconnection_client工程中,Conductor依赖DeviceManagerInterface接口的CreateVideoCapturer方法创建一个当前可用的视频设备采集对象VideoCapturer,将它作为视频采集源中的数据来源(通过挂接VideoCapturer的SignalVideoFrame信号来接收视频数据),此外MainWnd还创建了一个内部类VideoRenderer从VideoRendererInterface接口派生,并将其添加到Video Track中, VideoRenderer的实现就是将接收到的视频帧数据渲染到窗口上。

三、逐步解析

Windows版本的peerconnection_client demo是一个win32程序,入口函数为main.cc里面的wWinMain,程序整体流程就从这个入口函数下手开始分析。

1.peerconnection_client demo中主要的类的关系

整个demo中有3个主要的类分别是窗口类MainWnd,它的主要功能是实现了一个窗体程序,然后是PeerConnectionClient类,他的作用是与信令服务器来进行TCP通信,最后是联系MainWnd和PeerConnectionClient的类Conductor,Conductor实现了MainWndCallback和PeerConnectionClientObserver接口,当PeerConnectionClient和MainWnd完成某个事件时,会通过调用相应的接口来通知Conductor。

然后从入口函数wWinMain开始来分析一下demo的函数调用流程

2.入口函数wWinMain分析

在函数的一开始,初始化了Windows socket,以及webRTC消息循环。

  rtc::EnsureWinsockInit();
  rtc::Win32Thread w32_thread;
  rtc::ThreadManager::Instance()->SetCurrentThread(&w32_thread);

然后去处理启动程序的时候传入的命令行参数,感觉不太重要,这里略过。

处理完命令行参数之后,调用了MainWnd的Create函数创建了窗体

 //创建窗体
  MainWnd wnd(FLAG_server, FLAG_port, FLAG_autoconnect, FLAG_autocall);
  if (!wnd.Create()) {
    RTC_NOTREACHED();
    return -1;
  }

紧接着就是初始化SSL以及创建PeerConnectionClient和Conductor

//创建PeerConnectionClient
  //PeerConnectionClient主要用来处理与信令服务器的tcp通讯
  //它有两个Win32Socket:control_socket_和hanging_get_,
  //在PeerConnectionClient::DoConnect()中创建,并在PeerConnectionClient::InitSocketSignals()中连接好socket的信号。
  PeerConnectionClient client;
  //scoped_refptr 是一个智能指针
  //RefCountedObject实现了一个线程安全的引用计数功能
  //代码的作用是创建了一个Conductor对象并用conductor指向它
  rtc::scoped_refptr conductor(
        new rtc::RefCountedObject(&client, &wnd));

完成了上面的操作之后就进入了窗体消息循环,等待窗体上的操作

//窗体消息循环
  // Main loop.
  MSG msg;
  BOOL gm;
  //GetMessage函数只有在接收到WM_QUIT消息时才返回0
  while ((gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
    if (!wnd.PreTranslateMessage(&msg)) {
        //将虚拟键消息转换为字符消息
      ::TranslateMessage(&msg);
      //分派一个消息到窗口进程由窗口进程对消息进行处理
      ::DispatchMessage(&msg);
    }
  }

  //上面的消息循环退出后,如果仍然链接着,继续处理消息
  if (conductor->connection_active() || client.is_connected()) {
    while ((conductor->connection_active() || client.is_connected()) &&
           (gm = ::GetMessage(&msg, NULL, 0, 0)) != 0 && gm != -1) {
      if (!wnd.PreTranslateMessage(&msg)) {
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
      }
    }
  }

再以后是关闭SSL

rtc::CleanupSSL();

3.窗体消息分析

窗体的消息是在MainWnd的OnMessage函数中进行处理的。

当点击connect按钮时

      //connect按钮按下
    case WM_COMMAND:
      if (button_ == reinterpret_cast(lp)) {
        if (BN_CLICKED == HIWORD(wp))
          OnDefaultAction();
      } else if (listbox_ == reinterpret_cast(lp)) {
        if (LBN_DBLCLK == HIWORD(wp)) {
          OnDefaultAction();
        }
      }
      return true;

点击connect按钮和连接服务器成功之后点击peer名都会进入OnDefaultAction函数

void MainWnd::OnDefaultAction() {
  if (!callback_)
    return;
  //点击connect按钮
  if (ui_ == CONNECT_TO_SERVER) {
    std::string server(GetWindowText(edit1_));
    std::string port_str(GetWindowText(edit2_));
    int port = port_str.length() ? atoi(port_str.c_str()) : 0;
    //登陆服务器
    callback_->StartLogin(server, port);
    //点击peer名
  } else if (ui_ == LIST_PEERS) {
    LRESULT sel = ::SendMessage(listbox_, LB_GETCURSEL, 0, 0);
    if (sel != LB_ERR) {
      LRESULT peer_id = ::SendMessage(listbox_, LB_GETITEMDATA, sel, 0);
      if (peer_id != -1 && callback_) {
          //连接到peer
        callback_->ConnectToPeer(peer_id);
      }
    }
  } else {
    MessageBoxA(wnd_, "OK!", "Yeah", MB_OK);
  }
}

首先看一下怎么连接服务器的

void PeerConnectionClient::DoConnect() {
    //创建control_socket和hanging_get_两个AsyncSocket,等待socket事件
    //control_socket_和hanging_get_是两个指向AsyncSocket的智能指针
  control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
  hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
  //连接socket信号和槽
  InitSocketSignals();
  char buffer[1024];
  sprintfn(buffer, sizeof(buffer),
           "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str());
  onconnect_data_ = buffer;

  //control_socket_连接服务器,等待连接成功信号,调用OnConnect槽函数
  bool ret = ConnectControlSocket();
  if (ret)
    state_ = SIGNING_IN;
  if (!ret) {
    callback_->OnServerConnectionFailure();
  }
}

这里因为是异步的socket,通过注册socket信号的槽函数,会在socket连接成功和读socket的时候触发相应的事件,从而调用和信号绑定的槽函数

void PeerConnectionClient::InitSocketSignals() {
  RTC_DCHECK(control_socket_.get() != NULL);
  RTC_DCHECK(hanging_get_.get() != NULL);
  // control_socket_关闭信号连接OnClose槽函数
  control_socket_->SignalCloseEvent.connect(this,
      &PeerConnectionClient::OnClose);
  //hanging_get_关闭信号连接OnClose槽函数
  hanging_get_->SignalCloseEvent.connect(this,
      &PeerConnectionClient::OnClose);
  //control_socket_连接信号连接OnConnect槽函数
  control_socket_->SignalConnectEvent.connect(this,
      &PeerConnectionClient::OnConnect);
  //hanging_get_连接信号连接OnHangingGetConnect槽函数
  hanging_get_->SignalConnectEvent.connect(this,
      &PeerConnectionClient::OnHangingGetConnect);
  //control_socket_读信号连接了OnRead槽函数
  control_socket_->SignalReadEvent.connect(this,
      &PeerConnectionClient::OnRead);
  //hanging_get_读信号连接了OnHangingGetRead槽函数
  hanging_get_->SignalReadEvent.connect(this,
      &PeerConnectionClient::OnHangingGetRead);
}

所以直接去看PeerConnectionClient的OnConnect函数

void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) {
  RTC_DCHECK(!onconnect_data_.empty());
  //control_socket_连接服务器成功就发送  "GET /sign_in?%s HTTP/1.0\r\n\r\n"
  //成功后,服务器会返回当前 channel连接的其他peer ,"200 Added"
  size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
  RTC_DCHECK(sent == onconnect_data_.length());
  onconnect_data_.clear();
}

连接服务器成功之后会向服务器发送登录请求,成功之后,服务器返回当前channel连接的其他peer名,接着就去看一下PeerConnectionClient的OnRead函数

void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) 
{
     ....
    //peer连接成功
    callback_->OnPeerConnected(id, name);
     ....
        //登录服务器成功之后,切换到显示已登录用户列表UI
    callback_->OnSignedIn();
}

这时就到了显示peer名的界面了,当点击peer名时会通过消息循环调用上面的OnDefaultAction函数

void Conductor::ConnectToPeer(int peer_id) {
  RTC_DCHECK(peer_id_ == -1);
  RTC_DCHECK(peer_id != -1);

  if (peer_connection_.get()) {
    main_wnd_->MessageBox("Error",
        "We only support connecting to one peer at a time", true);
    return;
  }
  //初始化PeerConnection
  if (InitializePeerConnection()) {
    peer_id_ = peer_id;
    //创建一个offer!!!!
    peer_connection_->CreateOffer(this, NULL);
  } else {
    main_wnd_->MessageBox("Error", "Failed to initialize PeerConnection", true);
  }
}
bool Conductor::InitializePeerConnection() {
  RTC_DCHECK(peer_connection_factory_.get() == NULL);
  RTC_DCHECK(peer_connection_.get() == NULL);

  //创建PeerConnectionFactory
  peer_connection_factory_  = webrtc::CreatePeerConnectionFactory();
  ....
   //添加stream,切换到stream UI
  AddStreams();
  ....
  }

然后就开始进行通信了

void Conductor::AddStreams() {
  if (active_streams_.find(kStreamLabel) != active_streams_.end())
    return;  // Already added.

  rtc::scoped_refptr audio_track(
      peer_connection_factory_->CreateAudioTrack(
          kAudioLabel, peer_connection_factory_->CreateAudioSource(NULL)));

  rtc::scoped_refptr video_track(
      peer_connection_factory_->CreateVideoTrack(
          kVideoLabel,
          peer_connection_factory_->CreateVideoSource(OpenVideoCaptureDevice(),
                                                      NULL)));
  main_wnd_->StartLocalRenderer(video_track);
  //创建MediaStream采集并传送本地音视频
  rtc::scoped_refptr stream =
      peer_connection_factory_->CreateLocalMediaStream(kStreamLabel);

  stream->AddTrack(audio_track);
  stream->AddTrack(video_track);
  if (!peer_connection_->AddStream(stream)) {
    LOG(LS_ERROR) << "Adding stream to PeerConnection failed";
  }
  typedef std::pair >
      MediaStreamPair;
  active_streams_.insert(MediaStreamPair(stream->label(), stream));
  //切换到StreamingUI
  main_wnd_->SwitchToStreamingUI();
}

5.程序主要流程图

demo的主要程序流程图如下图所示

WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第5张图片

四、运行效果

WebRTC学习进阶之路 --- 十三、分析源码音视频互动peerconnection-client+server实例_第6张图片

WebRTC学习进阶之路系列总目录:https://blog.csdn.net/xiaomucgwlmx/article/details/103204274

你可能感兴趣的:(WebRTC学习进阶之路系列)