WebRTC Native层框架

Fiona ·
更新时间:2024-09-21
· 832 次阅读

WebRTC Native框架

我的书:

购买链接:

京东购买链接

淘宝购买链接

当当购买链接

WebRTC还是比较庞大的,乍一看无从下手,本篇以WebRTC自带的例子,阐述WebRTC Native核心的音频、视频和信令三个部分,WebRTC本身架构是P2P的,信令的部分也是围绕P2P展开的,好了,废话不多,直接上正文了。

WebRTC是Google开源的Web实时音视频通信框架。其提供P2P的音频、视频和一般数据传输协议栈的支持,其音频主要包括:采集播放、众多音频编解码器、语音增强、回声消除、网络均衡和拥塞控制等音频处理单元,其视频主要包括:采集播放,丢包隐藏,视频增强和编解码几个部分,支持的编解码有H264、VP8,VP9,AV1、H265在2020年月已经基本集成完毕;在网络方面WebRTC提供针对音视频的动态抖动buffer管理和丢包隐藏处理,器也提供基于STURN和TRUN的P2P的多媒体数据传输。

理解WebRTC Native层需要非常多的知识,包括各种网络协议,网络通信,音频视频编解码,音视频处理,音视频数据采集和播放,服务器、客服端通信架构以及多平台等,具体到编程实现上,包括object c 、c/c++,java,涉及平台包括苹果、Linux、windows以及android,由于官方WebRTC给的nija编译架构,这一编译系统屏蔽了较多的细节,这使得基于vs2019、Xcode以及androidstudio和GNU make编译需要重新搞一遍,很多新手在编译WebRTC是遇到的难题就退缩了。

本篇文章以Native层为出发点,对于Web浏览器不涉及,WebRTC Natvie代码的核心部分是音频、视频以及信令三个部分。为了便于理解这里以WebRTC自带的例子入手,涉及到Ubuntu和IOS两个平台的编译,例子在exmaple目录文件夹下。

WebRTC代码架构

WebRTC目录结构如下所示,其中紫色的为目录。

各个目录的功能如下:

api目录:是对WebRTC功能件的封装,以更方便应用层调用,这里封装的内容包括audio、video、数据通道以及RTP传输,并在create_peerconnection_factory.h文件中定义了P2P通信的核心类PeerConnectionFactoryInterface;

audio目录:这里的audio层是用于发送和接收音频数据流的网络层,真实硬件的采集播放放在adm,增强处理放在了apm,里,adm和apm并不在这一目录下;

base目录:提供了一些依赖OS的基础函数,比如内存管理等;

build相关目录:使用与编译WebRTC的,在编译小节中会有编译说明;

call目录:从字面可以知道是用于通信用的,主要是RTP和RTCP相关协议的封装一遍WebRTC使用;

common_audio和common_video目录:音视频的各种算法都可能用到的,比如fir滤波,环形缓冲区,窗函数等;

examples:P2P等各个平台各种例子所在的目录;

media:是对video和audio增强和编解码的封装层,即video engine和audio engine。

modules:音视频具体功能实现所在的目录,如音视频编解码实现;音频混音、处理以及设备管理,视频采集播放以及数据发送和占用带宽估计等;

pc目录:P2P连接实现的核心目录;

sdk目录:android平台应用层Java和MAC平台应用层Object C访问natvie层的桥接层;

server端

STUN服务器:用于获取设备的外网地址;

TURN服务器:在P2P通信失败后用于中继;

ICE框架,整合了TURN和STUN。

信令服务器:负责端到端的连接,如SDP,candidate等;

因为由于IPv4地址数量不够用和安全的问题,开会的双方基本都在防火墙和NAT之后,通过运营商接入公网,当在家时手机、电脑、网络电视通过电信路由器上网时,路由器分配给我们的地址就是“192.168.XXX.XXX”,但是在公网上我们的数据头地址被转换成电信服务商提供的地址了,如果双发希望直接通信而不需要公共服务器中转(加大了延迟和丢包的不确定性)数据包,这时需要NAT穿透技术,STUN和TURN就是这种透传协议,ICE是一套整合了这两个协议的框架。

client端

Android IOS Windows MAC Linux 浏览器。

由于不同平台使用了不同的UI库和编程语言,他们的实现差异很大,但是不同的平台都会支持c/c++,所以为了适配不同的平台,WebRTC提供了SDK层,Linux平台基于使用GTK的c++,MAC和IOS平台基于使用cocoa库的Object c,android平台基于JAVA,为了让这些平台都能够调用c/c++核心函数,WebRTC提供了android和object c的封装层,这称为SDK层,android中使用了JNI机制使得UI层的JAVA程序和实现核心功能的c/c++程序可以互相调用,类似的object c使用了.mm扩展程序是得UI的.m程序可以和c/c++互相调用。由于访问各个平台都提供了C API(为了效率),所有已在音视频以及网络API都可以直接通过包含不同操作系统的头文件来实现跨平台差异化编译。

