You need to enable JavaScript to run this app.
文档中心
实时音视频

实时音视频

复制全文
下载 pdf
实现音视频通话
iOS (Swift)
复制全文
下载 pdf
iOS (Swift)
本文介绍如何集成火山引擎 RTC SDK,并实现实时音视频通话。根据如下步骤操作,即可从 0 开始构建一个简单的音视频通话应用。​
你也可以参考示例项目,了解更完整的项目实现。​
前提条件​
在开始集成 RTC SDK 前,请确保满足以下要求:​
  • macOS 开发电脑,且可以正常访问互联网​
  • Xcode 14.1 或以上版本​
  • Apple 开发者账号​
  • iOS 11.0 或以上版本真机设备,且可以正常访问互联网​
创建和配置项目​
新建项目​
  1. 打开 Xcode,单击 Create New Project... 新建项目。​
  1. 在项目模板页选择 iOS > App,单击 Next。​
  1. 在项目配置页填写 Product Name(本文以 RTCDemo 为例)、Team、Organization Identifier。Interface 选择 Storyboard,Language 选择 Swift。单击 Next。​
说明​
如果你尚未登录 Apple 账户,单击 Add account… 并按照提示登录。完成后即可选择你的 Apple 账户作为开发团队。​
  • ​图片​​
  1. 选择项目存储位置,单击 Create。​
配置签名​
选中项目,进入 TARGETS > RTCDemo > Signing & Capabilities,勾选 Automatically manage signing。​
​图片​​
说明​
理论上来讲,Bundle Identifier 可以任意选择。不过对于个人开发者账户而言,一周只能创建最多 7个 Bundle Identifier。因此请注意管理已经创建的 Bundle Identifier。​
配置权限​
切换到 Info 页面,单击 + 添加音频和视频设备权限。​
  • 添加 Privacy - Microphone Usage Description,并填入使用麦克风的原因。​
  • 添加 Privacy - Camera Usage Description,并填入使用摄像头的原因。​
​图片​​
集成 SDK​
通过 CocoaPods 集成​
  1. 在终端窗口执行如下命令安装 CocoaPods。​
  • ​
    sudo gem install cocoapods​
    ​
  1. 进入项目根目录,执行如下命令,创建 Podfile 文件。​
  • ​
    pod init​
    ​
  1. 打开 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​
    ​
  1. 执行 pod install 命令安装 VolcEngineRTC 相关库。安装成功后,项目文件夹中出现 RTCDemo.xcworkspace 文件,使用 Xcode 打开该文件进行后续操作。​
说明​
执行 pod install 后,请务必打开 .xcworkspace 文件,而非 .xcodeproj 文件。这是因为如果以 .xcodeproj 文件打开项目的话,刚才使用 pod 安装的依赖库不会被打开,整个项目因而无法编译。​
手动集成​
  1. 下载并解压火山引擎 RTC SDK 文件。​
  1. 将解压后目录中的 VolcEngineRTC.xcframework、 RealXBase.xcframework 和 RTCFFmpeg.xcframework 拖入到项目中,勾选 Copy items if needed。​
  • ​图片​​
  1. 选中项目,进入 TARGETS > RTCDemo > General,在 Frameworks, Libraries, and Embedded Content 中将 VolcEngineRTC.xcframework、 RealXBase.xcframework 和 RTCFFmpeg.xcframework 的属性设置为 Embed & Sign。​
添加隐私清单文件​
应苹果公司的要求,你的 iOS App 如需要上线 App Store,必须准确描述 App 本身和集成的第三方 SDK 使用指定范围内系统接口的原因。自 2024 年 5 月 1 日起,如果你未提供相关描述,你的 App 将无法通过 App Store Connect 的审核。详见 Describing use of required reason API。​
如果你在 App 中集成了 3.58 及之前版本的 RTC SDK,你必须添加相关说明:​
  • 如果你的 App 中没有隐私清单文件,你可以直接将 RTC SDK 的隐私清单文件添加项目根目录下。如图:​
  • ​图片​​
  • 如果你的 App 中已有隐私清单文件,请将 RTC SDK 的隐私清单文件中的内容添加到 App 的隐私清单文件中。​
