本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。
你也可以参考示例项目,了解更完整的项目实现。

前提条件

在开始集成 RTC SDK 前,请确保满足以下要求:

创建和配置项目
新建项目
  1. 打开 Xcode,单击 Create New Project... 新建项目。

  2. 在项目模板页选择 iOS > App,单击 Next。

  3. 在项目配置页填写 Product Name(本文以 RTCDemo 为例)、Team、Organization Identifier。Interface 选择 Storyboard,Language 选择 Swift。单击 Next。

    说明

    如果你尚未登录 Apple 账户,单击 Add account… 并按照提示登录。完成后即可选择你的 Apple 账户作为开发团队。

  4. 选择项目存储位置,单击 Create。

配置签名

选中项目,进入 TARGETS > RTCDemo > Signing & Capabilities,勾选 Automatically manage signing。

配置权限

切换到 Info 页面,单击 + 添加音频和视频设备权限。

集成 SDK
通过 CocoaPods 集成
  1. 在终端窗口执行如下命令安装 CocoaPods。

    sudo gem install cocoapods
    
    bash
  2. 进入项目根目录,执行如下命令,创建 Podfile 文件。

    pod init
    
    bash
  3. 打开 Podfile 文件,替换为如下内容并保存。

    说明

    • 如果你的项目名称不是 RTCDemo,请注意替换。
    • 你需要将 '3.x.y.z' 替换为具体的版本号,最新版本号请参看下载 SDK。
    source 'https://github.com/volcengine/volcengine-specs.git'
    target 'RTCDemo' do
      pod 'VolcEngineRTC', '3.x.y.z'
    
    end
    
    ruby
  4. 执行 pod install 命令安装 VolcEngineRTC 相关库。安装成功后,项目文件夹中出现 RTCDemo.xcworkspace 文件,使用 Xcode 打开该文件进行后续操作。

手动集成
  1. 下载并解压火山引擎 RTC SDK 文件。

  2. 将解压后目录中的 VolcEngineRTC.xcframework 和 RealXBase.xcframework 拖入到项目中,勾选 Copy items if needed。

  3. 选中项目,进入 TARGETS > RTCDemo > General,在 Frameworks, Libraries, and Embedded Content 中将 VolcEngineRTC.xcframework 和 RealXBase.xcframework 的属性设置为 Embed & Sign。

实现音视频通话

说明

本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。

时序图

下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。

alt

完整示例代码

将以下示例代码替换 ViewController.swift 文件中的全部内容,连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键),即可快速实现音视频通话。

说明

你需要将代码中的 roomId、userId、kAppID、token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。

