WebRTC Native框架(五),金三银四我带你去BAT面试现场

delete s;  // sorry, that’s all we can take.

printf(“Connection limit reached\n”);

} else {

//如果新连接被成功添加到SocketArray中,则中断会打印改信息

sockets.push_back(s);

printf(“New connection…\n”);

}

}

//在后续的循环中,由于SocketArray中有新的连接,所以会进入如下逻辑

for (SocketArray::iterator i = sockets.begin(); i != sockets.end(); ++i) {

DataSocket* s = *i;

if (FD_ISSET(s->socket(), &socket_set)) {

if (s->OnDataAvailable(&socket_done) && s->request_received()) {

//从服务端注册列表中,检索这个client,由于是for循环从begin到end的遍历,

//所以遍历完了之后,所有客户端界面都会显示连上服务器的其它客户端,这样想和谁通信,

//客户端上点击对应的客户端项就行了,如果未找到,返回NULL

ChannelMember* member = clients.Lookup(s);

if (member || PeerChannel::IsPeerConnection(s)) {

//如果发现s是注册,并且这个s并没有在服务端的注册列表中,则会用AddMember将其添加到注册列表中

if (!member) {

if (s->PathEquals(“/sign_in”)) {

clients.AddMember(s);//这里的AddMember会打印记录的客户端名,如截图的gsc@240

}

//如果客户端是等待状态,这是什么也不用做,等到有message时才进行处理

} else if (member->is_wait_request(s)) {

// no need to do anything.

socket_done = false;

//流程到这里,说明这个客户端已经在服务器注册了,并且这次收到的HTTP消息是message,不是wait,sign_out之类的

} else {

//这里找到想要通信客户端(设为B)的ChannelMember对象,如果server端没找到,则返回NULL

ChannelMember* target = clients.IsTargetedRequest(s);

if (target) {

//这里将包含客户端A SDP协议信息转送给B

member->ForwardRequestToPeer(s, target);

} else if (s->PathEquals(“/sign_out”)) {

s->Send(“200 OK”, true, “text/plain”, “”, “”);

}

}

}

}

}

当client点击connect之后,客户端给服务器发过去"/sign_in"的get请求,服务器给当前客户列表中的每个都发一遍新用户的信息,格式是"<client_name>,<client_id>,1\r\n",服务器将现有的peers列表信息以如下格式连接"<client_name>,<client_id>,if_connected?1:0\r\n"发送给新来的client.得到这些信息后客户端就会把他们显示在pees列表中。客户端每次给服务器发送消息之后都要发送一次"/wait"的get请求,目的是提供response对象,以便服务器在需要给客户端发消息时使用。当客户端双击某个peer发起连接时,会给服务器发来"/message"的post请求,请求的peer_id参数是发起方的id,to参数是要连接的peer的id,内容是sdp信息;服务器把内容转发给相应id的客户端,客户端接收到后解析sdp,得到媒体流信息(是否含音视频,音视频参数等),在本地建立媒体流通道,以便之后接收媒体数据和传给主窗体显示;完了创建本地的sdp,并发回给对方。客户端从STUN/TURN服务器获取本地地址(candidate),经由服务器中转发给对方(同样走/messgae),对方接收到后会向该地址发送消息等待回应以验证是否可连通,联通成功peerconnection就此建立完成,音视频数据就可以通过基于udp的rtp/rtcp协议在peerconnection之间传输。

客户端逻辑

客户端稍微复杂些,和server端相比,多了多媒体内容,但是WebRTC的例子非常巧妙,使用较少的代码就实现待UI的视频通信的完整例子。

Conductor是封装了windows和PeerConnection两个主要类,是该工程的核心,conductor通过CreatePeerConnectionFactory方法创建PeerConnectionFactoryInterface接口的实现对象,通过改接口创建WebRTC核心协议的PeerConnectionInterface接口对象,PeerConnectionFactoryInterface还提供了创建本地音视频功能的接口,conductor的回调通过PeerConnectionObserver接口完成。

Linux UI

linux UI层使用了GTK显示框架,将点击、输入等事件使用信号槽的机制,将界面和需要执行的动作关联起来,如点击加入会议涉及到PeerConnection创建和连接,这里clicked是Button触发的信号,而Button触发时会自动触发row-activated信号,所以下面的两个回调都会被调用到,OnClickedCallback获取用户输入的登录服务IP地址和端口号,并向服务器发起HTTP登录请求,而row-activated信号的回调PeerConnection初始化并创建和发送SDP包。代码实现上将PeerConnection和windows作为两个独立的组件,用conductor类管理起来。

g_signal_connect(button, “clicked”, G_CALLBACK(OnClickedCallback), this);

g_signal_connect(peer_list_, “row-activated”,

G_CALLBACK(OnRowActivatedCallback), this);

P2P的创建过程有些琐碎,这里罗列如下:

peer_connection_factory_ = webrtc::CreatePeerConnectionFactory(

nullptr /* network_thread */, nullptr /* worker_thread */,

nullptr /* signaling_thread */, nullptr /* default_adm */,

webrtc::CreateBuiltinAudioEncoderFactory(),

webrtc::CreateBuiltinAudioDecoderFactory(),

webrtc::CreateBuiltinVideoEncoderFactory(),

webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,

nullptr /* audio_processing */);

//这里config的一些配置包括dtls是否启用等标识

peer_connection_ = peer_connection_factory_->CreatePeerConnection(

config, nullptr, nullptr, this);

rtc::scoped_refptrwebrtc::AudioTrackInterface audio_track(

peer_connection_factory_->CreateAudioTrack(

kAudioLabel, peer_connection_factory_->CreateAudioSource(

cricket::AudioOptions())));

auto result_or_error = peer_connection_->AddTrack(audio_track, {kStreamId});

rtc::scoped_refptr video_device =

CapturerTrackSource::Create();

if (video_device) {

rtc::scoped_refptrwebrtc::VideoTrackInterface video_track_(

peer_connection_factory_->CreateVideoTrack(kVideoLabel, video_device));

main_wnd_->StartLocalRenderer(video_track_);

result_or_error = peer_connection_->AddTrack(video_track_, {kStreamId});

发送完登录请求和SDP请求之后,由网络线程一直检测来自网络的数据,并触发相应的函数,如更新列表和对端发来的数据,最终调用OnMessageFromPeer处理对端发来的数据,如果对端是新来的,则会创建InitializePeerConnection对象,这在主动发起会议时已经见过,如果是主动发起方回调了这个函数,就不会再创建这个对象了,但是依然要解析对端的SDP信息,SDP信息是JSON格式。

std::unique_ptrwebrtc::SessionDescriptionInterface session_description =

webrtc::CreateSessionDescription(type, sdp, &error);

peer_connection_->SetRemoteDescription(

DummySetSessionDescriptionObserver::Create(),

session_description.release());

if (type == webrtc::SdpType::kOffer) {

peer_connection_->CreateAnswer(

this, webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());

}

拿到的信息除了SDP外还可能是打洞信息,则也需要将他们保存下来。

std::unique_ptrwebrtc::IceCandidateInterface candidate(

webrtc::CreateIceCandidate(sdp_mid, sdp_mlineindex, sdp, &error));

if (!peer_connection_->AddIceCandidate(candidate.get())) {

RTC_LOG(WARNING) << “Failed to apply the received candidate”;

return;

}

MAC UI

这里以MAC平台示例说明如何使用WebRTC,下图是object c所写的应用程序的界面,即上一节UI层相关内容:

程序最开始的执行函数在examples/objc/AppRTCMobile/mac目录的main.m(object c下的c扩展文件以.m结尾,c++扩展以.mm结尾),这个main函数如下:

APPRTCAppDelegate.h文件

@interface APPRTCAppDelegate : NSObject

@end

APPRTCAppDelegate.m文件

@implementation APPRTCAppDelegate {

APPRTCViewController* _viewController;

NSWindow* _window;

}

main.m文件

#import <AppKit/AppKit.h>

#import “APPRTCAppDelegate.h”

int main(int argc, char* argv[]) {

@autoreleasepool {

[NSApplication sharedApplication];

APPRTCAppDelegate* delegate = [[APPRTCAppDelegate alloc] init];

[NSApp setDelegate:delegate];

[NSApp run];

}

}

这个函数是object的语法,NSApplicationDelegate是cocoa库里的应用程序代理,本身这个应用程序定理很多方法,如run运行等,@interface 表明APPRTCAppDelegate是继承?了NSApplicationDelegate协议,这个类本在实现时(@implementation)定义了两种组件,APPRTCViewController和NSWindow,NSWindow是上图看到的图像界面,比如长宽之类都在这里,APPRTCViewController用于和用户交互,如输入框和按钮。当应用程序在mian.m里执行到run时,会触发APPRTCAppDelegate.m(这和cocoa里的应用程序生命周期有关)。

- (void)applicationDidFinishLaunching:(NSNotification*)notification {

RTCInitializeSSL();

NSScreen* screen = [NSScreen mainScreen];

NSRect visibleRect = [screen visibleFrame];

NSRect windowRect = NSMakeRect(NSMidX(visibleRect),

NSMidY(visibleRect),

1320,

1140);

NSUInteger styleMask = NSTitledWindowMask | NSClosableWindowMask;

_window = [[NSWindow alloc] initWithContentRect:windowRect

styleMask:styleMask

backing:NSBackingStoreBuffered

defer:NO];

_window.delegate = self;

[_window makeKeyAndOrderFront:self];

[_window makeMainWindow];

_viewController = [[APPRTCViewController alloc] initWithNibName:nil

bundle:nil];

[_window setContentView:[_viewController view]];

}

其中-号表示的私有实现,这里就是设置显示窗的参数,并初始化控制组件(APPRTCViewController),APPRTCViewController负责显示local和remote视频流,会议号和会议控制,当点击加入会议后会执行APPRTCViewController.m中的如下方法:

- (void)startCall:(id)sender {

NSString* roomString = _roomField.stringValue;

// Generate room id for loopback options.

if (_loopbackButton.intValue && [roomString isEqualToString:@“”]) {

roomString = [NSUUID UUID].UUIDString;

roomString = [roomString stringByReplacingOccurrencesOfString:@“-” withString:@“”];

}

[self.delegate appRTCMainView:self

didEnterRoomId:roomString

loopback:_loopbackButton.intValue];

[self setNeedsUpdateConstraints:YES];

}

roomString是前面输入的房间号字符串,如果空的就是会测试模式.

@interface APPRTCViewController ()

<ARDAppClientDelegate, APPRTCMainViewDelegate>

@property(nonatomic, readonly) APPRTCMainView* mainView;

@end

这个控件因为要管理signaling和多媒体传输以及状态和多媒体显示,这里使用ARDAppClientDelegate实现前者,而APPRTCMainViewDelegate实现状态和多媒体显示;尖括号表示APPRTCViewController遵循上面两个protocol,点击start call最终会调到ARDAppClientDelegate里的connectToRoomWithId方法,下面的代码片段是mac目录掉到上一级目录AppRTCMobile的核心代码段。

mac/APPRTCViewController.m

ARDAppClient* client = [[ARDAppClient alloc] initWithDelegate:self];

[client connectToRoomWithId:roomId

settings:[[ARDSettingsModel alloc] init]  // Use default settings.

这个方法定义于src/examples/objc/AppRTCMobile/ARDAppClient.h。

- (void)connectToRoomWithId:(NSString *)roomId

settings:(ARDSettingsModel *)settings

isLoopback:(BOOL)isLoopback {

NSParameterAssert(roomId.length);

NSParameterAssert(_state == kARDAppClientStateDisconnected);

_settings = settings;

_isLoopback = isLoopback;

self.state = kARDAppClientStateConnecting;

//初始化视频编解码参数

RTCDefaultVideoDecoderFactory *decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];

RTCDefaultVideoEncoderFactory *encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];

encoderFactory.preferredCodec = [settings currentVideoCodecSettingFromStore];

_factory = [[RTCPeerConnectionFactory alloc] initWithEncoderFactory:encoderFactory

decoderFactory:decoderFactory];

#if defined(WEBRTC_IOS)

if (kARDAppClientEnableTracing) {

NSString *filePath = [self documentsFilePathForFileName:@“webrtc-trace.txt”];

RTCStartInternalCapture(filePath);

}

#endif

// 申请 TURN.

__weak ARDAppClient *weakSelf = self;

[_turnClient requestServersWithCompletionHandler:^(NSArray *turnServers,

NSError *error) {

if (error) {

RTCLogError(@“Error retrieving TURN servers: %@”, error.localizedDescription);

}

ARDAppClient *strongSelf = weakSelf;

[strongSelf.iceServers addObjectsFromArray:turnServers];

strongSelf.isTurnComplete = YES;

[strongSelf startSignalingIfReady];//关键

}];

// 通过room server加入开会房间

[_roomServerClient joinRoomWithRoomId:roomId

isLoopback:isLoopback

completionHandler:^(ARDJoinResponse *response, NSError *error) {

ARDAppClient *strongSelf = weakSelf;

if (error) {

[strongSelf.delegate appClient:strongSelf didError:error];

return;

}

NSError *joinError =

[[strongSelf class] errorForJoinResultType:response.result];

if (joinError) {

RTCLogError(@“Failed to join room:%@ on room server.”, roomId);

[strongSelf disconnect];

[strongSelf.delegate appClient:strongSelf didError:joinError];

return;

}

RTCLog(@“Joined room:%@ on room server.”, roomId);

strongSelf.roomId = response.roomId;

strongSelf.clientId = response.clientId;

strongSelf.isInitiator = response.isInitiator;

for (ARDSignalingMessage *message in response.messages) {

if (message.type == kARDSignalingMessageTypeOffer ||

message.type == kARDSignalingMessageTypeAnswer) {

strongSelf.hasReceivedSdp = YES;

[strongSelf.messageQueue insertObject:message atIndex:0];

} else {

[strongSelf.messageQueue addObject:message];

}

}

strongSelf.webSocketURL = response.webSocketURL;

strongSelf.webSocketRestURL = response.webSocketRestURL;

[strongSelf registerWithColliderIfReady];

[strongSelf startSignalingIfReady];

}];

// Join room on room server.

[_roomServerClient joinRoomWithRoomId:roomId

isLoopback:isLoopback

completionHandler:^(ARDJoinResponse *response, NSError *error) {

ARDAppClient *strongSelf = weakSelf;

if (error) {

[strongSelf.delegate appClient:strongSelf didError:error];

return;

}

NSError *joinError =

[[strongSelf class] errorForJoinResultType:response.result];

if (joinError) {

RTCLogError(@“Failed to join room:%@ on room server.”, roomId);

[strongSelf disconnect];

[strongSelf.delegate appClient:strongSelf didError:joinError];

return;

}

RTCLog(@“Joined room:%@ on room server.”, roomId);

strongSelf.roomId = response.roomId;

strongSelf.clientId = response.clientId;

strongSelf.isInitiator = response.isInitiator;

for (ARDSignalingMessage *message in response.messages) {

if (message.type == kARDSignalingMessageTypeOffer ||

message.type == kARDSignalingMessageTypeAnswer) {

strongSelf.hasReceivedSdp = YES;

[strongSelf.messageQueue insertObject:message atIndex:0];

} else {

[strongSelf.messageQueue addObject:message];

}

}

strongSelf.webSocketURL = response.webSocketURL;

strongSelf.webSocketRestURL = response.webSocketRestURL;

[strongSelf registerWithColliderIfReady];

[strongSelf startSignalingIfReady];

}];

}

SDK桥接层

SDK层负责将UI的视窗控件和Natvie层的相关组件(audio/video/network)相关连,这里的关联有两层意义,一层是调用Native层的相关功能,一层是将natvie层的相关类和方法进行聚合使用(如将audio和video组成一个mediastream使用)。在P2P的应用场景中,SDK层主要有video source、video track、audio source、audio track、DataChannel、PeerConnection、RTP、ICE、SessionDescription等组成。从字面即可了解他们的意义,其中PeerConnection是核心中的核心,SDK层的提供的音视频、网络等都或多或少和其相关。

P2P会议的状态由PeerConnection进行管理,signaling state用于信令握手管理,ice连接状态用于透传管理,

/** Represents the signaling state of the peer connection. */

typedef NS_ENUM(NSInteger, RTCSignalingState) {

RTCSignalingStateStable,

RTCSignalingStateHaveLocalOffer,

RTCSignalingStateHaveLocalPrAnswer,

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后送福利了,现在关注我并且加入群聊可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,欢迎加群探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

点击GitHub领取
录播视频图.png

,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-RDcDnqes-1710850517315)]
[外链图片转存中…(img-dGnpYNT9-1710850517316)]
[外链图片转存中…(img-Bu8LNQzQ-1710850517317)]
[外链图片转存中…(img-9LMuTiPq-1710850517317)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-amX65AMg-1710850517318)]

最后送福利了,现在关注我并且加入群聊可以获取包含源码解析,自定义View,动画实现,架构分享等。
内容难度适中,篇幅精炼,每天只需花上十几分钟阅读即可。
大家可以跟我一起探讨,欢迎加群探讨,有flutter—底层开发—性能优化—移动架构—资深UI工程师 —NDK相关专业人员和视频教学资料,还有更多面试题等你来拿

点击GitHub领取
[外链图片转存中…(img-V0vQSH1o-1710850517319)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值