获取 RTC SDK 的隐私清单文件:​
​
​
PrivacyInfo.xcprivacy.zip
​
​
实现音视频通话​
说明​
本章节将先向你提供 API 调用时序图和完整的实现代码,再对具体的实现步骤展开介绍。​
时序图​
下图为使用火山引擎 RTC SDK 实现基础音视频通话的 API 调用时序图。​
​图片​​
完整示例代码​
将以下示例代码替换 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, ByteRTCEngineDelegate, ByteRTCRoomDelegate {​
​
var rtcEngine: ByteRTCEngine?​
var rtcRoom: ByteRTCRoom?​
​
override func viewDidLoad() {​
super.viewDidLoad()​
self.createUI()​
self.buildRTCEngine()​
self.bindLocalRenderView()​
}​
​
deinit {​
// 销毁房间​
self.rtcRoom?.leave()​
self.rtcRoom?.destroy()​
self.rtcRoom = nil​
​
// 销毁引擎​
ByteRTCEngine.destroyRTCEngine()​
self.rtcEngine = nil​
}​
​
// MARK: Private method​
​
@objc func joinRoom() {​
joinButton.isSelected = !joinButton.isSelected​
​
if joinButton.isSelected {​
joinButton.setTitle("离开房间", for: .normal) // 在房间中时显示 “离开房间”​
​
// 加入房间​
self.rtcRoom = self.rtcEngine?.createRTCRoom(roomId)​
self.rtcRoom?.delegate = self​
let userInfo = ByteRTCUserInfo.init()​
userInfo.userId = userId​
let roomCfg = ByteRTCRoomConfig.init()​
roomCfg.isPublishAudio = true​
roomCfg.isPublishVideo = true​
roomCfg.isAutoSubscribeAudio = true​
roomCfg.isAutoSubscribeVideo = true​
self.rtcRoom?.joinRoom(token, userInfo: userInfo, userVisibility: true, roomConfig: roomCfg)​
}​
​
else {​
joinButton.setTitle("加入房间", for: .normal) // 离开房间中后显示 “加入房间”​
self.rtcRoom?.leave()​
}​
}​
​
func buildRTCEngine() {​
// 创建引擎​
let engineCfg = ByteRTCEngineConfig.init()​
engineCfg.appID = kAppID​
engineCfg.parameters = [:]​
self.rtcEngine = ByteRTCEngine.createRTCEngine(engineCfg, delegate: self)​
// 开启本地音视频采集​
self.rtcEngine?.startVideoCapture()​
self.rtcEngine?.startAudioCapture()​
}​
​
func bindLocalRenderView() {​
// 设置本地渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = self.localView​
canvas.renderMode = .hidden​
self.rtcEngine?.setLocalVideoCanvas(withCanvas: canvas);​
}​
​
func bindRemoteRenderView(streamId: String) { // streamID:对方流的ID​
// 设置远端用户视频渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = remoteView​
canvas.renderMode = .hidden​
self.rtcEngine?.setRemoteVideoCanvas(streamId, withCanvas: canvas)​
}​
​
func removeRemoteRenderView(streamId: String) { // streamID:对方流的ID​
// 移除远端用户视频渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = nil // 置为空​
canvas.renderMode = .hidden​
​
self.rtcEngine?.setRemoteVideoCanvas(streamId, 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: ByteRTCEngineDelegate & ByteRTCRoomDelegate 协议所需要的回调函数​
​
// 进房状态​
func rtcRoom(_ rtcRoom: ByteRTCRoom, onRoomStateChanged roomId: String, withUid uid: String, state: Int, extraInfo: String) {​
if state == 0 {​
print("Join Room Success")​
}​
}​
​
// 下面两个方法为:远端用户发流、远端用户销毁流(注:销毁流的部分已经集成于func rtcRoom(rtcRoom, onUserPublishStreamVideo streamId, info, isPublish)。设置isPublish = false即为销毁流)​
// onUserPublishStreamVideo方法负责控制视频流,另一个onUserPublishStreamAudio方法负责控制音频流​
func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserPublishStreamVideo streamId: String, info: ByteRTCStreamInfo, isPublish: Bool) {​
if isPublish {​
DispatchQueue.main.async {​
self.bindRemoteRenderView(streamId: streamId)​
}​
} else {​
DispatchQueue.main.async {​
self.removeRemoteRenderView(streamId: streamId)​
}​
}​
}​
func rtcRoom(_ rtcRoom: ByteRTCRoom, onUserPublishStreamAudio streamId: String, info: ByteRTCStreamInfo, isPublish: Bool) {​
​
}​
}​
​
实现步骤详解​
引入头文件​
在 ViewController.swift 引入以下头文件。​
​
import UIKit​
import VolcEngineRTC​
​
设置进房参数​
将 ViewController.swift 中的 roomId、userId、kAppID、token 替换为你在控制台上生成临时 Token 时所使用的房间 ID 和用户 ID,以及获取到的 AppID 和临时 Token。​
​
let kAppID = "" // 填写 appId​
let roomId = "" // 填写房间号​
let userId = "" // 填写 userId​
let token = "" // 填写临时 token​
​
创建用户界面​
这里为了演示,我们创建两个 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​
}()​
​
创建引擎​
调用 createRTCEngine 创建引擎,所有 RTC 相关的 API 调用都要在创建引擎之后。​
​
let engineCfg = ByteRTCEngineConfig.init()​
engineCfg.appID = kAppID​
engineCfg.parameters = [:]​
self.rtcEngine = ByteRTCEngine.createRTCEngine(engineCfg, delegate: self)​
​
采集音视频​
创建引擎后,调用 startVideoCapture 开启视频采集,调用 startAudioCapture 开启音频采集。​
​
self.rtcEngine?.startVideoCapture()​
self.rtcEngine?.startAudioCapture()​
​
创建并加入房间​
调用 createRTCRoom 创建 RTC 房间,所有和房间相关的 API 都在 ByteRTCRoom 类。​
joinRoom 表示进房,进房状态可以通过 ByteRTCRoomDelegate 中 onRoomStateChanged 回调。​
​
self.rtcRoom = self.rtcEngine?.createRTCRoom(roomId)​
self.rtcRoom?.delegate = self​
let userInfo = ByteRTCUserInfo.init()​
userInfo.userId = userId​
let roomCfg = ByteRTCRoomConfig.init()​
roomCfg.isPublishAudio = true​
roomCfg.isPublishVideo = true​
roomCfg.isAutoSubscribeAudio = true​
roomCfg.isAutoSubscribeVideo = true​
self.rtcRoom?.joinRoom(token, userInfo: userInfo, userVisibility: true, roomConfig: roomCfg)​
​
渲染本端视频流​
调用 setLocalVideoCanvas 设置本端渲染窗口。​
​
func bindLocalRenderView() {​
// 设置本地渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = self.localView​
canvas.renderMode = .hidden​
​
self.rtcEngine?.setLocalVideoCanvas(.main, withCanvas: canvas);​
}​
​
渲染远端视频流​
在收到远端用户的 rtcRoom:onUserPublishStreamVideo:info:isPublish: 或 onFirstRemoteVideoFrameDecoded 回调后,你需要调用 setRemoteVideoCanvas:withCanvas: 设置远端视图以在通话中查看远端视频。建议您在 onFirstRemoteVideoFrameDecoded 回调中设置视频渲染,确保视频渲染在视频解码完成后进行,以避免卡顿。​
​
func bindRemoteRenderView(streamId: String) { // streamID:对方流的ID​
// 设置远端用户视频渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = remoteView​
canvas.renderMode = .hidden​
self.rtcEngine?.setRemoteVideoCanvas(streamId, withCanvas: canvas)​
}​
​
停止渲染远端视频流​
在收到远端用户的 rtcRoom:onUserPublishStreamVideo:info:isPublish: 回调并确认其中的 isPublish 参数为 false 后,你需要停止渲染远端视频。​
​
func removeRemoteRenderView(streamId: String) { // streamID:对方流的ID​
// 移除远端用户视频渲染视图​
let canvas = ByteRTCVideoCanvas.init()​
canvas.view = nil // 置为空​
canvas.renderMode = .hidden​
​
self.rtcEngine?.setRemoteVideoCanvas(streamId, withCanvas: canvas)​
}​
​
停止音视频通话​
以下代码在 ViewController.swift 的析构函数中执行。调用 leave 离开房间,destroy 销毁房间;调用 destroyRTCEngine 销毁 RTC 引擎。​
​
deinit {​
// 销毁房间​
self.rtcRoom?.leave()​
self.rtcRoom?.destroy()​
self.rtcRoom = nil​
​
// 销毁引擎​
ByteRTCEngine.destroyRTCEngine()​
self.rtcEngine = nil​
}​
​
编译与运行​
  1. 在 Xcode 中连接并选择你的 iOS 真机设备,单击 Xcode 窗口左上角的运行按钮(或使用 Command ⌘ + R 快捷键)。​
