WebRTC iOS Native SDK 接入

借助于 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)為由三個部分組成:

  1. PeerConnectionInterface 為中心的核心框架 API。WebRTC 中 PeerConnection 定義 RTC 的 pipeline门扇,它把各模塊組件有機(jī)地連接起來雹有,構(gòu)造完整的 RTC 場景。核心框架 API 既包括 PeerConnection 的 API臼寄,也包括 PeerConnection 的 pipeline 中各個模塊組件的部分接口霸奕。相關(guān)頭文件位于 webrtc/api 目錄下。

  2. 模塊組件 API吉拳。PeerConnection 的 pipeline 中各個模塊組件提供的 API质帅,如 audio processing 提供的 AudioProcessingAudioProcessingBuilder,位于 webrtc/modules/audio_processing/include留攒,用于桌面共享的 API 位于 webrtc/modules/desktop_capture煤惩。

  3. 基礎(chǔ)的實(shí)用程序 API。如在 webrtc/rtc_base 目錄下包含了大量的輔助工具 API稼跳,包括日志盟庞,線程,任務(wù)隊(duì)列汤善,同步原語等等等什猖。

WebRTC 的開發(fā)者一定是非常不希望其它開發(fā)者直接使用 WebRTC 的 C++ API 的票彪,否則 WebRTC 的 C++ API 也不至于設(shè)計(jì)的如此混亂:

  1. 核心框架 API 的大量頭文件包含了各個模塊組件的頭文件以及基礎(chǔ)的實(shí)用程序頭文件。

  2. 核心框架 API 的大量組件依賴于其它部分的接口不狮。

  3. 其他開發(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/androidwebrtc/sdk/objc。WebRTC 的 Objective-C API 主要是對底層 C++ API 的封裝驻仅,但也通過定義 RTCVideoEncoderFactoryRTCVideoDecoderFactory 將系統(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)建一個工程

  1. 打開 Xcode粘优。
Xcode
  1. 選擇 "Create a new Xcode project"仇味。
Xcode 001
  1. 選擇 "iOS" 標(biāo)簽下的 "App"。
Xcode 002
  1. 輸入產(chǎn)品名稱 "Product Name: "雹顺,選擇 "Team: "丹墨,輸入組織標(biāo)識符 "Organization Identifier:","Interface: " 選擇 "Storyboard"嬉愧,語言 "Language: " 選擇 "Objective-C"贩挣,然后點(diǎn)擊 "Next"。為 iOS 工程選擇一個目錄來創(chuàng)建工程目錄没酣,結(jié)束 Xcode 工程創(chuàng)建過程揽惹。
Xcode 003

為 iOS 應(yīng)用工程引入 WebRTC 靜態(tài)庫

  1. WebRTC native SDK 提供的是 C++ 的 API,為了在 iOS 應(yīng)用工程中使用 WebRTC 的 native API四康,需要將引用 WebRTC native API 的源文件后綴名由 .m 修改為 .mm搪搏,這里修改 ViewController.mmain.m 這兩個源文件的后綴名為 .mm

  2. 引入 WebRTC 靜態(tài)庫

在這一步中闪金,我們在 main.mm 中包含一些 WebRTC 的頭文件疯溺,調(diào)用一些基本的 API,并使應(yīng)用程序編譯鏈接成功哎垦,并能在 iPhone 模擬器中運(yùn)行起來囱嫩。

我門在 main.mm 中調(diào)用創(chuàng)建 webrtc::PeerConnectionFactoryInterfacewebrtc::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 PathsLibrary Search Paths 中加入 webrtc 庫文件的路徑以將 webrtc 庫文件的路徑添加庫文件搜索路徑中。如:

Xcode 003

選中 TARGETS 下我們 iOS 應(yīng)用的 loop_connect肉迫,選中 Build Phases验辞,點(diǎn)開 Link Binary With Libraries,然后將 webrtc 的靜態(tài)庫文件拖進(jìn)來喊衫,如:

Xcode 004
  1. 添加 abseil-cpp 的路徑為頭文件搜索路徑

完成上面的配置之后受神,我們通過 Product -> Build 第一次構(gòu)建我們的工程。此時報出如下的編譯錯誤:

Xcode 005

錯誤提示說找不到頭文件 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)工程的頭文件搜索路徑來解決朝墩。

  1. 定義平臺宏

完成上面的配置之后,再次構(gòu)建我們的工程伟姐。這次報出如下的編譯錯誤:

Xcode 006