import UIKit
import VolcEngineRTC
let kAppID = "" // 填写 appId
let roomId = "" // 填写房间号
let userId = "" // 填写 userId
let token =  "" // 填写临时 token
class ViewController: UIViewController, ByteRTCVideoDelegate, ByteRTCRoomDelegate {
        var rtcVideo: ByteRTCVideo?
    var rtcRoom: ByteRTCRoom?
    override func viewDidLoad() {
        super.viewDidLoad()
                self.createUI()
        self.buildRTCEngine()
        self.bindLocalRenderView()
    }
        deinit {
        // 销毁房间
        self.rtcRoom?.leaveRoom()
        self.rtcRoom?.destroy()
        self.rtcRoom = nil
                // 销毁引擎
        ByteRTCVideo.destroyRTCVideo()
        self.rtcVideo = nil
    }
        // MARK: Private method
    @objc func joinRoom()  {
        joinButton.isSelected = !joinButton.isSelected
                if joinButton.isSelected {
            joinButton.setTitle("离开房间", for: .normal)
                        // 加入房间
            self.rtcRoom = self.rtcVideo?.createRTCRoom(roomId)
            self.rtcRoom?.delegate = self
                        let userInfo = ByteRTCUserInfo.init()
            userInfo.userId = userId
                        let roomCfg = ByteRTCRoomConfig.init()
            roomCfg.isAutoPublish = true
            roomCfg.isAutoSubscribeAudio = true
            roomCfg.isAutoSubscribeVideo = true
                        self.rtcRoom?.joinRoom(token, userInfo: userInfo, roomConfig: roomCfg)
        }
        else {
            joinButton.setTitle("加入房间", for: .normal)
            self.rtcRoom?.leaveRoom()
        }
    }
        func buildRTCEngine() {
        // 创建引擎
        self.rtcVideo = ByteRTCVideo.createRTCVideo(kAppID, delegate: self, parameters: [:])
                // 开启本地音视频采集
        self.rtcVideo?.startVideoCapture()
        self.rtcVideo?.startAudioCapture()
    }
            func bindLocalRenderView() {
        // 设置本地渲染视图
        let canvas = ByteRTCVideoCanvas.init()
        canvas.view = self.localView
        canvas.renderMode = .hidden
                self.rtcVideo?.setLocalVideoCanvas(.main, withCanvas: canvas);
    }
        func bindRemoteRenderView(roomId: String, userId: String) {
        // 设置远端用户视频渲染视图
        let canvas = ByteRTCVideoCanvas.init()
        canvas.view = remoteView
        canvas.renderMode = .hidden
                let streamKey = ByteRTCRemoteStreamKey.init()
        streamKey.userId = userId;
        streamKey.roomId = roomId;
        streamKey.streamIndex = .main;
                self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas)
    }
        func removeRemoteRenderView(roomId: String, userId: String) {
        // 移除远端用户视频渲染视图
        let canvas = ByteRTCVideoCanvas.init()
        canvas.view = nil // 置为空
        canvas.renderMode = .hidden
                let streamKey = ByteRTCRemoteStreamKey.init()
        streamKey.userId = userId;
        streamKey.roomId = roomId;
        streamKey.streamIndex = .main;
                self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas)
    }
     // 添加视图
    func createUI() -> Void {
        let width = self.view.bounds.size.width*0.5
        let height = self.view.bounds.size.height*0.5
                // 本地预览
        localView.frame = CGRect(x: 0, y: 0, width: width, height: height)
        self.view.addSubview(localView)
                // 远端预览
        remoteView.frame = CGRect(x: width, y: 0, width: width, height: height)
        self.view.addSubview(remoteView)
                // 加入房间按钮
        joinButton.frame = CGRect(x: 10, y: height + 30, width: width*2 - 20, height: 44)
        self.view.addSubview(joinButton)
    }
        // MARK: Lazy load
    lazy var joinButton: UIButton = {
        let button = UIButton(type: .custom)
        button.backgroundColor = .blue
        button.setTitle("加入房间", for: .normal)
        button.addTarget(self, action: #selector(joinRoom), for: .touchUpInside)
        return button
    }()
        lazy var remoteView: UIView = {
        let view = UIView.init()
        view.backgroundColor = .lightGray
        return view
    }()
        lazy var localView: UIView = {
        let view = UIView.init()
        view.backgroundColor = .lightGray
        return view
    }()
        // MARK: ByteRTCVideoDelegate & ByteRTCRoomDelegate
    //进房状态
    func rtcRoom(_ rtcRoom: ByteRTCRoom, onRoomStateChanged roomId: String, withUid uid: String, state: Int, extraInfo: String) {
        
    }
        // 远端用户发流
    func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserPublishStream userId: String, type: ByteRTCMediaStreamType) {
        if type == .video || type == .both {
            DispatchQueue.main.async {
                self .bindRemoteRenderView(roomId: rtcRoom.getId(), userId: userId)
            }
        }
    }
        func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserUnpublishStream userId: String, type: ByteRTCMediaStreamType, reason: ByteRTCStreamRemoveReason) {
        if type == .video || type == .both {
            DispatchQueue.main.async {
                self .removeRemoteRenderView(roomId: rtcRoom.getId(), userId: userId)
            }
        }
    }
}
swift
实现步骤详解
引入头文件

在 ViewController.swift 引入以下头文件。

import UIKit
import VolcEngineRTC
swift
设置进房参数

将 ViewController.swift 中的 roomId、userId、kAppID、token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。

let kAppID = "" // 填写 appId
let roomId = "" // 填写房间号
let userId = "" // 填写 userId
let token =  "" // 填写临时 token
swift
创建用户界面

这里为了演示,我们创建两个 View 分别用于渲染本端视频和远端视频。界面左上角显示本端视频,右上角显示远端视频,下方为加入房间的按钮。

// 添加视图
func createUI() -> Void {
    let width = self.view.bounds.size.width*0.5
    let height = self.view.bounds.size.height*0.5
    
    // 本地预览
    localView.frame = CGRect(x: 0, y: 0, width: width, height: height)
    self.view.addSubview(localView)
    
    // 远端预览
    remoteView.frame = CGRect(x: width, y: 0, width: width, height: height)
    self.view.addSubview(remoteView)
    
    // 加入房间按钮
    joinButton.frame = CGRect(x: 10, y: height + 30, width: width*2 - 20, height: 44)
    self.view.addSubview(joinButton)
}

