借助于 OpenRTCClient 項(xiàng)目遵绰,我們可以非常方便地編譯出 WebRTC iOS native SDK辽幌,通過 OpenRTCClient 項(xiàng)目提供的 webrtc_pack
工具,我們可以很方便地創(chuàng)建包含了 arm64
椿访、和 x64
兩種還在廣泛使用的 CPU 架構(gòu)二進(jìn)制代碼的 webrtc 靜態(tài)庫文件乌企。這里說明為 iOS 應(yīng)用接入 webrtc 靜態(tài)庫文件的過程。(WebRTC 構(gòu)建系統(tǒng)默認(rèn)也提供了構(gòu)建 Framework 的 target成玫,具體的構(gòu)建 target 為 framework_objc
逛犹。)
在 iOS 應(yīng)用程序中使用 WebRTC iOS native API 的一種比較簡單的接入方式是,在 WebRTC 的構(gòu)建系統(tǒng)中為 iOS 應(yīng)用程序創(chuàng)建一個 GN/ninja 的構(gòu)建 target梁剔,統(tǒng)一用 WebRTC 的構(gòu)建系統(tǒng)來編譯 WebRTC SDK 和 iOS 應(yīng)用程序。但這種 WebRTC native SDK 的接入方式舞蔽,對于我們一般的 iOS 應(yīng)用開發(fā)來說荣病,不是很友好。一般來說渗柿,我們更希望可以拿到一個 SDK 包个盆,里面包含必須的二進(jìn)制庫文件和頭文件,然后修改 iOS 應(yīng)用的 Xcode 工程配置來引入庫朵栖,如向頭文件搜索路徑中添加庫的頭文件路徑等颊亮。
要為 WebRTC native SDK 創(chuàng)建一個開發(fā)包的話,獲得編譯生成的二進(jìn)制庫文件比較容易陨溅,但獲得完整的 API 頭文件比較麻煩终惑。
WebRTC iOS native SDK API 說明
WebRTC C++ 核心庫提供的 API 可以認(rèn)為由三個部分組成:
以
PeerConnectionInterface
為中心的核心框架 API。WebRTC 中 PeerConnection 定義 RTC 的 pipeline门扇,它把各模塊組件有機(jī)地連接起來雹有,構(gòu)造完整的 RTC 場景。核心框架 API 既包括 PeerConnection 的 API臼寄,也包括 PeerConnection 的 pipeline 中各個模塊組件的部分接口霸奕。相關(guān)頭文件位于webrtc/api
目錄下。模塊組件 API吉拳。PeerConnection 的 pipeline 中各個模塊組件提供的 API质帅,如 audio processing 提供的
AudioProcessing
和AudioProcessingBuilder
,位于webrtc/modules/audio_processing/include
留攒,用于桌面共享的 API 位于webrtc/modules/desktop_capture
煤惩。基礎(chǔ)的實(shí)用程序 API。如在
webrtc/rtc_base
目錄下包含了大量的輔助工具 API稼跳,包括日志盟庞,線程,任務(wù)隊(duì)列汤善,同步原語等等等什猖。
WebRTC 的開發(fā)者一定是非常不希望其它開發(fā)者直接使用 WebRTC 的 C++ API 的票彪,否則 WebRTC 的 C++ API 也不至于設(shè)計(jì)的如此混亂:
核心框架 API 的大量頭文件包含了各個模塊組件的頭文件以及基礎(chǔ)的實(shí)用程序頭文件。
核心框架 API 的大量組件依賴于其它部分的接口不狮。
其他開發(fā)者幾乎無法捋出來 WebRTC SDK 的 C++ API 頭文件降铸,因?yàn)樗麄儚V泛分布于整個代碼庫的各個位置。
WebRTC C++ API 的混亂設(shè)計(jì)導(dǎo)致它們完全無法脫離 WebRTC 的源碼庫來使用摇零。
WebRTC 項(xiàng)目倒是很友好地為 Android 和 iOS 開發(fā)者提供了非常干凈的 Java 和 Objective-C API推掸,這些 API 分別位于 webrtc/sdk/android
和 webrtc/sdk/objc
。WebRTC 的 Objective-C API 主要是對底層 C++ API 的封裝驻仅,但也通過定義 RTCVideoEncoderFactory
和 RTCVideoDecoderFactory
將系統(tǒng)提供的視頻硬編硬解的能力接入 PeerConnection 定義 RTC 的 pipeline谅畅。WebRTC 的 Objective-C API 完整頭文件可以通過 webrtc/sdk/BUILD.gn
中定義的構(gòu)建 target "framework_objc" 來了解。
這里我們使用一些 WebRTC 的 C++ API噪服,使用一些 WebRTC 的 Objective-C API毡泻。后面我們還會看到 WebRTC 的 C++ API 其它一些十分坑爹的地方。
通過 Xcode 創(chuàng)建一個工程
- 打開 Xcode粘优。
- 選擇 "Create a new Xcode project"仇味。
- 選擇 "iOS" 標(biāo)簽下的 "App"。
- 輸入產(chǎn)品名稱 "Product Name: "雹顺,選擇 "Team: "丹墨,輸入組織標(biāo)識符 "Organization Identifier:","Interface: " 選擇 "Storyboard"嬉愧,語言 "Language: " 選擇 "Objective-C"贩挣,然后點(diǎn)擊 "Next"。為 iOS 工程選擇一個目錄來創(chuàng)建工程目錄没酣,結(jié)束 Xcode 工程創(chuàng)建過程揽惹。
為 iOS 應(yīng)用工程引入 WebRTC 靜態(tài)庫
WebRTC native SDK 提供的是 C++ 的 API,為了在 iOS 應(yīng)用工程中使用 WebRTC 的 native API四康,需要將引用 WebRTC native API 的源文件后綴名由
.m
修改為.mm
搪搏,這里修改ViewController.m
和main.m
這兩個源文件的后綴名為.mm
。引入 WebRTC 靜態(tài)庫
在這一步中闪金,我們在 main.mm
中包含一些 WebRTC 的頭文件疯溺,調(diào)用一些基本的 API,并使應(yīng)用程序編譯鏈接成功哎垦,并能在 iPhone 模擬器中運(yùn)行起來囱嫩。
我門在 main.mm
中調(diào)用創(chuàng)建 webrtc::PeerConnectionFactoryInterface
的 webrtc::CreatePeerConnectionFactory()
接口,并包含相關(guān)的頭文件漏设,這樣 main.mm
的完整代碼如下:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
#include "api/create_peerconnection_factory.h"
#include "api/peer_connection_interface.h"
#include "api/video_codecs/builtin_video_decoder_factory.h"
#include "api/video_codecs/builtin_video_encoder_factory.h"
#include "rtc_base/thread.h"
int main(int argc, char * argv[]) {
std::unique_ptr<rtc::Thread> network_thread_ = rtc::Thread::CreateWithSocketServer();
network_thread_->SetName("network_thread", nullptr);
if (!network_thread_->Start()) {
fprintf(stderr, "Failed to start network thread\n");
}
std::unique_ptr<rtc::Thread> worker_thread_ = rtc::Thread::Create();
worker_thread_->SetName("worker_thread", nullptr);
if (!worker_thread_->Start()) {
fprintf(stderr, "Failed to start worker thread\n");
}
std::unique_ptr<rtc::Thread> signaling_thread_ = rtc::Thread::Create();
signaling_thread_->SetName("signaling_thread", nullptr);
if (!signaling_thread_->Start()) {
fprintf(stderr, "Failed to start worker signaling thread\n");
}
rtc::scoped_refptr<webrtc::PeerConnectionFactoryInterface> pcf_ = webrtc::CreatePeerConnectionFactory(
network_thread_.get() /* network_thread */,
worker_thread_.get() /* worker_thread */,
signaling_thread_.get() /* signaling_thread */, nullptr /* default_adm */,
webrtc::CreateBuiltinAudioEncoderFactory(),
webrtc::CreateBuiltinAudioDecoderFactory(),
webrtc::CreateBuiltinVideoEncoderFactory(),
webrtc::CreateBuiltinVideoDecoderFactory(), nullptr /* audio_mixer */,
nullptr /* audio_processing */);
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
毫無疑問墨闲,我們應(yīng)該將 WebRTC 的源碼根目錄加入 iOS app 工程的頭文件搜素路徑中去,以便于 Xcode 的構(gòu)建系統(tǒng)可以找到我們包含的那些頭文件郑口,應(yīng)該將 WebRTC 庫文件的路徑加進(jìn)庫的搜索路徑鸳碧,同時應(yīng)該配置工程盾鳞,增加對 webrtc 庫的依賴。
在 Xcode 的左側(cè)瞻离,選中項(xiàng)目腾仅,點(diǎn)擊 Build Settings
標(biāo)簽,找到 Search Paths
套利,然后在 Header Search Paths
中加入 WebRTC 的源碼根目錄為頭文件搜索路徑增加WebRTC 的源碼根目錄推励。在 Search Paths
的 Library Search Paths
中加入 webrtc 庫文件的路徑以將 webrtc 庫文件的路徑添加庫文件搜索路徑中。如:
選中 TARGETS
下我們 iOS 應(yīng)用的 loop_connect
肉迫,選中 Build Phases
验辞,點(diǎn)開 Link Binary With Libraries
,然后將 webrtc 的靜態(tài)庫文件拖進(jìn)來喊衫,如:
- 添加
abseil-cpp
的路徑為頭文件搜索路徑
完成上面的配置之后受神,我們通過 Product
-> Build
第一次構(gòu)建我們的工程。此時報出如下的編譯錯誤:
錯誤提示說找不到頭文件 absl/types/optional.h
格侯。這是由于在 WebRTC 的 API 頭文件中,包含了第三方庫 abseil-cpp
的頭文件财著。這是 WebRTC C++ API 的一個非沉模坑爹的地方 —— 開發(fā)者只是想使用 WebRTC 的 API,但卻不得不把第三方庫 abseil-cpp
也引進(jìn)自己的工程撑教。
這個問題可以通過把 webrtc/third_party/abseil-cpp
的路徑添加進(jìn)工程的頭文件搜索路徑來解決朝墩。
- 定義平臺宏
完成上面的配置之后,再次構(gòu)建我們的工程伟姐。這次報出如下的編譯錯誤:
這次是有幾十個對于用來做網(wǎng)絡(luò)字節(jié)序和本地字節(jié)序之間的轉(zhuǎn)換的符號的引用找不到收苏。這是 WebRTC C++ API 最坑爹的地方了。這需要我們仔細(xì)了解 WebRTC 庫的構(gòu)建過程和一些頭文件的代碼愤兵。這個錯誤主要發(fā)生在 webrtc/rtc_base/ip_address.h
頭文件中鹿霸,這個頭文件相關(guān)的代碼如下:
#if defined(WEBRTC_POSIX)
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
#if defined(WEBRTC_WIN)
#include <winsock2.h>
#include <ws2tcpip.h>
#endif
#include <string.h>
#include <string>
#include "rtc_base/byte_order.h"
#if defined(WEBRTC_WIN)
#include "rtc_base/win32.h"
#endif
#include "rtc_base/system/rtc_export.h"
在 webrtc/rtc_base/ip_address.h
頭文件中,它會根據(jù)不同的平臺宏的定義情況來包含不同的頭文件秆乳,以使得訪問用于執(zhí)行網(wǎng)絡(luò)字節(jié)序和本地字節(jié)序之間的轉(zhuǎn)換的符號成為可能懦鼠。但這里的平臺宏不是編譯器定義的,而是 WebRTC 的構(gòu)建系統(tǒng)定義的屹堰,如 WEBRTC_POSIX
和 WEBRTC_WIN
等肛冶。坑爹的地方在于扯键,WEBRTC_POSIX
和 WEBRTC_WIN
這些平臺宏睦袖,不是根據(jù)編譯器定義的平臺宏在一個基礎(chǔ)的頭文件中定義,并包含在其它頭文件中的荣刑,而是在 BUILD.gn
或 .gni
這樣的構(gòu)建配置文件中定義的馅笙。這使得使用了 WebRTC C++ API 的其它項(xiàng)目根本無法了解到需要去定義這樣的宏伦乔,于是常常會導(dǎo)致編譯 WebRTC 時,WebRTC 的源文件看到的這些頭文件的內(nèi)容和引用這些頭文件的其它項(xiàng)目的代碼看到的這些頭文件的內(nèi)容有巨大的差異延蟹。
對于 iOS评矩,WebRTC 中與它相關(guān)的平臺宏有這樣三個,WEBRTC_POSIX
阱飘,WEBRTC_MAC
和 WEBRTC_IOS
斥杜。對于這個問題,我們需要通過配置工程定義這幾個預(yù)處理器宏來解決沥匈。選擇 Build Settings
--> Apple Clang - Preprocessing
--> Preprocessor Macros
蔗喂,添加這幾個宏,如下圖:
這里遇到的編譯錯誤高帖,屬于 WebRTC 在 API 中對于自定義平臺宏的依賴所造成的相對比較小的問題缰儿。這種做法實(shí)際上還有著極大的隱患。如 WebRTC 的 webrtc/modules/audio_device/audio_device_generic.h
這個頭文件中的一段代碼:
// Play underrun count.
virtual int32_t GetPlayoutUnderrunCount() const;
// iOS only.
// TODO(henrika): add Android support.
#if defined(WEBRTC_IOS)
virtual int GetPlayoutAudioParameters(AudioParameters* params) const;
virtual int GetRecordAudioParameters(AudioParameters* params) const;
#endif // WEBRTC_IOS
virtual void AttachAudioBuffer(AudioDeviceBuffer* audioBuffer) = 0;
virtual ~AudioDeviceGeneric() {}
};
webrtc::AudioDeviceGeneric
這個類接口的定義對于不同平臺來說是不同的散址。如果編譯 WebRTC 的時候定義了宏乖阵,使用 WebRTC 的其它項(xiàng)目在包含這個頭文件的時候沒有定義宏,代碼中調(diào)用這個類的接口预麸,則編譯將安安靜靜地成功完成瞪浸,但在運(yùn)行期,調(diào)用這個類的接口函數(shù)時吏祸,輕則應(yīng)用程序莫名其妙地崩潰对蒲,重則將發(fā)現(xiàn)實(shí)際執(zhí)行的函數(shù)是牛頭不對馬嘴,明明在代碼中調(diào)用了函數(shù) A贡翘,實(shí)際執(zhí)行的確實(shí)函數(shù) B蹈矮。
- 添加對 iOS 系統(tǒng) framework 的依賴
解決了上面的問題之后,再次編譯我們的工程鸣驱,此時將報出大量符號找不到的鏈接錯誤泛鸟,如下圖:
在 WebRTC 中使用了大量的 iOS 系統(tǒng)庫的符號,還需要添加對于相關(guān)系統(tǒng)庫的依賴踊东。報錯中的這些符號需要添加對 AudioToolbox.framework
和 AVFoundation.framework
這兩個 framework 的依賴谈况。類似于前面添加 webrtc 靜態(tài)庫那樣,添加對這兩個 framework 的依賴递胧。
- 引入更多對 iOS 系統(tǒng) framework 的依賴
經(jīng)過了前面的操作碑韵,再次編譯我們的工程,此時 iOS App 工程可以成功編譯缎脾。我們在模擬器上運(yùn)行我們的應(yīng)用祝闻。此時很快就報出了 unrecognized selector sent to . . .
的錯誤,如下圖:
這與 Objective-C 本身的機(jī)制有關(guān)。webrtc 靜態(tài)庫的某些符號沒有鏈接進(jìn)來联喘。這個問題可以通過把 "-ObjC" 添加到 "Build Settings" -> "Linking" -> "Other Linker Flags" 來解決华蜒。這個鏈接選項(xiàng)會促使鏈接器加載 webrtc 靜態(tài)庫中的所有 Objective-C class 或 category。由于這個鏈接選項(xiàng)會使鏈接器加載更多 Objective-C class 或 category豁遭,我們再次編譯時叭喜,報出了更多的符號找不到的鏈接錯誤,如下圖:
這需要我們添加更多對 iOS 系統(tǒng) framework 的依賴蓖谢。關(guān)于這些 iOS 系統(tǒng) framework 的線索捂蕴,可以在 webrtc/sdk/BUILD.gn
找到一部分俺夕。具體來說贡茅,這些需要依賴的 iOS 系統(tǒng) framework 還包括這些:Network.framework
晦款,CoreVideo.framework
梨树,VideoToolbox.framework
,CoreMedia.framework
冻辩,GLKit.framework
圣蝎,OpenGLES.framework
娜膘,QuartzCore.framework
腕够。
- 引用 Objective-C 接口
經(jīng)過上面的配置级乍,我們的集成了 WebRTC iOS native SDK 的 iOS App 已經(jīng)可以正常編譯運(yùn)行起來了。
要想在我們的 iOS App 中引用 WebRTC 的 Objective-C 接口帚湘,如 RTCDefaultVideoEncoderFactory
和 RTCDefaultVideoDecoderFactory
玫荣,還需要給我們的 iOS App 工程的頭文件搜索路徑加上 webrtc/sdk/objc
和 webrtc/sdk/objc/base
這兩個目錄。
- 實(shí)現(xiàn)同一臺設(shè)備上兩個連接之間的音頻數(shù)據(jù)收發(fā)
這里使用 WebRTC 的 API 實(shí)現(xiàn)一個簡單的示例:在同一臺設(shè)備上創(chuàng)建兩個連接客们,一個連接采集發(fā)送音頻,另一個連接接收播放音頻材诽。
首先是 main.mm
底挫,它恢復(fù)到 Xcode 剛剛創(chuàng)建工程時的樣子:
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
然后是 ViewController.mm
文件,它定義我們示例 App 的 UI 界面脸侥,并控制我們示例的流程的初始化建邓、啟動和停止,它的代碼如下:
#import "ViewController.h"
#include <memory>
#import <UIKit/UIButton.h>
#import "sdk/objc/base/RTCVideoRenderer.h"
#if defined(RTC_SUPPORTS_METAL)
#import "sdk/objc/components/renderer/metal/RTCMTLVideoView.h" // nogncheck
#endif
#import "sdk/objc/components/renderer/opengl/RTCEAGLVideoView.h"
#import "sdk/objc/helpers/RTCCameraPreviewView.h"
#import "CallClientWithNoVideo.h"
@interface ViewController ()
@property(nonatomic) __kindof UIView<RTC_OBJC_TYPE(RTCVideoRenderer)> *remoteVideoView;
@property(nonatomic) UIButton * button_init;
@property(nonatomic) UIButton * button;
@property(nonatomic) BOOL started;
@property(nonatomic) std::shared_ptr<CallClientWithNoVideo> call_client;
@end
@implementation ViewController {
UIView *_view;
}
@synthesize button_init = _button_init;
@synthesize button = _button;
@synthesize started = _started;
@synthesize call_client = _call_client;
- (void)loadView {
_view = [[UIView alloc] initWithFrame:CGRectZero];
#if defined(RTC_SUPPORTS_METAL)
_remoteVideoView = [[RTC_OBJC_TYPE(RTCMTLVideoView) alloc] initWithFrame:CGRectZero];
#else
_remoteVideoView = [[RTC_OBJC_TYPE(RTCEAGLVideoView) alloc] initWithFrame:CGRectZero];
#endif
_remoteVideoView.translatesAutoresizingMaskIntoConstraints = NO;
[_view addSubview:_remoteVideoView];
_button_init = [UIButton buttonWithType:UIButtonTypeSystem];
_button_init.translatesAutoresizingMaskIntoConstraints = NO;
[_button_init setTitle:@"Initialize" forState:UIControlStateNormal];
[_button_init setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button_init addTarget:self action:@selector(loopCallInitialize) forControlEvents:UIControlEventTouchUpInside];
[_view addSubview:_button_init];
_button = [UIButton buttonWithType:UIButtonTypeSystem];
_button.translatesAutoresizingMaskIntoConstraints = NO;
[_button setTitle:@"Start loop call" forState:UIControlStateNormal];
[_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_button addTarget:self
action:@selector(loopCallControl)
forControlEvents:UIControlEventTouchUpInside];
[_view addSubview:_button];
UILayoutGuide *margin = _view.layoutMarginsGuide;
[_remoteVideoView.leadingAnchor constraintEqualToAnchor:margin.leadingAnchor].active = YES;
[_remoteVideoView.topAnchor constraintEqualToAnchor:margin.topAnchor].active = YES;
[_remoteVideoView.trailingAnchor constraintEqualToAnchor:margin.trailingAnchor].active = YES;
[_remoteVideoView.bottomAnchor constraintEqualToAnchor:margin.bottomAnchor].active = YES;
[_button_init.leadingAnchor constraintEqualToAnchor:margin.leadingAnchor constant:8.0].active =
YES;
[_button_init.bottomAnchor constraintEqualToAnchor:margin.bottomAnchor constant:8.0].active = YES;
[_button_init.widthAnchor constraintEqualToConstant:100].active = YES;
[_button_init.heightAnchor constraintEqualToConstant:40].active = YES;
[_button.trailingAnchor constraintEqualToAnchor:margin.trailingAnchor constant:8.0].active =
YES;
[_button.bottomAnchor constraintEqualToAnchor:margin.bottomAnchor constant:8.0].active =
YES;
[_button.widthAnchor constraintEqualToConstant:100].active = YES;
[_button.heightAnchor constraintEqualToConstant:40].active = YES;
self.view = _view;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.call_client = nullptr;
}
- (void)loopCallInitialize{
if (self.call_client) {
NSLog(@"Loop call client has been created");
} else {
self.call_client = std::make_shared<CallClientWithNoVideo>();
}
}
- (void)loopCallControl{
if (!self.call_client) {
NSLog(@"Loop call client has not been created");
return;
}
if (self.started) {
self.call_client->StopLoopCall();
[self.button setTitle:@"Start loop call" forState:UIControlStateNormal];
} else {
self.call_client->StartLoopCall();
[self.button setTitle:@"Stop loop call" forState:UIControlStateNormal];
}
self.started = !self.started;
}
@end
然后是實(shí)現(xiàn)了連接創(chuàng)建睁枕,及連接建立的 CallClientWithNoVideo.mm
文件:
CallClientWithNoVideo::CallClientWithNoVideo() : call_started_(false) {
thread_checker_.Detach();
CreatePeerConnectionFactory();
}
void CallClientWithNoVideo::CreatePeerConnectionFactory() {
network_thread_ = rtc::Thread::CreateWithSocketServer();
network_thread_->SetName("network_thread", nullptr);
RTC_CHECK(network_thread_->Start()) << "Failed to start thread";
worker_thread_ = rtc::Thread::Create();
worker_thread_->SetName("worker_thread", nullptr);
RTC_CHECK(worker_thread_->Start()) << "Failed to start thread";
signaling_thread_ = rtc::Thread::Create();
signaling_thread_->SetName("signaling_thread", nullptr);
RTC_CHECK(signaling_thread_->Start()) << "Failed to start thread";
webrtc::PeerConnectionFactoryDependencies dependencies;
dependencies.network_thread = network_thread_.get();
dependencies.worker_thread = worker_thread_.get();
dependencies.signaling_thread = signaling_thread_.get();
dependencies.task_queue_factory = webrtc::CreateDefaultTaskQueueFactory();
cricket::MediaEngineDependencies media_deps;
media_deps.task_queue_factory = dependencies.task_queue_factory.get();
media_deps.audio_encoder_factory = webrtc::CreateBuiltinAudioEncoderFactory();
media_deps.audio_decoder_factory = webrtc::CreateBuiltinAudioDecoderFactory();
media_deps.video_encoder_factory = webrtc::ObjCToNativeVideoEncoderFactory(
[[RTC_OBJC_TYPE(RTCDefaultVideoEncoderFactory) alloc] init]);
media_deps.video_decoder_factory = webrtc::ObjCToNativeVideoDecoderFactory(
[[RTC_OBJC_TYPE(RTCDefaultVideoDecoderFactory) alloc] init]);
media_deps.audio_processing = webrtc::AudioProcessingBuilder().Create();
dependencies.media_engine = cricket::CreateMediaEngine(std::move(media_deps));
RTC_LOG(LS_INFO) << "Media engine created: " << dependencies.media_engine.get();
dependencies.call_factory = webrtc::CreateCallFactory();
dependencies.event_log_factory =
std::make_unique<webrtc::RtcEventLogFactory>(dependencies.task_queue_factory.get());
pcf_ = webrtc::CreateModularPeerConnectionFactory(std::move(dependencies));
RTC_LOG(LS_INFO) << "PeerConnectionFactory created: " << pcf_;
}
bool CallClientWithNoVideo::LoopCallStarted() {
webrtc::MutexLock lock(&pc_mutex_);
return call_started_;
}
void CallClientWithNoVideo::CreateConnections() {
webrtc::MutexLock lock(&pc_mutex_);
webrtc::PeerConnectionInterface::RTCConfiguration config;
config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
// DTLS SRTP has to be disabled for loopback to work.
config.enable_dtls_srtp = false;
send_observer_ = std::make_unique<ConnectionObserver>("SendConn");
webrtc::PeerConnectionDependencies pc_dependencies(send_observer_.get());
send_connection_ = pcf_->CreatePeerConnection(config, std::move(pc_dependencies));
RTC_LOG(LS_INFO) << "Send PeerConnection created: " << send_connection_;
recv_observer_ = std::make_unique<ConnectionObserver>("RecvConn");
webrtc::PeerConnectionDependencies recv_pc_dependencies(recv_observer_.get());
recv_connection_ = pcf_->CreatePeerConnection(config, std::move(recv_pc_dependencies));
RTC_LOG(LS_INFO) << "Receive PeerConnection created: " << recv_connection_;
send_connection_->AddTrack(local_audio_track_, {kStreamId});
RTC_LOG(LS_INFO) << "Local auido track set up: " << local_audio_track_;
}
void CallClientWithNoVideo::Connect() {
webrtc::MutexLock lock(&pc_mutex_);
std::string send_role("SendConn");
rtc::scoped_refptr<CreateOfferObserver> observer(new rtc::RefCountedObject<CreateOfferObserver>(send_role));
send_connection_->CreateOffer(observer,
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
std::future<std::string> sdp_future = observer->GetSDP();
std::string sdp = sdp_future.get();
std::unique_ptr<webrtc::SessionDescriptionInterface> send_offer(
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, sdp));
send_connection_->SetLocalDescription(new rtc::RefCountedObject<SetLocalSessionDescriptionObserver>("SendConn"),
send_offer.release());
std::unique_ptr<webrtc::SessionDescriptionInterface> recv_send_offer(
webrtc::CreateSessionDescription(webrtc::SdpType::kOffer, sdp));
recv_connection_->SetRemoteDescription(std::move(recv_send_offer),
new rtc::RefCountedObject<SetRemoteSessionDescriptionObserver>("RecvConn"));
rtc::scoped_refptr<CreateOfferObserver> recv_answer_observer(new rtc::RefCountedObject<CreateOfferObserver>("RecvConn"));
recv_connection_->CreateAnswer(recv_answer_observer,
webrtc::PeerConnectionInterface::RTCOfferAnswerOptions());
std::future<std::string> recv_sdp_answer_future = recv_answer_observer->GetSDP();
std::string recv_sdp_answer = recv_sdp_answer_future.get();
std::unique_ptr<webrtc::SessionDescriptionInterface> recv_answer(
webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, recv_sdp_answer));
send_connection_->SetRemoteDescription(std::move(recv_answer),
new rtc::RefCountedObject<SetRemoteSessionDescriptionObserver>("SendConn"));
std::unique_ptr<webrtc::SessionDescriptionInterface> recv_recv_answer(
webrtc::CreateSessionDescription(webrtc::SdpType::kAnswer, recv_sdp_answer));
recv_connection_->SetLocalDescription(new rtc::RefCountedObject<SetLocalSessionDescriptionObserver>("RecvConn"),
recv_recv_answer.release());
send_observer_->SetConnection(recv_connection_);
recv_observer_->SetConnection(send_connection_);
}
void CallClientWithNoVideo::StartLoopCall() {
{
webrtc::MutexLock lock(&pc_mutex_);
if (call_started_) {
RTC_LOG(LS_WARNING) << "Call already started.";
return;
}
call_started_ = true;
}
cricket::AudioOptions audio_options;
audio_source_ = pcf_->CreateAudioSource(audio_options);
local_audio_track_ = pcf_->CreateAudioTrack(kAudioLabel, audio_source_);
CreateConnections();
Connect();
}
void CallClientWithNoVideo::DestroyConnections() {
webrtc::MutexLock lock(&pc_mutex_);
for (const rtc::scoped_refptr<webrtc::RtpTransceiverInterface>& tranceiver :
send_connection_->GetTransceivers()) {
send_connection_->RemoveTrack(tranceiver->sender().get());
}
send_observer_->SetConnection(nullptr);
recv_observer_->SetConnection(nullptr);
send_connection_ = nullptr;
recv_connection_ = nullptr;
local_audio_track_ = nullptr;
audio_source_ = nullptr;
}
void CallClientWithNoVideo::StopLoopCall() {
DestroyConnections();
webrtc::MutexLock lock(&pc_mutex_);
if (!call_started_) {
RTC_LOG(LS_WARNING) << "Call already started.";
return;
}
call_started_ = false;
}
相關(guān)更完整的示例代碼可以在 OpenRTCClient 項(xiàng)目的 examples/loop_connect_ios
下找到官边。
參考文檔
[UIDevice deviceType]: unrecognized selector sent to class 0x23a2421b0
What does the -ObjC linker flag do?
webrtc iOS native : +[UIDevice deviceType]: unrecognized selector sent toclass 0x3aa4b420