這次是有幾十個對于用來做網(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_POSIXWEBRTC_WIN 等肛冶。坑爹的地方在于扯键,WEBRTC_POSIXWEBRTC_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_MACWEBRTC_IOS斥杜。對于這個問題,我們需要通過配置工程定義這幾個預(yù)處理器宏來解決沥匈。選擇 Build Settings --> Apple Clang - Preprocessing --> Preprocessor Macros蔗喂,添加這幾個宏,如下圖:

Xcode 007

這里遇到的編譯錯誤高帖,屬于 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蹈矮。

  1. 添加對 iOS 系統(tǒng) framework 的依賴

解決了上面的問題之后,再次編譯我們的工程鸣驱,此時將報出大量符號找不到的鏈接錯誤泛鸟,如下圖:

Xcode 008

在 WebRTC 中使用了大量的 iOS 系統(tǒng)庫的符號,還需要添加對于相關(guān)系統(tǒng)庫的依賴踊东。報錯中的這些符號需要添加對 AudioToolbox.frameworkAVFoundation.framework 這兩個 framework 的依賴谈况。類似于前面添加 webrtc 靜態(tài)庫那樣,添加對這兩個 framework 的依賴递胧。

  1. 引入更多對 iOS 系統(tǒng) framework 的依賴

經(jīng)過了前面的操作碑韵,再次編譯我們的工程,此時 iOS App 工程可以成功編譯缎脾。我們在模擬器上運(yùn)行我們的應(yīng)用祝闻。此時很快就報出了 unrecognized selector sent to . . . 的錯誤,如下圖:

Xcode 008

這與 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豁遭,我們再次編譯時叭喜,報出了更多的符號找不到的鏈接錯誤,如下圖:

Xcode 009

這需要我們添加更多對 iOS 系統(tǒng) framework 的依賴蓖谢。關(guān)于這些 iOS 系統(tǒng) framework 的線索捂蕴,可以在 webrtc/sdk/BUILD.gn 找到一部分俺夕。具體來說贡茅,這些需要依賴的 iOS 系統(tǒng) framework 還包括這些:Network.framework晦款,CoreVideo.framework梨树,VideoToolbox.frameworkCoreMedia.framework冻辩,GLKit.framework圣蝎,OpenGLES.framework娜膘,QuartzCore.framework腕够。

  1. 引用 Objective-C 接口

經(jīng)過上面的配置级乍,我們的集成了 WebRTC iOS native SDK 的 iOS App 已經(jīng)可以正常編譯運(yùn)行起來了。

要想在我們的 iOS App 中引用 WebRTC 的 Objective-C 接口帚湘,如 RTCDefaultVideoEncoderFactoryRTCDefaultVideoDecoderFactory玫荣,還需要給我們的 iOS App 工程的頭文件搜索路徑加上 webrtc/sdk/objcwebrtc/sdk/objc/base 這兩個目錄。

  1. 實(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

Technical Q&A QA1490

What does the -ObjC linker flag do?

webrtc iOS native : +[UIDevice deviceType]: unrecognized selector sent toclass 0x3aa4b420

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市外遇,隨后出現(xiàn)的幾起案子注簿,更是在濱河造成了極大的恐慌,老刑警劉巖跳仿,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诡渴,死亡現(xiàn)場離奇詭異,居然都是意外死亡菲语,警方通過查閱死者的電腦和手機(jī)妄辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門惑灵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人眼耀,你說我怎么就攤上這事英支。” “怎么了哮伟?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵干花,是天一觀的道長。 經(jīng)常有香客問我澈吨,道長把敢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任谅辣,我火速辦了婚禮修赞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘桑阶。我一直安慰自己柏副,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布蚣录。 她就那樣靜靜地躺著割择,像睡著了一般。 火紅的嫁衣襯著肌膚如雪萎河。 梳的紋絲不亂的頭發(fā)上荔泳,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音虐杯,去河邊找鬼玛歌。 笑死,一個胖子當(dāng)著我的面吹牛擎椰,可吹牛的內(nèi)容都是我干的支子。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼达舒,長吁一口氣:“原來是場噩夢啊……” “哼值朋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巩搏,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昨登,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后贯底,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體篙骡,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了糯俗。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尿褪。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖得湘,靈堂內(nèi)的尸體忽然破棺而出杖玲,到底是詐尸還是另有隱情,我是刑警寧澤淘正,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布摆马,位于F島的核電站,受9級特大地震影響鸿吆,放射性物質(zhì)發(fā)生泄漏囤采。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一惩淳、第九天 我趴在偏房一處隱蔽的房頂上張望蕉毯。 院中可真熱鬧,春花似錦思犁、人聲如沸代虾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽棉磨。三九已至,卻和暖如春学辱,著一層夾襖步出監(jiān)牢的瞬間乘瓤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工策泣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衙傀,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓着降,卻偏偏與公主長得像差油,于是被迫代替她去往敵國和親拗军。 傳聞我的和親對象是個殘疾皇子任洞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容