WebRTC官方工程里包括了一些应用程序例子,它们位于src/examples目录下,首先看下各个目录的作用。

peerconnection:windows和linux平台下使用Native API进行P2P通信的例子,其中客户端应用程序在client目录,服务器端应用程序在server目录下。客户端具有简单的音视频功能,服务器端使得客户端程序能够通过信令开启会议。整个会议过程需要服务器先启动服务,如./peerconnection_server --port=8888,正确启动后会有如下输出:

Server listening on port 8888

然后启动客户端,客户端UI包括如下部分,connecting to a server:在客户端程序启动时,需要指定服务器IP地址,然后可以点击connect按钮;其中localhost表示本机地址。

select a peer:当不同的client端连接到server时,他们就会互相发现对方如下所示(这是因为两个client是在同一个电脑不同终端启动的,所以两边看到的都是gsc@240),这是可以直接双击或者选中后回车建立P2P连接;

视频会议:当成功建立端到端的链接后,桌面会以全屏的形式显示视频界面,如下;

Ending chat session:当按Esc键时会推回到P2P列表选择界面;

Ending connection:继续按Esc将回到server连接界面;

工程编译 export PKG_CONFIG_PATH=$WEBRTCBUILDS_FOLDER/lib/Release/pkgconfig Go to the peerconnection server folder g++ -o peerconnection_server main.cc data_socket.cc peer_channel.cc utils.cc \ $(pkg-config --cflags --libs --define-variable=prefix=$WEBRTCBUILDS_FOLDER libwebrtc_full) Go to the peerconnection client folder g++ -o peerconnection_client linux/main.cc linux/main_wnd.cc conductor.cc defaults.cc peer_connection_client.cc \ $(pkg-config --cflags --libs --define-variable=prefix=$WEBRTCBUILDS_FOLDER libwebrtc_full) \ $(pkg-config --cflags --libs gtk+-2.0) \ $(pkg-config --cflags --libs x11) WebRTC peerconnection分析

在WebRTC自带的端到端连接的例子中,客户端A和客户端B均通过socket和信令服务器建立TCP连接,object C下可以使用cocoaAsyncSocket框架;

然后客户端A通过voiceegine和videoengine获取多媒体数据并创建PeerConnection对象,然后通过AddTracks将音视频添加到PeerConnection中;

接下来客户端A调用PeerConnection对象的CreateOffer方法创建SDP offer,然后将这个SDP通过signaling server转发给客户端B,B在收到服务端转发的SDP offer之后,调用CreateAnswer创建SDP 应答,并通过信令服务器转发给A,客户端A收到SDP 应答之后,两边就完成了多媒体能力的协商,为音视频流的接收做好了准备;

接下来打洞以便进行P2P多媒体数据传输,首先客户端A通过PeerConnection创建时的参数等待来自ICE服务器的通信,获取自己的candidate,当获取到candidate时客户端会自动调用OnIceCandidate,客户端A通过signaling server将自己的candidate发送给客户端B,客户端B用类似的逻辑(同一套代码)将自己的candidate发送给客户端A,至此candidate握手完成,在SDP和candidate握手完成之后,两个客户端之间就建立了一条P2P连接,视频流就可以不通过Server端而直接进行传输;

PeerConnection应用层

这小节主要讲述应用程序是如何和native层代码交互的,使用WebRTC工具编译的native层仓库代码会链接到libjingle_peerconnection.so库中,上层调用这个库提供的音频、视频以及网络功能实现控制。其中linux和windows的上层界面开发较为相似,MAC/IOS(涉及object c)和android(涉及java)较为相似,Linux和windows通过c++直接调用natvie层代码,相对而言比较容易入门。

server端逻辑

server端首先启动,使用了socket编程,然后等待客户端连接,当客户端A连接时,会记录客户端A的信息,然后又有客户端B接入(和A一样会有HTTP的GET请求),这是会记录客户端B的信息,并将客户端A推动给客户端B,客户端B推送给客户端A,使得它们互相之间能够发现对方,然后客户端A点解连接客户端B,这是它们会启动SDP协议,以协商通信能力,并最终启动视频会议,服务端的逻辑较为简单,这里以核心代码片段说明:

int main(int argc, char* argv[]) { //首先创建侦听套接字,默认端口号位8888 ListeningSocket listener; if (!listener.Create()) { PeerChannel clients; typedef std::vector SocketArray; SocketArray sockets; //quit 标识是是否退出的标志,否则这个循环一直会侦听网络端口数据情况 while (!quit) { fd_set socket_set; FD_ZERO(&socket_set); if (listener.valid()) FD_SET(listener.socket(), &socket_set); //在新客户端接入时,会进入以下逻辑, if (FD_ISSET(listener.socket(), &socket_set)) { DataSocket* s = listener.Accept(); if (sockets.size() >= kMaxConnections) { 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请求,服务器给当前客户列表中的每个都发一遍新用户的信息,格式是",,1\r\n",服务器将现有的peers列表信息以如下格式连接",,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_refptr 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_refptr 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_ptr 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_ptr 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 #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 () @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, RTCSignalingStateHaveRemoteOffer, RTCSignalingStateHaveRemotePrAnswer, // Not an actual state, represents the total number of states. RTCSignalingStateClosed, }; /** Represents the ice connection state of the peer connection. */ typedef NS_ENUM(NSInteger, RTCIceConnectionState) { RTCIceConnectionStateNew, RTCIceConnectionStateChecking, RTCIceConnectionStateConnected, RTCIceConnectionStateCompleted, RTCIceConnectionStateFailed, RTCIceConnectionStateDisconnected, RTCIceConnectionStateClosed, RTCIceConnectionStateCount, }; /** Represents the combined ice+dtls connection state of the peer connection. */ typedef NS_ENUM(NSInteger, RTCPeerConnectionState) { RTCPeerConnectionStateNew, RTCPeerConnectionStateConnecting, RTCPeerConnectionStateConnected, RTCPeerConnectionStateDisconnected, RTCPeerConnectionStateFailed, RTCPeerConnectionStateClosed, }; /** Represents the ice gathering state of the peer connection. */ typedef NS_ENUM(NSInteger, RTCIceGatheringState) { RTCIceGatheringStateNew, RTCIceGatheringStateGathering, RTCIceGatheringStateComplete, }; natvie API

在WebRTC应用程序示例中,多次使用到了API层,P2P连接的核心对象是peer_connection_factory_,其定义如下:

RTC_EXPORT rtc::scoped_refptr CreatePeerConnectionFactory( rtc::Thread* network_thread, rtc::Thread* worker_thread, rtc::Thread* signaling_thread, rtc::scoped_refptr default_adm, rtc::scoped_refptr audio_encoder_factory, rtc::scoped_refptr audio_decoder_factory, std::unique_ptr video_encoder_factory, std::unique_ptr video_decoder_factory, rtc::scoped_refptr audio_mixer, rtc::scoped_refptr audio_processing); } // namespace webrtc

其中三个线程分别是网络通信,工作者线程和信号线程;这之后分别是audio和video,本节就在API层展开多媒体的核心内,至于类是如何管理和实现多媒体功能的在video和audio章节中会有详细叙述。

adm

adm是audio device module的简写,这个类的定义在./modules/audio_device/include/audio_device.h,adm要适配不同的操作系统,所以定义了一个枚举类型来表征,这个枚举类型,显示了在所有平台上OS支持的audio API。

enum AudioLayer { kPlatformDefaultAudio = 0, kWindowsCoreAudio, kWindowsCoreAudio2, kLinuxAlsaAudio, kLinuxPulseAudio, kAndroidJavaAudio, kAndroidOpenSLESAudio, kAndroidJavaInputAndOpenSLESOutputAudio, kAndroidAAudioAudio, kAndroidJavaInputAndAAudioOutputAudio, kDummyAudio, };

WebRTC线程模型

WebRTC Native API使用信令线程(Signaling thread)和工作者线程(worker thread)这两个全局线程,应用程序可以自行实现这两个线程或者由WebRTC内部创建。Stream API和PeerConnection API的调用都会被代理到信令线程,这就意味着应用程序可以任何线程调用这些API。

Stream API定义于:media_stream_interface.h

PeerConnection API定义于:peer_connection_interface.h

由于不同平台的图形界面实现方式并不一样,MAC/IOS使用Cocoa SDK,安卓平台使用Android SDK,Linux 平台使用GTK,而Windows平台使用windows SDK,它们的编程差异比较大,实现的机理也有差异,为方便不同平台调用Stream API和Peerconnection API来使用Natvie 层的相关组件,都会在这个原生的基础上封装出一套代码以便使用。

P2P Native层 API

MediaStream:获取本地麦克风和camera的音视频同步多媒体流;

RTCPeerConnection:构建点对点之间稳定、高效的流传输组件;

RTCDataChannel:P2P之间构建一个高吞吐量、低延迟的信道,用于传输任意数据;

MediaSteam API

WebRTC中每一种多媒体流类型都可以使用流接口来表示(MediaStreamTrackInterface类,可以用来表示一种或多种语音或者视频流,定义于media_stream_interface.h ),而流媒体的实现则用(MediaStream类实现,定义于media_stream.h),这是个聚合了若干多媒体流(多语音流,多视频流)的接口。

api/media_stream_interface.h 和pc/media_stream.h

PeerConnection API

由于这个类比较长,所以这里不再展示其和其它类之间的关系了。

Voice Engine

WebRTC的语音引擎的类嵌套关系如下图所示;

audio streaming

Video Engine

264编码过程

编码硬件加速

增加新的编码器

shichaog 原创文章 179获赞 273访问量 96万+ 关注 他的留言板 展开阅读全文
作者:shichaog



native webrtc 框架

需要 登录 后方可回复, 如果你还没有账号请 注册新账号