// MARK: Lazy load
lazy var joinButton: UIButton = {
    let button = UIButton(type: .custom)
    button.backgroundColor = .blue
    button.setTitle("加入房间", for: .normal)
    button.addTarget(self, action: #selector(joinRoom), for: .touchUpInside)
    return button
}()

lazy var remoteView: UIView = {
    let view = UIView.init()
    view.backgroundColor = .lightGray
    return view
}()

lazy var localView: UIView = {
    let view = UIView.init()
    view.backgroundColor = .lightGray
    return view
}()
swift
创建引擎

调用 createRTCVideo 创建引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。

self.rtcVideo = ByteRTCVideo.createRTCVideo(kAppID, delegate: self, parameters: [:])
swift
采集音视频

创建引擎后,调用 startVideoCapture 开启视频采集,调用 startAudioCapture 开启音频采集。

self.rtcVideo?.startVideoCapture()
self.rtcVideo?.startAudioCapture()
swift
创建并加入房间

调用 createRTCRoom 创建 RTC 房间,所有和房间相关的 API 都在 ByteRTCRoom 类。
joinRoom 表示进房,进房状态可以通过 ByteRTCRoomDelegate 中 onRoomStateChanged 回调。

self.rtcRoom = self.rtcVideo?.createRTCRoom(roomId)
self.rtcRoom?.delegate = self
let userInfo = ByteRTCUserInfo.init()
userInfo.userId = userId
let roomCfg = ByteRTCRoomConfig.init()
roomCfg.isAutoPublish = true
roomCfg.isAutoSubscribeAudio = true
roomCfg.isAutoSubscribeVideo = true
self.rtcRoom?.joinRoom(token, userInfo: userInfo, roomConfig: roomCfg)
swift
渲染本端视频流

调用 setLocalVideoCanvas 设置本端渲染窗口。

func bindLocalRenderView() {
    // 设置本地渲染视图
    let canvas = ByteRTCVideoCanvas.init()
    canvas.view = self.localView
    canvas.renderMode = .hidden
    
    self.rtcVideo?.setLocalVideoCanvas(.main, withCanvas: canvas);
}
swift
渲染远端视频流

在收到远端用户的 onUserPublishStream 回调后,你需要调用 setRemoteVideoCanvas 设置远端视图以在通话中查看远端视频。

func bindRemoteRenderView(roomId: String, userId: String) {
    // 设置远端用户视频渲染视图
    let canvas = ByteRTCVideoCanvas.init()
    canvas.view = remoteView
    canvas.renderMode = .hidden
    
    let streamKey = ByteRTCRemoteStreamKey.init()
    streamKey.userId = userId;
    streamKey.roomId = roomId;
    streamKey.streamIndex = .main;
    
    self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas)
}
swift
停止渲染远端视频流

在收到远端用户的 onUserUnpublishStream 回调后,你需要停止渲染远端视频。

func removeRemoteRenderView(roomId: String, userId: String) {
    // 移除远端用户视频渲染视图
    let canvas = ByteRTCVideoCanvas.init()
    canvas.view = nil // 置为空
    canvas.renderMode = .hidden
    
    let streamKey = ByteRTCRemoteStreamKey.init()
    streamKey.userId = userId;
    streamKey.roomId = roomId;
    streamKey.streamIndex = .main;
    
    self.rtcVideo?.setRemoteVideoCanvas(streamKey, withCanvas: canvas)
}
swift
停止音视频通话

以下代码在 ViewController.swift 的析构函数中执行。调用 leaveRoom 离开房间,destroy 销毁房间;调用 destroyRTCVideo 销毁 RTC 引擎。

deinit {
    // 销毁房间
    self.rtcRoom?.leaveRoom()
    self.rtcRoom?.destroy()
    self.rtcRoom = nil
    
    // 销毁引擎
    ByteRTCVideo.destroyRTCVideo()
    self.rtcVideo = nil
}
swift
编译与运行
  1. 在 Xcode 中连接并选择你的 iOS 真机设备,单击 XCode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键)。

    说明

    如果你尚未信任开发者,请根据 Xcode 提示,在 iOS 设备上打开设置,选择通用 > VPN 与设备管理,在开发者 APP 中单击信任开发者。

  2. 在 iOS 设备上打开 Demo 应用时,在弹窗中选择开启摄像头和麦克风权限。

  3. (可选)在第二台设备上使用相同的 AppID 和 RoomID,更换 UserID 并生成新的临时 Token,即可加入同一个房间体验双端通话。

双端通话效果如下:

常见问题