在成功编译WebRTC源码之后,没有编译成功的可以直接下载我编译好的。https://github.com/hujianhua888/webrtc_vs2015
成功编译后,可以运行WebRTC自带的例子体验一对一音视频通信效果。使用src/out/Debug 目录下的peerconnection_client.exe 和 peerconnection_server.exe两个文件,最终运行的架构图如下图所示:
效果如下:
局域网运行PeerConnection 例子需要用到两台电脑,并要求两台电脑都配置有摄像头和麦克风(没有的话,使用虚拟摄像头,看我之前的帧子)。测试步骤如下:
1. 电脑A运行peerconnection_server.exe。
2. 电脑A运行peerconnection_client.exe, Server一栏输入 localhost,点击Connect。
3. 电脑B运行peerconnection_client.exe,Server一栏输入电脑A的局域网ip地址,点击Connect。
4. 电脑A或电脑B双击列表框出现的第一个选项, 建立音视频通信。
peerconnection_client 客户端代码框图如下:
界面消息分发处理:
下面函数,主要是私有消息,当我们通过程序处理得到sdp等信息时,通过调用
void Conductor::OnSuccess(webrtc::SessionDescriptionInterface* desc) 调用sdp信息写
jmessage[kSessionDescriptionSdpName] = sdp;
SendMessage(writer.write(jmessage));
通过main_wnd_->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg)放入队列中,然后
UIThreadCallback回调,进行消息的发送。
另外还有void Conductor::OnAddStream以及void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate) 使用同样的方法进行消息发送。
bool MainWnd::PreTranslateMessage(MSG* msg) {
bool ret = false;
if (msg->message == WM_CHAR) {
if (msg->wParam == VK_TAB) {
HandleTabbing();
ret = true;
} else if (msg->wParam == VK_RETURN) {
OnDefaultAction();
ret = true;
} else if (msg->wParam == VK_ESCAPE) {
if (callback_) {
if (ui_ == STREAMING) {
callback_->DisconnectFromCurrentPeer();
} else {
callback_->DisconnectFromServer();
}
}
}
} else if (msg->hwnd == NULL && msg->message == UI_THREAD_CALLBACK) {
//通过此函数把SDP,ICE等信息发送到服务器
callback_->UIThreadCallback(static_cast<int>(msg->wParam),
reinterpret_cast<void*>(msg->lParam));
ret = true;
}
return ret;
}
Ui界面点击通过回调函数如下。
以及socket收到消息时,通过下面回调进行处理。
LRESULT CALLBACK MainWnd::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
OnMessage//点击UI界面分发的消息主要通过此函数处理。
//bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result),实现包括连接peer功能。
}
LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam) {
OnMessage//收到服务器的消息通过此函数处理,并调用OnSocketNotify处理消息。
}
OnMessage创建时用来处理界面的消息程序,OnPaint()里面包括对视频图像的显示功能。
if (ui_ == STREAMING && remote_renderer && local_renderer) {
AutoLock local_lock(local_renderer);
AutoLock remote_lock(remote_renderer);
......
}
ui_值为CONNECT_TO_SERVER,LIST_PEERS,首先是为CONNECT_TO_SERVER显示连接到服务器的界面,当连接完成后,并且读到其他peer名字后,会置为LIST_PEERS,显示列表的界面。当点击连接与其他peer连接后,会进入到显示视频的界面。
bool MainWnd::OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT* result) {
switch (msg) {
case WM_ERASEBKGND:
*result = TRUE;
return true;
case WM_PAINT:
OnPaint();
return true;
case WM_SETFOCUS:
if (ui_ == CONNECT_TO_SERVER) {
SetFocus(edit1_);
} else if (ui_ == LIST_PEERS) {
SetFocus(listbox_);
}
return true;
case WM_SIZE:
if (ui_ == CONNECT_TO_SERVER) {
LayoutConnectUI(true);
} else if (ui_ == LIST_PEERS) {
LayoutPeerListUI(true);
}
break;
case WM_CTLCOLORSTATIC:
*result = reinterpret_cast(GetSysColorBrush(COLOR_WINDOW));
return true;
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;
case WM_CLOSE:
if (callback_)
callback_->Close();
break;
}
return false;
}
OnDefaultAction 表示连接到服务器按下处理。
(1)如果点击connect,那么执行StartLogin表示连接到服务器。那么将准备建立连接到服务器。那么会执行Connect等函数。其中DoConnect表示建立连接,OnConnect 表示发送消息,并读取peer列表,并且显示。详见下面的描述
(2)当得到peer列表时,点击相对应的peer,那么执行ConnectToPeer,表示创建p2p连接。那么会调用CreatePeerConnection等函数,创建本地信息。详见下面的描述
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);
} 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_) {
callback_->ConnectToPeer(peer_id);
}
}
} else {
MessageBoxA(wnd_, "OK!", "Yeah", MB_OK);
}
连接到服务器
执行StartLogin连接到服务器,当建立连接后,然后会收到服务器发过来的信息,包括peer name以及id,下面过程是读取其他客户端peer的名字。并进行显示。主要是socket接口回调PeerConnectionClient::OnRead来得到消息。
void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) ->
callback_->OnSignedIn()->SwitchToPeerList->LayoutPeerListUI
if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
&connected) && id != my_id_) {
peers_[id] = name;
callback_->OnPeerConnected(id, name);
}
当得到其他peer的名字,会把界面转为peer 列表。会调用PeerConnectionClient里面的callback_->OnSignedIn();
即void Conductor::OnSignedIn() ->SwitchToPeerList进行转化。
连接到其他peer
当点击其他peer名字,执行:
MainWnd::WndProc->OnDefaultAction->ConnectToPeer->CreatePeerConnection->CreatePeerConnection
MainWnd::WndProc->OnDefaultAction->ConnectToPeer->CreatePeerConnection-> AddStream
ConnectToPeer当第二次调用OnDefaultAction,当前客户端连接到另一个客户端,即建立p2p连接。那么其中会调用AddStreams(), creatoffer等函数。
其中在AddStreams()中调用:
StartLocalRenderer 显示本地流。
AddTrack 把流加载,用于进行传输。
通过creatoffer得到本地的SDP信息后,然后需要把SDP信息发送到服务器。主要是通信把消息放在队列中,然后通过函数void Conductor::UIThreadCallback(int msg_id, void* data)实现发送。
void Conductor::UIThreadCallback(int msg_id, void* data)
{
case SEND_MESSAGE_TO_PEER: {//SEND_MESSAGE_TO_PEER 表示把当前的信息发送给服务器
LOG(INFO) << "SEND_MESSAGE_TO_PEER";
std::string* msg = reinterpret_cast<std::string*>(data);//SDP信息
if (!client_->SendToPeer(peer_id_, *msg) && peer_id_ != -1) {
LOG(LS_ERROR) << "SendToPeer failed";
DisconnectFromServer();
}
}
case NEW_STREAM_ADDED: {//当建立p2p连接时,收到远程流,并且用于显示
webrtc::MediaStreamInterface* stream =
reinterpret_cast(
data);
webrtc::VideoTrackVector tracks = stream->GetVideoTracks();
// Only render the first track.
if (!tracks.empty()) {
webrtc::VideoTrackInterface* track = tracks[0];
main_wnd_->StartRemoteRenderer(track);
}
stream->Release();
break;
}
}
OnMessage当建立socket连接时,用来接受服务器发送消息。其中调用OnSocketNotify
void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) {
case FD_READ:
if (error != ERROR_SUCCESS) {
ReportWSAError("WSAAsync:read notify", error, addr_);
} else {
SignalReadEvent(this);
}
break;
case FD_WRITE:
if (error != ERROR_SUCCESS) {
ReportWSAError("WSAAsync:write notify", error, addr_);
} else {
SignalWriteEvent(this);
}
break;
}
通过Win32Window::WndProc->OnSocketNotify->OnMessageFromPeer 表示收到远程的信息,其中会调用下面的OnAddStream等函数,被OnSocketNotify读函数调用,收到远程SDP等信息。
Win32Window::WndProc->OnSocketNotify->OnMessageFromPeer->SetRemoteDescription->void Conductor::OnAddStream 远程流调用函数,通过此方法接受远程信息,与socket读数据有点区别。
Win32Window::WndPro->void Conductor::OnIceCandidate(const webrtc::IceCandidateInterface* candidate)->SendMessage(writer.write(jmessage))->QueueUIThreadCallback(SEND_MESSAGE_TO_PEER, msg)
收到ice信息。然后回调发送下一ice消息给远程。
如果收到SDP信息,在OnMessageFromPeer调用下面函数进行远程SDP设置。
SetRemoteDescription->OnAddStream->main_wnd_->QueueUIThreadCallback(NEW_STREAM_ADDED, stream.release());通过此函数回调准备发送下一次的ICE信息。然后通过UIThreadCallback主动发送信息。
通过Win32Window::WndProc->OnSocketNotify-> OnIceCandidate 收到ice消息
void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) 读服务器的数据。
void MainWnd::VideoRenderer::OnFrame 表示接受视频数据。
重新VideoRenderer:此函数,这样能被调用。
void OnFrame(const webrtc::VideoFrame& frame) override;