说明​
如果你尚未信任开发者,请根据 Xcode 提示,在 iOS 设备上打开设置,选择通用 > VPN 与设备管理,在开发者 APP 中单击信任开发者。​
  1. 在 iOS 设备上打开 Demo 应用时,在弹窗中选择开启摄像头和麦克风权限。​
  1. (可选)在第二台设备上使用相同的 AppID 和 RoomID,更换 UserID 并生成新的临时 Token,即可加入同一个房间体验双端通话。​
双端通话效果如下:​
​图片​​
#{style="margin: auto"}#​
后续步骤​
在实现音视频通话后,如遇无声音、无画面、视频卡顿等问题时,您可以使用诊断工具快速排查和定位异常房间及用户,并获取异常根因分析、处理建议、分析报告等。​
常见问题​
  • 使用模拟器编译报错 No such module 'VolcEngineRTC'?​
  • 解决方案:如果你使用的是搭载 Apple 芯片的 Mac 电脑,该问题可能是编译的架构(ARM64)和模拟器的架构(x86_64)不匹配导致的,请使用真机编译。​
  • Xcode 15 编译报错 Sandbox: rsync.samba(xxxxx) deny(1)?​
  • 解决方案:选中项目,进入 TARGETS > 项目名称 > Build Settings,在 Build Options 中将 User Script Sandboxing 修改为 No。​
  • ​图片​​
​
最近更新时间:2026.09.21 16:42:02
这个页面对您有帮助吗?
有用
有用
无用
无用